Kubernetes CNI 外掛選型和應用場景探討

KubeSphere發表於2022-11-23
作者:馬偉,青雲科技容器顧問,雲原生愛好者,目前專注於雲原生技術,雲原生領域技術棧涉及 Kubernetes、KubeSphere、KubeKey 等。

本文介紹容器環境常見網路應用場景及對應場景的 Kubernetes CNI 外掛功能實現。幫助搭建和使用雲原生環境的小夥伴快速選擇心儀的網路工具。

常見網路外掛

我們在學習容器網路的時候,肯定都聽說過 Docker 的 bridge 網路,Vethpair,VxLAN 等術語,從 Docker 到 kubernetes 後,學習了 Flannel、Calico 等主流網路外掛,分別代表了 Overlay 和 Underlay 的兩種網路傳輸模式,也是很經典的兩款 CNI 網路外掛。那麼,還有哪些好用的 CNI 外掛呢 ? 我們看看 CNCF Landscape:

拋去商業版 CNI,此次分享來聊聊幾款熱門開源 CNI 外掛,分別為 Kube-OVNAntreaCilium。Kube-OVN 和 Antrea 都是基於 OpenvSwitch 的專案,Cilium 使用 eBPF 這款革命性的技術作為資料路徑,亦是這兩年很火熱的一個開源容器專案。

那麼,又回到學習新產品的第一步,如何快速部署 K8s 體驗不同地 CNI 外掛呢?還是交給我們親愛的 Kubekey 吧。

Kubekey 作為一個開源的 Kubernetes 和 KubeSphere 叢集部署工具,可以輕鬆的部署 Kubernetes 叢集,提供節點管理、作業系統安全加固、容器執行時管理、網路儲存安裝、Etcd 管理等。Kubekey 支援一鍵部署 Calico / Flannel / Cilium / Kube-OVN 等網路外掛,只需在 kk 的配置檔案中註明 network 的 plugin 值即可:

  network:
    plugin: calico/kubeovn/cilium
    kubePodsCIDR: 10.233.64.0/18
    kubeServiceCIDR: 10.233.0.0/18

對於 antrea,由於版本較新,目前可透過 addon 的形式新增 helm 檔案的形式進行一鍵安裝:

  addons:
  - name: antrea
    namespace: kube-system
    sources: 
      chart: 
        name: antrea
        repo: https://charts.antrea.io
        # values:

在此基礎上,可以透過以下一條命令

?  → kk create cluster --with-kubernetes --with-kubesphere

建立一個 kubernetes 叢集並安裝 KubeSphere,在此之上體驗不同的 CNI 在 Kubernetes 的功能應用。畢竟,哪個運維人員不喜歡頁面友好的容器管理平臺呢?

網路應用場景

現在我們已經有了一個 Kubernetes 叢集,先來思考一下,容器網路除了讓叢集正常執行,能讓安裝 Kubernetes 後 Pending 的 CoreDNS running 起來(抖個雞靈-_-)以外還有哪些使用場景?

這裡我透過一張圖總結了七個主要使用的場景,應該也涵蓋大部分運維人員網路需求。

  • 固定 IP。對於現存虛擬化 / 裸機業務 / 單體應用遷移到容器環境後,都是透過 IP 而非域名進行服務間呼叫,此時就需要 CNI 外掛有固定 IP 的功能,包括 Pod/Deployment/Statefulset。
  • 網路隔離。不同租戶或不同應用之間,容器組應該是不能互相呼叫或通訊的。
  • 多叢集網路互聯。 對於不同的 Kubernetes 叢集之間的微服務進行互相呼叫的場景,需要多叢集網路互聯。這種場景一般分為 IP 可達和 Service 互通,滿足不同的微服務例項互相呼叫需求。
  • 出向限制。對於容器叢集外的資料庫 / 中介軟體,需能控制特定屬性的容器應用才可訪問,拒絕其他連線請求。
  • 入向限制。限制叢集外應用對特定容器應用的訪問。
  • 頻寬限制。容器應用之間的網路訪問加以頻寬限制。
  • 出口閘道器訪問。對於訪問叢集外特定應用的容器,設定出口閘道器對其進行 SNAT 以達到統一出口訪問的審計和安全需求。
    理完需求和應用場景,我們來看看如何透過不同的 CNI 外掛解決以上痛點。

