上回在 Docker 開發筆記 - 從 Gitlab 觸發 CI/CD,製作 Docker Image 並發布到 Amazon Elastic Container Service (AWS ECS) 已經提到在 Gitlab.com 完成 Docker Images 的建置、打包、儲存至 Gitlab Container Registry 的架構,以及從 AWS ECS 上如何取得 Gitlab Container Registry 的資料,但沒有紀錄太多 AWS ECS 筆記,這次就完整順一次在 AWS ECS 發布服務時要經歷的事情。我們挑 AWS Japan Region (ap-northeast-1) 來做事。
第一步,可以先在 AWS ECS 建立 Task,例如取名為 myapp-task,過程中會設置 "基礎設施需求" 和 數個 "容器" 設定。
在 基礎設施需求 頁面,有幾個重點要留意:
- 啟動類型是 AWS Fargate 還是 Amazon EC2
- 網路類型
- 作業系統/架構,CPU, 記憶體
- 任務角色,是否有從 Container 內發送 AWS API 的需求,以及執行 Container 建置時,要用哪個角色(預設為 ecsTaskExecutionRole)
此例先以 AWS Fargate, Linux/X86_64, 1 vCPU, 2GB 為例。
接下來定義容器的部分,此例會定義兩個容器,一個是 web container ,一個以 nginx 為基礎的 Docker Image,另一個是 app container,一個是 php-fpm 為基礎的 Docker Image
容器 - 1,一個單純的 Laravel framework project- 名稱: php-fpm-docker- 映像 URI: registry.gitlab.com/MyGroup/MyProject/myapp-php-fpm:latest- 基本容器: 是- 私有登入檔案: On- Secrets Manager ARN 或名稱: arn:aws:secretsmanager:ap-northeast-1:####:secret:gitlab.com-container-registry- 連接映射- 容器連接 9000, 通訊協定 TCP, 連接阜名稱 9000, 應用程式通訊協定 HTTP- 唯讀根檔案系統, 唯讀: 否- CPU: 0.5- 記憶體硬性限制: 1.5GB, 記憶體軟性限制: 1.5GB- 環境變數- PHPFPM_HOST, localhost- PHPFPM_PORT, 9000- 運作狀態檢查: CMD-SHELL,/usr/local/bin/healthcheck.sh
容器 - 2,一個單純的 nginx web server- 名稱: web-docker- 映像 URI: registry.gitlab.com/MyGroup/MyProject/myapp-nginx:latest- 基本容器: 是- 私有登入檔案: On- Secrets Manager ARN 或名稱: arn:aws:secretsmanager:ap-northeast-1:####:secret:gitlab.com-container-registry- 連接映射- 容器連接 80, 通訊協定 TCP, 連接阜名稱 80, 應用程式通訊協定 HTTP- 唯讀根檔案系統, 唯讀: 否- CPU: 0.5- 記憶體硬性限制: 0.5GB, 記憶體軟性限制: 0.5GB- 環境變數- PHPFPM_HOST, localhost- PHPFPM_PORT, 9000- 運作狀態檢查: CMD-SHELL,/usr/local/bin/healthcheck.sh- 啟動相依性排序: php-fpm-docker, 條件: Healthy
其中的 私有登入檔案 就是用來存取 registry.gitlab.com Container Registry 的地方,請記得在 Gitlab.com Project 建立 Deploy token 得到一組帳密:
接著到 AWS Secrets Manager 添加一組,在此定為 gitlab.com-container-registry 名稱:
https://ap-northeast-1.console.aws.amazon.com/secretsmanager/listsecrets?region=ap-northeast-1
添加完得到 arn:aws:secretsmanager:ap-northeast-1:####:secret:gitlab.com-container-registry。
下一刻,到 AWS IAM Roles 設定,讓 ecsTaskExecutionRole 可以讀取 secretsmanager 內的資料:
https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/roles
在此透過自訂一個足夠大的權限即可,例如可讀必要的資料即可(建議限制 Resource 可以存取地區甚至命名規則等):
如此,終於把 AWS ECS - Task 運行時所要的設定都安置好了,下一刻就是回到 AWS ECS - Cluster,建立一個 myapp-cluster,其中基礎設施維持使用 AWS Fargate (無伺服器) 模式,接著來建立 Service ,建立 AWS ECS -> Cluster -> Service 時,就比較多要留意的地方
- 環境
- 運算組態: 都用預設的,需看一眼確認運算選項: 容量供應商策略
- 部署組態:
- 應用程式類型: 服務
- 任務定義: myapp-task (也就是剛剛創立的 myapp,內有定義兩個容器)
- 服務名稱: myapp-service
- 聯網:
- VPC, 子網路: 需要留意, 可自行設計
- 安全群組: 需要留意, 可自行設計
- 公有 IP: 開啟
- Load Balancer
- 使用負載平衡
- 負載平衡器類型: Application Load Balancer
- 容器: web-docker 80:80
- Application Load Balancer: 建立新的負載平衡器
- 負載平衡器名稱: myapp-service-load-balanacer
- 接聽程式: 80, HTTP
- 目標群組: 建立新的目標群組, 目標群組名稱 ecs-myapp--myapp-service, 通訊協定 HTTP, 取消註冊延遲 300, 運作狀態檢查通訊協定 HTTP, 運作狀態檢查路徑 /
按下建立 myapp-service 後,在 AWS ECS Cluster 頁面上不一定會馬上看到,有時需要重整頁面數次才會看到資訊,接著我們進去 Amazon Elastic Container Service -> 叢集 -> myapp-cluster -> 服務觀看:
https://ap-northeast-1.console.aws.amazon.com/ecs/v2/clusters/myapp-cluster/services?region=ap-northeast-1
再往下點進 myapp-service 進入到運作狀態,如網址 https://ap-northeast-1.console.aws.amazon.com/ecs/v2/clusters/myapp-cluster/services/myapp-service/health?region=ap-northeast-1 等
接著也可以點擊 Load Balancer 得知分配到的 DNS name,如 myapp-service-load-balanacer-##########.ap-northeast-1.elb.amazonaws.com 去瀏覽。此外 Load Balancer 這邊也有個小技巧,其實可以讓容器只提供 HTTP 80 服務,單純從 Load Balancer 增加 HTTPS 443 的服務,並把流量導過去 80,這也是讓 Load balancer 幫你扛掉一些算力的架構,但自身的服務也需要一些判斷設定,避免一直無限強制跳轉到 https 等。
如此,就算完成一個服務部署了,練習後想要刪光資源,記得刪掉 Service 後,有用到 Load balancer 的,還要特別去 EC2 Load Balancer 去刪除釋放掉資源,該 Load Balancer 就像一組 EC2 機器,創建多久算多久的錢。
接下來的實驗,就不需依賴 Load Balancer ,可以單純有個 Public IP 可存取即可。刪掉 Service 後,再重新創一個,接著我們要設計 CI/CD 架構,如何通報 AWS ECS 更新服務(Containers),這邊研究一下後,有兩種設計原理:
- 不停的去追蹤 registry.gitlab.com/MyGroup/MyProject/myapp-php-fpm:latest 和 registry.gitlab.com/MyGroup/MyProject/myapp-nginx:latest 是否有更新,有更新時透過 aws cli 通報 AWS ECS -> Cluster -> Service -> 觸發更新任務
- 在 Gitlab.com 創建 Docker images 時,最後一刻發動 aws cli 通報 AWS ECS -> Cluster -> Service -> 觸發更新任務
其中 (1) 的策略是透過 Amazon EventBridge 去排程執行,原理上需要記錄 Container Registry 的狀態,像是每次執行時,把資料記錄在 DB 內,下次檢查發現有變動時,呼叫 "AWS ECS -> Cluster -> Service -> 觸發更新任務" 後,再把狀態記錄下來,此優點是不用暴露 AWS 的資訊出去,缺點是效率較差,以及需要額外資料紀錄。此例 CI/CD 實踐是用 (2) 招式,建立一個身份可以完成 "AWS ECS -> Cluster -> Service -> 觸發更新任務" 即可。
首先在 IAM 創立一個人員: gitlab.com-call-AWS-ECS-Cluster-Service-Update
https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/users/create
接著建立政策: https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/policies/create
給予以下權限
- Read: DescribeServices
- Write: UpdateService
- Specify ARNs: ap-northeast-1, myapp-cluster
取名為: AWS-ECS-Cluster-Service-Update
如此,後續就可以改到 Gitlab.com 串 CI/CD 了,例如 .gitlab-ci.yml 和 tool/aws/ecs-update-service.sh:
deploy_to_ecs:stage: deployimage: alpine:latestneeds:- job: web_docker_image_buildoptional: true- job: app_docker_image_buildoptional: truebefore_script:- apk add --no-cache aws-cli curl jq tree bash- echo "AWS_ACCESS_KEY_ID = ${AWS_ACCESS_KEY_ID}"- echo "AWS_SECRET_ACCESS_KEY = ${AWS_SECRET_ACCESS_KEY}"- echo "AWS_DEFAULT_REGION = ${AWS_DEFAULT_REGION}"- echo "ECS_DEPLOY_ENABLE = ${ECS_DEPLOY_ENABLE}"- echo "ECS_CLUSTER = ${ECS_CLUSTER}"- echo "ECS_SERVICE = ${ECS_SERVICE}"script:- chmod +x tool/aws/ecs-update-service.sh- bash tool/aws/ecs-update-service.shrules:- if: $CI_COMMIT_TAG =~ /^web\-v\d+\.\d+\.\d+$/when: on_success- if: $CI_COMMIT_TAG =~ /^app\-v\d+\.\d+\.\d+$/when: on_success- if: '$CI_PIPELINE_SOURCE == "web"'when: on_success- if: '$CI_PIPELINE_SOURCE == "api"'when: on_success
當創建了相關命名規則的 git tag 後,將觸發 deploy stage 做事,在此規劃 Gitlab.com Project -> Settings -> CI/CD 需要定義相關參數,當參數存在時,就會透過 AWS cli 更新雲端的服務,邏輯上依序呼叫:
- aws ecs describe-services --cluster myapp-cluster --services myapp-service | jq '.services | .[] | .events'
- aws ecs wait services-stable --cluster myapp-cluster --services myapp-service && echo "Status is stable"
- aws ecs update-service --cluster myapp-cluster --service myapp-service --force-new-deployment
- aws ecs wait services-stable --cluster myapp-cluster --services myapp-service && echo "Done"
以上就是稍微完整的流程,但仍有很多有趣的地方沒有細說,包括 Docker 的建置、AWS VPC/Security Group/NAT Gateway/ECS Container 啟動類型的影響/Container 狀態檢查該怎樣設計/Zero Downtime/刪除 ECS 服務後仍要手動釋放Load Balancer等,此外,在 Gitlab.com 的 git tag 事件也有滿多有趣的,包括觸發 deploy 跟 build 的 git tag 規則是可以不一樣的,以及在 Gitlab.com 上的 Runner 運行了敏感資料 (aws cli, AWS Access Key ID, AWS Secret Access Key)、是否到底足夠信任挑選的 docker images 等等,都是值得深思的議題。
沒有留言:
張貼留言