1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ArgoCDでマニフェストを移行する時は、リソースを消してしまわないように気をつけて!- 実践編

Last updated at Posted at 2025-02-08

本記事の内容

ArgoCDで管理しているKubernetesマニフェストで、管理するApplicationを別のApplicationに移行した際に、誤ってリソースを消してしまった記事を先週書きました。

この記事では、「移行元Applicationのpruneを無効にすれば誤消去を回避できる」という結論でまとめました。

本記事では、具体的にどのようにすれば安全にマニフェストを移行できるのかを、実際にKubernetesのリソースを使って確かめてみます。

TL;DR

ArgoCDのマニフェストを別のApplicationリソースへ安全に移行する手順は以下です。

  • (1) 移行元のApplicationリソースで、pruneを無効にする
  • (2) 移行先ApplicationのGitリポジトリに、移行するマニフェストを追加する
  • (3) 移行先ApplicationでArgoCDのsyncを実行し、ArgoCDのマニフェストとKubernetesクラスターのマニフェストを同期させる
    • 【重要!】同期後に、移行するマニフェストが移行先Applicationとsyncできていることを確認する
  • (4) 移行元ApplicationのGitリポジトリから、移行が完了したマニフェストを削除する

移行元のApplicationで、pruneを有効にしたい場合

上記手順を実施後に移行元のApplicationでpruneを有効にする場合は、「移行元Applicationで、OutOfSyncになっている移行対象リソースが存在しないこと」を確認します。もし移行元AppでOutOfSyncのリソースが残っている場合、pruneを有効にする前に OutOfSync を解消する必要があります。

移行元Appで事前にOutOfSyncを解消しておかないと、pruneを有効にした際に移行が済んだはずのリソースが消えてしまうので注意です!

どうしても消したくなリソースには、argocd.argoproj.io/sync-optionsのアノテーションを付与する

metadata:
  annotations:
    argocd.argoproj.io/sync-options: Prune=false

参考ドキュメント

環境情報

今回利用したArgoCDのバージョンは、v2.13.3です。

マニフェストを格納しているディレクトリ

app_aapp_bのディレクトリがあり、それぞれにKubernetesのマニフェストが格納されています。

.
├── app_a
│   ├── config-a.cm.yaml
│   └── config-move.cm.yaml
└── app_b
    └── config-b.cm.yaml

app_aapp_bのディレクトリはGit管理されており、リモートリポジトリがoriginという名前で次のように設定されています。

$ cd app_a/
$ git remote -v
origin  https://path/to/repo/app_a.git (fetch)
origin  https://path/to/repo/app_a.git (push)

$ cd ../app_b/
$ git remote -v
origin  https://path/to/repo/app_b.git (fetch)
origin  https://path/to/repo/app_b.git (push)

Applicationリソース

Kubernetesクラスターには、app_aapp_bのGitリモートと同期するためのApplicationリソースが、以下のようにデプロイされています。

$ kubectl get app -n argocd
NAME                  SYNC STATUS   HEALTH STATUS
app-a                 Synced        Healthy
app-b                 Synced        Healthy

各Applicationリソースのマニフェストは以下です。app-aapp-bで、リソース名とrepoURL以外は全て同じ内容です。

app-a.app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-a
spec:
  project: default
  source: # 解説(1)
    repoURL: "https://path/to/repo/app_a.git"
    path: "."
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc # 解説(2)
    namespace: default
  syncPolicy:
    automated:
      selfHeal: true # 解説(3)
      prune: true    # 解説(4)
app-b.app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-b
spec:
  project: default
  source: # 解説(1)
    repoURL: "https://path/to/repo/app_b.git"
    path: "."
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc # 解説(2)
    namespace: default
  syncPolicy:
    automated:
      selfHeal: true # 解説(3)
      prune: true    # 解説(4)

解説(1) : spec.source

