AWS Container本番運用Vol2|ArgoCD×Kustomize×Argo Rollouts

目次

Container本番運用 Vol2 GitOps編|EKS × ArgoCD × Kustomize × Argo Rollouts 完全ガイド

GitOps全体アーキテクチャ|ArgoCD App-of-Apps × Kustomize overlays × Argo Rollouts Progressive Delivery

AWS本番運用 Container本番運用シリーズ Vol2 GitOps編 — Vol1 ECS編からの拡張
本記事は Container本番運用 Vol1 (ECS×Fargate×ECR×ECS Exec×Service Connect) を完遂した中堅エンジニア向けの GitOps 編です。Vol1 が AWS Native コンテナ運用 (ECS中心・小〜中規模最適) であったのに対し、Vol2 では Kubernetes (EKS) を基盤に ArgoCD × Kustomize × Argo Rollouts の3本柱で本番品質 Progressive Delivery を確立します。

EKS基礎シリーズとの住み分け

  • EKS Vol1-3 = Kubernetes本番運用 (Cluster設計 / IRSA / Observability / ArgoCD基礎): K8s文化必須・ArgoCD導入起点
  • Container本番運用 Vol2 GitOps編 = ArgoCD × Kustomize × Argo Rollouts 統合運用: Progressive Delivery × Multi-tenant GitOps × Repo構成設計

選定基準: EKS導入済 / 複数チームで Manifest 管理が破綻 / Canary/Blue-Green を本番で運用したい → 本Vol2推奨。


1. なぜContainer本番運用 Vol2 GitOps編か — Vol1 ECS編からの架橋 + GitOps採用の現実解

Container本番運用シリーズ ナビゲーション

Vol1 を完遂した方が Vol2 へ進むと、ECS Native 運用から Kubernetes GitOps への移行パスを体系的に習得できます。

1-1. 本記事のゴール

本記事では以下の4つの成果を得ることを目標とします。

  1. ArgoCD Multi-tenant 運用: App-of-Apps × ApplicationSet を使い、複数チームの Kubernetes Manifest を一元管理するアーキテクチャを設計・実装する
  2. Kustomize 環境差分管理: base / overlays / components / patches の4階層で DRY な Manifest 管理を実現し、dev / staging / prod の環境差分を宣言的に制御する
  3. Argo Rollouts Progressive Delivery: Canary Release と Blue-Green Deployment を AnalysisTemplate で自動判定し、本番Rollbackまでを自動化する
  4. GitOps Repo 設計: Monorepo / Polyrepo の選定基準と、セキュリティ・スケーラビリティを両立する Repo 構造を設計する

1-2. 読者像

本記事は以下のエンジニアを対象としています。

  • Container Vol1 ECS編を完遂した中堅エンジニア: ECS × Fargate の AWS Native 運用を習得済みで、次のステップとして Kubernetes GitOps を本番導入したい方
  • EKS導入済みで Manifest 管理が破綻しかけているチーム: kubectl apply 手動運用から脱却し、Git を Single Source of Truth とした Pull型デプロイに移行したい方
  • Canary/Blue-Green を本番で安全に運用したいSRE: 手動でのトラフィック切り替えではなく、メトリクス連動の自動Promotion/Rollbackを実現したい方

Kubernetes の基礎知識 (Pod/Deployment/Service/Ingress) および EKS クラスター操作の経験を前提とします。

1-3. Vol1 ECS編からの架橋

Vol1 ECS編では AWS Native のコンテナ運用 (ECS × Fargate × ECR × ECS Exec × Service Connect) を中心に解説しました。Vol1 は AWS マネージドサービスを最大限活用する「AWS Native コンテナ運用」であり、Kubernetes の運用コストを避けたい小〜中規模チームに最適です。

Vol2 GitOps編では、チームが成長し複数プロダクト・複数環境を抱えるようになった段階での移行先として、EKS上の GitOps アーキテクチャを解説します。両者は排他的ではなく、組織のコンテナ成熟度に応じて選択します。

観点Vol1 ECS編Vol2 GitOps編
デプロイ方式ECS Service Update / CodePipelineArgoCD Pull型 / Git push → 自動同期
Manifest管理Task Definition JSON / CloudFormationKubernetes YAML / Kustomize
Progressive DeliveryCodeDeploy Blue-Green (ECS統合)Argo Rollouts Canary / Blue-Green
Multi-tenant複数ECSクラスター / IAM分離ArgoCD Project / Namespace RBAC
適正規模小〜中規模 / AWS集中度高中〜大規模 / Kubernetes文化定着後

1-4. GitOps 5原則

GitOps を本番運用に適用する際には、以下の5原則を遵守することで運用品質が安定します。

  1. Git = Single Source of Truth: すべての Kubernetes Manifest はGitリポジトリで管理し、手動でのkubectl applyを禁止する
  2. Pull型同期: ArgoCD がGitの変更を検知して Cluster に Pull型で同期する (Push型 CI/CD パイプラインからの直接apply禁止)
  3. Drift 即時検知: Cluster の実状態がGitの宣言状態から乖離した場合、Slack通知 + 自動Sync または手動承認フローで即時対処する
  4. Progressive Delivery 標準化: 本番デプロイは Canary / Blue-Green を標準化し、AnalysisTemplate で自動判定・Rollback を保証する
  5. Secret 暗号化必須: Kubernetes Secret は生の状態で Git に保存せず、SealedSecrets または External Secrets Operator で暗号化管理する
痛点5選: ECS編完遂者がGitOps本番運用で直面する地雷

  • 痛点1: Manifest 管理が kubectl apply 手動運用で破綻
    10チーム × 5環境 = 50通りの Drift が発生。誰がどのバージョンを apply したか追跡不能になり、本番障害時に Git の状態と Cluster の実態が一致しない事態が頻発する。GitOps導入前に必ずManifest の Git管理を徹底すること。
  • 痛点2: ArgoCD App-of-Apps 循環参照で Cluster リソース全消失
    親Application が子Applicationを管理し、子Application が親Applicationのリポジトリを参照する循環構造を作ると、ArgoCD が無限同期ループに入り Cluster上のリソースを次々Pruneする。App-of-Apps設計時は必ず親子関係の方向を一方向に固定し、循環参照チェックをCI/CDに組み込む。
  • 痛点3: Argo Rollouts AnalysisTemplate 閾値ミスで本番Canary全Promotion暴走
    AnalysisTemplate のsuccess条件を誤設定 (例: errorRate < 0.9 とすべきところを < 0.1) すると、エラー率90%の状態でも自動Promotionが続く。AnalysisTemplate は staging で必ず実測値でキャリブレーションし、本番初回は ManualGate (pause: {}) を設けること。
  • 痛点4: Kustomize patches 優先順位を理解せず production overlay が dev設定で上書き
    複数の patches が同一フィールドを変更する場合、適用順は kustomization.yaml の記述順に依存する。base側の commonLabels が overlay側の patches より後に適用されると、prod 専用設定が base の dev値で上書きされる事態が発生する。kustomize build で必ず出力を検証すること。
  • 痛点5: SealedSecrets 鍵紛失で全環境 Secret 復号不能
    SealedSecrets Controller の秘密鍵は Cluster内の Kubernetes Secret として保存される。Cluster を再作成した際に鍵を退避しないと、Git上の全 SealedSecret が永久に復号不能になる。鍵のバックアップは AWS Secrets Manager に定期エクスポートし、Cluster再作成手順書に必須ステップとして組み込む。

Container本番運用 Vol1 ECS編 — AWS Native コンテナ運用の起点


2. ArgoCD本番運用 — App-of-Apps × ApplicationSet × Sync Policy × RBAC × SSO × Drift Detection

2-1. アーキテクチャ全体

ArgoCD は Kubernetes クラスター上で動作する GitOps 継続的デリバリーツールです。構成要素は4つのコアコンポーネントで成り立ちます。

ArgoCD Server: Web UI と gRPC/REST API を提供するフロントエンド。ユーザー・CI/CDシステムとのインターフェース。kubectl port-forward または Ingress 経由でアクセスする。

Repo Server: Git リポジトリのクローンと Manifest の生成を担当。Kustomize / Helm / Jsonnet / Plain YAML を解釈し、最終的な Kubernetes Manifest を出力する。セキュリティ境界として他コンポーネントとの gRPC 通信のみ許可する。

Application Controller: Git の宣言状態と Cluster の実状態を比較 (diff) し、同期処理を実行するコントローラー。大規模 Cluster では sharding により複数 Controller に分散する。

Dex: OIDC プロバイダーとして SSO を仲介するコンポーネント。GitHub / Google Workspace / Okta / Azure AD などのアイデンティティプロバイダーを ArgoCD RBAC に接続する。

Git Repository
  │ (webhook / polling)
  ▼
Repo Server ──── Application Controller
(Manifest生成)  │ (diff & sync)
  │▼
  └──────── ArgoCD Server ──── Dex (SSO)
│
Kubernetes Cluster
(実際のリソース適用)

2-2. App-of-Apps パターン

App-of-Apps は ArgoCD の階層管理パターンです。親Application が複数の 子Application を管理し、子Application がそれぞれのワークロードを管理します。Cluster全体の ArgoCD Application を Git で宣言的に管理するための手法です。

# apps/parent-app.yaml (親Application: ArgoCD Application リソース)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform-apps
  namespace: argocd
  finalizers:
 - resources-finalizer.argocd.argoproj.io
spec:
  project: platform
  source:
 repoURL: https://github.com/example/gitops-repo
 targetRevision: main
 path: apps/  # この配下の Application YAMLを全て管理
  destination:
 server: https://kubernetes.default.svc
 namespace: argocd
  syncPolicy:
 automated:
prune: true
selfHeal: true

親Application (platform-apps) が apps/ ディレクトリ配下の子Application YAML を監視し、新しいApplication YAML を Git にコミットするだけで子Applicationが自動作成されます。

# apps/cert-manager.yaml (子Application例)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
spec:
  project: platform
  source:
 repoURL: https://charts.jetstack.io
 chart: cert-manager
 targetRevision: v1.14.4
 helm:
values: |
  installCRDs: true
  global:
 leaderElection:
namespace: cert-manager
  destination:
 server: https://kubernetes.default.svc
 namespace: cert-manager
  syncPolicy:
 automated:
prune: true
selfHeal: true
 syncOptions:
