GitHub Actions で Lambda のデプロイを自動化する

概要

AWS Lambda を手動でデプロイしていると、更新のたびに「zip を作って」「コンソールにアップロードして」「環境変数を確認して」という作業が発生します。ちょっとした修正でもこの手順を踏むのは負担で、手作業のぶんだけデプロイ漏れや事故の温床にもなります。

この記事では、GitHub Actions を使って Lambda のデプロイを自動化する手順をハンズオン形式でまとめます。特に OIDC による IAM ロール連携(長期的なアクセスキーを GitHub に置かない方式)を中心に据えます。

対象読者は、Lambda を触ったことがあり、GitHub Actions の基本的な YAML が読める人です。

環境

  • ランタイム: Node.js 20.x(Python 3.12 でも考え方は同じ)
  • デプロイ対象: 単一の Lambda 関数
  • トリガー: main ブランチへの push
  • AWS 側: OIDC プロバイダ + デプロイ用 IAM ロール
  • CLI: aws v2

全体像

やることは大きく3つです。

  1. AWS 側に GitHub Actions を信頼する OIDC プロバイダと IAM ロールを作る
  2. GitHub Actions のワークフローで、そのロールを一時的に引き受ける(AssumeRole)
  3. ビルドした成果物を aws lambda update-function-code で反映する

アクセスキーを GitHub Secrets に長期保存しないのがポイントです。OIDC を使うと、ワークフロー実行時に発行される短命なトークンで AWS のロールを引き受けられます。

Step 1: AWS 側に OIDC プロバイダを作る

まず、GitHub の OIDC を信頼するプロバイダを IAM に登録します。

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list <指紋>

すでに同一 URL のプロバイダがアカウントに存在する場合は作成不要です。

Step 2: デプロイ用 IAM ロールを作る

このロールの信頼ポリシーで「どのリポジトリ・どのブランチからなら引き受けを許すか」を絞ります。ここが緩いと、他人のリポジトリからロールを引き受けられてしまうため重要です。

信頼ポリシーの例:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_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:<OWNER>/<REPO>:ref:refs/heads/main"
        }
      }
    }
  ]
}

sub の条件を repo:<OWNER>/<REPO>:ref:refs/heads/main にすることで、「そのリポジトリの main ブランチのワークフローからのみ」に限定できます。

権限ポリシー(何ができるか)は最小限にします。関数コードの更新だけなら以下で足ります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["lambda:UpdateFunctionCode", "lambda:GetFunction"],
      "Resource": "arn:aws:lambda:<REGION>:<ACCOUNT_ID>:function:<FUNCTION_NAME>"
    }
  ]
}

設定変更まで自動化する場合だけ lambda:UpdateFunctionConfiguration を足しますが、必要になるまで付けないのが安全です。

Step 3: GitHub Actions ワークフロー

.github/workflows/deploy.yml を作ります。

name: Deploy Lambda

on:
  push:
    branches: [main]

permissions:
  id-token: write   # OIDC トークン発行に必須
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: npm ci --omit=dev

      - name: Package
        run: zip -r function.zip . -x "*.git*" "tests/*"

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/<DEPLOY_ROLE_NAME>
          aws-region: <REGION>

      - name: Deploy to Lambda
        run: |
          aws lambda update-function-code \
            --function-name <FUNCTION_NAME> \
            --zip-file fileb://function.zip \
            --publish

ポイントは以下です。

  • permissions.id-token: write を忘れると OIDC トークンが発行されず AssumeRole に失敗します(ハマりどころ No.1)
  • configure-aws-credentials に role-to-assume を渡すだけで、以降のステップは一時クレデンシャルで動きます
  • --publish を付けると新しいバージョンが発行され、ロールバックや alias 運用がしやすくなります

デプロイ完了を待って確認する

update-function-code はコードの反映を非同期で開始します。直後にトラフィックを流す運用なら、更新完了を待つのが安全です。

aws lambda wait function-updated \
  --function-name <FUNCTION_NAME>

設定更新直後にコード更新をかけると ResourceConflictException が出ることがあるので、連続更新するワークフローでは wait を挟むと安定します。

実装例: alias を使った安全なリリース

いきなり本番トラフィックに新バージョンを当てるのが不安なら、alias を使います。

VERSION=$(aws lambda update-function-code \
  --function-name <FUNCTION_NAME> \
  --zip-file fileb://function.zip \
  --publish \
  --query 'Version' --output text)

aws lambda update-alias \
  --function-name <FUNCTION_NAME> \
  --name prod \
  --function-version "$VERSION"

alias にトラフィックの重み付けを設定すれば、カナリアリリースも可能です。

確認方法

  1. main に push し、Actions のログで Configure AWS credentials が成功しているか確認する
  2. aws lambda get-function --function-name <FUNCTION_NAME> --query 'Configuration.LastModified' で更新時刻が変わっているか確認する
  3. 実際に関数を叩いて挙動を確認する
aws lambda invoke \
  --function-name <FUNCTION_NAME> \
  --payload '{}' \
  --cli-binary-format raw-in-base64-out \
  out.json && cat out.json

注意点

  • id-token: write の付け忘れ — 最頻出のエラー原因
  • 信頼ポリシーの sub の書き方 — ブランチ限定・タグ限定・環境限定を使い分ける。ワイルドカードを広げすぎない
  • 権限は最小限 — まず UpdateFunctionCode だけで始める
  • 大きな成果物 — zip が 50MB を超える場合は S3 経由にする。コンテナイメージ運用なら ECR + --image-uri
  • 依存の同梱 — npm ci --omit=dev で本番依存だけにする

まとめ

  • GitHub Actions からの Lambda デプロイは、OIDC + IAM ロールで秘密鍵を持たずに実現できる
  • 信頼ポリシーの sub 条件でリポジトリ・ブランチを絞り、権限は最小限にする
  • id-token: write の付与を忘れない
  • 連続更新するなら wait function-updated、安全にリリースしたいなら alias を活用する

最初のセットアップさえ済ませれば、以降は main に merge するだけでデプロイが回ります。手作業から解放されるだけでなく、「誰が・いつ・どのコミットを」デプロイしたかがログに残るのも大きな利点です。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。