repoURL には、ArgoCD がアプリケーションのマニフェストを取得する Git リポジトリの URL が書かれています。pathには、リポジトリ内でマニフェストがあるディレクトリを書きます。今回はルートディレクトリにマニフェストがあるため、"."と記載します。targetRevisionには、どのブランチやタグ、コミットを使用するかを定義します。

解説(2) : spec.destination.server

このパラメータには「Applicationリソースが管理対象としているリソースをデプロイする、KubernetesクラスターのAPIサーバーのURL」を指定します。今回のように https://kubernetes.default.svc を指定すると、ArgoCD がデプロイされているクラスターと同じクラスターに、管理対象リソースがデプロイされます。他のクラスターにデプロイしたい場合は、ここを https://[デプロイしたいクラスターのAPIサーバーのURL]:[port] のように書き換えます。

解説(3) : spec.syncPolicy.automated.selfHeal

selfHealtrueだと、KubernetesクラスターのリソースがGitの定義と異なっている場合に、ArgoCDが自動的に修正します。例えば管理対象リソースを kubectl editkubectl patch などで手動変更しても、ArgoCDはGitリポジトリのマニフェストと異なることを検知し、自動的に元の状態に戻します。
一方selfHealfalseの場合、GitリポジトリのマニフェストやKubernetesクラスターのマニフェストを手動で変更して差分が発生しても、自動で同期はされません。同期はArgoCDのWebUIやCLIを使って、手動で行う必要があります。

解説(4) : spec.syncPolicy.automated.prune

pruneは、管理対象から外れたリソースを自動で削除する設定です。prunetrueの場合、Gitリポジトリからマニフェストを削除すると、Kubernetesクラスターで同期していたマニフェストも削除されます。そのため、マニフェストに基づいてデプロイされたリソースも自動的に削除されます。
prunefalseだと、Gitリポジトリからマニフェストを削除してArgoCDのSyncを実行しても、Kubernetesクラスターのマニフェストとリソースは削除されません。

管理対象リソースのデプロイ状況

app_aapp_bのApplicationで管理されているリソースは、以下のように確認できます。

$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-a"
NAME          DATA   AGE
config-a      2      80m
config-move   2      80m
$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-b"
NAME       DATA   AGE
config-b   2      80m

これらのリソースには、argocd.argoproj.io/instance=[application名]というラベルが付与されているのが特徴です。各Applicationリソースは、このラベルによって自分が管理するリソースを認識しています。ArgoCDはラベルが付いているリソースの状態を監視し、Gitリポジトリの定義とKubernetesクラスター内のリソースを比較して、必要に応じて更新や削除を行います。

リソースをapp_aからapp_bに移行する

現在のApplicationリソースとマニフェストの構成を再掲します。

.
├── app_a
│   ├── config-a.cm.yaml
│   └── config-move.cm.yaml
└── app_b
    └── config-b.cm.yaml

この状態から、app_a管理のconfig-move.cm.yamlに定義されたリソースを、app_bで管理するように変更したいです。そのためには、config-move.cm.yamlのマニフェストをapp_bに移行する必要があります。

マニフェストをapp_aからapp_bコピーして、Gitリモートに反映

まずは、config-move.cm.yamlのマニフェストをapp_bにコピーしてGit管理対象にします。以下のコマンドを実行します。

cp app_a/config-move.cm.yaml app_b/
cd app_b
git add config-move.cm.yaml
git commit -m "add config-move"
git push origin main

以上のコマンドを実行すると、app_bのApplicationの管理対象リソースに、config-move.cm.yamlに書かれたConfigMapのリソースconfig-moveが追加されます。

├── app_a
│   ├── config-a.cm.yaml
│   └── config-move.cm.yaml
└── app_b
    ├── config-b.cm.yaml
    └── config-move.cm.yaml # copied

このことは、ArgoCDのCLIで app_bで管理されたリソース一覧を出力することで確認できます。

