AWS CDK + ecspresso + CodeDeployを使用してECSをデプロイする
目次
こんにちは、クラウドビルダーズのきむです。
業務でECSを利用する機会があったのでECSのデプロイツールとしてecspressoを採用してみました。
今回はAWS CDK + ecspresso + CodeDeployを利用して環境を構築する方法を紹介します。
ecspressoとは
ecspressoはECSサービスのデプロイに特化したツールであり、ECSサービスとタスクに関わるリソースをコードとして管理します。
exspressoは下記の3ファイルで構成されています。
- ecs-service-def: ECSサービス定義
- ecs-task-def: ECSタスク定義
- ecspresso: ecspresso全体の動作定義
ecspressoでは下記の形式がサポートされています。
- yaml
- json
- jsonnet
ecspressoのファイルではSecrets ManagerやParameter Storeに格納された値を参照可能です。
各値の参照方法は公式リポジトリのREADMEに記載されています。
ソースコード
今回使用するソースコードは下記に置いています。
今回は下記のような構成を作成します。
ECSサービスで実行されるアプリはGoで実装されています。’/’にアクセスすると文字列を返すだけの簡易的なものです。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
// return c.String(http.StatusOK, "Welcome to ecspresso-demo!")
})
e.Logger.Fatal(e.Start(":8080"))
}
サービスのデプロイにはCodeDeployを使用してB/Gデプロイ方式を採用します。
VPC, IAM, ALBなどはもちろんですが、ECSサービスに関連するAutoScaling設定やCodeDeploy(デプロイコントローラーで’CODE_DEPLOY’を指定した場合)等はecspressoでは管理されないのでこれらのリソースはAWS CDKで作成します
AWS CDKのデプロイ(1回目)
ECSサービに設定するAutoScalingやCodeDeployのデプロイグループはECSサービスをデプロイした後でなければデプロイができないため、AWS CDKは2回に分けてデプロイします。
上記のリソースの作成についてはAWS CDKに渡すフラグで制御を行っています。
...省略
// 初回時は作成しない
if (props.isFirst === 'n') {
this.createAutoScaling();
}
...省略
// 初回時は作成しない
if (props.isFirst === 'n') {
new codedeploy.EcsDeploymentGroup(this, 'EcsCodeDeployGroup', {
application: this.ecsDeployApp,
deploymentGroupName: `${this.prefix}-ecs-deploy-group`,
service: ecs.BaseService.fromServiceArnWithCluster(
this,
'EcsService',
`arn:aws:ecs:${cdk.Stack.of(this).region}:${
cdk.Stack.of(this).account
}:service/${props.cluster.clusterName}/${this.prefix}-service`,
),
deploymentConfig: codedeploy.EcsDeploymentConfig.ALL_AT_ONCE,
role: this.codeDeployRole,
blueGreenDeploymentConfig: {
blueTargetGroup: props.blueTargetGroup,
greenTargetGroup: props.greenTargetGroup,
listener: props.listener,
testListener: props.testListener,
},
autoRollback: {
failedDeployment: true,
stoppedDeployment: true,
},
});
}
AWS CDKに必要なパッケージをインストールします。
npm install --prefix infra
AWS CDKに関するコマンドは全てcdk.shにまとめているので実行権限を与えてデプロイを行います。
chmod +x infra/cdk.sh
infra/cdk.sh deploy
上記のコマンドを実行するとAutoScaling, CodeDeployデプロイグループを除いたリソースが作成されます。
ecspressoデプロイ(1回目)
今回使用するecspressoのコードはjsonnetで記載しています。
タスク定義やサービス定義で必要なAWSリソース情報(ネットワークリソースやIAMロールなど)はParameter Storeから参照しています。
local must_env = std.native('must_env');
{
region: 'ap-northeast-1',
cluster: must_env('SYS_NAME') + '-' + must_env('CLUSTER'),
service: must_env('SYS_NAME') + '-' + must_env('SERVICE'),
service_definition: 'ecs-service-def.jsonnet',
task_definition: 'ecs-task-def.jsonnet',
timeout: '10m0s',
codedeploy: {
application_name: must_env('SYS_NAME') + '-' + must_env('APPLICATION_NAME'),
deployment_group_name: must_env('SYS_NAME') + '-' + must_env('DEPLOYMENT_GROUP_NAME'),
},
}
local must_env = std.native('must_env');
local ssm = std.native('ssm');
{
family: must_env('SYS_NAME'),
containerDefinitions: [
{
name: must_env('SYS_NAME') + '-container',
image: must_env('REPO') + ':' + must_env('TAG'),
cpu: 0,
portMappings: [
{
containerPort: 8080,
hostPort: 8080,
protocol: 'tcp',
appProtocol: 'http',
},
],
essential: true,
environment: [],
secrets: [],
mountPoints: [],
volumesFrom: [],
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-group': '/aws/' + must_env('SYS_NAME') + '/logs',
mode: 'non-blocking',
'max-buffer-size': '25m',
'awslogs-region': 'ap-northeast-1',
'awslogs-stream-prefix': '${prefix-name}/${container-name}/${ecs-task-id}',
'awslogs-create-group': 'true',
},
secretOptions: [],
},
systemControls: [],
dependsOn: [],
},
],
taskRoleArn: ssm('/' + must_env('SYS_NAME') + '/task-role-arn'),
executionRoleArn: ssm('/' + must_env('SYS_NAME') + '/execution-role-arn'),
networkMode: 'awsvpc',
requiresCompatibilities: ['FARGATE'],
cpu: '1024',
memory: '2048',
runtimePlatform: {
cpuArchitecture: 'ARM64',
operatingSystemFamily: 'LINUX',
},
}
local must_env = std.native('must_env');
local ssm = std.native('ssm');
{
capacityProviderStrategy: [
{
base: 0,
capacityProvider: 'FARGATE',
weight: 1,
},
],
deploymentConfiguration: {
maximumPercent: 200,
minimumHealthyPercent: 100,
},
deploymentController: {
type: 'CODE_DEPLOY',
},
desiredCount: 1,
enableECSManagedTags: true,
enableExecuteCommand: false,
healthCheckGracePeriodSeconds: 0,
launchType: '',
loadBalancers: [
{
containerName: must_env('SYS_NAME') + '-container',
containerPort: 8080,
targetGroupArn: ssm('/' + must_env('SYS_NAME') + '/target-group-arn'),
},
],
networkConfiguration: {
awsvpcConfiguration: {
assignPublicIp: 'DISABLED',
securityGroups: [ssm('/' + must_env('SYS_NAME') + '/security-group-id')],
subnets: [
ssm('/' + must_env('SYS_NAME') + '/subnet-id-1'),
ssm('/' + must_env('SYS_NAME') + '/subnet-id-2'),
],
},
},
platformFamily: 'Linux',
platformVersion: 'LATEST',
propagateTags: 'NONE',
schedulingStrategy: 'REPLICA',
}
ecspressoに関するコマンドは全てecspresso.shにまとめているので実行権限を与えてコマンドを実行していきます。
chmod +x app/ecspresso.sh
アプリをビルドして作成したECRリポジトリにプッシュします。
// ECRへログイン
app/ecspresso.sh login
// アプリのビルド & ECRへのプッシュ
app/ecspresso.sh push
ECRへのプッシュが完了したらecspressoファイルの検証を行います。
全てのチェックで問題がなければVerify OKが出力されます。
app/ecspresso.sh verify
2024/12/02 15:39:27 [INFO] ecspresso version: v2.4.1
2024/12/02 15:39:28 ecspresso-demo-service/ecspresso-demo-cluster Starting verify
TaskDefinition
ExecutionRole[arn:aws:iam::xxxxxxxxxxxx:role/ecspresso-demo-execution-role]
--> [OK]
TaskRole[arn:aws:iam::xxxxxxxxxxxx:role/ecspresso-demo-task-role]
--> [OK]
ContainerDefinition[ecspresso-demo-container]
Image[xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecspresso-demo:latest]
--> [OK]
LogConfiguration[awslogs-create-group=true,awslogs-group=/aws/ecspresso-demo/logs,awslogs-region=ap-northeast-1,awslogs-stream-prefix=${prefix-name}/${container-name}/${ecs-task-id},max-buffer-size=25m,mode=non-blocking]
--> [OK]
--> [OK]
--> [OK]
ServiceDefinition
LoadBalancer[0]
--> [OK]
--> [OK]
Cluster
--> [OK]
2024/12/02 15:39:30 ecspresso-demo-service/ecspresso-demo-cluster Verify OK!
ファイルの検証に失敗すると下記のようなエラーが出力されます。
app/ecspresso.sh verify
2024/12/02 16:45:51 [INFO] ecspresso version: v2.4.1
2024/12/02 16:45:52 [ERROR] FAILED. failed to load task definition ecspresso/ecs-task-def.jsonnet: RUNTIME ERROR: something went wrong calling get-parameter API: operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 3f2f6240-9f2a-40e7-ba05-53b994c6a958, ParameterNotFound:
ecspresso/ecs-task-def.jsonnet:41:21-76 object <anonymous>
Field "executionRoleArn"
During manifestation
ファイルの検証に成功したのでデプロイを行います。
Service is stable now. Completed!が出力されればデプロイは完了です。
app/ecspresso.sh deploy
2024/12/02 15:37:31 [INFO] ecspresso version: v2.4.1
2024/12/02 15:37:31 ecspresso-demo-service/ecspresso-demo-cluster Starting deploy
2024/12/02 15:37:32 ecspresso-demo-service/ecspresso-demo-cluster Service ecspresso-demo-service not found. Creating a new service
2024/12/02 15:37:32 ecspresso-demo-service/ecspresso-demo-cluster Starting create service
2024/12/02 15:37:32 ecspresso-demo-service/ecspresso-demo-cluster Registering a new task definition...
2024/12/02 15:37:32 ecspresso-demo-service/ecspresso-demo-cluster Task definition is registered ecspresso-demo:1
2024/12/02 15:37:37 ecspresso-demo-service/ecspresso-demo-cluster Service is created
2024/12/02 15:37:40 ecspresso-demo-service/ecspresso-demo-cluster [INFO] failed to find CodeDeploy Application/DeploymentGroup for ECS service ecspresso-demo-service on cluster ecspresso-demo-cluster
2024/12/02 15:37:40 ecspresso-demo-service/ecspresso-demo-cluster Waiting a task set PRIMARY stable: STABILIZING
2024/12/02 15:39:02 ecspresso-demo-service/ecspresso-demo-cluster Waiting a task set PRIMARY stable: STEADY_STATE
2024/12/02 15:39:02 ecspresso-demo-service/ecspresso-demo-cluster Service is stable now. Completed!
上記の実行ログでCodeDeployのデプロイグループが見つからない旨のメッセージが出力しています。
2024/12/02 15:37:40 ecspresso-demo-service/ecspresso-demo-cluster [INFO] failed to find CodeDeploy Application/DeploymentGroup for ECS service ecspresso-demo-service on cluster ecspresso-demo-cluster
CodeDeployのデプロイグループが未作成なので想定内のメッセージです。
初回のデプロイには影響がありませんが、未作成の状態で2回目のデプロイを行おうとするとデプロイが失敗するので注意してください。
2024/12/02 15:39:53 [INFO] ecspresso version: v2.4.1
2024/12/02 15:39:53 ecspresso-demo-service/ecspresso-demo-cluster Starting deploy
Service: ecspresso-demo-service
Cluster: ecspresso-demo-cluster
TaskDefinition: ecspresso-demo:1
TaskSets:
PRIMARY ecspresso-demo:1 desired:1 pending:0 running:1 STEADY_STATE
Events:
2024/12/02 15:39:54 ecspresso-demo-service/ecspresso-demo-cluster Registering a new task definition...
2024/12/02 15:39:54 ecspresso-demo-service/ecspresso-demo-cluster Task definition is registered ecspresso-demo:2
2024/12/02 15:39:54 ecspresso-demo-service/ecspresso-demo-cluster [INFO] deployment by CodeDeploy
2024/12/02 15:39:54 ecspresso-demo-service/ecspresso-demo-cluster Updating service attributes...
2024/12/02 15:39:57 ecspresso-demo-service/ecspresso-demo-cluster desired count: 1
2024/12/02 15:39:57 ecspresso-demo-service/ecspresso-demo-cluster updating desired count to 1
2024/12/02 15:39:58 [ERROR] FAILED. failed to find CodeDeploy Application/DeploymentGroup for ECS service ecspresso-demo-service on cluster ecspresso-demo-cluster
作成されたサービスを確認していきます。
サービスは無事作成されました。初回のデプロイはCodeDeployによるデプロイが行われなかったのでデプロイのステータスが’-‘となっています。また、AutoScalingについても未作成なので設定はされいません。
動作確認のためALBのエンドポイントにアクセスしてみます。
curl -i http://ecspresso-demo-alb-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com
HTTP/1.1 200 OK
Date: Mon, 02 Dec 2024 06:39:13 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 13
Connection: close
Hello, World!%
AWS CDKのデプロイ(2回目)
AutoScalingとCodeDeployのデプロイグループを作成します。
infra/cdk.sh deploy:n
デプロイが完了するとecspressoでデプロイしたサービスにAutoScalingが設定されいるのとCodeDeployのデプロイグループが作成されているのを確認できます。
ecspressoデプロイ(2回目)
CodeDeployのデプロイグループが作成されたのでB/Gデプロイを試してみます。
まずはアプリを修正して返却される文字列を変更します。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
// return c.String(http.StatusOK, "Hello, World!") コメントアウト
return c.String(http.StatusOK, "Welcome to ecspresso-demo!") // コメントアウト解除
})
e.Logger.Fatal(e.Start(":8080"))
}
変更が完了したらもう一度ビルド & プッシュを行います。
app/ecspresso.sh push
ECRへのプッシュが完了したらデプロイを行います。
デプロイコマンドを実行するとCodeDeployのデプロイ画面が開かれます。
Service is stable now. Completed!が出力されればデプロイは完了です。
app/ecspresso.sh deploy
2024/12/02 15:46:30 [INFO] ecspresso version: v2.4.1
2024/12/02 15:46:30 ecspresso-demo-service/ecspresso-demo-cluster Starting deploy
Service: ecspresso-demo-service
Cluster: ecspresso-demo-cluster
TaskDefinition: ecspresso-demo:1
TaskSets:
PRIMARY ecspresso-demo:1 desired:1 pending:0 running:1 STEADY_STATE
AutoScaling:
Capacity min:1 max:2
Suspended in:false out:false scheduled:false
Policy name:ecspressodemostackEcsApiServiceAutoScaleApiServiceCPUUtilizationACFEBD3D type:TargetTrackingScaling
Policy name:ecspressodemostackEcsApiServiceAutoScaleApiServiceMemoryUtilization886BF5C6 type:TargetTrackingScaling
Events:
2024/12/02 15:46:32 ecspresso-demo-service/ecspresso-demo-cluster Registering a new task definition...
2024/12/02 15:46:32 ecspresso-demo-service/ecspresso-demo-cluster Task definition is registered ecspresso-demo:3
2024/12/02 15:46:32 ecspresso-demo-service/ecspresso-demo-cluster [INFO] deployment by CodeDeploy
2024/12/02 15:46:32 ecspresso-demo-service/ecspresso-demo-cluster Updating service attributes...
2024/12/02 15:46:35 ecspresso-demo-service/ecspresso-demo-cluster desired count: 1
2024/12/02 15:46:35 ecspresso-demo-service/ecspresso-demo-cluster updating desired count to 1
2024/12/02 15:46:36 ecspresso-demo-service/ecspresso-demo-cluster Deployment d-WTAOIT3F9 is created on CodeDeploy:
2024/12/02 15:46:36 ecspresso-demo-service/ecspresso-demo-cluster https://ap-northeast-1.console.aws.amazon.com/codesuite/codedeploy/deployments/d-WTAOIT3F9?region=ap-northeast-1
2024/12/02 15:46:36 ecspresso-demo-service/ecspresso-demo-cluster Waiting for a deployment successful ID: d-WTAOIT3F9
2024/12/02 15:46:46 ecspresso-demo-service/ecspresso-demo-cluster BeforeInstall: Succeeded
2024/12/02 15:46:46 ecspresso-demo-service/ecspresso-demo-cluster Install: InProgress
2024/12/02 15:49:06 ecspresso-demo-service/ecspresso-demo-cluster Install: Succeeded
2024/12/02 15:49:06 ecspresso-demo-service/ecspresso-demo-cluster AfterInstall: Succeeded
2024/12/02 15:49:06 ecspresso-demo-service/ecspresso-demo-cluster AllowTestTraffic: Succeeded
2024/12/02 15:49:06 ecspresso-demo-service/ecspresso-demo-cluster AfterAllowTestTraffic: Succeeded
2024/12/02 15:49:06 ecspresso-demo-service/ecspresso-demo-cluster BeforeAllowTraffic: InProgress
Traffic shifted 100% |████████████████████|
2024/12/02 15:49:22 ecspresso-demo-service/ecspresso-demo-cluster Service is stable now. Completed!
デプロイ完了後にALBへアクセスするとアプリが更新されていることがわかります。
curl -i http://ecspresso-demo-alb-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com
HTTP/1.1 200 OK
Date: Mon, 02 Dec 2024 06:39:13 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 13
Connection: close
Welcome to ecspresso-demo!%
ECSサービスのターゲットグループが切り替わり、デプロイステータスも成功へと変化しています。
これでAWS CDK + ecspresso + CodeDeployを利用した環境の構築が完了しました。
環境の削除
最後に下記のコマンドを使用して今回作成した環境の削除を行います。
// ECSサービスのスケールを0にする
app/ecspresso.sh scale0
2024/12/02 17:15:17 [INFO] ecspresso version: v2.4.1
2024/12/02 17:15:17 ecspresso-demo-service/ecspresso-demo-cluster Starting deploy
Service: ecspresso-demo-service
Cluster: ecspresso-demo-cluster
TaskDefinition: ecspresso-demo:3
TaskSets:
PRIMARY ecspresso-demo:3 desired:1 pending:0 running:1 STEADY_STATE
AutoScaling:
Capacity min:1 max:2
Suspended in:false out:false scheduled:false
Policy name:ecspressodemostackEcsApiServiceAutoScaleApiServiceCPUUtilizationACFEBD3D type:TargetTrackingScaling
Policy name:ecspressodemostackEcsApiServiceAutoScaleApiServiceMemoryUtilization886BF5C6 type:TargetTrackingScaling
Events:
2024/12/02 17:15:18 ecspresso-demo-service/ecspresso-demo-cluster desired count: 0
2024/12/02 17:15:18 ecspresso-demo-service/ecspresso-demo-cluster updating desired count to 0
2024/12/02 17:15:19 ecspresso-demo-service/ecspresso-demo-cluster [INFO] No deployments found in progress on CodeDeploy
kmrysk@kmrysknoMac-mini ecspresso-demo % app/ecspresso.sh delete
// ECSサービスの削除
app/ecspresso.sh delete
2024/12/02 17:15:21 [INFO] ecspresso version: v2.4.1
2024/12/02 17:15:21 ecspresso-demo-service/ecspresso-demo-cluster Deleting service
Service: ecspresso-demo-service
Cluster: ecspresso-demo-cluster
TaskDefinition: ecspresso-demo:3
TaskSets:
PRIMARY ecspresso-demo:3 desired:1 pending:0 running:1 STEADY_STATE
AutoScaling:
Capacity min:1 max:2
Suspended in:false out:false scheduled:false
Policy name:ecspressodemostackEcsApiServiceAutoScaleApiServiceCPUUtilizationACFEBD3D type:TargetTrackingScaling
Policy name:ecspressodemostackEcsApiServiceAutoScaleApiServiceMemoryUtilization886BF5C6 type:TargetTrackingScaling
Events:
2024/12/02 15:49:25 (service ecspresso-demo-service, taskSet ecs-svc/4445189337499124883) has begun draining connections on 1 tasks.
2024/12/02 15:49:25 (service ecspresso-demo-service, taskSet ecs-svc/4445189337499124883) updated state to STEADY_STATE.
2024/12/02 15:50:32 (service ecspresso-demo-service) has reached a steady state.
Enter the service name to DELETE: ecspresso-demo-service
2024/12/02 17:15:44 ecspresso-demo-service/ecspresso-demo-cluster Service is deleted
// AWS CDKの削除
infra/cdk.sh destroy
さいごに
ECSを運用する上でインフラ側とアプリ側のライフサイクルの違いがあり、AWS CDKだけでは管理が難しかったのですが、ecspressoを採用したことで境界が明確になり抱えていた課題も解決できました。
ECSのサービスとタスクのみを管理するシンプルなツールなので学習コストも高くありません。
非常に使いやすいツールですのでぜひ活用してみてください。