CI/CDパイプラインの基礎|継続的インテグレーション・デリバリーの全体像

kento_morota 18分で読めます

「手動でテストを実行して、手動でサーバーにデプロイしている」——このような開発フローでは、ミスが発生しやすく、リリースに時間がかかり、チームの開発速度が低下します。

CI/CD(継続的インテグレーション・継続的デリバリー)は、コードの変更からテスト、デプロイまでを自動化し、高品質なソフトウェアを迅速にリリースするための仕組みです。本記事では、CI/CDの基本概念からパイプラインの構築方法まで、体系的に解説します。

CI/CDとは何か

CI/CDは「Continuous Integration(継続的インテグレーション)」と「Continuous Delivery/Deployment(継続的デリバリー/デプロイ)」の略称です。それぞれの概念を正確に理解しましょう。

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

CIは、開発者がコードの変更を頻繁に(少なくとも1日1回以上)共有リポジトリにマージし、その都度自動でビルドとテストを実行するプラクティスです。

CIがない場合の問題

・各開発者が長期間独自のブランチで作業し、マージ時に大量のコンフリクトが発生する
・「自分の環境では動くのに」というバグが頻発する
・テストの実行が属人的で、テスト漏れが起きやすい

CIを導入するメリット

・バグの早期発見(コミットのたびにテストが実行される)
・マージの負担軽減(小さな変更を頻繁にマージ)
・コード品質の一貫性(リンター、フォーマッター、静的解析の自動実行)
・チーム内の透明性向上(ビルド状態が常に可視化される)

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

CDには2つの意味があります。

継続的デリバリー(Continuous Delivery)
CIのパイプラインを拡張し、コードの変更をいつでもリリースできる状態に保つプラクティスです。本番環境へのデプロイは手動のトリガー(承認プロセス)を経て行います。

継続的デプロイ(Continuous Deployment)
すべてのテストに合格した変更が自動的に本番環境にデプロイされるプラクティスです。人手による承認プロセスがありません。

// CI/CDの段階
コード変更 → ビルド → テスト → ステージング環境にデプロイ
                │                          │
                └── ここまでがCI ──┘       │
                                            │
                                  ┌─── 手動承認 ───→ 本番デプロイ(継続的デリバリー)
                                  └─── 自動 ───→ 本番デプロイ(継続的デプロイ)

多くの組織では、まず継続的デリバリーから始めて、信頼性が確立された後に継続的デプロイへ移行します。

CI/CDパイプラインの構成要素

CI/CDパイプラインは、コードの変更がトリガーとなって実行される一連の自動化されたステップです。

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

1. ソースステージ
コードの変更を検知してパイプラインを起動します。プルリクエストの作成、ブランチへのプッシュ、タグの作成などがトリガーになります。

2. ビルドステージ
ソースコードをコンパイル、依存関係のインストール、Dockerイメージのビルドなどを行います。

3. テストステージ
自動テストを実行します。単体テスト、結合テスト、E2Eテスト、セキュリティテストなど、複数のテストを並列で実行します。

4. 静的解析ステージ
リンター、フォーマッター、コードの複雑度分析、セキュリティスキャンなどを実行します。

5. デプロイステージ
テストに合格したアーティファクトを環境にデプロイします。ステージング環境→本番環境の順序で進めることが一般的です。

GitHub Actionsでパイプラインを構築する

GitHub Actionsは、GitHubに統合されたCI/CDプラットフォームです。YAMLファイルでワークフローを定義し、リポジトリのイベントに応じて自動実行されます。

基本的なCIワークフロー

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

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

jobs:
  # リンターとフォーマッターの実行
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Node.jsのセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: 依存関係のインストール
        run: npm ci

      - name: ESLintの実行
        run: npm run lint

      - name: Prettierの実行
        run: npm run format:check

  # 単体テスト
  test:
    runs-on: ubuntu-latest
    needs: lint  # lintが成功した後に実行
    steps:
      - uses: actions/checkout@v4

      - name: Node.jsのセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: 依存関係のインストール
        run: npm ci

      - name: テストの実行
        run: npm test -- --coverage

      - name: カバレッジレポートのアップロード
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  # ビルド
  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4

      - name: Node.jsのセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: 依存関係のインストール
        run: npm ci

      - name: ビルド
        run: npm run build

      - name: ビルドアーティファクトのアップロード
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/

