天天看點

Cilium 官方文檔翻譯 - 替換kube-proxy

快速開始

通過kubeadm init初始化控制平面節點,并跳過kube-proxy插件的安裝:

根據您使用的CRI實作,您可能需要在kubeadm init…指令中使用--cri-socket選項定制CRI。例如:如果您使用的是Docker CRI,您将使用--cri-socket unix:///var/run/cri-dockerd.sock。

kubeadm init --skip-phases=addon/kube-proxy           

然後,通過指定控制平面節點IP位址和kubeadm init傳回的令牌來加入工作節點(對于本教程,至少要向叢集添加一個工作節點):

kubeadm join <..>           

如果您有多個網絡接口,請確定在每個worker上正确設定kubelet的--node-ip。否則,Cilium的kube-proxy 替換可能無法正常工作。您可以通過運作kubectl get nodes-owide來驗證這一點,以檢視每個節點是否具有配置設定給每個節點上具有相同名稱的裝置的InternalIP。

對于已經通過DaemonSet安裝了kube-proxy的叢集,請使用以下指令将kube-proxy其删除。注意:該操作斷開現有的服務連接配接,停止與服務相關的通信,直到安裝了cilium替換:

$ kubectl -n kube-system delete ds kube-proxy
$ # Delete the configmap as well to avoid kube-proxy being reinstalled during a Kubeadm upgrade (works only for K8s 1.19 and newer)
$ kubectl -n kube-system delete cm kube-proxy
$ # Run on each node with root permissions:
$ iptables-save | grep -v KUBE | iptables-restore           

注意請使用helm3完成本教程的安裝操作,helm2是不相容的。

設定helm倉庫:

helm repo add cilium https://helm.cilium.io/           

接下來,生成所需的YAML檔案并部署它們。重要:請通過kubeadm init報告的kube-apiserver IP位址和端口号确認你設定的 API_SERVER_IP和API_SERVER_PORT正确(kubeadm預設使用端口6443)。

因為kubeadm init是在沒有kube-proxy的情況下直接運作,是以必須要設定這兩個環境變量。盡管kubeadm将KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT使用ClusterIP的模式導出到環境變量中。但沒有kube-proxy,網絡是不通的。是以,需要通過以下配置使cilium了解該資訊:

API_SERVER_IP=<your_api_server_ip>
# Kubeadm default is 6443
API_SERVER_PORT=<your_api_server_port>
helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=strict \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

Cilium将自動在預設路徑路徑/run/cilium/cgroupv2上挂載附加BPF cgroupv2程式所需的cgroup2檔案系統。為此,它需要daemonset的init容器挂載主機/proc路徑。通過 --set cgroup.autoMount.enabled=false 可禁用自動挂載,并通過使用--set cgroup.hostRoot設定一個已經在主機挂載的cgroup v2檔案系統。例如,如果尚未安裝,可以通過在主機上運作mount -t cgroup2 none /sys/fs/cgroup指令來安裝cgroup v2檔案系統,然後找helm安裝指令設定參數--set cgroup.hostRoot=/sys/fs/cgroup。

helm最終将cilium安裝為一個CNI插件,并使用eBPF kube-proxy replacement來實作實作對ClusterIP、NodePort、LoadBalancer類型的Kubernetes服務和具有ExternalIP的服務的處理。此外,eBPF kube-proxy replacement還支援容器的hostPort,是以不再需要使用portmap插件。

最後,驗證cilium正确地在叢集中安裝,并準備好提供網絡服務。

$ kubectl -n kube-system get pods -l k8s-app=cilium
NAME                READY     STATUS    RESTARTS   AGE
cilium-fmh8d        1/1       Running   0          10m
cilium-mkcmb        1/1       Running   0          10m           

注意,在上述Helm配置中,kubeProxyReplacement已設定為嚴格(strict)模式。這意味着,如果缺少底層Linux核心支援,Cilium agent将直接退出。

預設情況下,Helm将kubeProxyReplacement設定為disabled,這意味着cilium僅對叢集内的ClusterIP服務啟用的負載平衡。

Cilium在直接路由和隧道模式中都支援eBPF kube-proxy replacement。

驗證設定

在使用上述快速入門指南部署Cilium之後,我們可以首先驗證Cilium代理是否以所需模式運作:

$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement
KubeProxyReplacement:   Strict      [eth0 (Direct Routing), eth1]           

使用--verbose選項可以檢視更多詳細資訊。

$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose
[...]
KubeProxyReplacement Details:
  Status:                Strict
  Socket LB:             Enabled
  Protocols:             TCP, UDP
  Devices:               eth0 (Direct Routing), eth1
  Mode:                  SNAT
  Backend Selection:     Random
  Session Affinity:      Enabled
  Graceful Termination:  Enabled
  NAT46/64 Support:      Enabled
  XDP Acceleration:      Disabled
  Services:
  - ClusterIP:      Enabled
  - NodePort:       Enabled (Range: 30000-32767)
  - LoadBalancer:   Enabled
  - externalIPs:    Enabled
  - HostPort:       Enabled
[...]           

下一步,我們将建立一個Nginx部署。然後,我們将建立一個新的NodePort服務,并驗證Cilium是否能正确處理網絡服務。

