November 30, 2023
この記事は以前Qiitaにて公開したものになります。
元記事はこちら : GitHub Actionsとecspressoを使ってECSへアプリをデプロイする
私の所属するチームでは Amazon ECS on Fargateで稼動しているサービスを開発しています。 ECS on Fargateを選択することで、サーバ自体の管理はAWSにお任せできるため、アプリケーションやその周辺リソースのみに集中すればよいため、開発者として体験がよく、また運用の負荷も低いです。 少し大変なのが初期構築とデプロイなのですが、デプロイについては kayac/ecspresso を使うと簡単にできるようになります。
今回、チームメンバーが新規でCDを作成することになったため、チーム内で利用しているecspressoを使ったデプロイについて解説します。
この記事で解説すること・しないことについては以下の通りです
今回、ECSへのデプロイ解説のために以下のような構成を想定します。 NAT Gatewayをプライベートサブネットに配置していますが、代わりにECRへのPrivate Linkを設定するとよりコストが抑えられます。
ECSのデプロイは、以下のようなフローで実施されます。
まず、開発者がなんらかの方法でコンテナイメージとタスク定義を更新します。
その後、同様に開発者がサービスに紐づくタスク定義を最新にします。
その後は、ECSサービスが新しいタスク定義を満たすようにコンテナを起動していきます。 まず、コンテナを起動するためにコンテナイメージをpullします。
そして、pullしたイメージとタスク定義を基にコンテナを起動します。
タスク定義に設定されたヘルスチェックを満たし、サービスから見て新しいタスクが安定したら、古いタスクを削除し、デプロイ完了になります。
ここまでで確認したように、ECSへのアプリデプロイには、
の3つの登場人物が存在します。 そして、それぞれに更新をかける必要があります。
タスク定義とサービスについては馴染みのない方も多いと思います。 そこで、ローカル開発環境でよく採用されるDockerの概念に例えると、タスク定義はcompose.yaml(docker-compose.yaml)に、サービスはDockerデーモンに相当します。 タスク定義はどんなコンテナイメージを使ってどんなサービスを起動するかを管理し、サービスはタスク定義を見て実際にタスクを起動したり、逆に終了したりします。
もちろん、よく似ているだけで実際には違うものなので、興味のある方は公式ドキュメントを参照してください。
仕組みのおさらいでは、開発者がなんらかの方法を用いて手動でそれぞれを更新していました。 たとえば、AWSのマネジメントコンソールを使って画面操作で更新してもいいし、AWS CLIを使って更新してもいいし、あるいはAWS SDKを使って自前でデプロイツールを作成してもいいわけです。
今回は、タスク定義・サービスの更新については kayac/ecspresso という便利なツールを使いつつ、GitHub Actions上にECSのデプロイフローを構築していきます。
このあと例示するデプロイフローは、以下のようなディレクトリ構成のPythonアプリを想定して作成しています。 必要に応じて読み替えてください。
.
├── .github
│ └── workflows
│ └── deploy.yaml
├── deploy # ecspressoなどのデプロイ情報が格納されているディレクトリ
│ ├── ecspresso.yaml
│ └── ecs-task-def.json
├── docker # コンテナイメージ
│ └── python
│ └── Dockerfile
├── README.md
└── src # アプリのディレクトリ
├── main.py
└── requirements.txt
今回のデプロイに必要な作業を順番に起こすと、
といった具合になります。 今回は、ecspressoを使用するので、ECS操作周辺が簡略化され、
の3ステップに圧縮できます。
ecspressoでデプロイを行うには、以下のファイルが必要です。
以下は ap-northeast-1
にある iketeru-cluster
の iketeru-service
に紐づいているタスク定義を更新したいときのecspresso.yamlの例です。
task_definition
で指定しているファイルはecspresso.yamlと同一のディレクトリに設置します。
region: ap-northeast-1
cluster: iketeru-cluster
service: iketeru-service
task_definition: ecs-task-def.json
timeout: 15m0s
ecs-task-def.jsonは、以下のように設定します。 AWSの公式ドキュメント を参考に各パラメータをいったんベタ書きで設定したあと、環境ごとに変更したい値やリポジトリで管理したくない値については環境変数から受け取るテンプレート構文に置き換えると楽です。
{
"containerDefinitions": [
{
"cpu": 0,
"environment": [
{
"name": "TZ",
"value": "Asia/Tokyo"
}
],
"essential": true,
"image": "{{ must_env `AWS_ACCOUNT_ID` }}.dkr.ecr.ap-northeast-1.amazonaws.com/{{ must_env `APP_IMAGE_NAME` }}:latest",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/iketeru-service/app",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"mountPoints": [],
"name": "python",
"portMappings": [],
"volumesFrom": []
}
],
"cpu": "256",
"executionRoleArn": "arn:aws:iam::{{ must_env `AWS_ACCOUNT_ID` }}:role/ecsTaskExecutionRole",
"family": "iketeru-service",
"memory": "512",
"networkMode": "awsvpc",
"placementConstraints": [],
"requiresCompatibilities": [
"FARGATE"
],
"taskRoleArn": "arn:aws:iam::{{ must_env `AWS_ACCOUNT_ID` }}:role/iketeru-service-task-role",
"volumes": []
}
前述の作業フローをGitHub Actionsのワークフローとしてざっくり実装してみると、以下のようになります。 以下のワークフローでは、コードに載せるべきではない情報をGitHub Actionsのenv, secretsに保存します。 そして、必要なタイミングでWorkflow上の環境変数に展開しています。
name: deploy
on:
push:
branches:
- main
jobs:
deploy-to-ecs:
runs-on: ubuntu-latest
steps:
# 事前準備
- name: "作業ブランチにcheckoutする"
uses: actions/checkout@v4
- name: "AWSの認証情報を取得する"
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
# ビルドしたイメージのpushができるようにECRの認証を通す
- name: "ECRへログインする"
uses: aws-actions/amazon-ecr-login@v1
id: login-ecr
- name: "アプリのコンテナイメージをビルドしてECRにpushする"
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ env.ECR_REPOSITORY_NAME }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build \
-f docker/python/Dockerfile \
-t $ECR_REGISTRY/$ECR_REPOSITORY:latest \
-t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: "ecspressoのインストール"
uses: kayac/ecspresso@v2
with:
version: v2.2.4
- name: "ecspressoを使ってECSへのデプロイを実行"
run: ecspresso deploy --config ecspresso.yml --tasks=-1
working-directory: ./deploy
env:
AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }}
ECR_REPOSITORY: ${{ env.ECR_REPOSITORY_NAME }}
APP_IMAGE_NAME: ${{ env.APP_IMAGE_NAME }}
以上がecspressoを使ってGitHub ActionsからECSへデプロイするために必要な設定です。 最小限のサンプルということもありますが、それを考慮してもかなりシンプルではないでしょうか。
また、今回はデプロイツールとしてecspressoを扱いましたが、デプロイ以外のECSに対する操作するツールとしても非常に便利です。 ぜひ、公式ドキュメントを参照して活用してみてください。