TechHub

エンジニアの成長をサポートする技術情報サイト

← 記事一覧に戻る

CI/CDパイプラインとは?継続的インテグレーションとデプロイの基礎

公開日: 2024年2月2日 著者: mogura
CI/CDパイプラインとは?継続的インテグレーションとデプロイの基礎

疑問

CI/CDパイプラインとは何で、どのように構築すればよいのでしょうか?継続的インテグレーションと継続的デプロイの基本を一緒に学んでいきましょう。

導入

CI/CD(Continuous Integration / Continuous Deployment)は、現代のソフトウェア開発において不可欠な手法です。コードの変更を自動的にテストし、デプロイすることで、開発効率と品質を大幅に向上させます。

本記事では、CI/CDの基本概念から、GitHub ActionsやJenkinsを使った実装方法、ベストプラクティスまで、実践的な観点から詳しく解説していきます。

CI/CDパイプラインのイメージ

解説

1. CI/CDとは

CI/CDは、継続的インテグレーション(CI)と継続的デプロイ/デリバリー(CD)を組み合わせた開発手法です。コードの変更を自動的にテストし、デプロイすることで、開発効率と品質を向上させます。

CI(Continuous Integration)

継続的インテグレーションは、開発者のコード変更を頻繁に統合し、自動的にテストする手法です。
主な目的:
- 早期のバグ発見
- 統合問題の早期解決
- コード品質の向上

CD(Continuous Deployment / Continuous Delivery)

継続的デプロイ/デリバリーは、CIを通過したコードを自動的に本番環境にデプロイする手法です。
主な目的:
- 迅速なリリース
- デプロイの自動化
- 人的ミスの削減

2. CI/CDパイプラインの基本フロー

CI/CDパイプラインは、コードの変更から本番環境へのデプロイまで、一連の自動化されたステップで構成されます。ビルド、テスト、デプロイの各段階を自動化することで、効率的な開発プロセスを実現します。

パイプラインの主要ステップ

  • ソースコードの取得: Gitリポジトリから最新のコードを取得します。通常は特定のブランチ(main、developなど)を監視し、変更を検出するとパイプラインを開始します。
  • ビルド: ソースコードをコンパイルし、実行可能な形式に変換します。依存関係のインストール、コンパイル、アーティファクトの作成などが含まれます。
  • テスト: ユニットテスト、統合テスト、E2Eテストなどを実行し、コードの品質と動作を確認します。テストが失敗した場合、パイプラインは停止します。
  • コード品質チェック: ESLint、SonarQubeなどのツールを使用して、コードの品質、セキュリティ、パフォーマンスをチェックします。
  • デプロイ: テストを通過したコードを、ステージング環境や本番環境にデプロイします。デプロイ戦略(ブルー・グリーン、カナリアなど)に応じて、適切な方法でデプロイします。

パイプラインの実行タイミング

パイプラインは、コードのプッシュ、プルリクエストの作成、マージ、スケジュール実行、手動実行などのタイミングで実行されます。開発者がコードをプッシュするたびに自動的に実行されることで、早期に問題を発見できます。

3. GitHub ActionsでのCI/CD実装

GitHub Actionsは、GitHubリポジトリ内で直接CI/CDパイプラインを構築できるサービスです。YAMLファイルでワークフローを定義し、コードのプッシュやプルリクエストに応じて自動的に実行されます。

基本的なワークフロー

GitHub Actionsのワークフローは、`.github/workflows`ディレクトリにYAMLファイルとして定義します。ワークフローは、トリガー(push、pull_requestなど)、ジョブ、ステップで構成されます。各ステップでは、アクションを実行し、ビルドやテストを行います。

デプロイワークフロー

デプロイワークフローでは、テストを通過したコードを本番環境にデプロイします。通常は、mainブランチへのマージ時に自動的にデプロイされます。環境変数やシークレットを使用して、デプロイ先の認証情報を管理します。

GitHub Actionsの基本的なワークフロー

この例では、プッシュとプルリクエスト時にテストを実行し、mainブランチへのマージ時にデプロイを実行するワークフローを定義しています。

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

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run linter
      run: npm run lint
    
    - name: Run tests
      run: npm test
    
    - name: Build
      run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Deploy to production
      run: |
        echo "Deploying to production..."
        # デプロイコマンドを実行
      env:
        DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

4. JenkinsでのCI/CD実装

Jenkinsは、オープンソースのCI/CDツールで、高度なカスタマイズが可能です。Jenkinsfileを使用して、パイプラインをコードとして管理できます。

Jenkinsfile(Pipeline as Code)

Jenkinsfileは、Jenkinsパイプラインをコードとして定義するファイルです。Groovy言語で記述され、リポジトリに含めることで、バージョン管理が可能になります。Declarative PipelineとScripted Pipelineの2つのスタイルがあります。

