本文へ移動
購読
GUIDE#devtools更新 2026.09.24

Argo CDでGitOpsを始める:k3sにアプリをデプロイして差分を自動同期する

Argo CD v3.5をk3sに入れ、Gitに置いたマニフェストを自動同期するまでの手順。CRD適用エラーの回避、prune・selfHealの意味、Helmチャートの扱い、運用の注意まで。

level
中級
time
約40分
stack
Argo CD 3.5 / k3s 1.36 / Git(GitHubやGitea)
author
@ryoma_t
// 前提条件
k3sなどのKubernetesクラスターにkubectlで接続できること / マニフェストを置くGitリポジトリを用意できること

Argo CD(ArgoCD)とは、Gitリポジトリに置いたKubernetesのマニフェストを「あるべき状態」とみなし、クラスターの実際の状態をそれに合わせ続けるGitOps用の継続的デリバリーツールです。デプロイの作業が「Gitにコミットする」ことに置き換わり、誰かが手で変えた設定も自動で元に戻せます。

ここでは執筆時点の最新版Argo CD v3.5.3を、k3s v1.36.4の1ノードクラスターに入れ、自前のGitサーバーに置いたマニフェストから小さなWebアプリを自動で同期させるところまでを、使い捨ての検証環境で確かめました。インストールで出たエラー、手で変えたレプリカ数が戻るまでの時間、メモリの使用量も実測値で載せています。

この記事でわかること

  • Argo CDとGitOpsの仕組み、CIからkubectl applyする方式との違い
  • k3sへのインストール手順と、そのままでは失敗するCRDの適用エラーの回避策
  • Applicationマニフェストの書き方と、自動同期・prune・selfHealの意味
  • Helmとは何か、HelmチャートをArgo CDで扱うときの注意
  • 初期パスワード・公開範囲・メモリ量など、自宅クラスターで運用するときの注意

Argo CDとは:GitOpsとpush型デプロイの違い

CIのジョブからkubectl applyでデプロイする方式は、CI側がクラスターへの書き込み権限を持ち、変更を押し込む「push型」です。一方のArgo CDはクラスターの中で動き、Gitを定期的に見に行って差分を取り込む「pull型」で、クラスターの認証情報をCIに渡す必要がありません。

観点push型(CIからkubectl)pull型(Argo CD)
クラスターの認証情報CIのシークレットに置くクラスターの外に出さない
誰かが手で変えた設定次のデプロイまで放置される差分として検出し、設定しだいで自動で戻す
今の状態の確認CIのログをたどる画面とCLIで「Gitとの差分」として見える
導入の手間ワークフローに数行足すだけArgo CD自体の運用(更新・認証・メモリ)が増える

中身は複数のコンポーネントに分かれています。repo-serverがGitを取得してマニフェストを組み立て(素のYAML、Kustomize、Helmに対応)、application-controllerがクラスターの状態と比べて同期し、argocd-serverが画面とAPIを提供します。Gitの変更は既定で2分ごと(最大1分のゆらぎ付き)に確かめに行くポーリングで検出され、間隔はargocd-cmのtimeout.reconciliationで変えられます(執筆時点の公式ドキュメントの既定値)。すぐに反映させたい場合は、GitHubなどからArgo CDのwebhookを呼ぶ設定を足します。

ArgoCDのインストール:k3sに入れて画面を開く

前提は、kubectl get nodesが通るk3sのクラスターです。まだなければk3sで作る自宅Kubernetesクラスタの手順で用意します。本体は専用の名前空間argocdに、公式のマニフェスト1枚で入ります。

terminal
kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.5.3/manifests/install.yaml
kubectl -n argocd get pods
出力(約100秒後)
NAME                                                READY   STATUS    RESTARTS      AGE
argocd-application-controller-0                     1/1     Running   0             98s
argocd-applicationset-controller-84549767db-42cbg   1/1     Running   0             98s
argocd-dex-server-6cc5dd7c9d-8pfp9                  1/1     Running   2 (71s ago)   98s
argocd-notifications-controller-57d4c66f69-w9kbh    1/1     Running   0             98s
argocd-redis-c55679569-vlhtf                        1/1     Running   0             98s
argocd-repo-server-7f9fdfbb74-w854z                 1/1     Running   0             98s
argocd-server-5f785dd555-wtht7                      1/1     Running   0             98s

