🐝

KEP-4188: New kubelet gRPC API with endpoint returning local pods info

2023/11/27に公開

概要

KEP-4188 は、Kubelet に Pod Conditions を公開する gRPC API を追加する KEP です。Pod Conditions は Status フィールドに含まれています。

❯ kubectl get pods -n kube-system coredns-5d78c9869d-8gglh -oyaml
(...)
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T07:16:49Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T07:16:50Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T07:16:50Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T07:16:49Z"
    status: "True"
    type: PodScheduled

利用者は Kubelet が作成した Unix ドメインソケットを Pod にマウントすることで、Pod の状態を参照することができます。参照する方法は KEP-2403: Extend kubelet pod resource assignment endpoint to return allocatable resources と同じ仕組みです。gRPC API は読み取り専用かつ同一ノード上の Pod の状態のみを公開します。

KEP-4188 で公開する Pod の状態は、PodConditionType と同じです。

  • PodScheduled: Pod がノードにスケジュールされた
  • ContainersReady: Pod 内の全てのコンテナが準備できた
  • Initialized: Pod 内の全ての Init コンテナを正常に終了できた
  • Ready: Pod はリクエストを処理する準備ができ、Service のバックエンドに追加される
  • DisruptionTarget: Eviction などで近いうちに Pod が中断される

別の KEP になりそうですが、今後他の情報を追加する可能性はあるようです。

KEP-4188 は Google の kl52752Rob Scott が提案しています。Rob Scott は、SIG-Network の主要メンバーで Endpoint Slice / Ingress / Gateway API / Topology Aware Routing などに関わっています。

PodStatus API

ノード上の /var/lib/Kubelet/status/Kubelet.sock にある Unix ドメインソケットを介して PodStatus API を消費することができます。k/k#121026 に gRPC サービスとして PodStatus を追加する PR がありますが、Kubernetes 1.29 時点ではマージされていません。

PodStatus API の公開方法として Kubelet の REST API と gRPC サービスのどちらが良いかも議論されていました。Kubelet の REST API は ReadOnlyPort と AuthenticatedPort の 2 種類のポートで公開されています。しかし、以下の理由から gRPC サービスでの公開を選択したようです。

  • ReadOnlyPort はセキュリティ的な懸念から無効化しているクラウドプロバイダーがある
  • AuthenticatedPort に関しても、API サーバーでの認証が必要なため API サーバー依存を無くしたい KEP-4188 とは逆行する

Pod の UID を指定して特定の Pod の状態を取得する GetPodStatus とノード上の全ての Pod の状態を取得する ListPodStatusk/k#121026 では不完全ですが、Pod の状態に変更があった場合に新しい状態の一覧を返す WatchPodStatus も追加予定のようです。

// PodStatus is a service provided by the kubelet that serves the information about
// the local pod's readiness.
service PodStatus {
    // GetPodStatus returns a PodStatus for given pod's UID
    rpc GetPodStatus(PodStatusGetRequest) returns (PodStatusGetResponse) {}
    // WatchPodStatus returns a stream of List of PodStatus
    // Whenever a pod state change api returns the new list
    rpc WatchPodStatus(PodStatusWatchRequest) returns (stream PodStatusWatchResponse) {}
    // ListPodStatus returns a of List of PodStatus
    rpc ListPodStatus(PodStatusListRequest) returns (PodStatusListResponse) {}
}

PodStatus API で取得できる情報は PodStatusType もしくは PodStatusType のリストです。PodCondition に加えて、PodUID、Pod の名前や namespace、static Pod かどうか、Pod が削除処理に入っているか (DeletionTimestamp が設定されているか) という情報を返す予定です。

// PodStatusType returns a Pod details and list of status Conditions with deletion info.
message PodStatusType {
    string podUID = 1;
    string podNamespace = 2;
    string podName = 3;
    bool static = 4;
    repeated PodCondition conditions = 5;
    k8s.io.apimachinery.pkg.apis.meta.v1.Time DeletionTimestamp = 6;
}

