服务发现如何与现代docker / docker-compose一起使用?

lar*_*sks 5 service-discovery docker docker-compose

我正在使用Docker 1.11.1和docker-compose 1.8.0-rc2。

在过去的好时光(例如去年),您可以这样设置docker-compose.yml文件:

app:
  image: myapp

frontend:
  image: myfrontend
  links:
    - app
Run Code Online (Sandbox Code Playgroud)

然后像这样启动环境:

docker scale app=3 frontend=1
Run Code Online (Sandbox Code Playgroud)

而你的前端容器可以检查为命名变量的环境变量APP_1_PORTAPP_2_PORT等相应发现可用的后端主机和配置本身。

时代变了。现在,我们这样做...

version: '2'

services:
  app:
    image: myapp

  frontend:
    image: myfrontend
    links:
      - app
Run Code Online (Sandbox Code Playgroud)

...而不是环境变量,我们得到了DNS。因此,在 frontend容器内,我可以要求app_app_1or app_app_2app_app_3得到对应的IP地址。我也可以要求 app获得地址app_app_1

但是,如何发现所有可用的后端容器?我想我可以循环getent hosts ...直到失败:

counter=1
while :; do
  getent hosts app_$counter || break
  backends="$backends app_$counter"
  let counter++
done
Run Code Online (Sandbox Code Playgroud)

但这看起来很丑陋且脆弱。

我听说过有关轮询dns的传言,但是(a)在我的测试环境中似乎没有发生,(b)如果您的前端需要同时连接到后端,则不一定有帮助。

简单的容器和服务发现如何在现代Docker世界中发挥作用?

The*_*ool 7

Docker 内置的 Nameserver 和 Loadbalancer

Docker 带有一个内置的名称服务器。默认情况下,服务器可通过127.0.0.11:53.

默认情况下,每个容器在 中都有一个名称服务器条目/etc/resolv.conf,因此不需要从容器内指定名称服务器的地址。这就是为什么您可以使用service或 来从网络中找到您的服务task_service_n

如果这样做,task_service_n您将获得相应服务副本的地址。

如果您只要求servicedocker 将internal load balancing在同一网络中的容器之间执行并external load balancing处理来自外部的请求。

当使用 swarm 时,docker 会额外使用两个特殊网络。

  1. ingress network,这实际上是一个覆盖网络和把手进来的TRAFIC到群。它允许从群中的任何节点查询任何服务。
  2. docker_gwbridge,一个桥接网络,其中单个主机的覆盖网络连接到他们的物理网络。(包括入口)

使用 swarm部署服务时,除非将 endpointmode 设置为 dns roundrobin 而不是 vip,否则下面示例中描述的行为将不起作用。

endpoint_mode: vip - Docker 为服务分配一个虚拟 IP (VIP),作为客户端访问网络上的服务的前端。Docker 在客户端和服务的可用工作节点之间路由请求,客户端不知道有多少节点参与服务或其 IP 地址或端口。(这是默认设置。)

endpoint_mode: dnsrr - DNS 循环 (DNSRR) 服务发现不使用单个虚拟 IP。Docker 为服务设置 DNS 条目,以便对服务名称的 DNS 查询返回 IP 地址列表,并且客户端直接连接到其中之一。如果您想使用自己的负载均衡器,或者对于混合 Windows 和 Linux 应用程序,DNS 轮询非常有用。

例子

例如从dig/docker-compose.yml部署三个副本

version: '3.8'
services:
  whoami:
    image: "traefik/whoami"
    deploy:
      replicas: 3
Run Code Online (Sandbox Code Playgroud)

DNS查询

您可以使用dignslookup等工具对同一网络中的名称服务器进行 DNS 查找。

docker run --rm --network dig_default tutum/dnsutils dig whoami
Run Code Online (Sandbox Code Playgroud)
; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> whoami
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58433
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;whoami.                                IN      A

;; ANSWER SECTION:
whoami.                 600     IN      A       172.28.0.3
whoami.                 600     IN      A       172.28.0.2
whoami.                 600     IN      A       172.28.0.4

;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Mon Nov 16 22:36:37 UTC 2020
;; MSG SIZE  rcvd: 90
Run Code Online (Sandbox Code Playgroud)

如果您只对IP感兴趣,您可以提供+short选项

docker run --rm --network dig_default tutum/dnsutils dig +short whoami
Run Code Online (Sandbox Code Playgroud)
172.28.0.3
172.28.0.4
172.28.0.2
Run Code Online (Sandbox Code Playgroud)

或寻找特定服务

