本記事の内容
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_a
とapp_b
のディレクトリがあり、それぞれにKubernetesのマニフェストが格納されています。
.
├── app_a
│ ├── config-a.cm.yaml
│ └── config-move.cm.yaml
└── app_b
└── config-b.cm.yaml
app_a
とapp_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_a
とapp_b
のGitリモートと同期するためのApplicationリソースが、以下のようにデプロイされています。
$ kubectl get app -n argocd
NAME SYNC STATUS HEALTH STATUS
app-a Synced Healthy
app-b Synced Healthy
各Applicationリソースのマニフェストは以下です。app-a
とapp-b
で、リソース名とrepoURL
以外は全て同じ内容です。
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)
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
selfHeal
がtrue
だと、KubernetesクラスターのリソースがGitの定義と異なっている場合に、ArgoCDが自動的に修正します。例えば管理対象リソースを kubectl edit
や kubectl patch
などで手動変更しても、ArgoCDはGitリポジトリのマニフェストと異なることを検知し、自動的に元の状態に戻します。
一方selfHeal
がfalse
の場合、GitリポジトリのマニフェストやKubernetesクラスターのマニフェストを手動で変更して差分が発生しても、自動で同期はされません。同期はArgoCDのWebUIやCLIを使って、手動で行う必要があります。
解説(4) : spec.syncPolicy.automated.prune
prune
は、管理対象から外れたリソースを自動で削除する設定です。prune
がtrue
の場合、Gitリポジトリからマニフェストを削除すると、Kubernetesクラスターで同期していたマニフェストも削除されます。そのため、マニフェストに基づいてデプロイされたリソースも自動的に削除されます。
prune
がfalse
だと、Gitリポジトリからマニフェストを削除してArgoCDのSyncを実行しても、Kubernetesクラスターのマニフェストとリソースは削除されません。
管理対象リソースのデプロイ状況
app_a
とapp_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-move
はapp_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-a
とapp-b
の両方に登録されているので、一方がSynced
の状態であれば、もう一方はOutOfSync
になります。さらにapp-a
とapp-b
の両方のApplicationリソースで、selfHeal: true
で自己修復機能が有効になっているため、config-move
はある時はapp-a
と同期し、また別の時はapp-b
と同期することになります。まるで「(app-a
)config-move
は私のものよ!」「(app-b
)いやあたしのものよ!」と、永遠に取り合いっこをしているような状況です。
app-a
とapp-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_a
でconfig-move
のマニフェストをGitリポジトリから削除した時に、たまたまapp_a
がそれを管理していたら削除されます。先ほどの永遠の取り合いでたとえてみると、次のような状況だと削除されます。
- (
app-a
)config-move
は私のものよ! - (
app-b
)config-move
はあたしのものよ! - (
app-a
)config-move
は私のものよ! - (
app-b
)config-move
はあたしのものよ! - (
app-a
)config-move
は私のものよ! - (
app-a
のGitリポジトリ)config-move
を消しました - (
app-a
)はーい。pruneが有効だから、Kubernetesクラスターのconfig-move
のマニフェストも消すねー - (
app-b
)config-move
はあたしのものよ!けどKubernetesクラスターにマニフェストがないから新しく作るね - (
app-b
)config-move
はあたしのものよ! - (
app-b
)config-move
はあたしのものよ! - (
app-b
)config-move
はあたしのものよ!
一方で、Gitリポジトリがconfig-move
を削除した際に、たまたまapp_b
が管理していた場合は削除されません。
- (
app-a
)config-move
は私のものよ! - (
app-b
)config-move
はあたしのものよ! - (
app-a
)config-move
は私のものよ! - (
app-b
)config-move
はあたしのものよ! - (
app-a
のGitリポジトリ)config-move
を消しました - (
app-a
)はーい。けど、config-move
は私のものじゃないねー - (
app-b
)config-move
はあたしのものよ! - (
app-b
)config-move
はあたしのものよ!
危機一髪、削除されずに済んだという感じですね。
pruneをfalseにすると削除を防げる
マニフェストとリソースの予期せぬ削除は、app-a
のApplicationリソースのマニフェストで、prune
をfalse
にすることで防げます。(デフォルトはArgoCD v2.13.3だとfalse
なので、コメントアウトしても問題ないです)
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からはマニフェストは削除されたけど、(prune
がfalse
だから)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-b
)config-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-move
をapp-b
の管理下に移す必要があります。
argocd app sync app-b
移行元(app_a
)で、OutOfSync
のリソースが残っていたら要解消(25/02/26追記)
app-b
でSynced
が確認できただけでは安心できません。移行元のapp_a
が、移行したはずのconfig-move
を中途半端に認識している可能性があるためです。具体的に、app_a
でconfig-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-a
のOutOfSync
は、「app-b
に移行が済んだはずのconfig-move
をapp-a
が自らの管理下に置こうとしている」状態です。この状態でapp-a
のpruneを有効にすると、config-move
がapp-a
の管理対象となり、即座に削除されてしまいます。そうならないように、app-a
のOutOfSync
は解消しておきたいです。
私自身がこのケースに遭遇した時は、一回移行元のapp_a
とsyncさせた後、移行先のapp_b
とsyncすることで解決しました。実行コマンドは以下です。
argocd app sync app-a
argocd app sync app-b
この後にapp_a
の状態を確認すると、次のようにOutOfSync
がSucceeded
に変わり、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リソースでprune
をtrue
に戻して大丈夫です。
namespace: default
syncPolicy:
automated:
- prune: false
+ prune: true
selfHeal: true
この後app-a
のsyncが完了すると、config-move
はapp-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-move
のConfigMap
リソースだと、次のようにアノテーションを追加します。
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-move
をapp_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