- 1 Container本番運用 Vol2 GitOps編|EKS × ArgoCD × Kustomize × Argo Rollouts 完全ガイド
- 1.1 1. なぜContainer本番運用 Vol2 GitOps編か — Vol1 ECS編からの架橋 + GitOps採用の現実解
- 1.2 2. ArgoCD本番運用 — App-of-Apps × ApplicationSet × Sync Policy × RBAC × SSO × Drift Detection
- 1.3 3. Kustomize本番運用 — overlays × patches × generators × components × Helm併用 × 環境差分管理
- 1.3.1 3-1 ディレクトリ構成設計 — base / overlays / components の3階層正規化
- 1.3.2 3-2 patches 優先順位 — strategic merge patch / JSON patch / 適用順序の制御
- 1.3.3 3-3 generators — configMapGenerator / secretGenerator / 環境変数注入
- 1.3.4 3-4 components — 横断的設定の再利用
- 1.3.5 3-5 Helm + Kustomize 併用 — helmCharts フィールドと post-rendering
- 1.3.6 3-6 環境昇格パターン — dev→stg→prod / image tag 自動更新
- 1.4 4. Argo Rollouts本番運用 — Canary × Blue-Green × AnalysisTemplate × 自動Rollback
- 1.4.1 4-1 Rollout リソース設計 — Deployment → Rollout 移行
- 1.4.2 4-2 Canary Progressive Delivery — ステップ制御 / traffic split / pause条件
- 1.4.3 4-3 Blue-Green デプロイ — prePromotionAnalysis / postPromotionAnalysis / autoPromotionEnabled
- 1.4.4 4-4 AnalysisTemplate + 自動Rollback — Prometheus metrics / 成功条件 / 失敗条件
- 1.4.5 4-5 ArgoCDとの統合 — Rollout health check / ArgoCD Rollouts Extension
- 1.4.6 4-6 実運用Tips — 手動Promote / Abort / retry 手順
- 1.5 5. GitOps運用設計 — Repo構成 × 環境昇格 × Secret管理 × DR
- 1.5.1 5-1 Gitリポジトリ構成設計 (monorepo vs polyrepo)
- 1.5.2 5-2 環境昇格フロー (dev→stg→prod / PR-based promotion / image tag strategy)
- 1.5.3 5-3 CI/CD × GitOps の境界設計 (CI=image build/push、CD=ArgoCD pull)
- 1.5.4 5-4 Secret管理 (SealedSecrets / External Secrets / AWS Secrets Manager連携)
- 1.5.5 5-5 DR設計 (ArgoCD自体のバックアップ / etcdバックアップ / クラスタ再構築フロー)
- 1.5.6 5-6 マルチクラスタ管理 (ArgoCD hub-spoke / cluster登録)
- 1.6 6. 詰まりポイント7選 — GitOps本番運用の地雷とフィックス
- 1.7 7. アンチパターン→正解パターン変換演習
- 1.7.1 演習一覧
- 1.7.2 演習1: ArgoCD manual sync → automated + selfHeal + prune
- 1.7.3 演習2: Kustomize base 直接編集 → overlay patches で差分管理
- 1.7.4 演習3: Rollout spec.replicas 手動指定 → HPA 管理に委譲
- 1.7.5 演習4: CI が kubectl apply 直接実行 → GitOps Repo 経由で ArgoCD 適用
- 1.7.6 演習5: Secret 平文 Git コミット → ExternalSecrets で AWS Native 化
- 1.7.7 演習6: Sync Wave 未設定で CRD→CR 同時デプロイ → Wave で順序制御
- 1.7.8 演習7: ApplicationSet で Cluster 全体を単一管理 → AppProject + RBAC 分離
- 1.8 8. まとめ — GitOps本番運用への道と次のステップ
Container本番運用 Vol2 GitOps編|EKS × ArgoCD × Kustomize × Argo Rollouts 完全ガイド