PodCondition の中身は corev1 の PodCondition と全く同じです。PodCondition と Protobuf の定義をドリフトをどう検出するかがベータ or GA 昇格の条件になりそうです。

// PodCondition aligns with v1.PodCondition.
message PodCondition {
    PodConditionType Type = 1;
    ConditionStatus Status = 2;
    k8s.io.apimachinery.pkg.apis.meta.v1.Time LastProbeTime = 3;
    k8s.io.apimachinery.pkg.apis.meta.v1.Time LastTransitionTime = 4;
    string Reason = 5;
    string Message = 6;
}

// PodConditionType aligns with v1.PodConditionType
// due to limiting messages size the type was changed to int
enum PodConditionType {
    ContainersReady = 0;
    Initialized = 1;
    Ready = 2;
    PodScheduled = 3;
    DisruptionTarget = 4;
}

// ConditionStatus aligns with v1.ConditionStatus
enum ConditionStatus {
    True = 0;
    False = 1;
    Unknown = 2;
}

Kubelet が不完全な情報を持っている (e.g. Kubelet 再起動直後) 場合は、中途半端な情報を返さずに gRPC の FAILED_PRECONDITION (9) のエラーコードを返却します。

ユースケース

この機能で最も気になるのはそのユースケースです。KEP に記載されているユースケースは以下になりますが、具体的に何を実現したいのかは見えてきません。

  • ノードで動作するシステムの Pod (e.g. kube-system の Pod) の一部がコントロールプレーンに依存している
    • バージョン更新時などコントロールプレーンが一時的に応答しなくなる場合があるため、コントロールプレーンへの依存を減らして安定性を向上したい
    • 各ノードのシステムの Pod が Watch API で状態を監視すると、kube-apiserver に負荷が掛かってしまうので、コントロールプレーンへの依存を減らしてパフォーマンスを改善したい
    • Pod オブジェクトを Informer キャッシュに持つとメモリ使用量が高騰しがちなため、必要な情報のみを取得してメモリのフットプリントを削減したい
  • 起動時や drain 中の Pod がトラフィックを受けとる準備が出来ているかを監視するカスタムツール

本記事では、KEP の議論をもとにユースケースを考察していきます。考察なので間違っている可能性はある点はご了承下さい。

システムの Pod のコントロールプレーン依存

KEP-4188 のユースケースで出てきたシステムの Pod とはどのコンポーネントのことでしょうか。SIG-Node ミーティングでの KEP-4188 の議論で、この機能を使うワークロードとして何を想定しているかという質問に「例えば Cilium」 と答えています。 KEP-4188 の提案者は Google の方なので、Google Kubernetes Engine (GKE) を想定しているはずです。GKE には Dataplane v2 と呼ばれるカスタムの Cilium を利用した eBPF ベースかつ kube-proxy 置き換えのネットワーク構成を選択することができます。Dataplane v2 を有効化した GKE クラスタの安定性を向上するのが狙いのようです。

SIG-Node ミーティングでの議論の中にヒントが他にもありました。知りたい情報は Pod のネットワークが準備できた状態かヘルスチェックに成功した状態かと言う質問に対して、「SIG-Network として現状興味のある情報は Pod がヘルスチェック (Readiness Probe) に成功しているかだけ」と答えています。PodConditions の中でも PodReady の情報だけを利用する予定のようです。Pod が Ready かを知っている Kubelet が側にいるにも関わらず、コントロールプレーンが一時的に使えない場合に Pod の状態を知る術がない現状を改善したいようです。

Cilium と Local Redirect Policy

では、Cilium の中で PodStatus の PodConditions を見ているのはどの処理でしょうか。Cilium v1.14.4 のコードから探していきます。Cilium は Kubernetes リソースの API 定義を直接使用しません。Cilium が使う情報だけに絞った slim と呼ばれる API 定義をコード生成できるようにしています。そして、この slim な Kubernetes API を wrap したヘルパー関数を用意しています。その中の GetLatestPodReadinessPodReady (Ready) の状態を返しています。

