KubernetesのコアとなるControllerとは?
コンテナオーケストレーションツールとして有名なKubernetes。代表的な特徴を挙げるとサービスディスカバリとロードバランシングがある。Serviceリソースを定義することで、多数起動されているコンテナに接続する経路を自動で調整してくれるものだ。他にもコンテナでボリュームを使う時のストレージオーケストレーションもある。
他にも自動化されたロールアウトやロールバック、自動ビンパッキング、セルフヒーリングなど挙げるときりがないものの、bells17氏は「個人的にはKubernetes Controllerがすべてを支えていると言っても過言ではないと思う」と言う。そこで今回はKubernetesの基盤となるControllerにフォーカスを当てて解説する。
まずはざっとKubernetesの全体像から見ていこう。図の左がKubernetesのコントロールプレーン、右下がノードでクラスタに参加しているサーバーだ。
コントロールプレーンには「etcd」というキーバリューストア(データベース)があり、Kubernetesはこれをデータストアとして使用している。これと直接やりとりするのはkube-api-serverというサーバーだけで、他のコンポーネントはすべてこのサーバーを経由して処理する仕組みになっている。
コントロールプレーン側でkube-api-serverとやりとりする代表的なコンポーネントにはkube-controller-managerとcloud-controller-managerの2種類のマネージャーと、kube-schedulerの他、KubernetesのWorker Node上で動作するkube-proxyとkubeletがある。cloud-controller-managerから伸びているクラウドは(AWSなどのパブリッククラウドよりは)Kubernetes実行基盤とイメージするといいだろう。kube-schedulerは新たにコンテナを実行する時に適切なノードを選択するものとなる。
Controllerはざっくり言うと、監視対象リソースの変更や一定時間経過などのイベントをトリガーとして調整ループ(Reconciliation Loop)を実行する。調整ループとは「公式ドキュメントには登場しないもののよく使われる。YAMLなどで宣言されたあるべき状態と現在の状態と比較して、変更や調整を行う制御ループ」とbells17氏は説明する。
Kubernetesにおけるkube-api-server以外のコンポーネントは、Controllerやそれに似たイベント駆動の仕組みがいろんなところで採用されている。そのためKubernetesでよく言われる「“宣言的API”はControllerの組み合わせで成り立っていると言っても過言ではない」とbells17氏は言う。
なおControllerはKubernetes開発者だけが作るものではなく、ユーザーが自作しデプロイすることでKubernetesの機能を拡張することもできる。KubernetesのCustom Resource(CR)というユーザー独自にデータを定義できる仕組みと、独自のControllerを組み合わせれば独自の仕組みを開発することもできる。これは「Kubernetes Operator」とも呼ばれている。
例えば、CRを利用してロードバランサーで使用する証明書の発行や管理を行う「cert-manager」、Kubernetesの名前空間を階層化できる「Hierarchical Namespace Controller」、GitOptsによるデプロイを実現する「Argo CD」などがある。bells17氏は「こうしたことを自作するフレームワークもあるので、Kubernetesを基盤にして独自プラットフォームの機能をいろいろ作りあげている会社さんも見かけます」と話す。
Kubernetes Controllerの仕組み
次はKubernetesがどのようにControllerを利用しているのかを見ていこう。
先述したように、コントロールプレーンには「kube-controller-manager」と「cloud-controller-manager」がある。基本は前者でControllerを大量(大抵30〜40個)に実行している。後者はKubernetes実行基盤(クラウド)と何らかの連携するためのControllerになる。例えばクラスタに参加しているワーカーノードにノードのラベルを設定するとか、クラウド側のロードバランサーと連携するなどだ。
前者の「kube-controller-manager」では、実際にどのようなものがあるか見ていこう。Pod関連のControllerには下図のようなものがある。
例えばKubernetesでPodをデプロイする時なら、図の左上から下に向かって処理が流れていく。Deploymentというリソースが作られると、deployment-controllerがReplicaSetを作る。続いてReplicaSetのリソースを監視しているreplicaset-controllerがPodのリソースを作る。
実際はローリングアップデートなどで複雑になるものの、基本的にはこのような数珠つなぎでいろんな機能を実現している。他にもDaemonSetやStatefulSetのような他リソース管理でも対応するControllerがいて、Jobを実行するCronJobリソースに対しても対応するControllerがいる。なおControllerとリソースは必ずしも1対1ではなく、1つのリソースに対して複数のControllerが働くこともある。
今度はkube-proxy、ワーカーノードで動作するコアコンポーネントの1つ。主にServiceリソースとEndpointSliceリソースを基にネットワーク設定を行い、Serviceのエンドポイントにアクセスした際にPodにリクエストが届くようにする。動作モードは3種類あり、デフォルトはiptablesとなる。他にはipvsと、Windows用のkernelspaceがある。なおIPv4とIPv6のデュアルスタックもサポートされている。
kube-proxyは主にNATの設定を行う。外部ネットワークからパケットを受信すると、まずはNATのPREROUTINGが動き、KUBE-SERVICESというkube-proxyが設定するチェーンにジャンプし、CNI(Container Network Interface)プラグインによりPodに割り当てられる。
iptablesの設定の流れは下図のようになる。先述したEndpointSlice(画面上、右から2つ目のリソース)があり、これを監視しているendpointSliceConfigがサービスリソースの変更やエンドポイントサービスの変更を検知すると、それをトリガーにproxier.syncRunnerなどが次々に呼ばれてiptables設定が適切になるように調整される。
こうした仕組みがあるため、もし手動でiptablesの設定が書き換えられたとしても、たとえ壊されたとしても、さほど時間かからず自動的に修復される。Kubernetesにおけるあるべき状態を維持する「セルフヒーリング」と呼ばれるものは、このような形で成り立っている。
イベント駆動でリソースを管理するkubelet、ボリュームを利用可能にするCSI Driver
続いてはkubelet、Kubernetesクラスタの各ノードで動作するコンポーネントだ。コンテナランタイムと連携し、さまざまなイベントをトリガーにワーカーノードで動かすコンテナと関連リソースを管理する。
例えばAPIサーバー(図の左上)から新しいPodが登録されたとか、PLEG Worker(図の上、右から3つ目)が実行中のコンテナの設定の変更を検知したとか、コンテナの死活判定するワーカーのLiveness Probe(図の上、右から2つ目)がコンテナの異常を検知したとか、こうしたものを検知するとイベントを発火する。
APIサーバーで新しいPodが作成されて、そのPodに対応するコンテナグループを作るとすれば、Handler、Pod Worker、Runtime Managerを経てコンテナを起動する。その途中でPodが使用するSecretやConfigMapの取得処理、各種probe、ボリュームの初期化などといったコンテナの管理起動を行う処理が様々なイベントをトリガーとして行われるアーキテクチャとなっている。
最後にCSI Driver、ボリュームを利用するためのプラグインだ。下図の緑の部分がCSI Driver、赤がサイドカーと呼ばれるものでCSI Driverと一緒にデプロイする独立したKubernetes Controllerになる。サイドカーにはexternal-attacherやexternal-provisionerなどがありボリュームのプロビジョニングなどを実施する際にKubernetesのリソース監視を行い、CSI Driverへボリューム処理を依頼する。例えば「PersistentVolumeClaim(PVC)リソース」という名前のボリュームを作成すると、サイドカー(external-provisioner))がPVCの作成を検知して、ボリューム作成処理が流れる。
払い出したボリュームを利用するまでは下図のような流れとなる。kube-controller-managerの中でAttach/Detach Controller(図の画面上中央)が稼働していて、このControllerが管理するVolumeAttachementObjectリソースを監視する別のサイドカー(external-attacher)がノードに対してボリュームのアタッチを行い、最後にkubeletがノード上で動作するCSI Driverを通してボリュームの初期化やマウントが行われる。
先に「Kubernetesでは自作Controllerを利用できる」という話をしたように、ここのCSI Driverは外部から実行するControllerの好例となる。
まとめとしてbelles17氏は「KubernetesはこのようにController(とそれに類いするアプリケーション)の組み合わせで、いろんなものを実現しています」と述べた。なお今回のセッションの内容をより詳しく知りたければ、下記資料で確認できる。
クラウドインフラ領域のスペシャリストがシステムの戦略策定から設計・実装・運用までワンストップでサポート
SRE、Platform Engineering、k8s、コンテナセキュリティなど、Sreakeが提供するサービスはお客様のエンジニア組織における技術戦略の策定から設計、実装、そして運用まで、クラウドインフラ領域における様々な領域のプロフェッショナルが包括的に支援します。
本記事で興味を持たれた方は、お問い合わせページよりご連絡ください。