🏗️

マルチステージCIとGitOpsで実現するマイクロサービスのCI/CD戦略

に公開

■概要

この記事では、複数のマイクロサービスを並行開発するチームが直面しがちな「開発速度」と「リリースの信頼性」という、トレードオフの関係にある課題を解決するため、実践的なCI/CDパイプライン戦略を提案します。この戦略は、開発速度を重視する「GitHub Flow」と、環境ごとの変更管理を厳密に行う「GitLab Flow」の長所を組み合わせた、独自のハイブリッドアプローチです。

この戦略の核心は、CI(継続的インテグレーション)とCD(継続的デリバリー)の責務を、リポジトリレベルで明確に分離する点にあります。

  • CIの責務 (アプリケーションリポジトリ): 高品質なコンテナイメージという「部品」を迅速に生成することに特化します。
  • CDの責務 (設定リポジトリ): CIが生成した「部品」を組み込み、システム全体を各環境へ安全かつ確実に届けることに特化します。

CIはマルチステージCIの考え方を採用し、コードの統合範囲と目的に応じて以下の4階層に分割することで、フィードバックサイクルを最適化します。

  • Developer CI: 開発者個人への即時フィードバック
  • Team CI: チームのメインブランチの品質保護
  • Component CI: リリース可能なコンテナイメージ(成果物)の作成
  • System CI: 複数コンポーネントを統合したシステム全体の品質保証

CDは、Gitを信頼できる唯一の情報源(Single Source of Truth)とするGitOpsの原則に基づき、設定リポジトリで駆動させます。Argo CDのようなプル型のGitOpsツールが設定リポジトリへの変更を検知し、自動テスト環境から始まり、dev、stg、prodの各環境へ段階的に変更を プロモーション(昇格) させていきます。プロモーションとは、ある環境で検証済みの変更を、次の上位環境へ反映させるプロセスを指します。

●この戦略が解決する課題

  • フィードバックの遅延: mainブランチにマージするまでテストが実行されず、手戻りが大きい。
  • リリースの複雑化: どのバージョンのどのサービスが、どの環境にデプロイされているかの管理が煩雑になる。
  • セキュリティテストの形骸化: リリース直前に脆弱性が見つかり、手戻りが大きい。
  • 手動作業によるミス: 環境へのデプロイ申請やPR作成といった手作業が多く、時間がかかりミスも発生しやすい。

●特徴

この多層的パイプライン戦略には、以下の特徴があります。

  • 迅速なフィードバック
    開発者が個人の作業ブランチにプッシュすると、変更差分のみを対象とする高速な「Developer CI」が実行されます。これにより、開発者は即座にフィードバックを得られます。
  • セキュリティのシフトレフト
    開発の初期段階(Developer/Team CI)にSAST(静的アプリケーションセキュリティテスト)を組み込み、脆弱性を早期に発見・修正します。CDプロセスではDAST(動的アプリケーションセキュリティテスト)を実行し、実行時の脆弱性も検出することで、セキュアなソフトウェアデリバリーを実現します。
  • プロモーションプロセスの完全自動化
    ある環境でのCDパイプラインが成功すると、次の環境へデプロイするためのプルリクエスト(PR)が自動で作成されます。これにより、手動でのPR作成作業が不要になり、プロセスが高速化します。
  • 信頼性の高い段階的リリース
    System CIでシステム全体の包括的なテストを実施します。次に、ステージング(stg)環境で最終的な自動検証を行うことで、本番環境へのデプロイに伴うリスクを大幅に低減します。

■パイプライン戦略の全体像

この戦略の全体像を以下の図に示します。アプリケーションリポジトリでのCIが完了すると、設定リポジトリでCDがトリガーされ、各環境へ段階的にデプロイされていく流れです。

●リポジトリ構成

リポジトリ種別 目的
アプリケーション front, bff, authなど 各マイクロサービスのソースコード管理。CIパイプラインを実行し、コンテナイメージを生成。
設定 config 各環境のKubernetesマニフェスト管理。このリポジトリへの変更が Argo CD などのツールによって検知され、自動でデプロイをトリガー。

●ブランチ戦略

リポジトリ種別 ブランチ戦略 説明
アプリケーション GitHub Flow mainブランチを常にデプロイ可能な状態に維持。フィーチャーブランチからmainへのPRを通じて開発を進行。
設定 GitLab Flow 環境ごとのブランチ(auto-test, dev, stg, prod)を保持し、ブランチ間のマージで変更をプロモーション。

