urchin_hat
URCHIN_HAT / TECHNICAL FIELD NOTES

TECH MEMO

SRE、クラウド、AI、DevOps。
観測したこと、試したこと、考えたことを記録する技術アーカイブ。

ウニの帽子をかぶり、ノートに技術メモを書くキャラクター
OBSERVATION LOG 36 ENTRIES
# FIELD NOTES
ホームラボでArgo CDを試す。K3sで学ぶGitOps TECH MEMO / FIELD NOTE

ホームラボでArgo CDを試す。K3sで学ぶGitOps

はじめに

前回の記事「ホームラボでK3sを構築しよう!RHEL・Podman・Ansibleで作るKubernetes環境」では、RHEL 10上に軽量Kubernetesディストリビューションである「K3s」を導入し、手元の作業PCから操作できるクラスタ環境を構築しました。

Kubernetesクラスタが動いたので、次はGitOpsを試したくなりました。Gitに記録した定義をクラスタの期待状態とし、実際の状態をそこへ近づける運用です。今回は、Kubernetes向けのGitOps CDツールであるArgo CDを使います。

パブリッククラウドに複数の検証環境を作るとコストも気になります。まずは自宅のRHEL 10上で動かしているK3sを使い、Argo CDの導入からサンプルアプリの同期までを確認しました。

この記事で確認すること

  • 手元MacからリモートK3sクラスタを操作するセットアップ
  • Argo CD構築時に直面した「K3s特有のポート競合」「CRDサイズ制限」「Web UIアクセス」のトラブルシューティング記録
  • サンプルアプリで確認する、Git管理のリソースと手動作成したリソースの違い
  • Deploymentを手動でスケールしたときの自己修復(Self-Healing)
  • 本番運用へ発展させるときに考えたいCI検証、Secret管理、マルチクラスタ設計、ロールバック

検証環境のアーキテクチャ

本検証では、操作のたびにサーバーへSSHログインするのではなく、手元のMacのkubectlargocd CLIからリモート操作します。

検証環境のアーキテクチャ

Argo CD の構築手順と初期セットアップ

