GitHub ActionsでDockerレイヤーキャッシュを利用したい

この記事を以て2021年のブログ納めとする!
2021.12.31

こんにちは!コンサル部のinomaso(@inomasosan)です。

GitHub ActionsのCI/CDはVM上でワークフローが実行されるため、同じイメージであってもキャッシュを利用することなく最初からビルドが実行されます。
ワークフローが実行されるたびに、ベースとなるイメージのダウンロードやファイルコピーが実行されてしまうと、デプロイ時間が伸びてしまうので改善が必要です。

そこで今回は前回作成したGitHub Actionsワークフローに、Dockerレイヤーキャッシュを追加していきます。

どのActionsを追加するか

今回はdocker/build-push-actionというDockerイメージのビルドからプッシュ + キャッシュをいい感じに実行してくれるActionsを利用していきます。

上記ActionsのCacheのドキュメントに、以下のサンプルコードがあるので、こちらを参考にしていきます。

name: ci

on:
  push:
    branches:
      - 'master'

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      -
        name: Checkout
        uses: actions/checkout@v2
      -
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      -
        name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-
      -
        name: Login to DockerHub
        uses: docker/login-action@v1 
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      -
        name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: user/app:latest
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
      -
        # Temp fix
        # https://github.com/docker/build-push-action/issues/252
        # https://github.com/moby/buildkit/issues/1896
        name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

GitHub Actionsワークフロー修正

前回作成したワークフローを元に修正していきます。
以下がワークフローの全体で、修正した箇所をハイライトしました。

.github/workflows/aws.yml

# This workflow will build and push a new container image to Amazon ECR,
# and then will deploy a new task definition to Amazon ECS, when there is a push to the main branch.
#
# To use this workflow, you will need to complete the following set-up steps:
#
# 1. Create an ECR repository to store your images.
#    For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`.
#    Replace the value of the `ECR_REPOSITORY` environment variable in the workflow below with your repository's name.
#    Replace the value of the `AWS_REGION` environment variable in the workflow below with your repository's region.
#
# 2. Create an ECS task definition, an ECS cluster, and an ECS service.
#    For example, follow the Getting Started guide on the ECS console:
#      https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun
#    Replace the value of the `ECS_SERVICE` environment variable in the workflow below with the name you set for the Amazon ECS service.
#    Replace the value of the `ECS_CLUSTER` environment variable in the workflow below with the name you set for the cluster.
#
# 3. Store your ECS task definition as a JSON file in your repository.
#    The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`.
#    Replace the value of the `ECS_TASK_DEFINITION` environment variable in the workflow below with the path to the JSON file.
#    Replace the value of the `CONTAINER_NAME` environment variable in the workflow below with the name of the container
#    in the `containerDefinitions` section of the task definition.
#
# 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
#    See the documentation for each action used below for the recommended IAM policies for this IAM user,
#    and best practices on handling the access key credentials.

name: Deploy to Amazon ECS

on:
  push:
    branches:
      - main