●基本原則

原則 説明
フィードバック駆動開発 あらゆるレベルで、できるだけ早く的確なフィードバックを関係者に提供します。
すべてを自動化 ビルド、テスト、デプロイのプロセスを完全に自動化し、手動作業を排除します。
最小限のガバナンス チームが自律的に動けるよう、ガードレールとしてのパイプラインを提供します。
You build it, you run it 開発チームが自ら開発したサービスの運用まで責任を持つ文化を醸成します。

●CIパイプライン階層 (アプリケーションリポジトリ)

パイプラインステージ 目的 トリガー 主な活動
Developer CI 開発者個人への即時フィードバック フィーチャーブランチへのプッシュ - 静的コード解析 (差分のみ)
- SAST (差分のみ・フィードバック用)
- Small Test (単体テスト)
Team CI mainブランチの品質保護 mainブランチへのPR作成・更新 - 静的コード解析 (全体)
- SAST (全体・フィードバック用)
- Small Test (単体テスト)
Component CI リリース候補の成果物作成とCDプロセスの開始 mainブランチへのマージ - 静的コード解析 (全体)
- SAST (履歴管理用)
- Small & Medium Test (*1)
- コンテナのビルド & スキャン
- 後続のCDを駆動させるためconfigリポジトリのauto-testブランチへのPRを自動作成・マージ

CIとCDの連携: Component CIの完了は、あくまで高品質な 「部品」 (コンテナイメージ)が完成したことを意味します。この部品をシステムに組み込んで初めて価値が生まれます。次のSystem CIは、CDパイプラインの最初のステージとして、設定リポジトリ側でこの新しい部品をシステムに統合し、E2Eテストを通じて 「システムとしての品質」 を保証する、CIとCDの重要な橋渡し役を担います。

●CD パイプライン階層 (設定リポジトリ)

パイプラインステージ 環境 目的 トリガー 主な活動
System CI 自動テスト システム全体の統合テストと品質保証 auto-testブランチへのマージ - 自動テスト環境へのデプロイ
- Smoke Test
- Large Test (E2Eテスト)
- DAST
- 成功後、devブランチへのPRを自動作成
CD / Promotion Dev 開発者による変更単位での手動テスト devブランチへのマージ - Dev環境へのデプロイ
- Smoke Test
- 成功後、stgブランチへのPRを自動作成
- (開発者による手動テスト)
CD / Promotion Stg 本番前最終自動検証とQA/UAT stgブランチへのマージ - Stg環境へのデプロイ
- Smoke Test
- DAST
- 自動パフォーマンステスト
- 成功後、prodブランチへのPRを自動作成
- (手動によるUAT/QA)
CD / Release Prod 本番環境へのリリース prodブランチへのマージ - Prod環境へのデプロイ
- Smoke Test

■構築方法

この戦略を実現するための具体的なパイプライン定義を、GitHub ActionsGitLab CI/CDのそれぞれについて示します。どちらのプラットフォームでも、CIとCDの責務を分離し、PRを介してプロモーションを自動化するという中核的な考え方は共通です。

●アプリケーションリポジトリ (front) のパイプライン

mainブランチへのマージをトリガーにComponent CIを実行します。パイプラインの最終ステップで、コンテナイメージのビルド情報をconfigリポジトリに渡し、auto-testブランチへの**プルリクエスト(Pull Request)**を自動で作成・マージします。

GitHub Actions の場合

.github/workflows/ci.yml を作成します。configリポジトリへの書き込み権限を持つPersonal Access Tokenを、CONFIG_REPO_ACCESS_TOKENという名前でリポジトリのSecretsに保存しておく必要があります。

: 以下のコード内の your-org/configyour-registry/front といった箇所は、ご自身の環境に合わせて修正してください。

# .github/workflows/ci.yml
name: Component CI and Trigger CD

on:
  push:
    branches:
      - main

