使用 Etcd 和 Haproxy 作 Docker 服务发现

使用 Etcd 和 Haproxy 作 Docker 服务发现

标签(空格分隔): Etcd Haproxy Docker 服务发现 architecture discovery docker-gen golang service前端


本文做者是 jwilder,本文的原文是 Docker Service Discovery Using Etcd and Haproxynginx

在前一篇文章中,咱们展现了一种为 Docker 容器在同一台主机上建立一个自动化 Nginx 反向代理的方式。那个设置对于前端 web app 来讲工做的很好,可是对于后端服务来讲它不是一个好的点子,由于一般它们跨越多个主机。git

这篇文章描述了一个为后端服务的 Docker 容器提供服务发现的解决方案。github

咱们将构建的架构体系是模仿 SmartStack,可是使用 etcd 代替 Zookeeper,和两个 docker 容器运行 docker-genhaproxy 代替 nervesynapsegolang

它怎样工做的

此处输入图片的描述

相似于 SmartStack,咱们的组件服务做为一个注册(etcd),一个注册伙伴进程(docker-register),发现伙伴进程(docker-discover),一些后端服务(whoami)以及最后一个消费者(ubuntu/curl)。web

注册和发现组件做为设备与应用程序容器工做,所以在后端或消费者容器的注册或发现代码不是被嵌入的。它们仅仅监听端口或链接其余本地端口。docker

服务注册 - Etcd

在任何事情被注册以前,咱们须要一些地方跟踪注册条目(好比,服务的 IP 和端口)。咱们使用 etcd,由于它由服务注册的简单程序模型和支持键的 TTLs 以及目录。apache

一般,你将运行 3到5个 etcd 节点,可是咱们仅仅使用一个来保持事情简化。ubuntu

没有理由为何咱们不能使用 Consul 或任何其余存储选项支持 TTL 过时。后端

开始 etcd:

docker run -d --name etcd -p 4001:4001 -p 7001:7001 coreos/etcd

服务注册 - docker-register

注册服务容器被 jwilder/docker-register 容器处理。这个容器注册其余运行在同一台主机上的容器到 etcd 中。咱们想注册的容器必须暴露一个端口。容器在不一样的主机上运行相同的镜像是在 etcd 中被分组并将构成一个负载均衡集群。容器怎样分组是有点乱的,为这个演练我已经选择了容器镜像名字。在一个真实的部署中,你可能想经过环境变量,服务版本或其余的元数据分组。

(当前的实现仅仅支持每一个容器一个端口并假设它当前是 TCP,没有理由为何不能支持多个端口和类型以及不一样的分组属性)

docker-register 使用 docker-gen连同一个 Python 脚本做为一个模板。当运行的时候,动态的生成一个脚本,将在 /backends 目录注册每一个容器的 IP 和端口。

docker-gen 关注监控 docker events和调用在一个间隔调用生成脚原本确保 TTLs 始终在最近的日期,若是 docker-register 中止了,注册过时。

为了启动 docker-register,咱们须要传递主机的外部 IP,其余的主机能访问它的容器以及你的 etcd 主机的地址。 为了调用它的 API,docker-gen 要求访问 docker daemon,所以咱们也绑定挂载 docker 的 unix socket 到容器中。

HOST_IP=$(hostname --all-ip-addresses | awk '{print $1}')
ETCD_HOST=w.x.y.z:4001
docker run --name docker-register -d -e HOST_IP=$HOST_IP -e ETCD_HOST=$ETCD_HOST -v /var/run/docker.sock:/var/run/docker.sock -t jwilder/docker-register

服务发现 - docker-discover

服务发现被 jwilder/docker-discover 容器处理。 docker-discover 周期性的投票 etcd 并经过监听每一个服务类型来生成一个 haproxy 配置文件。

好比,容器运行 jwilder/whoami 被注册在 /backends/whoami/<id>以及被暴露在主机上的端口是 8000。

其余的容器须要调用 jwilder/whoami 服务,能够发送请求到 docker bridge IP:8000 或主机 IP:8000。

若是任何的后端服务宕了,haproxy 健康检查从池子中移除它并将在一台健康的主机上尝试请求。这确保后端服务能够随着需求被启动和中止,以及处理注册信息的不一致同时确保最小化的客户端影响。

最后,stats 能够经过在 docker-discover 容器上访问端口 1936 来查看。

运行 docker-discover:

ETCD_HOST=w.x.y.z:4001
docker run -d --net host --name docker-discover -e ETCD_HOST=$ETCD_HOST -p 127.0.0.1:1936:1936 -t jwilder/docker-discover

咱们正在使用 --net host 以致于容器使用主机网络栈。当这个容器绑定 8000 端口,它实际是绑定在主机的网络上。这个简化了代理的设置。

AWS Demo

咱们将在 4 台 AWS 主机上运行整套服务:一台 etcd 主机, 一台 client 主机 和 两台后端主机。后端服务 是一个简单的返回主机名的 golang HTTP 服务。

Etcd 主机

首先,咱们启动 etcd 注册:

$ hostname --all-ip-addresses | awk '{print $1}'
10.170.71.226

$ docker run -d --name etcd -p 4001:4001 -p 7001:7001 coreos/etcd

