KubernetesのCloud Controller Managerについて

bells17
12 min readDec 9, 2019

はじめに

これはKubernetes Advent Calendar 2019 10日目のエントリーです。
この記事ではKubernetesのCloud Controller Managerについて紹介していきます。

Cloud Controller Managerとは?

Cloud Controller ManagerとはKubernetesと任意のクラウド環境との連携を行うCloud Providerを動作させるためのKubernetesのコントローラー群を動かすためのアプリケーションです。

Cloud ProviderはKubernetes v1.15まではKubernetes本体にビルトインされており、Cloud Controller Managerが導入されるまではKubernetesとKubernetesを動作させるためのクラウド環境との連携はKubernetes本体にビルトインされたCloud Provider以外を利用することができなかったという背景があり、こういった問題を解決するために導入されたのがCloud Controller Managerとなります。

元々Kube Controller ManagerからCloud Providerに依存する処理をCloud Controller Managerに切り出し、更にKubeletといったCloud Providerに依存して動作していたコンポーネントをCloud Controller Manager経由で動作させるように変更することでCloud Providerに依存する処理をCloud Controller Managerにまとめ、Kubernetes本体にビルトインされていたCloud Providerを外部に切り出すようにしました。

Cloud Providerに依存する一部のコントローラーを別にのController Managerに切り出した

外部に切り出すことで以下のようなメリットがあるようです。

  1. 元々ビルトインされていたCloud Providerの開発・リリースサイクルがKubernetesに依存しなくなり、自由に開発・リリースしやすくなる
  2. Kubernetes本体がCloud Providerの開発を行わなくて良くなるので保守コストが減る
  3. Cloud Providerとしてのインターフェイスが定義されており、元々ビルトインされていないCloud Providerの開発も自由に行うことができる
  4. Kubernetesの各コンポーネントのバイナリサイズの削減につながる
    (↓の画像にあるようにKubernetes v1.16でCloud Providerが完全に外部化された際には各コンポーネントのバイナリサイズが70MBほど削減されたようです)
https://kubernetes.slack.com/archives/C718BPBQ8/p1566870958006200

ここらへんのメリットは11月のKubernetes Meetupで発表したKubernetesにCSIを導入することで得られるメリットと大体おんなじような感じかと思います。

Cloud Controller ManagerにCloud Providerに依存する処理をまとめることで最終的には以下のような構成となります。

https://github.com/kubernetes/website/blob/master/static/images/docs/components-of-kubernetes.png

Cloud Controller Managerで動作するコントローラーについて

Cloud Controller Managerでは公式ドキュメントにあるようにように

  • NodeController
  • RouteController
  • ServiceController

の3つになります。
(正確にはここにあるように上記に更にNodeLifecycleControllerを加えた4つのようです。またDesign ProposalにはVolumeControllerの記載があるのに公式ドキュメントでは記載がないのは、ボリューム周りはCSI対応でCloud Controller Managerとの依存関係が解消されたためと思われます。)

func newControllerInitializers() map[string]initFunc {
controllers := map[string]initFunc{}
controllers["cloud-node"] = startCloudNodeController
controllers["cloud-node-lifecycle"] = startCloudNodeLifecycleController
controllers["service"] = startServiceController
controllers["route"] = startRouteController
return controllers
}

NodeController

NodeControllerはこのようにNodeの作成・更新イベントをwatchしていて、まだNodeが初期化されていないことを示す

node.cloudprovider.kubernetes.io/uninitialized

taintがNodeにあった場合にのみ処理を行うようになっています。

処理としてはこのあたりがメインとなっていて

  • Nodeオブジェクトが持つIPアドレス情報の設定
  • NodeオブジェクトのラベルにNodeが動いているマシンのマシン情報やゾーン、リージョン情報の設定
  • node.cloudprovider.kubernetes.io/uninitializedtaintの除去

あたりとなっています。

NodeLifecycleController

NodeLifecycleControllerのメインロジックはこのようになっていて、リンク先のメソッドのコメントに書いてあるとおりなのですが、Nodeとして動いているマシンのシャットダウンや削除を検知して、シャットダウンしていることを示す

node.cloudprovider.kubernetes.io/shutdown