1. Namespace の選定理由(argocd vs argo

Namespaceは公式のインストール手順に合わせて argocd とします。

  • 公式マニフェストはargocd Namespaceを前提としています。別の名前にインストールする場合は、ClusterRoleBindingが参照するServiceAccountのNamespaceも変更する必要があります。
  • Argo Workflowsなど、他のArgoプロジェクトと後から同居させる場合にも区別しやすくなります。

2. マニフェストの適用と「CRDサイズ問題」の回避

手元Macのターミナルからマニフェストを適用します。

# 1. Namespace の作成
kubectl create namespace argocd

# 2. Argo CD 公式マニフェストの適用
kubectl apply -n argocd --server-side --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

stableは特定のバージョンではなく、リリースに合わせて移動します。今回は検証のため使いますが、長期運用する環境ではv3.5.1のように検証済みのバージョンへ固定します。 手順の最新版は、Argo CD公式の「Getting Started」で確認できます。

3. 初期管理者パスワードの取得

Argo CDの初回起動時、adminユーザーの初期パスワードがSecretに保存されます。Secret内の値はBase64エンコードされているだけで、それ自体は暗号化ではありません。

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
echo
  • ユーザー名: admin
  • パスワード: (上記コマンドで出力されたランダム文字列)

ログイン画面

4. Argo CD CLI のインストール (MacPorts / 手元Mac)

手元Macで Argo CD を操作するための CLI を導入します。

# MacPorts での導入
sudo port install argocd

# または公式バイナリを直接配置する場合(Apple Siliconの例)
VERSION=v3.5.1  # 導入したArgo CDに合わせて変更
curl -sSL -o argocd \
  "https://github.com/argoproj/argo-cd/releases/download/${VERSION}/argocd-darwin-arm64"
sudo install -m 0555 argocd /usr/local/bin/argocd
rm argocd

直接ダウンロードする場合は、MacがApple SiliconかIntelかを確認し、サーバーと互換性のあるCLIバージョンを選びます。必要に応じてリリースページのcli_checksums.txtや署名も確認します。公式手順は「CLI Installation」にあります。

K3s上のArgo CDへアクセスする

リモートの K3s サーバー上で Argo CD を動かす場合、Web UI や公開サービスのルーティング設計に注意が必要です。

1. K3s ServiceLB と Traefik のポート競合問題

K3sには標準でServiceLBとTraefik Ingress Controllerが組み込まれています。TraefikのLoadBalancer ServiceのためにServiceLB Podがノードの80 / 443番ポートを使っています。シングルノード環境でArgo CD ServerのServiceも同じポートのtype: LoadBalancerに変更すると、ポートが競合し、新しいServiceLB Podをスケジュールできません。詳細はK3s公式の「Networking Services」に記載されています。

Warning  FailedScheduling  default-scheduler  0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.

2. 今回採用した構成

管理画面をLANへ常時公開する必要はないと判断し、次のように分けました。

  1. Argo CD 管理画面: LANへ公開せず、必要なときだけ手元Macから kubectl port-forward 経由でアクセス。
  2. デプロイしたWebアプリケーション: 今回はサーバーの firewalld(80/tcp) を開放し、K3sの Traefik Ingress 経由でLAN公開。TLSは未設定のため、HTTPSではなくHTTPで確認します。
# Argo CD UI にアクセスするためのポートフォワード
kubectl port-forward svc/argocd-server -n argocd 8080:443

ブラウザで https://localhost:8080 を開きます。kubectl port-forwardはデフォルトでlocalhostだけをlistenします。Argo CDの初期証明書はブラウザから信頼されていないため、初回は警告が表示されます。

サンプルアプリ(Guestbook)で体験する GitOps

1. Application カスタムリソースの定義

Argo CD では、デプロイ対象の Git リポジトリと同期ルールを Application リソースとして宣言します。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: guestbook
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

targetRevision: HEADは公式サンプルに合わせた検証用の指定です。本番環境ではブランチ、タグ、コミットSHAのどれをリリース単位とするかを決めて運用します。

  • source.repoURL / path: デプロイ対象の Kubernetes マニフェストが存在する Git パス。
  • destination: デプロイ先のクラスタと Namespace。
  • automated.prune: true: Git からマニフェストが削除されたら、クラスタ上のリソースも自動削除する。
  • automated.selfHeal: true: クラスタ上の管理対象が変更されたら、Gitから生成した期待状態へ戻す。

これを kubectl apply すると、Argo CD が Git を読み込み、guestbook-ui の Deployment / Service を自動プロビジョニングします。

2. LAN公開用 Ingress の作成

デプロイされた guestbook-ui を LAN内の別PCやスマホからアクセスできるように Ingress を作成します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: guestbook-ingress
  namespace: default
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: guestbook-ui
            port:
              number: 80

デプロイされたPod
Podの詳細画面
これでブラウザから http://192.168.1.xxx を開くだけで、ゲストブックのWeb画面が表示されます。

Git管理のリソースと、手動で作ったリソース

1. 「メッセージ送信が動かない?」の調査

Web画面から文字を入力して「Submit」ボタンを押しても、メッセージが画面に追加されない事象が発生しました。 Pod 内の PHP コード(guestbook.php)を調査したところ、以下の処理が判明しました:

if ($_GET['cmd'] == 'set') {
  $client = new Predis\Client([
    'scheme' => 'tcp',
    'host'   => 'redis-leader',
    'port'   => 6379,
  ]);
  $client->set('guestbook', $_GET['value']);
}

原因: アプリはバックエンドとして Redis(redis-leader / redis-follower)を必要としていましたが、Argo CD 公式デモリポジトリの guestbook/ ディレクトリには UI 側のマニフェストしか含まれていませんでした。

2. 手動作成したRedisがArgo CDのツリーに出ない理由

そこで手元から直接 kubectl apply で Redis の Deployment / Service を起動したところ、アプリは正常に動作するようになりました。

しかし、Guestbook ApplicationのリソースツリーにRedisは表示されませんでした。

[ GitHub リポジトリ (argocd-example-apps/guestbook) ]
  ├── guestbook-ui-deployment.yaml ──┐
  └── guestbook-ui-svc.yaml        ──┼─► Argo CD が追跡・画面に描画(管理対象)
[ 手動で kubectl apply したリソース ]
  ├── redis-leader (Deployment / Service)   ──► Guestbook Applicationの管理対象外
  └── redis-follower (Deployment / Service) ──► Guestbook Applicationの管理対象外

Argo CDでいうOrphaned Resources Monitoringは、同じNamespaceにある「どのApplicationにも属さないトップレベルのリソース」を検出する別の機能です。デフォルトで無条件に警告されるわけではなく、AppProject側で機能を有効にする必要があります。有効化すると、フィルターからこれらのリソースを確認できます。 設定方法はArgo CD公式の「Orphaned Resources Monitoring」で確認できます。

3. この実験で分かったこと

  1. 「Git に書かれている定義こそがクラスタのあるべき姿(Desired State)である」
  2. クラスタ上にリソースが存在しても、そのApplicationのマニフェストから生成されたものでなければ、Applicationの管理対象にはならない。
  3. 必要なリソースをGit管理に含めると、変更履歴を追いやすくなり、同じ構成を再現しやすくなる。

自己修復(Self-Healing)の動作確認

Argo CD の自己修復機能を検証するため、わざと手動でクラスタの状態を変更してみました。

# 手動で Pod 数を 0 にスケールダウン
kubectl scale deployment guestbook-ui --replicas=0 -n default

結果

  1. クラスタ内の Pod が削除され、一瞬 OutOfSync(非同期)ステータスになる。
  2. 直後に Argo CD のコントローラーが「Git の定義(replicas: 1)とクラスタの状態が一致していない」ことを検知。
  3. selfHeal: trueの設定により再同期が行われ、レプリカ数が1に戻ってPodが再作成される。

今回のような管理対象フィールドの手動変更であれば、Gitから生成した状態へ戻ることを確認できました。ただし、すべての障害から自動復旧できるわけではありません。イメージ自体の不具合、データの損失、クラスタ障害などは別の対策が必要です。

本番運用へ広げるなら考えたいこと

ここからは今回の検証範囲外ですが、業務で使うなら追加で検討したい点をまとめます。必要な構成は組織の規模やクラウド、セキュリティ要件によって変わります。

GitOps / プラットフォームエンジニアリング設計論

1. CI での Dry-Run・静的解析(マニフェスト品質の担保)

壊れたマニフェストがそのまま同期されないよう、PRの段階でレンダリングとスキーマ検証を行います。GitHubを使っているなら、GitHub Actionsに組み込めます。

CI に組み込むべき検証ステップ

  1. KustomizeのレンダリングとKubernetesスキーマの検証:
    kustomize build overlays/prod | \
      kubeconform -strict -summary -kubernetes-version 1.36.0
    
    実際のクラスタに合わせて-kubernetes-versionを指定します。CRDを含む場合は、対応するスキーマも別途準備が必要です。
  2. サーバーサイドDry-Run (kubectl --dry-run=server): 実際にクラスタに送信した場合に Admission Webhook や CRD のバリデーションを通るかテスト。
    kubectl apply --dry-run=server -k overlays/prod/
    
  3. セキュリティ・ポリシー検証 (conftest / kyverno / trivy): 「root 権限でコンテナを実行していないか」「リソース制限(CPU/Memory requests/limits)が記載されているか」を自動チェック。
  4. PR上での差分確認: Argo CD CLIのargocd app diff APPNAME --local <repository-root>などで、ローカルの変更と稼働中のApplicationを比較できます。CIから実行するなら、Argo CDへの認証方法と権限も設計します。

2. Secret(機密情報)管理:GKE / EKS とクラウドマネージドの連携

GKEやEKSでは、AWS Secrets ManagerGoogle Cloud Secret Managerと**External Secrets Operator (ESO)**を組み合わせる方法が候補になります。これは一例であり、既存のシークレット管理基盤や要件に合わせて選びます。

[ AWS Secrets Manager / GCP Secret Manager ]
   │ (IAM認証: EKS IRSA / GKE Workload Identity による一時トークン認証)
[ External Secrets Operator (ESO) ]
   │ (Kubernetes Secret を自動生成)
[ 各 Pod (アプリケーション) ] ◄── (Argo CD が Git からデプロイ)

なぜこの構成が業務で選ばれるのか?

  1. Podへの長期クレデンシャル配布を避ける:
    • GKE: Workload Identity Federation for GKE
    • EKS: IRSA (IAM Roles for Service Accounts) または EKS Pod Identity
    • KubernetesのServiceAccountとクラウド側の権限を連携させ、長期的なアクセスキーをPodへ配布せずに認証できます。
  2. Git に平文の機密情報を置かない:
    • Git に書くのは「AWS / GCP のどのシークレット名(例: prod/db/password)を参照するか」という定義(ExternalSecret CRD)だけです。
  3. 暗号化・監査ログ・ローテーション:
    • クラウド側の暗号化や監査ログを利用できます。ローテーションはシークレットの種類やサービスごとに仕組みが異なるため、ESOの再同期間隔も含めて設計します。

3. プラットフォームエンジニアリングと「共通基盤テンプレート化」

基盤チームは、開発チームごとに個別マニフェストを作らせるのではなく、「監視」「ログ」「証明書」「Ingress」が最初から組み込まれた標準テンプレート(Golden Path / 舗装された道)を提供します。

  • EKS の場合: AWS Load Balancer Controller、Karpenter、CloudWatch / Datadog
  • GKE の場合: GKE Ingress / Gateway API, Managed Prometheus, Cloud Logging
  • Argo CD ApplicationSet による自動プロビジョニング:
    • Git Generator: Gitのapps/配下に追加されたディレクトリを検出し、テンプレートに従ってApplicationを生成。監視設定も配布したい場合は、それもテンプレート側に定義します。
    • Cluster Generator: Argo CDに登録済みのクラスタをラベルなどで選別し、対象クラスタごとにApplicationを生成。

4. 環境分離:Kustomize の base / overlays 戦略

環境分離の方法の一つとして、1つのリポジトリ内でKustomizeのbaseoverlaysを分ける構成があります。ブランチで分ける構成も含め、承認フローとリポジトリの責任範囲に合わせて選びます。

my-service/
├── base/                   # ① 全環境共通(Deployment, Service のひな形)
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/               # ② 環境ごとの「差分」のみをオーバーレイ
    ├── dev/
    │   ├── kustomization.yaml   (replicas: 1, host: dev.example.com, ENV: dev)
    │   └── patch-env.yaml
    ├── stg/
    │   └── kustomization.yaml   (replicas: 2, host: stg.example.com, ENV: stg)
    └── prod/
        └── kustomization.yaml   (replicas: 5, host: app.example.com, ENV: prod, 高リソース)

5. マルチクラスタ(Hub & Spoke)アーキテクチャ

チームや環境ごとにクラスタを分ける場合は、中央のArgo CDから複数クラスタへ同期するHub & Spoke構成も選択肢になります。

               ┌── [ 管理クラスタ (Hub) ] ──┐
               │    Argo CD / Vault / CI   │
               └─────────────┬─────────────┘
                             │ (最小権限でリモート同期)
     ┌───────────────────────┼───────────────────────┐
     ▼                       ▼                       ▼
[ Dev クラスタ ]        [ Stg クラスタ ]        [ Prod クラスタ ]
 (Spoke / 開発環境)      (Spoke / 検証環境)      (Spoke / 本番環境)
  • 日常のデプロイのために、開発者へ本番クラスタの強いkubeconfigを配布せずに済みます。障害対応に必要なアクセスは、別途RBACや時限付き権限で設計します。
  • 中央のArgo CDが侵害されると影響範囲が広がるため、登録先クラスタの権限、AppProject、ネットワーク制御も重要です。

6. リリースとロールバック

以下は承認フローを設ける場合の一例です。

  1. ブランチ運用:
    • 日常の開発は main ブランチへマージ。
    • 本番リリース時は mainproduction ブランチ への Pull Request を作成。
    • PR 上で差分を確認し、リーダーが Approve してマージすることで リリース承認ゲート とする。
  2. イミュータブルなイメージ参照:
    • latestのように中身が変わるタグではなく、一意のバージョンタグまたはイメージダイジェストをマニフェストに記録する。
  3. ロールバック:
    • 問題のある変更をrevertするPRを作り、マニフェストのイメージ参照や設定を前の状態へ戻す。
    • マージ後にArgo CDがその変更を同期する。復旧時間はGitの検出間隔、Webhook、同期設定、イメージ取得時間などに依存するため、事前に計測しておく。

まとめ

ホームラボのシングルノードK3sでも、Argo CDがGitとクラスタの差分を検出し、期待状態へ戻す流れを確認できました。一方で、可用性、権限分離、Secret管理、承認フローなどは今回の検証に含まれません。本番環境では、これらを別途設計する必要があります。

今回の重要ポイントのおさらい

  1. Argo CD のセットアップ: 公式マニフェスト全体をServer-Side Applyで適用し、UIには必要なときだけPort Forwardで接続する。
  2. Git管理の範囲: 継続して必要な宣言的リソースはGitで管理し、日常運用でのクラスタへの直接変更を避ける。
  3. 本番運用へ向けた課題: CIでのDry-Runとバリデーション、環境分離、イミュータブルなイメージ参照、revert手順を用意する。

次のステップ

次は自分用のマニフェストリポジトリを作り、GitHub Actionsで検証した変更をArgo CDが同期するところまで試します。

@urchin_hat
WRITTEN BY@urchin_hat

10月に向けて充電期間中

← BACK TO MEMO