jobs:
  build-and-trigger-cd:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout application code
        uses: actions/checkout@v4

      # (ここにビルド、テスト、SAST、コンテナビルド&プッシュのステップが入ります)
      - name: Build, Test, and Scan
        run: |
          echo "Building application..."
          echo "Running tests..."
          echo "Building and pushing container image to registry..."

      - name: Trigger System CI in Config Repo
        env:
          GH_TOKEN: ${{ secrets.CONFIG_REPO_ACCESS_TOKEN }}
          CONFIG_REPO_URL: "https://github.com/your-org/config.git"
          COMMIT_SHA_SHORT: $(echo $GITHUB_SHA | cut -c1-7)
        run: |
          echo "Cloning config repository..."
          git clone $CONFIG_REPO_URL config-repo
          cd config-repo

          # ここでKubernetesマニフェスト(例: Kustomize, Helm)のイメージタグを更新します
          # 例: Kustomizeを使用する場合
          # kustomize edit set image your-registry/front:${COMMIT_SHA_SHORT}
          echo "Updating image tag for front to ${COMMIT_SHA_SHORT}"

          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          
          BRANCH_NAME="release/front-${COMMIT_SHA_SHORT}"
          git checkout -b ${BRANCH_NAME}
          git add .
          git commit -m "Update front service to version ${COMMIT_SHA_SHORT}"
          git push origin ${BRANCH_NAME}

          echo "Creating and merging a pull request to the auto-test branch..."
          gh pr create \
            --repo your-org/config \
            --base auto-test \
            --head ${BRANCH_NAME} \
            --title "🚀 Promote front:${COMMIT_SHA_SHORT} to auto-test" \
            --body "Automated promotion from Component CI"
          
          gh pr merge ${BRANCH_NAME} --squash --delete-branch --repo your-org/config

GitLab CI/CD の場合

.gitlab-ci.yml を作成します。configリポジトリにマージリクエストを作成するための書き込み権限を持つアクセストークンを、CONFIG_REPO_ACCESS_TOKENという名前でCI/CD変数に設定しておく必要があります。

: 以下のコード内の your-group/config といった箇所は、ご自身の環境に合わせて修正してください。

# .gitlab-ci.yml
# ... (Developer CI, Team CI, Component CIのビルド部分は省略)

trigger-system-ci:
  stage: deploy
  image: registry.gitlab.com/gitlab-org/cli:latest
  script:
    - export GL_HOST="gitlab.com"
    - export GITLAB_TOKEN="${CONFIG_REPO_ACCESS_TOKEN}"
    - |
      # ここにマニフェストのイメージタグを更新する処理を記述します (git clone, sed, git pushなど)
      # この例では、MR作成機能のみを示します
      glab mr create \
        --repo your-group/config \
        --source-branch "release/front-${CI_COMMIT_SHORT_SHA}" \
        --target-branch "auto-test" \
        --title "🚀 Promote front ${CI_COMMIT_SHORT_SHA} to auto-test" \
        --remove-source-branch \
        --merge-when-pipeline-succeeds
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

●設定リポジトリ (config) のパイプライン

各環境ブランチへのマージをトリガーにCDパイプラインを実行します。パイプラインの最終ステップで、次の環境へのプロモーション用PRを自動で作成します。

GitHub Actions の場合

.github/workflows/cd.yml を作成します。PR作成には標準で提供されるGITHUB_TOKENが利用できます。

# .github/workflows/cd.yml
name: CD and Promotion

on:
  push:
    branches:
      - auto-test
      - dev
      - stg

jobs:
  # Automated Test Environment (System CI)
  system-ci:
    if: github.ref == 'refs/heads/auto-test'
    runs-on: ubuntu-latest
    steps:
      # このステップは後述のGitOpsツール連携で実現されるため、ここではPR作成のみに責務を絞る
      # - name: Run Large Tests
      #  run: echo "Running large tests (E2E, DAST) on auto-test environment..."

      - name: Promote to Dev
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # 実際の運用では、GitOpsツールからのテスト成功通知(Webhookなど)をトリガーに
          # このワークフローを起動するのが理想的です。
          # この例では、テストが成功したことを前提としてPRを作成します。
          if gh pr list --repo $GITHUB_REPOSITORY --base dev --head auto-test | grep -q "Promote changes from auto-test to dev"; then
            echo "Promotion PR already exists."
            exit 0
          fi
          gh pr create \
            --base dev \
            --head auto-test \
            --title "✅ Promote changes from auto-test to dev" \
            --body "Automated promotion from the System CI pipeline. Please review and merge." \
            --assignee "dev-lead-user"

  # Development Environment & Promotion to Staging
  promote-to-stg:
    if: github.ref == 'refs/heads/dev'
    runs-on: ubuntu-latest
    steps:
      - name: Promote to Staging
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if gh pr list --repo $GITHUB_REPOSITORY --base stg --head dev | grep -q "Promote changes from dev to stg"; then
            echo "Promotion PR already exists."
            exit 0
          fi
          gh pr create \
            --base stg \
            --head dev \
            --title "✅ Promote changes from dev to stg" \
            --body "Automated promotion. Please review and merge." \
            --assignee "qa-lead-user"

  # (promote-to-prodも同様に定義)