docker run --rm --network dig_default tutum/dnsutils dig +short dig_whoami_2
Run Code Online (Sandbox Code Playgroud)
172.28.0.4
Run Code Online (Sandbox Code Playgroud)

负载均衡

默认负载平衡发生在OSI 模型的传输层或第 4 层。所以它是基于 TCP/UDP 的。这意味着无法使用此方法检查和操作 http 标头。在企业版中,显然可以使用类似于 treafik 在示例中使用的标签。

docker run --rm --network dig_default curlimages/curl -Ls http://whoami
Run Code Online (Sandbox Code Playgroud)
docker run --rm --network dig_default curlimages/curl -Ls http://whoami
Run Code Online (Sandbox Code Playgroud)

这是 10 次 curl 的主机名:

Hostname: eedc94d45bf4
Hostname: 42312c03a825
Hostname: 42312c03a825
Hostname: 42312c03a825
Hostname: eedc94d45bf4
Hostname: d922d86eccc6
Hostname: d922d86eccc6
Hostname: eedc94d45bf4
Hostname: 42312c03a825
Hostname: d922d86eccc6
Run Code Online (Sandbox Code Playgroud)

健康检查

默认情况下,健康检查是通过检查主机内核上容器的进程 ID (PID) 来完成的。如果进程成功运行,则容器被认为是健康的。

通常需要其他健康检查。容器可能正在运行,但里面的应用程序已经崩溃。在许多情况下,首选 TCP 或 HTTP 检查。

可以将自定义健康检查烘焙到图像中。例如,使用curl执行 L7 健康检查。

FROM traefik/whoami
HEALTHCHECK CMD curl --fail http://localhost || exit 1   
Run Code Online (Sandbox Code Playgroud)

也可以在启动容器时通过 cli 指定健康检查。

