使用 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 上。雖然兩個都是有用的,可是有不少地方須要提高。請隨時提交或提出改進意見。

相關文章
相關標籤/搜索