【連載】Cybozu.comクラウド基盤の全貌 第3回 Neco のネットワーク

はじめに

クラウド基盤本部でサイボウズの Kubernetes 基盤である Neco の開発・運用を担当している杉浦です。
前回の記事では Neco について、サーバ管理の方法や自社製の Kubernetes エンジンである CKE を紹介しました。今回は Neco のネットワークに注目して、CKE によって構築した Kubernetes クラスタがどのようにしてクラスタ内外と通信しているかを紹介します。

Kubernetes クラスタのネットワークについて

Neco のネットワークについて紹介する前に、まず一般的な Kubernetes におけるネットワークについて紹介します。Kubernetes クラスタのネットワークを構築するにあたって、考慮すべき主な点はクラスタ内の通信クラスタ外との通信の 2 つです。

クラスタ内の通信

クラスタ内の通信では Pod への IP アドレスの割り当てと、それら Pod 間の疎通性をどう確保するかを考える必要があります。

Kubernetes において Pod への IP アドレスの割り当てやルーティングの設定は Kubernetes 自身ではなく CNI プラグインという種類のソフトウェアが担います。厳密には CNI プラグインの役割は Pod のネットワークインタフェースの作成・削除ですが、実際には Pod に割り当てる IP アドレスの管理やルーティングの設定をはじめとして、NetworkPolicy 機能、kube-proxy の代替など各 CNI プラグインごとに様々な機能・特徴を持っています。

この CNI プラグインのインストールについて、パブリッククラウドのマネージド Kubernetes クラスタでは、クラスタを作成したときに専用の CNI プラグインがインストールされていることが一般的です。しかし、Neco のようなオンプレミスのセルフマネージド Kubernetes クラスタでは要件に応じて CNI プラグインを選択、あるいは自作してインストールする必要があります。

クラスタ外との通信

クラスタ外との通信では、クラスタ外からの通信をどう受け付けるか・クラスタ内の Pod からクラスタ外にどう通信させるかの2点を考える必要があります。

クラスタ外からの通信の受け付けでは、Kubernetes にロードバランサを表現するためのリソースが用意されているため、これを活用します。
Kubernetes におけるロードバランサを表現するリソースは 2 つあり、L4 ロードバランサを表現する LoadBalancer タイプ Service リソースと、L7 ロードバランサを表現する Ingress リソースがあります1。これらはそれぞれ Kubernetes プリミティブなリソースですが、Kubernetes 自体はロードバランサを作成する機能は持っていません。そのため、Kubernetes でロードバランサを扱うためには専用のコントローラをクラスタにデプロイする必要があり、各リソースの内容に基づいてどうロードバランサを用意するかはクラスタ運用者に委ねられています。

多くのパブリッククラウドでは、各自が提供するロードバランサのサービスと連携した Kubernetes コントローラが予めデプロイされています。そのため、ユーザは何も準備しなくてもリソースを作成するだけでロードバランサを利用できます。しかし、オンプレミスにおいては、要件に応じてロードバランサの基盤を用意したり、用意したロードバランサに対応する Kubernetes コントローラをデプロイする必要があります。

クラスタ内の Pod からクラスタ外への通信については、IP アドレスの変換やプロキシサーバを用意します。
Pod に割り当てられる IP アドレスは基本的に同じ Kubernetes クラスタ内の Pod や Node としか疎通性がありません。そのため、クラスタ外に自発的な通信をするには、 目的の外部ネットワークに疎通性のある IP アドレスに変換したりプロキシサーバを中継させたりする必要があります。 IP アドレスを必要に応じて変換する技術を NAT(Network Address Translation)と呼びます。オンプレミスでは必要に応じて NAT を実現するためのコンポーネントやプロキシサーバもロードバランサ同様に自前で用意する必要があります。

以下の図はクラスタ内の通信とクラスタ外との通信をまとめたものです。

Kubernetes クラスタ内外の通信 概観

次の章では、これらクラスタ内の通信・クラスタ外との通信を Neco ではどのように実現しているかを紹介します。

Neco でのネットワーク構築

クラスタ内の通信

Neco ではクラスタ内の通信を構築する上で Coil と Cilium の 2 つの CNI プラグインを組み合わせて使っています。

1 つ目の Coil はサイボウズが OSS として開発している CNI プラグインです。Coil は Pod 間のネットワークを構築する際に VXLAN や Geneve といったトンネリング技術を使わずパケットを加工しないためオーバーヘッドが少なく通信パフォーマンスに優れています。
ただし、Coil 自体には Node を跨いだ Pod 間の経路を確立する機能はないため、Neco では Coil が書き出したルーティングテーブルの情報を BGP スピーカである BIRD2 によって上流のネットワークスイッチへ広報しています。

