GitHub Actions によるデプロイの学習目的で、
ソースコードからECSへデプロイする、最小限の構成のサンプルを作ってみました。
元になる環境は、拙著 ( Terraform × AWS 入門から実践へ ) の付録のTerraformのサンプルコードを使って、
nginxのイメージでサンプルページを表示させるECSベースのシステムを作ったところを起点とします。
ディレクトリ構成
リポジトリに格納するファイル、ディレクトリの構成図です。
ルートディレクトリ(リポジトリ名)
├── .github/
│ └── workflows/
│ └── deploy.yml
├── docker/
│ ├── Dockerfile
│ └── nginx.conf
├── ecs/
│ └── task-definition.json
├── public/
│ └── index.html
├── scripts/
├── .gitignore
└── README.md
各ファイル、設定
ファイルの内容や設定を紹介します。
deploy.yml
GitHub Actions の ワークフロー の定義ファイルです。
ワークフローを実行するタイミングは、次の設定にすることで、pushをトリガーとして実行することも可能です。
on:
push:
branches: [ "develop" ]
ここでは、GitHubのコンソールから手動でワークフローを実行できるようにしました。
この場合、ブランチはデフォルトブランチのみ実行できる仕様です。
つまり、実行前にデフォルトブランチにプッシュもしくはマージする必要があります。
今回は "develop" ブランチをデフォルトブランチにします。
name: Build and Deploy to ECS (OIDC)
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
TASK_DEFINITION: ${{ secrets.TASK_DEFINITION }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image to ECR
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f docker/Dockerfile .
docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "IMAGE=$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_ENV
- name: Render new task definition
id: render-task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.TASK_DEFINITION }}
container-name: samp01-ecs-service
image: ${{ env.IMAGE }}
- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
Dockerfile
デプロイする イメージ に対する実行内容の定義ファイルです。
今回、nginx でシンプルにWebページを公開するだけなのでこのような記載ですが、
実際のプロダクトでは、別の言語、ミドルウェアなどより複雑な内容の動作環境で動かすことが多いので、要件に合った内容に差し替えます。
FROM nginx:alpine
# 既存の default.conf を削除
RUN rm /etc/nginx/conf.d/default.conf
# nginx 設定を配置
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
# Webコンテンツを配置
COPY public/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf
nginxの設定ファイルです。
内容に深い意味はありません。ChatGPTに作ってもらったものをそのまま使っています。
実環境では、使う言語やミドルウェア等の仕様に合ったファイル名、内容に差し替えます。
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
task-definition.json
ECSの対象サービスのタスク定義です。
AWSマネコンから現状のタスク定義の内容を確認してコピペし、いくつか情報を示す要素やリビジョン固有の要素だけ削除しています。
- 削除した要素
- registeredAt
- registeredBy
- revision
- status
- taskDefinitionArn
{
"compatibilities": [
"EC2",
"MANAGED_INSTANCES",
"FARGATE"
],
"containerDefinitions": [
{
"cpu": 0,
"environment": [],
"essential": true,
"image": "nginx:latest",
"mountPoints": [],
"name": "samp01-ecs-service",
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
}
],
"systemControls": [],
"volumesFrom": []
}
],
"cpu": "256",
"executionRoleArn": "<ECSのタスク実行ロールのARN>",
"family": "<ECSサービス名>",
"memory": "512",
"networkMode": "awsvpc",
"placementConstraints": [],
"requiresAttributes": [
{
"name": "com.amazonaws.ecs.capability.task-iam-role"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"name": "ecs.capability.task-eni"
}
],
"requiresCompatibilities": [
"FARGATE"
],
"taskRoleArn": "<ECSタスクロールのARN>",
"volumes": [],
"tags": [
{
"key": "Project",
"value": "samp-pj"
},
{
"key": "Environment",
"value": "ouyo"
}
]
}
ワークフローが実行されると、リポジトリのファイルを元にDockerイメージとしてビルドされ、ECRに格納されます。
そして、その格納されたイメージを元にタスクが起動されるように、
やはりワークフローの中でこのファイルの定義内容が更新され、
ECSのタスク定義として登録されます。
注意点として、このデプロイ方法を使い始めて以降は、ECSサービスのタスク定義は、Terraformではなくこちらで定義、管理していきます。
(Terraformのコードにタスク定義の変更を無視する設定を追加します)
index.html
nginxでWebページとして公開するhtmlファイルです。
内容に深い意味はありません。ChatGPTに作ってもらったものをそのまま使っています。
実環境では、使う言語やミドルウェア等の仕様に合ったファイル名、内容に差し替えます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Hello nginx</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #0f172a;
color: #e5e7eb;
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
.card {
background: #020617;
padding: 2.5rem 3rem;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,.4);
text-align: center;
}
h1 {
margin: 0 0 0.5rem;
font-size: 2rem;
}
p {
margin: 0.5rem 0 0;
color: #94a3b8;
}
</style>
</head>
<body>
<div class="card">
<h1> Hello nginx!</h1>
<p id="message">This site is served by nginx.</p>
<p id="time"></p>
</div>
<script>
const now = new Date();
document.getElementById("time").textContent =
"Loaded at: " + now.toLocaleString();
</script>
</body>
</html>
IAM IDプロバイダの設定
GitHub Actions から AWSアカウントへの認証は OpenID Connect(OIDC) を使うので、IDプロバイダを設定します。
CloudShellから、次のコマンドを実行しました。
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
thumbprint の値は、次の記事から情報を得ました。(今後変更される可能性があります)
https://github.blog/changelog/2023-06-27-github-actions-update-on-oidc-integration-with-aws/?utm_source=chatgpt.com
IAMロールの設定
AssumeRole用のIAMロールを作成します。
- ロール名は、ここでは "GitHubActionsRole" とします(ロール名は任意です)
- リポジトリ名は「<アカウント名>/<リポジトリ名>」の形式です
信頼ポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<アカウントID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:<リポジトリ名>:ref:refs/heads/<ブランチ名(サンプルでは"develop")>"
}
}
}
]
}
インラインポリシー(ポリシー名は任意)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:DescribeRepositories"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:TagResource"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"<ECSのタスクロールARN>",
"<ECSのタスク実行ロールARN>"
]
}
]
}
Repository secrets
GitHub側に持たせる情報です。Secretとして設定します。
ここではRepository secretsに設定しますので、
Environment secrets と混同しないように注意してください。
(プロジェクトの方針によっては、Environment secrets に持たせることもできそうです)
| Name | Value(Secret) |
|---|---|
| AWS_REGION | ap-northeast-1 |
| AWS_ROLE_ARN | <IAMロール "GitHubActionsRole" のARN> |
| ECR_REPOSITORY | <ECRのリポジトリ名> |
| ECS_CLUSTER | <ECSのクラスター名> |
| ECS_SERVICE | <ECSのサービス名> |
| TASK_DEFINITION | ecs/task-definition.json |
デプロイの動き
実行前に、サイトの表示内容、ECSタスクの起動状況、ECSタスク定義のリビジョン番号を確認します。


GitHub Actions のメニューから、ワークフローを実行します。

図の箇所をクリックすることで、実行の経過や実行ログを見られます。

リポジトリ内のファイルを修正した場合は、再度デフォルトブランチへマージして、新たに実行します。
ECSのタスクが新たに直近の時間で起動されて差し替えられており、
ECSタスク定義のリビジョン番号がインクリメントされています。
(一度やり直したので図では2インクリメントされています)


おわりに
このコンパクトなサンプルを元に、発展させて使っていければと思います。
CodeDeploy と組み合わせるようにするとか。










