深入 K8s 網路原理(一)- Flannel VXLAN 模式分析

胡說雲原生發表於2023-12-19

1. 概述

這周集中聊下 K8s 的叢集網路原理,我初步考慮分成3個方向:

  1. Pod-to-Pod 通訊(同節點 or 跨節點),以 Flannel VXLAN 模式為例;
  2. Pod/External-to-Service 通訊,以 iptables 實現為例;
  3. Ingress 原理,以 NGINX Ingress Controller 實現為例;
  4. 其他:(到時候看心情)Flannel host-gw 模式,Calico,……

今天先介紹下 Flannel 實現 Pod 跨節點通訊的原理。

2. TL;DR

我知道你們著急,這樣吧,先看圖:

一圖勝千言,下文都不知道咋展開了。哎。

此外,網路這塊涉及的概念有點多,逐個細講感覺不合適。這樣,此處預設大家都熟悉 TCP/IP 協議族,下文該偷懶的地方我就偷懶。

3. Pod 間通訊問題的由來

容器化以前,當需要將多個應用靈活部署到一些伺服器上時,就不可避免地會遇到埠衝突問題,而協調這種衝突是很繁瑣的。K8s 體系的處理方式是將每個 Pod 丟到單獨的 netns 裡,也就是 ip 和 port 都彼此隔離,這樣就不需要考慮埠衝突了。

不過這套網路架構應該如何實現呢?整體來由需要解決下面這2個問題(結合上圖的 Pod1234):

  1. Pod1 如何和 Pod2 通訊(同節點)
  2. Pod1 如何和 Pod3 通訊(跨節點)

K8s 的網路模型要求每個 Pod 都有一個唯一的 IP 地址,即使這些 Pod 分佈在不同的節點上。為了實現這個網路模型,CoreOS 團隊發起了 CNI 專案(後來 CNI 進了 CNCF 孵化)。CNI (Container Network Interface) 定義了實現容器之間網路連通性和釋放網路資源等相關操作的介面規範,這套介面進而由具體的 CNI 外掛的去實現,CNI 外掛負責為 Pod 分配 IP 地址,並處理跨節點的網路路由等具體的工作。

行,接下來具體跟下 CNI 的 Flannel 實現是怎麼工作的。

4. 測試環境準備

我在本地透過 Minikube 啟動一個3節點的 K8s 叢集,3個節點的 IP 和 hostname 分別是:

  • 192.168.49.2 minikube
  • 192.168.49.3 minikube-m02
  • 192.168.49.4 minikube-m03

此外在這個叢集內建立了幾個 Pod,資訊如下(主要留一下 Pod 的 IP 以及所在的節點):

  • kgpoowide
NAME                                READY   STATUS    RESTARTS   AGE    IP           NODE
nginx-deployment-7fbb8f4b4c-89bds   1/1     Running   0          20h    10.244.2.4   minikube-m03
nginx-deployment-7fbb8f4b4c-d29zm   1/1     Running   0          20h    10.244.1.5   minikube-m02
nginx-deployment-7fbb8f4b4c-k5vh4   1/1     Running   0          102s   10.244.2.5   minikube-m03
nginx-deployment-7fbb8f4b4c-m4scr   1/1     Running   0          3s     10.244.1.6   minikube-m02

Pod 用的映象是帶有 ip 等命令的 NGINX,Dockerfile 如下:

FROM nginx:latest