Jenkinsの主な特徴

  • 豊富なプラグイン: 数千のプラグインが利用可能で、様々なツールやサービスと統合できます。
  • 分散ビルド: 複数のエージェントを使用して、並列にビルドやテストを実行できます。
  • 柔軟な設定: GUIとコードの両方で設定でき、複雑なワークフローを構築できます。

Jenkinsfileの例

この例では、Declarative PipelineスタイルでJenkinsfileを定義しています。チェックアウト、ビルド、テスト、デプロイの各ステージを定義しています。

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm install'
                sh 'npm run build'
            }
        }
        
        stage('Test') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    junit 'test-results.xml'
                }
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh 'npm run deploy'
            }
        }
    }
    
    post {
        success {
            echo 'Pipeline succeeded!'
        }
        failure {
            echo 'Pipeline failed!'
        }
    }
}

5. テストの自動化

CI/CDパイプラインでは、様々な種類のテストを自動化することで、コードの品質を保証します。ユニットテスト、統合テスト、E2Eテストを組み合わせて、包括的なテスト戦略を構築します。

ユニットテスト

ユニットテストは、個々の関数やメソッドをテストする最も基本的なテストです。高速に実行でき、開発者が頻繁に実行できるため、CI/CDパイプラインの最初のステップとして実行されます。Jest、pytest、JUnitなどのフレームワークが使用されます。

統合テスト

統合テストは、複数のコンポーネントが連携して動作することを確認するテストです。データベースや外部APIとの連携をテストし、システム全体の動作を検証します。ユニットテストよりも実行時間が長いため、パイプラインの後半で実行されることが多いです。

E2Eテスト

E2E(End-to-End)テストは、ユーザーの視点からアプリケーション全体の動作をテストします。ブラウザを自動操作し、実際のユーザーシナリオを再現します。Selenium、Cypress、Playwrightなどのツールが使用されます。実行時間が長いため、重要なシナリオのみをテストすることが推奨されます。

テスト自動化のワークフロー例

この例では、ユニットテスト、統合テスト、E2Eテストを順番に実行するワークフローを定義しています。

# GitHub Actionsでのテスト実行例
name: Test Pipeline

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run unit tests
      run: npm run test:unit
    
    - name: Run integration tests
      run: npm run test:integration
      env:
        DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
    
    - name: Run E2E tests
      run: npm run test:e2e
      env:
        E2E_BASE_URL: ${{ secrets.E2E_BASE_URL }}

6. コード品質チェック

コード品質チェックは、CI/CDパイプラインの重要なステップです。ESLint、SonarQubeなどのツールを使用して、コードの品質、セキュリティ、パフォーマンスを自動的にチェックします。

ESLint

ESLintは、JavaScriptやTypeScriptのコードを静的解析し、潜在的な問題を発見するツールです。コーディング規約の違反、バグの可能性、セキュリティの問題などを検出します。CI/CDパイプラインに組み込むことで、コードレビュー前に問題を発見できます。

SonarQube

SonarQubeは、コードの品質、セキュリティ、保守性を包括的に分析するプラットフォームです。コードカバレッジ、複雑度、重複コード、セキュリティ脆弱性などを測定し、品質ゲートを設定して、基準を満たさないコードのマージを防ぐことができます。

その他のコード品質ツール

  • Prettier: コードフォーマッターで、一貫したコードスタイルを保証します。
  • CodeClimate: コードの品質を継続的に監視し、メトリクスを提供します。
  • Snyk: 依存関係のセキュリティ脆弱性をスキャンします。

コード品質チェックのワークフロー例

この例では、ESLint、Prettier、SonarQubeを実行してコード品質をチェックしています。

# ESLintとSonarQubeの実行例
name: Code Quality

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: 0  # SonarQube用
    
    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run ESLint
      run: npm run lint
    
    - name: Run Prettier check
      run: npm run format:check
    
    - name: SonarQube Scan
      uses: sonarsource/sonarqube-scan-action@master
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

7. デプロイ戦略

デプロイ戦略は、本番環境へのリリース方法を定義します。ブルー・グリーンデプロイ、カナリアデプロイ、ロールバック戦略などを適切に選択することで、ダウンタイムを最小限に抑え、安全にデプロイできます。

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

ブルー・グリーンデプロイは、2つの完全に同じ環境(ブルーとグリーン)を用意し、新しいバージョンをグリーン環境にデプロイしてから、トラフィックを切り替える方法です。問題が発生した場合は、即座にブルー環境に戻すことができ、ダウンタイムが発生しません。

カナリアデプロイ

カナリアデプロイは、新しいバージョンを段階的に展開する方法です。最初は少数のユーザー(例:5%)に新しいバージョンを提供し、問題がなければ徐々に割合を増やしていきます。問題が発生した場合は、すぐにロールバックできます。

