Rails 部署實踐 - 整合 GitLab CI 自動部署
當我們從 Docker Compose 轉換到 Docker Swarm 之後,仍然還是面臨需要人工進行部署操作的狀況,因此我們還需要更近一步的利用 GitLab CI 來幫助我們解決部署的人工操作。
讓 GitLab CI 控制 Docker
GitLab CI 提供了非常多執行的方式,其中一個就是 Docker 模式,也因此我們可以利用這樣的特性來達到在 Docker Swarm 自動部署的效果。
這個技巧來自 Docker Swarm Rocks 網站上的介紹,簡單來說我們在 Manager Node 上以 Docker 的方式啟動 GitLab Runner 並且註冊到 GitLab 上面,因為 GitLab Runner 的 Docker 模式是利用將 /var/run/docker.sock
掛到容器中的方式啟動,自然而然的我們使用 docker
鏡像來執行任務時,自然就等同於對 Manager Node 進行操作。
接下來只需要處理 GitLab Runner 的註冊方式,像是使用標籤(Tag)來限制,或者以 Group Runner 的方式限定在某個團隊下。
在所有或者其中一個 Manager Node 使用以下命令。
1$ docker run -d \
2 --name gitlab-runner \
3 --restart always \
4 -v gitlab-runner:/etc/gitlab-runner \
5 -v /var/run/docker.sock:/var/run/docker.sock \
6 gitlab/gitlab-runner:latest
這樣就啟動了一個「未設定」的 GitLab Runner,接下來我們利用 docker exec
命令進入到容器進行設定。
1$ docker exec -it gitlab-runner bash
進入後,使用 GitLab Runner 的 register
命令進行註冊,這邊可以根據偏好直接填完所有參數,或者透過互動模式一步一步的回答。
1# Interactive Mode 適合節點數較少狀況
2$ gitlab-runner register
要記得在 GitLab 限制這類容器只在有對應的標籤狀況下啟用,以免普通任務佔用到這些節點,造成 Manager Node 的負擔。
GitHub Actions 要採用類似的方案就複雜很多,基於資安上的考量避免將 Docker 的 API 暴露在外網會更好,同時 GitHub Actions 的自訂 Runner 也暫時沒有容器的版本可用,因此無法利用這個技巧,可能就得使用像是 AWS Code Deploy 之類的方式繞一圈來實現。
自動化部署樣板
我們在 Rails 部署實踐 - 撰寫 Docker Compose 曾經提到過 Docker Compose 是可以使用環境變數的,像是利用 .env
來設定,進而避免直接寫在設定檔中,這個特性在 Docker Swarm 也是能夠生效的。
然而,因為命令的設計不太一樣,因此無法讀取到 .env
的設定,必須是使用 export
命令,或者原本存在的環境變數。這個問題在 GitLab CI 中反而不是太麻煩的問題,因此我們可以善用 .gitlab-ci.yml
的設定、GitLab 本身針對不同部署環境套用不同變數的性質,我們可以很彈性的調整。
這可以使用 elct9620/ruby-gitlab-ci 這個專為 Ruby 和 Rails 專案設計的 GitLab 樣板中讓 GitLab 的 Review Apps 能被自動部署的範例作為參考。
1services:
2 # ...
3 application:
4 image: "${IMAGE_NAME}:${IMAGE_TAG}"
5 environment:
6 - AUTO_MIGRATION=yes # Provided by "openbox" gem
7 - DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB
8 - RAILS_MASTER_KEY
9 deploy:
10 placement:
11 constraints:
12 - node.role != manager
13 labels:
14 - traefik.enable=true
15 - traefik.docker.network=traefik-public
16 # Hosts
17 - "traefik.http.routers.${DEPLOY_NAME}-http.rule=Host(`${DEPLOY_DOMAIN}`)"
18 - "traefik.http.routers.${DEPLOY_NAME}-http.entrypoints=web"
19 - "traefik.http.routers.${DEPLOY_NAME}-http.middlewares=https-redirect"
20 - "traefik.http.routers.${DEPLOY_NAME}-https.rule=Host(`${DEPLOY_DOMAIN}`)"
21 - "traefik.http.routers.${DEPLOY_NAME}-https.entrypoints=websecure"
22 - "traefik.http.routers.${DEPLOY_NAME}-https.tls=true"
23 - "traefik.http.routers.${DEPLOY_NAME}-https.tls.certresolver=letsencrypt"
24 - "traefik.http.routers.${DEPLOY_NAME}-https.tls.domains[0].sans=*.${DEPLOY_BASE_DOMAIN}"
25 - "traefik.http.services.${DEPLOY_NAME}.loadbalancer.server.port=3000"
26 networks:
27 - net
28 - traefik-public
29 depends_on:
30 - postgres
31# ...
從上面這段範例可以看出來,裡面大量使用了環境變數。這就表示當我們設計出一個標準的版本後,可以非常容易地套用在不同的專案中,進而快速讓我們把 Rails 專案部署到 Docker Swarm 上。
上述這段範例有不少環境變數都是樣板客製化的,我們可以簡單的來看一下。
像是 IMAGE_NAME
和 IMAGE_TAG
是從 CI_REGISTRY_IMAGE
和 CI_COMMIT_SHORT_SHA
這兩個 GitLab CI 的預先定義環境變數,這樣我們就能夠針對專案、Commit SAH 製作指定的版本,在需要「退版」的時候也可以透過指定版本處理,而且只需要重新執行某次 GitLab CI 的 Pipeline 即可。
至於 DEPLOY_NAME
則是由 CI_PROJECT_ID
和 CI_ENVIRONMENT_SLUG
所組成,這樣就可以產生像是 91-production
的 Docker Stack 命名空間(群組)也能夠很好處理在 GitLab 上有複數專案的情況。
最後我們來看一下 .gitlab-ci.yml
的部署任務該怎麼撰寫。
1deploy:
2 image: docker:stable
3 stage: deploy
4 environment:
5 name: production
6 url: $DEPLOY_DOMAIN
7 before_script:
8 - echo "$CI_JOB_TOKEN" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
9 - apk -Uuv add curl
10 - curl -LO https://github.com/sudo-bmitch/docker-stack-wait/raw/main/docker-stack-wait.sh
11 - chmod +x docker-stack-wait.sh
12 script:
13 - docker stack deploy -c $DEPLOY_STACK_FILE --with-registry-auth --prune $DEPLOY_NAME
14 - sleep $DEPLOY_WAIT_TIME
15 - ./docker-stack-wait.sh -r $DEPLOY_NAME
首先,我們使用 CI_JOB_TOKEN
來登入 GitLab 的 Registry 如此一來當任務結束時,我們的 Docker Swarm 就會自動的禁止存取容器鏡像,這樣會相對安全,因為能避免被抓取到不想被存取的鏡像。
接下來加入了 docker-stack-wait.sh
這個腳本,主要是因為 docker stack deploy
這個命令實際上是不會「確認部署成功」的,為了讓我們有機會注意到部署失敗,就可以利用這個腳本來幫忙,刻意的「等待部署」來確認是否成功。
另一個原因則是 CI_JOB_TOKEN
也會因為任務結束而失效,如果我們的 Docker Swarm 還在下載要部署的鏡像,即使部署本身沒有問題也會失敗,因此需要確認「部署完畢」才停止部署的任務。
如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用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 部署實踐 - 部署不是終點