網路外掛功能實現

固定 IP

基本上主流 CNI 外掛都有自己的 IPAM 機制,都支援固定 IP 及 IP Pool 的分配,並且各個 CNI 外掛殊途同歸的都使用了 Annotation 的方式指定固定 IP。對於 Pod,分配固定 IP,對於 Deployment,使用 IP Pool 的方式分配。對於有狀態的 Statefulset,使用 IP Pool 分配後,會根據 Pool 的分配順序記好 Pod 的 IP,以保證在 Pod 重啟後仍能拿到同樣的 IP。

Calico

  "cni.projectcalico.org/ipAddrs": "[\"192.168.0.1\"]"

Kube-OVN

ovn.kubernetes.io/ip_address: 192.168.100.100
ovn.kubernetes.io/ip_pool: 192.168.100.201,192.168.100.202

Antrea

Antrea IPAM 只能在 Bridge 模式下使用,因此可以在 Multus 的輔佐下,主網路卡使用 NodeIPAM 分配,副網路卡使用 Antrea IPAM 分配 VLAN 型別網路地址。

    ipam.antrea.io/ippools: 'pod-ip-pool1'
    ipam.antrea.io/pod-ips: '<ip-in-pod-ip-pool1>'

Cilium

Not Yet!

多叢集網路互聯

對於多叢集網路互聯,假設有現有多個叢集,不同的微服務執行在不同的叢集中,叢集 1 的 App01 需要和叢集 2 的 App02 進行通訊,由於他們都是透過 IP 註冊在叢集外的 VM 註冊中心的,所以 App01 和 App02 只能透過 IP 通訊。在這種場景下,就需要多叢集 Pod 互聯互通。

Calico

對於 Calico 這種原生對 BGP 支援很好的 CNI 外掛來說,很容易實現這一點,只要兩個叢集透過 BGP 建立鄰居,將各自的路由宣告給對方即可實現動態路由的建立。若存在多個叢集,使用 BGP RR 的形式也很好解決。但這種解決方式可能不是最理想的,因為需要和物理網路環境進行配合和聯調,這就需要網路人員和容器運維人員一同進行多叢集網路的建設,在後期運維和管理上都有不大方便和敏捷的感覺。

那 Calico VxLAN 模式呢?

既然說到 VxLAN,可以和 Kube-OVN、Antrea、Cilium 放到一起來看,四種 CNI 都支援 Overlay 的網路模型,都支援透過 VxLAN/GENEVE 的形式建立隧道網路打通容器網路通訊。這就賦予運維人員較高的靈活性,對於容器網路的調教、IPAM 分配、網路監控和可觀察性、網路策略調整都由容器叢集運維人員負責,而網路人員則只需要提前劃好物理網路大段,保證容器叢集 Node 之間網路互通即可。

那如何去實現 overlay 網路的多叢集互聯呢?

Submariner

CNCF 有個沙箱專案叫 Submariner,它透過在不同叢集建立不同的閘道器節點並打通隧道的形式實現多叢集通訊。從官方這張架構圖來說明:

簡單來說,Submariner 由一個叢集後設資料中介服務(broker)掌握不同叢集的資訊(Pod/Service CIDR),透過 Route Agent 將 Pod 流量從 Node 導向閘道器節點(Gateway Engine),然後由閘道器節點打通隧道丟到另一個叢集中去,這個過程就和不同主機的容器之間使用 VxLAN 網路通訊的概念是一致的。
要達成叢集連線也很簡單,在其中一個叢集部署 Broker,然後透過 kubeconfig 或 context 分別進行註冊即可。

