Kubernetesのカスタマイズポイントのまとめ

## この記事について


インフラエンジニアがKubernetesを使って自社の基盤を設計する上で気になるのは「Kuberneretesが自社の要件にフィットするのかどうか」です。
そういった時のためにKubernetesは拡張可能なポイントを数多く備えています。
この記事では現時点で最新のKubernetes v1.13のカスタマイズ可能なポイントを整理して自社の要件に必要な機能をKubernetesに持たせることができるのかどうかを考えるための情報をまとめます。

なお、この記事で扱うのはAPIリクエストのカスタマイズの方法であり、ネットワークやストレージまわりのカスタマイズは対象としません。
通信のパフォーマンスやデータのバックアップ、リストアについて検討されている方は[CNI(Container Network Interface)](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration)や[CSI(Container Storage Interface)](https://github.com/container-storage-interface/spec)のドキュメントをご覧ください。個人的にはCSIを使った実装の一つである[Rook](https://github.com/rook/rook)に注目しています。

## KubernetesのAPIリクエストのフロー

まず、カスタマイズをする前のKubernetesの基本的な処理の流れを確認します。KubernetesはRESTfullなAPIで設計されており、Masterで立ち上がっているAPI ServerがKubernetesの各種リソースに対するリクエストを受け付けています。

Kubernetesのアーキテクチャはとてもシンプルで、ユーザがkubectlを実行すると対象のリソースの操作内容に対応するAPI Serverのエンドポイントにリクエストが送られます。
API Serverの役割はリクエストのヘッダを確認して認証と認可を実施したあと、受け取ったリクエストをetcd上に保存することです。Kuberbernetesは各種リソースに対応するControllerがetcdを監視しており、コンテナの作成やServiceの作成、変更などの実際のアクションはControllerがetcd上のデータの追加や変更を検知して実行します。
このように、あるべき姿を定義した上でControllerが状態の変化を監視し、変化があれば対応する処理を実施することをReconciliation loopと呼びます。


## カスタマイズ機能の分類

Kubernetesにどんなことがカスタマイズできるのかを調べるということは、ユーザがリクエストを送ってからetcdにリソースの定義を書き込むまでの間にどのような情報を付与するか、またはオリジナルのリソースとそれに対応するControllerを定義するか、あるいはそのどれでもない完全に独自の仕組みを作るかに分類できます。

それぞれの選択肢には明確な違いがあり、API Serverのリクエストを補完する拡張やAPI ServerのAPIエンドポイントを増やす拡張、Kubernertesが想定する枠組みをほとんど使わない方法があります。


大まかに分類すると以下の通りです。

|手法|説明|
|---|---|
|Admission Controller|API Serverに対するリクエストの認証。認可を済ませたあとにそのリクエストをハンドルする拡張。様々な種類がある。どの拡張を実施するかはKubernetesクラスタ構築時に指定する|
|Custom Resource Definition|API Serverに新しい種類のリソースを登録する。リソースの定義や振る舞いは独自に定義できる。クラスタ構築後でもサービスを停止することなく追加可能|
|Aggregated API Server|k8sのAPI Serverの基本プロトコルでは実現できない処理を実現するための専用のAPI Serverを別途用意する拡張|

なお、最近のKubernetesの流行として押さえておくべきトピックとしてOperatorがあります。OperatorはCustom Resorce Definition（通称CRD）のうち、特定のアプリケーション、ミドルウェアに特化したものです。Operatorは特定のツールを抽象化するための強力な仕組みです。

例えば、ビッグデータのインメモリ分散処理フレームワークのApache SparkがKubernetesをサポートしたすぐ後にGoogleが[Apache SparkのためのOperator](https://japan.zdnet.com/article/35132058/)を公表しました。Spark Operetorがデプロイされたk8sクラスタであれば、以下のマニフェストを使ってKubernetesにリクエストを投げることでSparkジョブを実行することができます。　

```yaml
apiVersion: sparkoperator.k8s.io/v1beta1
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: gcr.io/spark/spark:v2.4.0
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.11-2.4.0.jar
```

もしもKubernetes上で特定のアプリ、ミドルウェアを使うことがあるのであれば、対応するOperatorが公開されているかを調べてみるのもいいかもしれません。

ただし、Operatorは想定した動作を簡単なyamlを使って実行するための仕組みです。普段の作業をテンプレートに落とし込めるので正常系の処理は楽になりますが、障害が発生したときはそのアプリやミドルに詳しい人が必要であることには変わらないので、Operatorがあれば運用の全てが解決するわけではないことは忘れないようにしましょう。
例えばorg.apache.spark.examples.SparkPi以外のSparkアプリケーションを実行すると毎回SparkのMasterのPodがOOMで死ぬといった場合はOperatorが対処できる範囲外で、`spark.driver.memory`を見直してDriverに割り当てるメモリを増やすなどSparkの知識を必要とするチューニングが必要になります。

### Admission Controller

Admisstion Controllerは最もシンプルで簡単なカスタマイズ手法です。
ユーザからリクエストを受けとったAPI Serverが認証と認可を済ませた後に、そのリクエストに含まれるパラメータを差し替えたり認可とは別に実行そのものを許可するかどうかを制御することができます。
Kubernetesがあらかじめ用意している複数のポリシーから選択してAPI Serverに設定することができます。

Kubernetes v1.13時点では以下がデフォルトで設定されています。

`NamespaceLifecycle`,`LimitRanger`,`ServiceAccount`,`PersistentVolumeClaimResize`,`DefaultStorageClass`,`DefaultTolerationSeconds`,`MutatingAdmissionWebhook`,`ValidatingAdmissionWebhook`,`ResourceQuota`,`Priority`

API Server起動時に`--enable-admission-plugins`オプションにカンマ区切りで書くことで他のAdmission Controllerの追加や削除をすることができます。 

Admission Controllersは数が多いのでここでは個別の説明は省きます。
詳細を知りたい方は以下のリンク先にそれぞれの説明がありますので参照してください。

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/




### Custom Resource Definition

Custom Resource Definition（CRD）はAPI Serverに新規のエンドポイントを登録するための仕組みです。新しいリソースの定義をCRDとして登録すると、API Serverにそのリソースのエンドポイントを新規に追加することができます。

CRDだけではAPI Serverにリクエストのエンドポイントを追加することしかできません。追加したリソースの挙動はそのリソースのCRDに対応するCustom Resource Controllerが決定します。
このControllerは自分で実装した上でKubernetesクラスタ上にデプロイする必要があります。
デプロイされたContollerは独自のロジックに従い対応するリソースに対してReconciliation loopを実行していきます。

Custom Resource DefinitionとCustom Resource Controllerを実装するためのSDKの例として以下があります。

* kubebuilder
* Operator SDK
* Metacontroller

余談ですが、通常だと1つのリソースに対して1つのControllerが存在するのですが、PodやDeploymentなどデフォルトで利用可能なリソースのControllerはKubernetesクラスタ構築時にkube-controller-managerという名前で1つのPodとしてデプロイされています。


### Aggregated API Server

Aggregated API ServerはKubernetesの標準コンポーネントであるAPI Serverではなく新しく用意する別のServerで処理を実現するための仕組みです。
API Serverはユーザから受け取ったリクエストを処理する機能を持つことはこれまで説明してきましたが、実はその処理を他のServerを委譲する抽象化レイヤを持っています。
デフォルトのAPI Server以外にユーザから受け取ったリクエストを処理するAPI ServerをAggregated API Serverといいます。
Aggregated API Serverの例としてmetrics serverやservice catalogがあります。

Aggregated API Serverが登録されているAPI Serverはユーザのリクエストの認証と認可を済ませると該当する処理をAggregated API Serverに丸投げします。
Aggregated API Serverは受け取ったリクエストを内部で処理し、結果をAPI Serverと通じてユーザに返します。
Custom Resource DefinitionではAPI Serverが全ての処理を実施した上でReconcilation loop処理を実施していましたが、Aggregated API Serverではさらにその前でhookすることが可能になるため自由度はCustom Resource Definitionよりも高いです。

Aggregated API Serverを実装するためのSDKとしてapiserver-builderが用意されています。

## カスタマイズ手法の比較


|手法|追加実装|拡張の自由度の高さ|
|---|---|---|
|Admission Controller| 不要| 低い|
|Custom Resource Definition| 必要| ある程度制約があるが比較的自由|
|Aggregated API Server| 必要| ほとんど制約がなく自由|

導入を検討する場合はまず最初に使えるAdmission Controllerがないかを調査し、必要な機能が不足していると感じればCustom Resource Definitionを検討し、それでも駄目ならAggregated API Serverを採用する順番がおすすめです。
自由度の高さはそのまま運用コストと利用方法を普及するコストの増加につながりますので、運用者が少ない場合はできるだけAdmission Controllerの利用に留めるのが正解です。