ロールバック戦略

ロールバック戦略は、問題が発生した際に前のバージョンに戻す方法を定義します。自動ロールバック(メトリクスが閾値を超えた場合)と手動ロールバックの両方を準備しておくことが重要です。また、データベースのマイグレーションをロールバック可能にする必要があります。

カナリアデプロイの実装例

この例では、Kubernetesを使用してカナリアデプロイを実装しています。少数のレプリカで新しいバージョンをデプロイし、段階的に展開します。

# カナリアデプロイの例(Kubernetes)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-canary
spec:
  replicas: 1  # 全体の5%に相当
  selector:
    matchLabels:
      app: myapp
      version: canary
  template:
    metadata:
      labels:
        app: myapp
        version: canary
    spec:
      containers:
      - name: app
        image: myapp:v2.0.0
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: app-service
spec:
  selector:
    app: myapp
  ports:
  - port: 80
  # トラフィックをカナリアと本番に分散

8. 環境変数とシークレット管理

シークレット(APIキー、パスワード、トークンなど)の管理は、CI/CDパイプラインの重要なセキュリティ要件です。GitHub Secrets、AWS Secrets Managerなどのツールを使用して、安全にシークレットを管理します。

GitHub Secrets

GitHub Secretsは、リポジトリや組織レベルでシークレットを保存し、ワークフロー内で安全に使用できる機能です。シークレットは暗号化されて保存され、ログに出力されることはありません。`${{ secrets.SECRET_NAME }}`の形式でワークフロー内で参照できます。

AWS Secrets Manager

AWS Secrets Managerは、AWS環境でシークレットを安全に保存・管理するサービスです。自動ローテーション機能があり、定期的にシークレットを更新できます。CI/CDパイプラインからAWS CLIやSDKを使用してシークレットを取得できます。

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

  • 最小権限の原則: 各シークレットに必要な最小限の権限のみを付与します。
  • 定期的なローテーション: シークレットを定期的に更新し、古いシークレットを無効化します。
  • 監査ログ: シークレットへのアクセスを記録し、監査できるようにします。
  • 環境ごとの分離: 開発、ステージング、本番環境のシークレットを分離します。

シークレット管理の実装例

この例では、GitHub SecretsとAWS Secrets Managerを使用してシークレットを安全に管理しています。

# GitHub Secretsの使用例
name: Deploy with Secrets

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Deploy to production
      run: |
        # シークレットを使用してデプロイ
        echo "Deploying with API key..."
        curl -H "Authorization: Bearer ${{ secrets.API_KEY }}" \
             -X POST \
             https://api.example.com/deploy
      env:
        DATABASE_URL: ${{ secrets.DATABASE_URL }}
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

    # AWS Secrets Managerから取得する例
    - name: Get secret from AWS
      run: |
        SECRET=$(aws secretsmanager get-secret-value \
          --secret-id prod/database/password \
          --query SecretString --output text)
        echo "Secret retrieved"

9. 通知とアラート

CI/CDパイプラインの実行結果を通知することで、チームはパイプラインの状態を把握し、問題が発生した際に迅速に対応できます。Slack、メール、その他の通知チャネルを活用します。

Slack通知

Slack通知は、パイプラインの成功・失敗をSlackチャンネルに通知する方法です。GitHub ActionsやJenkinsのプラグインを使用して、Slack Webhook URLにメッセージを送信します。カスタマイズ可能なメッセージフォーマットで、詳細な情報を含めることができます。

メール通知

メール通知は、パイプラインの失敗時に開発者にメールを送信する方法です。SMTPサーバーを設定し、失敗したジョブの詳細を含むメールを送信します。ただし、頻繁な通知は避け、重要な失敗のみを通知することが推奨されます。

その他の通知方法

  • Microsoft Teams: Microsoft TeamsのWebhookを使用して通知を送信できます。
  • Discord: DiscordのWebhookを使用して通知を送信できます。
  • PagerDuty: 重要なアラートをPagerDutyに送信し、インシデント管理を行えます。

Slack通知の実装例

この例では、テストの成功・失敗に応じてSlackに通知を送信しています。

