Cilium 如何處理 L7 流量

那 Cilium 是如何處理 L7 流量的呢?今天就讓我們一探究竟。

注,這篇的內容是基於目前最新的 Cilium 1.13.3 和 proxy 1.23.9,不同版本間會有差異。

在開始之前先搭建先前的 “星球大戰” 環境,或者你也可以直接跳到 Debug 階段 [3]。

環境搭建

集羣

export INSTALL_K3S_VERSION=v1.27.1+k3s1
curl -sfL https://get.k3s.io | sh -s - --disable traefik --disable local-storage --disable metrics-server --disable servicelb --flannel-backend=none --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config

安裝 Cilium

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/master/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
cilium install

安裝示例應用

kubectl apply -n default -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: deathstar
  labels:
    app.kubernetes.io/name: deathstar
spec:
  type: ClusterIP
  ports:
  - port: 80
  selector:
    org: empire
    class: deathstar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deathstar
  labels:
    app.kubernetes.io/name: deathstar
spec:
  replicas: 1
  selector:
    matchLabels:
      org: empire
      class: deathstar
  template:
    metadata:
      labels:
        org: empire
        class: deathstar
        app.kubernetes.io/name: deathstar
    spec:
      containers:
      - name: deathstar
        image: docker.io/cilium/starwars
---
apiVersion: v1
kind: Pod
metadata:
  name: tiefighter
  labels:
    org: empire
    class: tiefighter
    app.kubernetes.io/name: tiefighter
spec:
  containers:
  - name: spaceship
    image: docker.io/tgraf/netperf
---
apiVersion: v1
kind: Pod
metadata:
  name: xwing
  labels:
    app.kubernetes.io/name: xwing
    org: alliance
    class: xwing
spec:
  containers:
  - name: spaceship
    image: docker.io/tgraf/netperf
EOF

設置策略

kubectl apply -n default -f - <<EOF
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "POST"
          path: "/v1/request-landing"
EOF

測試

kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
#Access denied
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
#Ship landed

查看 pod 信息。

kubectl get po -o wide -n default
NAME                         READY   STATUS    RESTARTS   AGE     IP           NODE          NOMINATED NODE   READINESS GATES
deathstar-7848d6c4d5-58jc8   1/1     Running   0          6h57m   10.0.0.111   ubuntu-dev3   <none>           <none>
xwing                        1/1     Running   0          6h57m   10.0.0.209   ubuntu-dev3   <none>           <none>
tiefighter                   1/1     Running   0          6h57m   10.0.0.123   ubuntu-dev3   <none>           <none>

後面 debug 的操作我們會直接在 cilium 的 agent pod 進行。

agent=$(kubectl get po -l app.kubernetes.io/name=cilium-agent -n kube-system -o jsonpath='{.items[0].metadata.name}')

Debug

先貼上總結的圖。

怎麼下手呢?

在 深入探索 Cilium 的工作機制 [4] 時,我們對 Cilium 的網絡策略處理機制一筆帶過:

Cilium Agent 中運行着大量的 watcher,其中一個就是 CiliumNetworkPolicy watcher。當策略創建或者更新時,Agent 會對策略進行轉換並將規則存儲到 BPF Map 中。在網絡通信時,BPF 程序會對網絡流量進行檢查並決定應當允許或者拒絕訪問。

實際上這裏的處理比較複雜,我們從 watcher 的初始化入手。

至此我們 apply 的網絡策略被寫入到 map 中。

接下來看下 ebpf 程序有任何使用該策略。

eBPF

還記得在 Kubernetes 網絡學習之 Cilium 與 eBPF[15] 中我們分析容器發出的數據包,被 LXC BPF Ingress程序處理。這裏不再贅述,處理流程可以看那篇文章。

我們先查看死星的 endpoint id 和 identity 分別爲 863 和 2033

kubectl get ciliumendpoint -n default
NAME                         ENDPOINT ID   IDENTITY ID   INGRESS ENFORCEMENT   EGRESS ENFORCEMENT   VISIBILITY POLICY   ENDPOINT STATE   IPV4         IPV6
tiefighter                   2216          29439         <status disabled>     <status disabled>    <status disabled>   ready            10.0.0.123
deathstar-7848d6c4d5-58jc8   863           2033          <status disabled>     <status disabled>    <status disabled>   ready            10.0.0.111
xwing                        775           5513          <status disabled>     <status disabled>    <status disabled>   ready            10.0.0.209

使用 endpoint id 通過通過命令查看爲死星配置的網絡策略,可以看到其中的兩條 ingress 的策略,其代理端口 19313,這個端口就是 Cilium 中 L7 代理的監聽端口。

kubectl exec $agent -n kube-system -c cilium-agent -- cilium bpf policy get 863
POLICY   DIRECTION   LABELS (source:key[=value])                                              PORT/PROTO   PROXY PORT   BYTES   PACKETS
Allow    Ingress     reserved:host                                                            ANY          NONE         0       0
                     reserved:kube-apiserver
