LabHub

ブログ

GitHub Actions 上級 CI/CD:マトリクスビルド、キャッシュ戦略、セキュリティハードニング

한국어English日本語

はじめに: なぜ今 GitHub Actions の高度な最適化なのか

2026年現在、GitHub Actions は世界で最も広く使われている CI/CD プラットフォームとして定着した。GitHub の公式発表によれば、Fortune 100 企業の 90% 以上が GitHub Actions を利用しており、毎日数百万のワークフローが実行されている。しかし、ほとんどのチームは基本的なビルド・テスト・デプロイのパイプラインにとどまっている。

プロダクション環境では単純なパイプラインだけでは足りない。マルチプラットフォーム対応、ビルド時間の最適化、シークレット管理、サプライチェーンセキュリティ (Supply Chain Security) まで考慮する必要がある。特に 2025 年末に発生した tj-actions/changed-files アクションのサプライチェーン攻撃は、GitHub Actions のセキュリティハードニングの重要性を改めて思い起こさせた。

本記事では、マトリクスビルド最適化キャッシュ戦略Reusable WorkflowOIDC ベースの認証セキュリティハードニングまで、プロダクションレベルの CI/CD パイプラインを構築するための高度な手法を扱う。

一次資料および公式ドキュメント

出典説明
GitHub Actions 公式ドキュメントワークフロー構文、イベント、ランナーなど全体のリファレンス
GitHub Blog - Actions Security Best Practicesサプライチェーンセキュリティおよびアクションのベストプラクティス
OpenID Connect in GitHub ActionsOIDC トークンによるクラウド認証の公式ガイド
GitHub Actions - Caching Dependencies依存関係キャッシュ戦略の公式ガイド
Reusable Workflowsワークフロー再利用パターンの公式ドキュメント
GitHub Actions Runner - Self-hostedセルフホストランナーの設定・運用ガイド

1. マトリクスビルドの最適化

1.1 基本のマトリクス戦略

マトリクス戦略 (Matrix Strategy) は、複数の環境の組み合わせに対してワークフローを並列に実行する中核機能だ。OS、言語バージョン、依存関係のバージョンなどを組み合わせてクロステストを自動化できる。

name: Matrix CI
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      max-parallel: 6
      matrix:
        os: [ubuntu-22.04, ubuntu-24.04, macos-14]
        node-version: [20, 22]
        include:
          - os: ubuntu-24.04
            node-version: 22
            coverage: true
        exclude:
          - os: macos-14
            node-version: 20

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - run: npm ci
      - run: npm test

      - name: Upload coverage
        if: ${{ matrix.coverage }}
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

上の設定で fail-fast: false は、1 つのマトリクス組み合わせが失敗しても残りの組み合わせの実行を続けさせる。こうすることで、どの環境で問題が起きているかを一度に把握できる。

1.2 マトリクスの動的生成