--server-side --force-conflictsを付けているのには理由があります。付けずに普通のkubectl applyをすると、検証では次のエラーでApplicationSetのCRDだけが作られませんでした。

出力(--server-side なしの kubectl apply)
The CustomResourceDefinition "applicationsets.argoproj.io" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes

通常の(クライアント側の)kubectl applyは、適用した内容を丸ごとkubectl.kubernetes.io/last-applied-configurationという注釈に保存します。ApplicationSetのCRDは定義が大きく、注釈の上限である262144バイトを超えてしまいます。サーバー側で差分を管理するserver-side applyはこの注釈を使わないため、上限に当たりません。URLは版を固定したv3.5.3のものにしており、stableのURLにすると適用した時点の最新版が入るので、再現性が必要な環境では版を明示します。

初期パスワードはSecretに入っています。画面はクラスターの外に公開せず、手元のPCからポートフォワードで開きます。

terminal
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d; echo
kubectl -n argocd port-forward svc/argocd-server 8080:443
# ブラウザで https://localhost:8080 を開き、ユーザー admin と上のパスワードでログイン

証明書は自己署名なので、ブラウザの警告を確認して進みます。ログインしたら、画面のUser Infoかargocd account update-passwordでパスワードを変え、使い終えたargocd-initial-admin-secretはkubectl -n argocd delete secret argocd-initial-admin-secretで消します(メニュー名は執筆時点のもの)。

検証のために、k3sを本番サーバー上のDockerの特権コンテナで動かすのは避けてください。特権コンテナでは/proc/sysがホストのものになり、kubeletが起動時にホストのカーネルパラメータ(vm.overcommit_memory=1、kernel.panic=10、kernel.panic_on_oops=1)を書き換えます。筆者の環境でも、検証用コンテナを動かしただけでホストの値が変わりました。試すなら専用のVMか検証用のマシンを使い、どうしてもコンテナで動かす場合はk3sに--kubelet-arg=protect-kernel-defaults=trueを渡します。

Gitリポジトリにマニフェストを置く

デプロイするものは、リクエストの内容をそのまま返す小さなWebアプリ(traefik/whoami)にします。リポジトリhomelab-appsのapps/whoami/に、DeploymentとServiceの2ファイルを置いてpushします。

apps/whoami/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  labels:
    app: whoami
spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami:v1.12.0
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
            limits:
              memory: 32Mi
apps/whoami/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  selector:
    app: whoami
  ports:
    - port: 80
      targetPort: 80

公開リポジトリなら、Argo CD側の設定はこれだけで読めます。非公開リポジトリの場合は、読み取り専用のデプロイキーを作り、argocd repo addか、ラベルargocd.argoproj.io/secret-type: repositoryを付けたSecretで接続情報を登録します。書き込み権限のあるトークンを渡す必要はありません。

Applicationマニフェストで自動同期する

Argo CDに「どのGitのどのディレクトリを、どのクラスターのどの名前空間へ同期するか」を伝えるのが、Applicationというカスタムリソースです。これ自体もYAMLなので、同じリポジトリに入れておくとArgo CDの設定もGitで履歴が追えます。

whoami-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: whoami
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/USER/homelab-apps.git
    targetRevision: main
    path: apps/whoami
  destination:
    server: https://kubernetes.default.svc
    namespace: whoami
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
terminal
$ kubectl apply -f whoami-app.yaml
application.argoproj.io/whoami created
$ kubectl -n argocd get applications
NAME     SYNC STATUS   HEALTH STATUS
whoami   Synced        Healthy

検証(リポジトリは自前のGitea)では、applyから45秒以内にSynced・Healthyになりました。destination.serverのhttps://kubernetes.default.svcは「Argo CDが動いているのと同じクラスター」を表します。syncPolicyの各項目は、有効にしたときとしないときで次のように動作が変わります。