Allow    Ingress     k8s:app.kubernetes.io/name=deathstar                                     80/TCP       19313        0       0
                     k8s:class=deathstar
                     k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default
                     k8s:io.cilium.k8s.policy.cluster=default
                     k8s:io.cilium.k8s.policy.serviceaccount=default
                     k8s:io.kubernetes.pod.namespace=default
                     k8s:org=empire
Allow    Ingress     k8s:app.kubernetes.io/name=tiefighter                                    80/TCP       19313        0       0
                     k8s:class=tiefighter
                     k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default
                     k8s:io.cilium.k8s.policy.cluster=default
                     k8s:io.cilium.k8s.policy.serviceaccount=default
                     k8s:io.kubernetes.pod.namespace=default
                     k8s:org=empire
Allow    Egress      reserved:unknown                                                         ANY          NONE         0       0

BPF 程序處理流量在檢查策略時 bpf_lxc.c#L1842[16],檢查配置的策略帶有代理端口執行 POLICY_ACT_PROXY_REDIRECT 將流量重定向給代理(端口 19313,地址爲主機地址)。

Cilium Proxy

Cilium agent 提供了 xds server 實現,通過 Unix Domain Socket /var/run/cilium/xds.sock 與 proxy 進行通信,下發配置。

我們參考 cilium-bugtool 的 dump 源碼 [17],dump 代理的配置。

kubectl exec $agent -n kube-system -c cilium-agent -- curl -s --unix-socket /var/run/cilium/envoy-admin.sock http://admin/config_dump?include_eds

從配置 config.json 中可以看到 Cilium 在 envoy proxy 中實現瞭如下三個不同類型的過濾器(Filter):

監聽器過濾器

監聽器過濾器(Listener Filter)cilium.BpfMetadata[18] 會從幾個數據源中準備元數據:策略、監聽器設置、請求方的標識等。數據源包括 xds 配置、BPF map cilium_ipcachecilium_ct4_global(ct:connection tracking。當然還包括 ct6 相關的 map)。

