Docker 小記 — Compose & Swarm
前言
任何相對完整的應用服務都不可能是由單一的程序來完成支持,計劃使用 Docker 來部署的服務更是如此。大型服務需要進行拆分,形成微服務集群方能增強其穩定性和可維護性。本篇隨筆將對 Docker Compose 和 Docker Swarm 的原理和配置做整理歸納,並分享其使用經驗。
1. YAML 簡介
Docker Compose 的配置文件采用 YAML 格式,因此有必要在正文之前簡要說明下。YAML 是一門專門用來寫配置文件的語言,設計目標就是方便讀寫,其實質上是一種通用的數據串行化格式,基本語法規則如下:
- 大小寫敏感。
#
表示註釋。- 使用縮進表示層級關系。
- 縮進時不允許使用 Tab 鍵,只允許使用空格。
- 縮進的空格數目不重要,只要相同層級的元素左側對齊即可。
YAML 支持的數據結構有三種:
- 對象:
animal:cat
。 - 數組:一組中劃線開頭的行,例如:
# ex1
- cat
- dog
- bird
# ex2
-
- cat
- dog
- bird
# ex3
animal: [cat, dog, bird]
- 值類型和字符串。
2. Docker Compose
2.1 安裝與簡介
Docker 可以極為方便地部署單個服務,但這時候我們需要一個工具來整合 Docker 的功能,使之能夠更便捷地去管理整個微服務集群的部署和遷移,Docker Compose 正是應此而生。他是由 Python 編寫的程序,能夠根據指令結合配置文件轉換成對應的 Docker API 的操作,並直接體現到 Docker Daemon 中,這就代替我們完成了重復輸入復雜指令的過程,主要功能可分為以下兩點:
- Service:代表的是運行同種應用程序的一個或多個相同容器的抽象定義,也是我們在Docker Compose 中配置的主要對象。在每個 Docker Compose 的配置文件中,我們可以定義多個服務,並定義服務的配置,以及服務於服務之間的以來關系。
- Project:代表的是由多個服務所組成的一個相對完整的業務單元。
安裝命令:
curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
2.2 配置參數
Docker Compose 的核心就是其配置文件,采用 YAML 格式,默認為 docker-compose.yml
,參數詳解可查閱“官方文檔”,以下只做一個常規摘要。
services
所有服務的根節點。
image
指定服務的鏡像名,若本地不存在,則 Compose 會去倉庫拉取這個鏡像:
services:
web:
image: nginx
ports
端口映射,例:
ports:
- "80:80"
- "81:81"
volumes
掛載主機目錄,其中 ro 表示只讀,例:
volumes:
- "/etc/nginx/www:/www"
- "/var/run/docker.sock:/tmp/docker.sock:ro"
大多數情況下集群中部署的應該都是無狀態服務,服務可復制且不固定在某一臺宿主機,所以掛載的數據卷最好應當與宿主機脫離關系,例:
web:
services:
image: nginx
volumes:
- type: volume
source: logs
target: "/mnt"
volume:
nocopy: true
volumes:
logs:
driver_opts:
type: "nfs"
o: addr="***.cn-hangzhou.nas.aliyuncs.com,rw"
device: ":/"
當然,這種情況下最好是優先創建數據卷,後在配置文件中引用,例:
docker volume create --driver local --opt type="nfs" --opt o=addr="***.cn-hangzhou.nas.aliyuncs.com,rw" --opt device=":/" logs
volumes:
logs:
external: true
若必須掛載集群中一臺宿主機的目錄作為數據卷,則要安裝一個 docker 插件:
docker plugin install vieux/sshfs
# 若配置了密鑰對則可省略 password 參數
docker volume create -d vieux/sshfs --name sshvolume -o "[email protected]:/remote" -o "password=$(cat file_containing_password_for_remote_host)\
sshvolume
networks
配置服務間的網路互通與隔離,例:
services:
web:
image: nginx
networks:
- proxy
- youclk
networks:
youclk:
external: true
proxy:
external: true
secrets
配置服務密碼訪問,例:
services:
redis:
image: redis:latest
deploy:
replicas: 1
secrets:
- my_secret
- my_other_secret
secrets:
my_secret:
file: "./my_secret.txt"
my_other_secret:
external: true
docker secret create [OPTIONS] SECRET [file|-]
echo "admin:password" | docker secret create my_secret -
docker secret create my_secret ./secret.json
healthcheck
健康檢查,這個非常有必要,等服務準備好以後再上線,避免更新過程中出現短暫的無法訪問。
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/alive"]
interval: 5s
timeout: 3s
其實大多數情況下健康檢查的規則都會寫在 Dockerfile 中:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s CMD curl -f http://localhost/alive || exit 1
depends_on
依賴的服務,優先啟動,例:
depends_on:
- redis
environment & env_file
設置環境變量和指定環境變量的文件,例:
environment:
- VIRTUAL_HOST="test.youclk.com"
env_file:
- ./common.env
deploy
部署相關的配置都在這個節點下,例:
deploy:
mode: replicated
replicas: 2
restart_policy:
condition: on-failure
max_attempts: 3
update_config:
delay: 5s
order: start-first # 默認為 stop-first,推薦設置先啟動新服務再終止舊的
resources:
limits:
cpus: "0.50"
memory: 1g
deploy:
mode: global # 不推薦全局模式(僅個人意見)。
placement:
constraints: [node.role == manager]
若非特殊服務,以上各節點的配置能夠滿足大部分部署場景了。
3. Swarm
Docker 默認包含了 Swarm,因此可以直接使用,初始化命令:docker swarm init
,此時將會默認當前節點為 Leader,以下命令為查看 token:docker swarm join-token (worker|manager)
,其他節點可以用 manager 或者 worker 的身份加入到當前集群,例:
docker swarm join --token SWMTKN-1-3pu6hszjas19xyp7ghgosyx9k8atbfcr8p2is99znpy26u2lkl-1awxwuwd3z9j1z3puu7rcgdbx 172.17.0.2:2377
執行 docker swarm leave
脫離集群。
以下各節點常規操作命令,比較簡單,就不解釋了:
- docker node demote [NODE]
- docker node inspect [NODE]
- docker node ls
- docker node promote [NODE]
- docker node ps [NODE]
- docker node rm [NODE]
- docker node update [OPTIONS] NODE
4. 應用案例
集群最擅長的就是解決多服務問題,只要在同一 network 之下,服務之間默認可以直接通過 service_name 互通有無。但為了避免混亂,各服務與外部的通信最好統一交給一個反向代理服務轉發。因對 nginx 比較熟悉,所以我最初選擇的代理是“jwilder/nginx-proxy”:
server
{
listen 80;
server_name localhost;
location /alive {
return 200;
}
}
server {
listen 81;
return 301 https://$host$request_uri;
}
FROM jwilder/nginx-proxy
ADD ./src /etc/nginx/conf.d
ADD https://gitee.com/youclk/entry/raw/master/debian/sources-vpc.list /etc/apt/sources.list
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s CMD curl -f http://localhost/alive || exit 1
version: "3.5"
services:
proxy:
image: $REGISTRY/proxy
ports:
- "80:80"
- "81:81"
volumes:
- "/var/run/docker.sock:/tmp/docker.sock:ro"
deploy:
placement:
constraints: [node.role == manager]
restart_policy:
condition: on-failure
max_attempts: 3
update_config:
delay: 5s
order: start-first
resources:
limits:
cpus: "0.50"
memory: 1g
負載均衡使用的是阿裏雲的 SLB,監聽 80 -> 81, 443 -> 80
,這樣一個服務就實現了節點檢查、代理和 https 重定向為一身。拖 nginx 的福,反正用起來就是爽,點擊“Nginx 原理解析和配置摘要”進一步了解。
正所謂樂極生悲,某一次我在擴展 Swarm 集群的時候提升了部分 work 節點為 manager, 並且擴展了代理的數量,這讓很多服務頻繁出現 503,找來找去我發現問題出在 nginx-proxy 代理上。當服務在各節點分布不均的時候,非 leader 節點上的那個代理無法找到服務,廢了老大的勁兒也沒找到合理的解決方案。
最後我決定選擇“Docker Flow Proxy”作為新的代理(好家夥,這一看文檔嚇我一跳,我還是第一次看到私人的開源項目能把參考文檔寫得這麽詳細,作者的細膩程度“令人發指”,小弟頂禮膜拜之),以下是我的案例:
version: "3.5"
services:
proxy:
image: vfarcic/docker-flow-proxy
ports:
- "80:80"
networks:
- proxy
environment:
- LISTENER_ADDRESS=swarm-listener
- MODE=swarm
secrets:
- dfp_users_admin
deploy:
replicas: 2
labels:
- com.df.notify=true
- com.df.port="8080"
- com.df.serviceDomain="localhost"
- com.df.reqPathSearchReplace="/alive,/v1/docker-flow-proxy/ping"
swarm-listener:
image: vfarcic/docker-flow-swarm-listener
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DF_NOTIFY_CREATE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/reconfigure
- DF_NOTIFY_REMOVE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/remove
deploy:
placement:
constraints: [node.role == manager]
networks:
proxy:
external: true
secrets:
dfp_users_admin:
external: true
更換代理的過程也並非一帆風順,我在 https 重定向這個問題浪費了好多時間,最後也沒在代理中解決。作者當然是考慮到了這個問題,經典的解決方案應如下:
services:
proxy:
image: vfarcic/docker-flow-proxy
ports:
- "80:80"
- "443:443"
networks:
- proxy
environment:
- LISTENER_ADDRESS=swarm-listener
- MODE=swarm
deploy:
replicas: 2
labels:
- com.df.notify=true
- com.df.httpsOnly=true
- com.df.httpsRedirectCode=301
但奈何哥哥“非經典”呀,我的 https 證書和負載均衡都委托給阿裏雲的 SLB 了,SLB 代理的後端請求只能限定 http。我的想法還是監聽所有請求 443 端口的域名並返回 301,但以下方案並沒有成功:
labels:
- com.df.notify=true
- com.df.httpsRedirectCode=301
- com.df.serviceDomainAlgo=hdr_dom(host)
- com.df.srcPort.1=80
- com.df.port.1=8080
- com.df.serviceDomain.1=localhost
- com.df.reqPathSearchReplace.1=/alive,/v1/docker-flow-proxy/ping
- com.df.srcPort.2=443
- com.df.port.2=8080
- com.df.serviceDomain.2=youclk.com,localhost
- com.df.httpsOnly.2=true
當然重定向可以在各服務內部實現,但我不認為這是個好的解決方案。最後的最後,我想反正遲早都要上 CND,於是就在 CND 中加了 https 重定向(哎,就是帶寬的費用要 double 咯...):
除了代理,最好再加一個監控服務,我選擇了官方案例中的 visualizer ,配合 proxy 示例:
services:
visualizer:
image: dockersamples/visualizer
networks:
- proxy
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
labels:
- com.df.notify=true
- com.df.serviceDomain=visualizer.youclk.com
- com.df.port=8080
- com.df.usersSecret=admin
visualizer 算是敏感服務了,一般需要用密碼保護,這裏通過 com.df.usersSecret
指定了密碼文件,密碼已寫入 secrets dfp_users_admin
中。註意,com.df.usersSecret 的值與 dfp_users_* 必須相同,示例已在上文。部署後顯示如下:
docker-flow-proxy 還有一個默認的監控服務,顯示如下:
不過數據沒有統一收集,因此意義不大,看看就好。除此之外就是真正需要部署的應用了,只要服務器性能足夠,隨便想來幾個來幾個。
5. 部署與維護
docker stack
部署命令:docker stack deploy -c docker-compose.yml --with-registry-auth youclk
,私有倉庫必須加 --with-registry-auth
才能下載鏡像。除此之外常用的如下:
# network volume service secret 用法都類似,同出一系嘛...
docker stack ls
docker stack ps youclk
docker stack rm youclk
docker service
我使用 Compose 的場景一般都結合 Swarm,因此很少去記手動創建或者更改配置的命令了,意義也不大。除了查看移除等與上文相似以外,此處還應記兩個:
docker service logs --tail 10 youclk_proxy
docker service update --force youclk_proxy
分別是查看日誌和服務異常後強制重啟。
結語
到此為止寫了蠻多了,其余還有一些比較重要內容的後續有空再整理一篇。總結一下,開頭我放的那張圖其實很形象:Docker 可以看做集裝箱把雜亂的貨物一個個整理歸類, Compose 則是用於編排這些集裝箱,最後 Swarm 就是多提供幾條船,掛掉一兩條還能繼續走,提高穩定性。
不知為何此刻我會突然想到一句詩:“天蒼蒼野茫茫風吹草低見牛羊”,有關聯嗎?沒關聯,想到就寫了,晚安 :)
Docker 小記 — Compose & Swarm