Article

GitOpsを2年実践して分かった、CI/CDパイプラインの理想形

高田晃太郎
GitOpsを2年実践して分かった、CI/CDパイプラインの理想形

エリートチームは「変更のリードタイムが1日未満」「障害からの復旧が1時間未満」「変更失敗率が0〜15%」というDORA(DevOps Research and Assessment, Accelerate 2021)の基準を満たすと報告されています。¹²³ 数字は静かですが、現場ではこの差がビジネス速度と信頼の差そのものになります。私は過去2年、複数プロダクトにGitOps(Gitを唯一の情報源とする運用プラクティス)を適用し、手戻りの多い従来型CDから、宣言と監査を中核に据えたプル型運用へ移行してきました。結果として、デプロイの心理的安全性が上がり、変更の可視性が経営と開発の共通言語になったと実感しています。本稿では、GitOpsを2年実践して見えたCI/CDパイプラインの理想形を、設計原則、実装例、運用KPIの順に整理します。

GitOpsがCI/CDの前提になる理由

GitOpsをCI/CD(継続的インテグレーション/継続的デリバリー・デプロイメント)の中心に置く最大の価値は、変更の唯一の情報源(Single Source of Truth)をGitに集約し、宣言的な所望状態(Desired State)と実際のクラスタ状態の差分を継続的に収束させる点にあります。⁴ パイプラインの責務を明確に分離すると、CIはアーティファクトのビルド、テスト、スキャン、署名、SBOM生成(ソフトウェア部品表による可視化)という供給側の品質保証に集中し、CDは所望状態の反映、差分検知、ロールバックという適用側の整合性維持に集中できます。プッシュからプルへの転換(人や外部から直接クラスタに適用するのではなく、Gitの変更をCDエンジンが取りに来て同期する方式)は、権限境界の明確化と事故時の影響範囲の抑制に効きます。クラスタに対して直接命令するのではなく、Gitにマージされた意図が唯一のトリガーになるため、監査証跡が自然に残り、再現可能性が飛躍的に高まります。⁵

宣言と再現性は、チームスケールとサービス数が増えるほど効きます。マニフェストやチャートをテンプレート化し、環境差分をオーバレイ(Kustomizeで差分だけを重ねる設計)に閉じ込めることで、「どこが違うか」ではなく「なぜ違うか」に議論を寄せられます。⁶ GitのレビューとCIの自動検証を通すことで、クラスタ外からの改変や手作業の逸脱を抑止でき、結果としてデプロイが「出来事」から「平常運転」に変わるのです。

プル型CDがもたらす運用の安定

プル型CDは、到達すべき状態と現在の状態の差分を継続的に監視します。万一のドリフト(意図しない乖離)を検知したら、望まれない変更を元に戻すか、差分を可視化してレビューに戻す運用が可能です。⁵ これにより、深夜の緊急対応は「手元からの祈りのkubectl」(手動コマンドでの直接修正)ではなく、意図をGitに戻すという筋の良い作業に置き換わります。ロールバックもタグの切替やリビジョンの巻き戻しとしてモデル化され、教育コストを含む運用コストが逓減します。⁴

2年の実践で見えた理想アーキテクチャ

理想像は、抽象に逃げない設計原則と、実行可能な最小構成から始まります。安定しやすい構成として、リポジトリ戦略、アーティファクト署名、環境昇格、ガードレール、段階的リリース、シークレット管理の六点に要諦が集約されます。ここでは主要な実装断面をコードとともに示します。

CIの責務: ビルド、スキャン、署名、SBOM

CIではソースからコンテナを生成し、脆弱性スキャンとSBOM生成、そして署名までを完結させます。⁷ GitHub Actionsを例に、タイムアウトや失敗時のアーティファクト保存など、現実的なエラーハンドリングも織り込みます(OIDCによるキーレス署名の前提権限も付与)。

# .github/workflows/ci.yml
name: ci-build
on:
  push:
    paths: ["app/**"]
permissions:
  contents: read
  id-token: write   # OIDC for keyless signing
  packages: write
jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=sha,format=short
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
      - name: Scan image
        uses: aquasecurity/trivy-action@0.20.0
        with:
          image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
          vuln-type: os,library
          format: sarif
          output: trivy.sarif
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
          format: spdx-json
          output-file: sbom.spdx.json
      - name: Sign image with cosign
        env:
          COSIGN_EXPERIMENTAL: "1"
        run: |
          TAG=$(echo '${{ steps.meta.outputs.tags }}' | head -n1)
          cosign sign --yes $TAG
      - name: Upload artifacts on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: ci-debug
          path: |
            trivy.sarif
            sbom.spdx.json

CIの出口は、レジストリ上のイミュータブル(書き換え不可)なイメージ、検査レポート、署名、そしてGitに書き戻されるバージョン情報です。ここで「どのイメージをどの環境に適用するか」は決めません。CIは供給品質、CDは適用判断、この分離が後段の安全性を生みます。⁴