設定有効にしたとき書かないとき
automatedGitの変更を検出したら自動で同期する差分はOutOfSyncと表示されるだけで、画面やCLIで手動同期する
prune: trueGitから消したリソースをクラスターからも削除する消したリソースはクラスターに残り続ける
selfHeal: trueクラスター側を手で変えても、Gitの状態に戻す次にGitが変わるまで手作業の変更が残る
CreateNamespace=true宛先の名前空間がなければ作る名前空間を先に作っておく必要がある

prune: trueは、Gitからファイルを消すとクラスターのリソースも消える、という強い設定です。リポジトリのディレクトリ名を変えたつもりがpathの指す場所が空になり、アプリがまるごと消えることもあります。最初はpruneを付けずにOutOfSyncの表示で差分を確かめ、動きに慣れてから有効にするのが安全です。

差分が自動で戻ることを確かめる:selfHeal

GitOpsの効果が一番わかりやすいのは、クラスターを手で変えたときです。Gitではreplicas: 2のDeploymentを、kubectl scaleで5に増やしてみました。

terminal
$ kubectl -n whoami scale deploy whoami --replicas=5
deployment.apps/whoami scaled
$ kubectl -n whoami get deploy whoami -o jsonpath='{.spec.replicas}'; echo
2

5秒後に確認した時点で、レプリカ数はGitの2に戻っていました。selfHealはクラスター側の変更を監視しているため、Gitのポーリング間隔を待たずに戻します。障害対応で一時的に台数を増やしたい場合は、Gitのreplicasを変えてpushするか、そのアプリのselfHealを一時的に外します。「手で直せない」ことが、そのまま「誰がいつ何を変えたかがGitに残る」ことになります。

Gitの側を変えた場合は、次のポーリング(既定で2〜3分以内)で差分が検出されて同期されます。待てないときは画面のRefreshボタンか、argocd CLIのargocd app get whoami --refreshで即座に確認させられます。同期の履歴はargocd app history whoamiで、コミットのハッシュとともに一覧できます(CLIはGitHubのリリースページから入手、執筆時点)。

Helmとは:チャートをArgo CDで使うときの注意

Helmとは、Kubernetesのマニフェスト一式をテンプレートと設定値(values.yaml)にまとめた「チャート」として配布・導入するパッケージマネージャーです。helm installは、テンプレートを展開してクラスターに適用し、その記録をリリースとして残します。cert-managerやGrafana、監視用のPrometheusなど、多くのソフトウェアがHelmチャートで配布されています。

Argo CDはHelmチャートも同期元にできますが、内部ではチャートを展開して得たマニフェストを適用するだけで、Helmのリリースとしては管理しません。そのためhelm listにはアプリが表示されず、ロールバックもHelmではなくArgo CD(Gitを戻す)で行います。値はhelm.valuesObjectにYAMLのまま書けます。次は公開されているpodinfoチャート(執筆時点の最新は6.15.0)を使う例です。

podinfo-app.yaml(例)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: podinfo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://stefanprodan.github.io/podinfo
    chart: podinfo
    targetRevision: 6.15.0
    helm:
      valuesObject:
        replicaCount: 1
  destination:
    server: https://kubernetes.default.svc
    namespace: podinfo
  syncPolicy:
    syncOptions:
      - CreateNamespace=true

チャートの版はtargetRevisionで固定し、上げるときはこの値を変えてコミットします。automatedを付けていないので、この例では同期は画面かCLIから手動で行います。外部のチャートは中身が大きく変わることがあるため、自動同期にする前に一度差分を目で確かめる運用にしています。

自宅クラスターで運用するときの注意

メモリは見積もっておきます。traefik・metrics-server・servicelbを無効にしたk3sの1ノードにArgo CD一式を入れた直後、アプリを1つも同期していない状態で、ノード全体の使用量は約923MiBでした(検証用のコンテナで測った値)。メモリが2GBのミニPCに載せるとアプリの分の余裕がほとんど残りません。画面が不要なら、argocd-serverを含まないcore-install.yaml(application-controller・applicationset-controller・repo-server・redisだけ)も用意されています。