從數據源中獲取的數據保存在 socket option 中(proxy 源碼 bpf_metadata.cc#L364[19]),作爲上下文元數據的在其他的過濾器中使用。

元數據數據源

xds filter 配置,這裏提供了 bpf map 的根目錄 /sys/fs/bpf,以及 is_ingress: true 表示當前 filter 是在入口監聽器上(ingress listener):

{
 "name""cilium.bpf_metadata",
 "typed_config"{
  "@type""type.googleapis.com/cilium.BpfMetadata",
  "bpf_root""/sys/fs/bpf",
  "is_ingress"true
 }

xds network policy 配置(截取了 proxy 的部分配置),從配置中可以找到 endpoint 的 IP 和 id,以及前面我們設置的 規則 [20]:

{
 "@type""type.googleapis.com/cilium.NetworkPoliciesConfigDump",
 "networkpolicies"[
  {
   "endpoint_ips"[
    "10.0.0.111"
   ],
   "endpoint_id""863",
   "ingress_per_port_policies"[
    {
     "port": 80,
     "rules"[
      {
       "http_rules"{
        "http_rules"[
         {
          "headers"[
           {
            "name"":method",
            "safe_regex_match"{
             "google_re2"{},
             "regex""POST"
            }
           },
           {
            "name"":path",
            "safe_regex_match"{
             "google_re2"{},
             "regex""/v1/request-landing"
            }
           }
          ]
         }
        ]
       }
      }
     ]
    }
   ],
   "egress_per_port_policies"[
    {}
   ],
   "conntrack_map_name""global"
  },
  ...
}

Map cilium_ipcache,可以通過連接信息中的 IP 地址獲取身份標識,如死星的 identity[21] 爲 2033(見 proxy 源碼 bpf_metadata.cc#L165[22]):

kubectl exec $agent -n kube-system -c cilium-agent -- cilium bpf ipcache list
IP PREFIX/ADDRESS   IDENTITY
10.0.0.67/32        identity=encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.111/32       identity=2033 encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.123/32       identity=29439 encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.243/32       identity=encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.160/32       identity=19608 encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.209/32       identity=5513 encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
192.168.1.13/32     identity=encryptkey=tunnelendpoint=0.0.0.0 nodeid=0
0.0.0.0/0           identity=encryptkey=tunnelendpoint=0.0.0.0 nodeid=0

Map cilium_ct4_global,從連接跟蹤(connection tracking)中獲取請求方的 identity(SourceSecurityID 29439,鈦戰機的標識):

cilium bpf ct list global
TCP OUT 10.0.0.123:48954 -> 10.0.0.111:80 expires=58774 RxPackets=RxBytes=435 RxFlagsSeen=0x1b LastRxReport=58764 TxPackets=TxBytes=522 TxFlagsSeen=0x1b LastTxReport=58764 Flags=0x0013 [ RxClosing TxClosing SeenNonSyn ] RevNAT=SourceSecurityID=29439 IfIndex=0
TCP IN 10.0.0.67:33988 -> 10.0.0.111:80 expires=58776 RxPackets=RxBytes=659 RxFlagsSeen=0x1b LastRxReport=58766 TxPackets=TxBytes=386 TxFlagsSeen=0x1b LastTxReport=58766 Flags=0x0013 [ RxClosing TxClosing SeenNonSyn ] RevNAT=SourceSecurityID=29439 IfIndex=0
TCP IN 10.0.0.123:48954 -> 10.0.0.111:80 expires=80364 RxPackets=RxBytes=522 RxFlagsSeen=0x1b LastRxReport=58764 TxPackets=TxBytes=TxFlagsSeen=0x00 LastTxReport=Flags=0x0051 [ RxClosing SeenNonSyn ProxyRedirect ] RevNAT=SourceSecurityID=29439 IfIndex=0

過濾器

過濾器(Filter)cilium.NetworkFilter[23] 工作在 L4,用於處理已建立的鏈接,應用端口級的策略,即 L4 策略。

從上下文元數據中保存的 endpoint 相關的策略中查找與目標端口相關的策略,檢查請求方證書中的 sni 和請求方的身份標識 identity 是否在白名單中,見 proxy 源碼 network_filter.cc#L169[24]。

假如策略上設置了 L7 的協議,會使用 Golang 編寫的解析器對 L7 的數據進行解析。

在本示例中並未使用 L4 的策略。

HTTP 過濾器

HTTP 過濾器(HTTP Filter)cilium.L7Policy[25] 是本文的重點,但相對其他兩個過濾器來說邏輯就簡單多了。

"http_filters"[
 {
  "name""cilium.l7policy",
  "typed_config"{
   "@type""type.googleapis.com/cilium.L7Policy",
   "access_log_path""/var/run/cilium/access_log.sock"
  }
 }

在過濾器對 HTTP 請求頭進行解碼時(見 proxy 源碼 l7policy.cc#L97[26]),依然是從上下文元數據中獲取策略等內容。拿到策略後,與請求方(對於這裏 ingress 的場景檢查請求方,如果是 egress 的場景,檢查上游的標識)的標識、請求頭的信息進行比對,決定放行還是拒絕請求。

總結

整篇看下來,Cilium 在處理 L7 流量上的實現還是比較複雜的,牽扯多個組件協同。eBPF 在 L3/L4 流量處理上有着優異的性能優勢,但是對 L7 流量處理仍然無法脫離 sidecar 代理(不論 sidecar 是 per pod 還是 per node)。而 L7 流量處理也恰恰有着非常多的使用場景,不僅僅是 HTTP 協議。

參考資料

[1] 

使用 Cilium 增強 Kubernetes 網絡安全: https://atbug.com/enhance-kubernetes-network-security-with-cilium/

[2] 

Envoy Proxy: https://github.com/cilium/proxy

[3] 

Debug 階段: #debug

[4] 

深入探索 Cilium 的工作機制: https://atbug.com/deep-dive-into-cilium/# 網絡策略

[5] 

#enableK8sWatchers: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/k8s/watchers/watcher.go#L525

[6] 

#ciliumNetworkPoliciesInit: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/k8s/watchers/cilium_network_policy.go#L85

[7] 

#addCiliumNetworkPolicyV2: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/k8s/watchers/cilium_network_policy.go#L159

[8] 

#PolicyAdd: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/daemon/cmd/policy.go#L224

[9] 

Daemon: https://atbug.com/deep-dive-into-cilium/#agent

[10] 

#policyAdd: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/daemon/cmd/policy.go#L249

[11] 

PolicyReactionEvent.Handle: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/daemon/cmd/policy.go#L454

[12] 

EndpointRegenerationEvent#Handle: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/endpoint/events.go#L27

[13] 

Endpoint.regenerate: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/endpoint/policy.go#L286

[14] 

Endpoint.regenerateBPF: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/endpoint/bpf.go#L584

[15] 

Kubernetes 網絡學習之 Cilium 與 eBPF: https://atbug.com/learn-cilium-and-ebpf/# 第 - 2 - 步 pod1-lxc-bpf-ingress

[16] 

bpf_lxc.c#L1842: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/bpf/bpf_lxc.c#L1842

[17] 

dump 源碼: https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/bugtool/cmd/root.go#L505

[18] 

cilium.BpfMetadata: https://github.com/cilium/proxy/blob/v1.23/cilium/bpf_metadata.cc

[19] 

bpf_metadata.cc#L364: https://github.com/cilium/proxy/blob/v1.23/cilium/bpf_metadata.cc#L364

[20] 

規則: #設置策略

[21] 

identity: https://atbug.com/deep-dive-into-cilium/# 端點 - endpoint

[22] 

bpf_metadata.cc#L165: https://github.com/cilium/proxy/blob/v1.23/cilium/bpf_metadata.cc#L165

[23] 

cilium.NetworkFilter: https://github.com/cilium/proxy/blob/v1.23/cilium/network_filter.cc

[24] 

network_filter.cc#L169: https://github.com/cilium/proxy/blob/v1.23/cilium/network_filter.cc#L169

[25] 

cilium.L7Policy: https://github.com/cilium/proxy/blob/v1.23/cilium/l7policy.cc

[26] 

l7policy.cc#L97: https://github.com/cilium/proxy/blob/v1.23/cilium/l7policy.cc#L97

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/XyVHJjxOjubxfeNecUVjfw