環境差分の閉じ込め: Kustomizeとオーバレイ

環境ごとの差分は、配置先URLやリソースリミット、フィーチャーフラグといった変数に尽きます。これらをオーバレイに閉じ込め、ベースは常に同一とする設計が重要です(Kustomizeでベース+差分の構成管理)。⁶

# deploy/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: web
          image: ghcr.io/acme/web:latest
          resources:
            requests: { cpu: "200m", memory: "256Mi" }
            limits:   { cpu: "500m", memory: "512Mi" }
# deploy/overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
images:
  - name: ghcr.io/acme/web
    newTag: sha-1a2b3c4
patches:
  - target:
      kind: Deployment
      name: web
    patch: |-
      - op: replace
        path: /spec/replicas
        value: 2

CDエンジン: Argo CDによるプルと昇格

Argo CD(Gitの所望状態をKubernetesへ同期するCDコントローラ)は、Git上の所望状態を周期的に同期し、差分を可視化してくれます。⁵ ステージングへの適用は、ステージング用オーバレイへのPRマージという運用に寄せます。これにより、変更の昇格の瞬間もGitレビューで統一できます。

# argo/app-staging.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-staging
spec:
  project: default
  source:
    repoURL: https://github.com/acme/web-deploy.git
    path: deploy/overlays/staging
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: web
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

プレビュー環境は、PRごとにオーバレイを動的生成することで、レビューの質を高めます。ApplicationSetを使うと定義が簡潔になります。

# argo/appset-preview.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: web-previews
spec:
  generators:
    - pullRequest:
        github:
          owner: acme
          repo: web
          tokenRef:
            secretName: github-token
            key: token
  template:
    metadata:
      name: 'web-pr-{{number}}'
    spec:
      source:
        repoURL: https://github.com/acme/web-deploy.git
        targetRevision: 'refs/pull/{{number}}/head'
        path: deploy/overlays/preview
      destination:
        server: https://kubernetes.default.svc
        namespace: 'web-pr-{{number}}'

ガードレール: ポリシーで「守れる自由」を設計する

Gitレビューは万能ではありません。機械が得意な領域、たとえば「署名のないイメージは禁止」「特権コンテナ禁止」「外向き通信の制約」などは、Admissionレイヤ(Kubernetesのリクエスト検証/拒否フック)で強制します。Gatekeeper(OPA/Regoベースのポリシーエンジン)を用いた例を示します。

# templates/verify-signature.yaml (ConstraintTemplate)
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sverifiedimage
spec:
  crd:
    spec:
      names:
        kind: K8sVerifiedImage
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sverifiedimage
        violation[{
          "msg": msg,
        }] {
          input.review.kind.kind == "Pod"
          some i
          img := input.review.object.spec.containers[i].image
          not startswith(img, "ghcr.io/acme/")
          msg := sprintf("untrusted registry: %v", [img])
        }
# constraints/verify-signature.yaml (Constraint)
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sVerifiedImage
metadata:
  name: only-trusted-registry
spec: {}

署名自体の検証はKyvernoや専用の署名検証コントローラを併用できます。人の注意力に依存しない安全策を一枚重ねることで、ミスの再発を根から断ち切れます。

段階的リリースと即時ロールバック

トラフィックを段階的に切り替え、SLO(サービス目標)に触れたら自動で巻き戻す仕組みは、GitOpsの安心感を実運用に橋渡しします。Argo Rollouts(カナリアやブルー/グリーンを実現するコントローラ)の定義例です。⁵

# deploy/base/rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: web
spec:
  replicas: 3
  strategy:
    canary:
      canaryService: web-canary
      stableService: web-stable
      steps:
        - setWeight: 20
        - pause: { duration: 120 }
        - setWeight: 50
        - pause: { duration: 300 }
      analysis:
        templates:
          - templateName: web-availability
        startingStep: 1
  selector:
    matchLabels: { app: web }
  template:
    metadata:
      labels: { app: web }
    spec:
      containers:
        - name: web
          image: ghcr.io/acme/web:sha-1a2b3c4
# analysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: web-availability
spec:
  metrics:
    - name: http_success_rate
      interval: 30s
      count: 10
      successCondition: result[0] >= 0.995
      failureCondition: result[0] < 0.99
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(nginx_ingress_controller_requests{status=~"2.."}[1m]))
            /
            sum(rate(nginx_ingress_controller_requests[1m]))

自動判定で守り、Gitの巻き戻しで速く戻す。この二段構えが、変更速度と安定性の両立を可能にします。

シークレット管理: SOPSとKMSでGitに置ける形にする

シークレットはGitに素のまま置けません。SOPS(ファイル単位の暗号化ツール)で暗号化し、クラスタ側ではKMS(クラウドの鍵管理サービス)連携で復号する形が、GitOpsと相性が良い運用でした。

