fbpx

利用 Etcd + Confd + HAProxy 實現服務動態拓展

實現雲服務動態拓展

Docker 應用在雲端服務上,為了能夠即時調配運算資源,或者進行動態拓展,常常需要 LBS (HAProxy) 這樣的流量平衡系統。好讓我們偵測到流量爆增或進行高運算量工作時,可以動態加入運算資源來應付更多的服務請求,並且在伺服器閒閒沒事的時候關閉多餘的運算節點 (或 Container),或者藉此執行一些大量且非同步的運算工作。以前在 Docker Swarm 在沒有整合 confd 的從前,每次加入或移除伺服器都要手動修改 HAProxy 的設定檔,才能重新載入套用新設定,手動是一件很麻煩的事,如果可以自動化那該有多好。

Etcd 是在 CoreOS 底下的一個專案,etcd 是一個輕量且分散式的 Key/Value 資料庫,並且提供了非常簡便的 Rest API 進行操作,適合用在分散式系統中進行資料同步與服務發現 (Service Discovery)。etcd 可以組成 Cluster,其中透過 Raft 共識演算法進行資料同步,這裡還有一個很棒的 Raft 說明網站,有興趣了解的可以看看,介紹的很棒。

為了實現 Container Micro Service Discovery,我們嘗試在 Docker Swarm Cluster 中導入 etcd 來管理 Container 服務。當有服務上線時會透過 Script Call API 自動註冊,好讓相依的其他服務可以進行對應的工作,讓整個 Cluster 的啟動更聰明,不必擔心啟動順序的問題。後續配合 confd 這套件,輪詢監聽 etc 的變化,動態生成服務需要的設定檔並且重新載入服務,實現自動拓展機制。

安裝與啟動 ectd 服務

Docker Swarm Cluster 的安裝我們就不介紹了,有興趣可以參考之前的文章「用 Docker Swarm 部署你的雲服務,我們先來看一下 etcd 如何安裝,以下作業在 Ubuntu Server 16.04 系統下進行示範:

curl -L  https://github.com/coreos/etcd/releases/download/v2.1.0-rc.0/etcd-v2.1.0-rc.0-linux-amd64.tar.gz -o etcd-v2.1.0-rc.0-linux-amd64.tar.gz

tar xzvf etcd-v2.1.0-rc.0-linux-amd64.tar.gz

mv etcd-v2.1.0-rc.0-linux-amd64/etcd /usr/sbin

mv etcd-v2.1.0-rc.0-linux-amd64/etcdctl /usr/sbin

rm -rf -R etcd-v2.1.0-rc.0-linux-amd64

rm -rf etcd-v2.1.0-rc.0-linux-amd64.tar.gz

安裝好以後接著啟動 etcd 服務,以下的 Script 會抓取 eth0 的 IP 作為 Publish IP,當你有使用 etcd 多節點架設 Cluster 的時候,這個 IP 要確保其他 etcd Node 可以進行連線與溝通 (GitHub)。

#!/bin/bash
THIS_IP=`ifconfig eth0 | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1`
/usr/sbin/etcd --data-dir=data.etcd \
              --name ${HOSTNAME} \
              --advertise-client-urls "http://${THIS_IP}:2379" \
              --listen-client-urls 'http://0.0.0.0:2379'

一般來說我習慣把每一個 Service 封裝成獨立的 Docker,讓每一個 Container 可以提供單一的服務,實現 Micro Service 的概念,也會比較好管理與重新組合。

安裝與啟動 confd 服務

有了 etcd 還需要搭配 confd 動態修改設定檔,confd 安裝方式如下:

curl -qL https://github.com/kelseyhightower/confd/releases/download/v0.9.0/confd-0.9.0-linux-amd64 -o /confd

chmod +x /confd

mv /confd /usr/sbin

mkdir -p /etc/confd/conf.d

mkdir -p /etc/confd/templates

安裝後我們需要先建立設定檔,這裡以 HAProxy Config File 作為示範,應用上我們會在 HAProxy Container 中安裝 confd 服務來透過 etcd Discovery Service。 首先我們先在 /etc/confd/conf.d 目錄中建立 haproxy.toml 檔案,設定檔必須以 .toml 作為結尾,HAProxy 的範例設定檔如下:

vim /etc/confd/conf.d/haproxy.toml

[template]
src = "haproxy.cfg.tmpl"
dest = "/etc/haproxy/haproxy.cfg"
keys = [
  "/app/servers",
]
reload_cmd = "/usr/sbin/service haproxy reload"