GitLab CI/CD の場合

.gitlab-ci.yml を作成します。glab CLIを利用して**マージリクエスト(MR)**を自動作成します。(前述の注釈参照)

# .gitlab-ci.yml
stages:
  - promote

# このファイルではプロモーション(MR作成)に責務を絞ります。
# テストの実行はGitOpsツール側で行います。

promote-to-dev:
  stage: promote
  image: registry.gitlab.com/gitlab-org/cli:latest
  script:
    - |
      # 実際の運用では、テスト成功をトリガーにこのジョブを実行します。
      glab mr create \
        --source-branch "auto-test" \
        --target-branch "dev" \
        --title "✅ Promote changes from auto-test to dev" \
        --assignee "dev-lead-user" \
        --description "Automated promotion from the System CI pipeline. Please review and merge."
  rules:
    - if: '$CI_COMMIT_BRANCH == "auto-test"'

# --- Development Environment & Promotion to Staging ---
promote-to-stg:
  stage: promote
  image: registry.gitlab.com/gitlab-org/cli:latest
  script:
    - |
      glab mr create \
        --source-branch "dev" \
        --target-branch "stg" \
        --title "✅ Promote changes from dev to stg" \
        --assignee "qa-lead-user" \
        --description "Automated promotion. Please review and merge."
  rules:
    - if: '$CI_COMMIT_BRANCH == "dev"'

# (stgからprodへのプロモーションジョブも同様に定義)

●デプロイ後のテスト実行:GitOpsツール連携

config repoにマージされ、Argo CDやFluxによってデプロイが完了した後、Smoke TestやE2Eテストをどう自動実行するかは非常に重要です。ここでは、各GitOpsツールでデプロイ後のテストを連携させる具体的な実装例を示します。

Argo CDの場合: Sync Hooksを利用

Argo CDは、同期(デプロイ)プロセスの特定のタイミングでKubernetes Jobなどを実行できるSync Hooksという強力な機能を提供します。デプロイ完了直後にテストを実行するにはPostSyncフックが最適です。

config repo内のアプリケーションマニフェストと同じディレクトリに、以下のようなテスト用のJobマニフェストを追加します。

smoke-test-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: smoke-test-job
  # このJobをArgo CDのHookとして認識させるためのアノテーション
  annotations:
    # "PostSync": デプロイが完全に完了し、Healthy状態になった後に実行
    argocd.argoproj.io/hook: PostSync
    # "HookSucceeded": フック(このJob)が成功したら、Jobリソースを自動で削除する
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
    # "Before": もし同名の古いHook Jobが残っていたら、実行前に削除する
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      containers:
      - name: tester
        # ここにテストコードを実行するコンテナイメージを指定
        image: your-registry/application-tester:latest
        # 実行したいテストコマンド
        command: ["/bin/sh", "-c", "npm test -- --reporter=junit"]
      # Jobが失敗した場合にリトライしない
      restartPolicy: Never
  # 失敗しても1回で終了させる
  backoffLimit: 1
  # Jobが一定時間内に完了しない場合にタイムアウトさせる
  activeDeadlineSeconds: 300

このマニフェストをリポジトリに含めるだけで、Argo CDはデプロイを完了するたびにこのテストJobを自動的に起動します。テストが失敗するとJobも失敗し、Argo CDの同期ステータスも失敗としてマークされるため、デプロイ後の品質検証をパイプラインに組み込むことができます。

Flux CDの場合: Kustomizationの依存関係とヘルスチェックを利用

Fluxは、複数のコントローラが連携して動作します。デプロイ後のテストは、Kustomizationリソースのヘルスチェック機能と依存関係 (dependsOn) を組み合わせることで、宣言的に実現できます。

