任何相對完整的應用服務都不多是由單一的程序來完成支持,計劃使用 Docker 來部署的服務更是如此。大型服務須要進行拆分,造成微服務集羣方能加強其穩定性和可維護性。本篇隨筆將對 Docker Compose 和 Docker Swarm 的原理和配置作整理概括,並分享其使用經驗。
html
Docker Compose 的配置文件採用 YAML 格式,所以有必要在正文以前簡要說明下。YAML 是一門專門用來寫配置文件的語言,設計目標就是方便讀寫,其實質上是一種通用的數據串行化格式,基本語法規則以下:node
#
表示註釋。YAML 支持的數據結構有三種:nginx
animal:cat
。# ex1 - cat - dog - bird # ex2 - - cat - dog - bird # ex3 animal: [cat, dog, bird]
Docker 能夠極爲方便地部署單個服務,但這時候咱們須要一個工具來整合 Docker 的功能,使之可以更便捷地去管理整個微服務集羣的部署和遷移,Docker Compose 正是應此而生。他是由 Python 編寫的程序,可以根據指令結合配置文件轉換成對應的 Docker API 的操做,並直接體現到 Docker Daemon 中,這就代替咱們完成了重複輸入複雜指令的過程,主要功能可分爲如下兩點:git
安裝命令:github
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
Docker Compose 的核心就是其配置文件,採用 YAML 格式,默認爲 docker-compose.yml
,參數詳解可查閱「官方文檔」,如下只作一個常規摘要。web
全部服務的根節點。redis
指定服務的鏡像名,若本地不存在,則 Compose 會去倉庫拉取這個鏡像:docker
services: web: image: nginx
端口映射,例:json
ports: - "80:80" - "81:81"
掛載主機目錄,其中 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 "sshcmd=user@1.2.3.4:/remote" \ -o "password=$(cat file_containing_password_for_remote_host)\ sshvolume
配置服務間的網路互通與隔離,例:
services: web: image: nginx networks: - proxy - youclk networks: youclk: external: true proxy: external: true
配置服務密碼訪問,例:
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: 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: - redis
設置環境變量和指定環境變量的文件,例:
environment: - VIRTUAL_HOST=test.youclk.com env_file: - ./common.env
部署相關的配置都在這個節點下,例:
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]
若非特殊服務,以上各節點的配置可以知足大部分部署場景了。
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
脫離集羣。
如下各節點常規操做命令,比較簡單,就不解釋了:
集羣最擅長的就是解決多服務問題,只要在同一 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 還有一個默認的監控服務,顯示以下:
不過數據沒有統一收集,所以意義不大,看看就好。除此以外就是真正須要部署的應用了,只要服務器性能足夠,隨便想來幾個來幾個。
部署命令: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
我使用 Compose 的場景通常都結合 Swarm,所以不多去記手動建立或者更改配置的命令了,意義也不大。除了查看移除等與上文類似之外,此處還應記兩個:
docker service logs --tail 10 youclk_proxy docker service update --force youclk_proxy
分別是查看日誌和服務異常後強制重啓。
到此爲止寫了蠻多了,其他還有一些比較重要內容的後續有空再整理一篇。總結一下,開頭我放的那張圖其實很形象:Docker 能夠看作集裝箱把雜亂的貨物一個個整理歸類, Compose 則是用於編排這些集裝箱,最後 Swarm 就是多提供幾條船,掛掉一兩條還能繼續走,提升穩定性。
不知爲什麼此刻我會忽然想到一句詩:「天蒼蒼野茫茫風吹草低見牛羊」,有關聯嗎?沒關聯,想到就寫了,晚安:)
個人公衆號《有刻》,咱們共同成長!