こんにちは。kintoneメンテチームの石井です。
サイボウズでは、cybozu.comのアーキテクチャ刷新プロジェクト「Neco」を実施しています。 今回、サイボウズの「大人の体験入部」の制度を利用し、Necoプロジェクトの体験入部をしてきたので、その内容をレポートします。
本記事では、体験入部中に行なったタスクの紹介をします。 「Neco」プロジェクトや「大人の体験入部」について知りたい方は、以下の記事を参照ください。
こんなことしてました
Necoでの体験入部中、いくつかのタスクを体験(勉強?)させてもらいました。 今回はその中の一つ、ロードバランサー(MetalLB)の導入について説明します。
Kubernetesとネットワーク
はじめに、Kubernetesとネットワークについて少しだけ説明しておきます。
Kubernetesとは、分散システム上で、コンテナ化したアプリケーションの管理をしてくれるシステムです。 システムのあるべき姿を宣言(設定)しておくと、Kubernetesが自律的にコンテナのデプロイやスケール、障害の復旧もしてくれる優れもので、 これにより、大規模なデータセンターでも、少人数で管理ができるようにしようというものです。 とても便利なシステムではありますが、Kubenetesクラスタ(Kubenetesが管理するサーバのまとまりのこと)を構築するには、 いろいろとやらなければいけないことがあり、その1つにネットワークの設定があります。
Kubernetesは、データセンターのような多くのサーバからなる分散システム上での動作を想定しているのですが、 サーバ間の通信方法はデータセンター内のネットワーク構成に依存するため、Kubernetesとしての標準実装を提供できません。
例えば、Pod
(Kubernetesでのアプリケーションの動作単位)を作成する場合、
Pod
へのIPアドレスの割り当てや、経路情報の広報が必要になるのですが、
IPアドレスの体系や、通信経路の冗長度、ルーティングプロトコル等は、データセンター毎に異なるので、
利用者(Kubernetesクラスタを構築する人)が環境に合わせた設定をしなければいけません。
そのため、利用者側で具体的な通信方式をプライグインとして追加できるように、 Kubernetesでは、通信に必要な要件とインターフェースだけを定義しています。
GCPやAWSといったIaaSでは、クラウドベンダーがプラグインを作成してくれているので、 サービス利用者側がこういったことを意識することはあまりありません。 しかし、Necoでは、自社データセンター内にKubernetesクラスタを構築するため、プラグインの選定や実装をする必要がありました。
Necoのネットワーク
Necoのネットワークについては、このブログ(Cybozu Inside Out)でもいくつか記載しています。
詳細は、上記記事を参照いただければと思いますが、これまで以下のような事を実施してきました。
- CLOSアーキテクチャを採用し、East-Westトラフィック(同クラスタ内のサーバ間の通信)が増大してもスケールしやすいネットワークの設計
- BGPを使用して、ネットワーク機器ベンダの独自機能に依存しない、高信頼なネットワークの実現
- 大規模クラスタでの経路情報の増大を防ぐために、ネットワークプラグイン
coil
の実装
これらにより、Kubernetesクラスタ内での通信は問題なく動作するようになりました。 簡単に表現すると、以下の図のようなイメージです。ただし、まだ外部からの通信の対応はできていませんでした。
Kubernetesのロードバランサー
Kubernetesでは、負荷分散や冗長化のため、Service
と呼ばれる単位で複数のPod
をひとまとめに扱うことができます。
Service
にはClusterIP
やLoadBalancer
などのタイプがあり、うちLoadBalancer
タイプが外部通信の負荷分散用とされています。
例えば、レプリカ数3でnginxのコンテナを動かす、といったLoadBalancer
タイプのService
を設定した場合、Kubernetesが3つのPod
でうまいこと負荷分散してくれます。
クラスタ外部からアプリケーションにアクセスする場合は、LoadBalancer
タイプのService
に割り当てられた公開用IPアドレス(External IP)に対してアクセスします。
すると以下の図のように、ロードバランサーによって動作中のPod
にアクセスが振り分けられます。
ただし前の節で説明したように、Kubernetesではネットワークに関する標準実装は提供していません。 ロードバランサーについても、外部からExternal IPへのルーティングや、各Podへのアクセスの振り分け方法は、利用者(Kubernetesクラスタを構築する人)側でプラグインの選定や実装をする必要がありました。
そこで今回のタスクでは、社内(現行のcybozu.com)から接続する際のロードバランサーの実現性検証と各種設定を行いました。
MetalLB
Necoでは、以下の要件から社内向けロードバランサーとして、MetalLBというロードバランサーを使うことを計画しており、 今回のタスクで、MetalLBをNecoに導入する場合ための実現性検証と各種設定を行いました。
- 自社データセンターのベアメタル環境で利用する
- 経路制御は BGP で行う
MetalLBとはベアメタル環境で使用できるKubernetesのExternal Load Balancerの実装の一種です。
Googleによって開発されたシンプルなロードバランサーで、LoadBalancer
タイプのService
に対する公開用IPアドレス(External IP)の割り当てと、
External IPに対する経路情報の広報、といった2つの機能を持ちます。
少し実装に踏み込んだ話をしますと、MetalLBはController
とSpeaker
の2種類のモジュールで構成されており、
External IPの割り当てと、経路情報の広報は次の図のように実施されます。
そして、外部からのアクセスがあった場合、設定された経路情報をもとに、ルータやipvsが接続先を調整してくれます。 (そのため、MetalLB自体がロードバランスをしている感じはないですね。)
Necoに入れたらこうなった
Necoでは、各Node上でBIRDというルーティングソフトウェアが動作しており、BGPで経路の交換を行なっています。 そこで、MetalLBからの経路の広報先を自NodeのBIRDに設定し、BIRDから経路の広報を行うようにしました。
下の図は、MetalLBから外部までどのように経路が広報されるか(≒通信経路)を示した図になります。 各Nodeで動作するMetalLBから、BIRD、各種Switchと、経路情報が広報されていき、 外部からKubernetes上で動作するPodにアクセスできるようになりました。
外部からアクセスされた場合、以下の図のようにルーティングされます。 各種Switchから各NodeまではECMP(Equal-Cost Multi-Path routing)でルーティングされます。 そして、各Nodeに到達したパケットは、ipvsにより各Podに振り分けられるようになりました。
MetalLB導入時の問題点
このように書くとすんなりできたように見えますが、以下のような問題がありました。
externalTrafficPolicy=Local
を指定するとパケットがロストする(kube-proxy v1.13の不具合)
LoadBalancer
タイプのService
にはexternalTrafficPolicy
というオプションがあり、kube-proxyでアクセス先のPodを振り分ける際に、
クラスタワイドに(Nodeをまたいで)アクセス先を振り分けるか、それともノードローカルのPodにだけ振り分けるか、を設定することができます。
NecoではNode間の通信を少なくするためexternalTrafficPolicy=Local
としてノードローカルのPodにだけアクセスを振り分ける設定にしたのですが、
この時外部からPodに対してアクセスすると一定の割合でパケットがロストするといった事象が発生しました。
Necoでは、Kubernetes v1.13を使用しているのですが、このバージョンのkube-proxyには 以下の条件でパケットがロストしてしまうという不具合があり、それが原因でした。
- kube-proxyをipvsモードで動作させる
Service
の設定でexternalTrafficPolicy:Local
を指定する
この不具合はKubernetes v1.14で改修されていたため、修正パッチをあてて対応しました。
https://github.com/kubernetes/kubernetes/issues/71596
kube-proxyのオプション指定誤り(kubeletとkube-proxyの両方で--hostname-overrideを指定する)
もう一つが、kube-proxyの--hostname-overrideオプション指定の誤りです。
kubelet(Podを管理するモジュール)は、デフォルトでは/etc/hostnameの値をNode名として扱うのですが、
Node名を別に設定したい場合は--hostname-override
オプションでNode名を指定することができます。
いままで、kubeletだけがNode名を使用していると認識していたのですが、
externalTrafficPolicy=Local
を指定したLoadBalancer
タイプのService
で、ノードローカルなPodがあるかどうかを判定する際に、
kube-proxyがNode名を参照していることが分かりました。
Necoでは、kubeletだけ--hostname-overideオプションを指定していたため、kubeletとkube-proxyでNode名の判定で不整合が出てしまい動作しませんでした。
こちらはCKEを改修し、kube-proxyの起動時に--hostname-overide
オプションを指定するようにしました。
最終的にどうなった?
MetalLBを導入することで、外部からNecoのKubernetesクラスタにアクセスできるようになりました。
Kubernetesクラスタ上で動作するService
にグローバルIPアドレスを割当て、
現行のcybozu.com(社内データセンター)及びInternet(社外)からアクセスできることを確認しました。
おわりに
今回の記事では、「大人の体験入部」として「Neco」プロジェクトで行ったタスクの一部を紹介しました。
Necoプロジェクトでは、まだまだやらなければいけないことが沢山あり、一緒に働いてくれる方を募集しています。 今回の内容に興味がある、やってみたい、そもそも俺だったら一瞬で終わらせてやるぜ!っといった方がおりましたら、 ぜひご応募ください。We are hiring!