以下yaml用于建立後端Pod:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80           

驗證nginx pod啟動并進入running階段。

$ kubectl get pods -l run=my-nginx -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP             NODE   NOMINATED NODE   READINESS GATES
my-nginx-756fb87568-gmp8c   1/1     Running   0          62m   10.217.0.149   apoc   <none>           <none>
my-nginx-756fb87568-n5scv   1/1     Running   0          62m   10.217.0.107   apoc   <none>           <none>           

接下來,為兩個pod建立NodePort類型的service。

$ kubectl expose deployment my-nginx --type=NodePort --port=80
service/my-nginx exposed           

驗證NodePort類型的service是否建立成功。

$ kubectl get svc my-nginx
NAME       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
my-nginx   NodePort   10.104.239.135   <none>        80:31940/TCP   24m           

通過cilium service list指令,我們可以驗證cilium的eBPF kube-proxy replacement建立了新的NodePort服務。在本例中,建立了具有端口31940的服務(裝置eth0和eth1各一個):

$ kubectl -n kube-system exec ds/cilium -- cilium service list
ID   Frontend               Service Type   Backend
[...]
4    10.104.239.135:80      ClusterIP      1 => 10.217.0.107:80
                                           2 => 10.217.0.149:80
5    0.0.0.0:31940          NodePort       1 => 10.217.0.107:80
                                           2 => 10.217.0.149:80
6    192.168.178.29:31940   NodePort       1 => 10.217.0.107:80
                                           2 => 10.217.0.149:80
7    172.16.0.29:31940      NodePort       1 => 10.217.0.107:80
                                           2 => 10.217.0.149:80           

建立一個用于node port測試的變量:

node_port=$(kubectl get svc my-nginx -o=jsonpath='{@.spec.ports[0].nodePort}')           

同時我們可以驗證,主控端上iptables應該查不到相關規則。

$ iptables-save | grep KUBE-SVC
[ empty line ]           

最後通過一個簡單的curl測試顯示暴露的NodePort和ClusterIP的連通性。

$ curl 127.0.0.1:$node_port
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
[....]           
$ curl 192.168.178.29:$node_port
<!doctype html>
<html>
<head>
<title>welcome to nginx!</title>
[....]           
$ curl 172.16.0.29:$node_port
<!doctype html>
<html>
<head>
<title>welcome to nginx!</title>
[....]           
$ curl 10.104.239.135:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
[....]           

如上,eBPF kube-proxy replacement 就正确設定并驗證完成了。

更多設定

用戶端源IP保留

cilium eBPF kube-proxy replacement 可以通過多個選項來禁止通路NodePort時執行SNAT,避免資料發到對端時源IP位址丢失。

  • externalTrafficPolicy=Local: eBPF支援Local政策。對于設定了externalTrafficPolicy=Local的service,叢集内所有節點都可以對後端的節點發起連接配接。
  • externalTrafficPolicy=Cluster:Cluster是建立service的預設政策。僅需要暴露TCP服務時這種政策下有多個選項可以實作保留外部流量源IP的能力:可以使用 kube-proxy replacement 的DSR或者混合模式來保留源位址。

Maglev Consistent Hash

cilium eBPF kube-proxy replacement 使用了The Maglev paper 論文介紹的變種hash算法來實作在負載均衡後端endpoint一緻性hash。這種算法提高了更好的負載平衡和故障恢複能力。叢集中的某個節點在不用考慮同步其它節點的狀态時就可以對某個5元組做出一緻的後端選擇。而在某個後端被删除後,後端查找表重新排列時對不相關的後端執行個體影響最小(重新配置設定後的一緻性hash差異率約為1%)。

通過下面的選項可以開啟Maglev hash。loadBalancer.algorithm=maglev

Maglev 是來自于google的負載均衡軟體

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=strict \
    --set loadBalancer.algorithm=maglev \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

請注意,Maglev hash僅适用于外部(N-S)流量。對于叢集内服務連接配接(E-W),套接字直接配置設定給服務後端,例如在TCP連接配接時,沒有任何中間跳,是以不受Maglev的影響。Cilium的XDP加速也支援Maglev hash。

使用Maglev hash時有兩個額外的配置:maglev.tableSize和 maglev.hashSeed。

  • maglev.tableSize為每個service定義了Maglev查找表,Maglev推薦将表空間(M)設定的要比後端執行個體數量(N)的最大值要大。在實踐中,這意味着M應該大于100*N,以保證後端更改的重新配置設定中最多有1%的差異。M必須是素數。Cilium使用M的預設大小16381。以下M可以作為cilium支援為Maglev表大小選項:
maglev.tableSize
251
509
1021
2039
4093
8191
16381
32749
65521
131071

例如大小16381适用于叢集任意一個服務最多有160個後端執行個體。如果服務有更多的後端,則後端更改的重新配置設定差異将增加。

  • maglev.hashSeed設定該參數後cilium将不再依賴固定的種子(推薦設定該值),種子是一個base64編碼的12位元組随機數,可以通過head -c12 /dev/urandom | base64 -w0生成一個種子,叢集中所有的cilium agent必須使用相同的Maglev種子才能正常工作。