CDワークフロー(本番デプロイ)

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    tags:
      - 'v*'  # v1.0.0 のようなタグがプッシュされたときに実行

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build

      - name: Dockerイメージのビルドとプッシュ
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ghcr.io/your-org/your-app:${{ github.ref_name }}
            ghcr.io/your-org/your-app:latest

  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    environment: staging
    steps:
      - name: ステージング環境にデプロイ
        run: |
          # デプロイコマンド(環境に応じて変更)
          echo "Deploying to staging..."

  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment:
      name: production
      url: https://app.example.com
    steps:
      - name: 本番環境にデプロイ
        run: |
          echo "Deploying to production..."

プルリクエスト時の自動チェック

# .github/workflows/pr-check.yml
name: PR Check

on:
  pull_request:
    types: [opened, synchronize, reopened]

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

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run lint
      - run: npm test -- --coverage

      # テストカバレッジをPRにコメントとして投稿
      - name: カバレッジレポート
        uses: davelosert/vitest-coverage-report-action@v2
        if: always()

  # セキュリティスキャン
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm audit --audit-level=high

  # 依存関係のライセンスチェック
  license-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx license-checker --failOn 'GPL-3.0'

GitLab CIでのパイプライン構築

GitLab CIは、GitLabに組み込まれたCI/CDプラットフォームです。.gitlab-ci.ymlファイルでパイプラインを定義します。

基本的なパイプライン設定

# .gitlab-ci.yml
stages:
  - lint
  - test
  - build
  - deploy

variables:
  NODE_VERSION: "20"

# 共通のキャッシュ設定
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/

# リンター
lint:
  stage: lint
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run lint
    - npm run format:check

# テスト
test:
  stage: test
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm test -- --coverage
  coverage: '/All files\s*\|\s*(\d+\.?\d*)\s*\|/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

# ビルド
build:
  stage: build
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

# ステージングデプロイ
deploy-staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - echo "Deploying to staging..."
  only:
    - develop

# 本番デプロイ
deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://app.example.com
  script:
    - echo "Deploying to production..."
  only:
    - tags
  when: manual  # 手動承認が必要

テスト自動化のベストプラクティス

CI/CDパイプラインの価値は、テストの品質に大きく依存します。効果的なテスト戦略を構築しましょう。

テストピラミッド

テストピラミッドは、テストの種類とその割合の指針を示すモデルです。

// テストピラミッド
        /\
       /  \        E2Eテスト(少数・遅い・高コスト)
      /    \       ブラウザを使った画面操作テスト
     /------\
    /        \     結合テスト(中程度)
   /          \    API・コンポーネント間のテスト
  /------------\
 /              \  単体テスト(多数・速い・低コスト)
/________________\ 関数・クラス単位のテスト

単体テスト:実行が速く、多数のケースをカバーできます。パイプラインの最初に実行し、素早くフィードバックを得ます。

結合テスト:複数のコンポーネントが正しく連携するかを確認します。データベースやAPIとの結合をテストします。

E2Eテスト:実際のユーザー操作をシミュレートします。重要なユーザーフローのみをカバーし、テスト数は最小限にとどめます。

テストの並列実行

テストの実行時間がCI/CDパイプラインのボトルネックになることがあります。テストを並列実行することで、フィードバックまでの時間を短縮できます。

# GitHub Actionsでのテスト並列実行
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]  # 4つのシャードに分割
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - name: テストの実行(シャード ${{ matrix.shard }}/4)
        run: npm test -- --shard=${{ matrix.shard }}/4

デプロイ戦略

本番環境へのデプロイは、サービスの可用性を維持しながら安全に行う必要があります。代表的なデプロイ戦略を紹介します。

ローリングデプロイ

複数のサーバーを1台ずつ順番に更新する方式です。ロードバランサーの背後にあるサーバーを一部ずつ更新します。

// ローリングデプロイの流れ
1. サーバーA をロードバランサーから除外
2. サーバーA を新バージョンに更新
3. サーバーA をロードバランサーに復帰
4. サーバーB について同じ手順を実行
5. サーバーC について同じ手順を実行

メリット:追加のインフラコストが不要。
デメリット:デプロイ中に新旧バージョンが混在する。

