ホワイトリストとブラックリストは向きが逆だ
一言でいうと
AppProjectは、「このチームがどこから取得して、どこに置けるか」を決める境界で、ApplicationSetは、その境界の中でApplicationを大量に作り出す工場です。AppProjectで最も間違えやすいのは、クラスターリソースは許可リスト(書かれたものだけを許可)で、ネームスペースリソースは拒否リスト(書かれたものだけを禁止)という、方向が逆の2つのフィールドです。
なぜ必要なのか
Argo CDにチームが1つしかないときは、defaultプロジェクトで十分です。チームが3つになった瞬間に、疑問が生まれます。Aチームが誤ってBチームのネームスペースにデプロイしたら、どうなるでしょうか。誰かが個人のGitHubリポジトリをソースとして登録したら。アプリ1つがClusterRoleBindingを作って、自分でcluster-adminになったら。RBACだけでは、これを防げません。Argo CDのServiceAccountは、どのみち強い権限を持っており、ユーザーはArgo CDを通して、その権限を借りて使う構造だからです。そのため、Argo CDは、自分の階層で、もう一度ふるいにかけます。そのふるいがAppProjectです。
ApplicationSetは、別の種類の苦痛から生まれました。クラスター5つに、サービス20個なら、Applicationは100個です。手で管理することはできず、新しいクラスターが加わるたびに、20個をコピーしなければなりません。ジェネレーターは、この繰り返しをデータに変えます。
どう動くのか
AppProjectの主要なフィールドは、方向まで一緒に覚える必要があります。
| フィールド | 方向 | 意味 |
|---|---|---|
| sourceRepos | 許可リスト | ここに書かれたパターンのリポジトリだけを、ソースとして使えます |
| destinations | 許可リスト | ここに書かれたクラスター・ネームスペースにだけ、デプロイできます |
| clusterResourceWhitelist | 許可リスト | ここに書かれたクラスタースコープの種類だけを、作れます |
| namespaceResourceBlacklist | 拒否リスト | ここに書かれたネームスペーススコープの種類だけを、作れません |
3つ目と4つ目が逆です。クラスターリソースは、デフォルトが全面禁止なので、Namespaceだけを書けば、それだけが開きます。ネームスペースリソースは、デフォルトが全面許可なので、ResourceQuotaを書けば、それだけが閉じます。この方向を取り違えると、「塞いだと思っていたものが開いている」状態になり、ポリシーがないよりも悪いです。
syncWindowsは、時間帯で同期を開閉します。ルールを1つだけ覚えれば十分です。denyがallowに勝ちます。平日の業務時間のallowと、週末のdenyが重なると、重なった区間は禁止です。manualSync: trueを付けると、そのウィンドウでは自動同期を止め、人が押すものだけを許可します。
ApplicationSetのジェネレーターは、list(静的なリスト)、clusters(登録されたクラスター)、git(ディレクトリのスキャンまたはファイルの読み取り)、pullRequest(PRプレビュー)、scmProvider(組織のリポジトリの自動検出)が基本で、そこに2種類の組み合わせ器が付きます。matrixは、2つのジェネレーターの直積なので、クラスター3つとアプリ4つなら12個が出ます。mergeは、mergeKeysで結合して、同じキーを持つ項目の値を上書きします。全クラスター共通の値を敷いて、特定のクラスターだけ違う値を与えるときに使うのが、mergeです。
テンプレートエンジンのデフォルトはfasttemplateで、これは変数の置換だけを行います。条件や繰り返しが必要なら、spec.goTemplate: trueを有効にする必要があり、有効にした瞬間に、参照の文法もドットの付いた形に変わります。goTemplateOptionsにmissingkey=errorを指定すると、存在しないキーを黙って空文字列にする代わりにエラーを出し、名前が空のApplicationができる事故を防いでくれます。
app-of-appsは、Applicationが、ほかのApplicationを含むディレクトリを指すパターンです。ブートストラップ1回で、プラットフォーム全体が立ち上がる代わりに、親を削除すると、子がすべてpruneの対象になるため、親のprune設定に特に注意する必要があります。
現場での姿
筆者のホームラボは、クラスターが1つですが、ネームスペースでテナントを分けています。ここで学んだことは、AppProjectが描く境界と、実際のリソースの境界は、別物だという点です。AppProjectのdestinationsは、「capa-team-alphaにデプロイしてよい」という文法上の許可にすぎず、そのネームスペースがノードのリソースをどれだけ消費するかは、まったく制御できません。リソースの境界は、ResourceQuotaとLimitRangeが作ります。そして、AppProjectのnamespaceResourceBlacklistにResourceQuotaを入れるのが定石である理由が、ここから出てきます。テナントが自分のクォータを自分で増やしてしまえば、クォータではないからです。クォータはプラットフォームチームが作り、テナントは触れないようにするのが、この2つのフィールドの組み合わせです。
GPUノードが混在するクラスターでは、さらに一歩進めました。GPU Feature Discoveryが付けてくれるラベルは文字列なので、「24GB以上」のような比較セレクターが使えないため、ノードにgpu.homelab/tierのような、意味ベースのラベルを自分で付けて、ワークロードが自分の規模を選べるようにしました。ApplicationSetのlistジェネレーターに、テナントごとにこのようなラベル値を載せて送れば、1つのテンプレートで、異なるノードグループに載るアプリを作り出せます。
次のラボですること
/root/capa-project/に、AppProjectを1つと、ApplicationSetを2つ(listジェネレーター、matrixジェネレーター)作成し、そのプロジェクトが許可するテナントのネームスペース3つと、それぞれのResourceQuotaを、実際のクラスターに載せて、文法上の境界とリソースの境界を、並べて立ててみます。