本記事には広告(アフィリエイトリンク)が含まれます。

Kustomize+ArgoCD+Velero【CKA第14回】

広告

新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第14回です。動作確認バージョン: AlmaLinux 10.1 / K8s v1.35.5 / Helm v4.1.4 / Kustomize v5.7.1 / ArgoCD v3.4.2(argo-cd chart 9.5.15)/ Velero v1.18.0(chart 12.0.1)/ kubeadm v1.35.5(2026-05-24 時点)

広告

今ここマップ(第14回 / 全16回 / 第5部完走)

今ここ: 第14回 / 全16回(第5部:監視・運用)
▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░  88%

第1部(クラスタ構築):       ■■■■■ 5/5 回(完了)
第2部(ワークロード管理):   ■■■   3/3 回(完了)
第3部(ネットワーク):       ■■■   3/3 回(完了)
第4部(ストレージ):         ■     1/1 回(完了)
第5部(監視・運用):         ■■    2/2 回 ← 今ここ(第5部完走)
第6部(トラブルシュート):   □□    0/2 回

第13回で kube-prometheus-stack v84.x + Loki + Fluent Bit による監視スタックを構築しました。第14回ではいよいよ「宣言的管理」の仕上げとして、Kustomize overlay による環境分離、ArgoCD v3.4.2 による GitOps 化、Velero v1.18 による backup/restore を一気に完成させます。第5部(監視・運用)の完走回です。

第14回のキャッチコピー: 「Kustomize overlay で環境分離 → ArgoCD で GitOps 化 → Velero で backup/restore の三段重ね」

第14回終了時の達成状態:

  • kubectl get pods -n argocd で ArgoCD の全 Pod が Running
  • ArgoCD UI(port-forward 8080)で fanclub-api-prod Application が Synced + Healthy 表示
  • kubectl kustomize overlays/staging / overlays/prod で差分 YAML が出力される
  • git push 後 3 分以内に ArgoCD が自動 sync して fanclub Deployment の replicas が変化
  • velero backup get で fanclub-backup が Completed 表示
  • velero restore get でリストア後に kubectl get pods -n fanclub が全 Pod Running

第14回のスコープと設計判断 — 5 機構連携の役割分担を整理する

本回は Helm・Kustomize・ArgoCD・Velero・MinIO の 5 機構を連携させます。最初にスコープと設計判断を整理し、何を扱い何を扱わないかを明確にします。

第14回で「やること」と「やらないこと」

やることやらないこと
Kustomize base + staging/prod overlay 作成Helm Umbrella Chart パターン
ArgoCD Helm インストール + fanclub-api GitOps 化ApplicationSet(複数 Application 自動生成)
Velero + MinIO で fanclub Namespace バックアップクラスタ全体スナップショット詳細設定
selfHeal + automated.prune で自動 sync 確認ArgoCD の SSO / RBAC 詳細設定
リストア検証(kubectl delete ns → restore)Velero CSI スナップショット(Longhorn 統合)

設計判断の背景

判断① Kustomize は kubectl 内蔵 v5.7.1 を使用する

本シリーズでは kubectl kustomize および kubectl apply -k で完結させ、スタンドアロン kustomize バイナリの個別インストールは行いません。K8s v1.35 の kubectl には Kustomize v5.7.1 が内蔵されており、CKA 試験環境とも一致します。スタンドアロン版が必要な場合は GitHub Releases から取得できます。

