我平常從事後端工程師的工作,經常在專案中使用 Terraform 和 ecspresso,然而這兩者的角色分配乍看之下似乎不太明確。本文將透過建立使用 Terraform 和 ecspresso 的範例專案來說明它們的生命週期差異。
「ecspresso 是專門用於管理 ECS 的工具。」
聽到這句話時,我心中不禁產生疑問:「Terraform 不是也可以管理 ECS 嗎?那為什麼還需要 ecspresso?」首先,我們來整理 AWS 資源的生命週期,確認 ecspresso 的適用範圍。
首先,Terraform 負責整體 AWS 的配置管理。大多數 AWS 資源在設定後變更的頻率較低。而在 ECS 上啟動的映像,卻是由應用程式工程師開發並經常進行部署。在具備 CI/CD 流程的優秀團隊中,可能會有每天多次高頻率的應用程式部署。
在這種情況下,如果使用 Terraform 來更新 ECS,會出現以下問題:
terraform apply
時,ECS 的狀態可能會被回滾至之前的狀態。尤其在商業服務中,為了避免事故,往往希望將頻繁變更的 ECS 管理與其他變更頻率低的 AWS 資源管理區分開來。
因此,基於基礎架構中生命週期較短的資源,如 ECS,應避免使用 Terraform 管理,而是改用其他管理工具。ecspresso 正是擔負此任務的工具。
項目 | Terraform | ecspresso | 變更頻率 |
---|---|---|---|
ECS 任務定義 | ○ | 高 | |
ECS 服務 | ○ | 高 | |
ECS 叢集 | ○ | 低 | |
其他 AWS 資源 | ○ | 低 |
在開發流程中,ECS 以外的基礎架構管理應托管於基礎架構專用的 Git 倉庫,而應用程式的部署和 ECS 資源管理則應托管於應用程式專用的 Git 倉庫。
如此一來,應用團隊與基礎架構團隊的責任邊界就變得明確,應用團隊可以自行管理應用程式的部署,而無需向基礎架構團隊請求變更。此外,這樣也能減少在應用部署時與基礎架構團隊的溝通成本。
以下程式碼是管理 ECS 的 tf 文件範例。最初是使用 Terraform 全面管理 ECS,但 services
下的部分以及 container_definitions
已被註解。因為這些不再屬於 terraform.tfstate 的管理範圍,所以即使重新部署容器,也不會在 tfstate 中出現差異。
module "ecs" {
source = "terraform-aws-modules/ecs/aws"
# 注意:Terraform 僅管理 ECS 叢集
cluster_name = "${var.product_name}-${var.env}"
cluster_configuration = {
}
default_capacity_provider_strategy = {
FARGATE_SPOT = {
weight = 1
}
}
# 注意:刪除 Terraform 的 ECS 服務定義,接下來使用 ecspresso 來管理 ECS 服務
services = {
# fastapi-service = {
# cpu = 256
# memory = 512
# desired_count = 1
# 注意:刪除 Terraform 的任務定義,轉移至 ecspresso
# container_definitions = {
# fastapi = {
# image = data.aws_ecr_image.this.image_uri
# essential = true
# ... (以下略)
# }
}
}
※ 參考:最初使用的 Terraform 模組
在之前的 modules/ecs/main.tf 中被註解的部分,將會移至 ecspresso 的 ecs-service-def.jsonnet
和 ecs-task-def.jsonnet
進行管理。這兩者分別控制 ECS 服務和 ECS 任務定義。執行 ecspresso deploy
命令後,只有 ECS 服務和任務將會被更新。
{
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": true,
"rollback": false
},
"maximumPercent": 200,
"minimumHealthyPercent": 50
},
"deploymentController": {
"type": "ECS"
},
"desiredCount": 1,
"enableECSManagedTags": true,
"enableExecuteCommand": true,
"healthCheckGracePeriodSeconds": 30,
"launchType": "FARGATE",
"networkConfiguration": {
"awsvpcConfiguration": {
"assignPublicIp": "ENABLED",
"securityGroups": [
"{{ tfstate `module.ecs.aws_security_group.ecs_sg.id` }}"
],
"subnets": [
"{{ tfstate `module.vpc.module.vpc.aws_subnet.public[0].id` }}",
"{{ tfstate `module.vpc.module.vpc.aws_subnet.public[1].id` }}"
]
}
},
"pendingCount": 0,
"platformFamily": "Linux",
"platformVersion": "LATEST",
"propagateTags": "NONE",
"runningCount": 0,
"schedulingStrategy": "REPLICA"
}
{
"containerDefinitions": [
{
"cpu": 0,
"essential": true,
"image": "{{ tfstate `module.ecs.data.aws_ecr_repository.this.repository_url`}}:latest",
"interactive": false,
"linuxParameters": {
"initProcessEnabled": true
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "{{tfstate `module.ecs.module.ecs.module.cluster.aws_cloudwatch_log_group.this[0].name`}}",
"awslogs-region": "{{ tfstate `module.ecs.module.ecs.module.cluster.aws_cloudwatch_log_group.this[0].region` }}",
"awslogs-stream-prefix": "ecs"
}
},
"name": "fastapi",
"portMappings": [
{
"containerPort": 8000,
"hostPort": 8000,
"protocol": "tcp"
}
],
"privileged": false,
"pseudoTerminal": false,
"readonlyRootFilesystem": false,
"startTimeout": 30,
"stopTimeout": 120,
"healthCheck": {
"command": [
"CMD-SHELL",
"curl -f http://localhost:8000/ || exit 1"
],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
}
}
],
"cpu": "256",
"executionRoleArn": "{{ tfstate `module.ecs.aws_iam_role.ecs_task_execution_role.arn` }}",
"family": "{{ tfstate `module.ecs.module.ecs.module.cluster.aws_ecs_cluster.this[0].tags.ServiceName` }}",
"ipcMode": "",
"memory": "512",
"networkMode": "awsvpc",
"pidMode": "",
"requiresCompatibilities": [
"FARGATE"
],
"runtimePlatform": {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX"
},
"taskRoleArn": "{{tfstate `module.ecs.aws_iam_role.ecs_task_role.arn`}}"
}
若想了解更詳細的使用方式,建議參考 ecspresso 作者所著的書籍。(¥500)
在應用程式倉庫中,設定當主分支有程式碼合併時,自動執行 GitHub Actions。GitHub Actions 的工作流程應如以下所示。
latest
標籤和提交哈希name: ECS 部署
on:
push:
branches:
- main
workflow_dispatch: # 供 GitHub UI 手動執行
jobs:
build_and_push:
name: 建立 Docker 並將 Docker 映像推送至 ECR
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: 檢出程式碼
uses: actions/checkout@v4
- name: 設定 AWS 憑證
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_AllowGitHubActions }}
aws-region: ap-northeast-1
- name: 登入 Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: 建立、標記並推送映像至 Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: n700a/fastapi-mini
IMAGE_TAG: latest # ${{ GitHub.sha }}
run: |
docker build -f backend/Dockerfile -t $ECR_REPOSITORY:$IMAGE_TAG backend
docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
deploy:
name: 部署至 ECS
needs: build_and_push
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: 檢出程式碼
uses: actions/checkout@v4
- name: 設定 AWS 憑證
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_AllowGitHubActions }}
aws-region: ap-northeast-1
# 在此執行 ecspresso deploy
- name: 部署至 ECS 服務
uses: kayac/ecspresso@v2
with:
version: v2.6.0
args: deploy --config ./infra/dev/ecspresso/ecspresso.yml
設定此工作流程後,應用團隊將不再需處理 ECS 的部署任務,得以專注於應用程式源コード的創作。
本文提到在 AWS 資源中,應用領域與基礎設施領域的部署頻率不同,並指出全用 Terraform 管理的問題。藉由同時使用 ecspresso 和 Terraform,可以明確劃分 AWS 資源中的應用管理與基礎架構管理區域。
本文未詳細說明 ecspresso 的具體環境建構方法。如有興趣,建議親自試試看。
希望本文能讓讀者更了解 ecspresso 的意義及概念。
<iframe id="qiita-embed-content360a5c3b9c84f9a9cb8c18b8b321f484"></iframe>
<iframe id="qiita-embed-content33c1a8d3cce3cb52534f600f38604321"></iframe>