ブルー/グリーンデプロイ

現在の本番環境(ブルー)と同一の新環境(グリーン)を用意し、トラフィックを一括で切り替える方式です。

// ブルー/グリーンデプロイの流れ
1. ブルー環境(旧バージョン)が本番トラフィックを処理中
2. グリーン環境(新バージョン)を構築
3. グリーン環境でテストを実行
4. ロードバランサーの向き先をグリーン環境に切り替え
5. 問題があればブルー環境に即座に切り戻し

メリット:即座のロールバックが可能。新旧の混在がない。
デメリット:2倍のインフラリソースが必要。

カナリアデプロイ

新バージョンを一部のユーザー(例:全体の5%)にのみ公開し、問題がなければ段階的に公開範囲を広げる方式です。

// カナリアデプロイの流れ
1. 新バージョンのサーバーを1台追加
2. トラフィックの5%を新バージョンに振り分け
3. エラー率・レスポンスタイムを監視
4. 問題がなければ 25% → 50% → 100% と段階的に拡大
5. 問題があれば新バージョンのサーバーを撤去

メリット:リスクを最小限に抑えられる。本番環境で段階的に検証できる。
デメリット:設定が複雑。モニタリング体制が必要。

CI/CDパイプラインのベストプラクティス

効果的なCI/CDパイプラインを構築・運用するためのベストプラクティスをまとめます。

パイプラインの高速化

依存関係のキャッシュ
npm、pip、Mavenなどの依存関係をキャッシュし、毎回のインストール時間を削減します。

# GitHub Actionsでのキャッシュ活用
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # node_modulesを自動キャッシュ

不要なステップの省略
変更されたファイルに応じて、実行するテストやビルドを限定します。

# パス条件によるジョブの実行制御
on:
  push:
    paths:
      - 'src/**'        # srcディレクトリの変更時のみ実行
      - 'package.json'
      - 'package-lock.json'
    paths-ignore:
      - '**.md'         # ドキュメントの変更では実行しない
      - '.github/**'

シークレットの管理

APIキー、パスワード、証明書などの機密情報は、CI/CDプラットフォームのシークレット管理機能を使用して安全に管理します。

# GitHub Actionsでのシークレット使用
steps:
  - name: デプロイ
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    run: |
      # シークレットは環境変数として利用可能
      aws s3 sync dist/ s3://my-bucket/

注意点

・シークレットをログに出力しない
・必要最小限の権限を持つサービスアカウントを使用する
・シークレットは定期的にローテーションする

パイプラインの可視化とモニタリング

パイプラインの状態をチーム全体で可視化することが重要です。

・READMEにビルドバッジを表示する
・パイプラインの失敗時にSlackやメールで通知する
・パイプラインの実行時間を定期的に確認し、ボトルネックを改善する
・デプロイ頻度、リードタイム、変更失敗率、平均復旧時間(DORA指標)を追跡する

まとめ:CI/CDで開発プロセスを改善しよう

CI/CDは、現代のソフトウェア開発において不可欠なプラクティスです。

本記事のポイントを振り返ります。

CI/CDの基本
・CIはコードの変更ごとにビルド・テストを自動実行する
・CDはリリース可能な状態を常に維持する(デリバリー)か、自動的にデプロイする(デプロイ)
・まずCIから始めて、段階的にCDに拡張する

パイプラインの構築
・GitHub ActionsやGitLab CIでYAMLベースのパイプラインを定義する
・リント→テスト→ビルド→デプロイの順序で構成する
・テストの並列実行とキャッシュで高速化する

デプロイ戦略
・ローリングデプロイ:シンプルで追加コスト不要
・ブルー/グリーンデプロイ:即座のロールバックが可能
・カナリアデプロイ:リスクを最小限に抑えた段階的リリース

運用のポイント
・パイプラインの実行時間は10分以内を目標にする
・シークレットは安全に管理し、定期的にローテーションする
・パイプラインの状態を可視化し、チーム全体で共有する

CI/CDの導入は一度にすべてを完璧にする必要はありません。まずは最もインパクトの大きい部分(テストの自動実行など)から始めて、段階的に改善していくことが成功の鍵です。

#CI/CD#パイプライン#DevOps
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

AI活用のヒントをお探しですか?お気軽にご相談ください。

まずは話だけ聞いてもらう