Hostname: eedc94d45bf4
IP: 127.0.0.1
IP: 172.28.0.3
RemoteAddr: 172.28.0.5:43910
GET / HTTP/1.1
Host: whoami
User-Agent: curl/7.73.0-DEV
Accept: */*
Run Code Online (Sandbox Code Playgroud)

使用 Swarm 的示例

正如最初提到的,swarm 行为的不同之处在于默认情况下它会为服务分配一个虚拟 IP。它实际上没有什么不同,它只是 docker 或 docker-compose 不创建真正的服务,它只是模仿 swarm 的行为但仍然正常运行容器,因为服务实际上只能由管理器节点创建。

请记住,我们在群管理器上,因此默认模式是 VIP

创建一个也可以被常规容器使用的覆盖网络

Hostname: eedc94d45bf4
Hostname: 42312c03a825
Hostname: 42312c03a825
Hostname: 42312c03a825
Hostname: eedc94d45bf4
Hostname: d922d86eccc6
Hostname: d922d86eccc6
Hostname: eedc94d45bf4
Hostname: 42312c03a825
Hostname: d922d86eccc6
Run Code Online (Sandbox Code Playgroud)

用 2 个副本创建一些服务

FROM traefik/whoami
HEALTHCHECK CMD curl --fail http://localhost || exit 1   
Run Code Online (Sandbox Code Playgroud)

现在让我们再次使用 dig 并确保我们将容器附加到同一网络

$ docker run --network testnet --rm tutum/dnsutils dig  digme
digme.                  600     IN      A       10.0.18.6
Run Code Online (Sandbox Code Playgroud)

我们看到确实只取回了一个IP地址,所以看起来这是docker分配的虚拟IP。

在这种情况下, Swarm 实际上允许获取单个 IP,而无需明确设置端点模式。

tasks.<servicename>在这种情况下,我们可以查询tasks.digme

docker run \
  --health-cmd "curl --fail http://localhost || exit 1" \
  --health-interval=5s \
  --timeout=3s \
  traefik/whoami
Run Code Online (Sandbox Code Playgroud)

这给我们带来了 2 个指向单个副本的 A 记录。

现在让我们创建另一个服务,端点模式设置为 dns roundrobin

$ docker network create --driver overlay --attachable testnet
Run Code Online (Sandbox Code Playgroud)
$ docker service create --network testnet --replicas 2 --name digme nginx
Run Code Online (Sandbox Code Playgroud)

这样我们就可以在不添加前缀的情况下获得两个 IP tasks

服务发现和负载均衡策略

如果内置功能不够,可以实施一些策略来实现更好的控制。下面是一些例子。

代理服务器

Haproxy可以结合使用docker nameserver 和动态服务器模板来发现正在运行的容器。然后可以利用传统的代理功能通过 http 标头操作和混沌工程(例如重试)来实现强大的第 7 层负载平衡。

$ docker run --network testnet --rm tutum/dnsutils dig  digme
digme.                  600     IN      A       10.0.18.6
Run Code Online (Sandbox Code Playgroud)
...
resolvers docker
    nameserver dns1 127.0.0.11:53
    resolve_retries 3
    timeout resolve 1s
    timeout retry   1s
    hold other      10s
    hold refused    10s
    hold nx         10s
    hold timeout    10s
    hold valid      10s
    hold obsolete   10s
...
backend whoami
    balance leastconn
    option httpchk
    option redispatch 1
    retry-on all-retryable-errors
    retries 2
    http-request disable-l7-retry if METH_POST
    dynamic-cookie-key MY_SERVICES_HASHED_ADDRESS
    cookie MY_SERVICES_HASHED_ADDRESS insert dynamic
    server-template whoami- 6 whoami:80 check resolvers docker init-addr libc,none
...
Run Code Online (Sandbox Code Playgroud)

特拉菲克

之前的方法已经很不错了。但是,您可能已经注意到,它需要知道应该发现哪些服务以及要发现的副本数量是硬编码的。Traefik是一个容器原生边缘路由器,解决了这两个问题。只要我们通过label启用 Traefik ,就会发现该服务。这分散了配置。就好像每个服务都注册了自己。

该标签还可用于检查和操作 http 标头

$ docker run --network testnet --rm tutum/dnsutils dig tasks.digme
tasks.digme.            600     IN      A       10.0.18.7
tasks.digme.            600     IN      A       10.0.18.8
Run Code Online (Sandbox Code Playgroud)

领事

Consul是一个用于服务发现和配置管理的工具。服务必须通过 API 请求注册。这是一个更复杂的解决方案,可能只在更大的集群中才有意义,但可能非常强大。通常它建议在裸机上而不是在容器中运行它。您可以将它与集群中每台服务器上的 docker 主机一起安装。

在这个例子中,它与注册器镜像配对,它负责在 consuls 目录中注册 docker 服务。

可以通过多种方式利用目录。其中之一是使用consul-template

请注意,consul 带有自己的 DNS 解析器,因此在这种情况下,docker DNS 解析器在某种程度上被忽略了。

docker service create --endpoint-mode dnsrr --network testnet --replicas 2 --name digme2 nginx
Run Code Online (Sandbox Code Playgroud)

用于自定义代理镜像的 Dockerfile,支持 consul 模板。

FROM nginx

RUN curl https://releases.hashicorp.com/consul-template/0.25.1/consul-template_0.25.1_linux_amd64.tgz \
  > consul-template_0.25.1_linux_amd64.tgz

RUN gunzip -c consul-template_0.25.1_linux_amd64.tgz | tar xvf -

RUN mv consul-template /usr/sbin/consul-template
RUN rm /etc/nginx/conf.d/default.conf
ADD proxy.conf.ctmpl /etc/nginx/conf.d/
ADD consul-template.hcl /

CMD [ "/bin/bash", "-c", "/etc/init.d/nginx start && consul-template -config=consul-template.hcl" ]
Run Code Online (Sandbox Code Playgroud)

Consul 模板接受一个模板文件,并根据 consuls 目录的内容进行渲染。

upstream whoami {
{{ range service "whoami" }}
  server {{ .Address }}:{{ .Port }};
{{ end }}
}

server {
   listen 80;
   location / {
      proxy_pass http://whoami;
   }
}
Run Code Online (Sandbox Code Playgroud)

更改模板后,执行重新启动命令。

consul {
  address = "consul:8500"

  retry {
    enabled  = true
    attempts = 12
    backoff  = "250ms"
  }
}
template {
  source      = "/etc/nginx/conf.d/proxy.conf.ctmpl"
  destination = "/etc/nginx/conf.d/proxy.conf"
  perms       = 0600
  command = "/etc/init.d/nginx reload"
  command_timeout = "60s"
}
Run Code Online (Sandbox Code Playgroud)

特征表

内置 代理服务器 特拉菲克 领事模板
解析器 码头工人 码头工人 码头工人 领事
服务发现 自动的 服务器模板 标签系统 KV商店+模板
健康检查 是的 是的 是的 是的
负载均衡 L4 L4、L7 L4、L7 L4、L7
粘性会话 是的 是的 取决于代理
指标 统计页面 仪表盘 仪表盘

您可以在 github 上更详细地查看一些代码示例