argocd-serverはクラスターのほぼすべてを変更できる管理画面なので、インターネットに直接公開しません。自宅の外から見たいときは、VPNかゼロトラスト型のトンネルの内側に置きます。チームで使う場合は、defaultプロジェクトのままにせず、AppProjectで同期元のリポジトリと宛先の名前空間を絞ると、設定を間違えたApplicationがkube-systemを書き換える、といった事故を防げます。

つまずきポイント

症状・エラーメッセージ原因対処
The CustomResourceDefinition "applicationsets.argoproj.io" is invalid: metadata.annotations: Too long: may not be more than 262144 bytesクライアント側のkubectl applyが大きなCRDを注釈に保存しようとした--server-side --force-conflictsを付けて適用し直す
インストール直後にargocd-dex-serverのRESTARTSが増える他のコンポーネントの起動待ちで再起動することがある(検証では2回で安定)数分待ってRunningが続くか確認する。増え続けるならkubectl -n argocd logsで原因を見る
kubectl scaleやkubectl editの変更がすぐ元に戻るselfHeal: trueの正常な動作Gitの側を変えてpushする。緊急時はそのアプリのselfHealを外す
SYNC STATUSがUnknownのまま進まないリポジトリに接続できない(URLの誤り、非公開リポジトリの認証情報がない)画面のアプリ詳細でエラー内容を確認し、URLとargocd repo addの登録を見直す

動作確認

Applicationが同期済みか、実際にPodとServiceができているかを確かめます。SYNC STATUSがSynced、HEALTH STATUSがHealthyで、DeploymentのREADYがGitに書いたレプリカ数と一致していれば完了です。

terminal
$ kubectl -n argocd get applications
NAME     SYNC STATUS   HEALTH STATUS
whoami   Synced        Healthy
$ kubectl -n whoami get deploy,svc,pods
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/whoami   2/2     2            2           43s

NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/whoami   ClusterIP   10.43.43.110   <none>        80/TCP    43s

NAME                          READY   STATUS    RESTARTS   AGE
pod/whoami-54b747cf5c-984kl   1/1     Running   0          43s
pod/whoami-54b747cf5c-mzmb8   1/1     Running   0          43s

よくある質問

ArgoCDのSyncとは?

Gitに書かれたマニフェストをクラスターに適用し、実際の状態をGitに一致させる操作です。一致していればSynced、ずれていればOutOfSyncと表示されます。syncPolicy.automatedを設定すると差分の検出時に自動で行われ、設定しなければ画面のSYNCボタンやCLIで手動で実行します。

ArgoCDのPruneとは?

Gitから削除したリソースを、同期のときにクラスターからも削除する機能です。既定では無効で、消したリソースはクラスターに残り、OutOfSyncとして表示されます。automated.prune: trueで自動の削除が有効になりますが、パスの指定ミスでアプリ全体が消えることもあるため、慣れてから有効にします。

GitOpsとは何ですか?

システムのあるべき状態をすべてGitに書き、Gitへのコミットを唯一の変更手段とする運用の考え方です。クラスターの中のツール(Argo CDなど)がGitを監視して状態を合わせ続けるため、変更の履歴・レビュー・取り消しがGitの仕組みでそのまま行えます。

DevOpsとGitOpsの違いは何ですか?

DevOpsは、開発と運用が協力して変更を速く安全に届けるための文化や進め方の総称です。GitOpsはその中の具体的なやり方の1つで、インフラやアプリの状態をGitで管理し、ツールで自動的に反映させる方法を指します。GitOpsを採らないDevOpsの実践もあります。

まとめ

Argo CDの導入は、--server-side付きでマニフェストを適用し、Applicationを1つ書くところまでは30分ほどで済みます。難しいのはその後の運用で、pruneとselfHealをどこまで自動にするか、画面をどこまで公開するか、メモリをどれだけ割けるかを先に決めておくと、自宅クラスターでも無理なく続けられます。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。