现代docker/docker-compose如何实现服务发现?

10

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

在过去(也就是去年),你可以像这样设置一个docker-compose.yml文件:

app:
  image: myapp

frontend:
  image: myfrontend
  links:
    - app

然后像这样启动环境:

docker scale app=3 frontend=1

您的前端容器可以检查环境变量中名称为APP_1_PORTAPP_2_PORT等的变量,以发现可用的后端主机并相应地进行配置。

时代已经改变。现在我们这样做...

version: '2'

services:
  app:
    image: myapp

  frontend:
    image: myfrontend
    links:
      - app

...而不是使用环境变量,我们使用DNS。因此,在 frontend 容器内,我可以请求 app_app_1 app_app_2 app_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

但那看起来很丑陋而且脆弱。

我听说过轮询DNS的传言,但是(a)在我的测试环境中好像没有发生这种情况,而且(b)如果您的前端需要同时连接到后端,则这并不一定有帮助。

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


1
你想出了答案吗? - user986408
1个回答

16

Docker内置的Nameserver和负载均衡器

Docker自带一个内置的nameserver。默认情况下,该服务器可以通过127.0.0.11:53访问。

每个容器默认都在/etc/resolv.conf中有一个nameserver条目,因此不需要在容器内指定nameserver的地址。这就是为什么你可以使用servicetask_service_n在网络内查找服务。

如果你使用task_service_n,那么你将得到相应服务副本的地址。

如果你只请求service,Docker将在同一网络中的容器之间执行内部负载平衡,并处理来自外部的请求的外部负载平衡

当使用swarm时,Docker还会使用两个特殊网络。

  1. ingress网络,实际上是一个overlay网络,用于处理进入swarm的流量。它允许从swarm中的任何节点查询任何服务。
  2. docker_gwbridge桥接网络,连接单个主机的覆盖网络与其物理网络。(包括ingress)

当使用swarm来部署服务时,除非将endpointmode设置为dns roundrobin而不是vip,否则以下示例中描述的行为将无法正常工作。

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

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

示例

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

version: '3.8'
services:
  whoami:
    image: "traefik/whoami"
    deploy:
      replicas: 3

DNS查找

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

docker run --rm --network dig_default tutum/dnsutils dig whoami

; <<>> 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

如果你只对IP感兴趣,可以使用+short选项。
docker run --rm --network dig_default tutum/dnsutils dig +short whoami

172.28.0.3
172.28.0.4
172.28.0.2

或查找特定的服务

docker run --rm --network dig_default tutum/dnsutils dig +short dig_whoami_2

172.28.0.4

负载均衡

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

docker run --rm --network dig_default curlimages/curl -Ls http://whoami

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: */*

以下是使用10次curl获取到的主机名:

Hostname: eedc94d45bf4
Hostname: 42312c03a825
Hostname: 42312c03a825
Hostname: 42312c03a825
Hostname: eedc94d45bf4
Hostname: d922d86eccc6
Hostname: d922d86eccc6
Hostname: eedc94d45bf4
Hostname: 42312c03a825
Hostname: d922d86eccc6

健康检查

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

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

可以将自定义健康检查集成到镜像中。例如,使用curl执行L7健康检查。

FROM traefik/whoami
HEALTHCHECK CMD curl --fail http://localhost || exit 1   

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

docker run \
  --health-cmd "curl --fail http://localhost || exit 1" \
  --health-interval=5s \
  --timeout=3s \
  traefik/whoami

使用Swarm进行示例

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

请记住我们正在一个swarm管理器上,因此默认模式是VIP。

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

$ docker network create --driver overlay --attachable testnet

创建2个副本的一些服务。
$ docker service create --network testnet --replicas 2 --name digme nginx

现在让我们再次使用dig,并确保将容器附加到相同的网络{{network}}上。
$ docker run --network testnet --rm tutum/dnsutils dig  digme
digme.                  600     IN      A       10.0.18.6

我们看到确实只返回了一个IP地址,因此看起来这是docker分配的虚拟IP。
Swarm允许在这种情况下获取单个IP,无需显式设置端点模式。
我们可以查询tasks.<servicename>,在这种情况下是tasks.digme
$ 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

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

现在,让我们创建另一个服务,并将endpointmode设置为DNS轮询。

docker service create --endpoint-mode dnsrr --network testnet --replicas 2 --name digme2 nginx

$ docker run --network testnet --rm tutum/dnsutils dig digme2
digme2.                 600     IN      A       10.0.18.21
digme2.                 600     IN      A       10.0.18.20

这样我们可以获取两个IP,而不添加前缀tasks

服务发现和负载均衡策略

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

HAProxy

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

version: '3.8'
services:

  loadbalancer:
    image: haproxy
    volumes: 
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    ports:
      - 80:80
      - 443:443
  
  whoami:
    image: "traefik/whoami"
    deploy:
      replicas: 3

...
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
...

Traefik

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

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

version: "3.8"

services:
  traefik:
    image: "traefik:v2.3"
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    image: "traefik/whoami"
    labels:
      - "traefik.enable=true"
      - "traefik.port=80"
      - "traefik.http.routers.whoami.entrypoints=web"
      - "traefik.http.routers.whoami.rule=PathPrefix(`/`)"
      - "traefik.http.services.whoami.loadbalancer.sticky=true"
      - "traefik.http.services.whoami.loadbalancer.sticky.cookie.name=MY_SERVICE_ADDRESS"
    deploy:
      replicas: 3

Consul

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

在此示例中,它已与 registrator image 配对,后者负责在consuls目录中注册Docker服务。

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

请注意,Consul附带其自己的DNS解析器,因此在此实例中,Docker DNS解析器有些被忽略。

version: '3.8'
services:

  consul:
    image:  gliderlabs/consul-server:latest
    command: "-advertise=${MYHOST} -server -bootstrap"
    container_name: consul
    hostname: ${MYHOST}
    ports:
    - 8500:8500

  registrator:
    image: gliderlabs/registrator:latest
    command: "-ip ${MYHOST} consul://${MYHOST}:8500"
    container_name: registrator
    hostname: ${MYHOST}
    depends_on:
    - consul
    volumes:
    - /var/run/docker.sock:/tmp/docker.sock

  proxy:
    build: .
    ports:
      - 80:80
    depends_on: 
      - consul

  whoami:
    image: "traefik/whoami"
    deploy:
      replicas: 3
    ports:
      - "80"


用于支持Consul模板的自定义代理镜像的Dockerfile。
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" ]

Consul模板会根据Consul目录的内容获取一个模板文件并进行渲染。

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

server {
   listen 80;
   location / {
      proxy_pass http://whoami;
   }
}

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

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"
}

功能表

内置 HAProxy Traefik Consul-Template
解析器 Docker Docker Docker Consul
服务发现 自动 服务器模板 标签系统 KV存储+模板
健康检查
负载均衡 L4 L4,L7 L4,L7 L4,L7
粘性会话 取决于代理
指标 统计页面 仪表盘 仪表盘

您可以在github上查看更多详细代码示例


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接