CI/CD入門 - 継続的インテグレーション・デリバリーの仕組みとパイプライン構築

CI/CD(Continuous Integration / Continuous Delivery・Deployment)の基本概念、パイプラインの構成要素、主要ツール比較、GitHub Actionsによる実装例をまとめる。

CIとCDの違い

用語正式名称概要
CIContinuous Integration(継続的インテグレーション)コード変更を頻繁にメインブランチへ統合し、自動でビルド・テストを実行する
CD(デリバリー)Continuous Delivery(継続的デリバリー)CIに加え、リリース可能な状態まで自動化する。本番デプロイは手動承認
CD(デプロイ)Continuous Deployment(継続的デプロイ)テスト通過後、本番環境への反映まですべて自動化する
flowchart LR
    A["コード変更"] --> B["ビルド"]
    B --> C["テスト"]
    C --> D["ステージング<br/>デプロイ"]
    D --> E{"手動承認"}
    E -->|"Continuous Delivery"| F["本番デプロイ"]
    E -->|"Continuous Deployment<br/>(承認不要)"| F

    style A fill:#e3f2fd
    style B fill:#e8f5e9
    style C fill:#e8f5e9
    style D fill:#fff3e0
    style F fill:#fce4ec

    subgraph CI ["CI(継続的インテグレーション)"]
        B
        C
    end

CI(継続的インテグレーション)

  • 開発者がコードをプッシュするたびに、自動でビルドとテストが実行される
  • 統合の問題を早期に検出し、修正コストを低減する
  • 「動くコード」が常にメインブランチに存在する状態を維持する

CD(継続的デリバリー / 継続的デプロイ)

  • Continuous Delivery: リリース候補を自動で作成するが、本番へのデプロイは人間が判断する
  • Continuous Deployment: テストをすべて通過すれば自動的に本番へデプロイされる
  • 小規模チームやSaaSではContinuous Deployment、規制産業やエンタープライズではContinuous Deliveryが採用されることが多い

パイプラインの構成要素

典型的なCI/CDパイプラインは以下のステージで構成される。

flowchart TD
    A["1. ソースコード取得<br/>(checkout)"] --> B["2. 依存関係の<br/>インストール"]
    B --> C["3. 静的解析<br/>(lint / format check)"]
    C --> D["4. ユニットテスト"]
    D --> E["5. ビルド"]
    E --> F["6. 統合テスト /<br/>E2Eテスト"]
    F --> G["7. アーティファクト<br/>の保存"]
    G --> H["8. デプロイ"]

    style A fill:#e3f2fd
    style B fill:#e3f2fd
    style C fill:#e8f5e9
    style D fill:#e8f5e9
    style E fill:#fff3e0
    style F fill:#fff3e0
    style G fill:#f3e5f5
    style H fill:#fce4ec
ステージ目的ツール例
ソースコード取得リポジトリからコードをチェックアウトactions/checkout
依存関係インストールパッケージの復元・キャッシュnpm, pip, Maven, Gradle
静的解析コードスタイル・品質チェックESLint, Prettier, Flake8, RuboCop
ユニットテスト個別の関数・モジュールの検証Jest, pytest, JUnit
ビルド実行可能なアーティファクトの生成Vite, webpack, Gradle, Docker
統合 / E2Eテスト複数コンポーネントの結合動作確認Playwright, Cypress, Selenium
アーティファクト保存ビルド成果物の保管S3, GitHub Artifacts, ECR
デプロイ環境への反映AWS CLI, kubectl, Terraform

主要CI/CDツール比較

クラウドホスティング型

ツール特徴料金体系適したケース
GitHub ActionsGitHubに統合、YAML定義、豊富なMarketplaceパブリックリポジトリ無料、プライベートは月2,000分無料GitHub利用プロジェクト全般
GitLab CI/CDGitLabに統合、Auto DevOps機能月400分無料(SaaS版)GitLab利用プロジェクト
CircleCI高速実行、Docker対応が強力月6,000クレジット無料Docker中心の開発
AWS CodePipelineAWSサービスとのネイティブ統合パイプラインごとの従量課金AWSインフラ中心の開発

セルフホスティング型

ツール特徴適したケース
Jenkinsプラグインエコシステムが巨大、高い拡張性オンプレ環境、複雑なパイプライン
Drone CIDocker中心のシンプルな設計コンテナベースのワークフロー
Gitea ActionsGitHub Actions互換の構文Giteaセルフホスト環境