咱们的 etcd 地址是 10.170.71.226。咱们将在其余主机上使用它。若是咱们正在运行的是一个在线环境,咱们能够分配一个 EIP 和 DNS 地址给它使得它更容易配置。

后端主机

下一步,咱们在每台主机上启动这个服务和 docker-register。该服务被配置成监听容器中的 8000 端口而且咱们让 docker 把它发布在一台主机上的随机端口。

后端主机 1:

$ docker run -d -p 8000:8000 --name whoami -t jwilder/whoami
736ab83847bb12dddd8b09969433f3a02d64d5b0be48f7a5c59a594e3a6a3541
$ docker run --name docker-register -d -e HOST_IP=$(hostname --all-ip-addresses | awk '{print $1}') -e ETCD_HOST=10.170.71.226:4001 -v /var/run/docker.sock:/var/run/docker.sock -t jwilder/docker-register
77a49f732797333ca0c7695c6b590a64a7d75c14b5ffa0f89f8e0e21ae47ae3e

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                     NAMES
736ab83847bb        jwilder/whoami:latest            /app/http              48 seconds ago      Up 47 seconds       0.0.0.0:49153->8000/tcp   whoami
77a49f732797        jwilder/docker-register:latest   "/bin/sh -c 'docker-   28 minutes ago      Up 28 minutes                                 docker-register

后端主机 2:

$ docker run -d -p 8000:8000 --name whoami -t jwilder/whoami
4eb0498e52076275ee0702d80c0d8297813e89d492cdecbd6df9b263a3df1c28
$ docker run --name docker-register -d -e HOST_IP=$(hostname --all-ip-addresses | awk '{print $1}') -e ETCD_HOST=10.170.71.226:4001 -v /var/run/docker.sock:/var/run/docker.sock -t jwilder/docker-register
832e77c83591cb33bba53859153eb91d897f5a278a74d4ec1f66bc9b97deb221

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                     NAMES
4eb0498e5207        jwilder/whoami:latest            /app/http              59 seconds ago      Up 58 seconds       0.0.0.0:49154->8000/tcp   whoami
832e77c83591        jwilder/docker-register:latest   "/bin/sh -c 'docker-   34 minutes ago      Up 34 minutes                                 docker-register

客户端主机

在客户端主机,咱们须要启动 docker-discover 和一个客户端服务。对于这个客户端容器,我使用 Ubuntu Trusty 并将作一些 curl 请求。

首先,启动 docker-discover:

$ docker run -d --net host --name docker-discover -e ETCD_HOST=10.170.71.226:4001 -p 127.0.0.1:1936:1936 -t jwilder/docker-discover

而后,启动一个简单的客户端容器并传给它 HOST_IP。咱们正在使用 eth0 地址,但也能够使用 docker0 IP。咱们正以一个环境变量传给它由于它是被配置的在两个部署之间变化的

$ docker run -e HOST_IP=$(hostname --all-ip-addresses | awk '{print $1}') -i -t ubuntu:14.04 /bin/bash
$ root@2af5f52de069:/# apt-get update && apt-get -y install curl

这时,构造一些请求给 whoami 服务端口 8000 来看他们的负载。

$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 4eb0498e5207
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 736ab83847bb
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 4eb0498e5207
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 736ab83847bb

咱们能够在后端启动一些实例:

$ docker run -d -p :8000 --name whoami-2 -t jwilder/whoami
$ docker run -d -p :8000 --name whoami-3 -t jwilder/whoami

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                     NAMES
5d5c12c96192        jwilder/whoami:latest            /app/http              3 seconds ago       Up 1 seconds        0.0.0.0:49156->8000/tcp   whoami-2
bb2a408b8ec5        jwilder/whoami:latest            /app/http              21 seconds ago      Up 20 seconds       0.0.0.0:49155->8000/tcp   whoami-3
4eb0498e5207        jwilder/whoami:latest            /app/http              2 minutes ago       Up 2 minutes        0.0.0.0:49154->8000/tcp   whoami
832e77c83591        jwilder/docker-register:latest   "/bin/sh -c 'docker-   36 minutes ago      Up 36 minutes                                 docker-register

而后再次在客户端主机上构造一些请求:

$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 736ab83847bb
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 4eb0498e5207
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm bb2a408b8ec5
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 5d5c12c96192
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 736ab83847bb

最后,咱们关闭一些容器,路由将被更新。这个杀死在后端 2 的任何东西。

$ docker kill 5d5c12c96192 bb2a408b8ec5 4eb0498e5207

$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 736ab83847bb
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 67c3cccbb8ba
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 736ab83847bb
$ root@2af5f52de069:/# curl $HOST_IP:8000
I'm 67c3cccbb8ba

若是你想看 haproxy 是怎样负载流量的或监控错误,咱们能够在 web 浏览器访问客户端主机的 1936 端口。

总结

虽然有不一样的方式来实现服务发现,SmartStack 的伙伴注册行为和代理保持应用程序代码简单以及很是容易的融合进一个分布式环境,真的适合 Docker 容器。

一样地,Docker 的事件和容器 APIs 减轻了服务注册和使用注册服务发现(好比 etcd)的困难。

docker-registerdocker-discover 的代码在 github 上。虽然两个都是有用的,可是有不少地方须要提高。请随时提交或提出改进意见。

相关文章
相关标签/搜索