また、Coil は IP アドレスの管理機能を実装しており、Neco ではこの機能によって Pod の IP アドレスを管理しています。Coil は AddressPool というカスタムリソースを提供しており、これによりクラスタ全体で Pod に割り当てる IP アドレスの範囲を指定します。そして、AddressPool リソースの内容に基づいて Coil が一定のブロックサイズごとに Node 単位で AddressBlock というカスタムリソースを使ってIP アドレスを割り当てています。Pod に IP アドレスを割り当てる際には、その Pod がスケジュールされる Node に割り当てられた AddressBlock から各 Node に配置された Coil のコンポーネントの一つである coild が IP アドレスを選びます。

Coil についてさらに詳しい情報は過去にも記事が上げられているので、よければそちらもご覧ください

blog.cybozu.io

Pod への IP アドレスの割り当てとルーティングの設定

2 つ目の Cilium は CNCF Graduated Project の一つで、eBPF をデータプレーンに活用した CNI プラグインです。eBPF とは Linux カーネル内でプログラムを実行する仕組みで、ネットワークの分野ではパケットのフィルタリングや加工を高速に処理するために使われています3
Neco ではこの Cilium の NetworkPolicy 機能と kube-proxy の代替機能、後述する L4 ロードバランサの機能を活用しています。

Cilium の NetworkPolicy 機能は Kubernetes が提供する通常の NetworkPolicy リソースに対応しているだけでなく、より細かい設定が可能な CiliumNetworkPolicy4 リソースというカスタムリソースも提供しています。CiliumNetworkPolicy では Kubernetes の NetworkPolicy にはない、クラスタ全体に対するポリシーの設定や、「クラスタ内エンドポイント」・「クラスタ外エンドポイント」といった通信主体の属性を使ったポリシー設定、HTTP のヘッダなど L7 の情報を使ったポリシー設定などができます。
Neco ではシングルクラスタ・マルチテナント構成を取っているため、同じクラスタ内に様々な Pod が同居しています。そのため CiliumNetworkPolicy を使って、意図せず他の Pod やクラスタ外と通信しないようにしています。また、ポリシー違反によってドロップされたパケットは Cilium のメトリクスや、Cilium の関連コンポーネントである Hubble を使って記録しモニタリングしています。

Cilium の kube-proxy の代替機能は、通常 kube-proxy が提供している ClusterIP や NodePort タイプの Service リソースの機能を Cilium が管理する eBPF プログラムによって代替します。eBPF はLinux カーネルのネットワークスタックに到達する前にパケットを処理できるため、通信性能の向上やスケーラビリティの向上が期待できます。

Neco では通信処理のパフォーマンス向上と、kube-proxy の代替機能を活用したいため、 Cilium を利用しています。
Cilium の運用については過去に運用で遭遇した問題をまとめた記事を上げています。よければそちらもご覧ください。

blog.cybozu.io

クラスタ外との通信

L4 ロードバランサ

Neco の L4 ロードバランサは、先ほどのクラスタ内の通信でも紹介した Cilium が提供するロードバランサの機能を使っています。

Cilium はいくつかのコンポーネントから成り立っており、そのうちの 1 つである Cilium Agent が各 Node に配置されています。
そして、この Cilium Agent の中には MetalLB が埋め込まれています5 。 MetalLB は BGP スピーカーとしての機能をもっており、適用された Service リソースの内容や Node 上の Pod に基づいて経路情報を同じ Node 上の BIRD に広報します。そして BIRD は広報された経路情報を Coil のときと同様に上流のネットワークスイッチへと広報します。

Neco ではハードウェアのロードバランサを用意せず、このようにソフトウェアのロードバランサによってクラスタ外部からの通信の負荷分散をしています。
MetalLB について、詳しくは過去にブログ記事を出していますので、よければそちらもご覧ください6

blog.cybozu.io

また、Neco の L4 ロードバランサは Cilium の DSR(Direct Server Return)機能を活用しています。
DSR とはロードバランサ経由のリクエストを、ロードバランサを介さずにサーバからクライアントへ返信する技術です。Kubernetes においては、 DSR を活用すると Service リソースの ExternalTrafficPolicy を Cluster に設定しているときでも、リクエストを処理した Pod からリクエストを転送した Node を経由せずに直接リクエスト送信元であるクライアントに返信できます。
これによりサーバからクライアントへ戻るパケットのホップ数が少なくなり、レスポンスタイムの向上やクラスタ内の通信負荷の低減が図れます。また、DSR にはサーバの Pod までクライアントの IP アドレスが保持される利点もあります。

