💭

Github Actionsでget-diff-actionを用いてDocker Build時間を削減する

2023/08/10に公開1

こんにちは!アルダグラムの開発ユニット長の田中です!

突然ですが、少しでもCIの実行時間を削減したいと思いませんか?

今回は、GitHub Actionsとget-diff-actionを活用してDockerビルド時間を削減する方法についてご紹介します。

TL;DR

get-diff-actionを使用してライブラリの変更差分を検知します。

変更差分がない場合はライブラリインストール済みのDockerイメージを使用してBuild時間を短縮します。

サンプルコード

具体的な例として、Railsアプリケーションを使用します。

github-actions.yml
on:
 pull_request:
   types: [ closed ]
   branches:
   - 'develop'

name: Deploy to Amazon ECS

jobs:
  ecr-push:
    name: Deploy
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true || github.event.inputs.manually == 'yes'

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1-node16
      with:
        role-to-assume: arn:aws:iam::(AWSのACCOUNT_ID):role/GithubActionsRole
        role-session-name: GitHubActions-${{ github.run_id }}
        aws-region: ap-northeast-1

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

    # Gemfile.lockに差分があるかどうか
    - name: Diff Gemfile.lock
      uses: technote-space/get-diff-action@v6
      id: check-diff
      with:
        PATTERNS: |
          Gemfile.lock

    # Gemfile.lockに差分があった場合、baseイメージをECRにPUSH
    - name: base image Build, tag, and push image to Amazon ECR
      id: build-base-image
      if: env.GIT_DIFF
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: base-rails
        IMAGE_TAG: latest
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f base.Dockerfile .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

    # baseイメージを元にrailsイメージをECRにプッシュ
    - name: rails image Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: rails
        IMAGE_TAG: latest
      run: |
        docker login -u AWS -p $(aws ecr get-login-password) https://(AWSのACCOUNT_ID).dkr.ecr.ap-northeast-1.amazonaws.com
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f production.Dockerfile
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
base.Dockerfile
FROM ruby:3.2-alpine

RUN apk upgrade --no-cache && \
    apk add --update && \
    apk add --no-cache --virtual build-dependencies build-base && \
    apk add --no-cache --update \
    curl \
    curl-dev \
    libstdc++ \
    gcompat \
    libxml2-dev \
    libxslt-dev \
    linux-headers \
    mariadb-dev \
    mysql-client \
    mysql-dev \
    ruby-dev \
    ruby-json \
    tzdata \
    yaml \
    yaml-dev \
    build-base \
    zlib-dev

RUN rm -rf /var/cache/apk/*

RUN gem install bundler

WORKDIR /app

RUN bundle config build.nokogiri -- \
    --use-system-libraries \
    --with-xml2-config=/usr/bin/xml2-config \
    --with-xslt-config=/usr/bin/xslt-config

COPY Gemfile Gemfile.lock ./

RUN CFLAGS="-Wno-cast-function-type" \
    BUNDLE_FORCE_RUBY_PLATFORM=1 \
    bundle install
production.Dockerfile
FROM (AWSのACCOUNT_ID).dkr.ecr.ap-northeast-1.amazonaws.com/base:latest

COPY . /app

解説

1. ライブラリの差分を検知

# Gemfile.lockに差分があるかどうか
- name: Diff Gemfile.lock
  uses: technote-space/get-diff-action@v6
  id: check-diff
  with:
    PATTERNS: |
      Gemfile.lock

get-diff-actionを用いて、Gemfile.lockに変更があるかを検知します。

https://github.com/technote-space/get-diff-action

差分があるかどうかは env.GIT_DIFF でbooleanで取得できます。

2. 差分があったらbaseイメージを作成

- name: base image Build, tag, and push image to Amazon ECR
  id: build-base-image
  if: env.GIT_DIFF
  env:
    ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
    ECR_REPOSITORY: base-rails
    IMAGE_TAG: latest
  run: |
    docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f base.Dockerfile .
    docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

手順1の結果はenv.GIT_DIFF を使用して取得できます。

もし差分があれば base.Dockerfile を元にライブラリインストール済みのDockerイメージを作成します。これをbaseイメージ用のECRにPUSHしておきます。

差分がない場合は、このステップはスキップされます。つまり、ライブラリインストールの時間をまるごと短縮できます。

3. baseイメージを元にDocker Imageを作成

# baseイメージを元にrailsイメージをECRにプッシュ
- name: rails image Build, tag, and push image to Amazon ECR
  id: build-image
  env:
    ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
    ECR_REPOSITORY: rails
    IMAGE_TAG: latest
  run: |
    docker login -u AWS -p $(aws ecr get-login-password) https://(AWSのACCOUNT_ID).dkr.ecr.ap-northeast-1.amazonaws.com
    docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f production.Dockerfile
    docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

手順2で作成したbaseイメージを元にして、Dockerイメージを作成し、ECRにPUSHすれば完了です!

簡単ですね!

最後に

今回はget-diff-actionを利用してDocker Buildの時間を削減する方法を紹介しました。

私たちのRailsアプリケーションのCI実行時間も、14~15分かかっていたものがたった3分で完了するように改善されました。

この手法はRailsアプリケーションだけでなく、他のフレームワークにも簡単に適用できるため、ぜひお試しください!

例えば、npmを使用しているアプリケーションの場合は以下のようになります。

- name: Diff package-lock.json
  uses: technote-space/get-diff-action@v6
  id: check-diff
  with:
    PATTERNS: |
      package-lock.json

もっとアルダグラムエンジニア組織を知りたい人、ぜひ下記の情報をチェックしてみてください!

アルダグラム Tech Blog

Discussion

ryoppippiryoppippi

github actionsのymlファイルの冒頭で
on.pull_request.paths にGemfile.lockを指定するのはいかがでしょうか
外部のactionsに依存せず、条件が発動しなければそもそもactionsが実行されず、また記述もより簡易的になるかなと思いました。
もしすでに検討されていたらすみません