以下のようなディレクトリ構成をconfig repo内に作成します。

.
└── apps/
    ├── my-app/          # アプリケーション本体のマニフェスト
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── kustomization.yaml
    └── my-app-tests/    # デプロイ後に実行するテストのマニフェスト
        ├── test-job.yaml
        └── kustomization.yaml

1. アプリケーション本体のKustomization
まず、アプリケーション本体のKustomizationリソースで、デプロイ対象のリソース(Deploymentなど)が正常に起動したことを確認するためのhealthChecksを定義します。

apps/my-app/kustomization.yaml

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 5m
  path: "./apps/my-app" # このディレクトリ内のマニフェストを適用
  prune: true
  sourceRef:
    kind: GitRepository
    name: config-repo # 事前に定義したGitリポジトリソース
  # このKustomizationによるデプロイが成功したと見なすためのヘルスチェック
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: my-app-deployment # deployment.yamlで定義したDeployment名
      namespace: default

2. テスト用のKustomization
次に、テストJobを実行するためのKustomizationを作成します。ここで最も重要なのはdependsOn句です。これにより、「my-appのデプロイとヘルスチェックが成功したら、このKustomizationを適用する」という依存関係を定義できます。

apps/my-app-tests/kustomization.yaml

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app-tests
  namespace: flux-system
spec:
  interval: 5m
  path: "./apps/my-app-tests" # テストJobのマニフェストのパス
  prune: true
  sourceRef:
    kind: GitRepository
    name: config-repo
  # `my-app` Kustomizationのヘルスチェックが成功するまで待機する
  dependsOn:
    - name: my-app

apps/my-app-tests/test-job.yaml は、Argo CDの例で示したような通常のKubernetes Jobマニフェストです。

この構成により、Fluxはまずアプリケーションをデプロイし、それが完全に利用可能になったことを確認してから、宣言的に定義された依存関係に基づいてテストJobを実行します。

■利用方法

開発から本番リリースまでの典型的なワークフローは以下の通りです。

  1. 開発と成果物作成 (CI)
    開発者はアプリケーションリポジトリで機能開発を行い、mainブランチにマージします。Component CIが成功すると、リリース可能なコンテナイメージが作成され、configリポジトリのauto-testブランチへのPRが自動で作成・マージされます。
  2. システム統合テスト (System CI)
    configリポジトリのauto-testブランチへのマージをトリガーに、GitOpsツール(Argo CD/Flux)が自動テスト環境へデプロイを開始します。デプロイ完了後、連携して設定されたE2Eテストなどの各種自動テストが実行されます。
  3. Dev環境へのプロモーション (CD)
    System CI(テスト)が成功すると、configリポジトリのCI/CDパイプラインがdevブランチへのPRを自動で作成します。開発チームはこのPRをレビューし、承認・マージすることでDev環境へデプロイします。
  4. Stg/Prod環境へのプロモーション (CD)
    Dev環境での検証後、同様のプロセスでstgブランチへのPRが作成されます。QAチームやプロダクトオーナーが承認・マージするとStg環境へデプロイされます。本番環境へのプロセスも同様に、厳格な承認を経て実行されます。

■運用方法

この戦略を継続的に改善し、安定して運用するためのプラクティスを紹介します。

●テスト戦略の維持

  • 定義の共有: 「Small」「Medium」「Large」テストが具体的に何を指すのか、チーム全体で明確な定義を共有し、ドキュメント化します。
  • 実行時間の監視: 各テストスイートの実行時間を継続的に監視します。実行時間の増加は開発者体験の低下やCI/CDコストの増大に直結するため、定期的にテストの見直しや並列化を検討します。

●セキュリティ (DevSecOps)

  • 結果の統合: SAST、コンテナスキャン、DASTの結果をGitHub/GitLabのセキュリティダッシュボードなどに集約し、脆弱性を一元管理します。
  • ポリシーの設定: Component CIのコンテナスキャンで「Critical」な脆弱性が発見された場合など、特定の条件下でパイプラインを失敗させる明確なポリシーを設定します。

●ブランチ保護と承認フロー

  • アプリケーションリポジトリ: mainブランチを保護ブランチに設定し、PRには最低1名の承認を必須とします。
  • 設定リポジトリ: dev, stg, prod といった環境ブランチを保護します。特にprodブランチへのマージは、特定の役割を持つユーザー(例: SRE、チームリード)のみが承認できるように権限を厳格に設定します。