$ argocd app resources app-b
GROUP  KIND       NAMESPACE  NAME         ORPHANED
       ConfigMap  default    config-b     No
       ConfigMap  default    config-move  No

$ argocd app get app-b | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS  HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  Synced                configmap/config-move configured
       ConfigMap  default    config-b     Synced

app_bで管理しているリソースは全てSyncedになっており、Kubernetesクラスターのマニフェストが、app_bで管理しているマニフェストと同期されていることがわかります。

一方で、config-moveapp_aの管理対象でもあります。

$ argocd app resources app-a
GROUP  KIND       NAMESPACE  NAME         ORPHANED
       ConfigMap  default    config-a     No
       ConfigMap  default    config-move  No

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  OutOfSync                configmap/config-move configured
       ConfigMap  default    config-a     Synced

app_aから見るとconfig-moveのSTATUSがOutOfSyncです。差分を確認してみましょう。

$ argocd app diff app-a

===== /ConfigMap default/config-move ======
11c11
<     argocd.argoproj.io/instance: app-b
---
>     argocd.argoproj.io/instance: app-a

config-move のラベルの値がapp-aであってほしいのに、Kubernetesクラスターのマニフェストではapp-bになっていることがOutOfSyncの原因です。

これを解消するには、app-aに対して以下のコマンドを実行します。

argocd app sync app-a

すると、app-aの管理リソースは全てsyncされた状態になります。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS  HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  Synced                configmap/config-move configured
       ConfigMap  default    config-a     Synced

ところが今度は、app-bがラベルの不一致で OutOfSync になってしまいます。

$ argocd app get app-b | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  OutOfSync                configmap/config-move configured
       ConfigMap  default    config-b     Synced

$ argocd app diff app-b

===== /ConfigMap default/config-move ======
11c11
<     argocd.argoproj.io/instance: app-a
---
>     argocd.argoproj.io/instance: app-b

config-moveリソースはapp-aapp-bの両方に登録されているので、一方がSyncedの状態であれば、もう一方はOutOfSyncになります。さらにapp-aapp-bの両方のApplicationリソースで、selfHeal: trueで自己修復機能が有効になっているため、config-moveはある時はapp-aと同期し、また別の時はapp-bと同期することになります。まるで「(app-aconfig-moveは私のものよ!」「(app-b)いやあたしのものよ!」と、永遠に取り合いっこをしているような状況です。

app-aapp-bのどちらが所有しているかは、既に登場した argocd.argoproj.io/instanceの値で決まります。例えば値がapp-aならばapp-aのApplicationリソースが管理している状態です。

app_aからマニフェストを削除

続いてapp_aからconfig-moveを定義しているマニフェストを削除して、Gitリモートリポジトリに反映します。

cd app_a
git rm config-move.cm.yaml
git commit -m "delete config-move"
git push origin main

約1分後に管理対象のマニフェストを確認してみると、なんとconfig-moveのConfigMapが app_aからもapp_b消えてしまっています...

$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-a"
NAME       DATA   AGE
config-a   2      9h

$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-b"
NAME       DATA   AGE
config-b   2      9h

app_aの状態を確認するとconfig-moveの行にPrunedと書かれており、app_aとしては定義通りにリソースを消していそうです...

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  Succeeded  Pruned        pruned
       ConfigMap  default    config-a     Synced

しばらくすると、app-bで自己修復機能が働いてconfig-moveを再び作成してくれます。

$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-b"
NAME          DATA   AGE
config-b      2      9h
config-move   2      6s

※ 今回のようにConfigMapの再作成なら問題ないと思いますが、対象がPVC等の永続化リソースだったら、と思うとゾッとします。

どのようにして消えてしまったか?

リソースが消えてしまう事象は必ず起きるわけではありません。app_aconfig-moveのマニフェストをGitリポジトリから削除した時に、たまたまapp_aがそれを管理していたら削除されます。先ほどの永遠の取り合いでたとえてみると、次のような状況だと削除されます。

  • app-aconfig-moveは私のものよ!
  • app-bconfig-moveはあたしのものよ!
  • app-aconfig-moveは私のものよ!
  • app-bconfig-moveはあたしのものよ!
  • app-aconfig-moveは私のものよ!
  • app-aのGitリポジトリ)config-moveを消しました
  • app-a)はーい。pruneが有効だから、Kubernetesクラスターのconfig-moveのマニフェストも消すねー
  • app-bconfig-moveはあたしのものよ!けどKubernetesクラスターにマニフェストがないから新しく作るね
  • app-bconfig-moveはあたしのものよ!
  • app-bconfig-moveはあたしのものよ!
  • app-bconfig-moveはあたしのものよ!