- CreateNamespace=true
App-of-Apps 循環参照の落とし穴と対策

  • 問題: 子Application が親Applicationと同じ apps/ ディレクトリを参照すると、ArgoCD が無限ループに入り Cluster上のリソースを Prune し続ける
  • 検知方法: argocd app list で Syncing → OutOfSync → Syncing のループを確認。Application の status.operationState.syncResult に Prune ループの形跡が残る
  • 対策1 — ディレクトリ分離: 親Application が参照するディレクトリ (apps/) と、子Applicationが管理するワークロード (workloads/) を明確に分離する
  • 対策2 — finalizer管理: 親Applicationには resources-finalizer.argocd.argoproj.io を付与し、削除時に子Applicationも連鎖削除されるようにする。緊急時は kubectl patch application platform-apps -p '{"metadata":{"finalizers":[]}}' --type=merge で finalizer を除去して強制削除
  • 対策3 — CI/CDチェック: argocd app diff をPRのCIで実行し、Prune対象リソースが意図外に含まれていないか検証する

2-3. ApplicationSet で Multi-tenant GitOps

ApplicationSet は ArgoCD の拡張リソースで、Generator を使って複数の ArgoCD Application を動的に生成します。手動で Application YAML を書かなくても、Generator の定義から自動的に Application を作成・更新・削除できます。

List Generator (固定リスト):

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: team-apps-list
  namespace: argocd
spec:
  generators:
  - list:
elements:
- cluster: production
  url: https://prod.k8s.example.com
  env: prod
- cluster: staging
  url: https://staging.k8s.example.com
  env: staging
  template:
 metadata:
name: '{{cluster}}-myapp'
 spec:
project: myapp-project
source:
  repoURL: https://github.com/example/gitops-repo
  targetRevision: main
  path: 'apps/myapp/overlays/{{env}}'
destination:
  server: '{{url}}'
  namespace: myapp
syncPolicy:
  automated:
 prune: true

Cluster Generator (登録Cluster自動検出):

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-cluster-addons
  namespace: argocd
spec:
  generators:
  - clusters:
selector:
  matchLabels:
 env: production  # productionラベルのClusterのみ対象
  template:
 metadata:
name: '{{name}}-platform-addons'
 spec:
project: platform
source:
  repoURL: https://github.com/example/gitops-repo
  targetRevision: main
  path: platform/addons
destination:
  server: '{{server}}'
  namespace: platform-system
syncPolicy:
  automated:
 prune: true
 selfHeal: true

Matrix Generator (環境 × Cluster の動的組み合わせ):

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-tenant-matrix
  namespace: argocd
spec:
  generators:
  - matrix:
generators:
# 軸1: 環境リスト
- list:
 elements:
 - env: dev
revision: develop
 - env: staging
revision: main
 - env: prod
revision: main
# 軸2: チームリスト
- list:
 elements:
 - team: team-alpha
namespace: alpha
 - team: team-beta
namespace: beta
 - team: team-gamma
namespace: gamma
  template:
 metadata:
name: '{{team}}-{{env}}'
labels:
  team: '{{team}}'
  env: '{{env}}'
 spec:
project: '{{team}}-project'
source:
  repoURL: https://github.com/example/gitops-repo
  targetRevision: '{{revision}}'
  path: 'teams/{{team}}/overlays/{{env}}'
destination:
  server: https://kubernetes.default.svc
  namespace: '{{namespace}}-{{env}}'
syncPolicy:
  automated:
 prune: true
 selfHeal: '{{env != "prod"}}'  # prod のみ手動sync
  syncOptions:
 - CreateNamespace=true

Matrix Generator では「環境 × チーム = 9通りのApplication」を1つの ApplicationSet YAML で管理できます。新しいチームを追加する場合は、team: リストに1エントリを追加してコミットするだけで ArgoCD が自動的に新Applicationを生成します。

2-4. Sync Policy 設計

Sync Policy は ArgoCD Application がどのように Git の状態を Cluster に同期するかを制御します。

Automated Sync 設定:

syncPolicy:
  automated:
 prune: true# Git から削除されたリソースを Cluster からも削除
 selfHeal: true# kubectl 手動変更を自動で Git 状態に戻す
  syncOptions:
 - Validate=false  # CRD インストール前の validation スキップ
 - CreateNamespace=true  # Namespace 自動作成
 - PrunePropagationPolicy=foreground  # 子リソースから順に削除
 - ApplyOutOfSyncOnly=true  # OutOfSync リソースのみ apply (高速化)
  retry:
 limit: 5
 backoff:
duration: 5s
factor: 2
maxDuration: 3m

Resource Hook (Sync前後の処理):

# データベースマイグレーション Job (PreSync Hook)
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
 argocd.argoproj.io/hook: PreSync
 argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
 spec:
containers:
- name: migrate
  image: myapp:latest
  command: ["python", "manage.py", "migrate"]
restartPolicy: Never

Sync Wave を使うと、リソース種別ごとに適用順序を制御できます。

flowchart LR
 A["Namespace\n(wave: -10)"] --> B["CRD\n(wave: -5)"]
 B --> C["ConfigMap / Secret\n(wave: 0)"]
 C --> D["Deployment / StatefulSet\n(wave: 5)"]
 D --> E["Ingress / Service\n(wave: 10)"]

 style A fill:#4a90d9,color:#fff
 style B fill:#7b68ee,color:#fff
 style C fill:#50c878,color:#fff
 style D fill:#ffa500,color:#fff
 style E fill:#dc143c,color:#fff

Sync Wave は Kubernetes リソースに argocd.argoproj.io/sync-wave: "-10" のような annotation を付与して指定します。ArgoCD は wave の小さいリソースから順に apply し、各 wave の全リソースが healthy になってから次の wave へ進みます。これにより CRD が存在しない状態で CRD依存リソースが apply されるエラーを防止できます。

# CRD に wave -5 を付与する例
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: analysistemplates.argoproj.io
  annotations:
 argocd.argoproj.io/sync-wave: "-5"

2-5. RBAC + SSO

ArgoCD の RBAC は Project 単位で権限を分離します。AppProject リソースで、各チームがアクセスできる Cluster・Namespace・リポジトリを制限します。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-alpha-project
  namespace: argocd