●ロールバック戦略

  • Git Revertによるロールバック: 本番環境で問題が発生した場合、GitOpsの原則に従います。configリポジトリのprodブランチに対して問題のあったマージコミットをgit revertしてプッシュするだけで、Argo CDやFluxがその変更を検知し、自動的に以前の正常な状態にシステムを収束させます。これにより、迅速かつ安全な復旧が可能になります。
  • 手動vs自動: このgit revert操作は、意図しない副作用を避けるため、人間が状況を判断した上で手動実行するのが基本原則です。ただし、自動ロールバックの仕組みをパイプラインに組み込むことも技術的には可能ですが、その場合は自動化が新たな障害を引き起こさないよう、極めて慎重な設計が求められます。

●パイプラインのモジュール化

  • Lint、SAST、テスト、ビルドといった共通処理を、GitLabのinclude機能やGitHub ActionsのReusable Workflowsとして切り出します。これにより、各リポジトリのCI/CD定義を簡潔に保ち、パイプライン全体の一貫性を維持します。

●パイプラインの観測性 (Observability)

  • メトリクスの計測: DevOpsの健全性を示すFour Keys(デプロイの頻度、変更のリードタイム、変更障害率、サービス復元時間)を計測します。本戦略は、自動化によってデプロイ頻度とリードタイムを改善し、段階的リリースによって変更障害率とサービス復元時間を改善することを目指します。
  • 実行時間の可視化: パイプラインの各ジョブの実行時間をダッシュボードで可視化し、ボトルネックとなっている箇所を特定・改善します。

●Gitタグによるバージョンの同期

本番リリースとソースコードのコミットを正確に紐付けることは、障害調査やロールバックの確実性を高める上で極めて重要です。ここでは、config repoのリリースと各app repoのコミットを同一のGitタグで同期させる、高度な自動化戦略を紹介します。

この戦略のゴールは、「config repoのタグv20250912.1400を見れば、その時点でリリースされたサービスfrontのコードも、同じタグv20250912.1400で正確に特定できる」状態を実現することです。

Step 1: 前提 - Component CIでのコミットハッシュ利用

まず前提として、各app repoのComponent CIは、ビルドしたコンテナイメージのタグとして、自身のコミットハッシュを使用します。このコミットハッシュ付きのイメージタグが、後続のCDプロセスのためにconfig repo内のKubernetesマニフェスト(例: Kustomization.yamlやHelmのvalues.yaml)に設定されます。

# config repo内のkustomization.yamlの例
# ...
images:
  - name: your-registry/front
    newTag: a1b2c3d  # ← app repo "front" のコミットハッシュ
  - name: your-registry/bff
    newTag: e4f5g6h  # ← app repo "bff" のコミットハッシュ
# ...

Step 2: リリースのマーキング (config repo側)

prod環境へのCDパイプラインがデプロイとSmoke Testを成功させたら、その時点のconfig repoprodブランチに対して、リリースバージョンを示す日時ベースのタグを自動で付与します。

これは、config repoのCDワークフローに、以下のようなジョブを追加することで実現できます。

.github/workflows/cd.yml (config repo側) に追記するジョブの例

# ... (stgからprodへのPRをマージするまでのジョブ)

jobs:
  # ...
  
  release-on-prod:
    if: github.ref == 'refs/heads/prod'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout config repo
        uses: actions/checkout@v4

      - name: Run Smoke Test on Production
        run: |
          echo "Running smoke tests on production environment..."
          # ここに実際のテストスクリプトを記述
          # 成功したら exit 0, 失敗したら exit 1 となるように実装
          exit 0

      - name: Tag Release on Config Repo
        if: success() # Smoke Testが成功した場合のみ実行
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          
          # 日本時間 (JST) でタグを生成 (例: v20250912.1400)
          RELEASE_TAG="v$(TZ=Asia/Tokyo date +'%Y%m%d.%H%M')"
          echo "Creating release tag on config repo: $RELEASE_TAG"
          
          git tag $RELEASE_TAG
          git push origin $RELEASE_TAG

Step 3: タグの伝播と同期 (app repo側)

