ArgoCDのプラグイン機能の注意点

この記事は [Kubernetes2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/kubernetes2) の9日目の記事です。

ArgoCDのプラグイン機能を使う際の注意点について書きます。

## ArgoCD とは

ArgoCD はまだ比較的マイナーな気がするので、 ArgoCD is 何、というところから少し説明させてください。

[ArgoCD](https://argoproj.github.io/argo-cd/) は Kubernetes のための GitOps 系CD（継続的デリバリー）ツールです。 GitOpsとは、マニフェストを格納したGitリポジトリを SSOT (Single Source of Truth) とすることで、デプロイの自動化やマニフェストの変更を簡単にしようぜ、という思想です。

ArgoCD を使ったデプロイを開始するまでのざっくりとした流れは、（ArgoCDをインストール・初期設定したあと、）

1. Kubernetes にデプロイするアプリのマニフェストが格納されたGitリポジトリを用意する
1. ArgoCDに以下の情報を登録する
    * 名前
    * リポジトリURL
    * マニフェストが置いてあるリポジトリ内のパス
    * デプロイ先クラスタ
    * namespace
    * その他オプション
3. デプロイ内容の変更が必要になったら、マニフェストのリポジトリに変更をプッシュする

これだけです。簡単ですね。

例えば公式ドキュメントで用意されている通り、Kubernetes界の Hello World として有名なWebアプリ Guestbook を ArgoCD に登録するには、

```
argocd app create guestbook \
  --repo https://github.com/argoproj/argocd-example-apps.git \
  --path guestbook \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default
```

というコマンドで登録するだけです。（[ArgoCD Getting Started](https://argoproj.github.io/argo-cd/getting_started/) より引用）

登録が終わると、ArgoCD は Gitリポジトリのマニフェストと、デプロイ先のKuberentesに定義されているマニフェストの差分を調べて、違っていれば Out of Sync 状態を検出してくれます。検出後にそのまま自動で新しいマニフェストをデプロイすることも可能です。 Web UI も付属しており、↓のような画面でアプリケーションのデプロイ状況を確認できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15320/976b4056-1051-2e54-9a80-e933335c9511.png" alt="ゲストブックをデプロイしたところ" width="70%">

ArgoCD 自体も Kubernetes上 で動作します。インストールは [公式が用意しているマニフェスト](https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml) を突っ込むだけで完了です。


個人的に ArgoCD の以下のポイントが好みで使い始めました。

* 比較的小さい問題領域をきっちり解決する
    * Kuberentes用のGitOps用途専用のツール
* 専用DBが不要（アプリごとの登録情報は CRD として管理されるので、最終的に etcd に格納される)
* Web UIがある（自分だけなら不要だけど、必要とする人もいるので）

## ArgoCDがサポートするマニフェスト

前述の ArgoCD に登録する情報を見て、「リポジトリURLとマニフェストを置いたリポジトリ内のパスって、指定したとしても具体的には何を見るんだ？」と疑問に思った人も多いかと思います。
ArgoCDがマニフェストとしてサポートしているのは以下の通りです。

1. kustomize アプリ
1. helm チャート
1. ksonnet アプリ
1. jsonnet ファイル
1. YAMLまたはJSONマニフェストを置いた単なるディレクトリ
1. コンフィグ管理プラグインとして設定されたコンフィグ管理ツールなら何でも

kustomize, helm, ksonnet は試していないのですが（[公式ドキュメント](https://argoproj.github.io/argo-cd/user-guide/tool_detection/) によれば、これらの場合ディレクトリ内の特徴から自動検出するようです）、 Jsonnet, YAML, JSONは、それぞれそれらを置いたディレクトリをパスに指定しておけば、そのディレクトリ内に存在する、当該種別のファイル名風のサフィックスを持ったファイルをデプロイ対象にしてくれるようです。（例えば `*.yaml` 全部、等）

1〜5に該当しない、独自のマニフェスト管理を行っている場合、 6 の「プラグイン」という機能を利用すれば、事実上なんでも行けますが、いくつか注意点があります。

## プラグイン機能とは

プラグイン機能とは、任意のコマンドを実行してマニフェストを生成する機能です。
詳しくは [公式ドキュメント](https://argoproj.github.io/argo-cd/user-guide/config-management-plugins/) を参照していただくと雰囲気がわかるのですが、

1. 「init(前処理)」と「generate(生成)」の2つのコマンドをArgoCDのConfigMap (ArgoCDのインストール用マニフェスト内に空の状態で)に指定しておく
2. アプリケーション登録情報のオプションとして利用プラグイン名を指定しておく

という手順で利用可能になります。

例えば、ArgoCDのConfigMapに

```YAML
data:
  configManagementPlugins: |
    - name: myPlugin
      init:
        command: ["foo"]
        args: ["arg1", "arg2"]
      generate:
        command: ["bar"]
        args: ["arg3", "arg4"]
```

と登録しておき、`argocd app create` 時に、プラグイン名を指定します

```
argocd app create guestbook --config-management-plugin myPlugin ...
```

（いずれも[公式マニュアル](https://argoproj.github.io/argo-cd/user-guide/config-management-plugins/) のサンプルをベースに改変）

これで、ArgoCDは対象アプリのデプロイ時、 まずコマンド `foo arg1 arg2` を実行し、続いて `bar arg3 arg4` を実行。
ArgoCDは 後者の出力をYAMLストリームのマニフェストとして解釈します。

## プラグインを使う場合の注意点

さて、ここからが本題です。

プラグイン機能を使う場合には、いくつか注意点があります。

* `command` と `args` の違い
* 実行ディレクトリ
* 使えるコマンド
* YAMLストリームが必須かどうか
* 標準出力に色々出力してしまうコマンドを使いたい場合はどうすればいいか

それぞれ見ていきます

###  `command` と `args` の違い

上の設定例を見て 「commandもargsもリスト指定だけど、何が違うんだ？」と思いませんか？（私は思いました）
結論としては、 **command の最初の引数のみが必須** で、残りの引数は command に書いても良いし、 args に書いても、分割して書いても良いです。

これは ArgoCD のソースを見ると明らかで、

```go
cmd := exec.Command(command.Command[0], append(command.Command[1:], command.Args...)...)
```

のようにコマンド名および引数の組み立てが行われるためです。（[ArgoCDのソース](https://github.com/argoproj/argo-cd/blob/master/reposerver/repository/repository.go#L592) より引用）

### 実行ディレクトリ

さて、「任意のコマンドを実行できるとっても、いったい何処で実行するんだよ？」と思いませんか？（私は思いました）
特に、何らかのコマンドによる処理を行ってマニフェストを生成している場合、そのコマンドを実行する場所が重要だったりします（例えば、私が管理しているマニフェストリポジトリでは、最終的なマニフェストを出力するのにツリーのトップディレクトリで `autoreconf -if && ./configure && make` する必要があります）

プラグイン機能を使う場合に、コマンドが実行されるタイミングでの **カレントディレクトリはアプリの情報として登録したリポジトリ内のパス** になります。
つまり、例えば「ツリーのトップディレクトリでconfigureを実行しなければならない」ようなリポジトリを持っている人は、一旦トップディレクトリに cd するようなスクリプトをプラグインとして登録してやる必要があります。

### 使えるコマンド

「プラグインで任意のコマンドが実行できるといっても、流石に何でもは無理でしょ」と思いませんか？（私は思いました）

結論としては、「ArgoCD の Docker イメージ内にあるコマンドなら何でも。それ以外が必要なら自分で用意しろ」です。
公式Dockerイメージに何がインストールされているかは、その [Dockerfile](https://github.com/argoproj/argo-cd/blob/master/Dockerfile) を読み解くと良いかと思います。
自分で用意する場合の方法としては以下の2つがあります。

* initContainers で wget 等で頑張る
* 独自のDockerイメージを作る

どちらも[公式ドキュメント](https://argoproj.github.io/argo-cd/operator-manual/custom_tools/)で解説されているので、そちらをご覧ください。

個人的には、実行時に initContainers でイメージを改変してしまうと再現性があやしいので、独自イメージ作成を推奨したいところです。

また、Jsonnetを使おうと思っている人は、「Jsonnet対応なら jsonnet コマンドもインストールされているんだろうな」と思いませんか？（私は思いました）
しかし、 デフォルトのArgoCD Dockerイメージに jsonnet コマンドは存在しません。おそらく Jsonnet の Go 実装をライブラリとして利用しているようです。
コマンドとしてのjsonnetが必要な場合は上記どちらかの方法でインストールしておきましょう。

### YAMLストリームが必須かどうか

[公式ドキュメント](https://argoproj.github.io/argo-cd/user-guide/config-management-plugins/) には「generateは標準出力にYAMLストリームを出力しなければならない」とちゃんと書いてあるんですが、もともと前述したとおり kustomize やら jsonnet やら色々な形式に対応しているわけですから「もしかしてJSONを標準出力に出しても行けるのでは？」と思いませんか？（私は思いました）

結論としては必ず、YAMLストリームである必要があります。何らかの方法でYAMLストリームに変換しましょう。
ちなみに、複数のマニフェストを `kind: List` 形式でつなげた単体のKubernetesマニフェストも受け付けてくれるので、YAMLストリームへの変換が難しい場合はそれを利用すると良いかもしれません。

Jsonnet 利用の場合は、 `kind: List` 形式でひとつにまとめたマニフェストを `std.manifestYamlDoc` で出力したあと、 `jq` 等で当該文字列部分をYAMLとして取り出しましょう。

### 標準出力に色々出力してしまうコマンドを使いたい場合はどうすればいいか

前述の通り「generateは標準出力にYAMLストリームを出力」するものでなくてはなりません。「えっ、オレの環境だと configure スクリプトとかがガッツリ標準出力にいろいろ吐くんだけど」とか思いませんか？（私は思いました）

先程の ArgoCD の ConfigMap には `generate` のほかに `init` があるのを覚えておいででしょうか？ 実は `init` の存在価値はここにあるのです。
標準出力に色々出力してしまうコマンドを実行する場合は `init` 側で実行してしまいましょう。何なら `init` ですべてを終わらせて、 `generate` は生成したマニフェストを `cat` するだけ、とかでもOKです。

## まとめ

ArgoCD のプラグイン機能を使うときの注意点についていくつか解説しました。
「Jsonnet管理しているマニフェストを configure && make で生成している」ような人にはお得な情報になったと思います（全部で3人くらいかな？）

最後までお読みいただき、ありがとうございました。

結論： ArgoCDはいいぞ。