TT Lab
はじめる
学ぶ 学習パス コース

CCA — Cilium認定アソシエイト

Gateway API とノードごとの Envoy — Cilium サービスメッシュの構造

TT Labで続きを見る

一言でいうと

Ciliumは、Gateway APIのリソースをCiliumEnvoyConfigに変換してノードごとに1つあるEnvoyに載せ、eBPFがServiceのポートに入ってきたトラフィックを、そのEnvoyに横取りします。Podごとにサイドカーを付けないこの構造がsidecarlessメッシュであり、暗号化は、WireGuardまたはIPsecでノード間に透過的にかけます。

なぜ必要なのか

Ingressリソースは、ホストとパスで振り分けるところまでが標準でした。重みでトラフィックを分けたり、ヘッダーを書き換えたり、URLを書き直したりするには、コントローラーごとに異なるアノテーションを使う必要があり、そのアノテーションは、別のコントローラーに移すと動作しませんでした。HTTPとHTTPSしか扱えず、TCP・UDPは標準になく、複数のチームが1つのロードバランサーを共有するクラスターには合いませんでした。Ciliumのドキュメントは、これらの点をIngressの限界として整理し、Kubernetes SIG-Networkが後継として設計したGateway APIを、その答えとしています。

メッシュの側にも、同じ種類の問題がありました。Podごとにサイドカープロキシを付けると、L7の機能は得られますが、Podの数だけプロキシが増えます。Ciliumは、IP・TCP・UDPの処理をeBPFでカーネルの中で行い、HTTP・gRPC・DNSのようなアプリケーションプロトコルだけをEnvoyに渡す構造を、最初から選びました。CCAのService Meshドメイン(16%)は、この構造の理由とGateway APIの利点、そして暗号化オプションを問います。

どう動くのか

Gateway APIのリソースと役割の分離

Gateway APIは、役割中心(role-oriented)に設計されました。ドキュメントが挙げる3つの役割は、インフラ提供者(Infrastructure Provider)、クラスター運用者(Cluster Operator)、アプリケーション開発者(Application Developer)です。GatewayClassは、どの実装がゲートウェイを担当するか(Ciliumではcilium)、Gatewayは、リスナー(プロトコル・ポート)と、どのネームスペースのRouteを受け入れるか、HTTPRouteは、マッチングルールとバックエンドを記述します。開発者は、自分のネームスペースにRouteだけを作成でき、Gatewayの設定には触れられないように、権限を分けることができます。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-route-1
spec:
  parentRefs:
  - name: cilium-gw            # 어느 Gateway 에 붙는가
  rules:
  - matches:
    - path: {type: PathPrefix, value: /echo}
    backendRefs:
    - {kind: Service, name: echo-1, port: 8080, weight: 50}
    - {kind: Service, name: echo-2, port: 8090, weight: 50}

backendRefsのweightが、トラフィックの分割です。ドキュメントの例では、50/50から始めて99/1に変更しながら、カナリアやA/Bのシナリオで使うと説明しています。アノテーションではなく標準のフィールドなので、別の実装に移しても、そのまま動作します。

Ciliumは、Gateway API v1.6.1のGatewayClass・Gateway・HTTPRoute・GRPCRoute・TLSRoute・BackendTLSPolicy・ReferenceGrantをサポートし、Coreの適合性テストに合格しています。TCPRoute・UDPRoute・ListenerSetは、CRDをインストールしたときだけ有効になります。前提条件は、kubeProxyReplacement=trueとl7Proxy=true(デフォルト値)で、gatewayAPI.enabled=trueでコントローラーを有効にします。デフォルトでは、LoadBalancerタイプのServiceを作成し、1.16からは、ホストネットワークで直接公開することもできます。

ほかのIngressコントローラーとの違い

ドキュメントが「最大の違い」と呼ぶのは、実装がCNIとどれだけ密着しているかです。一般的なコントローラーは、DeploymentやDaemonSetとして別に起動し、LoadBalancer Serviceとして公開されます。CiliumのIngressとGateway APIは、ネットワークスタックの一部なので、Serviceのポートにトラフィックが到着すると、eBPFコードが横取りして、TPROXYでEnvoyに渡します。その結果、CiliumNetworkPolicyが、Ingressを通って出入りするトラフィックにも適用されます。

