Kubernetesで数千個のCRDを使えるようにスケールさせる
(blog.upbound.io)Upboundが開発したCrossplaneは、Kubernetes上でクラウド control plane を提供する。そのため、AWS、Azure、GCPのようなクラウドリソースに対応するための数百個のCRD(Custom Resource Definition)がある。CrossplaneではこれをMR(Managed Resource)と呼ぶ。
高度なKubernetesユーザーであっても、通常はせいぜい数十個程度のCRを運用するのが適切だが、Crossplaneでは数百個のMRを使う必要があるため、Kubernetesがどれだけ多くのCRDを扱えるのか、その限界を調べ始めた。
これはクライアント側の問題とサーバー側の問題に分けて考えられる。
クライアント側の問題
- クライアントでは discovery の過程が問題になる。
kubectlのようなクライアントは、サーバーがどのAPIを提供しているかを調べるために discovery を行うが、その際にすべてのAPIエンドポイントを一度たどる必要がある。- CRはAPIエンドポイントとして提供される。
https://example.org/apis/rds.aws.upbound.io/v1/instances/cool-dbのようなMRを参照するには、https://example.org/apis/でサポートされるAPIグループを見つけ、https://example.org/apis/rds.aws.upbound.ioでサポートされるバージョンを見つけ、https://example.org/apis/rds.aws.upbound.io/v1でサポートされるCRを見つける必要がある。- AWS、Azure、GCPのクラウドプロバイダーを提供するCrossplane MRはおよそ2,000個あり、300のAPIグループとバージョンに分かれている。
- クライアントは discovery のために300件のHTTPリクエストを送ることになる。
- 現在のネットワーク環境では大きな問題ではないが、見つかった問題はレートリミットとキャッシュだった。
クライアントのレートリミット
- 平均で毎秒5リクエストのレートリミットがかかっており(100件までバースト可能)、10分ごとに discovery キャッシュを無効化する。
- これはレートリミットを引き上げることで解決でき、現在も毎秒5リクエストが既定だが、300まで引き上げられるようになった。
kubectlv1.22でこの制限を引き上げてほしいというIssueが提起され、discovery キャッシュも10分ではなく5時間に調整されて、Kubernetes v1.25ではクライアントの引き上げられた上限を利用できる。
クライアントキャッシュ
- レートリミットを無効にしてテストしても、300個のAPIグループを照会するのに20秒近くかかった。
- 当初はネットワークの問題かと思われたが、調べてみるとキャッシュファイル参照時に発生する問題だった。
- Kubernetes 1.25で修正され、macOSでは25倍高速になり、Linuxでは2倍高速になった。
今後のクライアント改善
- クライアント側でレートリミットをかけるのは合理的ではあるが、実際にはサーバーを十分に保護できない。
- Kubernetes 1.20で導入されたAPI Priority and Fairness(AP&F)は、サーバー側でキューイングとトラフィック制御を提供し、APIサーバーを保護する。
- discovery 用の単一の集約HTTPエンドポイントがKEPで承認され、1.26でアルファサポートされる予定だ。
サーバー側の問題
OpenAPIスキーマ計算
- 数百個のCRDを登録した後、APIリクエストがほぼ1時間にわたって遅くなる現象が見つかった。
- プロファイリングによって、この問題がOpenAPI v2スキーマを計算するロジックにあることが分かった。
- CRDを追加または更新すると、OpenAPIコントローラーがCRのswagger仕様をビルドし、すべてのCRのswaggerと結合して1つの大きな仕様にしたうえでJSONにシリアライズし、
/openapi/v2として提供する。 /openapi/v2を遅延計算するようにし、実際にCRに対するエンドポイントがリクエストされたときに計算されるよう修正した。- この修正はv1.24.0に入り、1.20.13、1.21.7、1.22.4にバックポートされた。
etcdクライアント
- OpenAPIの問題を解決した後に見つかった新たなボトルネックである。
- APIサーバーはCRDごとに4MiBのメモリを使用することが分かった。
- これはGKE、EKSのようなマネージドKubernetesではさらに問題で、APIサーバーのCPUとメモリに制限があるためだ。リソースがさらに必要ならAPIサーバーを自動的にスケールしてくれるが、残念ながらCRD追加はそのスケール判断要素ではない。そのため、APIサーバーが繰り返しOOM killedされない限りスケールしない。
- GKE、AKE、EKSでテストしたところ、自動ヒーリングは行われるが、APIサーバーが5秒から1時間程度利用できなかった。クラスター全体が完全に停止するわけではないが、すべての reconciliation が止まった。
- プロファイリングによって、ロギングライブラリのZapがメモリの20%を占めていることが分かった。
- APIサーバーはCRのバージョンごとに1つのetcdクライアントを生成し、各etcdクライアントはZapロガーを生成する。
- その結果、重複したロガーによってメモリが増加しただけでなく、APIサーバーとetcdの間に不要なTCPコネクションも発生していた。
- すべてのCRエンドポイントで1つのetcdクライアントを使うべきだとメンテナーも同意したが、Kubernetes 1.25リリースが迫っていたため全面的な修正は難しく、まずはより小さい変更として、すべてのetcdクライアントが1つのロガーを共有するよう修正した。
- これは1.25に含まれる予定で、1.22、1.23、1.24にもバックポートされる見込みだ。メモリ使用量を20%削減する。
今後のサーバー側改善
- CRのバージョンごとに作っていたetcdクライアントを、トランスポートごとに1つ(etcdクラスターごと)作るよう変更する予定だ。
- GKE、EKS、AKEのエンジニアリングチームとも協業し、多数のCrossplane CRDインストールに対応できるよう作業を進めている。
2件のコメント
無料化 -> 無効化
クライイアント -> クライアント