下面的部署示例生成種子後傳遞給Helm,同時将Maglev表大小設定為65521,允許給定服務的最大後端數為650(後端重新配置設定的屬性差異最多為1%):

SEED=$(head -c12 /dev/urandom | base64 -w0)
helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=strict \
    --set loadBalancer.algorithm=maglev \
    --set maglev.tableSize=65521 \
    --set maglev.hashSeed=$SEED \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

注意相對于預設的負載均衡算法(loadBalancer.algorithm=random),Maglev一緻性算法會消耗更多的記憶體。

Direct Server Return (DSR)

預設情況下,Cilium eBPF NodePort的實作使用SNAT模式。也就是說,當節點外部流量到達并且節點确定負LoadBalancer、NodePort或具有externalIP的服務的後端位于其它節點時,則執行SNAT僞裝源位址其将請求重定向到遠端後端。這不需要對資料包的内容更改。但是來自後端的響應需要在将資料包直接傳回到外部用戶端之前,額外跳回到該童工負載均衡器的節點以在執行反向SNAT轉換。

Helm 選項loadBalancer.mode=dsr 可以設定Cilium的eBPF NodePort在dsr模式下運作。在這種模式下,後端直接回複外部用戶端,而不需要額外的躍點,這意味着後端通過使用service IP/port作為源進行回複。DSR隻在Native-Routing模式下工作,隧道模式下無法工作。

DSR模式的另一個優點是保留了用戶端的源IP,是以可以在後端節點上對其進行政策比對。。如果一個特定的後端可以被多個服務使用,那麼後端需要知道它們回複資料包時應使用的service IP/port。是以,cilium在定制了專屬的IPv4選項(IPv6目的地選項),并把service的IP/port資訊編碼在擴充報頭中,代價是MTU值變小。對于TCP服務,Cilium僅對SYN資料包的service IP/port進行編碼,而不對後續資料包進行編碼。如果想避免MTU減少,後面還介紹了混合DSR模式。如下一小節所述,其中DSR用于TCP,SNAT用于UDP。

請注意,DSR模式的使用在某些公共雲提供商環境中可能不起作用,因為Cilium特定的IP選項可能會被底層網絡結構丢棄。如果通過NodePort服務請求的遠端節點上的服務存在連接配接問題,請首先檢查資料包是否實際到達NodePort service包含的後端節點。如果資料包無法傳遞,建議将切換回預設SNAT模式。

此外,在一些實作源/目的地IP位址檢查(例如AWS)的公共雲提供商環境中,為了使DSR模式工作,必須禁用該檢查。

啟用DSR模式的配置如下:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set tunnel=disabled \
    --set autoDirectNodeRoutes=true \
    --set kubeProxyReplacement=strict \
    --set loadBalancer.mode=dsr \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

DSR與SNAT混合模式

Cilium還支援混合DSR和SNAT模式,例如對TCP連接配接執行DSR,對UDP執行SNAT。這樣就不影響網絡的MTU了,同時通過減少了額外的應答躍點(尤其是當TCP是工作負載的主要傳輸時),獲得改進延遲收益。

loadBalancer.mode選項可以有三個可選值:snat(預設值)、dsr和hybrid。

下面是一個通過helm配置混合模式的示例:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set tunnel=disabled \
    --set autoDirectNodeRoutes=true \
    --set kubeProxyReplacement=strict \
    --set loadBalancer.mode=hybrid \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

繞過直接在pod命名空間中執行Socket LoadBalancer

套接字級負載均衡器對cilium的下層資料路徑透明,因為在應用程式發起 connect(TCP或連接配接的UDP),sendmsg(UDP)或者recvmsg(UDP)系統調用時,會檢查目标IP是否在現有的service IP中,并會從對應的Endpoint中選擇一個後端作為實際目标。這意味着雖然應用程式假設它連接配接到服務位址,但相應的核心套接字實際上連接配接到的是後端的位址,是以不需要額外的底層NAT。

當應用自定義的重定向或流量操作依賴了pod命名空間中的原始ClusterIP時,或者由于pod的性質導緻套接字級負載均衡器無法生效(如在KubeVirt,Kata容器環境下)時,cilium會自動降級為在veth接口上使用tc負載均衡器。

socketLB.hostNamespaceOnly=true選項可以開啟繞過模式。這将繞過bpf鈎子對connect()和sendmsg()系統調用套接字重寫,并将原始資料包傳遞到下一個操作階段(例如,per-endpoint-routing下的堆棧),并重新啟用tc bpf程式中的服務查找。

下面示例展示了如何配置在 kube-proxy-free環境中繞過Socket LB:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set tunnel=disabled \
    --set autoDirectNodeRoutes=true \
    --set kubeProxyReplacement=strict \
    --set socketLB.hostNamespaceOnly=true           

LoadBalancer & NodePort XDP 加速

cilium 1.8 引入了XDP(eXpress Data Path),當 cilium 需要轉發請求,且後端位于遠端節點時,cilium 内置了對NodePort、LoadBlancer和ExternalIP服務加速的功能。XDP的eBPF程式直接在網卡上運作,而不是在更高層運作。