変更されたファイルに応じてテスト対象を動的に決めるパターンは、大規模なモノレポでビルド時間を大きく削減する。

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Detect changed services
        id: set-matrix
        run: |
          CHANGED=$(git diff --name-only HEAD~1 HEAD | grep -oP '^services/\K[^/]+' | sort -u | jq -R . | jq -s .)
          if [ "$CHANGED" = "[]" ]; then
            echo "matrix={\"service\":[\"dummy\"]}" >> $GITHUB_OUTPUT
          else
            echo "matrix={\"service\":$CHANGED}" >> $GITHUB_OUTPUT
          fi

  build:
    needs: detect-changes
    if: ${{ fromJson(needs.detect-changes.outputs.matrix).service[0] != 'dummy' }}
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJson(needs.detect-changes.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v4
      - name: Build service
        run: |
          echo "Building ${{ matrix.service }}"
          cd services/${{ matrix.service }}
          docker build -t ${{ matrix.service }}:${{ github.sha }} .

1.3 マトリクス最適化のヒント


2. キャッシュ戦略の深掘り

2.1 キャッシュ種別の比較

キャッシュ戦略長所短所適した状況
actions/cache汎用的で細かい制御が可能キー管理が手動カスタムビルドツール
setup-node の cache オプション設定が簡単で自動でキー生成Node.js 専用Node.js プロジェクト
Docker layer cachingイメージビルド時間の短縮キャッシュサイズ制限(10GB)コンテナビルド
Artifact cachingジョブ間のデータ受け渡しワークフロー内に限定ビルド成果物の共有

2.2 高度なキャッシュ設定

効果的なキャッシュキー戦略は、ビルド時間を 50~70% 短縮できる。

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

      # 多段階キャッシュ復元戦略
      - name: Cache node_modules
        uses: actions/cache@v4
        id: npm-cache
        with:
          path: |
            node_modules
            ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      # Next.js ビルドキャッシュ
      - name: Cache Next.js build
        uses: actions/cache@v4
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
            ${{ runner.os }}-nextjs-

      # Gradle キャッシュ (Java/Kotlin プロジェクト)
      - name: Cache Gradle packages
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Install dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci

      - name: Build
        run: npm run build

2.3 Docker ビルドキャッシュの最適化

Docker イメージのビルドで BuildKit キャッシュを活用すると、ビルド時間を大きく短縮できる。

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

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

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push with cache
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

cache-from: type=ghacache-to: type=gha,mode=max を使うと、GitHub Actions のキャッシュバックエンドを介して Docker レイヤーをキャッシュする。mode=max は中間レイヤーまですべてキャッシュし、後続ビルドの速度を最大化する。

2.4 キャッシュ管理の注意点


3. Reusable Workflow と Composite Action

3.1 Reusable Workflow

組織全体で標準化された CI/CD パイプラインを共有するには Reusable Workflow を使う。呼び出す側では uses キーワードで別リポジトリのワークフローを参照する。

呼び出されるワークフロー (.github/workflows/reusable-deploy.yml):

name: Reusable Deploy Workflow

on:
  workflow_call:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: string
      image-tag:
        description: 'Docker image tag'
        required: true
        type: string
    secrets:
      KUBE_CONFIG:
        required: true
    outputs:
      deploy-url:
        description: 'Deployed URL'
        value: ${{ jobs.deploy.outputs.url }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    outputs:
      url: ${{ steps.deploy.outputs.url }}
    steps:
      - uses: actions/checkout@v4

      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=kubeconfig

      - name: Deploy to Kubernetes
        id: deploy
        run: |
          kubectl set image deployment/app \
            app=${{ inputs.image-tag }} \
            -n ${{ inputs.environment }}
          kubectl rollout status deployment/app \
            -n ${{ inputs.environment }} --timeout=300s
          URL=$(kubectl get ingress app -n ${{ inputs.environment }} -o jsonpath='{.spec.rules[0].host}')
          echo "url=https://$URL" >> $GITHUB_OUTPUT

呼び出す側のワークフロー:

name: Production Deploy

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        id: meta
        run: |
          TAG="ghcr.io/${{ github.repository }}:${{ github.ref_name }}"
          docker build -t $TAG .
          docker push $TAG
          echo "tags=$TAG" >> $GITHUB_OUTPUT

  deploy-staging:
    needs: build
    uses: my-org/shared-workflows/.github/workflows/reusable-deploy.yml@v2
    with:
      environment: staging
      image-tag: ${{ needs.build.outputs.image-tag }}
    secrets:
      KUBE_CONFIG: ${{ secrets.STAGING_KUBE_CONFIG }}

  deploy-production:
    needs: [build, deploy-staging]
    uses: my-org/shared-workflows/.github/workflows/reusable-deploy.yml@v2
    with:
      environment: production
      image-tag: ${{ needs.build.outputs.image-tag }}
    secrets:
      KUBE_CONFIG: ${{ secrets.PROD_KUBE_CONFIG }}

3.2 Composite Action

Composite Action は複数のステップ (steps) を 1 つの再利用可能なアクションにまとめる。Reusable Workflow と違い、ジョブではなくステップ単位で再利用される。

# .github/actions/setup-and-test/action.yml
name: 'Setup and Test'
description: 'Install dependencies, lint, and test'

inputs:
  node-version:
    description: 'Node.js version'
    required: false
    default: '22'
  working-directory:
    description: 'Working directory for the project'
    required: false
    default: '.'

outputs:
  coverage-percentage:
    description: 'Test coverage percentage'
    value: ${{ steps.coverage.outputs.percentage }}

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
        cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json

    - name: Install dependencies
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      run: npm ci

    - name: Lint
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      run: npm run lint

    - name: Test with coverage
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      run: npm test -- --coverage --coverageReporters=text-summary

    - name: Extract coverage
      id: coverage
      shell: bash
      run: |
        COVERAGE=$(cat ${{ inputs.working-directory }}/coverage/coverage-summary.json | jq '.total.lines.pct')
        echo "percentage=$COVERAGE" >> $GITHUB_OUTPUT

3.3 Reusable Workflow と Composite Action の比較

区分Reusable WorkflowComposite Action
再利用の単位ジョブ全体ステップ単位
呼び出し方法jobs.*.usessteps.*.uses
secrets の受け渡し明示的に渡す必要がある呼び出し元のコンテキストを継承
ネスト呼び出し最大 4 段階最大 10 段階
ランナーの選択ワークフロー内で指定呼び出し元のランナーを使用
適した用途パイプライン全体の標準化共通の setup/teardown ステップ

4. GitHub-hosted と Self-hosted Runner の比較

4.1 比較表

項目GitHub-hosted RunnerSelf-hosted Runner
コスト分単位の課金 (Linux 0.008 USD/分)インフラ費用のみ発生
環境毎回クリーンな VM永続的な環境 (キャッシュ保持が可能)
カスタマイズ限定的 (プリインストール済みツールのみ)完全に自由 (GPU、特殊 HW)
セキュリティGitHub が管理組織が自ら管理
ネットワークパブリックインターネットプライベートネットワークにアクセス可能
スケーリング自動手動またはオートスケーリング構成が必要
同時実行の上限プランにより異なる直接制御
メンテナンス不要OS パッチ、ランナー更新が必要

4.2 Self-hosted Runner のセキュリティ上の注意点

Self-hosted Runner をパブリックリポジトリで使ってはならない。フォークされた PR から悪意あるコードがランナー上で実行されうるからだ。必ずプライベートリポジトリでのみ使うか、Actions Runner Controller (ARC) のようなツールでエフェメラル (ephemeral) ランナーを使うこと。

# Actions Runner Controller (ARC) - Kubernetes ベースのオートスケーリング セルフホストランナー
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
  name: runner-deployment
spec:
  replicas: 3
  template:
    spec:
      repository: my-org/my-repo
      ephemeral: true
      labels:
        - self-hosted
        - linux
        - x64
        - gpu
      resources:
        limits:
          nvidia.com/gpu: 1
          memory: '16Gi'
        requests:
          cpu: '4'
          memory: '8Gi'
---
apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
  name: runner-autoscaler
spec:
  scaleTargetRef:
    kind: RunnerDeployment
    name: runner-deployment
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: TotalNumberOfQueuedAndInProgressWorkflowRuns
      repositoryNames:
        - my-org/my-repo

5. OIDC ベースのクラウド認証 (セキュリティハードニングの中核)

5.1 なぜ OIDC なのか

従来の方式では、AWS Access Key や GCP Service Account Key といった長期クレデンシャルを GitHub Secrets に保存して使っていた。この方式にはいくつものリスクがある。

OIDC (OpenID Connect) トークンを使うと、GitHub Actions はクラウドプロバイダーに短期トークンで認証する。長期クレデンシャルが不要になり、セキュリティが大きく向上する。

5.2 AWS の OIDC 認証設定

name: Deploy to AWS
on:
  push:
    branches: [main]

permissions:
  id-token: write # OIDC トークンのリクエストに必要
  contents: read

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

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          role-session-name: github-actions-${{ github.run_id }}
          aws-region: ap-northeast-2

      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster production \
            --service my-app \
            --force-new-deployment

      - name: Verify deployment
        run: |
          aws ecs wait services-stable \
            --cluster production \
            --services my-app

5.3 GCP の OIDC 認証設定

jobs:
  deploy-gcp:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
          service_account: 'deploy@my-project.iam.gserviceaccount.com'

      - name: Deploy to Cloud Run
        uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: my-app
          region: asia-northeast3
          image: gcr.io/my-project/my-app:${{ github.sha }}

6. セキュリティハードニング総合ガイド

6.1 最小権限の原則の適用

GitHub Actions の GITHUB_TOKEN はデフォルトで広範な権限を持つ。本当に必要な権限だけを明示的に宣言しなければならない。

name: Secure Workflow

# グローバルレベルですべての権限を最小化
permissions: {}

on:
  pull_request:
    branches: [main]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    # ジョブレベルで必要な権限だけを宣言
    permissions:
      contents: read
      checks: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

6.2 アクションバージョンのピン留め (Supply Chain Security)

タグ (v4) の代わりにコミット SHA を使ってアクションを固定する。タグは悪意を持って変更されうるが、コミット SHA は変更できない。

steps:
  # 危険: タグは変更されうる
  # - uses: actions/checkout@v4

  # 安全: コミット SHA で固定
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

  - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
    with:
      node-version: '22'

  - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
    with:
      path: node_modules
      key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

GitHub が提供する Dependabot を活用すれば、この SHA ピン留めを自動で管理できる。.github/dependabot.yml に次を追加する。

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: 'github-actions'
    directory: '/'
    schedule:
      interval: 'weekly'
    groups:
      actions:
        patterns:
          - '*'

6.3 シークレット管理のベストプラクティス

jobs:
  secure-deploy:
    runs-on: ubuntu-latest
    environment: production # Environment Protection Rules を適用
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      # シークレットを環境変数として公開するときの注意点
      - name: Deploy with secrets
        env:
          # 個別のシークレットだけを明示的に渡す
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}
        run: |
          # シークレットがログに出ないようマスクする
          echo "::add-mask::$DATABASE_URL"
          echo "::add-mask::$API_KEY"
          ./deploy.sh

6.4 フォーク PR のセキュリティ設定

フォークされたリポジトリから来る Pull Request はセキュリティリスクが高い。次の設定を推奨する。

jobs:
  test:
    # フォーク PR でも安全にテストを実行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - run: npm ci
      - run: npm test

  deploy-preview:
    # フォーク PR では実行しない
    if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: Deploy preview
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: ./deploy-preview.sh

6.5 Artifact Attestation (ビルド出所の証明)

GitHub Actions は SLSA (Supply-chain Levels for Software Artifacts) フレームワークをサポートしており、ビルド成果物の出所を証明できる。

jobs:
  build-with-attestation:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
      attestations: write
      packages: write
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Build Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .

      - name: Push to GHCR
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@v2
        with:
          subject-name: ghcr.io/${{ github.repository }}
          subject-digest: sha256:${{ steps.build.outputs.digest }}
          push-to-registry: true

7. 実践的な統合ワークフロー: プロダクションパイプライン

ここまで扱ったすべての手法を統合した、プロダクションレベルの CI/CD パイプラインの例だ。

name: Production CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# グローバルの最小権限
permissions:
  contents: read

# 同一ブランチでの以前の実行をキャンセル
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
        with:
          node-version: '22'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check

  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
        with:
          node-version: '22'
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --shard=${{ matrix.shard }}/4
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.shard }}
          path: test-results/
          retention-days: 7

  security-scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
      - name: Upload scan results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

  build-and-push:
    needs: [lint, test, security-scan]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
      attestations: write
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

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

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Attest build provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          subject-digest: ${{ steps.build.outputs.digest }}
          push-to-registry: true

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    environment: production
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: ap-northeast-2

      - name: Deploy to EKS
        run: |
          aws eks update-kubeconfig --name production-cluster
          kubectl set image deployment/app \
            app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          kubectl rollout status deployment/app --timeout=300s