一方で、Gitリポジトリがconfig-moveを削除した際に、たまたまapp_bが管理していた場合は削除されません。

  • app-aconfig-moveは私のものよ!
  • app-bconfig-moveはあたしのものよ!
  • app-aconfig-moveは私のものよ!
  • app-bconfig-moveはあたしのものよ!
  • app-aのGitリポジトリ)config-moveを消しました
  • app-a)はーい。けど、config-moveは私のものじゃないねー
  • app-bconfig-moveはあたしのものよ!
  • app-bconfig-moveはあたしのものよ!

危機一髪、削除されずに済んだという感じですね。

pruneをfalseにすると削除を防げる

マニフェストとリソースの予期せぬ削除は、app-aのApplicationリソースのマニフェストで、prunefalseにすることで防げます。(デフォルトはArgoCD v2.13.3だとfalseなので、コメントアウトしても問題ないです)

app-a.app.yamlのdiff
     namespace: default
   syncPolicy:
     automated:
-      prune: true
+      prune: false
       selfHeal: true

記載内容がやや冗長になってしまいますが、再度移行を一から実施していきます。

マニフェストをapp_aからapp_bコピーして、Gitリモートに反映

以下のコマンドを実行します。

cp app_a/config-move.cm.yaml app_b/
cd app_b
git add config-move.cm.yaml
git commit -m "add config-move"
git push origin main

config-moveが、app-bでも認識されるのを確認します。

$ argocd app get app-b | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS  HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  Synced                configmap/config-move configured
       ConfigMap  default    config-b     Synced

app_aからマニフェストを削除

以下のコマンドを実行します。

cd app_a
git rm config-move.cm.yaml
git commit -m "delete config-move"
git push origin main

この後にapp-aの状態を確認すると、config-moveのリソースの行に"ignored (requires pruning)" というメッセージが表示されます。ArgoCDからはマニフェストは削除されたけど、(prunefalseだから)Kubernetesクラスターのマニフェストは残っている状態です。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  OutOfSync                ignored (requires pruning)
       ConfigMap  default    config-a     Synced                   configmap/config-a configured

確かに、app-aのラベルがついたconfig-moveのリソースは残っています。

$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-a"
NAME          DATA   AGE
config-a      2      23h
config-move   2      14h

ArgoCDのマニフェストとKubernetesクラスターのマニフェストを同期させる

