Rails 部署實踐 - 部署到 Docker Swarm
部署的環境已經準備好後,我們就可以來將之前所撰寫的 Docker Compose 設定檔轉換為 Docker Swarm 支援的格式,以及調整我們的架構來讓專案可以被部署到 Docker Swarm 上面。
搭建入口
想要讓使用者可以連上我們部署的網站就需要入口,最簡單的方式就是直接連上我們在 Rails 部署實踐 - Docker Swarm 安裝與設定中所建立好的 Manager Node。
然而,我們在大多數的應用情境中,如果一次需要被一個專案佔用 HTTP(80)和 HTTPS(443)這兩個埠號(Port),就很難部署其他應用,或者將服務拆分出來應用,為了對應這樣的情境,在 Kubernetes 就設計了 Ingress(入口)的機制,我們可以選則直接佔用某個埠號,或者利用雲端服務的 Load Balancer(負載平衡)來以 Host(主幾名稱)的方式區分,將流量引導到對應的服務上。
使用 Docker Swarm 的時候我們可以善用在 Rails 部署實踐 - 使用 HTTPS 協定加密連線我們所採用的 Traefik Proxy 來擔任這個角色,因此我們可以先部署一個 Stack(Docker Swarm 一組服務的單位)來設定 Traefik 負責處理流量的轉發。
首先,我們需要在任意一台 Manager Node 上建立一個「共用」的網路,讓其他服務可以被 Traefik 透過這個網路存取到。
1$ docker network create --driver=overlay traefik-public
接下來撰寫 Traefik 的 Docker Compose 設定,將每一個 Manager Node 都部署一個 Traefik Proxy 來處理轉發。
1# traefik.yml
2version: '3.8'
3
4services:
5 traefik:
6 image: traefik:v2.6
7 ports:
8 - target: 80
9 published: 80
10 mode: host
11 - target: 443
12 published: 443
13 mode: host
14 deploy:
15 mode: global # 在所有節點部署
16 placement:
17 constraints:
18 - node.role == manager # 限定 Manager 節點
19 labels:
20 - traefik.enable=true
21 - traefik.docker.network=traefik-public
22 volumes:
23 - /var/run/docker.sock:/var/run/docker.sock:ro
24 - traefik-public-certificates:/certificates
25 environment:
26 - CLOUDFLARE_DNS_API_TOKEN=[YOUR_TOKEN]
27 command:
28 - --providers.docker
29 - --providers.docker.exposedbydefault=false
30 - --providers.docker.swarmmode # 啟用 Swarm 模式
31 - --entrypoints.web.address=:80
32 - --entrypoints.websecure.address=:443
33 - --[email protected]
34 - --certificatesresolvers.letsencrypt.acme.storage=/certificates/acme.json
35 - --certificatesresolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare
36 - --accesslog
37 - --log
38 networks:
39 - traefik-public
40
41volumes:
42 traefik-public-certificates:
43
44networks:
45 traefik-public:
46 external: true
新的 Traefik Stack 設定跟之前基本上是差不多的,然而有幾點不太一樣的地方需要注意。
- 增加
deploy
設定,限定在所有 Manager Node 部署,確保所有節點都能連上 - 改為使用 DNS Challenge 模式來取得 Let’s Encrypt 的 SSL 憑證,這是因為 Traefik Enterprise 版本才有跨節點共用憑證的機制,如果使用 HTTP 驗證模式我們可能會遇到部分節點沒有部署到的狀況
- 網路設定增加了
external: true
表示這不需要由這個 Stack 建立,是已經存在的網路
完成設定後,我們可以複製檔案到任何一台 Manager Node 部署,或者在自己的電腦上利用設定 DOCKER_HOST
環境變數暫時切換到遠端的 Docker 節點。
1$ export DOCKER_HOST=ssh://user@remotehost
2$ docker stack deploy -c traefik.yml ingress
成功部署後,我們在每個 Manager Node 直接使用 IP 開啟,就會變成 404 Not Found
的訊息。
部署服務
解決了入口問題後,我們就可以開始著手將服務部署到 Docker Swarm 上。我們可以繼續修改之前用來部署的 Docker Compose 留下服務的部分,來撰寫用於部署的設定。
1# my-app.yml
2
3services:
4 # ...
5 rails:
6 image: "registry.example.com/myapp:${VERSION:-latest}"
7 restart: unless-stopped
8 environment:
9 - DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB
10 - RAILS_MASTER_KEY
11 deploy:
12 placement:
13 constraints:
14 - node.role != manager
15 update_config:
16 parallelism: 2
17 delay: 10s
18 order: start-first
19 labels:
20 - "traefik.enable=true"
21 # Hosts
22 - "traefik.http.services.rails.loadbalancer.server.port=3000"
23 - "traefik.http.services.rails.loadbalancer.sticky.cookie=true"
24 - "traefik.http.routers.rails-http.rule=Host(`demo.aotoki.dev`)"
25 - "traefik.http.routers.rails-http.entrypoints=web"
26 - "traefik.http.routers.rails-http.middlewares=https-redirect"
27 - "traefik.http.routers.rails-https.rule=Host(`demo.aotoki.dev`)"
28 - "traefik.http.routers.rails-https.entrypoints=websecure"
29 - "traefik.http.routers.rails-https.tls=true"
30 - "traefik.http.routers.rails-https.tls.certresolver=letsencrypt"
31 networks:
32 - traefik-public
33
34networks:
35 traefik-public:
36 external: true
在這邊我們同樣的加入了 deploy
相關的設定,雖然 Manager Node 也能夠運行服務,然而如果服務的負擔太大的會可能會造成卡住,資源足夠的狀況下會設定為不要部署在 Manager Node 上來確保入口是順暢的。
在 Docker Swarm 也很容易去設定 Rolling Upgrade(滾動更新)也就是將舊版的節點跟新版的節點同時並存,因此我們可以設定同時能存在 2 個服務實體,並且先將新版本啟動等待 10 秒後再關閉舊版,這樣就不會出現更新中服務有一瞬間中斷的狀況。
因為 Rails Assets Precompile 的機制,這個做法會有一個缺點是 CSS 和 JS 會因為使用者有幾秒是能同時連到不同服務實體的,造成無法讀取到正確的 CSS 和 JS 而出現跑版的狀況,因此我們額外加上了 sticky
的設定,讓 Traefik 幫我們保持同一個使用者會連接到同一個實體,來避免這個問題。
不要忘記使用
docker login
讓 Docker Swarm 可以順利下載到容器鏡像
持久化資料難題
當我們從單一節點轉變為叢集(Cluster)的時候,必定會面臨儲存資料的問題。舉例來說,當我們的資料庫從 A 節點轉移到 B 節點的時候,我們該如何在 B 節點恢復資料庫呢?
在 Docker Swarm 最簡單的做法是使用 constriants
(限制)的機制,將有需要持續保存資料的服務固定在單一一台伺服器上,這樣就能暫時性的解決這類問題,然而我們還是會遭遇單一伺服器故障造成所有服務停擺的狀況。
這也不難看出為什麼在 12 Factor 的設計中,會將資料庫等等這類服務單獨拆分出來,這樣我們就可以考慮在部署服務時,以獨立的 Stack 方式搭建 PostgreSQL 或 MySQL 的叢集來確保可用性,並利用 Docker Swarm 的 Overlay 網路串聯起來。
或者我們可以利用像是 AWS RDS 這類雲端服務,減少我們自行搭建服務的困難,至於如何取捨就完全基於我們怎樣規劃,大多數時候能使用 RDS 這類服務維護成本會相對的降低更多。
如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用Rails 部署實踐回饋表單告訴我。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- Rails 部署實踐 - 補上 Rails 教學缺少的一塊
- Rails 部署實踐 - 以容器部署 Rails 的方案
- Rails 部署實踐 - 部署前置準備
- Rails 部署實踐 - 容器化 Rails 專案概述
- Rails 部署實踐 - 上傳容器鏡像
- Rails 部署實踐 - 伺服器搭建
- Rails 部署實踐 - 撰寫 Docker Compose
- Rails 部署實踐 - 使用 HTTPS 協定加密連線
- Rails 部署實踐 - 健康檢查
- Rails 部署實踐 - 滾動更新
- Rails 實踐部署 - 使用 Alpine 製作容器鏡像
- Rails 部署實踐 - 容器化的 Bundler 最佳設定
- Rails 部署實踐 - 多階段建置
- Rails 部署實踐 - 素材預先編譯
- Rails 部署實踐 - 容器進入點
- Rails 部署實踐 - 容器相關工具
- Rails 部署實踐 - 持續部署
- Rails 部署實踐 - 使用 GitLab CI 自動化建置
- Rails 部署實踐 - 使用 GitHub Actions 自動化建置
- Rails 部署實踐 - 使用 Watchtower 自動更新
- Rails 部署實踐 - Docker Swarm 與 Docker Compose
- Rails 部署實踐 - Docker Swarm 安裝與設定
- Rails 部署實踐 - 部署到 Docker Swarm
- Rails 部署實踐 - 整合 GitLab CI 自動部署
- Rails 部署實踐 - 使用 GitLab 的 Review Apps 機制
- Rails 部署實踐 - 部署不是終點