さくらのクラウドのマネージドなL4ロードバランサーをKubernetesから使えるようにCloud Controller Manager
を実装してみました。
Kubernetesでserviceを作成する際にtype: LoadBalancer
と指定することで動的にマネージドロードバランサーを作成してくれます。
今回はこの仕組みをどう実現しているか/どう使うのかについてご紹介します。
3/30 追記:
コメントで「ループバックへのVIP設定は不要」とのご指摘をいただきました。
改めて0からクラスタを再構築して試したところ上手くいきましたのでVIP関連の記述を修正しました。
id:masaya_aoyama さんご指摘ありがとうございました!
Kubernetesでtype: LoadBalancer
なserviceを作成するには
GKEやAKSではtype: LoadBalancer
と指定してserviceを作成することでマネージドなロードバランサーを利用可能となっています。
せっかくさくらのクラウドでKubernetesを使うならやっぱりtype: LoadBalancer
でマネージドなロードバランサでserviceを使いたいですよね。
でも単純にKubernetesクラスタをさくらのクラウド上にデプロイしただけではtype: LoadBalancer
は利用できません。
というのも、type: LoadBalancer
なserviceはKubernetesのCloud Controller Manager
というクラウド(等)のプラットフォームとのインテグレーションを担当する部分で
実装されており、各クラウドプラットフォームごとにCloud Controller Manager
を実装する必要があるためです。
参考:Kubernetes ドキュメント: Concepts Underlying the Cloud Controller Manager
Cloud Controller Manager
の実装としては、Kubernetesのソースツリー配下に以下のプラットフォーム向けの実装があります。(v1.10時点)
- aws
- azure
- cloudstack
- gce
- openstack
- ovirt
- photon
- vsphere
ここにないプラットフォームについては独自に実装することでKubernetesと統合可能となっています。 (実装すべきインターフェースはこちらに定義されています -> kubernetes/pkg/cloudprovider/cloud.go)
既にDigitalOceanやOracle Cloud Infrastructureといったプラットフォームについては実装が公開されていますし、既存の実装を改造することでオンプレミス向けにCloud Controller Manager
を実装した事例なども公開されています。
参考(スライド): Kubernetes meetup #7 スライド
参考(記事):Kubernetes をいじって Hardware LoadBalancer で “type LoadBalancer” を実現してみた @Kubernetes meetup #7
Cloud Controller Manager
の実装については↑↑の記事が素晴らしいですのでオススメです。
ということで、さくらのクラウドでtype: LoadBalancer
なサービスを使うにはCloud Controller Manager
が必要ということなので早速実装してみました。
さくらのクラウド向けのCloud Controlelr Manager
の実装
全体としては以下のようになります。
順番に解説していきます。
さくらのクラウドで利用できるロードバランサの仕様
まず、今回利用するさくらのクラウドのマネージドなロードバランサの主な仕様を押さえておきます。
主な仕様は以下の通りです。
- DSR(Direct Server Return)方式のL4ロードバランサ
ルータ+スイッチ
またはスイッチ
を使用して構築されたネットワーク内にのみ設置可能- VRRPを用いた冗長化に対応
- VIPは最大10個
- 実サーバはVIPあたり最大40台まで
Kubernetesと統合する上で特にポイントとなるのは以下2点です。
ルータ+スイッチ
またはスイッチ
を使用して構築されたネットワーク内にのみ設置可能- DSR方式であること
ルータ+スイッチ
とは、さくらのクラウドでグローバルIPをブロック単位(/28〜/24)で確保するためのもので、
割り当てられたブロック内のグローバルIPは配下に接続したサーバやロードバランサなどに自由に割り振り可能となっています。
このルータ+スイッチ
にサーバとロードバランサを接続することでDSRでのロードバランシングを実現しています。
DSR方式自体については以下の資料などを参照ください。
このDSR方式のロードバランサを使う場合は以下のような設定をサーバに対して行う必要があります。
- VIPに応答するためにループバックにVIPを設定
- VIP宛のARPに応答しない設定
3/30 修正 & 追記:
今回はロードバランサ作成時に動的にVIPを確保する仕組みとなっており、VIP確保時に各Workerノードに対してVIPをループバックに設定する必要があります。この辺りを動的に行うための仕組みも今回実装しています。
ループバックへのVIP設定はservice作成時にkube-proxy
によってiptablesに追加されるルールにより処理されるため設定不要です。
Cloud Controller Manager
の処理の流れ
今回中心となるのはControl Plane上で稼働するCloud Controller Manager
です。
service作成時に以下のような流れでロードバランサの作成やVIP割り当てなどを行ってくれます。
(1) serviceの作成
まずはkubectl expose
などでtype: LoadBalancer
と指定してserviceを作成します。
この時点ではまだロードバランサが作成されておらず、各WorkerノードにVIPが割り当てられていない状態です。
(2) ロードバランサの作成
serviceが作成されるとCloud Controller Manager
が動き出します。
まずスイッチ+ルータ
に割り当てられたグローバルIPブロックから未使用のグローバルIPを探します。
グローバルIPが見つかったらスイッチ+ルータ
の配下にロードバランサを作成します。
(3) VIPの割り当て & 実サーバの登録
次にロードバランサにserviceが利用するVIPを割り当てます。このVIPがkubectl get svc
などで見えるExternal IP
となります。
VIPを割り当てた後は振り分け先となる実サーバを登録します。現時点での実装だと全Workerノードあてに振り分けるようにしています(オプションで変更可能となる予定です。)
なおVIPはIPアドレス+ポート番号の組み合わせで登録するため、service作成時に複数のポートを公開するように指定されていた場合はポート番号ごとにVIPを登録します。
この時点でVIP宛てのリクエストが各Workerノードに振り分けられるようになりました。
しかしまだ各WorkerノードのループバックにVIPが割り当てられていないため、パケットが到着しても反応できない状態です。
3/30 追記:
serviceが作成されExternal IP
が割り当てられると、kube-proxy
によりVIP + 指定ポートへのパケットを処理するルールが各ノードのiptablesに追加されます。
このため、ループバックへのVIPの割り当ては不要です。
--- 3/30 修正: ここから不要 ---
VIP-Agent
が各WorkerノードにVIPを割り当てる
VIP-Agent
が各WorkerノードにVIPを割り当てる次に各WorkerノードにVIPを割り当てます。これはCloud Controller Manager
で実装しても良いのですが、今回は別途エージェントを用意しました。
エージェントはVIP-Agent
という名前で、Kubernetesオブジェクトの変更を監視するControllerとして実装しています。
画像: https://cdn-ak.f.st-hatena.com/images/fotolife/f/febc_yamamoto/20180329/20180329174211.png
VIP-Agent
は以下ようなyamlを用いて特権モード(privilege=true
)かつhostのネットワークを直接利用する形で起動します。
DaemonSet
ですので各Workerノードで起動し常駐することになります。
apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: sakura-vip-agent namespace: kube-system spec: template: metadata: labels: name: sakura-vip-agent spec: serviceAccountName: sakura-vip-agent hostNetwork: true containers: - image: sacloud/sakura-vip-agent:0.0.1 name: sakura-vip-agent securityContext: privileged: true volumeMounts: - mountPath: /dev name: dev volumes: - name: dev hostPath: path: /dev
このような形にしておくことでserviceにExternal IP
が割り当てられたことを検知し、ホスト側のインターフェースの設定を変更することが可能となります。
今回はserviceのExternal IP
にはVIPが割り当てられますので、それをループバックに設定する役割となっています。
これでVIP宛てのリクエストを各workerが処理できるようになりました。
--- 3/30 修正: ここまで不要 ---
ここまでくれば後はkube-proxy
がPodまで届けてくれます。
(この辺の詳細はQiita:GKE/Kubernetes でなぜ Pod と通信できるのかが詳しいです)
さくらのクラウド向けCloud Controller Manager
の使い方
次に実際の利用方法についてです。さくらのクラウド向けのCloud Controller Manager
を利用するには以下の作業が必要です。
- 各workerノードを
スイッチ+ルータ
配下に作成 - 各workerノードでVIP宛てのarpに応答しないための設定
- kubeletの起動パラメータとして
--cloud-provider=external
を指定 Cloud Controller Manager
のデプロイ(今回はhelmを利用)
各workerノードをスイッチ+ルータ
配下に作成
Kubernetesクラスタ構築の際にworkerノードをスイッチ+ルータ
配下に配置します。
注意点としては、各ノードのホスト名をクラスタ内で一意にすることと、ホスト名とさくらのクラウド上のサーバ名を同じにしておくことがあります。
これはCloud Controller Manager
がworkerノードを検知するのに各ノードのホスト名を元にさくらのクラウドAPIで各サーバを探しに行くようにしているからです。
各workerノードでVIP宛てのarpに応答しないための設定
次にVIP宛てのarpに応答しないようにカーネルパラメータを設定します。
CentOSの場合だと以下のように指定します。
# /etc/sysctl.confに以下2行を追記
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
# 追記した内容を反映
$ sysctl -p
kubeletの起動パラメータとして--cloud-provider=external
を指定
次にkubeletの起動パラメータを追加します。Kubernetes本体のソースツリー配下にないCloud Controller Manager
を利用する場合は--cloud-provider=external
というパラメータを指定する必要があります。
Kubernetesクラスタのセットアップ方法によって設定方法は異なります。
例えばkubeadmの場合は各ノードの/etc/systemd/system/kubelet.service.d/10-kubeadm.conf
にEnvironment="KUBELET_EXTRA_ARGS=--cloud-provider=external
を追記します。
(追記後にkubeletの再起動などで反映するのをお忘れなく!)
Cloud Controller Manager
のデプロイ
最後にCloud Controller Manager
をデプロイします。(3/30 修正: VIPの記述を除去)
デプロイは手動でyamlを投入しても良いですが、簡単にデプロイできるようにHelm Chartを用意しています。
まず、Cloud Controller Manager
はさくらのクラウドAPIを利用するためAPIキーの設定が必要になります。
helmでのインストール時にAPIキーを指定してください。
# helm initを実行(まだしていない場合のみ)
$ helm init
# helmにsacloudリポジトリを追加
$ helm repo add sacloud https://sacloud.github.io/helm-charts/
# デプロイ実行
$ helm install sacloud/sakura-cloud-controller-manager --name sakura-ccm \
--set sacloud.accessToken=<APIトークン> \
--set sacloud.accessTokenSecret=<APIシークレット> \
--set sacloud.zone=<ゾーン(is1a/is1b/tk1a)>
3/30 修正: VIP-Agent
の記述を除去
後は少し待てばCloud Controller Manager
とが起動するはずです。VIP-Agent
(kube-systemネームスペースにデプロイされますので、確認はkubectl get all -n kube-system
のようにしてください)
これで準備が整いました。 次に実際にserviceを作成してみます。
Podを作成しtype: LoadBalancer
なserviceを作成してみる
まずはserviceでexposeするコンテナを作成します。今回は例としてload-balancer-example
という名前でnginxを起動してみます。
kubectl run load-balancer-example --replicas=2 --labels="run=load-balancer-example" --image=nginx:latest --port=80
これでload-balancer-example
という名前のDeploymentが作成され、ReplicaSet/Podも作成されます。
次に作成されたDeploymentをexposeしてserviceを作成します。その際にtype: LoadBalancer
の指定をしておきます。
kubectl expose deployment load-balancer-example --type=LoadBalancer --name=load-balancer-example
service作成直後はExternal IP
が<pending>
となります。
$ kubectl get svc load-balancer-example
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
load-balancer-example LoadBalancer 10.107.97.231 <pending> 80:30900/TCP 1m
External IP
が割り当てられるまでしばらく待ちます。割り当てられると以下のような表示となるはずです。
$ kubectl get svc load-balancer-example
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
load-balancer-example LoadBalancer 10.107.97.231 nnn.nnn.nnn.nnn 80:30900/TCP 3m
後はブラウザなどでExternal IP
宛てにアクセスすると表示されるはずです。
後片付け
serviceを削除するとロードバランサも削除されます。(3/30 修正: VIPの記述を削除)
以下のコマンドでserviceとdeploymentを削除しておきましょう。
$ kubectl delete svc load-balancer-example
$ kubectl delete deploy load-balancer-example
終わりに
ということでさくらのクラウド+Kubernetesでもtype: LoadBalancer
なserviceが使えるようになりました。
今回実装したCloud Controller Manager
はまだ実験段階ですが、これからドッグフーディングということで実環境で使いつつブラッシュアップしていきたいと考えています。
さくらのクラウドでKubernetesを利用する際はぜひCloud Controller Manager
をお試しください〜!!
以上です。