Reading / Coding / Hacking
如果不想折腾 Kubernetes 或 Docker Swarm, 又需要一个能快速搭建并投入使用的 Docker 服务发现机制,这篇文章可能可以给你一点灵感。
Docker 容器的服务发现涉及到两个问题:
以下是我解决这两个问题的思路。
这里要用到两个开源组件,Registrator 和 CoreDNS. 前者负责监听 Docker 容器的启动和停止,根据容器的名字或 label 生成 DNS 信息,以 SkyDNS 2 的格式写入 etcd; 后者从 etcd 读取 DNS 信息以提供 service name 的解析。
具体配置是:
registrator -cleanup -internal=true -explicit=true skydns2://HOST_IP:2379/cluster.local
./skydns
, 响应本机的 DNS 解析请求。127.0.0.1
. UPDATED: 错的,应该写宿主机 IP.遇到的一些问题:
Registrator 生成的名字不太符合 DNS 规范,可以在容器启动时指定 SERVICE_ID
覆盖掉默认值。另外这个项目看起来已经很久没有更新了,用的还是 etcd v2 API.
CoreDNS 解析域名时会返回当前「目录」的所有记录,比如 redis.qcloud.local
的解析包含 redis-1
和 redis-2
两个实例的 IP.
覆盖 Docker daemon 的 DNS server 后,CoreDNS 容器也会受影响(虽然指定了 --network=host
),导致后者的 /etc/resolv.conf
里写的也是 127.0.0.1
, 所有 DNS 查询变成无限递归。
Docker daemon 的 DNS server 不能写 127.0.0.1
, 因为 CoreDNS 监听的是宿主机的 IP.
Docker 容器与宿主机使用的是不同的 IP 网段,比如宿主机的 CIDR 是 172.30.0.0/16
, 而容器网络用的是 10.32.0.0/16
. 默认情况下宿主机所在子网不太可能给容器 IP 网段配置路由,意味着我们只能自行解决路由问题。
如果宿主机是 Vultr / DigitalOcean / Linode 的 VPS, 在开启 Private Network 功能之后,直接给每台机器添加类似 10.32.0.0/16 via 172.30.x.x dev ethX
的路由即可。
而类似 AWS / GCP / 腾讯云这类比较「正规」的云服务,就稍微麻烦一点,因为他们的虚拟机网卡通常都开启了 IP 包的来源 / 目标地址检查1,所有不属于 VPC 子网网段的 IP 包都没法转发。简单粗暴的解决方法是在 VPC 路由表里添加容器 IP CIDR 的路由规则。
有精力折腾的话也可以试试 Calico 或 Flannel 的 IP-in-IP 方案。
解决上述两个问题,只能让不同机器间的 Docker 容器互相访问——如果想将服务暴露出去,比如提供公网的 HTTP 服务,还需要一些反向代理的配置。以 Nginx (OpenResty) 为例,如果所有服务都绑定在 *.example.com
这个公网域名上,可以将子域名映射为集群内的 service name, 再 proxy pass 过去。
具体实现:
SERVICE_8000_ID=web-0
, 这里 8000 是 abc 服务暴露的端口。web-0.abc.cluster.local
.server_name ~(?P<subdomain>[^.]+).example.com;
抽取子域名,比如访问 abc.example.com
时 subdomain
就是 abc
.access_by_lua
作用域里用 ngx.var.subdomain .. '.cluster.local'
拼接出 service name.resty.dns.resolver
解析 service name, 从 A 记录拿到 upstream IP, 从名为 web-[0-9]+
的 SRV 记录拿到端口号,记录到 ngx.ctx
里balancer_by_lua_block
从 ngx.ctx
里取出 IP 和端口号,作为 proxy_pass
的目标地址。完整的 Nginx 配置和 Lua 代码见这个 Gist.
AWS 可以关掉这个检查,见官方文档里提到的 Changing the Source or Destination Checking, 其他云服务就不太清楚了。
↩