RUN apt-get update && \
    apt-get install -y iproute2 && \
    rm -rf /var/lib/apt/lists/*

相應的 Deployment YAML 如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:test1
        ports:
        - containerPort: 80

5. 從 veth 裝置聊起

不得不先提一句 veth(Virtual Ethernet Device)。veth 是一種在 Linux 中用於網路虛擬化的技術,常用於容器網路中。veth pair 可以看作是一對虛擬網路介面裝置,它們像管道的兩端一樣相連。在一個 veth 對中,資料從一端傳送出去,可以在另一端被接收到,就像它們是透過一根乙太網線連線的兩個獨立裝置一樣。

在容器網路中,veth 對經常被用來連線容器和主機。具體來說,veth 對的一個端點(通常稱為 veth 介面)位於容器的網路名稱空間內,好像是容器的網路介面卡,而另一個端點位於主機的全域性網路名稱空間內,通常會連線到一個 Linux 橋接或者其他網路裝置。

這種設定允許容器內的網路流量透過 veth 介面流出容器,進入主機的網路名稱空間,並透過主機的網路路由和策略進行進一步的處理或轉發。

如圖所示,Pod 內的 eth0 和 host 上的 veth1 其實就是一個 veth pair,Pod 和 host 在2個不同的網路名稱空間內,透過 veth 裝置實現了2個 netns 之間的網路互通。

而 veth-n 又會橋接到 cni0 這個網橋上,進而實現流量在主機上的路由過程。接下來我們具體看下 Pod 內外的網路裝置和路由規則等。

6. 網橋 cni0

接著來看網橋 cni0。

6.1 在 Pod 內看網路卡資訊

Pod 10.244.1.6 內的網路卡資訊如下:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd :: permaddr 1622:c323:de90::
4: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue state UP group default
    link/ether 4a:2c:84:bb:56:5e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.1.6/24 brd 10.244.1.255 scope global eth0
       valid_lft forever preferred_lft forever

可以看到 eth0@f11 裝置,對應 IP 10.244.1.6/24,這裡看著和一個普通的 vm 沒有大差別。此外留一下這裡的 if11,這個 11 對應這個 veth pair 在主機上的另外一端的序號。

6.2 在 host 上看網路卡資訊

節點 minikube-m02 上的網路卡資訊如下:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
5: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue state UNKNOWN group default
    link/ether 62:07:aa:05:13:c4 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
6: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue state UP group default qlen 1000
    link/ether de:07:f7:20:e0:70 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
       valid_lft forever preferred_lft forever
7: vetha7eec1e2@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master cni0 state UP group default
    link/ether da:ab:17:55:be:50 brd ff:ff:ff:ff:ff:ff link-netnsid 1
10: vethc9667243@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master cni0 state UP group default
    link/ether ce:dd:d3:ec:5e:d3 brd ff:ff:ff:ff:ff:ff link-netnsid 2
11: vethd26e8b95@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master cni0 state UP group default
    link/ether b2:64:95:13:2a:de brd ff:ff:ff:ff:ff:ff link-netnsid 3
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65535 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:31:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.49.3/24 brd 192.168.49.255 scope global eth0
       valid_lft forever preferred_lft forever

前面 Pod 內看到的 eth0@if11 對應這裡的11號 vethd26e8b95@if4。別管這裡的 if4,如果你是在 vm 裡直接跑 Pod 就看不到 ifn 了。我這裡因為用了 Docker Desktop 跑 K8s,所以 K8s 所在的 nodes 本質也是容器,這裡多套了一層網路巢狀而已。

先看下 cni0:

  • ip link show master cni0
7: vetha7eec1e2@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master cni0 state UP mode DEFAULT group default
    link/ether da:ab:17:55:be:50 brd ff:ff:ff:ff:ff:ff link-netnsid 1
10: vethc9667243@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master cni0 state UP mode DEFAULT group default
    link/ether ce:dd:d3:ec:5e:d3 brd ff:ff:ff:ff:ff:ff link-netnsid 2
11: vethd26e8b95@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master cni0 state UP mode DEFAULT group default
    link/ether b2:64:95:13:2a:de brd ff:ff:ff:ff:ff:ff link-netnsid 3

可以看到前面提到的 veth vethd26e8b95 被橋接到了 cni0 上。

繼續看 host 的路由表:

default via 192.168.49.1 dev eth0
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
192.168.49.0/24 dev eth0 proto kernel scope link src 192.168.49.3

留意這裡的 10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1

cni0 的資訊是:

6: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue state UP group default qlen 1000
    link/ether de:07:f7:20:e0:70 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
       valid_lft forever preferred_lft forever

結合起來看,也就是所有發往 10.244.1.0/24 段的資料包都會透過 cni0 傳輸,10.244.1.0/24 也就是 Flannel 分配給當前節點的 Pod IP 段。

7. VTEP flannel.1

上述路由表中還有這樣2條記錄:

10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink

當前叢集是3個節點,也就是目的地址是當前節點內的 pods IP 段 10.244.1.0/24,流量交給 cni0 處理;而其他節點的 pods IP 段 10.244.0.0/2410.244.2.0/24 則交給 flannel.1 來處理。

flannel.1 是 VXLAN 網路的 VTEP 裝置。簡單介紹下 VXLAN 和 VTEP:

VXLAN (Virtual Extensible LAN) 是一種覆蓋網路技術,允許在現有的物理網路基礎設施之上建立大量虛擬化的區域網(LAN)。它主要用於解決傳統 VLAN 技術的一些限制,如 VLAN ID 數量限制(只有4096個)。VXLAN 可以支援高達1600萬個虛擬網路,極大地擴充套件了網路的規模和靈活性。

VXLAN 相關的一些概念與原理:

  • 封裝與隧道技術:VXLAN 透過封裝原始的乙太網幀(Layer 2)到 UDP 資料包(Layer 3)中來工作。這意味著它可以跨越不同的網路和子網,實現跨網路邊界的通訊。
  • VXLAN 網路識別符號 (VNI):每個 VXLAN 網路都有一個唯一的識別符號,稱為 VNI(VXLAN Network Identifier),它提供了地址隔離,確保各個 VXLAN 網路之間的資料包不會互相干擾。
  • VTEP(VXLAN Tunnel Endpoint):VTEP 是 VXLAN 架構中的端點裝置,負責封裝和解封裝資料包。每個透過 VXLAN 通訊的網路裝置都有一個或多個 VTEP。
    • 當資料包從虛擬網路出發時,VTEP 會捕獲這些資料包,將它們封裝在 VXLAN 格式中(即加入 VNI 和 UDP 頭),然後透過物理網路傳送。
    • 當 VXLAN 資料包到達目的地的 VTEP 時,該 VTEP 將對資料包進行解封裝,移除 VXLAN 頭部,然後將原始的乙太網幀轉發到目標虛擬網路中。
    • VTEP 通常部署在資料中心的交換機(物理或虛擬)上,但也可以部署在其他網路裝置或伺服器上。
    • 在容器化環境(如 Kubernetes 使用 Flannel 等 CNI)中,VTEP 可以作為軟體元件執行,處理容器或 Pod 之間的 VXLAN 通訊。

所以當資料包到達 flannel.1 的時候,就開始了 VXLAN 封包(MAC in UDP)過程,一個乙太網幀被依次加上了 VXLAN 頭(VNI 資訊)、UDP 頭、外部 IP 頭和 MAC 頭等。外部 IP 頭裡包含了 VXLAN 隧道的源地址和目的地址(VTEP 地址),外部 MAC 頭則包含了乙太網幀到達下一跳所需的 MAC 地址。

8. 最後看下 Flannel 的配置

在 minikube 環境中,Flannel 會被預設部署到 kube-flannel namespace 下。在這個 namespace 裡有一個 ConfigMap 叫做 kube-flannel-cfg,裡面包含這樣2個配置檔案:

  • cni-conf.json
{
    "name": "cbr0",
    "cniVersion": "0.3.1",
    "plugins": [
    {
        "type": "flannel",
        "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": true
        }
    },
    {
        "type": "portmap",
        "capabilities": {
        "portMappings": true
        }
    }
    ]
}
  • net-conf.json
{
    "Network": "10.244.0.0/16",
    "Backend": {
    "Type": "vxlan"
    }
}

如果需要修改網路模式或者 pods 網段,就可以在 net-conf.json 中靈活調整。早幾年用 Flannel 的時候,我就習慣將 10.244.0.0/16 改成 10.100.0.0/16。此外 vxlan 改成 host-gw 可以提高網路傳輸效能,如果你的叢集規模不是大幾百好幾千個節點,也可以考慮用 host-gw 模式。

9. 總結

從來不總結,下班。

算了,補張圖吧,前文提到的 Pod1 到 Pod2/Pod3 的流量怎麼走:

相關文章