この記事は、リレーブログ企画「CI/CD」の記事です。
はじめに
ニフティでWEBサービスの開発・運用を担当している渡邊です。
Goで実装した複数のバッチをAWS Lambdaにデプロイする機会があったので、そのときに実装した内容を紹介していきます。
構成
GitHub Actionsを使ってデプロイを実装しています。
Lambdaはコンテナベースでバッチを管理し、GitHub ActionsによってECRへプッシュします。
通常、LambdaをECRのプッシュをトリガーに自動更新すにはEventBridgeが必要ですが、今回は管理を簡素化するため、Lambdaの更新もGitHub Actionsで直接行うことにしました。
ディレクトリ構成
以下のようなディレクトリ構造でバッチを実装することを想定します。
cmd/ディレクトリの下に、各バッチのメインロジックがそれぞれのディレクトリに配置される構成です。
各バッチディレクトリにDockerfileを設置し、それぞれのロジックが独立したLambda(バッチ)として実行されます。
.github内にデプロイを行うワークフローを配置しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
.github └── workflows ├── deploy_batch1.yml ├── deploy_batch2.yml ├── deploy_batch3.yml ├── reusable_build_push_docker_image.yml ├── reusable_update_lambda_function_image.yml batch ├── cmd │ ├── batch1 │ │ └── main.go │ │ └── Dockerfile │ ├── batch2 │ │ └── main.go │ │ └── Dockerfile │ └── batch3 │ └── main.go │ └── Dockerfile ├── go.mod ├── go.sum ├── models └── hoge.go └── usecase └── hoge.go |
Dockerfileの中身
|
FROM --platform=$BUILDPLATFORM golang:1.24.0-alpine3.21 AS builder ARG TARGETARCH WORKDIR / COPY ./ /. WORKDIR /cmd/batch1 RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -o batch1 ./main.go FROM alpine:3.21 RUN apk --no-cache add ca-certificates COPY --from=builder /cmd/batch1 . COPY /lambda_layer/ /opt/ ENTRYPOINT [ "/batch1" ] |
ワークフロー
今回は例として3つのバッチを作成するようになっていますが、実際に運用するときは数十個以上のバッチを管理することもあります。そのため、GitHub Actionsのワークフローは再利用できるように作っていきます。
さらに、今回の構成では Arm64 向けにビルドすることで、実行環境でのパフォーマンス向上を目指します。
ワークフローを作る前に、リポジトリに以下の設定が必要です。
- AWS_ACCESS_IAM_ROLE
- AWSへのアクセスをOIDCで行うため
- AWS_REGION
- AWS_ECR_REGISTRY_URI
まず、ECRへイメージをプッシュするワークフローを作ります。
ARM64で動くバッチにするための方法を2パターン紹介します。
ビルド
Linux x86_64で動かす場合
GitHub Actions のデフォルトの実行環境は Linux x86_64 です。そのため、Arm64 向けにビルドを行うには クロスコンパイルが必要になります。
Go の場合、公式にクロスコンパイルがサポートされているため、環境変数を指定することで比較的簡単に Arm64 向けのバイナリを作成できます。
また、Docker イメージのように OS/アーキテクチャが密接に関係する場合には、QEMU(マルチプラットフォームイメージのビルド)を用いたエミュレーションとDocker Buildxを使ってマルチアーキテクチャ対応のビルドを行う方法が有効です。
以下がワークフローの処理になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
# reusable_build_push_docker_image.yml on: workflow_call: inputs: docker_image_name: # ECRリポジトリ名 required: true type: string docker_tag: # イメージタグ(通常はlatest) required: true type: string dockerfile_path: # Dockerfileのパス required: true type: string secrets: AWS_ACCESS_IAM_ROLE: # AWS IAMロール required: true jobs: build_push_docker_image: name: Build and push Docker image runs-on: ubuntu-latest timeout-minutes: 3 environment: ${{ inputs.environment }} permissions: id-token: write # OIDC認証用 contents: read # リポジトリ読み取り用 steps: - name: Check out code uses: actions/checkout@v4 - name: Setup QEMU(linux/arm64) uses: docker/setup-qemu-action@v3 with: platforms: linux/arm64 - name: Build Docker image run: docker buildx build --platform linux/arm64 -t ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} . -f ${{ inputs.dockerfile_path }} - name: Assume role AWS token uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ACCESS_IAM_ROLE }} aws-region: ${{ vars.AWS_REGION }} - name: Push Docker image to ECR run: | aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin ${{ vars.AWS_ECR_REGISTRY_URI }} docker tag ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} docker push ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} |
メリット
- GitHub Actionsの毎月の無料利用枠内で実行できるため、コストを抑えられる
デメリット
- QEMUを使用してArm64環境をエミュレートするため、ネイティブのArm64環境でのビルドと比べて処理が遅くなる
Arm64ランナーを使う場合
2024/06/03にGitHub ActionsにArm64ランナーが追加されました。
そのため、QEMUが不要になり、直接ビルドを行えるようになりました。
以下がQEMUを使わないワークフローになります。
パブリックリポジトリの場合はubuntu-24.04-arm
を、プライベートリポジトリの場合はArm64のランナーを作成して指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
# reusable_build_push_docker_image.yml on: workflow_call: input docker_image_name: # ECRリポジトリ名 required: true type: string docker_tag: # イメージタグ(通常はlatest) required: true type: string dockerfile_path: # Dockerfileのパス required: true type: string aws_access_iam_role: # AWS IAMロール required: true type: string jobs: build_push_docker_image: name: Build and push Docker image runs-on: ubuntu-24.04-arm timeout-minutes: 3 environment: ${{ inputs.environment }} permissions: id-token: write # OIDC認証用 contents: read # リポジトリ読み取り用 steps: - name: Check out code uses: actions/checkout@v4 - name: Build Docker image run: docker buildx build --platform linux/arm64 -t ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} . -f ${{ inputs.dockerfile_path }} - name: Assume role AWS token uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ACCESS_IAM_ROLE }} aws-region: ${{ vars.AWS_REGION }} - name: Push Docker image to ECR run: | aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin ${{ vars.AWS_ECR_REGISTRY_URI }} docker tag ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} docker push ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} |
メリット
- QEMUを使っていないため、より高速にビルドを行える
デメリット
- プライベートリポジトリではLarge Runner扱いのため、無料利用枠外の扱いになってしまう(2025/04/02現在)
Lambdaへのデプロイ
aws cliを使ってLambdaファンクションを更新します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
# reusable_update_lambda_function_image.yml on: workflow_call: inputs: docker_image_name: # ECRリポジトリ名 required: true type: string docker_tag: # イメージタグ(通常はlatest) required: true type: string lambda_function_name: # Lambdaファンクション名 required: true type: string aws_access_iam_role: # AWS IAMロール required: true type: string jobs: updete_lambda_function_image: name: Updete Lambda function image runs-on: ubuntu-latest timeout-minutes: 3 environment: ${{ inputs.environment }} permissions: id-token: write contents: read steps: - name: Assume role AWS token uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ACCESS_IAM_ROLE }} aws-region: ${{ vars.AWS_REGION }} - name: Updete Lambda function image run: aws lambda update-function-code --region ${{ vars.AWS_REGION }} --function-name ${{ inputs.lambda_function_name }} --architectures arm64 --image ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} |
メイン処理
先ほど作成した再利用可能なワークフローを呼び出します。
Dockerのイメージ名とファイルパスを指定し、各バッチごとにこの設定を作成することでデプロイ環境が完成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
# deploy_batch1.yml name: Deploy batch1 on: push: branches: - develop paths: - "cmd/batch1/**" - "models/**" - "usecase/**" - "repository/**" - "go.mod" - "go.sum" jobs: build_push_docker_image: name: Call reusable build and push Docker image workflow uses: ./.github/workflows/reusable_build_push_docker_image.yml with: docker_image_name: batch1-ecr docker_tag: latest dockerfile_path: cmd/batch1/Dockerfile aws_access_iam_role: ${{ vars.AWS_ACCESS_IAM_ROLE }} update_lambda_function_ime: name: Call reusable update Lambda function image needs: [build_push_docker_image] uses: ./.github/workflows/reusable_update_lambda_function_image.yml with: docker_tag: latest lambda_function_name: batch1 docker_image_name: batch1-ecr aws_access_iam_role: ${{ vars.AWS_ACCESS_IAM_ROLE }} |
実行
今回は特定ブランチへのpushをトリガーとしてワークフローが実行されるように設定しました。
以下が、実際にデプロイした際の実行結果です。
QEMUを使った場合
Arm64ランナーを使った場合
検証に使ったバッチでは、Arm64ランナーを使うことで処理が少し速くなりました。
コードの大きさによってはもっと大きな差が出てくると思います。早くプライベートリポジトリでも無料利用枠ないで使えるようになるといいですね。
最後に
今回はGoで作成したバッチをLambdaに効率よくデプロイする方法を紹介しました。
GitHub Actionsは再利用可能なワークフローを作成することができるので、今回のようなデプロイ先が違うだけで、内部処理が一緒の場合は非常に役立ちます。
また、今回のように複数のバッチをコンテナベースで管理することで、環境の統一性が保たれ、メンテナンス性も向上します。
次回は、IWS さんです。
今回と同じくGitHub Actionsを使ったデプロイ方法を紹介してくれるようなので、楽しみです!