?  → subctl deploy-broker --kubeconfig ~/.kube/config1
?  → subctl join --kubeconfig ~/.kube/config1 broker-info.subm --clusterid ks1 --natt=false --cable-driver vxlan --health-check=false
?  → subctl join --kubeconfig ~/.kube/config2 broker-info.subm --clusterid ks2 --natt=false --cable-driver vxlan --health-check=false
?  → subctl show all
✓ Showing Endpoints
CLUSTER ID                    ENDPOINT IP     PUBLIC IP       CABLE DRIVER        TYPE
ks1                           192.168.100.10  139.198.21.149  vxlan               local
ks2                           192.168.100.20  139.198.21.149  vxlan               remote

Cilium

Cilium Cluster Mesh 和 Submariner 有異曲同工之妙,可以透過隧道形式或 NativeRoute 形式實現叢集互聯。


Cilium 開啟多叢集網路連線也很簡單:

?  → cilium clustermesh enable --context $CLUSTER1
?  → cilium clustermesh enable --context $CLUSTER2

KubeOVN

Kube-OVN 還提供一個 OVNIC 的元件,它執行一個路由中繼的 OVN-IC 的 Docker 容器,作為兩個叢集的閘道器節點,將不同叢集的 Pod 網路進行連通。

多叢集服務互訪

除了 Pod IP 的互聯互通,多叢集網路還可考慮叢集間的 Service 互訪,Submariner、Cilium,Antrea 都能實現。Submariner 和 Antrea 都使用了 Kubernetes 社群的 MultiCluster Service 並在此之上結合自身元件實現多叢集的服務訪問。MultiCluster Service 透過 ServiceExport 和 ServiceImport 的 CRD,ServiceExport 將需要公開的服務匯出,然後透過 ServiceImport 將此服務匯入到另一個叢集。

Submariner

Submariner 實現舉例,有兩個叢集 ks1 和 ks2,ks1 在 test 名稱空間有一個服務 nginx,此時透過 ServiceExport 將 nginx 服務進行匯出,Submariner 會把這個 nginx.test.svc.cluster.local 服務發現為 nginx.test.svc.clusterset.local,兩個叢集的 coredns 都會建立一個新的 clusterset.local 的存根域,將所有匹配 cluster.set 的請求傳送給 submariner 的服務發現的元件。同時 ServiceImport 匯入到 ks2 叢集,ks2 叢集的 Pod 就可以透過 nginx.test.svc.clusterset.local 解析到 ks1 叢集的 nginx Service。如果兩個叢集都有 nginx 的同名服務,此時 submariner 就可以優先本地進行訪問,本地服務端點有故障後再訪問其他叢集的 nginx 服務,是不是可以開始構建雙活服務了哈哈。

Antrea

Antrea 實現方式類似,也是結合 ServiceExport 和 ServiceImport 並進行封裝成 ResourceExport 和 ResourceImport 構建多叢集服務,在每個叢集選擇一個節點作為閘道器,透過閘道器打通不同叢集隧道來實現多叢集服務的訪問。

Cilium

Cilium 沒有用 MultiService 的概念,Cilium 透過 Global Service 的概念構建多叢集訪問服務訪問。

從這張圖可以看出,Cilium 更適合做多活叢集的多叢集服務訪問需求,透過對相應的服務新增 Annotation 的做法,把不同叢集的服務設定為 global-service,並透過 shared-service 和 service-affinity 來控制服務是否能被其他叢集訪問及服務親和性。以下是一個例子:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
    io.cilium/global-service: 'true'
    io.cilium/shared-service: 'true'
    io.cilium/service-affinity: 'local'
    # Possible values:
    # - local
    #    preferred endpoints from local cluster if available
    # - remote
    #    preferred endpoints from remote cluster if available
    # none (default)
    #    no preference. Default behavior if this annotation does not exist   
spec:
  type: ClusterIP
  ports:
    - port: 80
  selector:
    name: nginx

以上,當有多叢集互訪需求又不想 CNI 強相關時,可以嘗試玩一下 Submariner,作為 CNCF Landscape Network 中一個專注於多叢集互訪的 SandBox 專案,Submariner 提供多叢集網路通訊,服務發現,以及安全加密,是一個很好的選擇。

網路策略