上面的 src 表示 Template 設定檔樣板檔名,我們等等會建立這個樣板檔。dest 是樣板經過渲染後產生的目的檔案位置,透過 keys 設定我們想監聽的 etcd 資料位置,當樣板有發生變化時會執行 reload_cmd。整個概念其實很簡單,如果樣板不複雜你甚至可以自己寫個 Script 與 etcd 串接做一樣的事 (我相信你不會這樣做的...),廢話少說我們先看看樣板 haproxy.cfg.tmpl 的內容:

vim /etc/confd/templates/haproxy.cfg.tmpl

defaults
  log     global
  mode    http

listen frontend 0.0.0.0:8080
  mode http
  stats enable
  stats uri /haproxy?stats
  balance roundrobin
  option httpclose
  option forwardfor
  {{range gets "/app/servers/*"}}
  server {{base .Key}} {{.Value}}:80 check{{end}}

其實樣板就是原本的設定檔內容,只是多了一些渲染需要的 {{}} 語法,詳細更多的樣板語法可以參考官方說明文件,我們這裡就不多做介紹了 (其實我也只會上面這招... -_-)。

HAProxy confd 設定檔與樣板都建好以後,接著透過以下命令啟動 confd 服務

/usr/sbin/confd -interval 10 -node '${ETCD_HOST}:2379' -confdir /etc/confd

上面的命令執行後會與 ETCD_HOST 進行連線,每 10 秒檢查看看 etcd 資料庫有沒有變化,好重新產生設定檔並重啟 HAProxy。整個服務動態拓展流程如下圖:

Etcd + HAProxy + Confd 實現服務動態拓展

Micro Service 透過 Etcd API 註冊服務

最終的目的,我們希望達到是各種 Micro Service 啟動即可自動加入 HAProxy 提供服務。假設我們有不同型態的 Service,一樣透過 Docker 進行封裝與發布,當新的 Container 服務啟動或升級時,需要告知 Etcd 進行註冊,好讓對應的服務 (比如上述的 HAProxy) 可以重新配置設定。當服務 Container 正確啟動後,我們可以透過以下 Script 呼叫 Etcd API 進行註冊,如下:

#!/bin/bash

# wait for etcd start
while ! nc -z ${ETCD_HOST} 2379; do
 echo "Waiting for etcd start..."
 sleep 1
done

# registe srvice hostname into etcd
curl -L http://${ETCD_HOST}:2379/v2/keys/app/servers/server-${HOSTNAME} -XPUT -d value="${HOSTNAME}"

上面的 Script 連線到 ETCD_HOST 並且將自己的 HOSTNAME 寫入 /app/servers 這個位置,各位有有發現這裡的位置必須對應剛剛 haproxy.toml 檔案裡面的設定,如此註冊時才會觸發 confd 進行 Reload。通常我們會把上述的 Script 寫進 Container Start Command,這樣服務啟動時就可以自動註冊了。

還有一個問題?萬一服務結束或 Container 結束時,要如何從 ectd 移除註冊資訊,好讓對應的服務可以知道並重新產生設定檔。這個功能一樣透過 etcd REST API 即可完成,只要透過 HTTP Delete Method 刪除 key path 即可。為了想要在 Container 結束時自動反註冊,我們可以在 Container Start Script 加入以下內容:

#!/bin/bash

_term() {
 echo "Caught SIGTERM signal and remove etcd key..."
 curl -L http://${ETCD_HOST}:2379/v2/keys/push/servers/server-${HOSTNAME} -XDELETE
 kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

# Start your service at here...

child=$!
wait "$child"

這段 Script 可以讓自己結束時 (收到來至上層 Docker 發出的 SIGTERM Signal) 去執行 _term Function 的內容,就可以實現反註冊資訊的功能囉。這個 Script 可以定義在你的 Dockerfile CMD 的設定裡,好讓 Container 啟動的時候去監聽 SIGTERM Singal,當 Container 被中斷時可以自動呼叫 Etcd API 刪除自己的主機資訊,連動其他 Container 變更自己的設定,像是我們範例架構中的 HAProxy 便可以自動從 Cluster 中移除這一台 Service,達到自動拓展或縮減的效果。

所有的範例設定檔與 Script 都在 GitHub 上,需要的人可以自行參考。目前已經利用 Docker Cluster 管理不少的 Container,關於大量 Docker Container 的管理,除了 Docker Swarm 以外,其實還有另一個更好的選擇,就是 K8S (Kubernetes) 這個由 Google 所大力發展的 Project。最近也開始研究 K8S,未來也計畫透過 K8S 進行容器管理,有心得再與各位分享了。