選項loadBalancer.acceleration值為native可以開啟加速,預設使用disable禁用加速。大多數10GB或更高速率的網卡驅動程式都在新版的核心上支援了nativeXDP。在雲上大多數驅動程式都支援SR-IOV變體,也支援nativeXDP。對于私有化部署,cilium XDP加速可以與kubernetes負載均衡器(如 MetalLB)配合使用。加速器僅能在支援直接路由的單個裝置上使用。

在大規模的叢集環境,應該注意考慮調整eBPF預設Map的大小。例如通過config.bpfMapDynamicSizeRatio可以在cilium agent啟動時根據系統記憶體來動态設定幾個占用空間較大的eBPF Map的大小。

loadBalancer.acceleration選項支援與DSR、SNAT和混合模式配合使用。下面是結合混合模式負載均衡器使用的例子:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set tunnel=disabled \
    --set autoDirectNodeRoutes=true \
    --set kubeProxyReplacement=strict \
    --set loadBalancer.acceleration=native \
    --set loadBalancer.mode=hybrid \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

在多個裝置的環境中,可以通過指定裝置或者通過cilium裝置自動檢測機制選擇多個裝置來暴露節點端口,所有被選中的裝置都會執行XDP加速。這意味着需要每個裝置的驅動都支援nativeXDP,此外處于性能的原因,推薦在使用多裝置加速環境的核心版本>=5.5。

下表給出了支援nativeXDP加速的驅動清單。通過ethtool -i eth0可以檢視裝置使用的驅動。

# ethtool -i eth0
driver: nfp
[...]           
Vendor Driver XDP Support
Amazon ena >= 5.6
Broadcom bnxt_en >= 4.11
Cavium thunderx >= 4.12
Freescale dpaa2 >= 5.0
Intel ixgbe >= 4.12
ixgbevf >= 4.17
i40e >= 4.13
ice >= 5.5
Marvell mvneta >= 5.5
Mellanox mlx4 >= 4.8
mlx5 >= 4.9
Microsoft hv_netvsc >= 5.6
Netronome nfp >= 4.10
Others virtio_net >= 4.10
tun/tap >= 4.14
Qlogic qede >= 4.10
Socionext netsec >= 5.3
Solarflare sfc >= 5.5
Texas Instruments cpsw >= 5.3

通過cilium status指令可以反映出cilium kube-proxy XDP模式。

$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep XDP
  XDP Acceleration:    Native           

注意,由于資料包的處理在網絡堆棧的更底層,是以在XDP層處理NodePort服務并推回裝置的資料包通過tcpdump不可見。通過cilium的螢幕指令和度量計數器可用于觀察XDP處理的資料包。

NodePort 裝置、端口和綁定設定

當運作Cilium eBPF kube-proxy replacement時,NodePort 、LoadBalancer和externalIP service通過主機預設路由和kubernetes已配置設定InternalIP或ExternalIP的本地IP位址是可以通路的(InternalIP優于ExternalIP)。要更改綁定的裝置,可以通過Helm devices選項。例如: devices='{eth0,eth1,eth2}'。列出的裝置名必須在每個cilium管理的節點都存在。如果裝置在不同節點之間不一直,則可以使用通配符選項,例如, devices=eth+,這将比對以字首eth開頭的任何裝置。如果沒有裝置可以比對,cilium将嘗試執行自動檢測。

當使用多個裝置時,隻有一個裝置可以用于cilium節點之間的直接路由。預設情況下,如果通過裝置檢測到或指定了單個裝置,則Cilium将使用該裝置進行直接路由。否則,Cilium将使用設定了Kubernetes InternalIP或ExternalIP的裝置。如果兩者都存在,則内部IP優先于外部IP。要更改直接路由裝置,請設定nodePort.directRoutingDeviceHelm選項,例如:directRoutingDevice=eth+。如果有多個裝置比對通配符選項,Cilium将按照字母數字遞增的順序對它們進行排序,并選擇第一個。如果裝置中不存在直接路由裝置,Cilium會将該裝置添加到後一個清單中。直接路由裝置也用于NodePort XDP加速(如果啟用)。

此外,基于Socket-LB功能,可以從叢集内的主機或pod通過其公共、本地(docker*字首名稱除外)或環回位址(例如127.0.0.1:NODE_PORT)通路NodePort服務。

如果kube-apiserver配置為使用非預設NodePort範圍(30000-32766),則必須通過nodePort.range選項将相同範圍傳遞給Cilium。例如,作為節點端口。範圍為10000-32767的範圍nodePort.range="10000\,32767"。

如果節點端口範圍與臨時端口範圍(net.ipv4.ip_local_port_range)重疊,Cilium會将節點端口範圍附加到保留端口(net.ipv4.ip_ local_ reserved_ports)。這是為了防止節點端口服務劫持源端口與服務端口比對的主機本地應用程式的流量。要禁用保留端口,請設定nodePort.autoProtectPortRanges=false。