spec:
  description: "Team Alpha のワークロード管理"
  # 許可するソースリポジトリ
  sourceRepos:
  - https://github.com/example/team-alpha-repo
  # 許可するデプロイ先
  destinations:
  - namespace: alpha-*
 server: https://kubernetes.default.svc
  # 許可するリソース種別 (ClusterScopedリソースは原則禁止)
  clusterResourceWhitelist: []
  namespaceResourceWhitelist:
  - group: apps
 kind: Deployment
  - group: ""
 kind: Service
  - group: networking.k8s.io
 kind: Ingress
  # RBAC ロール定義
  roles:
  - name: developer
 description: "アプリ Sync 権限 (prod 以外)"
 policies:
 - p, proj:team-alpha-project:developer, applications, sync, team-alpha-project/*, allow
 - p, proj:team-alpha-project:developer, applications, get, team-alpha-project/*, allow
 groups:
 - github-org:team-alpha-developers
  - name: readonly
 description: "読み取り専用"
 policies:
 - p, proj:team-alpha-project:readonly, applications, get, team-alpha-project/*, allow
 groups:
 - github-org:team-alpha-readonly

GitHub OAuth (Dex) 設定 (argocd-cm ConfigMap):

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  url: https://argocd.example.com
  dex.config: |
 connectors:
 - type: github
id: github
name: GitHub
config:
  clientID: $GITHUB_CLIENT_ID
  clientSecret: $GITHUB_CLIENT_SECRET
  orgs:
  - name: your-github-org
 teams:
 - team-alpha-developers
 - team-alpha-readonly
 - platform-admins

RBAC ポリシー (argocd-rbac-cm ConfigMap):

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
 # Platform Admin: 全権限
 g, github-org:platform-admins, role:admin
 # Team Alpha: 自チーム Project のみ
 g, github-org:team-alpha-developers, proj:team-alpha-project:developer
 g, github-org:team-alpha-readonly, proj:team-alpha-project:readonly

2-6. Notifications + Drift Detection

ArgoCD Notifications はApplication の状態変化を Slack / Teams / Email / Webhook に送信します。

# argocd-notifications-cm ConfigMap (抜粋)
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  # Slack 通知テンプレート
  template.app-sync-succeeded: |
 message: |
:white_check_mark: *{{.app.metadata.name}}* Sync 完了
- Revision: `{{.app.status.sync.revision | truncate 7}}`
- 環境: `{{.app.metadata.labels.env}}`
- 同期先: `{{.app.spec.destination.namespace}}`
  template.app-sync-failed: |
 message: |
:x: *{{.app.metadata.name}}* Sync 失敗
- エラー: `{{.app.status.operationState.message}}`
- 環境: `{{.app.metadata.labels.env}}`
  template.app-health-degraded: |
 message: |
:warning: *{{.app.metadata.name}}* Health 低下 (Degraded)
- 状態: `{{.app.status.health.status}}`
- 詳細: `{{.app.status.health.message}}`
  # Trigger 設定
  trigger.on-sync-succeeded: |
 - when: app.status.operationState.phase in ['Succeeded']
send: [app-sync-succeeded]
  trigger.on-sync-failed: |
 - when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]
  trigger.on-health-degraded: |
 - when: app.status.health.status == 'Degraded'
send: [app-health-degraded]

Drift Detection は ArgoCD の OutOfSync 検知機能で実現します。Application が Git の宣言状態と異なる実態を検知した際に、自動 Sync または Slack 通知で対処します。

# Application に Notification annotation を付与
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: production-myapp
  namespace: argocd
  annotations:
 notifications.argoproj.io/subscribe.on-sync-succeeded.slack: "#deployments"
 notifications.argoproj.io/subscribe.on-sync-failed.slack: "#incidents"
 notifications.argoproj.io/subscribe.on-health-degraded.slack: "#incidents"
spec:
  # OutOfSync false-positive 対策: ignoreDifferences
  ignoreDifferences:
  - group: apps
 kind: Deployment
 jsonPointers:
 - /spec/replicas  # HPA による replicas 変更を無視
  - group: ""
 kind: ConfigMap
 name: kube-proxy
 namespace: kube-system
 jsonPointers:
 - /data  # kube-proxy の自動更新を無視

OutOfSync false-positive 対策が重要です。HPA (HorizontalPodAutoscaler) が replicas を変更した場合、Git上の replicas: 2 と実態の replicas: 5 が乖離し、ArgoCD が永続的に OutOfSync を報告します。ignoreDifferences で HPA 管理フィールドを除外し、誤報を抑制します。

ArgoCD App-of-Apps vs ApplicationSet 選定基準

| 選定基準 | App-of-Apps | ApplicationSet |
|———|————|—————-|
| 適用シナリオ | Platform Apps (cert-manager / external-dns / ingress-nginx) の階層管理 | Multi-tenant / Multi-cluster の動的Application生成 |
| Application数 | 静的・少数 (〜20個) | 動的・大量 (20個超 / 増減あり) |
| Generator不要 | 各Application YAMLを手書き | Generator (List/Cluster/Git/Matrix) で自動生成 |
| 運用負荷 | Application追加ごとにYAML作成 | Generatorエントリ追加のみ |
| Multi-cluster | Cluster毎に親Application作成 | Cluster Generatorで全Clusterに自動展開 |
| Multi-tenant | Project分離はManual | Git/Matrix Generatorでチーム×環境を動的管理 |
| 推奨用途 | Platform基盤Opsチームの中央管理 | 開発チームへのセルフサービス型GitOps委譲 |

判断フロー: Application数が固定 + Platform Ops中心 → App-of-Apps。Application数が変動 + Multi-tenant + Multi-cluster → ApplicationSet。両者の組み合わせ (App-of-AppsでApplicationSetを管理) が本番規模での標準構成。


3. Kustomize本番運用 — overlays × patches × generators × components × Helm併用 × 環境差分管理

3-1 ディレクトリ構成設計 — base / overlays / components の3階層正規化

EKS × ArgoCD環境では Manifest専用monorepo が主流だ。base配下に全環境共通の完全動作可能な
Manifestを置き、overlaysで環境差分を上書きする。overlaysは 3階層以下に制限することが鉄則だ。

# monorepo: Manifest専用Repo推奨構成
k8s-manifests/
├── base/
│├── kustomization.yaml# 共通リソース定義
│├── deployment.yaml
│├── service.yaml
│└── hpa.yaml
├── overlays/
│├── dev/
││├── kustomization.yaml
││└── patches/
││ └── replicas-dev.yaml
│├── staging/
││└── kustomization.yaml
│└── prod/
│ ├── kustomization.yaml
│ └── patches/
│  └── resources-prod.yaml
└── components/
 ├── pod-security/
 │└── kustomization.yaml
 └── monitoring/
  └── kustomization.yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - hpa.yaml

commonLabels:
  app.kubernetes.io/managed-by: kustomize
  app.kubernetes.io/part-of: myapp

base配置の鉄則: base配下のリソースはoverlayなしでkubectl applyできる完全動作可能な
Manifestであること。未完成baseはoverlayで補完できず、prod環境への意図しない設定漏れを招く。


3-2 patches 優先順位 — strategic merge patch / JSON patch / 適用順序の制御

Kustomizeのpatchesは strategic merge patchJSON 6902 patch の2種類がある。
同一フィールドに複数patchが適用された場合、patches配列の後から記述された方が優先される。

patches 優先順位の落とし穴 — 同一フィールドへの複数patch適用
patches配列の順序が実際の適用順序であり、最後に記述されたpatchが勝つ。
例: replicas を dev-patch.yaml で 1 に設定し、prod-patch.yaml で 3 に設定した場合、
kustomization.yamlでprod-patch.yamlを後に書かないとdev設定(1)がprodに漏れる。
デバッグは kustomize build overlays/prod | grep replicas で最終出力を直接確認すること。
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

patches:
  # strategic merge patch: List型フィールド (env/containers) の追加・更新に強い
  - path: patches/replicas-prod.yaml
 target:
kind: Deployment
name: myapp
  # JSON patch: 配列インデックス指定や値の完全置換に強い
  - patch: |-
- op: replace
  path: /spec/template/spec/containers/0/resources/requests/memory
  value: "512Mi"
 target:
kind: Deployment
name: myapp
# strategic merge patch 例: replicas-prod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 5
  template:
 spec:
containers:
  - name: app
 resources:
requests:
  cpu: "500m"
  memory: "512Mi"
limits:
  cpu: "2000m"
  memory: "2Gi"
 env:
- name: APP_ENV
  value: production
- name: LOG_LEVEL
  value: warn

3-3 generators — configMapGenerator / secretGenerator / 環境変数注入

configMapGeneratorbehaviorフィールドは create(新規生成) / merge(既存にマージ) /
replace(完全置換) の3種類だ。disableNameSuffixHash: false(デフォルト)のままにすること。
trueにするとConfigMap更新時のPod自動再起動が無効化され、設定変更が反映されない事故が発生する。

# overlays/prod/kustomization.yaml — generators設定例
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

configMapGenerator:
  - name: app-config-prod
 behavior: create
 literals:
- APP_ENV=production
- LOG_LEVEL=warn
- MAX_CONNECTIONS=200
  - name: app-config
 behavior: merge
 literals:
- FEATURE_NEW_UI=true

secretGenerator:
  - name: app-secrets-prod
 behavior: create
 envs:
- secrets/prod.env
 type: Opaque

generatorOptions:
  disableNameSuffixHash: false
  labels:
 env: production

3-4 components — 横断的設定の再利用

Kustomize componentsはセキュリティ・監視などの横断的設定を複数overlayでDRYに再利用するユニットだ。

# components/pod-security/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

patches:
  - patch: |-
- op: add
  path: /spec/template/spec/securityContext
  value:
 runAsNonRoot: true
 runAsUser: 1000
 fsGroup: 2000
 seccompProfile:
type: RuntimeDefault
- op: add
  path: /spec/template/spec/containers/0/securityContext
  value:
 allowPrivilegeEscalation: false
 readOnlyRootFilesystem: true
 capabilities:
drop: ["ALL"]
 target:
kind: Deployment
# overlays/prod/kustomization.yaml — components を使用
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

components:
  - ../../components/pod-security
  - ../../components/monitoring

patches:
  - path: patches/replicas-prod.yaml
 target:
kind: Deployment
name: myapp

3-5 Helm + Kustomize 併用 — helmCharts フィールドと post-rendering

helmChartsフィールドを使うとhelm templateの実行をKustomize内に統合し、
生成されたManifestにさらにpatchesを適用できる。

Helm + Kustomize 競合パターンと回避策
競合1: strategic merge patchでHelm生成Manifestのフィールドが消える
HelmのList型配列管理とKustomizeのマージ戦略が競合し、意図しないフィールド削除が起きる場合がある。
対策: JSON patch (op: replace/add) でフィールドを直接指定する。
競合2: helmCharts + patchesの適用順序は固定
helmChartsが先に実行されてからpatchesが適用される順序は変更不可。
Chart内部のvaluesで解決できる場合はvaluesファイルを優先し、patchesは最終手段とすること。
# kustomization.yaml — helmCharts フィールド使用例
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

helmCharts:
  - name: ingress-nginx
 repo: https://kubernetes.github.io/ingress-nginx
 version: "4.9.1"
 releaseName: ingress-nginx
 namespace: ingress-nginx
 valuesFile: values/ingress-nginx-prod.yaml
 includeCRDs: true

patches:
  - patch: |-
- op: add
  path: /metadata/annotations/custom.example.com~1team
  value: platform-engineering
 target:
kind: Deployment
labelSelector: "app.kubernetes.io/name=ingress-nginx"

3-6 環境昇格パターン — dev→stg→prod / image tag 自動更新

Argo CD Image Updaterを使うと、CIでイメージをプッシュした際にManifest Repoの
image fieldを自動更新するPRを作成できる。GitOpsのCI/CD境界が明確になる。

# ArgoCD Application: Image Updater integration アノテーション
metadata:
  annotations:
 argocd-image-updater.argoproj.io/image-list: |
myapp=123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp
 argocd-image-updater.argoproj.io/myapp.update-strategy: semver
 argocd-image-updater.argoproj.io/myapp.allow-tags: regexp:^v[0-9]+\.[0-9]+\.[0-9]+$
 argocd-image-updater.argoproj.io/write-back-method: git
 argocd-image-updater.argoproj.io/git-branch: main

4. Argo Rollouts本番運用 — Canary × Blue-Green × AnalysisTemplate × 自動Rollback

4-1 Rollout リソース設計 — Deployment → Rollout 移行

Argo RolloutsはKubernetesのDeploymentを拡張するCRDだ。既存DeploymentをRolloutに移行するには
apiVersion/kindの変更に加え、spec.strategyでCanaryまたはBlue-Green戦略を指定する。

# Canary Rollout 定義例: Deployment → Rollout 移行
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
  namespace: myapp-prod
spec:
  replicas: 10
  revisionHistoryLimit: 3
  selector:
 matchLabels:
app: myapp
  template:
 metadata:
labels:
  app: myapp
 spec:
containers:
  - name: app
 image: "123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:v2.3.0"
 ports:
- containerPort: 8080
 resources:
requests:
  cpu: "200m"
  memory: "256Mi"
limits:
  cpu: "1000m"
  memory: "512Mi"
  strategy:
 canary:
canaryService: myapp-canary-svc
stableService: myapp-stable-svc
trafficRouting:
  alb:
 ingress: myapp-ingress
 servicePort: 80
steps:
  - setWeight: 20
  - pause: {duration: 2m}
  - analysis:
templates:
  - templateName: composite-health-check
  - setWeight: 50
  - pause: {duration: 2m}
  - setWeight: 75
  - pause: {duration: 2m}
  - setWeight: 100
maxSurge: "25%"
maxUnavailable: 0

4-2 Canary Progressive Delivery — ステップ制御 / traffic split / pause条件

Canary戦略の中核はsteps配列だ。setWeightpauseanalysisの組み合わせで
段階的なトラフィック昇格と自動判定を実現する。pausedurationを指定しない場合は
無期限pauseとなり、argocd-rollouts promoteコマンドで手動昇格する。

# Canary steps 詳細例: setWeight / pause / analysis の組み合わせ
strategy:
  canary:
 steps:
- setWeight: 20
- pause: {duration: 2m}
- analysis:
 templates:
- templateName: composite-health-check
 args:
- name: service-name
  value: myapp-canary-svc
- setWeight: 50
- pause: {duration: 2m}
- analysis:
 templates:
- templateName: composite-health-check
 args:
- name: service-name
  value: myapp-canary-svc
- setWeight: 75
- pause: {duration: 1m}
- setWeight: 100
 abortScaleDownDelaySeconds: 30

4-3 Blue-Green デプロイ — prePromotionAnalysis / postPromotionAnalysis / autoPromotionEnabled

Blue-GreenデプロイはactiveService(現行100%) と previewService(新版0%) の2サービスを維持し、
Promotion時に瞬時切り替えを行う。DBスキーマ変更が不要で即時切り戻しが求められる場合に最適だ。

# Blue-Green Rollout 定義例
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp-bg
  namespace: myapp-prod
spec:
  replicas: 5
  selector:
 matchLabels:
app: myapp-bg
  template:
 metadata:
labels:
  app: myapp-bg
 spec:
containers:
  - name: app
 image: "123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:v2.3.0"
 ports:
- containerPort: 8080
  strategy:
 blueGreen:
activeService: myapp-active-svc
previewService: myapp-preview-svc
autoPromotionEnabled: false
prePromotionAnalysis:
  templates:
 - templateName: smoke-test
  args:
 - name: service-name
value: myapp-preview-svc
postPromotionAnalysis:
  templates:
 - templateName: composite-health-check
  args:
 - name: service-name
value: myapp-active-svc
scaleDownDelaySeconds: 300
previewReplicaCount: 2

4-4 AnalysisTemplate + 自動Rollback — Prometheus metrics / 成功条件 / 失敗条件

AnalysisTemplateはRolloutの品質判定ロジックを定義するCRDだ。Prometheusメトリクスを
クエリし、successCondition/failureConditionに基づいてPromoteまたは自動Rollbackする。

# AnalysisTemplate 例: HTTP error rate + p99 latency の複合判定
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: composite-health-check
  namespace: myapp-prod
spec:
  args:
 - name: service-name
  metrics:
 - name: error-rate
interval: 30s
count: 5
successCondition: result[0] < 0.01
failureCondition: result[0] > 0.05
failureLimit: 1
provider:
  prometheus:
 address: http://prometheus-operated.monitoring:9090
 query: |
sum(rate(http_requests_total{
  service="{{args.service-name}}",
  status_code=~"5.."
}[2m])) /
sum(rate(http_requests_total{
  service="{{args.service-name}}"
}[2m]))
 - name: latency-p99
interval: 30s
count: 5
successCondition: result[0] < 500
failureCondition: result[0] > 2000
failureLimit: 1
provider:
  prometheus:
 address: http://prometheus-operated.monitoring:9090
 query: |
histogram_quantile(0.99,
  sum(rate(http_request_duration_milliseconds_bucket{
 service="{{args.service-name}}"
  }[2m])) by (le)
)

AnalysisTemplateによる自動RollbackのフローをシーケンスとしてArgo Rollouts + AnalysisTemplate 自動Rollbackシーケンスに示す。

sequenceDiagram
 participant ArgoCD
 participant Rollout
 participant AnalysisRun
 participant Prometheus
 ArgoCD->>Rollout: Sync (new image)
 Rollout->>Rollout: Canary 20% traffic
 Rollout->>AnalysisRun: Start Analysis
 AnalysisRun->>Prometheus: Query error rate
 Prometheus-->>AnalysisRun: 0.5% (OK)
 AnalysisRun-->>Rollout: Success
 Rollout->>Rollout: Promote to 100%
 Note over Rollout,AnalysisRun: Rollback path
 Rollout->>AnalysisRun: Start Analysis
 AnalysisRun->>Prometheus: Query error rate
 Prometheus-->>AnalysisRun: 8% (FAIL)
 AnalysisRun-->>Rollout: Failure
 Rollout->>Rollout: Auto Rollback

4-5 ArgoCDとの統合 — Rollout health check / ArgoCD Rollouts Extension

ArgoCD Application側でselfHeal: falseを設定することが重要だ。ArgoCDがRolloutの中間状態を
OutOfSyncと誤認して繰り返しSyncする事故を防ぐ。またignoreDifferencesでRollout controllerが
動的に更新するフィールドを除外する。

# ArgoCD Application: Argo Rollouts管理のRolloutを含む場合
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-rollouts
  namespace: argocd
spec:
  project: myapp
  source:
 repoURL: https://github.com/example/manifest-repo
 targetRevision: main
 path: overlays/prod
  destination:
 server: https://kubernetes.default.svc
 namespace: myapp-prod
  syncPolicy:
 automated:
prune: true
selfHeal: false
 syncOptions:
- CreateNamespace=true
- RespectIgnoreDifferences=true
  ignoreDifferences:
 - group: argoproj.io
kind: Rollout
jsonPointers:
  - /status
  - /spec/template/metadata/annotations/rollouts.argoproj.io

4-6 実運用Tips — 手動Promote / Abort / retry 手順

# Rollout状態確認 (watch モードでCanary進捗をリアルタイム表示)
kubectl argo rollouts get rollout myapp -n myapp-prod --watch

# CanaryのPauseを手動解除してPromotion続行
kubectl argo rollouts promote myapp -n myapp-prod

# 全StepをスキップしてCanary 100%に即時昇格 (緊急リリース時のみ)
kubectl argo rollouts promote myapp -n myapp-prod --full

# Rolloutを中断してstableバージョンに即時Rollback
kubectl argo rollouts abort myapp -n myapp-prod

# Abort後にstable imageでRollout再開
kubectl argo rollouts undo myapp -n myapp-prod

# abortedステートからretry
kubectl argo rollouts retry rollout myapp -n myapp-prod
Canary vs Blue-Green 選定基準

項目CanaryBlue-Green
主な用途段階的リスク軽減 / 本番品質検証瞬時切り替え / 即時切り戻し保証
リリースリスク低 (20%→50%→100%段階昇格)中 (Promotion時に100%一斉切替)
切り戻し速度中 (Rollbackに数分かかる場合あり)高 (scaleDownDelaySeconds以内)
インフラコスト低 (maxSurgeぶんの追加Pod)中 (active+preview で2倍のPod数)
DB変更との相性△ (schema変更時はPhase分割必須)× (旧版Podが残るため後方互換必須)
推奨ユースケースAPIサービス / A/Bテスト / 新機能展開フロントエンド / 設定変更 / 切戻し重視

5. GitOps運用設計 — Repo構成 × 環境昇格 × Secret管理 × DR

GitOpsは「Gitをシステムの唯一の真実 (Source of Truth) とし、宣言的マニフェストへの変更をトリガーに自動デプロイする」運用モデルだ。EKS × ArgoCD環境での本番運用では、Repo構成・環境昇格フロー・Secret管理・DR設計の4軸を整合させることで、監査性・障害復旧速度・セキュリティを同時に担保できる。

5-1 Gitリポジトリ構成設計 (monorepo vs polyrepo)

リポジトリ構成の選択はチーム規模・CI連携複雑度・ArgoCD設定量に直結する。monorepoはすべてのApplication CodeとManifestを単一リポジトリで管理する方式、polyrepoはApp Repoとmanifest Repoを分離する方式だ。

monorepo vs polyrepo 比較表

観点monorepopolyrepo (App+Manifest分離)
管理コスト低 (1リポジトリ)中 (2リポジトリ + cross-repo PR)
権限分離困難 (同一ACL)容易 (開発者=App Repo / Ops=Manifest Repo)
CI連携単純 (monorepo trigger)CI=App Repo / CD=Manifest Repo で明確分離
ArgoCD設定量少 (path指定のみ)多 (repoURL + targetRevision + path)
本番推奨小規模チーム (3人以下)中〜大規模 (5人以上) ◎

本番推奨はpolyrepo (App Repo + Manifest Repo分離) だ。Application Codeのビルド権限とKubernetesマニフェストの変更権限を分離することで、開発者がクラスタ設定を直接変更するリスクを排除できる。

monorepo構成を採用する場合のディレクトリ例:

# monorepo ディレクトリ構成 (apps/ + infra/ + gitops/)
my-platform/
├── apps/
│├── frontend/ # Application Code
││├── src/
││└── Dockerfile
│└── backend/
│ ├── src/
│ └── Dockerfile
├── infra/
│├── terraform/# EKS/VPC/RDS インフラ定義
│└── helm/  # Helm Chart カスタマイズ
└── gitops/
 ├── clusters/
 │├── prod/
 ││└── apps/
 ││ ├── frontend.yaml# ArgoCD Application
 ││ └── backend.yaml
 │└── staging/
 │ └── apps/
 ├── base/# Kustomize base
 │├── frontend/
 ││├── deployment.yaml
 ││└── kustomization.yaml
 │└── backend/
 └── overlays/  # 環境別パッチ
  ├── prod/
  └── staging/

polyrepo構成ではApp Repoにはコード・Dockerfile・CI定義のみを配置し、Manifest Repoにはclusters/{cluster-name}/apps/{app-name}/overlays/{env}/の3階層構造でKustomize overlaysを管理する。

5-2 環境昇格フロー (dev→stg→prod / PR-based promotion / image tag strategy)

環境昇格フローの設計ミスは、本番への意図しないデプロイや監査ログの欠落につながる。PR-based promotionを基本とし、image tagをGitコミットで追跡可能にする設計が本番標準だ。

環境昇格の基本フロー:

  1. CI (GitHub Actions) がApplication Codeをビルド → ECRにimage push → image tag = git SHA
  2. CI が Manifest Repoのoverlays/dev/kustomization.yamlを更新 (kustomize edit set image)
  3. ArgoCD がdev環境にSyncを検知して自動デプロイ
  4. QA通過後、開発者がdev→stagingのPRを作成 → レビュー&マージ
  5. ArgoCD がstaging環境に自動Sync
  6. 本番昇格はstaging→prod PRにLGTMラベル2名必須 → マージ後に自動Sync

image tag自動更新のGitHub Actions例:

# .github/workflows/update-image-tag.yaml
name: Update Image Tag in Manifest Repo

on:
  workflow_call:
 inputs:
image_tag:
  required: true
  type: string
environment:
  required: true
  type: string# dev / staging / prod

jobs:
  update-manifest:
 runs-on: ubuntu-latest
 steps:
- name: Checkout Manifest Repo
  uses: actions/checkout@v4
  with:
 repository: my-org/k8s-manifests
 token: ${{ secrets.MANIFEST_REPO_TOKEN }}

- name: Set up Kustomize
  run: |
 curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
 sudo mv kustomize /usr/local/bin/

- name: Update image tag
  run: |
 cd overlays/${{ inputs.environment }}
 kustomize edit set image \
myapp=123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:${{ inputs.image_tag }}
 git config user.email "ci-bot@myorg.com"
 git config user.name "CI Bot"
 git add kustomization.yaml
 git commit -m "chore: update myapp image to ${{ inputs.image_tag }} [${{ inputs.environment }}]"
 git push

image tagはgit SHAの短縮形 (7文字) を使用することで、どのコミットからビルドされたイメージが稼働しているかをgit logで即座に追跡できる。latestタグは本番禁止とし、CI側でimmutable tagを強制する。

5-3 CI/CD × GitOps の境界設計 (CI=image build/push、CD=ArgoCD pull)

CI/CDとGitOpsの責務を明確に分離しないと、CIが直接kubectlを叩くHybrid運用に退行し、変更履歴がGitに残らない状態が生まれる。

CI/CDとGitOpsの責務分離

  • CIが担う (Pushモデル): ソースコードビルド → テスト → Dockerfileビルド → ECR push → image tag更新PR作成
  • ArgoCDが担う (Pullモデル): Manifest Repo監視 → 差分検知 → クラスタへのSync → HealthCheck → Slack通知
  • CIが絶対にやらないこと: kubectl apply / helm upgrade の直接実行 (本番クラスタへの直接Push)
  • ArgoCDが絶対にやらないこと: Application Codeのビルド / ECRへのimage push
  • 境界点: Manifest RepoへのPR merge = CI→CD の唯一の引き渡しポイント

この境界設計により、本番クラスタへのすべての変更がGit PRとして監査ログに残り、Revertもgit revert1コマンドで実行可能になる。

Argo CD Image Updaterの活用:

開発環境ではargocd-image-updaterを使い、新しいimage tagをArgoCDが自動検知してManifest Repoに自動コミットする方式も有効だ。ただし本番環境では人間のレビューをPRに挟むため、Image Updaterはdev環境のみに適用し、staging/prodはPR-based promotionを維持する。

5-4 Secret管理 (SealedSecrets / External Secrets / AWS Secrets Manager連携)

KubernetesのSecretはBase64エンコードに過ぎず、Manifest RepoにそのままコミットするとGitに平文が残る。本番では暗号化とAWS Nativeの鍵管理を組み合わせる2方式が主流だ。

SealedSecrets (Bitnami):

公開鍵でSecretを暗号化したSealedSecretオブジェクトをManifest Repoにコミットし、クラスタ内のcontrollerが秘密鍵で復号する。鍵ローテーションとバックアップ運用が必要なため、単一クラスタかつ小規模チームに適する。

# SealedSecret 例
# Step 1: kubesealで暗号化
# kubectl create secret generic db-credentials \
#--from-literal=username=admin \
#--from-literal=password=S3cr3t! \
#--dry-run=client -o yaml | \
#kubeseal --controller-name=sealed-secrets-controller \
#--controller-namespace=kube-system \
#--format=yaml > sealed-db-credentials.yaml

# Step 2: 生成されたSealedSecretをManifest Repoにコミット
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  encryptedData:
 username: AgBjK2p8...# kubesealで暗号化済み
 password: AgC9mQ1n...# kubesealで暗号化済み
  template:
 metadata:
name: db-credentials
namespace: production
 type: Opaque

External Secrets Operator + AWS Secrets Manager:

AWS Secrets Manager (ASM) にSecretを格納し、ExternalSecretリソースがクラスタ内でKubernetes Secretに変換する方式。IRSAでASMへのアクセス権を制御するためAWS Native統合が強固で、鍵ローテーションが不要なマルチクラスタ環境の第一選択だ。

# ExternalSecret + SecretStore (AWS Secrets Manager) 例

# Step 1: SecretStore (クラスタ×AWSリージョンの接続設定)
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
 aws:
service: SecretsManager
region: ap-northeast-1
auth:
  jwt:
 serviceAccountRef:
name: external-secrets-sa
namespace: external-secrets
---
# Step 2: ExternalSecret (Secretの取得・変換定義)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
 name: aws-secrets-manager
 kind: ClusterSecretStore
  target:
 name: db-credentials# 生成されるKubernetes Secret名
 creationPolicy: Owner
  data:
 - secretKey: username
remoteRef:
  key: prod/myapp/db-credentials# ASMのシークレット名
  property: username
 - secretKey: password
remoteRef:
  key: prod/myapp/db-credentials
  property: password

AWS Systems Manager Parameter Store経由の設定値注入:

機密度の低い設定値 (feature flag / 接続先エンドポイント等) はSSM Parameter Storeの/myapp/prod/パス配下に格納し、ExternalSecretのservice: ParameterStoreで参照することでASMのコスト (0.40USD/Secret/月) を抑制できる。

Secret管理方式の比較と選択基準

方式AWS統合鍵管理負荷Gitへのコミット推奨シーン
SealedSecrets不要高 (鍵ローテーション/バックアップ必須)暗号化SealedSecretをコミット可オンプレ / 非AWS環境
External Secrets + ASM強 (IRSA)低 (AWSが管理)ExternalSecretのみコミット (値はASM)本番AWS環境 ◎
External Secrets + SSM Parameter Store強 (IRSA)同上大量設定値 / コスト重視
HashiCorp Vault中 (VaultAgent)最高不要Multi-cloud / エンタープライズのみ

選択基準: AWS環境でのEKS本番 → External Secrets Operator + ASM。Gitコミット可能な暗号化が必須かつ単一クラスタ → SealedSecrets。

5-5 DR設計 (ArgoCD自体のバックアップ / etcdバックアップ / クラスタ再構築フロー)

GitOpsの利点は「Manifest Repoがあればクラスタを再構築できる」点にあるが、ArgoCDの設定自体 (Application定義・Project設定・RBAC) もバックアップ対象だ。

ArgoCDバックアップ:

# ArgoCD全設定のエクスポート (定期実行推奨: 1日1回)
argocd admin export > argocd-backup-$(date +%Y%m%d).yaml

# S3にバックアップ保存
aws s3 cp argocd-backup-$(date +%Y%m%d).yaml \
  s3://my-argocd-backup/$(date +%Y/%m/)/

# 復元時
argocd admin import < argocd-backup-20241201.yaml

バックアップファイルにはApplications・AppProjects・Repository接続情報が含まれる。S3のバージョニングとCross-region replicationを有効化してRPO=24時間を確保する。

etcdバックアップ:

EKS Managed Nodeの場合、etcdはAWSが管理するため手動バックアップは不要だ。ただしセルフマネージドコントロールプレーンの場合はetcdctl snapshot saveを定期実行し、S3に保存する必要がある。

クラスタ再構築フロー:

  1. TerraformでVPC/EKSクラスタを再構築 (IaC由来のため5〜10分)
  2. EKS add-on (VPC CNI / kube-proxy / CoreDNS) を自動インストール
  3. ArgoCD をHelmでインストール
  4. argocd admin import < argocd-backup.yaml で設定復元
  5. ArgoCDがManifest Repoを検知し全ApplicationをSync (自動復旧)
  6. External Secrets OperatorがASMからSecretを再取得

GitOpsを徹底することでStep 5以降が完全自動化される。RTO目標はクラスタ再構築30分+全App Sync10分=約40分が現実的な目安だ。

5-6 マルチクラスタ管理 (ArgoCD hub-spoke / cluster登録)

大規模本番環境では複数EKSクラスタ (リージョン別 / Blue-Green クラスタ / 本番・DR) をArgoCDで一元管理するhub-spokeモデルが標準だ。

Hub-Spoke構成:

  • Hub: ArgoCD管理クラスタ (本番Workloadは載せない専用クラスタ)
  • Spoke: WorkloadがデプロイされるEKSクラスタ群
# ArgoCD cluster登録 (Hub ArgoCDにSpokeクラスタを追加)

# Step 1: Spoke クラスタの kubeconfig を取得
aws eks update-kubeconfig \
  --name prod-cluster-ap-northeast-1 \
  --region ap-northeast-1 \
  --alias prod-apne1

# Step 2: ArgoCD CLIでHubに登録
argocd cluster add prod-apne1 \
  --name prod-ap-northeast-1 \
  --annotation "region=ap-northeast-1,tier=production"

# Step 3: 登録確認
argocd cluster list
# SERVER NAME VERSION  STATUS
# https://AAABBBCCC.gr7.ap-northeast-1.eks.amazonaws.comprod-ap-northeast-1 1.29  Successful
# https://DDDEEEFFF.gr7.us-east-1.eks.amazonaws.com  prod-us-east-11.29  Successful

ApplicationSet でマルチクラスタデプロイ:

ApplicationSetのCluster Generatorを使うと、登録済みクラスタ全てに同一ApplicationをデプロイするArgoCD Application群を自動生成できる。

# ApplicationSet でマルチクラスタデプロイ
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: myapp-multicluster
  namespace: argocd
spec:
  generators:
 - clusters:
  selector:
 matchLabels:
environment: production # productionラベルを持つ全クラスタが対象
  template:
 metadata:
name: '{{name}}-myapp'  # prod-ap-northeast-1-myapp など自動生成
 spec:
project: production
source:
  repoURL: https://github.com/my-org/k8s-manifests.git
  targetRevision: main
  path: overlays/prod
destination:
  server: '{{server}}'  # Generator が各クラスタのAPIサーバーURLを注入
  namespace: myapp
syncPolicy:
  automated:
 prune: true
 selfHeal: true
  syncOptions:
 - CreateNamespace=true

マルチクラスタ構成ではargocd-notificationsを活用し、各クラスタのSync結果・HealthStatus変化をSlack/PagerDutyに通知することで、複数クラスタの状態を一元監視できる。

GitOps運用設計 本番チェックリスト

  • ✅ Manifest RepoはApp Repoと分離し、Ops担当のみ直Push権限を付与している
  • ✅ image tagはgit SHA固定 ('latest'タグ本番禁止) をCI側で強制している
  • ✅ 本番昇格PRはLGTM 2名以上 + CI全Pass必須のBranch Protectionを設定している
  • ✅ External Secrets Operator + ASMを採用し、Secretの平文がManifest Repoに存在しない
  • ✅ argoCDバックアップを1日1回S3に保存し、Cross-region replicationを有効化している
  • ✅ DR手順書のクラスタ再構築RTOを40分以内で検証済み (年1回以上)
  • ✅ マルチクラスタはhub-spoke構成でHubはWorkload非搭載の専用ArgoCD管理クラスタ
  • ✅ CI/CDとGitOpsの境界がチーム全員に共有され、kubectl直接適用が本番で行われていない

6. 詰まりポイント7選 — GitOps本番運用の地雷とフィックス

EKS × ArgoCD × Kustomize × Argo Rollouts の統合運用では、各ツールの内部メカニズムを理解していないと
同じトラブルに何度も引っかかる。本セクションでは現場で頻発する7つの詰まりポイントを
「症状→原因→解決策→落とし穴」の4点セットで解説する。


6-1 App-of-Apps 循環参照

症状: ArgoCDコンソールで親ApplicationのStatusが ComparisonError または Unknown のままSyncが完了しない。
argocd app get <parent-app> を実行すると以下のエラーが出力される。

FATA[0001] rpc error: code = Unknown desc = unable to get application resources:
  application spec is invalid: InvalidSpecError: self-reference not allowed

子Applicationが次々と生成されてArgoCDのApplication一覧が増殖し、最終的にクラスタ全体のReconcileが
詰まってArgoCDコントローラのメモリ使用率が急上昇する。

原因: App-of-Apps パターンで親ApplicationのソースパスがArgoCD Applicationマニフェスト自身を
含むディレクトリを指してしまうことで発生する。ArgoCD は そのディレクトリに存在するApplication
マニフェストを読んで子Appを作り、その子Appもまた同じパスから別の子Appを作ろうとする無限ループに陥る。

# NG構成: source.pathがappsディレクトリ全体を指している
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-app
  namespace: argocd
spec:
  source:
 repoURL: https://github.com/example/gitops-repo
 targetRevision: HEAD
 path: apps # この配下にroot-app.yaml自体が存在するとループ
  destination:
 server: https://kubernetes.default.svc
 namespace: argocd
  syncPolicy:
 automated:
prune: true

解決策: 親AppのYAMLは bootstrap/ に格納し、子Appのマニフェストを格納する apps/ とは別ディレクトリに分離する。

# OK構成: source.pathはapps/のみを指し、自分自身(bootstrap/)は含まない
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-app
  namespace: argocd
spec:
  source:
 repoURL: https://github.com/example/gitops-repo
 targetRevision: HEAD
 path: apps  # bootstrap/root-app.yamlを含まないパス
  destination:
 server: https://kubernetes.default.svc
 namespace: argocd

既に循環が発生した場合は argocd app delete <parent-app> --cascade=false で子Appを保持したまま
親のみを削除してからディレクトリ構成を修正し、再デプロイする。

落とし穴: 親Appのsource.pathと自身のmanifest格納場所を同一にしない

App-of-Apps パターンで最も多い初歩ミスは「全マニフェストをapps/以下に統一」という判断。
親AppのYAMLをapps/root-app.yamlに置いてsource.path: appsを指定した瞬間に循環が始まる。
bootstrap/(ArgoCD初期化用)とapps/(子App定義用)の2層分離を必ず守ること。
循環発生後はArgoCDコントローラのCPUが張り付いて他のSyncも止まるため、
障害の影響範囲が全クラスタに波及する点も見落とせない。


6-2 Kustomize patches 優先順位混乱

症状: kustomize build overlays/production を実行すると、productionオーバーレイで指定した
レプリカ数やリソース制限が反映されず、developmentの値のままapplyされる。
kubectl get deployment <name> -o yaml で確認すると意図しない設定が混入している。

原因: Kustomize における patchesStrategicMergepatches の適用順序は kustomization.yaml内の定義順 に従う。
後に定義したパッチが前のパッチを上書きするルールだが、overlaysとcomponentsが混在すると
この順序が直感に反する動作をする。また、上位overlayが下位overlayを resources: で参照する場合、
下位で設定した値を上位が上書きするという仕様が混乱を生む。

解決策: overlayの継承元は必ず base/ に統一し、overlays/ 同士での継承を禁止する。
環境横断の共通変更は components/ を使って横串パッチとして管理する。

# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base# baseのみを継承 (他overlayを参照しない)
components:
  - ../../components/monitoring
patches:
  - path: patch-replicas.yaml
 target:
kind: Deployment
name: api-server
  - path: patch-resources.yaml
 target:
kind: Deployment
name: api-server

パッチの競合が疑われる場合は kustomize build overlays/production 2>&1 | head -100
ビルド結果を確認してから kubectl diff -k overlays/production で差分を検証する。

落とし穴: overlays同士の継承は「後勝ち」が直感に反する

Kustomizeのpatchesはkustomization.yaml内の記述順で後優先となる。
overlays/production が overlays/development を resources に含めると、
developmentのpatchがproductionのpatchより後に解決されて上書きされる場合がある。
解決の鉄則は「overlayの継承元はbaseのみ」。横断設定はcomponents化して
各overlayのkustomization.yamlに個別インポートする設計が最も見通しがよい。


6-3 Argo Rollouts AnalysisTemplate 誤設定

症状: Canaryデプロイ開始直後に AnalysisRun のステータスが Failed となり、
意図しない自動Rollbackが連続発生する。kubectl get analysisrun -n <namespace> で確認すると
Message: Metric "error-rate" assessed Failed due to failed (1) > failureLimit (0) と出力される。

原因: AnalysisTemplateのPrometheusクエリが誤っているか、metricsサーバへの疎通が
Canary開始直後には確立されていないため、最初の計測で即座にfailedと判定されてRollbackが走る。
また、failureLimit: 0 (デフォルト) の設定では1度でも計測失敗するとRollbackとなるため、
クエリのtypoや一時的なPrometheusの応答遅延でも本番がRollbackされる。

解決策: 本番投入前に dryRun でAnalysisTemplateを検証し、
failureLimitinconclusiveLimit を現実的な値に設定する。単一指標ではなく複合判定を採用する。

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: canary-analysis
spec:
  metrics:
  - name: error-rate
 interval: 60s
 failureLimit: 3 # 3回連続失敗で初めてFailed (デフォルト0は危険)
 inconclusiveLimit: 2  # 計測不能2回まで許容
 provider:
prometheus:
  address: http://prometheus-operated.monitoring.svc:9090
  query: |
 sum(rate(http_requests_total{
job="api-server",
status=~"5.."
 }[5m]))
 /
 sum(rate(http_requests_total{
job="api-server"
 }[5m]))
# dryRunで本番前にAnalysisTemplateを検証
kubectl argo rollouts create analysisrun \
  --from-template canary-analysis \
  --name test-analysis \
  --dry-run=client \
  -n production

# 実行中のAnalysisRunのログを確認
kubectl argo rollouts get analysisrun canary-analysis-xxxxx -n production
落とし穴: AnalysisTemplateのPrometheusクエリエラーはRollout開始後まで発覚しない

AnalysisTemplateはRollout作成時にバリデーションされない。
クエリのtypoや存在しないメトリクス名の参照はCanaryが開始されて初めてエラーになる。
本番Canaryを止めてから「クエリが間違っていた」と気づくパターンが多発する。
対策は2つ: ①デプロイ前に kubectl argo rollouts create analysisrun --dry-run でテスト実行、
②Prometheusコンソールで実際にクエリを打って結果が返ることを確認。
failureLimit: 0 をそのまま使うのも厳禁。段階的に failureLimit: 3 以上から始めること。


6-4 ArgoCD Sync Wave 依存関係

症状: ArgoCD Syncを実行するとCRDの作成を待たずにCRDを使うOperatorのDeploymentが
先にapplyされて no matches for kind "SomeCustomResource" in version "example.com/v1" エラーが発生する。
または、PreSync Hookが失敗してもSyncが継続し、データベースマイグレーション未完了の状態で
アプリケーションが起動してしまう。

原因: Sync Waveのデフォルト値は 0 であり、Waveアノテーションを付与しないと全リソースが
Wave 0として同時にapplyされる。CRDより先にCRDを使うCustomResourceが作成されようとするため、
no kind registered エラーが出る。Hookの failurePolicy デフォルトは Fail だが、
Hookが失敗してもArgoCD全体のSyncが止まらずに後続リソースが部分的にapplyされるケースがある。

解決策: リソース種別ごとにSync Waveの標準値を決めて全マニフェストに一貫して適用する。

# 標準Wave設計: アノテーション付与例
# Namespace → Wave -10
metadata:
  annotations:
 argocd.argoproj.io/sync-wave: "-10"
# CRD → Wave -5
# ConfigMap/Secret → Wave 0 (デフォルト)
# Deployment → Wave 5
# Service → Wave 10
# PreSync Hook: DBマイグレーション (Wave 3, failurePolicy: Fail)
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
 argocd.argoproj.io/hook: PreSync
 argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
 argocd.argoproj.io/sync-wave: "3"
spec:
  template:
 spec:
containers:
  - name: migrate
 image: api-server:latest
 command: ["python", "manage.py", "migrate", "--noinput"]
restartPolicy: Never
  backoffLimit: 3
落とし穴: Wave設計なしでCRD+Operator+CustomResourceを同一Syncは必ず失敗する

ArgoCDのSyncは同一Wave内のリソースを並列にapplyする。
CRDとそのCRDを使うリソースを同じWaveに入れると、適用順序が不定になり
no matches for kind エラーが確率的に発生する。
標準Wave設計の鉄則は Namespace(-10) → CRD(-5) → ConfigMap/Secret(0) → Deployment(5) → Service(10)
Hookは対象Waveより1〜2低いWaveを割り当てて「Hookが完了してからリソースが作られる」順序を保証すること。


6-5 OutOfSync false-positive

症状: マニフェストを変更していないにも関わらず、ArgoCDが常に OutOfSync を報告し続ける。
argocd app diff <app-name> を実行すると以下のような差分が永続的に表示される。

===== apps/Deployment production/api-server ======
178c178
< app.kubernetes.io/managed-by: Helm
---
> app.kubernetes.io/managed-by: argocd

または kubectl.kubernetes.io/last-applied-configuration アノテーションの値が差分として検出される。

原因: Kubernetesはリソース作成・更新時に一部のフィールドやアノテーションを自動付与する。
Helmでデプロイしたリソースには app.kubernetes.io/managed-by: Helm ラベルが付き、
ArgoCDから見ると managed-by: argocd との差分として検出される。
また、Horizontally Pod Autoscaler (HPA) の spec.replicas は HPAが動的に変更するため、
マニフェストの replicas: 3 と実際の値が常にずれる。

解決策: ignoreDifferences を使って誤検知フィールドをArgoCD側の差分比較から除外する。

# Application spec内でignoreDifferencesを設定
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-server
  namespace: argocd
spec:
  ignoreDifferences:
 - group: apps
kind: Deployment
jsonPointers:
  - /spec/replicas# HPA管理下のreplicas
 - group: ""
kind: Service
jsonPointers:
  - /spec/clusterIP  # 自動付与IP
 - group: apps
kind: Deployment
managedFieldsManagers:
  - kube-controller-manager# コントローラが変更するフィールド
  syncPolicy:
 syncOptions:
- RespectIgnoreDifferences=true # Sync時もignore設定を適用
# 現在発生している差分の詳細確認
argocd app diff api-server --refresh

# ignoreDifferences設定後の動作確認
argocd app get api-server --refresh
# STATUS列がSyncedになればOK
落とし穴: Helm生成ラベルとHPA管理replicasが2大誤検知源

ArgoCDでHelm Chartをデプロイする場合、Helmが付与する app.kubernetes.io/managed-by: Helm
ラベルがArgoCDの差分比較で常にフラグされる。またHPAを使うDeploymentでspec.replicasをマニフェストに書くと、
HPAがreplicasを動的変更するたびに差分が発生してArgoCD Alertが誤発火し続ける。
対策は ignoreDifferences でjsonPointersを除外することと、
HPA管理下のDeploymentマニフェストからは spec.replicas フィールドを削除すること。


6-6 Helm + Kustomize 競合

症状: Helm Chartを helmCharts (HelmChartInflationGenerator) 経由でKustomizeと組み合わせた場合に
KustomizeのpatchesがHelm Hookリソースに適用されない。または kustomize build の実行に時間がかかり、
ArgoCDのSync Timeoutが発生する。argocd app sync を実行すると以下のエラーが出る。

ComparisonError: failed to load application from repo: exit status 1
helm template . --include-crds  --name-template ... failed: Error: ...

原因: ArgoCDはHelmとKustomizeを同時に適用する場合、内部でHelmテンプレートを展開してから
Kustomize patchesを適用するパイプラインを使う。このパイプラインではHelm Hookリソース
(helm.sh/hook: post-install アノテーション付き) はKustomizeのpatchターゲットに含まれないため
Hookリソースへのpatchが無効化される。また、Helm RendererとKustomize Builderがバージョン不一致の場合に
テンプレート展開自体が失敗する。

解決策: HelmとKustomizeを混在させる場合はkustomization.yamlhelmChartsセクションを使い、
Helm hookリソースへのpatchはpatchesではなくHelm values経由で制御する。

# kustomization.yaml でHelm Chartを取り込む
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

helmCharts:
  - name: my-app
 repo: https://charts.example.com
 version: 1.2.3
 releaseName: my-app
 namespace: production
 valuesFile: values-production.yaml# 環境差分はvalues経由で制御
 includeCRDs: true

# Helm展開後のDeploymentにはpatchが有効
patches:
  - patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
 spec:
nodeSelector:
  node-role: production
 target:
kind: Deployment
name: my-app
落とし穴: Helm hookはKustomizeのpatchターゲットにならない

Helm hookリソース (helm.sh/hook アノテーション付き) はKustomizeのpatch処理後に
Helmが注入するため、Kustomize patchesで Helm hookを書き換えようとしても無効になる。
hookの設定変更はHelmのvalues.yamlを通じて行うのが唯一の正解。
また、ArgoCDのHelm+Kustomize混在デプロイでは argocd-cm ConfigMapの
kustomize.buildOptions 設定がグローバルに影響するため、
一つのApplicationの設定変更が他のApplicationのビルドに副作用を起こす場合がある。


6-7 GitOps × CI/CD 境界の曖昧化

症状: CIパイプラインからの kubectl apply とArgoCDのSyncが同一クラスタに対して競合し、
ArgoCDが次のSyncで kubectl apply の変更を「差分」として検知して元の状態に戻す。
結果として、CIで適用したはずの設定が数分後に巻き戻される「Sync Holy War」が発生する。
また、イメージタグの自動更新のためにCIが直接Gitリポジトリを書き換えてArgoCDが追従するが、
コミット履歴がCIの自動コミットで汚染されてPRレビューが困難になる。

原因: GitOpsの原則はGitリポジトリが「唯一の真実の源泉」であること。
CIパイプラインがKubernetesクラスタに直接書き込む操作はGitの状態と乖離を生み、
ArgoCDはGitの状態を正とするため、CIの変更を「望ましくない差分」として扱い巻き戻す。

解決策: CI/CDの役割を明確に分離し、クラスタへの直接操作はArgoCDのみが行う設計を徹底する。
イメージタグの自動更新はArgoCD Image Updaterに委任してPR自動作成フローで管理する。

# ArgoCD Image Updater: Gitリポジトリへの自動PR作成設定
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-server
  namespace: argocd
  annotations:
 # Image Updaterにイメージタグ追跡を委任
 argocd-image-updater.argoproj.io/image-list: api=ghcr.io/example/api-server
 argocd-image-updater.argoproj.io/api.update-strategy: semver
 argocd-image-updater.argoproj.io/api.semver-constraint: ">=1.0.0 <2.0.0"
 # Git書き戻し設定: マニフェストリポジトリにPRを作成
 argocd-image-updater.argoproj.io/write-back-method: git
 argocd-image-updater.argoproj.io/git-branch: image-update/{{.AppName}}/{{.Image.Name}}
spec:
  source:
 repoURL: https://github.com/example/gitops-manifests
 targetRevision: HEAD
 path: overlays/production
落とし穴: CI での kubectl apply は GitOps を破壊する

最も多い設計ミスは「CIからデプロイが簡単だから」という理由でGitHubActionsに
kubectl apply -f を書いてしまうこと。ArgoCDは次のSync周期(デフォルト3分)に
そのCI適用をGitとの差分と判定して自動的に巻き戻す
CI側は「デプロイ成功」と判断しているのに数分後に古い状態に戻っているという事態になる。
CIの役割はBuild・Test・イメージPushのみに限定し、クラスタへの書き込みはArgoCD Syncのみとすること。
イメージタグ更新はArgoCD Image Updaterに完全委任するのが現場での正解パターン。


7. アンチパターン→正解パターン変換演習

なぜアンチパターン学習が有効か

GitOps本番運用の失敗パターンは共通している。「動いている」状態のアンチパターンは発見が遅れ、スケール時・障害時に一気に顕在化する。本演習では実際の現場で頻出する7つのアンチパターンを「症状→根本原因→正解パターン→Trade-off」の4軸で解剖する。各演習のmanifest例を自チームの設計レビューチェックリストとして活用されたい。

演習一覧

#アンチパターン主症状正解パターン
1ArgoCD manual sync 運用手動忘れによるドリフト常態化automated + selfHeal + prune
2Kustomize base 直接編集環境間差分が消えず本番事故overlay patches で差分管理
3Rollout spec.replicas 手動指定HPA と競合しスケール不安定HPA 管理に委譲・replicas 削除
4CI が kubectl apply 直接実行Git 非経由変更→ドリフト常態化GitOps Repo 経由で ArgoCD 適用
5Secret 平文 Git コミット認証情報漏洩リスクSealedSecrets / ExternalSecrets
6Sync Wave 未設定で CRD→CR 同時デプロイCRD 未登録で CR が FailedSync Wave 設定で順序制御
7ApplicationSet で Cluster 全体を単一管理権限境界なし・テナント混在AppProject + RBAC 分離

演習1: ArgoCD manual sync → automated + selfHeal + prune

症状: syncPolicy.automated を設定せず手動 sync に頼ると、Git push 後の反映漏れ・ドリフト常態化が起きる。特に夜間デプロイや複数チーム運用で「誰かが sync するはず」という思い込みが事故を生む。kubectl 直接操作後に「クラスタが自動 revert される」という誤解も多い。

アンチパターン

# BAD: 手動 sync / selfHeal なし — Git push しても自動適用されない
spec:
  syncPolicy: {}

正解パターン

# GOOD: 全自動 + 自己修復 + 孤立リソース自動削除
spec:
  syncPolicy:
 automated:
selfHeal: true# kubectl 直接変更を即時 revert
prune: true# Git 削除済みリソースを自動 delete
 syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- ServerSideApply=true

Trade-off: prune: true は誤 delete リスクがある。kubectl.argoproj.io/sync-options: Prune=false アノテーションで除外リソースを明示的に保護すること。selfHeal は変更の即時 revert が前提になるため、kubectl 直接デバッグが禁止される副作用を運用ルールとして周知する。


演習2: Kustomize base 直接編集 → overlay patches で差分管理

症状: base/ の YAML を直接編集すると全環境に変更が波及し、stg 向け修正が本番を壊すリスクが常在する。replica 数や resource limits を base に書いた結果、本番環境でリソース不足が生じたケースが多い。

アンチパターン

# BAD: base/deployment.yaml を環境ごとに直接書き換え
# dev 用の replica 数が base に混入する
spec:
  replicas: 1# 本番に混入すると障害
  resources:
 limits:
memory: 256Mi# 本番では不足

正解パターン

# overlays/prod/replica-patch.yaml — 本番専用パッチ
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  template:
 spec:
containers:
  - name: my-app
 resources:
requests:
  cpu: "500m"
  memory: "512Mi"
limits:
  memory: "1Gi"
# overlays/prod/kustomization.yaml
resources:
  - ../../base
patches:
  - path: replica-patch.yaml
 target:
kind: Deployment
name: my-app
images:
  - name: my-app
 newTag: "v1.2.3"

Trade-off: overlays が増えると patch の管理コストが増加する。横串の共通設定 (sidecar injection, resource limits の基底値) は components/ に切り出し、再利用性を高める。環境ごとの overlay は base に対する「差分のみ」を持つ原則を崩さない。


演習3: Rollout spec.replicas 手動指定 → HPA 管理に委譲

症状: Rollout.spec.replicas を固定値で設定すると HPA の desiredReplicas と競合し、スケールアウト直後にリソースが縮退する。ArgoCD の selfHeal が固定値に revert し続ける「スケール拮抗」が本番で発生する。

アンチパターン

# BAD: replicas 固定で HPA と競合
spec:
  replicas: 3# HPA が 5 にスケールしても ArgoCD が 3 に revert
  selector:
 matchLabels:
app: my-app

正解パターン

# GOOD: replicas フィールドを削除し HPA に全権委譲
# Rollout manifest から replicas を省略
spec:
  selector:
 matchLabels:
app: my-app
  template:
 metadata:
labels:
  app: my-app
# HPA manifest — Rollout を scaleTargetRef に指定
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
 apiVersion: argoproj.io/v1alpha1
 kind: Rollout
 name: my-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
 - type: Resource
resource:
  name: cpu
  target:
 type: Utilization
 averageUtilization: 60
 - type: Resource
resource:
  name: memory
  target:
 type: AverageValue
 averageValue: 800Mi

Trade-off: HPA が適切に動作するには Metrics Server または Prometheus Adapter の稼働が前提。minReplicas: 2 以上を設定し、scale-to-zero を防ぐ。また Canary rollout 中は HPA が stable/canary 両 ReplicaSet を合算してスケールするため、canary weight 設定と HPA の min/max の整合性を設計時に確認する。


演習4: CI が kubectl apply 直接実行 → GitOps Repo 経由で ArgoCD 適用

症状: CI pipeline が直接 kubectl apply を実行すると、Git の manifest と実クラスタ状態が乖離しドリフト検知が機能しない。また CI の kubeconfig 権限が過大になりやすく、ブレークグラス操作のログが散逸する。

アンチパターン

# BAD: GitHub Actions で直接 apply
- name: Deploy
  run: kubectl apply -f k8s/
  env:
 KUBECONFIG: ${{ secrets.KUBECONFIG }}

正解パターン

# GOOD: manifest を GitOps Repo に push → ArgoCD が自動 sync
- name: Update manifest version
  run: |
 cd gitops-repo
 kustomize edit set image my-app=my-app:${{ github.sha }}
 git config user.email "ci@example.com"
 git config user.name "CI Bot"
 git add .
 git commit -m "chore: update my-app to ${{ github.sha }}"
 git push origin main

Trade-off: GitOps Repo への push から実際の適用まで ArgoCD のポーリング間隔 (デフォルト 3 分) のラグが生じる。argocd app sync --prune をトリガーする Webhook を設定して即時反映に切り替えることを検討する。また GitOps Repo の main ブランチは Branch Protection Rule で PR 必須化し、CI Bot の直接 push も stg 環境用ブランチに限定する設計が望ましい。


演習5: Secret 平文 Git コミット → ExternalSecrets で AWS Native 化

症状: kubectl create secret --dry-run=client -o yaml の出力を base64 のままコミットすると、Git 履歴に認証情報が永続的に残る。リポジトリのアクセス権者全員が読める状態になり、漏洩が発生しても検知できない。

アンチパターン

# BAD: base64 平文を直接コミット (デコード即読み取り可能)
apiVersion: v1
kind: Secret
metadata:
  name: my-app-secret
data:
  password: cGFzc3dvcmQ=# base64 であり暗号化ではない
  api-key: c2VjcmV0a2V5MTIz

正解パターン (External Secrets Operator + AWS Secrets Manager)

# ExternalSecret — ASM の値を Kubernetes Secret に同期
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-app-secret
spec:
  refreshInterval: 5m
  secretStoreRef:
 name: aws-secretsmanager
 kind: ClusterSecretStore
  target:
 name: my-app-secret
 creationPolicy: Owner
 deletionPolicy: Retain
  data:
 - secretKey: password
remoteRef:
  key: prod/my-app/db
  property: password
 - secretKey: api-key
remoteRef:
  key: prod/my-app/external-api
  property: api-key
# ClusterSecretStore — IRSA で ASM にアクセス
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmanager
spec:
  provider:
 aws:
service: SecretsManager
region: ap-northeast-1
auth:
  jwt:
 serviceAccountRef:
name: external-secrets-sa
namespace: external-secrets

Trade-off: External Secrets Operator は ASM へのネットワーク依存が生じる。ASM が一時的に到達不能になると Secret の更新が停止するが、既存の Secret は残るため即時障害にはならない。refreshInterval は Secret の機密度に応じて調整し、ローテーション後の強制更新手順 (kubectl annotate externalsecret ... force-sync=$(date +%s)) を運用手順書に記載する。


演習6: Sync Wave 未設定で CRD→CR 同時デプロイ → Wave で順序制御

症状: CRD と CR を同じ sync で適用すると、CRD の登録完了前に CR が作成され no kind "XxxResource" is registered エラーで Failed になる。Operator が依存する CRD を自分より先に登録しなければならないケースで頻発する。

アンチパターン

# BAD: CRD と CR を wave アノテーションなしで同居
# ArgoCD は適用順序を保証しないため CR が先に処理される場合がある
metadata:
  name: my-crd# wave なし
---
metadata:
  name: my-cr # wave なし — CRD より先に処理される可能性

正解パターン

# CRD manifest — wave 0 で先行適用
metadata:
  name: my-crd
  annotations:
 argocd.argoproj.io/sync-wave: "0"
# Operator Deployment — wave 1 で CRD 登録完了後に起動
metadata:
  name: my-operator
  annotations:
 argocd.argoproj.io/sync-wave: "1"
# CR manifest — wave 2 で Operator 起動完了後に作成
metadata:
  name: my-cr
  annotations:
 argocd.argoproj.io/sync-wave: "2"

Trade-off: Sync Wave が増えると sync 時間が線形に増加する。Wave は最小限 (0/1/2 の 3 段階程度) に留め、PreSync Hook でヘルスチェック・PostSync Hook でスモークテストを組み合わせてデプロイ前後の検証を分担させる。


演習7: ApplicationSet で Cluster 全体を単一管理 → AppProject + RBAC 分離

症状: 単一の ApplicationSet で全テナントの Application を管理すると、あるチームの誤操作が他チームの Namespace に波及するリスクがある。また RBAC の粒度が粗くなり、最小権限の原則を実現しにくい。

アンチパターン

# BAD: 1 つの ApplicationSet で全 cluster の全 app を管理
spec:
  generators:
 - clusters: {}# 全 cluster 対象 — 権限境界なし
  template:
 spec:
project: default# default project は制限なし

正解パターン

# GOOD: AppProject でテナント境界を設定
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-a-project
  namespace: argocd
spec:
  description: "Team A の管理スコープ"
  sourceRepos:
 - "https://github.com/org/team-a-gitops"# ソースを限定
  destinations:
 - namespace: "team-a-*"# Namespace を限定
server: "https://kubernetes.default.svc"
  clusterResourceWhitelist:
 - group: ""
kind: Namespace
  namespaceResourceBlacklist:
 - group: ""
kind: ResourceQuota# チームが ResourceQuota を変更できないよう禁止
  roles:
 - name: team-a-deployer
policies:
  - p, proj:team-a-project:team-a-deployer, applications, sync, team-a-project/*, allow
  - p, proj:team-a-project:team-a-deployer, applications, get, team-a-project/*, allow
# ApplicationSet — AppProject を明示指定
spec:
  generators:
 - list:
  elements:
 - cluster: prod
team: team-a
  template:
 spec:
project: team-a-project# AppProject で権限境界を強制
destination:
  namespace: "team-a-{{.env}}"

Trade-off: AppProject が増えると管理オーバーヘッドが増える。ApplicationSet の project: フィールドで AppProject を指定し、generator → AppProject の対応を 1:1 に保つことで管理コストを抑制する。AppProject の RBAC ポリシーはチームリーダーと Platform チームが共同でレビューし、四半期ごとに棚卸しを実施する。


8. まとめ — GitOps本番運用への道と次のステップ

8-1 本記事で学んだこと

Vol2 GitOps 編では、EKS 上の GitOps 本番運用に必要な 3 本柱と周辺設計を体系的に習得した。

  1. ArgoCD 本番運用: App-of-Apps パターンによる大規模 Application 管理、ApplicationSet でのマルチクラスタ/マルチテナント展開、Sync Policy/RBAC/SSO/ドリフト検知の実装
  2. Kustomize 本番運用: overlays/patches/generators/components の使い分け、Helm 併用戦略、環境差分管理の設計原則 (3 階層 + components 横串)
  3. Argo Rollouts 本番運用: Canary/Blue-Green の段階的 rollout 設計、AnalysisTemplate による 3 指標複合判定、自動 Rollback のトリガー条件設計
  4. GitOps Repo 設計: Application Code / Manifest 分離 (polyrepo)、環境昇格フロー (stg→prod の Pull Request ベース昇格)、Secret 管理 AWS Native 化 (External Secrets Operator + ASM)
  5. DR 戦略: Velero によるリソース定期バックアップ、Multi-Region Failover の設計指針、RTO/RPO 目標に対応したリカバリ手順
  6. 詰まりポイント 7 選: ArgoCD ドリフト誤検知・Kustomize 差分消失・AnalysisTemplate 誤 Rollback・Secret ローテーション・ApplicationSet 権限境界の実践的対処法
  7. アンチパターン 7 選: GitOps 現場で頻出する設計ミスを「症状→根本原因→正解 manifest」で解剖し、設計レビューチェックリストとして活用可能な形で整理

8-2 落とし穴10選

GitOps 本番運用で実際に遭遇しやすい落とし穴を横断チェックリストとして整理する。

  1. ArgoCD の automated.prune を恐れて無効化 → ドリフト常態化。Prune=false アノテーションで例外を明示した上で prune を有効化する
  2. Kustomize overlay を本番環境にのみ作成 → 検証なし本番適用になる。必ず stg overlay を先に作り、環境昇格フローに組み込む
  3. Rollout の analysisRun 失敗をアラートに含めない → 自動 Rollback が無音で発火し、リリースの失敗に気づかない。AnalysisRun の Failed を通知基盤に連携する
  4. GitOps Repo の main ブランチに直接 push → レビューなし変更がそのままクラスタに適用される。ブランチ保護 + Pull Request 必須化をリポジトリ設定で強制する
  5. External Secrets の refreshInterval を長く設定しすぎる → Secret ローテーション後も古い値が使われ続ける。機密度に応じて 5m〜1h に設定し、強制更新手順を手順書に記載する
  6. Sync Wave 設定を CRD だけに適用 → CRD 依存の Operator pod が wave 0 の CRD より先に起動して crash する。Operator Deployment も wave 1 以上に配置する
  7. ApplicationSet の generators に clusters: {} を使いすぎる → 新規クラスタ追加時に意図しない Application が自動生成される。クラスタラベルフィルタ (matchLabels) で対象を限定する
  8. Argo Rollouts の canary steps で pause を長くしすぎる → リリースが停止し開発速度が落ちる。pause: {duration: 10m} 程度に留め、AnalysisTemplate で自動判定に委ねる
  9. Kustomize の images: ディレクティブを各 overlay で重複管理 → overlay 間でイメージタグが乖離する。CI が GitOps Repo の中央設定ファイルのみを更新する仕組みを整備する
  10. ArgoCD の Application 削除でリソースが Cluster に残る → Application を削除する前にファイナライザを解除するか --cascade=false の挙動を理解した上で操作する。削除手順は runbook として文書化する

Container本番運用シリーズ — ナビゲーション

Vol1 (ECS本番) + Vol2 (GitOps本番) で AWS Container 運用の二大派閥を制覇。次は Service Mesh / Karpenter / 大規模 Multi-cluster Federation へ。

Container本番運用 Vol1 ECS編 — AWS Native コンテナ運用の起点


8-3 全17軸クロスリンク