8. 運用上の注意点と失敗事例

8.1 よくある失敗事例

事例 1: キャッシュポイズニング攻撃

パブリックリポジトリで、フォーク PR が悪意ある依存関係をキャッシュに注入し、その後のビルドがそのキャッシュを使って悪意あるコードが実行された事例がある。対策として pull_request イベントでのキャッシュ書き込みを制限し、キャッシュキーにブランチ情報を含める。

事例 2: シークレットのログ露出

デバッグのために環境変数を出力する過程で、シークレットがログに露出した事例だ。GitHub Actions は登録済みのシークレットを自動的にマスクするが、シークレットを加工 (base64 エンコードなど) してから出力するとマスクが回避される。echo "::add-mask::" コマンドで加工後の値も必ずマスクしなければならない。

事例 3: サードパーティアクションのタグ改ざん

2025 年末に発生した tj-actions/changed-files のサプライチェーン攻撃では、攻撃者がアクションのタグを悪意あるコードを含むコミットに付け替えた。タグではなくコミット SHA を使っていれば影響を受けなかったはずだ。

事例 4: concurrency 未設定による重複デプロイ

立て続けにプッシュしたときに複数のデプロイジョブが同時に実行され、ロールバックが複雑になった事例だ。concurrency グループを設定し、同一環境への同時デプロイを防ぐ必要がある。

8.2 コスト最適化のヒント


9. プロダクション CI/CD チェックリスト

セキュリティ

パフォーマンス

メンテナンス

サプライチェーンセキュリティ


まとめ

GitHub Actions の高度な機能を活用すれば、単純なビルド・テストの自動化を超えて、プロダクションレベルのセキュリティと効率性を備えた CI/CD パイプラインを構築できる。特に次の 3 点を核として覚えておきたい。

  1. セキュリティ優先: 最小権限の原則、SHA ピン留め、OIDC 認証は選択ではなく必須だ
  2. キャッシュ戦略: 適切なキャッシュ設定だけでもビルド時間を 50% 以上短縮できる
  3. モジュール化: Reusable Workflow と Composite Action で保守性を高め、組織全体の標準を確立する

CI/CD パイプラインは一度作って終わりではなく、継続的に改善しセキュリティを強化していく生きたシステムだ。定期的にチェックリストを点検し、新しいセキュリティ脅威に対応することが重要だ。


参考資料

コメント

まだコメントはありません。

ログインするとコメントできます