// GetLatestPodReadiness returns the lastest podReady condition on a given pod.
func GetLatestPodReadiness(podStatus slim_corev1.PodStatus) slim_corev1.ConditionStatus {
	for _, cond := range podStatus.Conditions {
		if cond.Type == slim_corev1.PodReady {
			return cond.Status
		}
	}
	return slim_corev1.ConditionUnknown
}

Cilium のコードベースからこの関数を使用しているところを検索してみると 3 箇所あります。

1 つ目は Deployment としてデプロイされる Cilium Operator なので KEP-4188 とは関係がなさそうです。残りの 2 つの実装を見ていきます。残りの 2 つの実装はどちらも Local Redirect Policy (LRP) と呼ばれる機能に関わる実装のようです。LRP は、Kubernetes Service の VIP に対するリクエストを同一ノード上の Pod にリダイレクトする機能です。CNI として Cilium を使いつつ、NodeLocal DNSCache を機能させたい場合に利用します。

2 つ目の updateK8sPodV1 は LRP が有効な場合の処理です。PodReady の状態が False から True に変わると、LRP の OnUpdatePod の hook を実行し、ポリシーを再設定しているようです。

func (k *K8sWatcher) updateK8sPodV1(oldK8sPod, newK8sPod *slim_corev1.Pod) error {
	...
	if option.Config.EnableLocalRedirectPolicy {
		oldPodReady := k8sUtils.GetLatestPodReadiness(oldK8sPod.Status)
		newPodReady := k8sUtils.GetLatestPodReadiness(newK8sPod.Status)

		if lrpNeedsReassign || (oldPodReady != newPodReady) {
			k.redirectPolicyManager.OnUpdatePod(newK8sPod, lrpNeedsReassign, newPodReady == slim_corev1.ConditionTrue)
		}
	}
	...
}

3 つ目の getLocalPodsForPolicy は同一ノード上の Pod 一覧を取得して、PodReady が True なエンドポイントの情報を返しています。同一ノード上の Pod 一覧から namespace が異なるものや PodReady が False のものを除外し、policyConfigSelectsPod でポリシーに定義したラベルセレクタに一致する Pod かを確認しています。そして、Service の VIP を ラベルセレクタに一致する Pod の IPs にリダイレクトする設定を追加しているようです。AddRedirectPolicy の実装を見ると、条件に一致する Pod が同一ノードに存在しない場合は設定を追加せず通常の Service の振り分けにフォールバックしているようです。

// Returns a slice of endpoint pods metadata that are selected by the given policy config.
func (rpm *Manager) getLocalPodsForPolicy(config *LRPConfig) ([]*podMetadata, error) {
	...

	podStore, err := rpm.localPods.Store(context.TODO())
	if err != nil {
		log.WithError(err).Error("failed to get reference to local pod store")
		return nil, err
	}
	for _, pod := range podStore.List() {
		if !config.checkNamespace(pod.GetNamespace()) {
			continue
		}
		podIPs := k8sUtils.ValidIPs(pod.Status)
		if len(podIPs) == 0 {
			continue
		}
		if podData, err = rpm.getPodMetadata(pod, podIPs); err != nil {
			log.WithError(err).WithFields(logrus.Fields{
				logfields.K8sPodName:   pod.Name,
				logfields.K8sNamespace: pod.Namespace,
			}).Error("failed to get valid pod metadata")
			continue
		}
		if k8sUtils.GetLatestPodReadiness(pod.Status) != slimcorev1.ConditionTrue {
			continue
		}
		if !config.policyConfigSelectsPod(podData) {
			continue
		}
		retPods = append(retPods, podData)
	}

	return retPods, nil
}

KEP-4188 と Cilium の LRP についてこれまで見てきたことをまとめます。

  • KEP-4188 は GKE の Dataplane v2 (Cilium) の機能の安定性の向上のため提案されている可能性がある
  • Cilium の LRP の機能はコントロールプレーンから取得した PodStatus の PodReady の情報に依存している
  • Cilium の LRP の機能は NodeLocal DNSCache を機能させるために利用する

