さくらのクラウド + Kubernetesでマネージドなロードバランサを使う

f:id:febc_yamamoto:20180329174148p:plain

さくらのクラウドのマネージドなL4ロードバランサーをKubernetesから使えるようにCloud Controller Managerを実装してみました。
Kubernetesでserviceを作成する際にtype: LoadBalancerと指定することで動的にマネージドロードバランサーを作成してくれます。

GitHub: sacloud/sakura-cloud-controller-manager

今回はこの仕組みをどう実現しているか/どう使うのかについてご紹介します。

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の実装

全体としては以下のようになります。

f:id:febc_yamamoto:20180329174217p:plain

順番に解説していきます。

さくらのクラウドで利用できるロードバランサの仕様

まず、今回利用するさくらのクラウドのマネージドなロードバランサの主な仕様を押さえておきます。
主な仕様は以下の通りです。

  • DSR(Direct Server Return)方式のL4ロードバランサ
  • ルータ+スイッチまたはスイッチを使用して構築されたネットワーク内にのみ設置可能
  • VRRPを用いた冗長化に対応
  • VIPは最大10個
  • 実サーバはVIPあたり最大40台まで

参考: さくらのクラウド マニュアル: ロードバランサ

Kubernetesと統合する上で特にポイントとなるのは以下2点です。

  • ルータ+スイッチまたはスイッチを使用して構築されたネットワーク内にのみ設置可能
  • DSR方式であること

ルータ+スイッチとは、さくらのクラウドグローバルIPをブロック単位(/28〜/24)で確保するためのもので、 割り当てられたブロック内のグローバルIPは配下に接続したサーバやロードバランサなどに自由に割り振り可能となっています。

このルータ+スイッチにサーバとロードバランサを接続することでDSRでのロードバランシングを実現しています。 DSR方式自体については以下の資料などを参照ください。

f:id:febc_yamamoto:20180329174228p:plain

出典: JANOG32 OSAKA - ロードバランスを考える

この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が割り当てられていない状態です。

f:id:febc_yamamoto:20180329174156p:plain

(2) ロードバランサの作成

serviceが作成されるとCloud Controller Managerが動き出します。
まずスイッチ+ルータに割り当てられたグローバルIPブロックから未使用のグローバルIPを探します。
グローバルIPが見つかったらスイッチ+ルータの配下にロードバランサを作成します。

f:id:febc_yamamoto:20180329174200p:plain

(3) VIPの割り当て & 実サーバの登録

次にロードバランサにserviceが利用するVIPを割り当てます。このVIPがkubectl get svcなどで見えるExternal IPとなります。
VIPを割り当てた後は振り分け先となる実サーバを登録します。現時点での実装だと全Workerノードあてに振り分けるようにしています(オプションで変更可能となる予定です。)

なおVIPはIPアドレス+ポート番号の組み合わせで登録するため、service作成時に複数のポートを公開するように指定されていた場合はポート番号ごとにVIPを登録します。

この時点でVIP宛てのリクエストが各Workerノードに振り分けられるようになりました。
しかしまだ各WorkerノードのループバックにVIPが割り当てられていないため、パケットが到着しても反応できない状態です。

f:id:febc_yamamoto:20180329174205p:plain

3/30 追記:
serviceが作成されExternal IPが割り当てられると、kube-proxyによりVIP + 指定ポートへのパケットを処理するルールが各ノードのiptablesに追加されます。
このため、ループバックへのVIPの割り当ては不要です。

--- 3/30 修正: ここから不要 ---

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ドキュメント: Running cloud-controller-manager

Kubernetesクラスタのセットアップ方法によって設定方法は異なります。
例えばkubeadmの場合は各ノードの/etc/systemd/system/kubelet.service.d/10-kubeadm.confEnvironment="KUBELET_EXTRA_ARGS=--cloud-provider=externalを追記します。
(追記後にkubeletの再起動などで反映するのをお忘れなく!)

Cloud Controller Managerのデプロイ

最後にCloud Controller Managerをデプロイします。(3/30 修正: VIPの記述を除去)
デプロイは手動でyamlを投入しても良いですが、簡単にデプロイできるようにHelm Chartを用意しています。

GitHub: sacloud/helm-charts/sakura-cloud-controller-manager

まず、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 ManagerVIP-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をお試しください〜!!

以上です。