しばらく待つと、app-bでself healが機能し、「(app-bconfig-moveはあたしのものよ!」の状態になります。

$ argocd app get app-b | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS  HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  Synced                configmap/config-move configured
       ConfigMap  default    config-b     Synced                configmap/config-b configured

$ kubectl get cm -n default -l "argocd.argoproj.io/instance=app-b"
NAME          DATA   AGE
config-b      2      15h
config-move   2      14h

【重要!】移行対象のconfig-moveのSTATUSがSyncedになっているのを忘れずに確認してください。

self healが無効の場合は自動でsyncしてくれないので、以下のコマンドでconfig-moveapp-bの管理下に移す必要があります。

argocd app sync app-b

移行元(app_a)で、OutOfSyncのリソースが残っていたら要解消(25/02/26追記)

app-bSyncedが確認できただけでは安心できません。移行元のapp_aが、移行したはずのconfig-moveを中途半端に認識している可能性があるためです。具体的に、app_aconfig-moveが以下のようにOutOfSyncのSTATUSになっていたら解消する必要があります。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  OutOfSync                (何らかのメッセージ)
       ConfigMap  default    config-a     Synced                   configmap/config-a configured

app-aOutOfSyncは、「app-bに移行が済んだはずのconfig-moveapp-aが自らの管理下に置こうとしている」状態です。この状態でapp-aのpruneを有効にすると、config-moveapp-aの管理対象となり、即座に削除されてしまいます。そうならないように、app-aOutOfSyncは解消しておきたいです。

私自身がこのケースに遭遇した時は、一回移行元のapp_aとsyncさせた後、移行先のapp_bとsyncすることで解決しました。実行コマンドは以下です。

argocd app sync app-a
argocd app sync app-b

この後にapp_aの状態を確認すると、次のようにOutOfSyncSucceededに変わり、app_aによってconfig-moveが削除されることを回避できました。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH        HOOK  MESSAGE
       ConfigMap  default    config-move  Succeeded  PruneSkipped        ignored (requires pruning)
       ConfigMap  default    config-a     Synced                         configmap/config-a configured

以上を確認できたら、pruneに戻してもOK

config-moveが完全にapp-bの管理下になったのを確認できたら、app-aのApplicationリソースでprunetrueに戻して大丈夫です。

app-a.app.yamlのdiff
     namespace: default
   syncPolicy:
     automated:
-      prune: false
+      prune: true
       selfHeal: true

この後app-aのsyncが完了すると、config-moveapp-aから完全に忘れ去られ、app-bのみが管理する状態になります。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME      STATUS  HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-a  Synced                configmap/config-a configured

$ argocd app get app-b | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS  HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  Synced                configmap/config-move configured
       ConfigMap  default    config-b     Synced                configmap/config-b configured

このようなやり方でマニフェストを移行すると、config-moveのリソースは確実に残ります。

消してほしくないリソースには、アノテーションを追加する(2/27追記)

上記の手順で実施しても、何らかの手違いでリソースを消してしまう可能性は否めなせん。PVCやnamespace等の「どうしても消えると困る」リソースの場合は、該当マニフェストに次のようなアノテーションを付与することで、ArgoCDによるリソース削除を回避できます。(参考ドキュメント

metadata:
  annotations:
    argocd.argoproj.io/sync-options: Prune=false

例えばこれまで使ってきたconfig-moveConfigMapリソースだと、次のようにアノテーションを追加します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: config-move
  namespace: default
  annotations:
    argocd.argoproj.io/sync-options: Prune=false
data:
  key1: value1
  key2: value2

このアノテーションを書いておくと、Gitリポジトリからconfig-moveのマニフェストを削除した際に、管理しているApplicationのpruneが有効でもリソースが消えることはありません。

config-moveのマニフェストをGitリポジトリから消した場合、次のように "ignored (no prune)"と出てきて削除していないのがわかります。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH  HOOK  MESSAGE
       ConfigMap  default    config-move  OutOfSync                ignored (no prune)
       ConfigMap  default    config-a     Synced                   configmap/config-a configured

この後、移行先のapp_bで同期してconfig-moveapp_b管理下にすると、app_aにおけるconfig-moveのSTATUSが Succeeded になり、今後app_aによって削除される可能性はなくなります。

$ argocd app get app-a | grep -A 4 ^GROUP
GROUP  KIND       NAMESPACE  NAME         STATUS     HEALTH        HOOK  MESSAGE
       ConfigMap  default    config-move  Succeeded  PruneSkipped        ignored (no prune)
       ConfigMap  default    config-a     Synced                         configmap/config-a configured
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?