NodeLocal DNSCache と Local Redirect Policy

NodeLocal DNSCache を機能させるために Local Redirect Policy (LRP) が必要な背景を少し見ていきます。NodeLocal DNSCache は kube-dns や CoreDNS などのクラスタ内の DNS へのトラフィックをノード上の DNS キャッシュ (CoreDNS) に向けます。これを実現するために、ノード上にダミーの仮想 NIC を作成し、クラスタ DNS の VIP (ClusterIP) を割り当てます。そして、ノード上の iptables のルールによってクラスタ DNS の VIP への通信をノード内に閉じ込め (NAT を回避)、ダミーの仮想 NIC に向けます。あとは NodeLocal DNSCache を hostNetwork を有効化してデプロイし、CoreDNS のプロセスを bind plugin を使用して仮想 NIC のソケットに紐付けます。詳細は Reliable and Performant DNS Resolution with High Available NodeLocal DNSCache を参照して下さい。


出展: https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/

Cilium は XDP や Traffic Control (tc) に eBPF をアタッチしてトラフィックをルーティングするため、仮想 NIC を利用した NodeLocal DNSCache の hack をバイパスしてしまいます。これにより、CNI として Cilium を利用しているクラスタでは NodeLocal DNSCache が期待通りに動作しません。この制限を解消するために、LRP の機能が追加されています。ちなみに、LRP を利用するには Cilium を kube-proxy 置き換えモードで動かす必要があります。

GKE と Local Redirect Policy

GKE で Dataplane v2 を有効化したクラスタを作成して、Local Redirect Policy (LRP) が利用されているか確認してみます。Cilium v1.14 時点で LRP はベータ機能のため、設定で明示的に有効化していないと利用できません。確認してみたところ、Dataplane v2 を有効化している全ての GKE のタイプ (Standard, Autopilot) や組み合わせ (e.g. NodeLocal DNSCache ON/OFF) で enable-local-redirect-policy のオプションが有効化されています。

apiVersion: v1
data:
  (...)
  # カスタムの Cilium なので Google 固有の機能が生えている
  # 最近 Preview で入った Pod のマルチネットワーク機能を実現するための実装っぽい
  # https://cloud.google.com/kubernetes-engine/docs/how-to/setup-multinetwork-support-for-pods
  enable-google-multi-nic: "false"
  # ???
  enable-google-service-steering: "false"
  (...)
  # LRP が有効化されている
  enable-local-redirect-policy: "true"
  (...)

クラスタ DNS として kube-dns を使う GKE クラスタを Standard タイプで作成し、NodeLocal DNSCache のアドオンを有効化します。アドオンを有効化することで NodeLocal DNSCache の DaemonSet が kube-system の namespace にデプロイされます。NodeLocal DNSCache の DaemonSet の設定を見てみると、-setupinterface=false-setupiptables=false のフラグが指定されているのが分かります。これにより、NodeLocal DNSCache がダミーの仮想 NIC の作成と iptables ルールを追加しないようにしています。

❯ kubectl get daemonset -n kube-system node-local-dns -oyaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  (...)
  name: node-local-dns
  namespace: kube-system
spec:
  (...)
  template:
    (...)
    spec:
      containers:
      - args:
        - -localip
        - 0.0.0.0
        - -conf
        - /etc/Corefile
        - -upstreamsvc
        - kube-dns-upstream
        - -kubednscm
        - /etc/kube-dns
        - -skipteardown=true
        - -setupinterface=false
        - -setupiptables=false
        image: gke.gcr.io/k8s-dns-node-cache:1.22.22-gke.0@sha256:88052cde040efad7f40959a301d01bfd5130f36ead4c927880c459cbb93d7066
      (...)

iptables のルール一覧を見てみると確かに NodeLocal DNSCache が作成したルールは見当たりません。代わりに Cilium が作成した DNS 用のルールがいくつか見えます。この中に cilium/cilium#16694 で Google の人が追加した iptables のルールも含まれています。LRP の機能を利用しても最初 NodeLocal DNSCache が期待した通りに動かなかったようです。