對於 Pod 網路隔離、入向限制、出向限制的網路場景,可以整合成網路策略一同來說。主流開源 CNI 都支援 Kubernetes NetworkPolicy,透過 Network Policy,可以在 3 層或 4 層做相應的網路安全限制。Network Policy 透過 Ingress 和 Egress 兩種進行網路限制,預設都是放行的。也就是說,設定 Kubernetes 網路策略,主要以白名單的形式對叢集內的流量進行安全限制。

比如只允許指定 label 的 Pod 訪問叢集外資料庫(透過 CIDR 指定)

apiVersion: networking.K8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-allow
  namespace: default
spec:
  podSelector: 
    matchLabels:
      role: db
  policyTypes:
  - Egress
egress:
    - to:
        - ipBlock:
            cidr: 192.168.100.40/24
      ports:
        - protocol: TCP
          port: 3306
apiVersion: networking.K8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-allow
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: app
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 172.17.0.0/16
            except:
              - 172.17.1.0/24
        - namespaceSelector:
            matchLabels:
              project: web-project
        - podSelector:
            matchLabels:
              role: web

雖然 Network Policy 能滿足大多場景,但是不是感覺還是少了點東西?比如 7 層策略、基於 NodeSelector、Drop/Reject 型別的策略指定、指定 Egress 節點進行控制等高階能力。這個時候 Cilium 和 Antrea 就大放異彩了。

Cilium

Cilium 有兩個 CRD,CiliumNetworkPolicy 和 CiliumClusterwideNetworkPolicy,來實現單叢集和多叢集的網路策略能力。Cilium 支援 3、4、7 層網路策略。並增加 EndPoint Selector 和 Node Selector。除了普通的基於 PodSelector 和 CIDR 的限制,Cilium 可以支援更多種策略,比如:

DNS 限制策略,只允許 app: test-app 的端點透過 53 埠去 kube-system 名稱空間的 "K8s:K8s-app": kube-dns 標籤的 DNS 伺服器訪問 my-remote-service.com:

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "to-fqdn"
spec:
  endpointSelector:
    matchLabels:
      app: test-app
  egress:
    - toEndpoints:
      - matchLabels:
          "K8s:io.kubernetes.pod.namespace": kube-system
          "K8s:K8s-app": kube-dns
      toPorts:
        - ports:
           - port: "53"
             protocol: ANY
          rules:
            dns:
              - matchPattern: "*"
    - toFQDNs:
        - matchName: "my-remote-service.com"

Http 限制策略 , 只允許 org: empire 標籤的端點對 deathstar 的 /v1/request-landing 進行 POST 操作:

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule"
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"

kafka 策略控制:

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "enable empire-hq to produce to empire-announce and deathstar-plans"
  endpointSelector:
    matchLabels:
      app: kafka
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: empire-hq
    toPorts:
    - ports:
      - port: "9092"
        protocol: TCP
      rules:
        kafka:
        - role: "produce"
          topic: "deathstar-plans"
        - role: "produce"
          topic: "empire-announce"

Antrea

Antrea 除了增加現有 NetworkPolicy 功能外,抽象了 Antrea NetworkPolicy 和 Antrea ClusterNetworkPolicy 兩個 CRD 實現名稱空間級別和叢集級別的安全管理。,還提供了 Group,Tier 的概念,用於資源分組和優先順序設計,嗯,果真是 NSX 的親兄弟。因此 Antrea 有零信任的網路策略安全防護手段,可以實現嚴格的 pod 和名稱空間隔離。

網路層 Antrea 增加了對 ICMP 和 IGMP,Mutlicast 的限制,禁 ping 人員狂喜。

apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-reject-ping-request
spec:
    priority: 5
    tier: securityops
    appliedTo:
      - podSelector:
          matchLabels:
            role: server
        namespaceSelector:
          matchLabels:
            env: prod
    egress:
      - action: Reject
        protocols:
          - icmp:
              icmpType: 8
              icmpCode: 0
        name: DropPingRequest
        enableLogging: true

基於 FQDN 的過濾:

apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-fqdn-all-foobar
spec:
  priority: 1
  appliedTo:
  - podSelector:
      matchLabels:
        app: client
  egress:
  - action: Allow
    to:
      - fqdn: "*foobar.com"
    ports:
      - protocol: TCP
        port: 8080
  - action: Drop 

設定不同型別的 Group,基於 Group 設定網路策略,就不用對同類業務寫一堆 Label 了

apiVersion: crd.antrea.io/v1alpha3
kind: Group
metadata:
  name: test-grp-with-namespace
spec:
  podSelector:
    matchLabels:
      role: db
  namespaceSelector:
    matchLabels:
      env: prod
---
# Group that selects IP block 10.0.10.0/24.
apiVersion: crd.antrea.io/v1alpha3
kind: Group
metadata:
  name: test-grp-ip-block
spec:
  ipBlocks:
    - cidr: 10.0.10.0/24
---
apiVersion: crd.antrea.io/v1alpha3
kind: Group
metadata:
  name: test-grp-svc-ref
spec:
  serviceReference:
    name: test-service
    namespace: default
---
# Group that includes the previous Groups as childGroups.
apiVersion: crd.antrea.io/v1alpha3
kind: Group
metadata:
  name: test-grp-nested
spec:
  childGroups: [test-grp-sel, test-grp-ip-blocks, test-grp-svc-ref]

Egress

對於特定業務出叢集需不暴露 IP 或符合安全審計需求的場景,需要 Pod IP -> External IP 對外部業務進行訪問。Cilium,Kube-OVN,Antrea 都有類似 Egress Gateway/Egress IP 的功能,特定標籤的 Pod 透過 SNAT 為 Egress IP 訪問叢集外服務。

Cilium

apiVersion: cilium.io/v2
kind: CiliumEgressGatewayPolicy
metadata:
  name: egress-sample
spec:
  selectors:
  - podSelector:
      matchLabels:
        app: snat-pod
        io.kubernetes.pod.namespace: default
  destinationCIDRs:
  - "0.0.0.0/0"
  egressGateway:
    nodeSelector:
      matchLabels:
        node.kubernetes.io/name: node1
    egressIP: 10.168.60.100

KubeOVN

apiVersion: v1
kind: Pod
metadata:
  name: pod-gw
  annotations:
    ovn.kubernetes.io/eip: 172.10.0.1
    #或ovn.kubernetes.io/snat: 172.10.0.1
spec:
  containers:
  - name: eip-pod
    image: nginx:alpine

Antrea:

apiVersion: crd.antrea.io/v1alpha2
kind: Egress
metadata:
  name: egress-staging-web
spec:
  appliedTo:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: staging
    podSelector:
      matchLabels:
        app: web
  externalIPPool: external-ip-pool
  #或IP形式 egressIP: 10.10.10.1

頻寬管理

kube-ovn 和 Clium 都支援頻寬管理,kube-ovn 還支援 QoS 調整,只需要 Annotation 一下即可搞定:

Kube-OVN

apiVersion: v1
kind: Pod
metadata:
  name: qos
  namespace: ls1
  annotations:
    ovn.kubernetes.io/ingress_rate: "3"
    ovn.kubernetes.io/egress_rate: "1"
    ovn.kubernetes.io/latency: 3
    ovn.kubernetes.io/loss: 20

Cilium

apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/egress-bandwidth: 10M
...

以上。就是此次分享的全部內容了,讀到這裡你可以也會感慨,從最早學 docker0,Vethpair 熟悉容器網路原理,到搭建 K8s 後節點 NotReady 就 apply 個 Flannel 逐步瞭解 CNI 外掛機制,到今天的 CNCF Network&Service Proxy 生態的花團錦簇,雲原生網路在日新月異的發展著,容器網路從最初的連通性到現在演變出更多的玩法和適用性,不論是網路功能、安全控制、網路洞察和可觀測性,都在更好地為運維人員服務。若要體驗更多功能,快到開源社群選擇喜歡的容器網路專案 Hands on Lab 吧!

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章