env:
  AWS_REGION: ap-northeast-1                   # set this to your preferred AWS region, e.g. us-west-1
  ECR_REPOSITORY: githubactions-httpd          # set this to your Amazon ECR repository name
  ECS_SERVICE: httpd-service               # set this to your Amazon ECS service name
  ECS_CLUSTER: httpd-cluster                # set this to your Amazon ECS cluster name
  ECS_TASK_DEFINITION: task-definition.json # set this to the path to your Amazon ECS task definition
                                               # file, e.g. .aws/task-definition.json
  CONTAINER_NAME: httpd-container           # set this to the name of the container in the
                                               # containerDefinitions section of your task definition
  AWS_ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubActionsOIDC # GitHub用に作成したIAMロールのARN                                            

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production
    # These permissions are needed to interact with GitHub's OIDC Token endpoint.
    permissions:
      id-token: write
      contents: read
    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v1

    - name: Cache Docker layers
      uses: actions/cache@v2
      with:
        path: /tmp/.buildx-cache
        key: ${{ runner.os }}-buildx-${{ github.sha }}
        restore-keys: |
          ${{ runner.os }}-buildx-

    - name: Configure AWS credentials of IAM Role
      uses: aws-actions/configure-aws-credentials@v1
      with:
        role-to-assume: ${{ env.AWS_ROLE_ARN }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - uses: docker/build-push-action@v2
      id: build-image
      with:
        push: true
        tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
        cache-from: type=local,src=/tmp/.buildx-cache
        cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

    - name: Move cache
      run: |
        rm -rf /tmp/.buildx-cache
        mv /tmp/.buildx-cache-new /tmp/.buildx-cache

    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true

ワークフロー実行結果確認

1回目

docker/build-push-action9秒程時間がかかりました。 以下はログの抜粋となりますが、最初からビルドしていることがわかります。

#4 [1/2] FROM docker.io/library/httpd:latest@sha256:0954cc1af252d824860b2c5dc0a10720af2b7a3d3435581ca788dff8480c7b32
#4 resolve docker.io/library/httpd:latest@sha256:0954cc1af252d824860b2c5dc0a10720af2b7a3d3435581ca788dff8480c7b32 done
#4 sha256:d982c879c57eba6fa5c08ea1e9811442ec89196c9a5793cc9db6e28b60dfff88 297B / 297B 0.0s done
#4 sha256:dcc4698797c83ed22cecf9babcfc5c5abd15c5f86b73f688d65c4334e1632e1e 177B / 177B 0.0s done
#4 sha256:41c22baa66ecf728c1ea0c5405ebe72c5b2606ef66b4565a209e23e1ab05fe80 915.38kB / 915.38kB 0.1s done
#4 sha256:67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1 11.53MB / 24.13MB 0.2s
#4 sha256:67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1 24.13MB / 24.13MB 0.3s
#4 sha256:a2abf6c4d29d43a4bf9fbb769f524d0fb36a2edab49819c1bf3e76f409f953ea 7.34MB / 31.36MB 0.2s
#4 sha256:67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1 24.13MB / 24.13MB 0.3s done
#4 sha256:a2abf6c4d29d43a4bf9fbb769f524d0fb36a2edab49819c1bf3e76f409f953ea 31.36MB / 31.36MB 0.3s
#4 sha256:a2abf6c4d29d43a4bf9fbb769f524d0fb36a2edab49819c1bf3e76f409f953ea 31.36MB / 31.36MB 0.5s done
#4 extracting sha256:a2abf6c4d29d43a4bf9fbb769f524d0fb36a2edab49819c1bf3e76f409f953ea
#4 extracting sha256:a2abf6c4d29d43a4bf9fbb769f524d0fb36a2edab49819c1bf3e76f409f953ea 1.1s done
#4 extracting sha256:dcc4698797c83ed22cecf9babcfc5c5abd15c5f86b73f688d65c4334e1632e1e 0.0s done
#4 extracting sha256:41c22baa66ecf728c1ea0c5405ebe72c5b2606ef66b4565a209e23e1ab05fe80 0.0s done
#4 DONE 1.6s

#4 [1/2] FROM docker.io/library/httpd:latest@sha256:0954cc1af252d824860b2c5dc0a10720af2b7a3d3435581ca788dff8480c7b32
#4 extracting sha256:67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1
#4 extracting sha256:67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1 0.6s done
#4 extracting sha256:d982c879c57eba6fa5c08ea1e9811442ec89196c9a5793cc9db6e28b60dfff88 done
#4 DONE 2.2s

#5 [2/2] COPY ./html /usr/local/apache2/htdocs/
#5 DONE 0.8s

2回目

docker/build-push-action6秒程ほどに時間が短縮されました 以下はログの抜粋となりますが、キャッシュを利用していることがわかります。

#4 [1/2] FROM docker.io/library/httpd:latest@sha256:0954cc1af252d824860b2c5dc0a10720af2b7a3d3435581ca788dff8480c7b32
#4 resolve docker.io/library/httpd:latest@sha256:0954cc1af252d824860b2c5dc0a10720af2b7a3d3435581ca788dff8480c7b32 done
#4 DONE 0.0s

#6 importing cache manifest from local:3587489754217729963
#6 DONE 0.0s

#5 [2/2] COPY ./html /usr/local/apache2/htdocs/
#5 CACHED

ハマったこと

docker/build-push-actionを実行するにあたり、GitHub ActionsからECSとECRへのCI/CDを最小権限で実行したいのIAMポリシーを利用したのですが、権限不足でエラーになってしまいました。 もし最小権限で実行している場合は、以下のブログをご参照願います。

あわせて読みたい

まとめ

GitHub Actionsは色々と便利なActionsが多いので、ゴリゴリにCLIを書かなくても簡単にCI/CDを実行できるのは本当にありがたいですね!

この記事が、どなたかのお役に立てば幸いです。それでは!