ツール選定の指針

  • ソースコード管理と同一プラットフォームを選ぶとセットアップが最小限になる
  • セルフホスティングランナーのサポートがあれば、クラウド型でもオンプレ実行が可能
  • エコシステム(Marketplace・プラグイン) の充実度が運用の効率に直結する

GitHub Actionsのワークフロー

基本構造

GitHub Actionsのワークフローは .github/workflows/ ディレクトリにYAMLファイルとして配置する。

# .github/workflows/ci.yml
name: CI

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

# ワークフロー全体で使用する環境変数
env:
  NODE_VERSION: "20"

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

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"

      - run: npm ci
      - run: npm run lint

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

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"

      - run: npm ci
      - run: npm test

  build:
    runs-on: ubuntu-latest
    needs: [lint, test] # lint, testの両方が成功してから実行
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"

      - run: npm ci
      - run: npm run build

      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/

主要な概念

概念説明
ワークフロー.ymlファイル1つに対応する自動化プロセス全体
イベント(onワークフローを起動するトリガー(push, pull_request, scheduleなど)
ジョブ(jobs同一ランナー上で実行されるステップのまとまり。デフォルトで並列実行
ステップ(stepsジョブ内の個別の処理。usesでアクション、runでシェルコマンドを実行
needsジョブ間の依存関係を定義する。指定したジョブの完了後に実行される
アクション(uses再利用可能な処理単位。Marketplaceで公開されている
マトリクス戦略複数の環境(Node.js 18/20/22 など)で同一ジョブを並列実行する

マトリクス戦略の例

複数のNode.jsバージョンとOSの組み合わせでテストを実行する場合:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node-version: [18, 20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

デプロイジョブの例(S3 + CloudFront)

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' # mainブランチのみ
    permissions:
      id-token: write  # OIDC認証に必要
      contents: read
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
          aws-region: ap-northeast-1

      - name: Deploy to S3
        run: aws s3 sync dist/ s3://my-bucket/ --delete

      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

典型的なパイプライン構成

フロントエンドプロジェクト

flowchart LR
    A["Push /<br/>Pull Request"] --> B["lint<br/>(ESLint, Prettier)"]
    B --> C["test<br/>(Jest / Vitest)"]
    C --> D["build<br/>(Vite / webpack)"]
    D --> E["deploy<br/>(S3 / Vercel /<br/>Cloudflare Pages)"]

    style A fill:#e3f2fd
    style B fill:#e8f5e9
    style C fill:#e8f5e9
    style D fill:#fff3e0
    style E fill:#fce4ec

バックエンドプロジェクト(コンテナ)

flowchart LR
    A["Push /<br/>Pull Request"] --> B["lint"]
    B --> C["unit test"]
    C --> D["Docker<br/>build"]
    D --> E["integration<br/>test"]
    E --> F["ECR<br/>push"]
    F --> G["ECS /<br/>EKS deploy"]

    style A fill:#e3f2fd
    style B fill:#e8f5e9
    style C fill:#e8f5e9
    style D fill:#fff3e0
    style E fill:#fff3e0
    style F fill:#f3e5f5
    style G fill:#fce4ec

モノレポ構成

モノレポでは変更されたパッケージのみパイプラインを実行することで効率化を図る。

on:
  push:
    paths:
      - "packages/frontend/**"

jobs:
  frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx turbo run build --filter=frontend

主要なモノレポツールとCI連携:

ツール特徴
Turborepoタスクのキャッシュ・並列実行、--filterで対象パッケージを絞り込み
Nx依存グラフに基づく影響範囲の検出(nx affected
Lernanpmパッケージ管理に特化、changesets連携

ブランチ戦略との連携

CI/CDパイプラインはブランチ戦略と密接に関連する。

Trunk-Based Development

gitGraph
    commit id: "init"
    branch feature-a
    commit id: "feat-a"
    checkout main
    merge feature-a id: "merge-a"
    commit id: "release-1"
    branch feature-b
    commit id: "feat-b"
    checkout main
    merge feature-b id: "merge-b"
特性内容
メインブランチmainのみ。常にデプロイ可能
フィーチャーブランチ短命(1〜2日以内にマージ)
CI実行タイミングすべてのpushとPR
CDmainマージ後に自動デプロイ
適したチーム小〜中規模、デプロイ頻度が高い

Git Flow

特性内容
メインブランチmain(本番)+ develop(開発)
フィーチャーブランチdevelopから分岐、developへマージ
リリースブランチrelease/*で最終調整後mainへマージ
CI実行タイミングdeveloprelease/*へのpush
CDmainマージ時に本番デプロイ
適したチーム大規模、リリースサイクルが固定

GitHub Flow

特性内容
メインブランチmainのみ
フィーチャーブランチPRベースでレビュー・マージ
CI実行タイミングPRの作成・更新時
CDmainマージ後に自動デプロイ
適したチーム中規模、WebアプリケーションやSaaS

セキュリティ

シークレット管理

CI/CDパイプラインではAPIキー、認証情報、トークンなどの機密情報を安全に扱う必要がある。

GitHub Actionsのシークレット:

steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.API_KEY }}         # リポジトリシークレット
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
    run: ./deploy.sh
レベルスコープ設定場所
リポジトリシークレット単一リポジトリSettings → Secrets and variables → Actions
環境シークレット特定の環境(production等)Settings → Environments → 環境名 → Secrets
Organizationシークレット組織内の複数リポジトリOrganization Settings → Secrets

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

  • ワークフローのログにシークレットが出力されないよう::add-mask::を活用する
  • シークレットは定期的にローテーションする
  • 最小権限の原則に基づき、必要なシークレットのみ各ジョブに渡す
  • .envファイルをリポジトリにコミットしない(.gitignoreに追加)

OIDC認証(OpenID Connect)

従来の長期的なアクセスキーの代わりに、OIDCを使用して一時的な認証情報を取得する方式。GitHub ActionsからAWSなどのクラウドプロバイダーへの認証で推奨される。

flowchart LR
    A["GitHub Actions<br/>ワークフロー"] -->|"1. OIDCトークン<br/>を取得"| B["GitHub<br/>OIDCプロバイダー"]
    B -->|"2. トークンを<br/>提示"| C["AWS STS<br/>(AssumeRoleWithWebIdentity)"]
    C -->|"3. 一時認証情報<br/>を返却"| A
    A -->|"4. 一時認証情報で<br/>AWSリソースにアクセス"| D["AWS<br/>リソース"]

    style A fill:#e3f2fd
    style B fill:#e8f5e9
    style C fill:#fff3e0
    style D fill:#fce4ec

OIDCのメリット:

観点長期アクセスキーOIDC
有効期間手動ローテーションまで有効ワークフロー実行時のみ有効(数分〜1時間)
漏洩リスクシークレットの漏洩リスクあり一時的な認証情報のため影響が限定的
管理コスト定期的なローテーションが必要自動的に管理される
設定アクセスキーをシークレットに保存IAMロールの信頼ポリシーを設定

AWS側のIAM信頼ポリシー例:

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

環境保護ルール

GitHub Actionsの Environments 機能を使用すると、デプロイ前に承認を要求したり、特定のブランチからのみデプロイを許可したりできる。

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production          # 保護ルールが適用される
      url: https://example.com
    steps:
      - run: ./deploy.sh
保護ルール説明
必須レビュアー指定したユーザーの承認がないとデプロイされない
待機タイマー指定した時間だけデプロイを遅延させる
ブランチ制限特定のブランチからのみデプロイを許可
デプロイメントブランチmainrelease/*のみにデプロイを制限

キャッシュとパフォーマンス最適化

CI/CDパイプラインの実行時間を短縮するための手法。

依存関係のキャッシュ

- uses: actions/setup-node@v4
  with:
    node-version: "20"
    cache: "npm"  # node_modulesのキャッシュを自動管理

手動でキャッシュを管理する場合:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-npm-

ジョブの並列実行

依存関係のないジョブは並列で実行し、needsで依存関係のあるジョブのみ直列にする。

jobs:
  lint:        # ┐
    ...        # ├─ 並列実行
  test:        # ┘
    ...
  build:
    needs: [lint, test]  # lint, testの完了後に実行

その他の最適化手法

手法効果
ジョブの並列化独立したジョブを同時に実行して全体時間を短縮
pathsフィルタ変更のあったファイルに関連するジョブのみ実行
DockerレイヤーキャッシュDockerビルド時にレイヤーを再利用して高速化
セルフホストランナー専用マシンで実行し、キャッシュの永続性を確保
concurrency設定同一ブランチでの重複実行をキャンセル

concurrencyの設定例:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # 同じグループの古い実行をキャンセル

参考リンク