預設情況下,NodePort實作防止應用程式bind(2)對NodePort服務端口的請求。在這種情況下,應用程式通常會看到bind: Operation not permitted的錯誤。預設情況下,對于較舊的核心,或者從v5.7核心開始,隻針對主機名稱空間,是以不再影響任何應用程式pod bind(2)請求。專家使用者可以通過nodePort.bindProtection=false來更改此設定。

NodePort 對 FHRP 和 VPC 的支援

Cilium eBPF kube-proxy replacement與FHRP(如VRRP)或Cisco的HSRP和VPC(也稱為多機箱以太網通道)結合使用時,Cilium預設的FIB優化配置将繞過對FIB表的查找,使用目的地為NodePort的ingress向的源IP位址對應的mac位址作為應答時轉發L2幀的目的位址,導緻資料路徑出現問題。這種環境中,最好關閉Cilium對FIB查找優化。確定應答資料包能在忽略來源位址,始終轉發到目前活動的對端FHRP對應的mac位址。

通過設定bpf.lbBypassFIBLookup=false可以禁用優化:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=strict \
    --set bpf.lbBypassFIBLookup=false           

配置BPF Map 容量

在大規模的叢集環境,應該注意考慮調整eBPF預設Map的大小。使用Helm選項可以調節這些配置。

bpf.lbMapMax預設大小是65535,調節該參數可以增加BPF LB service條目,後端和關聯的映射的大小。

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=strict \
    --set bpf.lbMapMax=131072           

支援Container HostPort

盡管主機端口映射不是kube-proxy的能力,但Cilium eBPF kube-proxy replacement 還是選擇了在無需配置Helm CNIcni.chainingMode=portmap選項時,原生的支援主機端口映射。

kubeProxyReplacement=strict模式下,将自動啟用主機端口映射支援能力。如果不在此模式下,則需要通過hostPort.enabled=true啟用該功能。

如果指定的hostPort沒有額外的hostIP,則Pod将同NodePort類型的service一樣自動發現主機的位址暴露給外部,例如使用Kubernetes InternalIP或ExternalIP暴露服務位址(如果設定)。此外,還可以通過節點上的環回位址(例如127.0.0.1:hostPort)通路Pod。如果除了hostPort之外,還為Pod指定了hostIP,則Pod将僅在給定的hostIP上公開。0.0.0.0的hostIP與未指定hostIP行為相同。注意hostPort不應在NodePort端口範圍内,以避免沖突。

下面在沒有kube-proxy的環境下使用deployment工作負載展示一個示例:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=strict \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

确認每個節點都通過 INTERNAL-IP 或 EXTERNAL-IP知道自身的ip位址。

$ kubectl get nodes -o wide
NAME   STATUS   ROLES    AGE     VERSION   INTERNAL-IP      EXTERNAL-IP   [...]
apoc   Ready    master   6h15m   v1.17.3   192.168.178.29   <none>        [...]
tank   Ready    <none>   6h13m   v1.17.3   192.168.178.28   <none>        [...]           

如果沒有發現node ip,則可以通過kubelet啟動參數 --node-ip來指定。假設eth0是預設網卡名,下面腳本可以用于發現node ip。

echo KUBELET_EXTRA_ARGS=\"--node-ip=$(ip -4 -o a show eth0 | awk '{print $4}' | cut -d/ -f1)\" | tee -a /etc/default/kubelet           

然後更新/etc/default/kubelet檔案,重新開機kubelet。

為了驗證是否已在Cilium中啟用HostPort功能,通過cilium status CLI指令的KubeProxyReplacement資訊行可以觀察到相關資訊。如果已成功啟用,HostPort将顯示為已啟用,例如:

$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep HostPort
  - HostPort:       Enabled           

下面的yaml指定hostPort: 8080驗證端口映射。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
          hostPort: 8080           

部署後,我們可以驗證Cilium eBPF kube-proxy replacement 是否為容器暴露HostPort 8080端口。

$ kubectl exec -it -n kube-system cilium-fmh8d -- cilium service list
ID   Frontend               Service Type   Backend
[...]
5    192.168.178.29:8080    HostPort       1 => 10.29.207.199:80           

類似地,我們可以通過主機名稱空間中的iptables檢查主機端口服務是否存在iptables規則:

$ iptables-save | grep HOSTPORT
[ empty line ]           

最後使用curl測試聯通性:

$ curl 192.168.178.29:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
[....]           

删除deployment後,對應的HostPort也被删除。

kubectl delete deployment my-nginx           

kube-proxy 混合模式

Cilium eBPF kube-proxy replacement 支援多種配置模式。它可以完全替換kube-proxy,當linux核心不支援完整的 kube-proxy replacement時也可以與kube-proxy共存。

注意:在使用 kube-proxy replacement和kube-proxy共存時,這兩個元件各自獨立運作。這意味着如果需要在叢集中添加或删除 kube-proxy replacement,如果需要将服務流量委托給kube-proxy,則現有的服務流量會中斷。這是因為兩個NAT表彼此之間資料并不一樣。

