Build a Docker image using GitHub Action, save it to Amazon ECR, and deploy it to Amazon ECS.
--Point --Assuming production operation, Release Make GitHub Action work when you create). --Enable build cache to speed up Docker builds
--Create a destination ECR --Create an ECR to deploy to --Create an AWS IAM user for use with GitHub Action (requires ECR and ECS permissions)
Proceed with these as created.
Create a workflow like the one below and save it to .github/workflows/
in the repository with a suitable name like deploy-to-ecs.yml
.
By pushing the tag to the main branch, this workflow works, the Docker image is pushed to the ECR and deployed to the ECS.
Operate by using [Release] of Github (https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository) I'm assuming that.
.github/workflows/deploy-to-ecs.yml
name: Deploy to ECS
on:
push:
tags:
- v*
env:
ECR_REPOSITORY: your-repository-name
ECS_SERVICE: your-service-name
ECS_CLUSTER: your-cluster-name
jobs:
deploy:
name: Deploy to ECS
if: github.event.base_ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Configure AWS Credentials #AWS access permission settings
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Login to Amazon ECR #ECR login process
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Set Docker Tag Env #Match Docker Image version to tag
run: echo "::IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")" >> $GITHUB_ENV
- name: Build, tag, and push image to Amazon ECR #Docker image build&Push
env:
DOCKER_BUILDKIT: 1
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
docker build --cache-from=$ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg BUILDKIT_INLINE_CACHE=1 -f Dockerfile -t $ECR_REPOSITORY .
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Render Amazon ECS task definition for app container #ECS task definition file rendering of app container
id: render-app-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: .aws/ecs/task-definition.json
container-name: app
image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
- name: Deploy to Amazon ECS service #ECS service deployment
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-app-container.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: false
The workflow operation will be explained in detail from the top.
name: Deploy to ECS
I have decided on the name of the workflow. This name will be displayed when you check the workflow in the list from the repository of Github or the Action tab. When creating multiple workflows in a verification or production environment, it is recommended to rename them so that they are easy to distinguish.
on:
push:
tags:
- v*
It works when tags starting with v
such as v1.0.0
are pushed, and Release It is assumed that this workflow will work by creating (/ github / administration-a-repository / managing-releases-in-a-repository).
Also, this time in jobs so that it will not work if the tag is pushed to another branch
if: github.event.base_ref == 'refs/heads/main'
If it is not the main branch like, it will not work.
To make it work when pushed to a specific branch, remove the if
description
on:
push:
branches:
- target-branch
Please describe as. (Change target-branch to the branch you want to deploy.)
In order to realize that it works when a tag is pushed to a specific branch by the description method of on: push:
on:
push:
tags:
- v*
branches:
- target-branch
If you write like, it will be an or condition (when either the tag or the branch is pushed) instead of the and condition, so specify the target tag with on: push:
and target with if
The branch of is specified.
env:
ECR_REPOSITORY: your-repository-name
ECS_SERVICE: your-service-name
ECS_CLUSTER: your-cluster-name
I am assigning it to an environment variable to reduce the description of the value that is used many times in job.
Please change your-〇〇〇-name
to the one in your environment
jobs:
deploy:
name: Deploy to ECS
if: github.event.base_ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
Check out to the target branch in the environment of ubuntu-latest
and start executing the job.
(As I mentioned earlier, I wrote the if
line so that it will be executed only in the main branch when the tag is pushed. It is not necessary if the operating condition is changed to push in the branch.)
- name: Configure AWS Credentials #AWS access permission settings
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
I am logged in to the AWS account that has created the ECR to push and the ECS to deploy to.
Deploy using Github's Secret (https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) IAM access key and secret key AWS_ACCESS_KEY_ID Please register with
and AWS_SECRET_ACCESS_KEY
.
- name: Login to Amazon ECR #ECR login process
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
Log in to the ECR of the AWS account you logged in with in step 5.
- name: Set Docker Tag Env #Match Docker Image version to tag
run: echo "::IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")" >> $GITHUB_ENV
The tag pushed so that the image tag of Docker has the same value as the tag of git is taken out in a format like v1.0.0
and assigned to the environment variable.
If you do not use tags in the workflow operating conditions,
run: echo "::IMAGE_TAG=${{ github.sha }}" >> $GITHUB_ENV
I think it's a good idea to use a commit hash to make the image tag unique.
- name: Build, tag, and push image to Amazon ECR #Docker image build&Push
env:
DOCKER_BUILDKIT: 1
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
docker build --cache-from=$ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg BUILDKIT_INLINE_CACHE=1 -f Dockerfile -t $ECR_REPOSITORY .
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
When building a Docker image locally, the build speeds up using the previously built image, but GitHub Action does not retain the previous build result, so it takes time to build each time.
Therefore, here, Docker's docker build --cache-from
is used to speed up the build.
What we are doing is building a docker image, pushing the tag saved in the environment variable IMAGE_TAG, and the value changes dynamically for each workflow operation, so the value at the time of the previous build is used. Since it is difficult to get it, I also push the latest tag and use the latest tag for caching. By doing this, the cache is used while being consistent with the release tag.
(DOCKER_BUILDKIT
, BUILDKIT_INLINE_CACHE
is described because it is necessary to use --cache-from
.)
Note: If you're using Docker's multi-stage build, you can't cache this method because the Docker image doesn't include the entire build process. In that case, I think you should refer to Articles like this.
- name: Render Amazon ECS task definition for app container #ECS task definition file rendering of app container
id: render-app-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: .aws/ecs/task-definition.json
container-name: app
image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
To execute ECS, a task definition is required, and it is necessary to describe the repository and tag information of the Docker image to be used there. Therefore, when updating DockerImage, it is also necessary to update the task definition, and that process is being performed.
今回はレポジトリへタスク定義ファイル.aws/ecs/task-definition.json
を作成してあり、そのファイルを呼び出し、Dockerイメージの情報を更新しています。
Reference example of task-definition.json
)
{
"containerDefinitions": [
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
],
"image": "your-image-name",
"name": "app"
}
],
"cpu": "256",
"executionRoleArn": "your-role-name",
"family": "your-family",
"memory": "512",
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc"
}
Reading from s3 seems to be possible, so please manage each task definition you like.
- name: Deploy to Amazon ECS service #ECS service deployment
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-app-container.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: false
It is deployed to ECS based on the task definition created in 9.
By setting wait-for-service-stability
to true
, it is possible to wait for the completion of execution until the deployment is completed and notify the completion of deployment, but the execution time of Github Actions will increase. There is a possibility that it will exceed the execution frame.
It is also possible to hook the completion of deployment using AWS Lambda and notify it, so it is recommended to do so in an environment with many deployments.
If the above flow works correctly, the deployment is complete.
- name: Run Migrate #Run Migration
env:
CLUSTER_ARN: your_cluster_arn
ECS_SUBNER_FIRST: your_subnet_first
ECS_SUBNER_SECOND: your_subner_second
ECS_SECURITY_GROUP: your_security_group
run: |
aws ecs run-task --launch-type FARGATE --cluster $ECS_CLUSTER --task-definition ${{ steps.put-migrate-task.outputs.render-app-container }} --network-configuration "awsvpcConfiguration={subnets=[$ECS_SUBNER_FIRST, $ECS_SUBNER_SECOND],securityGroups=[$ECS_SECURITY_GROUP],assignPublicIp=ENABLED}" > run-task.log
TASK_ARN=$(jq -r '.tasks[0].taskArn' run-task.log)
aws ecs wait tasks-stopped --cluster $CLUSTER_ARN --tasks $TASK_ARN
I wanted to migrate the DB before deploying this time, so I added such a job to the ECS in step 10 before deploying and executed the task alone. By doing this, it is also possible to execute a single task within Github Actions.
When deploying to ECS, I think there are options such as combining AWS CodeBuild and AWS CodePipeline, but it is also possible to easily deploy using GitHub Action.
I have referred to the following article very much.
-Deploy an app from GitHub Actions to Amazon ECS
Recommended Posts