iptables のルール一覧
# sudo iptables-save
# Generated by iptables-save v1.8.5 on Thu Nov 16 12:12:42 2023
*raw
:PREROUTING ACCEPT [545533:839106258]
:OUTPUT ACCEPT [296389:171742761]
:CILIUM_OUTPUT_raw - [0:0]
:CILIUM_PRE_raw - [0:0]
-A PREROUTING -m comment --comment "cilium-feeder: CILIUM_PRE_raw" -j CILIUM_PRE_raw
-A OUTPUT -m comment --comment "cilium-feeder: CILIUM_OUTPUT_raw" -j CILIUM_OUTPUT_raw
-A CILIUM_OUTPUT_raw -o lxc+ -m mark --mark 0xa00/0xfffffeff -m comment --comment "cilium: NOTRACK for proxy return traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o cilium_host -m mark --mark 0xa00/0xfffffeff -m comment --comment "cilium: NOTRACK for proxy return traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o lxc+ -m mark --mark 0x800/0xe00 -m comment --comment "cilium: NOTRACK for L7 proxy upstream traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o cilium_host -m mark --mark 0x800/0xe00 -m comment --comment "cilium: NOTRACK for L7 proxy upstream traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -d 10.0.4.130/32 -p tcp -m tcp --dport 53 -j CT --notrack
-A CILIUM_OUTPUT_raw -s 10.0.4.130/32 -p tcp -m tcp --sport 53 -j CT --notrack
-A CILIUM_OUTPUT_raw -d 10.0.4.130/32 -p udp -m udp --dport 53 -j CT --notrack
-A CILIUM_OUTPUT_raw -s 10.0.4.130/32 -p udp -m udp --sport 53 -j CT --notrack
-A CILIUM_PRE_raw -m mark --mark 0x200/0xf00 -m comment --comment "cilium: NOTRACK for proxy traffic" -j CT --notrack
-A CILIUM_PRE_raw -d 10.0.4.130/32 -p tcp -m tcp --dport 53 -j CT --notrack
-A CILIUM_PRE_raw -s 10.0.4.130/32 -p tcp -m tcp --sport 53 -j CT --notrack
-A CILIUM_PRE_raw -d 10.0.4.130/32 -p udp -m udp --dport 53 -j CT --notrack
-A CILIUM_PRE_raw -s 10.0.4.130/32 -p udp -m udp --sport 53 -j CT --notrack
COMMIT
# Completed on Thu Nov 16 12:12:42 2023
# Generated by iptables-save v1.8.5 on Thu Nov 16 12:12:42 2023
*mangle
:PREROUTING ACCEPT [848:439179]
:INPUT ACCEPT [457:141594]
:FORWARD ACCEPT [391:297585]
:OUTPUT ACCEPT [448:222415]
:POSTROUTING ACCEPT [839:520000]
:CILIUM_POST_mangle - [0:0]
:CILIUM_PRE_mangle - [0:0]
:KUBE-IPTABLES-HINT - [0:0]
:KUBE-KUBELET-CANARY - [0:0]
-A PREROUTING -m comment --comment "cilium-feeder: CILIUM_PRE_mangle" -j CILIUM_PRE_mangle
-A OUTPUT -s 169.254.169.254/32 -p tcp -m tcp --sport 53 -j ACCEPT
-A OUTPUT -s 169.254.169.254/32 -p udp -m udp --sport 53 -j ACCEPT
-A OUTPUT -s 169.254.169.254/32 -j DROP
-A POSTROUTING -m comment --comment "cilium-feeder: CILIUM_POST_mangle" -j CILIUM_POST_mangle
-A CILIUM_PRE_mangle -m socket --transparent -m comment --comment "cilium: any->pod redirect proxied traffic to host proxy" -j MARK --set-xmark 0x200/0xffffffff
-A CILIUM_PRE_mangle -p tcp -m mark --mark 0x378b0200 -m comment --comment "cilium: TPROXY to host cilium-dns-egress proxy" -j TPROXY --on-port 35639 --on-ip 0.0.0.0 --tproxy-mark 0x200/0xffffffff
-A CILIUM_PRE_mangle -p udp -m mark --mark 0x378b0200 -m comment --comment "cilium: TPROXY to host cilium-dns-egress proxy" -j TPROXY --on-port 35639 --on-ip 0.0.0.0 --tproxy-mark 0x200/0xffffffff
COMMIT
# Completed on Thu Nov 16 12:12:42 2023
# Generated by iptables-save v1.8.5 on Thu Nov 16 12:12:42 2023
*nat
:PREROUTING ACCEPT [505:31944]
:INPUT ACCEPT [30:2550]
:OUTPUT ACCEPT [17247:1046519]
:POSTROUTING ACCEPT [17555:1065893]
:CILIUM_OUTPUT_nat - [0:0]
:CILIUM_POST_nat - [0:0]
:CILIUM_PRE_nat - [0:0]
:DOCKER - [0:0]
:IP-MASQ - [0:0]
:KUBE-KUBELET-CANARY - [0:0]
-A PREROUTING -m comment --comment "cilium-feeder: CILIUM_PRE_nat" -j CILIUM_PRE_nat
-A PREROUTING -d 169.254.169.254/32 ! -i eth0 -p tcp -m tcp --dport 8080 -m comment --comment "metadata-concealment: bridge traffic to metadata server goes to metadata proxy" -j DNAT --to-destination 169.254.169.252:987
-A PREROUTING -d 169.254.169.254/32 ! -i eth0 -p tcp -m tcp --dport 80 -m comment --comment "metadata-concealment: bridge traffic to metadata server goes to metadata proxy" -j DNAT --to-destination 169.254.169.252:988
-A OUTPUT -m comment --comment "cilium-feeder: CILIUM_OUTPUT_nat" -j CILIUM_OUTPUT_nat
-A POSTROUTING -m comment --comment "cilium-feeder: CILIUM_POST_nat" -j CILIUM_POST_nat
-A POSTROUTING -m comment --comment "ip-masq: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ chain" -m addrtype ! --dst-type LOCAL -j IP-MASQ
-A IP-MASQ -d 169.254.0.0/16 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 10.0.0.0/8 -m comment --comment "ip-masq: RFC 1918 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 172.16.0.0/12 -m comment --comment "ip-masq: RFC 1918 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 192.168.0.0/16 -m comment --comment "ip-masq: RFC 1918 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 240.0.0.0/4 -m comment --comment "ip-masq: RFC 5735 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 192.0.2.0/24 -m comment --comment "ip-masq: RFC 5737 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 198.51.100.0/24 -m comment --comment "ip-masq: RFC 5737 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 203.0.113.0/24 -m comment --comment "ip-masq: RFC 5737 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 100.64.0.0/10 -m comment --comment "ip-masq: RFC 6598 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 198.18.0.0/15 -m comment --comment "ip-masq: RFC 6815 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 192.0.0.0/24 -m comment --comment "ip-masq: RFC 6890 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -d 192.88.99.0/24 -m comment --comment "ip-masq: RFC 7526 reserved range is not subject to MASQUERADE" -j RETURN
-A IP-MASQ -m comment --comment "ip-masq: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE
COMMIT
# Completed on Thu Nov 16 12:12:42 2023
# Generated by iptables-save v1.8.5 on Thu Nov 16 12:12:42 2023
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
:CILIUM_FORWARD - [0:0]
:CILIUM_INPUT - [0:0]
:CILIUM_OUTPUT - [0:0]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
:KUBE-FIREWALL - [0:0]
:KUBE-KUBELET-CANARY - [0:0]
-A INPUT -m comment --comment "cilium-feeder: CILIUM_INPUT" -j CILIUM_INPUT
-A INPUT -j KUBE-FIREWALL
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -j ACCEPT
-A INPUT -p udp -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -p sctp -j ACCEPT
-A FORWARD -m comment --comment "cilium-feeder: CILIUM_FORWARD" -j CILIUM_FORWARD
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -p tcp -j ACCEPT
-A FORWARD -p udp -j ACCEPT
-A FORWARD -p icmp -j ACCEPT
-A FORWARD -p sctp -j ACCEPT
-A OUTPUT -m comment --comment "cilium-feeder: CILIUM_OUTPUT" -j CILIUM_OUTPUT
-A OUTPUT -j KUBE-FIREWALL
-A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A CILIUM_FORWARD -o cilium_host -m comment --comment "cilium: any->cluster on cilium_host forward accept" -j ACCEPT
-A CILIUM_FORWARD -i cilium_host -m comment --comment "cilium: cluster->any on cilium_host forward accept (nodeport)" -j ACCEPT
-A CILIUM_FORWARD -i lxc+ -m comment --comment "cilium: cluster->any on lxc+ forward accept" -j ACCEPT
-A CILIUM_FORWARD -i cilium_net -m comment --comment "cilium: cluster->any on cilium_net forward accept (nodeport)" -j ACCEPT
-A CILIUM_FORWARD -o lxc+ -m comment --comment "cilium: any->cluster on lxc+ forward accept" -j ACCEPT
-A CILIUM_FORWARD -i lxc+ -m comment --comment "cilium: cluster->any on lxc+ forward accept (nodeport)" -j ACCEPT
-A CILIUM_FORWARD -d 10.0.4.130/32 -p tcp -m tcp --dport 53 -j ACCEPT
-A CILIUM_FORWARD -s 10.0.4.130/32 -p tcp -m tcp --sport 53 -j ACCEPT
-A CILIUM_FORWARD -d 10.0.4.130/32 -p udp -m udp --dport 53 -j ACCEPT
-A CILIUM_FORWARD -s 10.0.4.130/32 -p udp -m udp --sport 53 -j ACCEPT
-A CILIUM_INPUT -m mark --mark 0x200/0xf00 -m comment --comment "cilium: ACCEPT for proxy traffic" -j ACCEPT
-A CILIUM_INPUT -s 10.0.4.130/32 -p tcp -m tcp --sport 53 -j ACCEPT
-A CILIUM_INPUT -d 10.0.4.130/32 -p tcp -m tcp --dport 53 -j ACCEPT
-A CILIUM_INPUT -s 10.0.4.130/32 -p udp -m udp --sport 53 -j ACCEPT
-A CILIUM_INPUT -d 10.0.4.130/32 -p udp -m udp --dport 53 -j ACCEPT
-A CILIUM_OUTPUT -m mark --mark 0xa00/0xfffffeff -m comment --comment "cilium: ACCEPT for proxy return traffic" -j ACCEPT
-A CILIUM_OUTPUT -m mark --mark 0x800/0xe00 -m comment --comment "cilium: ACCEPT for l7 proxy upstream traffic" -j ACCEPT
-A CILIUM_OUTPUT -m mark ! --mark 0xe00/0xf00 -m mark ! --mark 0xd00/0xf00 -m mark ! --mark 0xa00/0xe00 -m mark ! --mark 0x800/0xe00 -m mark ! --mark 0xf00/0xf00 -m comment --comment "cilium: host->any mark as from host" -j MARK --set-xmark 0xc00/0xf00
-A CILIUM_OUTPUT -d 10.0.4.130/32 -p tcp -m tcp --dport 53 -j ACCEPT
-A CILIUM_OUTPUT -s 10.0.4.130/32 -p tcp -m tcp --sport 53 -j ACCEPT
-A CILIUM_OUTPUT -d 10.0.4.130/32 -p udp -m udp --dport 53 -j ACCEPT
-A CILIUM_OUTPUT -s 10.0.4.130/32 -p udp -m udp --sport 53 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
-A KUBE-FIREWALL ! -s 127.0.0.0/8 -d 127.0.0.0/8 -m comment --comment "block incoming localnet connections" -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP
COMMIT
# Completed on Thu Nov 16 12:12:42 2023