# secrets/db.enc.yaml (暗号化済み)
apiVersion: v1
kind: Secret
metadata:
  name: db
  namespace: web
stringData:
  url: ENC[AES256_GCM,data:vS...]
sops:
  kms:
    - arn: arn:aws:kms:ap-northeast-1:123456789012:key/abcd-...
  encrypted_regex: '^(data|stringData)$'
  version: 3.7.3

復号はコントローラ側で行うため、CI/CDに秘密鍵を持ち込む必要がありません。秘密の在処と責務を分離し、漏洩面積を最小化できます。

現場で詰まるポイントと解法

データベース移行は、GitOps導入時に必ず議論になる難所です。アプリのロールアウトとスキーマの変更順序が重要で、可逆な移行計画を原則に据えると失敗が減ります。互換スキーマの先出し、アプリの新旧両対応、古いコードの退役と不要列の削除という三相で考えると、ロールバック可能性を保ちながら移行できます。マイグレーションツールはCIで検証だけを行い、適用は環境の所望状態に記述してCDが行うと、誰がいつ何をしたかがGitで追えるようになります。

リポジトリの分割は、モノレポかマルチレポかという宗教戦争になりがちです。アプリのソースと環境の宣言を分ける「コードレポ」「デプロイレポ」の二層に分離する設計は、変更の境界を明確にし、権限も分離しやすくなります。環境ごとにリポジトリを更に分けるかは、運用のオーナーシップとコンプライアンス要件に依存します。監査要件が強い領域は物理分割、内製の閉じた環境は論理分割という方針が現実的です。

プレビュー環境は便利ですが、コストとクォータの管理を怠ると一気に破綻します。自動クリーンアップの条件を明記し、PRクローズから一定時間で環境を撤去するルールを守ると健全性が保てます。Kubernetesのリソースクォータとネットワークポリシーを標準装備にしておくと、思わぬ横断影響を防げます。

人的運用の落とし穴もあります。直しやすさが上がると、レビューを素通りしたくなる誘惑が生まれます。ここをポリシーと自動テストで支えるのがプラットフォームチームの役割です。「速さは自動化で、例外は少人数の判断で」という原則を明文化し、例外の記録を残すことで、速度と統制のバランスを崩さずに済みます。

成果を測る: DORAと運用KPI

理想のパイプラインを描いたら、同じ粒度で計測し続ける必要があります。変更リードタイムは、PRの最初のコミットから本番の同期完了までを計測し、段階別に時間を分解します。レビュー待ち、CI実行、承認待ち、CD同期、段階的リリースの各区間に滞留があるかを追うと、改善の焦点が定まり、投資対効果を説明しやすくなります。変更失敗率は、ロールバックや緊急パッチの発生件数を母数のデプロイ回数で割って追跡します。復旧時間は、SLO逸脱から安定復帰までの時間をPrometheusのアラートタイムラインと照合します。これらはArgo CDやRollouts、監視基盤が持つイベントから自動算出できます。¹

公開レポートや一般的な実践例では、こうした計測とガードレールへの投資を継続することで、リードタイムが営業日ベースで数日から半日程度へ、変更失敗率が一桁台、復旧時間が30分前後といった水準に近づくケースが見られます。プロダクトや規模に依存しますが、ボトルネックを計測し、ガードレールに投資することで、速度と安定のトレードオフは想像以上に小さくできます。CIの並列度やキャッシュ戦略、CDの同期ウィンドウ、段階的リリースのステップ幅といった調整パラメータは、メトリクスを見ながら週次で小さく更新するのが実務的です。

まとめ: 変更を「怖くない」にする設計へ

GitOpsの価値は、単にデプロイを自動化することではなく、変更の意図と結果を一貫した形で記述し、計測し、巻き戻せることにあります。CIは品質を作り込み、CDは整合性を守り、ポリシーと段階的リリースが安全域を広げる。この分業が整ったとき、チームは変更を怖れず、ユーザー価値に時間を使えます。次の一歩として、あなたの現行パイプラインで「人手に寄りすぎている工程」を一つだけGitに寄せ、署名と可観測性を足してみてください。小さく始め、計測し、ガードレールに投資する。二年後に振り返ったとき、デプロイは平常運転になり、議論は「なぜそう作るのか」に戻っているはずです。

参考文献

  1. Google Cloud. Accelerate State of DevOps Report 2021 — Lead time for changes
  2. Google Cloud. Accelerate State of DevOps Report 2021 — Time to restore service
  3. Google Cloud. Accelerate State of DevOps Report 2021 — Change failure rate
  4. CNCF. Add GitOps without throwing out your CI tools. 2022-08-10.
  5. CNCF. An introduction to GitOps and Argo. 2022-09-30.
  6. Amplication. Extending GitOps: Effortless Continuous Integration and Deployment on Kubernetes.
  7. 経済産業省. SBOM(ソフトウェア部品表)活用手引書の策定について. 2023-07-28.