Docker 的出现,改变了软件的交付方式,使得开发、测试、运维都能在一个完全统一的环境中进行。在服务容器化的网络中,需要添加服务发现功能。每个服务可能对应多个示例以容器运行在多个机器上,并且提供自动注册和失败检测机制。
目前服务发现已经有很多成熟的解决方案,例如 Spring Cloud中的 Eureka 注册中心,Hystrix 断路器,zuul 服务网关等全家桶。但是需要依赖原生 Java 语言支持。另一个选择同样是 Spring 中的 Sidecar。我们这里介绍一个相比较轻量化的服务发现平台,可以通过一行代码不写,只需要配置就可以搭建服务发现集群。唯一的要求就是先将服务docker化。
总结需求如下:
- 先将服务使用 docker 部署。每个服务对应一个或多个容器。
- 多个机器上具有相同 name 启动参数的容器属于同一个服务。
- 容器的启停需要有统一的机制注册到存储并且在集群里同步。
- 为了实现外部的域名访问,申请一个前缀 FQDN。如只是内部访问,可以搭建 DNS Server 或者配置 resolve 也可以。
- 提供统一的 UI 界面对集群提供的服务进行查询。
Consul 是一个开源的提供服务发现和KV键值存储的分布式框架,支持负载均衡,允许和 Kubernetes 集成,可以跨 物理机,虚拟机或是 Kubernetes 完成服务网格的部署。Consul下载地址在 https://www.consul.io/downloads.html。Consul 只是一个单可执行文件,类似 docker 提供了服务端和命令行工具二合一的使用方式。直接放在
/usr/local/bin
目录中或者任意可执行路径中就可以了。
参数说明
-server - Serve模式,不带该参数时默认是Agent模式
-bootstrap-expect - Server数量,加上这个参数时,服务器启动到指定数量集群才启动
-data-dir - 数据目录
-config-dir - 配置文件目录
-node - Node名称
-bind - 集群通讯地址,取本机IP地址
-client:consul绑定在哪个client地址上,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1
-ui - 启动web ui
常用命令
查看成员
consul members
加入到集群
consul join <集群节点IP>
退出集群
如果consul通过命令行启动,ctl+C就退出集群,如果是以服务的方式启动,则停止服务就退出了集群
consul-cli的用法说明
consul-cli是consul的HTTP接口的命令行版本,提供了一些便捷的命令行工具,用于对consul中注册的服务进行操作。详情参考 https://github.com/mantl/consul-cli
consul-cli agent force-leave skydns-100: 强制一个节点离线
consul-cli status leader:查看当前集群中的leader
consul-cli catalog service mongo-rs0:检查一个服务的状态
consul-cli service deregister consul-dc1-node3:mongo:27017:反注册一个服务
服务的注册
有几种注册服务的方式
- 通过配置文件
echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' > /etc/consul.d/web.json
可以通过这种方式注册一个测试服务。
通过这种方式注册的服务,在consul启动的时候会自动获取 config-dir 目录的内容进行注册了。 - 直接通过HTTP请求
curl -XPUT
127.0.0.1:8500/v1/agent/service/register
-d '{
"ID": "simple_instance_1",
"Name":"simple",
"Port": 8000,
"tags": ["tag"]
}'
无论是哪种注册方式,随后可以通过 UI 来查看服务的状态http://127.0.0.1:8500/
上面两种方式并没有真正自动发现一个服务,而只是一个注册中心应该有的功能。要做到真正意义上的服务自动发现,就需要有一个机制可以监听服务的生命周期并且自动完成注册动作。而我们知道, dockerd 提供了 http API 来可以让外部程序获取或者管理容器,这就使得自动化服务发现成为可能。
Registrator简介
Registrator 通过 Docker 提供的API,监听Docker 容器的启动、停止等状态变化,并且可以利用 inspect API 获取容器提供的服务,包括端口,IP地址等。在本文描述中,可以把服务看做任何监听一个端口的东西。Registrator 发现服务之后,将服务相关的信息注册到 Consul 或者 etcd 等分布式数据库中。官网地址在http://gliderlabs.github.io/registrator/latest/user/quickstart/
更可喜的是 Registrator 可以以 docker 方式启动, 以注册到 Consul 为例,下面的例子启动一个 Registrator 服务, net 参数为 host 表示使用本机的网络配置,以监听本机的端口。使用 volume 参数获取 dockerd 工作的端口。同时将注册的目标地址这里是 Consul 集群的地址,作为启动参数传入。Registrator 会判断这个参数的 Schema 来决定发送的数据协议。
docker run -d --name=registrator --net=host --volume=/var/run/docker.sock:/tmp/docker.sock --restart always --log-opt max-size=50m gliderlabs/registrator:latest consul://localhost:8500
到此已经解决了服务注册的问题,而且随着容器的启动,在 Consul 的 Web-UI 界面上可以看到新添加的服务地址。但是对于服务使用方来说,需要每次查询服务列表才能知道对应服务的地址。如果将每个服务在每个机器上都部署在相同端口上,则就失去了 Consul 的意义。是否有方法可以让容器的 IP 和端口可以自由变化,在 Registrator 发现并注册后,当我们访问 Consul 集群中任何一台机器时,可以重定向到真正的服务地址上。
当然有啊,就是使用 Nginx 转发。因此就需要解决每次服务发生变化时,对 nginx 自动进行配置。
Consul Template 可以实时查询 Consul 分布式数据库中注册的服务,并根据这些服务的元信息按照模板格式更新 Nginx 的路由设置,如果有任何变化,则重新启动Nginx服务。当然 Consul Template 可以更新的模板、重新加载的服务不仅仅包括Nginx,还可以是任何其他的服务。
在启动 Consul 的时候加上如下参数指定 consul template 使用的配置文件
-config=/opt/consul/consul-template.conf
这个配置文件中需要指定Consule Template所更新的模板(源),模板更新之后的目标路径,模板更新之后执行的命令
模板的格式请参考 consul template 主页了解关于配置文件的格式以及模板语言的语法
https://github.com/hashicorp/consul-template
https://www.hashicorp.com/blog/introducing-consul-template/
结论:
以上内容连在一起,
- Consul提供了个高可用强一致性的分布式key-value数据库,并提供健康检查、DNS等功能。
- Registrator自动发现本机上通过Docker容器提供的服务,注册到Consul数据库中
- Consul template则自动根据 Consul 数据库注册的内容更新Nginx的配置文件、路由设置并自动重启Nginx
- enable、start consul服务,组建集群
- consul集群的每个节点上启动registrator container
- consul集群的每个节点上enable、start consule template服务
- 接下来就是启动任意docker容器提供web service了。整个过程不需要任何其他手工配置,服务动态扩容,水平扩展
将consul 和 consul 和 consul-template 服务文件,分别放置到位置 /usr/lib/systemd/system/
注意修改下面参数的填写
consul.service 文件(需要先改主机名,先用 hostname 命令,再改 /etc/hostname 文件)
[Unit]
Description=consul
[Service]
Type=simple
ExecStartPre=/bin/mkdir -p /opt/consul/data /opt/consul/config /opt/consul/log
ExecStart=/bin/bash -c 'exec /opt/consul/consul agent -bootstrap -server -ui -node=$(hostname) -bind=$(hostname -i) -data-dir=/opt/consul/data/ -config-dir=/opt/consul/config/ -advertise=$(hostname -i) -recursor=10.64.1.54 -recursor=10.64.1.55 -dns-port=53 -client=0.0.0.0 &>> /opt/consul/log/consul.log'
WorkingDirectory=/opt/consul
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
consul-template.template 文件
[Unit]
Description=consul-template
[Service]
Type=simple
ExecStart=/bin/bash -c 'exec /opt/consul/consul-template -config=/opt/consul/consul-template.conf &>> /opt/consul/log/consul-template.log'
WorkingDirectory=/opt/consul
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
然后分别启动服务
systemctl start consul
systemctl start consul-template
使用下面的命令查询是否成功,然后尝试启动
systemctl status consul
journalctl -f -xn | grep consul // 跟踪显示 consul 这个服务的信息 , x 选项是添加 category 错误信息提示。
如果没有错误了,加入自启动列表
systemctl enable consul
systemctl enable consul-template
consul-template 这个服务的功能是用来使用 /var/lib/consul/sock
这个 docker 启动的监听通道监控是否有新的服务变动(包含启动和停止),
如果有变动机会 1. 渲染源模板文件. 2.替换目标文件. 3. 执行指定的命令(常常是更新系统配置的命令)
上面例子中的nginx模板文件 nginx.ctmpl
内容如下
server_names_hash_bucket_size 128;
client_max_body_size 10G;
{{range services}} {{$name := .Name}} {{$service := service .Name}}
upstream {{$name}} {
zone upstream-{{$name}} 64k;
{{range $service}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
} {{end}}
server {
listen 80 default_server;
location / {
return 404;
}
location /health {
return 200;
}
}
{{range services}} {{$name := .Name}}
server {
listen 80;
server_name {{$name}}.service.test.org {{$name}}.service.org;
location / {
proxy_pass http://{{$name}};
proxy_http_version 1.1;
proxy_set_header Host {{$name}}.service.test.org;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
# upgrade connection for websocket
set $proxy_upgrade "";
set $proxy_connection "";
if ($http_upgrade != ''){
set $proxy_upgrade $http_upgrade;
set $proxy_connection "upgrade";
}
proxy_set_header Upgrade $proxy_upgrade;
proxy_set_header Connection $proxy_connection;
}
}
{{end}}
server {
listen 80;
server_name consul-ui.service.test.org;
location / {
proxy_pass http://127.0.0.1:8500;
}
}
当第一个consul 节点启动成功之后,就可以在其他节点上面使用下面的命令加入到consul集群中
consul join 第一台节点 ip 地址
每台机器需要使用 nginx 服务做端口转发。最终的效果是访问 Consul 集群中任何一个机器的 8500 看到的 web 界面是一样的。
/opt/consul/consul-template.conf 文件
# see https://github.com/hashicorp/consul-template#configuration-file-format
consul {
auth {
enabled = true
username = "test"
password = "test"
}
address = "127.0.0.1:8500"
token = "111111"
retry {
enabled = true
attempts = 5
backoff = "250ms"
}
}
reload_signal = "SIGHUP"
#dump_signal = "SIGQUIT"
kill_signal = "SIGINT"
max_stale = "10m"
log_level = "warn"
pid_file = "/opt/consul/run/consul-template.pid"
wait {
min = "5s"
max = "10s"
}
syslog {
enabled = true
facility = "LOCAL5"
}
deduplicate {
enabled = true
prefix = "consul-template/dedup/"
}
template {
source = "/opt/consul/nginx.ctmpl"
destination = "/etc/nginx/sites-enabled/default"
command = "service nginx reload"
command_timeout = "60s"
perms = 0644
#backup = true
left_delimiter = "{{"
right_delimiter = "}}"
wait {
min = "2s"
max = "10s"
}
}
常见问题:
- 在 consul-ui 网页上面看到,显示的服务在实际的机器上面不存在。这是因为缓存没有更新造成的。删除掉 consul/data 文件夹,重启 consul, consul-template 服务。
- 使用的consul, consul-template, registrator 版本要一致,否则会出现莫名其妙的问题。
- registrator 一定要注册到 localhost:8500, 否则这台机器上启动的容器会显示到 注册的机器 上面。
- 重新修复的 consul 集群,启动 registrator 之后,consul-ui 会找不到现有的 docker 容器。需要手动触发一次 registrator 的reload
docker exec -it registrator sh
/bin/registrator -resync=0 consul://localhost:8500
其中,consul://localhost:8500 就是 docker run 启动 registrator 的参数。
- 启动单节点的Consul时往往会报错
2016/11/25 21:06:10 [ERR] agent: failed to sync remote state: No cluster leader
2016/11/25 21:06:35 [ERR] agent: coordinate update error: No cluster leader
这是因为 Consul 集群启动数量太少无法进行选举。可以在 consul 启动命令中加上参数 -bootsrapt, 就可以让单节点启动。
节点加入到集群中之后,又修改了hostname导致以旧hostname注册的节点一直存在集群中
通过 force-leave 命令强制离线Mongo节点出现故障,恢复之后service registry中注册的mongo服务的节点仍然处于不正确的状态,
通过 deregister 注销然后再重新注册服务