本記事は 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採用の現実解
- Vol1 ECS編: AWS Native コンテナ運用 — ECS × Fargate × ECR × ECS Exec × Service Connect (小〜中規模・AWS Native中心)
- Vol2 GitOps編 (本記事): EKS × ArgoCD × Kustomize × Argo Rollouts — Kubernetes本番 Progressive Delivery (中〜大規模・Multi-tenant GitOps)
Vol1 を完遂した方が Vol2 へ進むと、ECS Native 運用から Kubernetes GitOps への移行パスを体系的に習得できます。
1-1. 本記事のゴール
本記事では以下の4つの成果を得ることを目標とします。
- ArgoCD Multi-tenant 運用: App-of-Apps × ApplicationSet を使い、複数チームの Kubernetes Manifest を一元管理するアーキテクチャを設計・実装する
- Kustomize 環境差分管理: base / overlays / components / patches の4階層で DRY な Manifest 管理を実現し、dev / staging / prod の環境差分を宣言的に制御する
- Argo Rollouts Progressive Delivery: Canary Release と Blue-Green Deployment を AnalysisTemplate で自動判定し、本番Rollbackまでを自動化する
- 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 / CodePipeline | ArgoCD Pull型 / Git push → 自動同期 |
| Manifest管理 | Task Definition JSON / CloudFormation | Kubernetes YAML / Kustomize |
| Progressive Delivery | CodeDeploy Blue-Green (ECS統合) | Argo Rollouts Canary / Blue-Green |
| Multi-tenant | 複数ECSクラスター / IAM分離 | ArgoCD Project / Namespace RBAC |
| 適正規模 | 小〜中規模 / AWS集中度高 | 中〜大規模 / Kubernetes文化定着後 |
1-4. GitOps 5原則
GitOps を本番運用に適用する際には、以下の5原則を遵守することで運用品質が安定します。
- Git = Single Source of Truth: すべての Kubernetes Manifest はGitリポジトリで管理し、手動での
kubectl applyを禁止する - Pull型同期: ArgoCD がGitの変更を検知して Cluster に Pull型で同期する (Push型 CI/CD パイプラインからの直接apply禁止)
- Drift 即時検知: Cluster の実状態がGitの宣言状態から乖離した場合、Slack通知 + 自動Sync または手動承認フローで即時対処する
- Progressive Delivery 標準化: 本番デプロイは Canary / Blue-Green を標準化し、AnalysisTemplate で自動判定・Rollback を保証する
- Secret 暗号化必須: Kubernetes Secret は生の状態で Git に保存せず、SealedSecrets または External Secrets Operator で暗号化管理する
- 痛点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
- 問題: 子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 管理フィールドを除外し、誤報を抑制します。
| 選定基準 | 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 patch と JSON 6902 patch の2種類がある。
同一フィールドに複数patchが適用された場合、patches配列の後から記述された方が優先される。
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 / 環境変数注入
configMapGeneratorのbehaviorフィールドは 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を適用できる。
競合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配列だ。setWeight → pause → analysisの組み合わせで
段階的なトラフィック昇格と自動判定を実現する。pauseにdurationを指定しない場合は
無期限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 | Blue-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 | polyrepo (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コミットで追跡可能にする設計が本番標準だ。
環境昇格の基本フロー:
- CI (GitHub Actions) がApplication Codeをビルド → ECRにimage push → image tag =
git SHA - CI が Manifest Repoの
overlays/dev/kustomization.yamlを更新 (kustomize edit set image) - ArgoCD がdev環境にSyncを検知して自動デプロイ
- QA通過後、開発者がdev→stagingのPRを作成 → レビュー&マージ
- ArgoCD がstaging環境に自動Sync
- 本番昇格は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が担う (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/月) を抑制できる。
| 方式 | 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に保存する必要がある。
クラスタ再構築フロー:
- TerraformでVPC/EKSクラスタを再構築 (IaC由来のため5〜10分)
- EKS add-on (VPC CNI / kube-proxy / CoreDNS) を自動インストール
- ArgoCD をHelmでインストール
argocd admin import < argocd-backup.yamlで設定復元- ArgoCDがManifest Repoを検知し全ApplicationをSync (自動復旧)
- 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に通知することで、複数クラスタの状態を一元監視できる。
- ✅ 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-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 における patchesStrategicMerge と patches の適用順序は 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 で差分を検証する。
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を検証し、failureLimit と inconclusiveLimit を現実的な値に設定する。単一指標ではなく複合判定を採用する。
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は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
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
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.yamlのhelmChartsセクションを使い、
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リソース (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からデプロイが簡単だから」という理由で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例を自チームの設計レビューチェックリストとして活用されたい。
演習一覧
| # | アンチパターン | 主症状 | 正解パターン |
|---|---|---|---|
| 1 | ArgoCD manual sync 運用 | 手動忘れによるドリフト常態化 | automated + selfHeal + prune |
| 2 | Kustomize base 直接編集 | 環境間差分が消えず本番事故 | overlay patches で差分管理 |
| 3 | Rollout spec.replicas 手動指定 | HPA と競合しスケール不安定 | HPA 管理に委譲・replicas 削除 |
| 4 | CI が kubectl apply 直接実行 | Git 非経由変更→ドリフト常態化 | GitOps Repo 経由で ArgoCD 適用 |
| 5 | Secret 平文 Git コミット | 認証情報漏洩リスク | SealedSecrets / ExternalSecrets |
| 6 | Sync Wave 未設定で CRD→CR 同時デプロイ | CRD 未登録で CR が Failed | Sync Wave 設定で順序制御 |
| 7 | ApplicationSet で 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 本柱と周辺設計を体系的に習得した。
- ArgoCD 本番運用: App-of-Apps パターンによる大規模 Application 管理、ApplicationSet でのマルチクラスタ/マルチテナント展開、Sync Policy/RBAC/SSO/ドリフト検知の実装
- Kustomize 本番運用: overlays/patches/generators/components の使い分け、Helm 併用戦略、環境差分管理の設計原則 (3 階層 + components 横串)
- Argo Rollouts 本番運用: Canary/Blue-Green の段階的 rollout 設計、AnalysisTemplate による 3 指標複合判定、自動 Rollback のトリガー条件設計
- GitOps Repo 設計: Application Code / Manifest 分離 (polyrepo)、環境昇格フロー (stg→prod の Pull Request ベース昇格)、Secret 管理 AWS Native 化 (External Secrets Operator + ASM)
- DR 戦略: Velero によるリソース定期バックアップ、Multi-Region Failover の設計指針、RTO/RPO 目標に対応したリカバリ手順
- 詰まりポイント 7 選: ArgoCD ドリフト誤検知・Kustomize 差分消失・AnalysisTemplate 誤 Rollback・Secret ローテーション・ApplicationSet 権限境界の実践的対処法
- アンチパターン 7 選: GitOps 現場で頻出する設計ミスを「症状→根本原因→正解 manifest」で解剖し、設計レビューチェックリストとして活用可能な形で整理
8-2 落とし穴10選
GitOps 本番運用で実際に遭遇しやすい落とし穴を横断チェックリストとして整理する。
- ArgoCD の automated.prune を恐れて無効化 → ドリフト常態化。
Prune=falseアノテーションで例外を明示した上で prune を有効化する - Kustomize overlay を本番環境にのみ作成 → 検証なし本番適用になる。必ず stg overlay を先に作り、環境昇格フローに組み込む
- Rollout の analysisRun 失敗をアラートに含めない → 自動 Rollback が無音で発火し、リリースの失敗に気づかない。AnalysisRun の
Failedを通知基盤に連携する - GitOps Repo の main ブランチに直接 push → レビューなし変更がそのままクラスタに適用される。ブランチ保護 + Pull Request 必須化をリポジトリ設定で強制する
- External Secrets の refreshInterval を長く設定しすぎる → Secret ローテーション後も古い値が使われ続ける。機密度に応じて
5m〜1hに設定し、強制更新手順を手順書に記載する - Sync Wave 設定を CRD だけに適用 → CRD 依存の Operator pod が wave 0 の CRD より先に起動して crash する。Operator Deployment も wave 1 以上に配置する
- ApplicationSet の generators に clusters: {} を使いすぎる → 新規クラスタ追加時に意図しない Application が自動生成される。クラスタラベルフィルタ (
matchLabels) で対象を限定する - Argo Rollouts の canary steps で pause を長くしすぎる → リリースが停止し開発速度が落ちる。
pause: {duration: 10m}程度に留め、AnalysisTemplate で自動判定に委ねる - Kustomize の
images:ディレクティブを各 overlay で重複管理 → overlay 間でイメージタグが乖離する。CI が GitOps Repo の中央設定ファイルのみを更新する仕組みを整備する - ArgoCD の Application 削除でリソースが Cluster に残る → Application を削除する前にファイナライザを解除するか
--cascade=falseの挙動を理解した上で操作する。削除手順は runbook として文書化する
- Vol1 ECS編: AWS Container本番運用 Vol1 — ECS×Fargate×ECR によるマネージドコンテナ運用
ECS Task Definition 設計・Fargate Spot 活用・ECR ライフサイクルポリシー・Blue/Green デプロイの AWS Native アプローチ - Vol2 GitOps編 (本記事): EKS×ArgoCD×Kustomize×Argo Rollouts による GitOps 本番運用
Kubernetes ネイティブな GitOps パイプラインで、ECS 編と対をなす Kubernetes 派閥の完全ガイド
Vol1 (ECS本番) + Vol2 (GitOps本番) で AWS Container 運用の二大派閥を制覇。次は Service Mesh / Karpenter / 大規模 Multi-cluster Federation へ。
Container本番運用 Vol1 ECS編 — AWS Native コンテナ運用の起点
8-3 全17軸クロスリンク
- IAM: Vol1 IAM ポリシー設計
- EKS: Vol1 クラスタ設計 IRSA×ALB Ingress
- 復旧: Incident Response Runbook
- AI Bedrock: Vol1 Bedrock Agents
- セキュリティ: Vol1 セキュリティ運用
- コスト: Vol1 Cost Optimization
- マルチアカウント: Vol1 Multi-Account
- Observability: Vol1 Application Signals
- Network: Vol2 Hybrid Connectivity / Network本番運用Vol2 TGW×PrivateLink×DX×Peering×VPN マルチアカウント網設計
- DevOps: Vol1 CodePipeline
- Database: Vol1 RDS×Aurora×DynamoDB / Vol2 DMS×Aurora Global / Vol3 ElastiCache×DAX×MemoryDB
- Serverless: Vol1 Lambda×API GW×SF / Vol2 EventBridge×SQS
- Container: Vol1 ECS×Fargate×ECR / Vol2 GitOps編 (本記事)
- Storage: Vol1 S3×EFS×FSx×Storage Gateway
- Edge/CDN: Vol1 CloudFront×Lambda@Edge×Route53
- Analytics: Vol1 Glue×Athena×Redshift
- ML/AI: Vol2 Bedrock Embedding×RAG×Agents