enable-local-redirect-policy が有効になっているので、LRP の機能は使える状態です。LocalRedirectPolciy のカスタムリソースの一覧を表示してみます。どうやら LocalRedirectPolicy のカスタムリソースを作成して設定している訳ではないようです。

❯ kubectl get ciliumlocalredirectpolicies.cilium.io -A
No resources found

Cilium の Pod の中に exec して、LRP の一覧を表示してみます。すると、kube-dns の Service の VIP へのリクエストを同一ノード上の NodeLocal DNSCache に向ける設定があります。どうやら GKE でカスタマイズしている Cilium のコードの中で NodeLocal DNSCache 用の LRP を設定する処理をハードコードしているようです。ユーザーが間違って LRP のカスタムリソースを消すといった事故が起きないようにしているのかもしれませんね。

# cilium lrp list
level=info msg="Initializing Google metrics" subsys=metrics
LRP namespace   LRP name   FrontendType              Matching Service
kube-system     default    clusterIP + named ports   kube-system/kube-dns
                |          10.0.8.10:53/TCP -> 10.0.4.130:53(kube-system/node-local-dns-rpt2q),
                |          10.0.8.10:53/UDP -> 10.0.4.130:53(kube-system/node-local-dns-rpt2q),

GKE でハードコードされている LRP の設定をカスタムリソースとして再現すると以下のようになります。