下面詳述 kube-proxy replacement的選項:

  • kubeProxyReplacement=strict:此選項期望Cilium eBPF kube-proxy replacement完全替代kube-proxy運作。cilium agnet啟動後将負責處理ClusterIP、NodePort、LoadBalancer類型的Kubernetes服務,以及帶有externalIPs和HostPort的服務。如果未滿足底層核心版本要求,則cilium agent将在啟動時列印錯誤消息。
  • kubeProxyReplacement=partial:此選項為混合模式,即kube-proxy正常在叢集中運作, kube-proxy replacement替換并優化部分kube-proxy的能力。partial選項需要使用者手動指定需要 eBPF kube-proxy replacement替換的功能。當kubeProxyReplacement設定為partial時,請確定還将enableHealthCheckNodeport設定為false,以便cilium agnet不會啟動對NodePort健康檢查。與strict模式類似,如果不滿足最低核心要求,cilium agent将在啟動時發出錯誤消息。socketLB.enabled, nodePort.enabled, externalIPs.enabled和 hostPort.enabled四個細粒度配置預設都是false。下面提供了部分選項的一些示例配置。

下面的Helm選項相當于kubeProxyReplacement=strict:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=partial \
    --set socketLB.enabled=true \
    --set nodePort.enabled=true \
    --set externalIPs.enabled=true \
    --set hostPort.enabled=true \
    --set k8sServiceHost=${API_SERVER_IP} \
    --set k8sServicePort=${API_SERVER_PORT}           

下面的Helm選項相當于kube-proxy環境中使用v1.6或更早版本Cilium處理service,即為Pod提供ClusterIP:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=partial           

以下Helm選項将優化Cilium的NodePort、 LoadBalancer 和 externalIPs service,并在kube-proxy環境中處理外部ingress到cilium管理的節點的流量:

helm install cilium cilium/cilium --version 1.12.1 \
    --namespace kube-system \
    --set kubeProxyReplacement=partial \
    --set nodePort.enabled=true \
    --set externalIPs.enabled=true           
  • kubeProxyReplacement=disabled:完全禁用kube-proxy replacement,但從pod内通路ClusterIP服務除外。這是Cilium Helm部署的預設模式。

通過指令行工具檢視目前cilium kube-proxy replacement的模式:

$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement
KubeProxyReplacement:   Strict      [eth0 (DR)]           

優雅退出

cilium kube-proxy replacement支援服務後端pod優雅退出,該功能需要kubernetes 版本大于等于1.20,且開啟了EndpointSliceTerminatingCondition特性。cilium agent預設會檢測pod的Terminating狀态,如果不需要可以通過選項enable-k8s-terminating-endpoint來關閉該特性。

cilium status指令可以檢視cilium的特性辨別:

$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose
[...]
KubeProxyReplacement Details:
 [...]
 Graceful Termination:  Enabled
[...]           

當cilium agent接收到端點Terminating的Kubernetes更新事件時,該端點的資料路徑狀态将被删除,這樣它将不會為新連接配接提供服務,但端點已有的活動連接配接要能正常終止。當agent收到端點的Kubernetes删除事件時,端點狀态被完全删除。Kubernetes pod termination文檔包含terminationGracePeriodSeconds的行為和配置的更多資訊。

Session Affinity

Cilium eBPF kube-proxy replacement支援kubernetes Service Session親和。從同一pod或主機到配置了sessionAffinity: ClusterIP的服務的每個連接配接,将始終選擇相同的服務端點。親和性的預設逾時時間為三小時(根據服務的每個請求更新)。如果有需要,可以通過Kubernetes的sessionAffinityConfig進行配置。

親和關聯取決于請求的來源。如果從叢集外部向服務發送請求,則該請求的源IP位址用于确定端點關聯。如果請求是從叢集内部發送的,則源取決于使用使用socket-LB特性用于ClusterIP服務的負載均衡。如果是,則用戶端的網絡名稱空間cookie用作源。後者是在5.7 Linux核心中引入的,用于在套接字層實作親和性,socket-LB在該層運作(不能使用源IP,因為端點選擇發生在核心建構網絡包之前)。如果未使用socket-LB(即在pod網絡接口上對每個資料包實作負載均衡),則将請求的源IP位址用作源。

cilium 預設啟用了Session親和特性支援,對于不支援網絡名稱空間cookie的舊核心,将在叢集内場景中降級為基于固定cookie值的。主機上的所有應用程式為通路配置了Session親和服務選擇的服務端點相同。要禁用該功能,請将config.sessionAffinity設定為false。

當不使用固定cookie值時,具有多個端口的服務Session親和的依據是每個服務IP和端口。這意味着從相同源發送到相同服務端口的給定服務的所有請求都将路由到相同的服務端點,從相同源發送到不同服務端口的相同服務的兩個請求可以路由到不同的服務端點。