次に、各app repoconfig repoに新しいリリースタグが作成されたことを検知し、自リポジトリ内の適切なコミットに同じ名前のタグを付けに行きます。この「検知とタグ付け」の処理は、各app repoに配置した専用のワークフローによって自律的に実行されます。

検知方法にはWebhookなどもありますが、ここではシンプルで堅牢な 定期実行(ポーリング) による実装例を示します。

.github/workflows/sync-release-tag.yml (各app repoに配置)

name: Sync Release Tag from Config Repo

on:
  schedule:
    # 30分に1回など、プロジェクトに適した頻度で実行
    - cron: '*/30 * * * *'
  workflow_dispatch: # 手動実行も可能にする

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout App Repo code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # タグ付けのために全履歴を取得

      - name: Get latest release tag from Config Repo
        id: get_tag
        run: |
          # config repoのリモートタグ一覧から、"v"で始まる最新のタグを取得
          LATEST_TAG=$(git ls-remote --tags --sort="v:refname" https://github.com/your-org/config.git "v*" | tail -n1 | awk '{print $2}' | sed 's|refs/tags/||')
          if [ -z "$LATEST_TAG" ]; then
            echo "No release tags found in config repo."
            exit 1
          fi
          echo "Latest config repo tag: $LATEST_TAG"
          echo "latest_config_tag=$LATEST_TAG" >> $GITHUB_OUTPUT

      - name: Check if tag already exists in this App Repo
        id: check_local_tag
        run: |
          # このリポジトリに同じタグが既に存在するか確認
          if git rev-parse -q --verify "refs/tags/${{ steps.get_tag.outputs.latest_config_tag }}"; then
            echo "Tag ${{ steps.get_tag.outputs.latest_config_tag }} already processed. Skipping."
            echo "exists=true" >> $GITHUB_OUTPUT
          else
            echo "New tag found. Proceeding to sync."
            echo "exists=false" >> $GITHUB_OUTPUT
          fi
      
      - name: Find corresponding commit hash from Config Repo
        if: steps.check_local_tag.outputs.exists == 'false'
        id: find_commit
        env:
          CONFIG_REPO_URL: "https://github.com/your-org/config.git"
          # このリポジトリでビルドされるコンテナイメージ名
          APP_IMAGE_NAME: "your-registry/front" 
        run: |
          # config repoを、取得した最新タグの状態でクローン
          git clone --depth 1 --branch ${{ steps.get_tag.outputs.latest_config_tag }} $CONFIG_REPO_URL config-repo
          
          # マニフェストファイルから、自アプリのイメージに設定されたコミットハッシュを抽出
          # 抽出方法は、Kustomize, Helmなど利用ツールに応じて調整が必要です
          # 以下はgrepを使った簡易的な例
          COMMIT_HASH=$(grep -r "${APP_IMAGE_NAME}" ./config-repo | head -n 1 | sed -n 's/.*:\([0-9a-f]\{7,40\}\).*/\1/p')
          
          if [ -z "$COMMIT_HASH" ]; then
            echo "This app's commit hash not found in config repo at tag ${{ steps.get_tag.outputs.latest_config_tag }}. Skipping."
            # このリリースに自アプリの変更が含まれていない場合は正常終了
            exit 0
          fi
          echo "Found corresponding commit hash: $COMMIT_HASH"
          echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT

      - name: Create Git Tag on App Repo
        if: steps.check_local_tag.outputs.exists == 'false' && steps.find_commit.outputs.commit_hash != ''
        run: |
          echo "Tagging commit ${{ steps.find_commit.outputs.commit_hash }} with tag ${{ steps.get_tag.outputs.latest_config_tag }}"
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          
          git tag ${{ steps.get_tag.outputs.latest_config_tag }} ${{ steps.find_commit.outputs.commit_hash }}
          git push origin ${{ steps.get_tag.outputs.latest_config_tag }}

この連携により、リリース管理の自動化と追跡性の担保を極めて高いレベルで両立させることが可能になります。

●Hotfix(緊急修正)フロー

本番環境で障害が発生し、通常のリリースサイクルを待てない緊急の修正が必要な場合、以下のHotfixフローを実行します。このフローの目的は、安全性を最低限確保しつつ、最速で本番環境に修正を届けることです。

1. 修正対応 (アプリケーションリポジトリ)

  1. Hotfixブランチの作成: mainブランチからではなく、現在本番環境で稼働しているバージョンに対応する Gitタグ(またはコミット) からhotfix/issue-nameのような名前でブランチを作成します。
  2. 修正とプッシュ: 修正コードをコミットし、リモートリポジトリにプッシュします。
  3. Hotfixビルドパイプラインの実行: hotfix/*ブランチへのプッシュをトリガーとする専用のCIパイプラインが実行されます。このパイプラインは、通常のComponent CIと同様にビルド、テスト、コンテナイメージ作成を行いますが、最終的にconfigリポジトリのauto-testブランチに対して直接プルリクエストを作成し、自動マージします。

2. 緊急リリース (設定リポジトリ)

  1. auto-test環境での検証: Hotfixの変更は、まず自動テスト環境にデプロイされ、システム全体のE2Eテスト(System CI)が実行されます。これにより、緊急修正が新たな重大なバグを引き起こさないことを最低限保証します。
  2. 通常プロモーションの停止: System CIが成功すると、devブランチへのPRが自動作成される場合がありますが、今回は緊急事態のため、このPRは手動でCloseします。
  3. 本番環境への手動プロモーション: auto-testブランチからprodブランチ(または必要に応じてstgブランチ)に対して、手動でプルリクエストを作成します。PRの概要欄には、緊急修正である旨と関連する障害チケットの情報を明記します。
  4. 承認とマージ: SREやチームリードなど、指定された承認者がレビューを行い、承認・マージすることでHotfixが本番環境へリリースされます。

3. 開発本流への反映 (事後処理)

リリースが完了し、問題が解決したことを確認した後、必ずHotfixブランチの変更内容をmainブランチにマージします。この作業を怠ると、次回の通常リリース時に修正内容が失われ、同じ障害が再発する(先祖返りする)原因となります。

■この戦略が適しているケース

この戦略は、特に以下のような特徴を持つプロジェクトやチームで効果を発揮します。

  • 複数のマイクロサービスでシステムが構成されている。
  • 複数の開発チームが並行して開発を進めている。
  • 環境ごとのリリース管理を厳密に行いたいが、開発速度も損ないたくない。
  • 高い品質とセキュリティを開発プロセスの早期から確保したい。

一方で、単一のサービス(モノリス)や非常に小規模なチームの場合、リポジトリやブランチの管理がやや煩雑に感じられるかもしれません。その場合は、まずアプリケーションリポジトリ内で環境ブランチを持つシンプルなGitLab Flowから始めるなど、状況に応じたカスタマイズを検討してください。

■まとめ

本記事では、GitHub FlowとGitLab Flowを組み合わせ、CIとCDの責務をリポジトリレベルで分離することで、マイクロサービス開発における速度信頼性を両立させるCI/CD戦略を提案しました。

この戦略の核心は、CI(部品の品質保証)とCD(システムの品質保証とデリバリー)の責務をリポジトリレベルで明確に分離し、両者の連携をGitOpsの仕組みを通じて完全に自動化した点にあります。この多層的なパイプラインアプローチは、単なるツールの組み合わせではなく、アジリティとガバナンスを両立させるための設計思想です。この戦略は、Four Keysに代表されるDevOpsメトリクスを向上させ、ビジネス価値の迅速な提供に直接貢献します。

  • 開発者体験の向上: 迅速なフィードバックにより、開発者は手戻りなく開発に集中できます。
  • 品質とセキュリティの担保: CI/CDの各段階にテストとセキュリティスキャンを組み込むことで、品質を継続的に保証します。
  • リリースの高速化と安全性: PRの自動作成と段階的なプロモーションにより、手動作業をなくし、安全かつ迅速に本番環境へ変更を届けられます。

この戦略は堅牢な出発点ですが、最も重要なのは、この記事を参考にしつつも、自身のプロジェクトの規模やチームの成熟度に応じてカスタマイズし、進化させていくことです。この記事が、皆さんのチームに合った最適なCI/CDパイプラインを構築するための一助となれば幸いです。

この記事が少しでも参考になった、あるいは改善点などがあれば、ぜひリアクションやコメント、SNSでのシェアをいただけると励みになります!

■関連リンク

実行環境がKubernetesではない場合でも、GitOpsライクな運用を取り入れてこの戦略を実現できます。

https://zenn.dev/suwash/articles/gitops_without_kubernetes_20250920


■参考リンク

Discussion