apiVersion: cilium.io/v2
kind: CiliumLocalRedirectPolicy
metadata:
  name: default
  namespace: kube-system
spec:
  redirectFrontend:
    serviceMatcher:
      serviceName: kube-dns
      namespace: kube-system
  redirectBackend:
    localEndpointSelector:
      matchLabels:
        k8s-app: node-local-dns
    toPorts:
      - port: "53"
        name: dns
        protocol: UDP
      - port: "53"
        name: dns-tcp
        protocol: TCP

NodeLocal DNSCache 用の LRP の設定は、GKE で NodeLocal DNSCache アドオンを有効にしていない場合にも存在します。LRP のラベルセレクタで一致する Pod が存在しない場合、LRP の設定自体は無害であることが分かります。Cilium の実装を追っていた時に見たように、通常の Service の VIP の確率ベースの負荷分散にフォールバックするだけです。

# cilium lrp list
level=info msg="Initializing Google metrics" subsys=metrics
LRP namespace   LRP name   FrontendType              Matching Service
kube-system     default    clusterIP + named ports   kube-system/kube-dns
                |          10.0.8.10:53/TCP ->
                |          10.0.8.10:53/UDP ->

以上の内容はあくまで私の考察です。外野の人間なので、KEP-4188 を提案している本当の理由までは分かりません。現在実装されている Cilium の機能を改善したい訳ではなく、今後 Cilium に追加したい機能のために KEP-4188 を提案している可能性もあります。KEP-4188 の実装が進むにつれて、この辺りは明らかになってくるはずなので、見守っていこうと思います。

まとめ

  • KEP-4188 は Pod Conditions (特に PodReady の情報) を Kubelet 経由で公開する機能
  • Kubelet は PodStatus API を gRPC サービスとして公開する予定
  • 利用者は Unix ドメインソケットを Pod にマウントして情報を読み取る
  • KEP-4188 は GKE の Dataplane v2 (Cilium) の機能の安定性の向上のため提案されている可能性がある
    • Cilium の Local Redirect Policy (LRP) の機能はコントロールプレーンから取得した PodStatus の PodReady の情報に依存している
    • Cilium の LRP の機能は NodeLocal DNSCache を機能させるために利用されている
    • GKE Dataplane v2 (Cilium) を有効化すると LRP が自動的に設定される
    • NodeLocal DNSCache が有効な場合は LRP の設定により kube-dns の ClusterIP へのトラフィックが NodeLocal DNSCache にリダイレクトされる

参考

Discussion