對于使用kube-proxy運作的使用者(即,禁用Cilium的kube-proxy replacement),當從在非主機網絡命名空間中運作的pod發送請求時,ClusterIP服務負載平衡仍在pod網絡接口上執行(直到修複GH#16197)。在這種情況下,預設情況下禁用Session親和支援。要啟用該功能,請設定config.sessionAffinity=true。

kube-proxy Replacement健康檢查服務

要為kube-proxy Replacement啟用健康檢查服務,必須設定kubeProxyReplacementHealthzBindAddr選項(預設情況下禁用)。該選項接受健康檢查伺服器服務的IP位址和端口。例如,要啟用IPv4設定kubeproxyReplacementHealthzBindR='0.0.0.0:10256',IPv6則設定為kubeProxyReplacementHealthzBindAddr='[::]:10256'。可以通過HTTP /healthz端點通路健康檢查伺服器。

LoadBlancer 源位址範圍檢查

當使用spec.loadBalancerSourceRanges配置LoadBalancer服務時,Cilium的eBPF kube-proxy Replacement将從外部(例如叢集外流量)對該服務的通路限制為該字段中指定的白名單CIDR。如果該字段為空,則不會配置通路限制。

當從叢集内部通路服務時,kube-proxy Replacement将忽略該字段,無論是否設定。這意味着叢集中的任何pod或任何主機程序都将能夠在内部通路LoadBalancer服務。

預設情況下,負載平衡器源位址範圍檢查功能已啟用,可以通過設定config.svcSourceRangeCheck=false來禁用。在某些特殊雲環境下禁用檢查是有意義的,例如 Amazon NLB原生支援源位址範圍檢查,是以可以禁用cilium的功能。

Service代理名字配置

kube-proxy都支援配置service.kubernetes.io/service-proxy-name選項,代表該kube-proxy隻管理比對service-proxy-name的service。Cilium通過Helmk8s.serviceProxyName選項也能做到同樣的功能。該選項預設為空字元串,代表Cilium不處理任何帶有service.kubernetes.io/service-proxy-name标簽的service。

關于 service.kubernetes.io/service-proxy-name标簽和其工作流程,請參閱https://github.com/kubernetes/enhancements/blob/3ad891202dab1fd5211946f10f31b48003bf8113/keps/sig-network/2447-Make-kube-proxy-service-abstraction-optional/README.md。

如果Cilium設定了服務代理名字,請确認kube-dns和kubernetes這種必須的service設定了代理标簽。

Topology Aware Hints

Cilium eBPF kube-proxy replacement支援kubernetes service 拓撲邏輯感應提示,要開啟該特性請配置選項loadBalancer.serviceTopology=true。

Neighbor Discovery

當啟用kube-proxy替換時,Cilium會主動對叢集中的節點進行L2鄰居發現。這是因為負載均衡器需要為後端填充L2位址,而快速資料路徑中不支援動态按需解析鄰居。

在Cilium 1.10或更早版本中,agent本身包含一個ARP解析庫,它在其中定時觸發發現并重新整理加入叢集的新節點。解析後的鄰居條目被推入核心并重新整理為永久條目。在某些少見的場景下,Cilium 1.10或更早版本可能會在鄰居表中留下過期的條目,導緻某些節點之間的資料包丢失。要跳過Cilium鄰居發現回退到依賴Linux核心來發現鄰居,可以将--enable-l2-neigh-discovery=false選項傳遞給cilium agent。

請注意,依賴Linux核心也可能導緻某些資料包丢失。例如,在中繼節點上可能發生的丢棄NodePort請求(中繼節點即接收到服務分組并将其轉發到服務端點的節點)。如果核心中沒有L2鄰居條目(由于該條目被垃圾收集,或者由于核心沒有執行鄰居解析),則可能會發生丢包的情況。這是因為在BPF快速路徑程式中不支援解析L2位址,例如在XDP層。

從Cilium 1.11開始,完全重新設計了鄰居發現,并且Cilium内部ARP解析庫已從agnet中删除。agent現在完全依賴Linux核心來發現同一L2網絡上的網關或主機。cilium agent中支援IPv4和IPv6鄰居發現。根據我們最近在Plumbers上介紹的核心工作,“managed”鄰居條目提案已被接受,并将在Linux核心v5.16或更高版本中可用,Cilium agent将檢測并透明使用托管鄰居。在這種情況下,agnet将加入叢集的新節點的L3位址向下推,作為外部學習的“托管”鄰居條目。在狀态展示方面,iproute2将托管鄰居顯示為“managed extern_learn”。“extern_learn”屬性防止核心的相鄰子系統對條目進行垃圾收集。如果在一段時間内沒有活動流量,Linux核心會動态解析并定期重新整理這些“受管理”鄰居條目。也就是說,核心試圖始終将它們保持在可達狀态。對于不存在“托管”鄰居條目的Linux核心v5.15或更早版本,Cilium agent将新節點的L3位址推送到核心中進行動态解析,使用agnet觸發的定期重新整理。在狀态展示上,iproute2僅顯示為“extern_learn”。如果在某一段時間内沒有活動流量,則Cilium agent控制器會觸發基于Linux核心的重新解析,以嘗試将它們保持在可通路狀态。如果需要,可以通過cilium agent的--arping-refresh-period=30s選項來改變重新整理間隔。預設周期為30秒,對應核心的可達性管理時間周期。

鄰居發現支援多裝置環境,其中每個節點具有多個裝置和到另一節點的下一跳。Cilium agent會管理推送所有目标裝置(包括直接路由裝置)的鄰居條目。目前,它支援為每個裝置都配置一個 next-hops下一跳資訊。以下示例說明了鄰居發現如何在多裝置環境中工作。每個節點都有兩個裝置連接配接到不同的L3網絡(10.69.0.64/26和10.69.0.128/26兩個不同的子網),全局範圍位址分别是10.69.0.1/26 和 10.69.0.2/26。從node1到node2的下一跳是10.69.0.66 dev eno1或10.69.0.130 dev eno2。在這種情況下,cilium agent推送10.69.0.66 dev eno1和10.69.0.130 dev eno2的鄰居條目。

+---------------+     +---------------+
|    node1      |     |    node2      |
| 10.69.0.1/26  |     | 10.69.0.2/26  |
|           eno1+-----+eno1           |
|           |   |     |   |           |
| 10.69.0.65/26 |     |10.69.0.66/26  |
|               |     |               |
|           eno2+-----+eno2           |
|           |   |     | |             |
| 10.69.0.129/26|     | 10.69.0.130/26|
+---------------+     +---------------+           

在node1上:

$ ip route show
10.69.0.2
        nexthop via 10.69.0.66 dev eno1 weight 1
        nexthop via 10.69.0.130 dev eno2 weight 1

$ ip neigh show
10.69.0.66 dev eno1 lladdr 96:eb:75:fd:89:fd extern_learn  REACHABLE
10.69.0.130 dev eno2 lladdr 52:54:00:a6:62:56 extern_learn  REACHABLE           

外部通路ClusterIP Service

Cilium預設禁止從叢集外部通路ClusterIP類型的service,通過bpf.lbExternalClusterIP=true選項可以關閉該限制。

常見問題

驗證eBPF cgroup程式注入

Cilium 連接配接BPF cgroup程式以實作基于socket的負載均衡(也被稱為host-reachable 服務)。如果你的ClusterIP服務連接配接出現連接配接問題,請檢查操BPF程式是否連接配接到了主機cgroup根目錄。預設的cgroup root路徑為/run/cilium/cgroupv2。注意如果容器運作時以cgroup namespace模式運作,cilium agent pod可能将BPF cgroup程式附加到virtualized cgroup root。在這種情況下,基于Cilium eBPF kube-proxy replacement的負載均衡可能失效,導緻連接配接問題。有關詳細資訊,請確定您有修複拉取請求。在運作cilium agent pod的kubernetes節點上運作以下指令可以檢視cgroup的狀态。

$ mount | grep cgroup2
none on /run/cilium/cgroupv2 type cgroup2 (rw,relatime)

$ bpftool cgroup tree /run/cilium/cgroupv2/
CgroupPath
ID       AttachType      AttachFlags     Name
/run/cilium/cgroupv2
10613    device          multi
48497    connect4
48493    connect6
48499    sendmsg4
48495    sendmsg6
48500    recvmsg4
48496    recvmsg6
48498    getpeername4
48494    getpeername6           

限制

  1. Cilium eBPF kube-proxy replacement 不能與IPsec或WireGuard傳輸加密混用。
  2. Cilium eBPF kube-proxy replacement 依賴socket-LB特性,該特性基于BPF cgroup hook實作服務轉換。如果需要部署libceph,最少需要5.8版本支援getpeername(2)位址轉換hook的核心。
  3. Cilium的DSR NodePort模式目前不相容TCP Fast Open(TFO)的環境。在這種場景,建議切換到snat模式。
  4. Cilium eBPF kube-proxy replacement 不支援SCTP協定,僅支援TCP/UDP協定。
  5. Cilium eBPF kube-proxy replacement 不允許Pod的hostPort配置與NodePort範圍重疊。如果配置錯誤,hostPort設定将被忽略,Cilium agent 日志會發出警告。不支援将主機IP顯式綁定到主機命名空間中的環回位址,Cilium agent同樣會在日志中發出警告。
  6. 當Cilium eBPF kube-proxy replacement與Kubernetes(<1.19)但支援EndpointSlices的版本一起使用時,沒有selector選擇器和後端EndpointSlices的服務将無法工作。因為Cilium僅監聽對EndpointSlices對象所做的更改(如果存在),并在這些情況下忽略Endpoints。Kubernetes 1.19版本引入了EndpointSliceMirroring控制器,該控制器将自定義Endpoints資源關聯相應的EndpointSlices,進而允許支援Endpoints工作。有關更詳細的讨論,請參閱GitHub issue 12438。
  7. 小于5.7版本的核心不支援network namespace cookie,cilium無法區分主機和pod的命名空間。是以在pod内通過回環位址可以通路k8s的服務。
  8. 多裝置環境中的鄰居發現與運作時裝置檢測無法同時使用,也就是說鄰居發現的目标裝置不跟随裝置變化而動态修改。

更多資料

下面的資料更詳細的說明了Cilium eBPF kube-proxy replacement 内部工作流程:

  1. 《從 kube-proxy 和 iptables 中解放kubernetes》 (KubeCon North America 2019, slides, video)
  2. 《使用BPF和XDP實作大規模Kubernetes服務負載平衡》 (Linux Plumbers 2020, slides, video)
  3. 《容器的革命性技術eBPF》 (Fosdem 2020, slides, video)
  4. 《cilium socket-LB的核心改進》 (LSF/MM/BPF 2020, slides)

繼續閱讀