このとき、Envoyに到着したトラフィックは、ポリシーエンジンで特別なingressアイデンティティを受け取ります。外部から来たトラフィックは、通常worldアイデンティティなので、ポリシーの判断ポイントが2か所になります。worldからingressに入る段階と、ingressからバックエンドのアイデンティティへ出る段階です。デフォルト拒否ポリシーをかけたのにゲートウェイがブロックされる場合は、この2つの段階のうち1つを許可していないのです。

内部の動作は、2つの部分に分かれます。Cilium operatorがGateway APIのリソースを監視して、有効性を検査し、acceptedとして表示したうえで、CiliumEnvoyConfigに変換します。Cilium agentは、そのCiliumEnvoyConfigを読み取って、組み込みのEnvoyまたはEnvoy DaemonSetに設定を渡します。トラフィックは、Envoyが処理します。

サイドカーのないメッシュ

ノードあたり1つのEnvoyを使う構造は、東西(east-west)のトラフィックにも、同じ方式で適用されます。GAMMAは、HTTPRouteのparentをGatewayではなくServiceにする方式で、Ciliumは、そのServiceへ向かうL7トラフィックを横取りして、ノードあたりのEnvoyにルーティングします。Ciliumは、現在、Serviceと同じネームスペースにあるproducer Routeだけをサポートしています。L7を使わないトラフィックは、Envoyを経由せず、eBPFデータパスにそのまま向かいます。サイドカー方式と比べると、プロキシの数がPodの数ではなくノードの数に比例し、アプリケーションPodを再起動せずにL7の機能を有効化・無効化できます。

暗号化オプション: WireGuard、IPsec、mTLS

Ciliumは、Ciliumが管理するエンドポイント間のトラフィックを、IPsec、WireGuard、またはztunnel(ベータ)で透過的に暗号化します。WireGuardを有効にすると、各ノードのagentが、ほかのすべてのノードとWireGuardトンネルを確立し、ノードごとに鍵ペアを作成して、公開鍵をCiliumNodeリソースのnetwork.cilium.io/wg-pub-keyアノテーションで配布します。トンネルのエンドポイントはUDP 51871で、同じノード内のトラフィックは暗号化しません(どのみちノードで見えるため、利点がないからです)。カーネルにWireGuardのサポートが必要で、encryption.enabled=true、encryption.type=wireguardで有効にします。IPsecは、鍵をKubernetes Secretとして配布し、1.18からは、トンネルのカプセル化のあとに暗号化して、ポリシー用のアイデンティティまで隠します。

Mutual Authentication(ベータ)は、SPIFFE/SPIREでワークロードのアイデンティティを発行し、mTLSのハンドシェイクを通常の接続の外で(out-of-band)実行します。ドキュメントは、認証と機密性をあわせて得るには、暗号化(WireGuardまたはIPsec)を有効にする必要があると書いています。つまり、CiliumのmTLSはアイデンティティの検証であり、実際の暗号化は、ノード間のトンネルが担当します。

現場での姿

NGINX IngressからCilium Gateway APIに移行するチームが、最初にぶつかるのはアノテーションです。ドキュメントは、実装ごとのアノテーションをそのまま移すことはまれで、リクエスト・応答の操作、トラフィックの分割、ヘッダー・クエリ・メソッドに基づくルーティングは、Gateway APIの標準フィールドに置き換えるよう案内しています。推奨される順序は、範囲と段階を決め、既存のアノテーションを分類し、同じ意味のGateway APIリソースを作成して並行して検証したうえで、トラフィックを段階的に移し、安定したあとでIngressを削除することです。

WireGuardを有効にしたあと、「最初の数パケットが平文で出ていった」という報告もあります。ドキュメントの既知の問題の項目が、まさにそれです。宛先IPがリモートのCiliumエンドポイントであるという情報が伝播される前は、クラスターの外とみなして平文で送ります。encryption.strictModeを有効にするか、worldへ出るegressをポリシーでブロックすることが、ドキュメントが示す対応です。

次の理論で見ること

すぐ続く理論では、Gatewayが受け取ったLoadBalancerのIPとPodのCIDRを、クラスターの外のルーターがどのように知るようになるのか、つまりBGP Control PlaneとEgress Gatewayを見ます。参考: Gateway API Support、Traffic Splitting Example、Migrating from Ingress to Gateway、WireGuard Transparent Encryption、Mutual Authentication。