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
のマニフェストを、別の Applicationapp_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マニフェストとその関係について図にしてみます。
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リソースが作成されます。
この過程で、ArgoCDは内部的にkubectl apply
やhelm upgrade
などを使用しています。
マニフェストを移行した時に何が起きたのか?
改めて、ArgoCDのマニフェストを移行した時に何が起きたのかを検証しましょう。
まずは、app_b
にマニフェストのコピーを作成した時の動きです。
- (1) ある Application
app_a
のマニフェストを、別の Applicationapp_b
にコピーする- (2) (1)の変更を git push して、ArgoCDにも反映する
この操作により、GitリポジトリとArgoCDでは同じマニフェストがapp_a
とapp_b
の両方に作られます。しかし、Kubernetesクラスターでは app_a
用と app_b
用に2つのリソースが作成されるわけではありません。これは、2つのマニフェストが「リソースの種類」「リソース名」「Namespace」の値を共有しているためです。その結果、Kubernetesクラスターでは1つのリソースとして扱われ、実際に作成されるリソースも1つになります。
図で表すと、それぞれのツールで以下のようにマニフェストが作られています。
続いて、app_a
からマニフェストを消した時の挙動の検証です。
- (3) コピー元の Application
app_a
から、app_b
にコピー済みのマニフェストを消す- (4) (3)の変更を git push して、ArgoCDにも反映する
この操作を行うと、GitリポジトリとArgoCDでapp_a
の Application リソース配下にあるマニフェストは全て削除されます。下図のような状態です。
さて、この時にKubernetesクラスターで作られたマニフェストは消えずに残るのでしょうか? それとも消されるのでしょうか?
「app_b
のマニフェストがKubernetesクラスターとまだSyncしているので、さすがに消えることはないでしょ」と思ってしまうのではないでしょうか。(C++のスマートポインタとかで同じことをやったら、値への参照がまだ残ってるから消えないですよね)
ところが、今回はKubernetesクラスターのマニフェスト(とリソース)は消えてしまいました。それはなぜでしょう?
pruneを有効にしていると消える
調査の結果、app_a
の Application リソースで、pruneを有効にしていると消えてしまう可能性が高いことがわかりました。app_a
からあるマニフェストが削除された際、pruneが有効だとSync先のKubernetesクラスターのマニフェストも消そうとします。
重要なので再度書きますが、
prune が有効な場合、たとえ他のApplicationリソースが同じリソースを管理していたとしても、お構いなしにそのマニフェストをKubernetesクラスターから消そうとするのです。
この時の様子を図で表すと、以下のようになります。
pruneが有効な場合、app_a
が一度Kubernetesクラスターのマニフェストを消して、その直後にapp_b
が同じリソースを再作成します。リソースによっては、再作成しても影響が少ない場合もありますが、これが PVC の場合、ボリュームごと消されてしまい大惨事につながる可能性があるので、非常に怖いです...
対応策はpruneを無効にすること
今回のトラブルは、移行元のApplicationリソースのpruneを無効にすることで防げます。
普段はpruneを有効にして運用している場合、一時的にpruneを無効にし、移行が完了したらpruneを有効に戻せばいいと考えてしまいがちです。しかしpruneを再び有効にしたタイミングで、移行中に行われた削除処理が適用される可能性があるため、慎重な対応が必要です。
移行元のApplicationで安全にpruneを有効にする具体的な方法は、以下の続編に記載しました。