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

# ArgoCDとは？

ArgoCDは、KubernetesクラスターのGitOpsを実現するツールです。
ArgoCDを使うと、Kubernetesのマニフェストを管理しているGitリポジトリと連携し、Gitの変更を検知してKubernetesクラスターに自動でリソースをデプロイできます。

「Gitリポジトリ」と「Kubernetesクラスター」を結びつけるのが、ArgoCDのカスタムリソースであるApplicationです。Applicationリソースは、Kubernetesのマニフェストに以下のような情報を記述し、利用できるようになります。

* 参照するGitリポジトリと、そのブランチなどの付加情報
* デプロイ先のKubernetesクラスターと、そのNamespaceなどの付加情報

# Applicationで管理しているリソースを誤って消してしまう...

私はあるプロジェクトでArgoCDを使っています。ArgoCDと連携しているGitリポジトリには、沢山のApplicationリソースが定義されています。
だんだんとApplicationの数が増えてきて、その中には別のApplicationリソースの配下に移行した方が適切なKubernetesマニフェストも出てきました。

そこでKubernetesマニフェストの移行を、以下の手順でやってみました。

* (1) ある Application ``app_a`` のマニフェストを、別の Application ``app_b`` にコピーする
* (2) (1)の変更を git push して、ArgoCDにも反映する
* (3) コピー元の Application ``app_a`` から、``app_b`` にコピー済みのマニフェストを消す
* (4) (3)の変更を git push して、ArgoCDにも反映する

やや直感的ですが、Linuxコマンドにたとえると以下の操作を行なっている感覚です。

```
cp app_a/manifest.yaml app_b/manifest.yaml

diff app_a/manifest.yaml app_b/manifest.yaml
(差分がないことを確認)

rm app_a/manifest.yaml
```

こうすれば、``manifest.yaml`` が消えてしまうことは一秒たりとも無いですよね。

ところがArgoCDで同じようなことをしたら、``manifest.yaml``に相当する **Kubernetesリソースが消えて、再作成されてしまったのです。** なんでぇー！？

# 原因調査の前に、ArgoCDの仕組みを確認

マニフェストはほんの一瞬も消えていないのに、なぜリソースが消えてしまったのか。
この謎を解明するためには、ArgoCDがKubernetesのリソースをデプロイする仕組みを理解した方がよさそうです。

以下のように、ArgoCDに関わるKubernetesマニフェストとその関係について図にしてみます。
![argocd-ページ1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/5d0dff1c-bc08-1552-1147-d4cb72315bf2.png)

YAMLと書かれたマニフェストが、Gitリポジトリ、ArgoCD、Kubernetesクラスターの3箇所に格納されていますね。ArgoCDはGitリポジトリとKubernetesクラスターの間に入って、双方のマニフェストの同期を実現する役割を担っています。もう少し詳しく説明しましょう。

## Gitリポジトリの変更をArgoCDが検知

誰かがGitリポジトリに変更をpushすると、ArgoCDが変更を検知します。検知方法は、ArgoCDからGitリポジトリに定期的に変更を確認するポーリングと、Gitリポジトリに変更があった場合にArgoCDに変更を通知するWebhookの2通りがあります。現時点で最新のArgoCDのv2.14では、デフォルトでポーリング方式が使用され、3分間隔でGitリポジトリに変更を問い合わせます。変更を検知した後、ArgoCDはGitリポジトリの状態に基づき、Kubernetesクラスターのリソースを更新する準備をします。

## ArgoCDのマニフェストをKubernetesクラスターに反映

次に、ArgoCDはGitリポジトリのマニフェストに基づいて、Kubernetesクラスター内のリソースを同期（Sync）します。Syncが成功すると、Kubernetesクラスター内のリソースがGitリポジトリに定義された状態と一致するように更新され、例えばDeploymentやService等のKubernetesリソースが作成されます。

:::note info
この過程で、ArgoCDは内部的に`kubectl apply`や`helm upgrade`などを使用しています。
:::

# マニフェストを移行した時に何が起きたのか？

改めて、ArgoCDのマニフェストを移行した時に何が起きたのかを検証しましょう。

まずは、``app_b``にマニフェストのコピーを作成した時の動きです。

> * (1) ある Application ``app_a`` のマニフェストを、別の Application ``app_b`` にコピーする
> * (2) (1)の変更を git push して、ArgoCDにも反映する

この操作により、GitリポジトリとArgoCDでは同じマニフェストが``app_a``と``app_b``の両方に作られます。しかし、Kubernetesクラスターでは ``app_a`` 用と ``app_b`` 用に2つのリソースが作成されるわけではありません。これは、2つのマニフェストが「リソースの種類」「リソース名」「Namespace」の値を共有しているためです。その結果、Kubernetesクラスターでは1つのリソースとして扱われ、実際に作成されるリソースも1つになります。

図で表すと、それぞれのツールで以下のようにマニフェストが作られています。

![argocd-copy.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/939598bf-a953-4b16-7855-e6c9cbad5a9d.png)

続いて、``app_a``からマニフェストを消した時の挙動の検証です。

> * (3) コピー元の Application ``app_a`` から、``app_b`` にコピー済みのマニフェストを消す
> * (4) (3)の変更を git push して、ArgoCDにも反映する

この操作を行うと、GitリポジトリとArgoCDで``app_a``の Application リソース配下にあるマニフェストは全て削除されます。下図のような状態です。

![argocd-delete.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/bbda481d-473c-2fa3-1ff0-5d2fdfe03fe7.png)

さて、この時にKubernetesクラスターで作られたマニフェストは消えずに残るのでしょうか？ それとも消されるのでしょうか？
「``app_b``のマニフェストがKubernetesクラスターとまだSyncしているので、さすがに消えることはないでしょ」と思ってしまうのではないでしょうか。（C++のスマートポインタとかで同じことをやったら、値への参照がまだ残ってるから消えないですよね）

ところが、今回はKubernetesクラスターのマニフェスト（とリソース）は消えてしまいました。それはなぜでしょう？

## pruneを有効にしていると消える

調査の結果、``app_a``の Application リソースで、pruneを有効にしていると消えてしまう可能性が高いことがわかりました。``app_a``からあるマニフェストが削除された際、pruneが有効だとSync先のKubernetesクラスターのマニフェストも消そうとします。

重要なので再度書きますが、

**prune が有効な場合、たとえ他のApplicationリソースが同じリソースを管理していたとしても、お構いなしにそのマニフェストをKubernetesクラスターから消そうとするのです。**

この時の様子を図で表すと、以下のようになります。

![argocd-prune.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/4afb3335-ba02-5d3d-7bfc-69f74d33c713.png)

pruneが有効な場合、``app_a``が一度Kubernetesクラスターのマニフェストを消して、その直後に``app_b``が同じリソースを再作成します。リソースによっては、再作成しても影響が少ない場合もありますが、これが PVC の場合、ボリュームごと消されてしまい大惨事につながる可能性があるので、非常に怖いです...

# 対応策はpruneを無効にすること

今回のトラブルは、移行元のApplicationリソースのpruneを無効にすることで防げます。
普段はpruneを有効にして運用している場合、一時的にpruneを無効にし、移行が完了したらpruneを有効に戻せばいいと考えてしまいがちです。しかしpruneを再び有効にしたタイミングで、移行中に行われた削除処理が適用される可能性があるため、慎重な対応が必要です。

移行元のApplicationで安全にpruneを有効にする具体的な方法は、以下の続編に記載しました。

https://qiita.com/showchan33/items/c6ffddbcf7ed02cc85a8