サイボウズではCilium の DSR 機能について、利用するだけでなく機能開発の貢献もしました。
Cilium の DSR 機能は元々 IP ヘッダのオプションフィールドを使って実装されていましたが、Neco においてこの方式は上流のネットワークスイッチのハードウェアオフロードを活用できずパフォーマンス面で課題がありました。そのため、Neco では Geneve プロトコルを使った DSR を設計・実装し、Cilium に PR を作成しマージされました。

Cilium の DSR の動作や開発の経緯については、CloudNative Days Tokyo 2023 でも紹介したため、よければこちらもご覧ください

cloudnativedays.jp

L7 ロードバランサ

Neco の L7 ロードバランサは、Contour7 という OSS を活用しています。
Contour は Envoy のコントロールプレーンとして機能しており、Ingress リソースの設定に応じて Envoy の設定を更新します。Envoy とは現在 OSS として開発されているプロキシサーバの一つで、ロードバランシングやルーティングなどの機能をはじめとした様々な機能をもつソフトウェアです。Neco ではこの Envoy を先ほど紹介した、L4 ロードバランサを使って公開しており、設定に応じてリクエストをクラスタ外から受け付け、対応する Pod に転送します。

また、Contour は HTTPProxy という Ingress リソースを拡張したカスタムリソースを用意しており、Neco ではこれを積極的に活用しています。HTTPProxy では Ingress リソースの機能に加えて、パス単位のタイムアウト設定・HTTP ヘッダの一致条件の記述・重みつきルーティングなど様々な機能があります。

さらに、サイボウズでは Contour Plus8 という OSS を開発・運用しています。
Contour Plus は作成した HTTPProxy リソースの内容に基づいて、external-dns が提供する DNSEndpoint リソースや cert-manager が提供する Certificate リソースを自動生成します。これにより Neco のユーザは HTTPProxy を作成するだけで、定義したホスト名に対する TLS 通信が可能になります。

NAT

Kubernetes クラスタ内の Pod からクラスタ外に通信するためには、一般的に外部と疎通性のある IP アドレスに変換する必要があります。
Neco ではこれを実現するために Coil の機能の一つとして Egress NAT を開発・運用しています。

Egress NAT は Egress というカスタムリソースを用意しており、この Egress リソースを作成すると NAT のゲートウェイとなる Pod (以下、NAT Pod)が作成されます。Egress NAT を利用したい場合は Pod に特定のアノテーションを付与すると、対象の通信が対応する NAT Pod にルーティングされるようになります。そして、NAT Pod には Coil のアドレス割り当て機能を利用して目的の外部ネットワークに疎通性のある IP アドレスを割り当てることで、Pod は Egress NAT を介して外部ネットワークと通信できるようになります。例えば、グローバルな IP アドレスを持っていない Pod に対して、グローバルな IP アドレスを割り当てた NAT Pod を経由させると、インターネットを介した通信ができるようになります。

Egress NAT の動作原理や実装については、Kintone Engineering Blog でまとめられています。興味のある方は、ご覧ください。

blog.kintone.io

プロキシサーバ

プロキシサーバについて、Neco では HTTP プロキシとして Squid9 を活用しています。 Squid では HTTP リクエストについてのカウンターやタイマーをメトリクスとして提供しており、Neco ではこれらのメトリクスを squid-exporter10 という自作の Prometheus エクスポータによって収集しています。

Neco のユーザは要件に応じて、Coil Egress NAT と Squid のどちらを使うかを選択できます。例えば、HTTP 以外の通信を扱いたい場合は Coil Egress NAT を使い、アプリケーションが扱う HTTP 通信のメトリクスやログを取得したい場合は Squid を使うという選択ができます。

おわりに・まとめ

本記事ではクラスタ内の通信・クラスタ外との通信に注目して、Kubernetes のネットワークを Neco でどのように構築しているかを紹介しました。

オンプレミスの Kubernetes クラスタはネットワークの観点においても、パブリッククラウドと異なり、様々なコンポーネントを自ら導入あるいは作成する必要があることをお伝えできたかなと思います。
大規模なセルフマネージドの Kubernetes クラスタをサービスの提供基盤として運用するのは決して簡単ではありませんが、その過程で得られる様々な知見は、個人の活動では得難いものであり、やりがいを感じます。Neco ではこれからもオンプレミスでの運用の知見を、提供するサービスの品質向上や技術コミュニティの発展に活かしたいと思っています。

次回はデータセンター管理とネットワーク設計についての記事が掲載される予定です。お楽しみに!