# Slack通知の例(GitHub Actions)
name: CI with Notifications

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Run tests
      run: npm test
    
    - name: Notify Slack on success
      if: success()
      uses: 8398a7/action-slack@v3
      with:
        status: custom
        custom_payload: |
          {
            text: "✅ Tests passed!"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    
    - name: Notify Slack on failure
      if: failure()
      uses: 8398a7/action-slack@v3
      with:
        status: custom
        custom_payload: |
          {
            text: "❌ Tests failed!"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

10. パフォーマンステスト

パフォーマンステストは、アプリケーションの応答時間、スループット、リソース使用率を測定し、パフォーマンスの問題を早期に発見します。CI/CDパイプラインに組み込むことで、パフォーマンスの回帰を防ぎます。

負荷テスト

負荷テストは、通常の負荷条件下でアプリケーションの動作を確認するテストです。期待される同時ユーザー数やリクエスト数でテストし、応答時間やエラー率を測定します。k6、Apache JMeter、Gatlingなどのツールが使用されます。

ストレステスト

ストレステストは、システムの限界を超える負荷をかけて、システムの動作を確認するテストです。システムがどのように動作するか、どの時点で失敗するかを確認し、適切なスケーリング戦略を決定します。

パフォーマンステストのベストプラクティス

  • 定期的な実行: パフォーマンステストをCI/CDパイプラインに組み込み、定期的に実行します。
  • ベースラインの設定: パフォーマンスのベースラインを設定し、回帰を検出します。
  • 段階的な負荷: 負荷を段階的に増やし、システムの動作を観察します。
  • メトリクスの監視: CPU、メモリ、ネットワークなどのリソース使用率を監視します。

負荷テストの実装例(k6)

この例では、k6を使用して負荷テストを実装しています。段階的に負荷を増やし、パフォーマンスの閾値を設定しています。

// k6による負荷テストの例
import http from 'k6/http';
import { check, sleep } from 'k6';

// テスト設定
export const options = {
  stages: [
    { duration: '30s', target: 20 },  // 30秒で20ユーザーまで増加
    { duration: '1m', target: 20 },   // 1分間20ユーザーを維持
    { duration: '30s', target: 0 },   // 30秒で0ユーザーまで減少
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95%のリクエストが500ms以内
    http_req_failed: ['rate<0.01'],  // エラー率が1%未満
  },
};

// テストシナリオ
export default function () {
  const response = http.get('https://api.example.com/users');
  
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  
  sleep(1);
}

11. ベストプラクティス

CI/CDパイプラインを効果的に構築・運用するには、適切なベストプラクティスに従うことが重要です。段階的な導入、適切なテスト戦略、セキュリティの考慮などが成功の鍵となります。

ベストプラクティス

  • 段階的な導入: 一度にすべてを自動化しようとせず、段階的にCI/CDを導入します。まずはビルドとテストの自動化から始め、徐々にデプロイの自動化を追加します。
  • 高速なフィードバック: パイプラインの実行時間を短縮し、開発者が迅速にフィードバックを受けられるようにします。並列実行やキャッシュを活用して、実行時間を最適化します。
  • 適切なテスト戦略: テストピラミッドに従い、ユニットテストを多く、E2Eテストを少なくします。重要なシナリオのみをE2Eテストでカバーし、実行時間を短縮します。
  • セキュリティの考慮: シークレットを安全に管理し、依存関係の脆弱性をスキャンします。最小権限の原則に従い、必要な権限のみを付与します。
  • 環境の一貫性: 開発、ステージング、本番環境を可能な限り同じに保ちます。コンテナやInfrastructure as Codeを使用して、環境の一貫性を確保します。
  • モニタリングとロギング: パイプラインの実行状況を監視し、問題が発生した際に迅速に対応できるようにします。ログを適切に管理し、問題の原因を特定しやすくします。
  • ドキュメント化: パイプラインの設定、デプロイ手順、トラブルシューティング方法をドキュメント化します。新しいメンバーが理解しやすいようにします。
  • 継続的な改善: パイプラインを定期的に見直し、改善の機会を探します。失敗したパイプラインの原因を分析し、再発を防ぐ対策を講じます。

よくある間違い

  • 過度な複雑さ: パイプラインを過度に複雑にすると、保守が困難になります。シンプルに保ち、必要に応じて段階的に複雑化します。
  • テストの不足: テストを十分に実行せずにデプロイすると、本番環境で問題が発生する可能性があります。適切なテスト戦略を実装します。
  • セキュリティの軽視: シークレットをコードに直接書いたり、適切に管理しないと、セキュリティリスクが発生します。適切なシークレット管理ツールを使用します。
  • 環境の不一致: 開発環境と本番環境が異なると、本番環境で予期しない問題が発生する可能性があります。環境の一貫性を保ちます。

まとめ

CI/CDパイプラインは、コードの変更を自動的にテストし、デプロイすることで、開発効率と品質を向上させる手法です。GitHub ActionsやJenkinsなどのツールを使用して、ビルド、テスト、デプロイを自動化できます。

適切なテスト戦略、デプロイ戦略、シークレット管理が重要です。段階的にCI/CDを導入し、継続的に改善することで、より効率的な開発プロセスを実現できます。

実践的なプロジェクトでCI/CDパイプラインを構築し、経験を積むことで、DevOpsのスキルが向上します。

Dockerとは?コンテナ技術の基礎と実践的な使い方