Kubernetesネットワークの最適化
2019年12月13日 金曜日
CONTENTS
Twitterフォロー&条件付きツイートで「バリーくんぬいぐるみ」を抽選で20名にプレゼント!
応募期間は2019/11/29~2019/12/31まで。詳細はこちらをご覧ください。
今すぐツイートするならこちら→ フォローもお忘れなく!
【IIJ 2019 TECHアドベントカレンダー 12/13(金)の記事です】
本稿はIIJ 2019 TECHアドベントカレンダー 12/6(金)の記事の続きです。
Kubernetesのネットワークモデルはシンプルに3行で定義されています。
- ノード上のPodは、すべてのノード上のすべてのPodとNATすることなく通信可能であること
- ノード上のエージェント(通常はkubelet)は、そのノード上のすべてのPodと通信可能であること
- あるノード上のホストネットワークに接続されたPodは、すべてのノード上のすべてのPodとNATすることなく通信可能であること
それゆえ、実際のクラスタネットワークをどのように構成するかはかなりのウェイトで利用者に任されていると言えます。そのため、Kubernetesクラスタを立ち上げるとき、利用者は最低でも二つの選択をしなければいけません。
- Podネットワークをどのように構成するか(CNIをどうするか?)
- イングレスをどのように構成するか(Cloud ProviderとIngress Controllerをどうするか?)
Podネットワークには前回少し触れたので、本稿ではIKE(IIJ kubernetes Engine)で後者のイングレスをどのように構成しているのかを紹介したいと思います。
Kubernetesのネットワークは非効率か
KubernetesのPodネットワークは通常クラスタ内部に閉じたプライベートネットワークであるため、例えばインターネットからPod上のWebサーバへアクセスしたければ、クラスタの外にロードバランサを用意するのが一般的です。もっとも簡単な方法でKubernetesクラスタを構築すると、おそらく下図のようになるでしょう。クラスタの外にexternal LoadBalancer、クラスタの中にinternal LoadBalancerを置いた構成です。external LoadBalancerが存在せず、service type NodePortだけでクラスタへアクセスするケースもあるでしょうが、それは実用目的で作られたクラスタではないはずです。
なぜロードバランサが二段構成になるかと言えば、Ingress Controllerを容易に準備するためです。簡単に解説すると、Kubernetesからロードバランサをコントロールするには、Cloud ProviderとIngress Controllerが必要です。前者はservice type LoadBalancerリソースを通じてL4ロードバランサとして利用する場合に、後者はIngressリソースを通じてL7(HTTP/HTTPS)ロードバランサとして利用する場合に必要です。両者ともにロードバランサを制御するデバイスドライバのようなものですから、Kubernetesに含まれているものではなく、ロードバランサ製品に応じて用意するものです。ですから、Cloud ProviderやIngress Controllerが提供されているロードバランサをexternal LoadBalancerに使える場合はよいのですが、もし無ければクラスタの中にこれらを利用できるロードバランサをデプロイし、二段階構成で使うということが行われるわけです。internal LoadBalancerにはnginx-ingressやhaproxyが使われることが多いでしょうか。
ただ、この構成はごらんのとおり効率の悪いものです。多段プロキシであるばかりか、serviceのexternalTrafficPolicyがCluster(デフォルト)の場合は運が悪いと図中の線のようにトラフィックが流れることがあります(左のkube-proxyを通るケース)。実に長い道のりですね。
ですから、プロダクション環境でKubernetesを利用するならば、external LoadBalancerにはCloud ProviderとIngress Controllerを用意したいところです。そうすれば、internal LoadBalancerが不要になります。また、そのCloud ProviderやIngress ControllerはexternalTrafficPolicy: Localをサポートしていることが理想です。そうすれば、下図のようにだいぶすっきりした状態になります。二段階構成にはそれなりのメリットがあるため特に否定するつもりはありませんが、この状態で利用できることが望ましいと言えます。
そのためIKEにはCloud ProviderとIngress Controllerが実装されています。
vtm-ingress-controller
IKEの各種プラグイン類の多くは自社で開発したものですが(CNIはcalico)、Ingress ConitrollerについてはPulseSecure社が開発し、IIJがKubernetesの運用的観点から協力させていただく体制で実装されました。というのは、IKEのexternal LoadBalancerにはPulseSecure社の仮想アプライアンスロードバランサであるVirtual Traffic Manager(以後vTMと略す)を利用しているためです。vTMはIIJのパブリッククラウドサービスであるIIJ GIO上で長年利用され、数千ライセンスが稼働する隠れたヒット商品なのです。
このvtm-ingress-controllerはingress controllerとしての基本機能を実装するのみならず、externalTrafficPolicyをLocalに指定できるためセッションパーシステンシーやヘルスチェックが利用できます。さらに、ACMEクライアントが内蔵されているため、Ingressリソースを作成するだけでLet’s Encryptを利用してサーバ証明書が発行され、TLSの終端までセットアップされる優れものです。トラフィックマネージメントに深い造詣を備えるPulseSecure社によって開発されたIngress Controllerは、やはり餅は餅屋と思わせるものです。
さらに過激に最適化
さて、先日のエントリでIKE Officeという特殊なネットワークのKubernetesクラスタに触れました。クライアント環境から直接Podへ到達できるというものです。
このネットワークは当初Kubernetesクラスタの外にベアメタルサーバを置いて、Podとシームレスに通信できるように設計されたものですが、お気づきでしょうか?external LoadBalanacerが直接Podへ到達できれば、kube-proxyやServiceすらバイパスして、直接Podをノードプールとしてアドレスできるということに。冒頭構成と比べてなんとシンプルなことでしょう。あらゆるオーバーヘッドをそぎ落とした構成がこれです。
こんなことが容易にできるのは、PulseSecure vTMが仮想アプライアンスだからです。vTMが稼働するサーバ上にcalico(bird, felix)をインストールして、calicoのノードメンバーに加えてやればBGPによってvTMとKubernetesクラスタ間で経路が交換されます。そして、Ingress ControllerがロードバランサのノードプールにKubernetesノードのホストアドレス+NodePortではなく、Podアドレス+containerPort(Serviceに対応するEndpointsリソースから取得(※1))を設定すれば、この構成が実現します。
ただ、今のところIKEではこのような構成では運用していません。インフラのネットワークに依存するところがあり、普遍的に利用できるわけではないからです。それに、ロードバランサからノードプールへ直接アクセスするなど、Kubernetesを使っていなければ当たり前のことです。Kubernetesの利便性を犠牲にしてパフォーマンスを上げるぐらいなら、最初からクラスタの外に構築してもよいでしょう。
ここで述べたかったのはKubernetesのネットワークは柔軟性が高く、こうしたコントロールも可能だということです。
- どうでもよいのですが「kubectl get endpoint」(単数形)が通らないのはバグですよね。こんなつまらないPRでも送るべきか…。[↑]