taintをつけたり、Nodeオブジェクトの削除を行ったりするようです。

ServiceController

ServiceControllerはcontroller-runtimeなどを利用して自作するコントローラーと大体同じような感じになっていて、特徴としては

type: Loadbalancer

のものだけを対象にしているので、workqueueに入れる箇所でこのように対象となるオブジェクトの制限を行っているところくらいかと思います。

メインのロジックとしてはこのあたりにまとまっていて、Cloud ProviderのLoadbalancerオブジェクトのGetLoadBalancerメソッドやEnsureLoadBalancerメソッドを行うことでクラウド側のロードバランサーのセットアップや削除などを行っています。

RouteController

RouteControllerは僕も詳しくないのですが各NodeへのRoute設定を行うために利用するようです。

AWSのCloud Providerの場合はこんな感じになっていてEC2のcreateRouteを実行してルート設定を行うようです。

独自のCloud Providerを開発する

自分たちが利用しているクラウド環境に対応したCloud Providerが見つからない、というケースもあるかもしれません。
その際には独自でCloud Providerを実装することでKubernetesと自身が利用しているクラウド環境を連携することが可能となります。

ただし、Cloud ProviderはCSIのような実装をするための仕様が定義されていないため、そこは他のCloud Providerの実装やKubernetesのCloud Controller Manager側の実装を読みながら実装することになります。

Cloud ProviderはCloud Controller Managerに組み込んでビルドを行い

--cloud-provider=<name>

フラグで実装したCloud Provider名を指定することで実装したCloud Providerを使用してCloud Controller Managerを起動することができます。

また、事前にKubeletは

--cloud-provider=external

フラグを付けて起動しておく必要があります。

図にするとCloud Providerの動作イメージは

Cloud Controller ManagerとCloud Providerの動作イメージ

のようになっており、組み込んだCloud ProviderがCloud Controller Managerの中で動く各種コントローラーから利用される、という流れになっています。

Cloud Providerのインターフェイスを実装する

インターフェイスは公式ドキュメントにあるようにcloudprovider.Interfaceを満たす go パッケージを実装します。

type Interface interface {
Initialize(clientBuilder ControllerClientBuilder, stop <-chan struct{})
LoadBalancer() (LoadBalancer, bool)
Instances() (Instances, bool)
Zones() (Zones, bool)
Clusters() (Clusters, bool)
Routes() (Routes, bool)
ProviderName() string
HasClusterID() bool
}

こうしてみると実装するメソッドや構造体が多いですが、ClustersやRoutesなど自身のクラウド環境とKubernetesクラスタに不要な構造体は実装する必要はありません。

ここらへんについては公式ドキュメントで紹介されている実際の実装例を参考に実装していくのが良いかと思います。

例えばDigital Oceanの場合に実装しているもの

  • LoadBalancer
  • Instances
  • Zones

のみになります。

上記のLoadbalancerは実装すればServiceリソースの

type: Loadbalancer

を利用してCloud Providerを利用してクラウドのロードバランサーを利用することができるようになります。

InstancesとZonesはNodeが起動した際の初期化処理に利用され公式ドキュメントにある

app.kubernetes.io/instance

のインスタンス情報などをCloud Providerで自動的に設定できるようになります。

このように利用したい自身のKubernetesクラスタで利用したい機能などの対応するCloud Providerのメソッドを実装することでCloud Controller Managerが提供する様々な機能を利用することができるようになります。

まとめ

KubernetesのCloud Controller ManagerとCloud Providerについて紹介し、更に独自のCloud Providerを実装する際の基礎的な情報を紹介しました。

自分でわざわざCloud Providerを実装する必要性を求められる場面はあまりないと思うので、直接自分で実装することは少ないとは思いますが、Kubernetesがどのようにクラウドと連携しているのか、実装がどのようになっているのかを知るのは面白く、Kubernetesを理解する上でも重要な要素の一つになるかと思いますので、興味を持っていただいた方はぜひCloud ProviderやCloud Controller Managerの実装を読んでみると良いかと思います。

Cloud Providerで言えば先程紹介したDigital Oceanの実装はそこまで大きくなく、読みやすい実装だと思うのでオススメです。

参考

--

--