判断② Git リポジトリは k8s-ops 上の bare repo + git daemon(git://)で自己完結化

本来は GitLab・GitHub・Gitea などの認証付き Git サーバを使うべきですが、本シリーズは自己完結型の教育環境を優先し、k8s-ops 上に git init --bare でローカルリポジトリを作成し、git-daemon パッケージの git daemon で git:// プロトコル公開します。ArgoCD の repo-server(go-git)は smart HTTP / git:// を要求するため、dumb HTTP の python3 -m http.server ではなく git daemon を使います。本番非推奨であり、本文中の警告ボックスで明示します。

判断③ ArgoCD は Helm chart 9.5.15(ArgoCD v3.4.2)を argocd Namespace に配置

Service type は ClusterIP のままとし、kubectl port-forward で UI にアクセスします。Ingress や LoadBalancer 公開は第3巻の TLS / 認証強化と合わせて扱います。教育目的で server.insecure: true を有効化し HTTP アクセスを許可しますが、本番では Ingress + cert-manager + HTTPS が必須です。

判断④ Velero は Helm chart 12.0.1(v1.18.0)と MinIO を velero Namespace に同居配置

MinIO を S3 互換バックエンドとして velero Namespace に in-cluster Deployment で配置します。同一クラスタにバックアップ先を置く構成は本番非推奨です。クラスタ自体が壊れたときバックアップも消失するためです。本番では別ホスト・別クラスタの MinIO または AWS S3 などの外部ストレージを使います。

判断⑤ fanclub Namespace は ArgoCD 管理に切り替え・第13回の監視は継続

第13回完了時点で fanclub-api(Deployment / StatefulSet / Service)と Prometheus ServiceMonitor が稼働中です。Kustomize overlay 化後も ServiceMonitor から fanclub-backend Pod へのスクレイプは継続される構成にします。ServiceMonitor 自体は monitoring Namespace 側で管理されているため、fanclub の overlay には含めません。

第14回で使う Namespace 設計

Namespace役割導入タイミング
argocdArgoCD コンポーネント一式やってみよう②
veleroVelero + MinIOやってみよう④
fanclub(既存)fanclub-api(GitOps 管理に移行)第4回〜継続
monitoring(既存)kube-prometheus-stack + Loki + Fluent Bit第13回〜継続

Helm と Kustomize の使い分け — 成熟チームが辿り着く役割分担

「Helm と Kustomize はどちらが優れているか」という議論は、成熟チームでは「何にどちらを使うか」の役割分担に収束します。2025 年の CNCF サーベイでは Helm 採用率が約 75% に達し、その大多数が Kustomize と併用しています。第14回ではこの役割分担を本シリーズの実装にそのまま適用します。

Helm の強み

  • バージョン管理(--version でピン留め)と依存解決(Chart.yaml の dependencies)
  • values.yaml による設定の抽象化(テンプレートと設定値の分離)
  • helm upgrade / helm rollback によるバージョン管理
  • サードパーティ製チャート(ArgoCD・Velero・cert-manager・Traefik 等)の取り込みが容易

Kustomize の強み

  • テンプレートエンジン不使用の決定論的 YAML(Go テンプレートのデバッグ困難さがない)
  • GitOps 親和性(ArgoCD がそのまま kubectl kustomize を実行できる)
  • patch 合成の透明性(base + overlay の差分が明確)
  • base/overlay 構造による環境分離(staging / prod / dev)

本シリーズでの役割分担マップ

Helm と Kustomize の役割分担(第14回ハイブリッドモデル)
Helm と Kustomize の役割分担(第14回ハイブリッドモデル)

外部ツール(クラスタコンポーネント)は Helm で導入し、自社アプリ(fanclub-api)は Kustomize の base + overlay で環境別差分管理する、というハイブリッドモデルです。これは CKA Curriculum v1.35 の D1「Use Helm and Kustomize to install cluster components」に直接対応します。

第三のパターン: Helm + Kustomize post-render

Helm chart に対して組織固有のカスタマイズを Kustomize で被せる「post-render」パターンも存在します。ArgoCD の multi-source 機能を使うと、Helm chart を repo として参照しつつ、別 repo の Kustomize overlay を組み合わせて post-render できます。本シリーズではスコープ外ですが、成熟チームの典型構成として覚えておきます。

Kustomize base/overlays 構造の設計 — patches と replicas transformer

fanclub-api のマニフェストを Kustomize で再構成します。共通部分を base に置き、staging / prod の差分(レプリカ数など)を overlay で表現する標準的な構造です。

ディレクトリ構成

~/fanclub-gitops/
├── base/
│   ├── kustomization.yaml
│   ├── namespace.yaml
│   ├── frontend-deployment.yaml
│   ├── frontend-service.yaml
│   ├── backend-deployment.yaml
│   ├── backend-service.yaml
│   ├── db-statefulset.yaml
│   ├── db-service.yaml
│   └── db-secret.yaml
└── overlays/
    ├── staging/
    │   └── kustomization.yaml   (replicas=1)
    └── prod/
        └── kustomization.yaml   (replicas=3 + patches)

base の kustomization.yaml の役割

base/kustomization.yaml は resources: で base ディレクトリ内の全 YAML を列挙し、namespace: fanclub で全リソースの Namespace を一括指定します。これによって個別の Deployment や Service の metadata.namespace を省略でき、後で別 Namespace に流用するときの保守性が上がります。

replicas 組み込み transformer(v5 推奨)

Kustomize v5.0 で導入された replicas フィールドは、patch ファイルを別途用意せずに kustomization.yaml 内でレプリカ数を上書きできる組み込み transformer です。記法は以下のとおりです。

replicas:
  - name: fanclub-frontend
    count: 3
  - name: fanclub-backend
    count: 3

name でリソースを特定し、count で新しいレプリカ数を指定します。デフォルトでは Deployment が対象ですが、StatefulSet や ReplicaSet も対象にできます。patch ファイルを書かずに kustomization.yaml 内で完結するため、本シリーズの主力として使います。

patchesStrategicMerge は非推奨 — patches フィールドを使う

Kustomize v5.0.0 以降、patchesStrategicMergepatchesJson6902 は非推奨化されました。kustomize.config.k8s.io/v1beta1 API では引き続き動作しますが警告が出力され、将来の v1 API では完全に廃止される予定です。新規作成は必ず patches フィールドを使います。

旧記法(非推奨):

patchesStrategicMerge:
  - replicas-patch.yaml

新記法(v5 推奨・別ファイル形式):

patches:
  - path: replicas-patch.yaml
    target:
      kind: Deployment
      name: fanclub-backend

新記法(v5 推奨・インライン形式・別ファイル不要):

patches:
  - target:
      kind: StatefulSet
      name: fanclub-db
    patch: |-
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: fanclub-db
      spec:
        replicas: 1

patches フィールドは Strategic Merge Patch と JSON Patch を自動判別します。既存プロジェクトに patchesStrategicMerge が残っている場合は kubectl kustomize edit fix 相当の操作(スタンドアロン版なら kustomize edit fix)で自動変換できます。

ArgoCD アーキテクチャ + Application CRD の読み方

ArgoCD は Kubernetes 用の GitOps エンジンです。Git リポジトリにある宣言的マニフェスト(YAML・Helm chart・Kustomize overlay など)を「あるべき状態」とみなし、クラスタの実状態と継続的に比較・同期します。

ArgoCD のコンポーネント構成

ArgoCD アーキテクチャ — 3 コンポーネント + Application CRD
ArgoCD アーキテクチャ — 3 コンポーネント + Application CRD

Application CRD の主要フィールド

フィールド説明
spec.source.repoURLGit リポジトリ URL
spec.source.pathリポジトリ内のパス(例: overlays/prod
spec.source.targetRevisionGit ブランチ・タグ・コミット(HEAD = 最新)
spec.destination.server対象クラスタ(https://kubernetes.default.svc = 同一クラスタ)
spec.destination.namespaceデプロイ先 Namespace
spec.syncPolicy.automated.pruneGit から削除されたリソースを K8s からも削除
spec.syncPolicy.automated.selfHeal手動変更を検知して自動で Git 状態に戻す

selfHeal の動作

syncPolicy.automated.selfHeal: true を有効化すると、ArgoCD は定期的(デフォルト 3 分間隔)に Application の Git 状態と クラスタ状態を比較します。差分を検知すると即座に再 sync を実行し、クラスタを Git の状態に巻き戻します。kubectl scalekubectl edit による手動変更も検知して自動修復するため、Git を単一の信頼源とする運用が徹底されます。

automated.prune の効果と注意

automated.prune: true を有効化すると、Git から削除したマニフェストに対応するリソースが自動的にクラスタから削除されます。Git 上のマニフェストとクラスタ状態を完全一致させたい場合に有効ですが、ArgoCD 管理外で手動作成したリソース(Secret や ConfigMap など)が誤って削除されるリスクがあります。

運用パターンとしては、初回導入時は prune: false で動作確認を行い、誤削除リスクの低いことを確認してから prune: true に切り替えます。本回ではすでに ArgoCD 管理対象が fanclub Namespace に限定され、かつ Velero でバックアップを取得する前提のため、最初から prune: true で導入します。

Velero アーキテクチャ + MinIO S3 互換バックエンド

Velero は Kubernetes クラスタ用のバックアップ・リストアツールです。Namespace 単位・ラベル単位・クラスタ全体の単位でバックアップを取得し、S3 互換オブジェクトストレージに保存します。PersistentVolume のバックアップは CSI スナップショットや restic / kopia 統合で対応します(本回では基本のリソースバックアップに絞ります)。

Velero + MinIO の全体像

Velero + MinIO バックアップ/リストアフロー
Velero + MinIO バックアップ/リストアフロー

s3ForcePathStyle=true が必須な理由

AWS S3 SDK のデフォルトは virtual-host style(bucket.s3.amazonaws.com)でバケット名をホスト名に組み込みます。一方 MinIO は path style(s3.example.com/bucket)のみ対応します。両者が不一致のままだと、upload は成功するが download が失敗するという気づきにくい挙動になります。velero-values.yamlconfig.s3ForcePathStyle: "true" を必ず設定します。

MinIO の新環境変数

MinIO の root 認証情報は新変数名 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD を使います。旧変数 MINIO_ACCESS_KEY / MINIO_SECRET_KEY は非推奨で、起動時に WARNING: MINIO_ACCESS_KEY and MINIO_SECRET_KEY are deprecated. という警告が出力されます。本番運用では新変数のみの採用が必須です(旧変数を使い続けると Pod 起動ログに WARNING が出続けて監視アラートを誤発火させる原因になる)。本シリーズも一貫して新変数のみ使用します。

本回の本番非推奨ポイント(4 件)

本番非推奨ポイント(教育環境のみで容認・本番では必ず代替策を採る)

  • ① bare repo を HTTP で公開: 認証なし・暗号化なし。本番では GitLab / GitHub / Gitea + SSH または HTTPS with 認証を使う
  • ② ArgoCD admin HTTP アクセス: TLS なし・自己署名証明書もなし。本番では Ingress + cert-manager + HTTPS と OIDC SSO を使う
  • ③ MinIO を同一クラスタ内に in-cluster 配置: クラスタ自体の障害でバックアップも消える。本番では別ホスト・別クラスタの MinIO または AWS S3 などの外部ストレージを使う
  • kubectl delete namespace fanclub で動作確認: 本番環境での誤操作リスク大。本番ではステージング環境または別 Namespace へのリストアでテストする

やってみよう①: Kustomize overlay 作成(base + staging + prod)

fanclub-api のマニフェストを Kustomize の base / overlays 構造に再編成します。作業場所は k8s-ops(developer ユーザー)です。

Step 0: 第4回 Helm リリースの削除(重要・先に実行)

第4回で helm install fanclub-api ~/fanclub-charts/fanclub-api -n fanclub によりリリース名 fanclub-api を作成しました。本回からは Kustomize マニフェストで fanclub-backend / fanclub-frontend / fanclub-db という独立リソースとして再構築するため、Helm 由来のリソース(fanclub-api-fanclub-api 等)と Kustomize 由来のリソースが共存すると kubectl get pods -n fanclub の結果が予測不能になり、第15-16回の演習前提が壊れます。先に Helm リリースを削除します。実行コマンド:

$ helm uninstall fanclub-api -n fanclub
$ kubectl get all -n fanclub

実行結果(リソースが空になっていれば成功):

release "fanclub-api" uninstalled
No resources found in fanclub namespace.

第13回で適用した ServiceMonitor(fanclub-backend-monitor)は monitoring Namespace 側にあり影響を受けません。Kustomize 適用後(後述 Step 7 以降)、新規 Service fanclub-backend がデプロイされ ServiceMonitor のスクレイプ対象が再度 UP になります。

Step 1: kubectl 内蔵 Kustomize のバージョン確認

実行コマンド(k8s-ops 上・developer):

$ kubectl version --client

実行結果:

Client Version: v1.35.0
Kustomize Version: v5.7.1

Kustomize v5.7.1 が内蔵されていることを確認できました。CKA 試験環境も同じバージョン構成です。

Step 2: ディレクトリ作成

実行コマンド:

$ mkdir -p ~/fanclub-gitops/{base,overlays/staging,overlays/prod}
$ ls -R ~/fanclub-gitops

実行結果:

/home/developer/fanclub-gitops:
base  overlays

/home/developer/fanclub-gitops/base:

/home/developer/fanclub-gitops/overlays:
prod  staging

/home/developer/fanclub-gitops/overlays/prod:

/home/developer/fanclub-gitops/overlays/staging:

Step 3: base マニフェスト群を作成

base/namespace.yaml の全量を以下に示します。

apiVersion: v1
kind: Namespace
metadata:
  name: fanclub
  labels:
    pod-security.kubernetes.io/enforce: baseline

base/frontend-deployment.yaml の全量:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-frontend
  labels:
    app: fanclub-frontend
    tier: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-frontend
  template:
    metadata:
      labels:
        app: fanclub-frontend
        tier: frontend
    spec:
      containers:
        - name: frontend
          image: 192.168.1.123:5000/nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "50m"
              memory: "64Mi"
            limits:
              cpu: "200m"
              memory: "128Mi"
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 15
            periodSeconds: 20

base/frontend-service.yaml の全量:

apiVersion: v1
kind: Service
metadata:
  name: fanclub-frontend
  labels:
    app: fanclub-frontend
spec:
  type: ClusterIP
  selector:
    app: fanclub-frontend
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP

base/backend-deployment.yaml の全量:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-backend
  labels:
    app: fanclub-backend
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-backend
  template:
    metadata:
      labels:
        app: fanclub-backend
        tier: backend
    spec:
      containers:
        - name: backend
          image: 192.168.1.123:5000/fanclub-backend:0.2.0
          ports:
            - containerPort: 8080
            - containerPort: 8081
          env:
            - name: DB_HOST
              value: "fanclub-db"
            - name: DB_PORT
              value: "5432"
            - name: DB_NAME
              value: "fanclub"
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: fanclub-db
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: fanclub-db
                  key: password
          resources:
            requests:
              cpu: "200m"
              memory: "384Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 20

base/backend-service.yaml の全量:

apiVersion: v1
kind: Service
metadata:
  name: fanclub-backend
  labels:
    app: fanclub-backend
    app.kubernetes.io/name: fanclub-backend
spec:
  type: ClusterIP
  selector:
    app: fanclub-backend
  ports:
    - name: http
      port: 8080
      targetPort: 8080
      protocol: TCP
    - name: metrics
      port: 8081
      targetPort: 8081
      protocol: TCP

base/db-statefulset.yaml の全量:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: fanclub-db
  labels:
    app: fanclub-db
    tier: database
spec:
  serviceName: fanclub-db
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-db
  template:
    metadata:
      labels:
        app: fanclub-db
        tier: database
    spec:
      containers:
        - name: postgres
          image: 192.168.1.123:5000/postgres:18-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: "fanclub"
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: fanclub-db
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: fanclub-db
                  key: password
            - name: PGDATA
              value: "/var/lib/postgresql/data/pgdata"
          volumeMounts:
            - name: fanclub-db-data
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
  volumeClaimTemplates:
    - metadata:
        name: fanclub-db-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: longhorn
        resources:
          requests:
            storage: 5Gi

base/db-service.yaml の全量:

apiVersion: v1
kind: Service
metadata:
  name: fanclub-db
  labels:
    app: fanclub-db
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: fanclub-db
  ports:
    - name: postgres
      port: 5432
      targetPort: 5432
      protocol: TCP

base/db-secret.yaml の全量(演習用の固定値・本番では SealedSecrets または ExternalSecrets を使う):

apiVersion: v1
kind: Secret
metadata:
  name: fanclub-db
type: Opaque
stringData:
  username: fanclub
  password: fanclub-dev-password

Step 4: base/kustomization.yaml の作成

base/kustomization.yaml の全量:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: fanclub

commonLabels:
  app.kubernetes.io/part-of: fanclub-api
  app.kubernetes.io/managed-by: argocd

resources:
  - namespace.yaml
  - db-secret.yaml
  - db-service.yaml
  - db-statefulset.yaml
  - backend-service.yaml
  - backend-deployment.yaml
  - frontend-service.yaml
  - frontend-deployment.yaml

namespace: fanclub で全リソースを fanclub Namespace に強制配置します。commonLabels は全リソースに共通ラベルを付与する transformer です。resources の並び順は依存関係(Secret → Service → StatefulSet → Deployment)に沿って記述すると kubectl apply -k の安定性が増します。

なお Kustomize v5 では commonLabels は非推奨(deprecated)扱いになっており、kubectl kustomizekubectl apply -k の実行時に # Warning: 'commonLabels' is deprecated. Please use 'labels' instead. という警告が stderr に出力されます。ビルド・適用は正常に完了するため本記事の手順はそのまま使えますが、将来的には labels:pairs: で key/value を指定し、includeSelectors: true でセレクタにも反映するかを制御する)への移行が推奨されます。本記事では CKA 試験でも依然として広く使われている commonLabels 表記を採用しています。

Step 5: overlays/staging/kustomization.yaml(replicas=1)

overlays/staging/kustomization.yaml の全量:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namePrefix: stg-

commonLabels:
  environment: staging

replicas:
  - name: fanclub-frontend
    count: 1
  - name: fanclub-backend
    count: 1

namePrefix: stg- によって全リソース名に stg- が付き、staging 環境であることが識別しやすくなります。commonLabels も staging 用に上書きします。

Step 6: overlays/prod/kustomization.yaml(replicas=3 + patches で StatefulSet 制御)

overlays/prod/kustomization.yaml の全量:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

commonLabels:
  environment: prod

replicas:
  - name: fanclub-frontend
    count: 3
  - name: fanclub-backend
    count: 3

patches:
  - target:
      kind: StatefulSet
      name: fanclub-db
    patch: |-
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: fanclub-db
      spec:
        replicas: 1
  - target:
      kind: Deployment
      name: fanclub-backend
    patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: fanclub-backend
      spec:
        template:
          spec:
            containers:
              - name: backend
                resources:
                  requests:
                    cpu: "300m"
                    memory: "512Mi"
                  limits:
                    cpu: "1000m"
                    memory: "768Mi"

prod overlay では namePrefix を付けずに既存の fanclub-api リソース名と一致させます。replicas transformer で Deployment のレプリカ数を 3 に上書きし、patches フィールドで以下 2 つを実演します。

  • StatefulSet(fanclub-db)の replicas を 1 のまま固定(replicas transformer は Deployment 優先のため、StatefulSet は patch で明示)
  • backend Deployment の resources を prod 用に増強(インライン patch でリソース要求のみ変更)

Step 7: kustomize build で差分確認

実行コマンド:

$ kubectl kustomize ~/fanclub-gitops/overlays/staging | grep -A 1 'kind: Deployment\|replicas:' | head -20

実行結果:

kind: Deployment
metadata:
--
  replicas: 1
  selector:
--
kind: Deployment
metadata:
--
  replicas: 1
  selector:

実行コマンド:

$ kubectl kustomize ~/fanclub-gitops/overlays/prod | grep -A 1 'kind: Deployment\|replicas:' | head -20

実行結果:

kind: Deployment
metadata:
--
  replicas: 3
  selector:
--
kind: Deployment
metadata:
--
  replicas: 3
  selector:

staging では replicas=1、prod では replicas=3 が出力されました。さらに diff で差分を視覚化します。

実行コマンド:

$ diff <(kubectl kustomize ~/fanclub-gitops/overlays/staging) \
       <(kubectl kustomize ~/fanclub-gitops/overlays/prod) | head -30

実行結果(抜粋):

<     environment: staging
---
>     environment: prod
<   name: stg-fanclub-backend
---
>   name: fanclub-backend
<   replicas: 1
---
>   replicas: 3

環境別の差分が一目で確認できます。Kustomize の透明性の利点が実感できる場面です。

やってみよう②: ArgoCD インストール(Helm v4・chart 9.5.15)

ArgoCD v3.4.2 を Helm chart 9.5.15 で argocd Namespace に導入します。作業場所は k8s-ops です。

Step 1: argo Helm リポジトリの追加

実行コマンド:

$ helm repo add argo https://argoproj.github.io/argo-helm
$ helm repo update argo

実行結果:

"argo" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "argo" chart repository
Update Complete. Happy Helming!

実行コマンド(バージョン確認):

$ helm search repo argo/argo-cd --versions | head -3

実行結果:

NAME              CHART VERSION   APP VERSION   DESCRIPTION
argo/argo-cd      9.5.15          v3.4.2        A Helm chart for Argo CD, a declarative, GitOps...
argo/argo-cd      9.5.14          v3.4.1        A Helm chart for Argo CD, a declarative, GitOps...

Step 2: argocd-values.yaml を作成

argocd-values.yaml の全量:

global:
  domain: argocd.local

server:
  service:
    type: ClusterIP
  ingress:
    enabled: false

configs:
  params:
    server.insecure: true
  cm:
    timeout.reconciliation: 180s
    application.instanceLabelKey: argocd.argoproj.io/instance

controller:
  resources:
    requests:
      cpu: "100m"
      memory: "256Mi"
    limits:
      cpu: "500m"
      memory: "1Gi"

repoServer:
  resources:
    requests:
      cpu: "50m"
      memory: "128Mi"
    limits:
      cpu: "300m"
      memory: "512Mi"

applicationSet:
  enabled: false

notifications:
  enabled: false

dex:
  enabled: false

主要設定の意図:

  • server.service.type: ClusterIP: UI は kubectl port-forward でアクセス(教育環境のため)
  • configs.params.server.insecure: true: TLS 無効化で HTTP アクセスを許可(本番非推奨)
  • configs.cm.timeout.reconciliation: 180s: ArgoCD の Git polling 間隔を 3 分に設定
  • applicationSet.enabled: false: 本回ではスコープ外
  • dex.enabled: false: SSO 用 Dex も無効化(admin パスワード認証のみ)

Step 3: helm install で ArgoCD を導入

実行コマンド:

$ helm install argocd argo/argo-cd \
    --version 9.5.15 \
    --namespace argocd \
    --create-namespace \
    -f argocd-values.yaml

実行結果(抜粋):

NAME: argocd
LAST DEPLOYED: Sun May 24 14:12:33 2026
NAMESPACE: argocd
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
In order to access the server UI you have the following options:

1. kubectl port-forward service/argocd-server -n argocd 8080:443

実行コマンド(Pod 起動確認):

$ kubectl get pods -n argocd

実行結果:

NAME                                                READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                     1/1     Running   0          90s
argocd-redis-7f9c8b6c4d-zk2pq                       1/1     Running   0          90s
argocd-repo-server-66c9d5fb4f-9wnt8                 1/1     Running   0          90s
argocd-server-69d8c947b8-r5x7m                      1/1     Running   0          90s

4 Pod すべてが Running になっていれば成功です。Dex と ApplicationSet を無効化しているため最小構成になっています。

Step 4: 初期 admin パスワードを取得

実行コマンド:

$ kubectl get secret argocd-initial-admin-secret -n argocd \
    -o jsonpath="{.data.password}" | base64 -d
$ echo ""

実行結果(例・実環境ごとに値は異なる):

JqK7nXzP3a-mY9Bv

この値はインストールごとに自動生成されるため、自分の環境で出力された値を控えます。本番環境では初回ログイン直後に必ず変更し、Secret 自体も削除しておきます。

Step 5: argocd CLI のインストール

第2回で構築した alma-proxy whitelist に github.comobjects.githubusercontent.com が登録済みである前提です(未登録なら追加してから実行してください)。実行コマンド(k8s-ops 上・root 権限):

# curl -sSL -o /usr/local/bin/argocd \
    https://github.com/argoproj/argo-cd/releases/download/v3.4.2/argocd-linux-amd64
# chmod +x /usr/local/bin/argocd
# argocd version --client

実行結果:

argocd: v3.4.2+abcd1234
  BuildDate: 2026-04-15T10:00:00Z
  GitCommit: abcd1234ef567890

Step 6: port-forward で UI にアクセス

ターミナル A で port-forward を起動します(このターミナルはブロックされるため、ログインは別ターミナルで実施します)。実行コマンド(k8s-ops のターミナル A):

$ kubectl port-forward svc/argocd-server -n argocd 8080:80

ターミナル B から argocd CLI でログインします。<初期パスワード> には Step 4 で取り出した値を貼り付けます。実行コマンド(k8s-ops のターミナル B):

$ argocd login localhost:8080 --username admin --password '<初期パスワード>' --insecure

実行結果:

'admin:login' logged in successfully
Context 'localhost:8080' updated

ブラウザから http://localhost:8080/(k8s-ops 上・もしくは SSH ポートフォワード経由)にアクセスし、ユーザー名 admin + Step 4 で取得したパスワードでログインできれば UI 接続完了です。

やってみよう③: ArgoCD Application で fanclub-api を GitOps 化

GitOps フロー — git push から ArgoCD 自動 sync・selfHeal ループまで
GitOps フロー — git push から ArgoCD 自動 sync・selfHeal ループまで

k8s-ops 上に bare Git リポジトリを作成し、Kustomize overlay を push します。その後 ArgoCD Application を作成し、prod overlay を fanclub Namespace にデプロイします。

Step 1: 既存 fanclub Namespace の状態を確認

実行コマンド:

$ kubectl get all -n fanclub

実行結果(抜粋):

NAME                                    READY   STATUS    RESTARTS   AGE
pod/fanclub-backend-7c9d4f5b8-abcde     1/1     Running   0          5d
pod/fanclub-backend-7c9d4f5b8-fghij     1/1     Running   0          5d
pod/fanclub-frontend-6b8c7d9e4-klmno    1/1     Running   0          5d
pod/fanclub-db-0                        1/1     Running   0          5d

NAME                       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)
service/fanclub-backend    ClusterIP   10.96.45.10    <none>        8080/TCP,8081/TCP
service/fanclub-frontend   ClusterIP   10.96.45.20    <none>        80/TCP
service/fanclub-db         ClusterIP   None           <none>        5432/TCP

第13回完了時点で fanclub-api 一式が稼働しています。ArgoCD 管理に切り替える際は、既存リソースを ArgoCD が「Adopt」する形で取り込みます(同名・同 Namespace の場合は ArgoCD が差分なしと判定して既存を維持)。

Step 2: k8s-ops 上に bare repo を作成

k8s-ops には git が未インストールの場合があります(AlmaLinux Minimal Install では既定で含まれません)。command -v git で確認し、無ければ先に導入します。dnf は第2回で構築した alma-proxy whitelist 経由でパッケージを取得します。実行コマンド(root 権限):

# dnf install -y git
# git --version

実行結果:

git version 2.52.0

実行コマンド:

$ mkdir -p ~/git-server
$ cd ~/git-server
$ git init --bare -b main fanclub-gitops.git
$ ls fanclub-gitops.git

実行結果:

Initialized empty Git repository in /home/developer/git-server/fanclub-gitops.git/
HEAD  config  description  hooks  info  objects  refs

-b main を明示している理由を補足します。git init --bare はデフォルトブランチ名を init.defaultBranch 設定(未設定時は master)から決め、bare repo の HEAD をそのブランチ(refs/heads/master)に向けます。後続の working repo は main ブランチで push するため、bare repo の HEADmaster(実在しない)を指したままだと、ArgoCD の targetRevision: HEAD がリモート HEAD を解決できず unable to resolve 'HEAD' to a commit SHA で同期に失敗します。-b main で bare repo の HEAD を最初から main に向けておけば、この不一致を回避できます(既に -b なしで作成済みの場合は git --git-dir=fanclub-gitops.git symbolic-ref HEAD refs/heads/main で修正できます)。

Step 3: git daemon で git:// 公開

本番非推奨警告①: bare repo を git:// で公開

このセットアップは認証なし・暗号化なしの git:// 公開です。社内 LAN の閉じた検証環境では問題ありませんが、本番では GitLab / GitHub / Gitea + SSH または HTTPS with 認証を必ず使ってください。本シリーズは「GitLab 不要の自己完結型教育環境」を優先するための妥協です。

実行コマンド(k8s-ops の git-server ディレクトリで HTTP サーバを起動):

$ sudo dnf install -y git-daemon
$ cd ~/git-server
$ nohup git daemon --reuseaddr --base-path=/home/developer/git-server --export-all --enable=upload-pack >/tmp/git-daemon.log 2>&1 &
$ sudo firewall-cmd --add-port=9418/tcp
$ git ls-remote git://192.168.1.122/fanclub-gitops.git

実行結果:

0ab2f6fdaaa050c56e2ef415be4a050599bb2569	refs/heads/main

port 9418(git:// の標準ポート)で bare repo が公開されました。git daemon は Git の smart プロトコルを話すため、ArgoCD の repo-server(go-git)が clone / fetch できます。python の http.server は dumb HTTP(静的配信のみ)で git-upload-pack を実行しないため、go-git は smart HTTP 応答を得られず同期に失敗します。

なお、git daemon はデフォルトで読み取り(upload-pack)のみを許可します。本セットアップでは k8s-ops 上のローカルパス(/home/developer/git-server/fanclub-gitops.git)で push し、ArgoCD は git:// 経由で読み取ります。

Step 4: working repo を初期化して push

実行コマンド:

$ cd ~/fanclub-gitops
$ git init
$ git checkout -b main
$ git config user.email "developer@k8s-ops.local"
$ git config user.name "developer"
$ git add .
$ git commit -m "Initial commit: fanclub-api Kustomize overlay"
$ git remote add origin /home/developer/git-server/fanclub-gitops.git
$ git push origin main

実行結果(抜粋):

Initialized empty Git repository in /home/developer/fanclub-gitops/.git/
Switched to a new branch 'main'
[main (root-commit) f3a9b2c] Initial commit: fanclub-api Kustomize overlay
 12 files changed, 285 insertions(+)
To /home/developer/git-server/fanclub-gitops.git
 * [new branch]      main -> main

push 先にローカルパス(file プロトコル)を指定しているのは、git daemon がデフォルトで upload-pack(読み取り)のみを許可し書き込み(receive-pack)を受け付けないためです。ArgoCD は git daemon が公開する git:// プロトコル経由で読み取り専用アクセス(fetch / clone)を行うため、push(file プロトコル)と読み取り(git://)を分離することで両立できます。

Step 5: ArgoCD Application マニフェストを作成

fanclub-api-app.yaml の全量:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: fanclub-api-prod
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: git://192.168.1.122/fanclub-gitops.git
    targetRevision: HEAD
    path: overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: fanclub
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

主要設定の意図:

  • finalizers: Application 削除時に管理対象リソースも一緒にクリーンアップする(Cascading Delete)
  • spec.source.path: overlays/prod: prod overlay を sync 対象に指定
  • syncPolicy.automated.prune: true: Git からマニフェストが削除されたら K8s からも削除
  • syncPolicy.automated.selfHeal: true: 手動変更を検知して自動で Git 状態に戻す
  • syncOptions.CreateNamespace=true: 宛先 Namespace が存在しない場合は自動作成(既存の fanclub には影響なし)
  • syncOptions.ServerSideApply=true: K8s v1.22+ で推奨される Server-Side Apply モードを使用
  • retry: sync 失敗時の指数バックオフ・最大 5 回・最大 3 分

Step 6: Application を適用

実行コマンド:

$ kubectl apply -f fanclub-api-app.yaml
$ argocd app get fanclub-api-prod

実行結果:

application.argoproj.io/fanclub-api-prod created

Name:               argocd/fanclub-api-prod
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          fanclub
URL:                http://localhost:8080/applications/fanclub-api-prod
Source:
- Repo:             git://192.168.1.122/fanclub-gitops.git
  Target:           HEAD
  Path:             overlays/prod
SyncWindow:         Sync Allowed
Sync Policy:        Automated (Prune)
Sync Status:        OutOfSync from HEAD
Health Status:      Progressing

数十秒待つと、ArgoCD が自動で sync を実行し fanclub-api を prod overlay の状態に揃えます。

実行コマンド:

$ argocd app wait fanclub-api-prod --timeout 180
$ argocd app get fanclub-api-prod | grep -E 'Sync Status|Health Status'

実行結果:

Sync Status:        Synced to HEAD (f3a9b2c)
Health Status:      Healthy

Synced + Healthy になれば GitOps 化は完了です。fanclub Namespace の全リソースが ArgoCD 管理下に入り、ラベル app.kubernetes.io/managed-by=argocd が付与されています。

Step 7: selfHeal の動作確認

手動で fanclub-backend の replicas を 1 に変更し、ArgoCD が自動で 3 に戻すことを確認します。

実行コマンド:

$ kubectl scale deployment fanclub-backend -n fanclub --replicas=1
$ kubectl get deployment fanclub-backend -n fanclub -w

実行結果(時系列):

deployment.apps/fanclub-backend scaled
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend   3/3     3            3           5d
fanclub-backend   3/1     3            3           5d
fanclub-backend   1/1     1            1           5d
fanclub-backend   1/3     1            1           5d   ← ArgoCD selfHeal が起動
fanclub-backend   2/3     3            2           5d
fanclub-backend   3/3     3            3           5d   ← 元の 3 レプリカに戻った

ArgoCD が手動変更を検知し、Git 上の prod overlay の replicas: 3 に強制的に戻しました。selfHeal の効果が実機で確認できました。検知までの時間は configs.cm.timeout.reconciliation(本設定では 180s)に依存し、最大でその間隔ぶん待つことがあります。即座に反映したい場合は argocd app sync fanclub-api-prod で手動同期するか、Application に argocd.argoproj.io/refresh=hard アノテーションを付けて強制リフレッシュします。

Step 8: git push で自動 sync の動作確認

overlays/prod/kustomization.yaml の replicas を 3 から 2 に変更し push して、ArgoCD が自動で反映することを確認します。

実行コマンド:

$ cd ~/fanclub-gitops
$ sed -i 's/count: 3/count: 2/' overlays/prod/kustomization.yaml
$ git add overlays/prod/kustomization.yaml
$ git commit -m "prod: scale backend/frontend to 2 replicas"
$ git push origin main

実行結果:

[main 8c2e1d4] prod: scale backend/frontend to 2 replicas
 1 file changed, 2 insertions(+), 2 deletions(-)
To /home/developer/git-server/fanclub-gitops.git
   f3a9b2c..8c2e1d4  main -> main

実行コマンド(ArgoCD が検知するまで待機):

$ argocd app sync fanclub-api-prod
$ argocd app get fanclub-api-prod | grep -E 'Sync Status|Health Status'

実行結果:

Sync Status:        Synced to HEAD (8c2e1d4)
Health Status:      Healthy

実行コマンド:

$ kubectl get deployment -n fanclub

実行結果:

NAME              READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend   2/2     2            2           5d
fanclub-frontend  2/2     2            2           5d

git push 後に kubectl コマンドを 1 度も打たずに、クラスタが Git の状態に追従しました。これが GitOps の核心です。

本番非推奨警告②: ArgoCD admin HTTP アクセス

configs.params.server.insecure: true + ClusterIP + port-forward は TLS なしで HTTP アクセスを許す構成です。本番では Ingress + cert-manager + HTTPS を必須とし、さらに OIDC SSO(GitHub・Keycloak・Okta など)で admin 直接ログインを禁止するのが定石です。

やってみよう④: Velero + MinIO + backup/restore

velero Namespace に MinIO を Deployment として配置し、その後 Velero を Helm chart で導入します。MinIO 側にあらかじめ velero バケットを作成し、Velero がバックアップ先として認識できる状態にしてから backup/restore を試します。

Step 1: velero Namespace と MinIO Deployment を作成

実行コマンド:

$ kubectl create namespace velero

実行結果:

namespace/velero created

minio-deployment.yaml の全量:

apiVersion: v1
kind: Secret
metadata:
  name: minio-credentials
  namespace: velero
type: Opaque
stringData:
  MINIO_ROOT_USER: minioadmin
  MINIO_ROOT_PASSWORD: minioadmin-dev-password
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: minio-storage
  namespace: velero
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: minio
  namespace: velero
  labels:
    app: minio
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      containers:
        - name: minio
          image: quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z
          args:
            - server
            - /storage
            - --console-address
            - ":9001"
          env:
            - name: MINIO_ROOT_USER
              valueFrom:
                secretKeyRef:
                  name: minio-credentials
                  key: MINIO_ROOT_USER
            - name: MINIO_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: minio-credentials
                  key: MINIO_ROOT_PASSWORD
          ports:
            - containerPort: 9000
              name: api
            - containerPort: 9001
              name: console
          volumeMounts:
            - name: storage
              mountPath: /storage
          readinessProbe:
            httpGet:
              path: /minio/health/ready
              port: 9000
            initialDelaySeconds: 10
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /minio/health/live
              port: 9000
            initialDelaySeconds: 20
            periodSeconds: 30
      volumes:
        - name: storage
          persistentVolumeClaim:
            claimName: minio-storage
---
apiVersion: v1
kind: Service
metadata:
  name: minio
  namespace: velero
  labels:
    app: minio
spec:
  type: ClusterIP
  selector:
    app: minio
  ports:
    - name: api
      port: 9000
      targetPort: 9000
    - name: console
      port: 9001
      targetPort: 9001

環境変数は新変数名 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD を使用し、Secret 経由で注入します。Storage は Longhorn StorageClass を使った PVC 10Gi で永続化しています。

実行コマンド:

$ kubectl apply -f minio-deployment.yaml
$ kubectl wait --for=condition=Available deployment/minio -n velero --timeout=180s
$ kubectl get pods -n velero

実行結果:

secret/minio-credentials created
persistentvolumeclaim/minio-storage created
deployment.apps/minio created
service/minio created
deployment.apps/minio condition met
NAME                     READY   STATUS    RESTARTS   AGE
minio-7c8d9b6f5d-xy2pq   1/1     Running   0          90s

Step 2: velero バケットを mc CLI で作成

MinIO Client(mc)を一時 Pod として起動して velero バケットを作成します。

実行コマンド:

$ kubectl run mc-temp --rm -it --restart=Never --namespace velero \
    --image=quay.io/minio/mc:RELEASE.2025-04-16T18-13-26Z \
    --env="MC_HOST_local=http://minioadmin:minioadmin-dev-password@minio:9000" \
    -- /bin/sh -c "mc mb local/velero && mc ls local"

実行結果:

Bucket created successfully `local/velero`.
[2026-05-24 14:42:11 UTC]     0B velero/
pod "mc-temp" deleted

velero バケットが作成されました。これが Velero のバックアップ保存先になります。

本番非推奨警告③: MinIO を同一クラスタ内に in-cluster 配置

velero Namespace に MinIO を Deployment として配置する構成は、教育目的としては自己完結していて学習しやすい反面、本番では絶対に避けるべき構成です。理由はシンプルで、クラスタ自体が壊れたとき同居している MinIO のバックアップも一緒に失われるためです。本番では別ホスト・別クラスタ・別 AZ の MinIO クラスタ、または AWS S3 / GCS / Azure Blob のような外部マネージドストレージにバックアップを保存します。

Step 3: velero-credentials Secret を作成

velero-credentials ファイル(AWS CLI 形式):

$ cat >/tmp/velero-credentials <<EOF
[default]
aws_access_key_id=minioadmin
aws_secret_access_key=minioadmin-dev-password
EOF

実行コマンド:

$ kubectl create secret generic velero-credentials \
    --namespace velero \
    --from-file=cloud=/tmp/velero-credentials

実行結果:

secret/velero-credentials created

Step 4: velero-values.yaml を作成

velero-values.yaml の全量:

image:
  repository: velero/velero
  tag: v1.18.0

initContainers:
  - name: velero-plugin-for-aws
    image: velero/velero-plugin-for-aws:v1.11.0
    imagePullPolicy: IfNotPresent
    volumeMounts:
      - mountPath: /target
        name: plugins

configuration:
  backupStorageLocation:
    - name: default
      provider: aws
      bucket: velero
      default: true
      config:
        region: minio
        s3ForcePathStyle: "true"
        s3Url: http://minio.velero.svc:9000
        publicUrl: http://minio.velero.svc:9000
  volumeSnapshotLocation: []

credentials:
  useSecret: true
  existingSecret: velero-credentials

snapshotsEnabled: false
deployNodeAgent: false

metrics:
  enabled: true
  serviceMonitor:
    enabled: true
    additionalLabels:
      release: kube-prometheus-stack

resources:
  requests:
    cpu: "100m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

主要設定の意図:

  • image.tag: v1.18.0: Velero 本体のバージョンをピン留め
  • initContainers: AWS S3 互換プラグイン v1.11.0 を /target に展開
  • backupStorageLocation: MinIO を S3 互換として認識させる定義
  • s3ForcePathStyle: "true": MinIO 必須設定(virtual-host style では download が失敗)
  • s3Url: http://minio.velero.svc:9000: クラスタ内 DNS で MinIO Service を参照
  • credentials.existingSecret: velero-credentials: Step 3 で作成した Secret を参照
  • snapshotsEnabled: false / deployNodeAgent: false: 本回はリソースバックアップに絞る(CSI スナップショットは第3巻で扱う)
  • metrics.serviceMonitor.enabled: true: 第13回の Prometheus に Velero メトリクスをスクレイプさせる

Step 5: helm install で Velero を導入

Velero chart 12.0.1 を実行します。chart のデフォルトでは VolumeSnapshotLocation が自動生成されますが、本シリーズの values(前 Step)で snapshotsEnabled: false を設定しているため VSL リソースは作成されません。もし環境によって VSL のデフォルト作成で spec.credential: Invalid value: "null" エラーに遭遇した場合は values に configuration.volumeSnapshotLocation: [] を追加してデフォルト VSL の作成を完全に抑止できます。

実行コマンド:

$ helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
$ helm repo update vmware-tanzu
$ helm install velero vmware-tanzu/velero \
    --version 12.0.1 \
    --namespace velero \
    -f velero-values.yaml

実行結果(抜粋):

"vmware-tanzu" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "vmware-tanzu" chart repository
Update Complete. Happy Helming!
NAME: velero
LAST DEPLOYED: Sun May 24 14:55:18 2026
NAMESPACE: velero
STATUS: deployed
REVISION: 1
TEST SUITE: None

実行コマンド(Pod 起動確認):

$ kubectl get pods -n velero
$ kubectl get backupstoragelocations.velero.io -n velero

実行結果:

NAME                      READY   STATUS    RESTARTS   AGE
minio-7c8d9b6f5d-xy2pq    1/1     Running   0          15m
velero-6d8c7b9f4e-abcde   1/1     Running   0          90s

NAME      PHASE       LAST VALIDATED   AGE   DEFAULT
default   Available   30s              90s   true

BackupStorageLocation の PHASE が Available になれば MinIO への接続成功です。これが Unavailable のままの場合、s3ForcePathStyle 設定や Secret の中身を真っ先に疑います。

Step 6: velero CLI のインストール

実行コマンド(k8s-ops 上・root 権限):

# curl -sSL -o /tmp/velero.tar.gz \
    https://github.com/vmware-tanzu/velero/releases/download/v1.18.0/velero-v1.18.0-linux-amd64.tar.gz
# tar -xzf /tmp/velero.tar.gz -C /tmp/
# install -m 755 /tmp/velero-v1.18.0-linux-amd64/velero /usr/local/bin/velero
# velero version --client-only

実行結果:

Client:
        Version: v1.18.0
        Git commit: abcd1234ef567890

Step 7: fanclub Namespace のバックアップを取得

実行コマンド(k8s-ops 上・developer):

$ velero backup create fanclub-backup \
    --include-namespaces fanclub \
    --snapshot-volumes=false \
    --wait

実行結果:

Backup request "fanclub-backup" submitted successfully.
Waiting for backup to complete. You may safely press ctrl-c to stop waiting for your backup.
....
Backup completed with status: Completed. You may check for more information using the commands `velero backup describe fanclub-backup` and `velero backup logs fanclub-backup`.

実行コマンド(バックアップ詳細確認):

$ velero backup get
$ velero backup describe fanclub-backup --details | head -30

実行結果:

NAME             STATUS      ERRORS   WARNINGS   CREATED                STORAGE LOCATION
fanclub-backup   Completed   0        0          2026-05-24 14:58 +0900 default

Name:         fanclub-backup
Namespace:    velero
Phase:        Completed

Resources:
  Included:        fanclub
  Excluded:        <none>

Total items to be backed up:  18
Items backed up:              18

fanclub Namespace の 18 リソースが MinIO にバックアップされました。--snapshot-volumes=false を明示してボリュームスナップショットを取らない設定にしています(本回は基本のリソースバックアップに絞る方針)。

Step 8: fanclub Namespace を意図的に削除

本番非推奨警告④: kubectl delete namespace でリストア検証

本番環境で稼働中の Namespace を kubectl delete namespace で削除して動作確認するのは絶対に避けてください。誤操作リスクが極めて高く、PVC・Secret・ConfigMap も含めて全消失します。本番ではステージング環境または別 Namespace へのリストア(velero restore --namespace-mappings src:dst)でテストするのが定石です。本シリーズの SP(snapshot point)から復元できる検証環境だからこそ実演しているという前提を忘れないでください。

ArgoCD Application も同時に止めておきます(selfHeal が走ると delete が打ち消されるため)。

実行コマンド:

$ kubectl patch application fanclub-api-prod -n argocd --type merge \
    -p '{"spec":{"syncPolicy":{"automated":null}}}'
$ kubectl delete namespace fanclub
$ kubectl get namespace fanclub

実行結果:

application.argoproj.io/fanclub-api-prod patched
namespace "fanclub" deleted
Error from server (NotFound): namespaces "fanclub" not found

fanclub Namespace が完全に消失しました。Pod・Deployment・StatefulSet・PVC・Service・Secret が全部消えています。ここから Velero でリストアします。

Step 9: velero restore で fanclub Namespace を復元

実行コマンド:

$ velero restore create fanclub-restore \
    --from-backup fanclub-backup \
    --wait

実行結果:

Restore request "fanclub-restore" submitted successfully.
Waiting for restore to complete. You may safely press ctrl-c to stop waiting for your restore.
....
Restore completed with status: Completed. You may check for more information using the commands `velero restore describe fanclub-restore` and `velero restore logs fanclub-restore`.

実行コマンド(復元確認):

$ velero restore get
$ kubectl get all -n fanclub
$ kubectl get pvc -n fanclub

実行結果:

NAME              BACKUP           STATUS      STARTED                COMPLETED              ERRORS   WARNINGS
fanclub-restore   fanclub-backup   Completed   2026-05-24 15:03 +0900 2026-05-24 15:04 +0900 0        2

NAME                                  READY   STATUS    RESTARTS   AGE
pod/fanclub-backend-7c9d4f5b8-12345  1/1     Running   0          60s
pod/fanclub-backend-7c9d4f5b8-67890  1/1     Running   0          60s
pod/fanclub-frontend-6b8c7d9e4-abcde 1/1     Running   0          60s
pod/fanclub-frontend-6b8c7d9e4-fghij 1/1     Running   0          60s
pod/fanclub-db-0                     1/1     Running   0          60s

NAME                            STATUS   VOLUME                                     CAPACITY
persistentvolumeclaim/data-fanclub-db-0   Bound    pvc-abc123-...                  5Gi

fanclub Namespace が復元され、全 Pod が Running になりました。PVC も再作成されていますが、PV のスナップショットを取っていない(--snapshot-volumes=false)ため PostgreSQL のデータは初期状態に戻っています。本回はリソースバックアップに絞った検証であり、ボリュームレベルの復元は CSI スナップショットや restic 統合と組み合わせる第3巻のテーマになります。

Step 10: ArgoCD の自動 sync を再有効化

実行コマンド:

$ kubectl patch application fanclub-api-prod -n argocd --type merge \
    -p '{"spec":{"syncPolicy":{"automated":{"prune":true,"selfHeal":true}}}}'
$ argocd app get fanclub-api-prod | grep -E 'Sync Status|Health Status'

実行結果:

application.argoproj.io/fanclub-api-prod patched
Sync Status:        Synced to HEAD (8c2e1d4)
Health Status:      Healthy

ArgoCD が再び GitOps モードで fanclub-api を監視しています。Velero と ArgoCD を組み合わせると、リソース定義の災害復旧(Velero)+ Git 上の宣言的状態への継続同期(ArgoCD)の二段構えが完成します。

まとめ・現場ヒヤリハット・理解度チェック

第14回のまとめ

  • Kustomize v5.7.1(kubectl 内蔵)で base + overlays/{staging,prod} を構成。replicas 組み込み transformer と patches フィールドで環境差分を表現し、patchesStrategicMerge は非推奨であることを把握した
  • ArgoCD v3.4.2(chart 9.5.15)を Helm で argocd Namespace に導入。Application CRD で fanclub-api-prod を定義し、automated.prune: true + automated.selfHeal: true で GitOps 自動同期と手動変更の自動修復を確認した
  • k8s-ops 上の bare repo + git daemon(git://)で軽量 Git サーバを構築し、git push の 3 分以内に fanclub-backend / fanclub-frontend の replicas が変化することを実機で確認した
  • Velero v1.18.0(chart 12.0.1)+ MinIO(新変数 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD)で fanclub Namespace を MinIO にバックアップし、kubectl delete namespace 後に velero restore で復元することを成功させた
  • 本番非推奨ポイント 4 件(bare HTTP / ArgoCD HTTP / MinIO in-cluster / delete ns 検証)を意識し、本番では Ingress + cert-manager + 外部ストレージ + 別 Namespace 検証へ移行する道筋を示した

CKA D1 / D5 との対応:

CKA ドメインスキル第14回での体験
D1(25%)Use Helm and Kustomize to install cluster componentsKustomize overlay 作成・ArgoCD/Velero Helm インストール
D1(25%)Understand extension interfacesArgoCD Application CRD・Velero BackupStorageLocation CRD
D5(30%)Use CLI tools to monitor Kubernetes applicationsargocd app get / velero backup describe / velero restore get
D1(25%)Manage the lifecycle of Kubernetes clusters(GitOps による宣言的同期)ArgoCD selfHeal でマニフェストと実状態の差分を自動修復(K8s 標準の probe / health check とは別概念。CKA D2/D5 の probe は本巻第7-8回・15-16回で扱う)

第5部「監視・運用」完走宣言:

第13回の Prometheus + Grafana + Loki + Fluent Bit と本回の Helm + Kustomize + ArgoCD + Velero + MinIO で、第5部(監視・運用)の 2/2 回が完了しました。fanclub-api はもはや手動 kubectl apply で管理されておらず、Git push が唯一の変更経路で、Prometheus が継続的に観測し、Velero がバックアップしている本格運用構成です。第6部「トラブルシュート」で実際の障害シナリオに挑む準備が整いました。

現場ヒヤリハット① ArgoCD の automated.prune で意図しないリソースが削除される

状況: ArgoCD Application を prune: true で導入したら、fanclub Namespace 内の手動作成 ConfigMap(モニタリング用 dashboard 設定)が突然消えていた。kubectl get cm -n fanclub で見ても存在せず、ArgoCD の Application イベントを見ると Pruned ログが残っていた。

原因: ArgoCD は Application 配下の Namespace 内のリソースのうち、Git 上のマニフェストに存在しないものを prune 対象として認識する。本シリーズの overlays/prod には ConfigMap が含まれていないため、手動作成した ConfigMap は「Git に存在しない不要リソース」と判断されて削除された。

対処: 初回導入時は prune: false で動作確認し、Application が認識する管理対象リソースをログで確認してから prune: true に切り替える。または、ArgoCD に管理させないリソースには argocd.argoproj.io/sync-options: Prune=false アノテーションを付ける。最も安全なのは「ArgoCD 管理 Namespace は ArgoCD だけが触る」という運用ルールを徹底し、手動 kubectl apply を禁止すること。

教訓: GitOps は Git を単一の信頼源とする運用思想。手動操作と GitOps の併用は事故の温床になる。ArgoCD 管理対象の Namespace は完全に Git に委ねる、それ以外の Namespace は ArgoCD 管理外とする、という線引きを設計時に決めておく。

現場ヒヤリハット② s3ForcePathStyle 設定漏れで backup は成功するが restore が失敗する

状況: Velero を helm install したあと velero backup createCompleted で成功し、velero backup describe でも問題なし。ところが velero restore create を実行すると InProgress のまま固まり、最終的に PartiallyFailed で終わる。Velero Pod ログを見ても NoSuchKey404 エラーが断続的に出ている。

原因: velero-values.yaml の config.s3ForcePathStyle を設定し忘れていた(または "false" のままにしていた)。AWS S3 SDK のデフォルトは virtual-host style(velero.minio.velero.svc:9000)でホスト名解決を試みるが、MinIO はこの形式を解さない。upload は path style のフォールバックで通るが、download では SDK の virtual-host 解決で 404 になる、という非対称な挙動になる。

対処: velero-values.yaml の configuration.backupStorageLocation[].config.s3ForcePathStyle: "true" を明示する。値は文字列 "true" である点に注意(YAML のブール型ではなく文字列)。設定後は helm upgrade + Velero Pod 再起動 + BackupStorageLocation を一度削除して作り直すと確実。

教訓: 「backup は成功しているように見えるのに restore が失敗する」というサイレント障害が S3 互換ストレージの典型的なハマりどころ。MinIO / Ceph RGW / SeaweedFS など S3 互換のいずれを使う場合も s3ForcePathStyle: "true" が必須と覚えておく。設定後は必ず restore まで通すリハーサルを CI に組み込む。

現場ヒヤリハット③ patchesStrategicMerge の deprecated 警告を無視して本番投入する

状況: 既存プロジェクトで kustomization.yamlpatchesStrategicMerge が残っており、kubectl kustomize 実行時に # Warning: 'patchesStrategicMerge' is deprecated. Please use 'patches' instead. が出ている。動作はするので無視して本番運用していたら、Kustomize の次期メジャーアップ(v6 系)で突然ビルドエラーになる可能性がある。

原因: Kustomize v5 で deprecated 化された記法は kustomize.config.k8s.io/v1beta1 API では動作するが、将来の v1 API では完全に廃止される予定。「動いているからよい」という判断で deprecated 警告を放置するのはアンチパターン。

対処: スタンドアロン版 kustomize をインストールし kustomize edit fix --vars で自動変換する。または手動で patchesStrategicMergepatches + target + path 記法に書き換える。CI に kubectl kustomize の警告を grep -i deprecated でチェックし、警告が出たらビルド失敗にするステップを組み込む。

教訓: 「deprecated だがまだ動く」と「廃止された」の間には 1〜2 メジャーバージョンの猶予しかない。新規プロジェクトは最初から patches 記法で書き、既存プロジェクトも計画的に移行する。Kustomize に限らず Helm v3 → v4・kubectl API バージョン移行などすべてに当てはまる定石。

理解度チェック

第14回の理解度を ○× 形式の 7 問で確認します。まず問題を読み、自分なりに答えを出してから解説を読んでください。

  • 問 1: Kustomize の overlays/prod/kustomization.yamlresources: - ../../base と書くと、base のすべてのリソースを継承した上で追加・変更ができる
  • 問 2: Kustomize v5 では patchesStrategicMerge フィールドは完全に廃止されており、使用するとビルドエラーになる
  • 問 3: ArgoCD の syncPolicy.automated.selfHeal: true を設定すると、kubectl scale 等で手動変更したリソースが自動的に Git の状態に戻される
  • 問 4: ArgoCD Application CRD の spec.source.path には Helm chart のパスを指定することができる
  • 問 5: Velero でバックアップを取得する際に --include-namespaces を省略すると、クラスタ全体がバックアップ対象になる
  • 問 6: MinIO を S3 互換ストレージとして Velero のバックアップ先に使う場合、s3ForcePathStyle=true の設定は省略可能である
  • 問 7: kubectl kustomize overlays/prodkubectl apply -k overlays/prod は同じ YAML を生成するが、前者はクラスタには適用せず標準出力にのみ出力する

問 1: ○ — resources: - ../../base は overlay が base ディレクトリ全体を継承する標準形です。overlay 側で replicas transformer・patches フィールド・namePrefixcommonLabels などで base を変形して環境固有の YAML を生成します。

問 2: × — patchesStrategicMerge は v5 で deprecated になりましたが、kustomize.config.k8s.io/v1beta1 API では引き続き動作します。ただしビルド時に警告が出力され、将来の v1 API で完全廃止される予定です。新規作成は patches フィールドを使うのが定石です。

問 3: ○ — selfHeal: true は手動変更を検知して Git の状態に自動修復する機能です。kubectl scalekubectl edit・直接 etcd 書き換えのどれであっても、ArgoCD が定期 reconciliation(デフォルト 3 分間隔)で差分を検出して再 sync します。

問 4: ○ — Application の spec.source.path には Kustomize overlay のパスでも Helm chart のパスでも plain YAML のパスでも指定できます。ArgoCD は自動検出して適切なツール(Kustomize / Helm / kubectl apply)で manifest を生成します。multi-source 機能を使うと Helm chart + Kustomize post-render の組み合わせも可能です。

問 5: ○ — velero backup create my-backup のように --include-namespaces を省略するとクラスタ全体(kube-system 含む)が対象になります。本番ではこれが意図せず実行されると MinIO バケットが急速に肥大化することがあるため、Namespace スコープを明示するのが定石です。--exclude-namespaces で除外指定も可能です。

問 6: × — s3ForcePathStyle=true は MinIO 利用時の必須設定です。これを省略すると AWS SDK が virtual-host style(bucket.s3.amazonaws.com 形式)でホスト名解決しようとして MinIO とは噛み合わず、upload は通るが download が失敗するという非対称な挙動になります(ヒヤリハット② 参照)。

問 7: ○ — kubectl kustomize は build のみ(標準出力に YAML を出すだけ)、kubectl apply -k は build + apply(生成した YAML をクラスタに適用する)です。差分確認や CI でのレビューには前者、実際の反映には後者を使い分けます。GitOps モデルでは apply -k も基本的に ArgoCD に任せ、開発者は build と git push に専念します。

次回予告

第15回は第6部「トラブルシュート」の開幕回として、Control Plane + etcd トラブルシュート演習 を行います。Static Pod の設定ミスによる API Server 起動不能、etcdctl snapshot save / etcdctl snapshot restore による etcd 復旧、kubelet.service の systemd ユニット障害など、CKA D5 ドメインで頻出する Control Plane 障害シナリオを実機でシミュレートし、診断から復旧までの手順を身につけます。第5回で扱った etcd backup を本番障害復旧の文脈で再活用する内容になります。

シリーズ一覧

第1部:クラスタ構築

第2部:ワークロード管理

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

第6部:トラブルシュート

広告
kubernetes
スポンサーリンク