1. 程式人生 > 實用技巧 >K8s網路外掛flannel與calico

K8s網路外掛flannel與calico

Kubernetes的網路通訊問題:
  1. 容器間通訊: 即同一個Pod內多個容器間通訊,通常使用loopback來實現。
  2. Pod間通訊: K8s要求,Pod和Pod之間通訊必須使用Pod-IP 直接訪問另一個Pod-IP
  3. Pod與Service通訊: 即PodIP去訪問ClusterIP,當然,clusterIP實際上是IPVS 或 iptables規則的虛擬IP,是沒有TCP/IP協議棧支援的。但不影響Pod訪問它.
  4. Service與叢集外部Client的通訊,即K8s中Pod提供的服務必須能被網際網路上的使用者所訪問到。


需要注意的是,calico配置的外掛時,配置的網段不能和本身的網段相同,不僅僅是這樣,需要注意的是,k8s叢集初始化時的service網段,pod網段,網路外掛的網段,以及真實伺服器的網段,都不能相同,如果相同就會出各種各樣奇怪的問題,而且這些問題在叢集做好之後是不方便改的,改會導致更多的問題,所以,就在搭建前將其規劃好。

CNI(容器網路介面):
  這是K8s中提供的一種通用網路標準規範,因為k8s本身不提供網路解決方案。
  目前比較知名的網路解決方案有:
    flannel
    calico
    canel
    kube-router
    .......
等等,目前比較常用的時flannel和calico,flannel的功能比較簡單,不具備複雜網路的配置能力,calico是比較出色的網路管理外掛,單具備複雜網路配置能力的同時,往往意味著本身的配置比較複雜,所以相對而言,比較小而簡單的叢集使用flannel,考慮到日後擴容,未來網路可能需要加入更多裝置,配置更多策略,則使用calico更好
所有的網路解決方案,它們的共通性:
  1. 虛擬網橋
  2. 多路複用:MacVLAN
  3. 硬體交換:SR-IOV(單根-I/O虛擬網路):它是一種物理網絡卡的硬體虛擬化技術,它通過輸出VF(虛擬功能)來將網絡卡虛擬為多個虛擬子介面,每個VF繫結給一個VM後,該VM就可以直接操縱該物理網絡卡。

kubelet來調CNI外掛時,會到 /etc/cni/net.d/目錄下去找外掛的配置檔案,並讀取它,來載入該外掛,並讓該網路外掛來為Pod提供網路服務。

flannel網路外掛要怎麼部署?
 1. flannel部署到那個節點上?
  因為kubelet是用來管理Pod的,而Pod執行需要網路,因此凡是部署kubelet的節點,都需要部署flannel來提供網路,因為kubelet正是通過呼叫flannel來實現為Pod配置網路的(如:新增網路,配置網路,啟用網路等)。

 2. flannel自身要如何部署?
  1》它支援直接執行為宿主機上的一個守護程序。
  2》它也支援執行為一個Pod
  對於執行為一個Pod這種方式:就必須將flannel配置為共享當前宿主機的網路名稱空間的Pod,若flannel作為控制器控制的Pod來執行的話,它的控制器必須是DaemonSet,在每一個節點上都控制它僅能執行一個Pod副本,而且該副本必須直接共享宿主機的網路名稱空間,因為只有這樣,此Pod才能設定宿主機的網路名稱空間,因為flannel要在當前宿主機的網路名稱空間中建立CNI虛擬介面,還要將其他Pod的另一半veth橋接到虛擬網橋上,若不共享宿主機的網路名稱空間,這是沒法做到的。

3. flannel的工作方式有3種:
  1) VxLAN:
   而VxLAN有兩種工作方式:
    a. VxLAN: 這是原生的VxLAN,即直接封裝VxLAN首部,UDP首部,IP,MAC首部這種的。
    b. DirectRouting: 這種是混合自適應的方式, 即它會自動判斷,若當前是相同二層網路
     (即:不垮路由器,二層廣播可直達),則直接使用Host-GW方式工作,若發現目標是需要跨網段
     (即:跨路由器)則自動轉變為使用VxLAN的方式。
  2) host-GW: 這種方式是宿主機內Pod通過虛擬網橋互聯,然後將宿主機的物理網絡卡作為閘道器,當需要訪問其它Node上的Pod時,只需要將報文發給宿主機的物理網絡卡,由宿主機通過查詢本地路由表,來做路由轉發,實現跨主機的Pod通訊,這種模式帶來的問題時,當k8s叢集非常大時,會導致宿主機上的路由表變得非常巨大,而且這種方式,要求所有Node必須在同一個二層網路中,否則將無法轉發路由,這也很容易理解,因為如果Node之間是跨路由的,那中間的路由器就必須知道Pod網路的存在,它才能實現路由轉發,但實際上,宿主機是無法將Pod網路通告給中間的路由器,因此它也就無法轉發理由。
  3) UDP: 這種方式效能最差的方式,這源於早期flannel剛出現時,Linux核心還不支援VxLAN,即沒有VxLAN核心模組,因此flannel採用了這種方式,來實現隧道封裝,其效率可想而知,因此也給很多人一種印象,flannel的效能很差,其實說的是這種工作模式,若flannel工作在host-GW模式下,其效率是非常高的,因為幾乎沒有網路開銷。

4. flannel的網路配置引數:
  1) Network: flannel使用的CIDR格式的網路地址,主要用於為Pod配置網路功能。
   如: 10.10.0.0/16 --->
    master: 10.10.0.0/24
    node01: 10.10.1.0/24
    .....
    node255: 10.10.255.0/24

  2) SubnetLen: 把Network切分為子網供各節點使用時,使用多長的掩碼來切分子網,預設是24位.
  3) SubnetMin: 若需要預留一部分IP時,可設定最小從那裡開始分配IP,如:10.10.0.10/24 ,這樣就預留出了10個IP
  4) SubnetMax: 這是控制最多分配多個IP,如: 10.10.0.100/24 這樣在給Pod分配IP時,最大分配到10.10.0.100了。
  5) Backend: 指定後端使用的協議型別,就是上面提到的:vxlan( 原始vxlan,directrouter),host-gw, udp

flannel的配置:
  .....
  net-conf.json: |
    {
     "Network": "10.10.0.0/16",
     "Backend": {
     "Type": "vxlan",    #當然,若你很確定自己的叢集以後也不可能跨網段,你完全可以直接設定為 host-gw.
     "Directrouting": true #預設是false,修改為true就是可以讓VxLAN自適應是使用VxLAN還是使用host-gw了。
     }
    }

#在配置flannel時,一定要注意,不要在半道上,去修改,也就是說要在你部署k8s集群后,就直接規劃好,而不要在k8s叢集已經執行起來了,你再去修改,雖然可能也不會出問題,但一旦出問題,你就!!

  

#在配置好,flannel後,一定要測試,建立新Pod,看看新Pod是否能從flannel哪裡獲得IP地址,是否能通訊。

Calico:
  Calico是一種非常複雜的網路元件,它需要自己的etcd資料庫叢集來儲存自己通過BGP協議獲取的路由等各種所需要持久儲存的網路資料資訊,因此在部署Calico時,早期是需要單獨為Calico部署etcd叢集的,因為在k8s中,訪問etcd叢集只有APIServer可以對etcd進行讀寫,其它所有元件都必須通過APIServer作為入口,將請求發給APIServer,由APIServer來從etcd獲取必要資訊來返回給請求者,但Caclico需要自己寫,因此就有兩種部署Calico網路外掛的方式,一種是部署兩套etcd,另一種就是Calico不直接寫,而是通過APIServer做為代理,來儲存自己需要儲存的資料。通常第二種使用的較多,這樣可降低系統複雜度。
  當然由於Calico本身很複雜,但由於很多k8s系統可能存在的問題是,早期由於各種原因使用了flannel來作為網路外掛,但後期發現需要使用網路策略的需求,怎麼辦?
  目前比較成熟的解決方案是:flannel + Calico, 即使用flannel來提供簡單的網路管理功能,而使用Calico提供的網路策略功能。


Calico網路策略:

  

Egress:是出站的流量,即自己是源,遠端為服務端,因此我自己的源IP可確定,但埠不可預知, 目標的埠和IP都是確定的,因此to 和 ports都是指目標的IP和埠。
Ingress:是入站的流量,即自己為目標,而遠端是客戶端,因此要做控制,就只能對自己的埠 和 客戶端的地址 做控制。
我們通過Ingress 和 Egress定義的網路策略是對一個Pod生效 還是 對一組Pod生效?
這個就要通過podSelector來實現了。
而且在定義網路策略時,可以很靈活,如:入站都拒絕,僅允許出站的; 或 僅允許指定入站的,出站都允許等等。
另外,在定義網路策略時,也可定義 在同一名稱空間中的Pod都可以自由通訊,但跨名稱空間就都拒絕。

網路策略的生效順序:
  越具體的規則越靠前,越靠前,越優先匹配

網路策略的定義:
  kubectl explain networkpolicy
  spec:
   egress: <[]Object> :定義出站規則
   ingress: <[]Object>: 定義入站規則
   podSelector: 如論是入站還是出站,這些規則要應用到那些Pod上。
   policyType:[Ingress|Egress| Ingress,Egress] :
    它用於定義若同時定義了egress和ingress,到底那個生效?若僅給了ingress,則僅ingress生效,若設定為Ingress,Egress則兩個都生效。
   注意:policyType在使用時,若不指定,則當前你定義了egress就egress生效,若egress,ingress都定義了,則兩個都生效!!
    還有,若你定義了egress, 但policyType: ingress, egress ; egress定義了,但ingress沒有定義,這種要會怎樣?
    其實,這時ingress的預設規則會生效,即:若ingress的預設規則為拒絕,則會拒絕所有入站請求,若為允許,則會允許所有入站請求,
    所以,若你只想定義egress規則,就明確寫egress !!

  egress:<[]Object>
   ports: <[]Object> :因為ports是有埠號 和 協議型別的,因此它也是物件列表
    port :
    protocol: 這兩個就是用來定義目標埠和協議的。
   to :<[]Object>
    podSelector: <Object> : 在控制Pod通訊時,可控制源和目標都是一組Pod,然後控制這兩組Pod之間的訪問。
    ipBlock:<[]Object> : 指定一個Ip地址塊,只要在這個IP範圍內的,都受到策略的控制,而不區分是Pod還是Service。
    namespaceSelector: 這是控制對指定名稱空間內的全部Pod 或 部分Pod做訪問控制。

  Ingress:
   from: 這個from指訪問者訪問的IP
   ports: 也是訪問者訪問的Port

#定義網路策略:
vim   networkpolicy-demo.yaml
apiVersion:  networking.k8s.io/v1  
    #注意:雖然kubectl  explain networkpolicy中顯示為 extensions/v1beta1 ,但你要注意看說明部分.
kind:  NetworkPolicy
metadata:
     name:  deny-all-ingress
     namespace:  dev
spec:
     podSelector:  {}  #這裡寫空的含義是,選擇指定名稱空間中所有Pod
     policyTypes:
     -  Ingress        #這裡指定要控制Ingress(進來的流量),但又沒有指定規則,就表示全部拒絕,只有明確定義的,才是允許的。
                       #egress: 出去的流量不控制,其預設規則就是允許,因為不關心,所以愛咋咋地的意思。
    
#寫一個簡單的自主式Pod的定義:
vim  pod1.yaml
    apiVersion:  v1
    kind: Pod
    metadata: 
      name:  pod1
    spec:
      containers:
      - name:  myapp
        image:  harbor.zcf.com/k8s/myapp:v1

#建立dev名稱空間,並應用規則
kubectl apply -f networkpolicy-demo.yaml -n dev

# kubectl describe -n dev networkpolicies
    Name:         deny-all-ingress
    Namespace:    dev
   ........................
    Spec:
      PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
      Allowing ingress traffic:
        <none> (Selected pods are isolated for ingress connectivity)
      Allowing egress traffic:
        <none> (Selected pods are isolated for egress connectivity)


#檢視dev名稱空間中的網路規則:
kubectl get networkpolicy -n dev     
 或   
 kubectl  get  netpol  -n  dev

#然後在dev 和 prod 兩個名稱空間中分別建立pod
kubectl  apply   -f  pod1.yaml   -n   dev
kubectl  apply   -f  pod1.yaml   -n   prod

#接著測試訪問這兩個名稱空間中的pod
kubectl  get  pod  -n  dev   -o   wide
    
    #測試訪問:
        curl   http://POD_IP
        
kubectl  get  pod  -n  prod   -o   wide

    #測試訪問:
        curl   http://POD_IP

#通過以上測試,可以看到,dev名稱空間中的pod無法被訪問,而prod名稱空間中的pod則可被訪問。
#測試放行所有dev的ingress入站請求。
# vim  networkpolicy-demo.yaml
    apiVersion:  networking.k8s.io/v1   
    kind:  NetworkPolicy
    metadata:
       name: allow-all-ingress
       namespace:  dev
    spec:
       podSelector:  {}
       ingress:
       -  {}      #這就表示允許所有,因為定義了規則,但規則是空的,即允許所有。
       policyTypes:
       -  Ingress

#接著測試,和上面測試一樣,也是訪問dev 和 prod兩個名稱空間中的pod,若能訪問,則成功。
# kubectl describe -n dev netpol
    Name:         deny-all-ingress
    Namespace:    dev
    .....................
    Spec:
      PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
      Allowing ingress traffic:
        To Port: <any> (traffic allowed to all ports)
        From: <any> (traffic not restricted by source)
      Allowing egress traffic:
        <none> (Selected pods are isolated for egress connectivity)
      Policy Types: Ingress

#測試定義一個僅允許訪問dev名稱空間中,pod標籤 app=myapp 的一組pod的80埠

  

#先給pod1打上app=myapp的標籤
#kubectl label pod pod1 app=myapp -n dev
    
vim  allow-dev-80.yaml
 apiVersion: networking.k8s.io/v1
 kind: NetworkPolicy
 metadata:
    name: allow-myapp-ingress
 spec:
   podSelector:
      matchLabels:
        app: myapp
     ingress:
     - from:
        - ipBlock:
            cidr:  10.10.0.0/16
            except:
            -  10.10.1.2/32
       ports:
       - protocol:  TCP
         port:  88
       - protocol:  TCP
         port:  443

#檢視定義的ingress規則
kubectl  get  netpol  -n  dev

#然後測試訪問 dev 名稱空間中的pod
curl  http://Pod_IP
curl  http://Pod_IP:443

curl   http://Pod_IP:88

上圖測試:
1. 先給dev名稱空間打上標籤
  kubectl   label  namespace   dev   ns=dev

2. 編寫網路策略配置清單
  vim  allow-ns-dev.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-ns-dev
    spec:
      podSelector: {}
      ingress:
      - from:
         - namespaceSelector:
              matchLabels:
                ns: dev
      egress:
      - to:
         - namespaceSelector:
             matchLabels:
               ns: dev



#要控制egress,也是如此,只是將ingress替換為egress即可,然後在做測試。
另外,關於網路策略,建議:
 名稱空間內:
     拒絕所有出站,入站流量
     僅放行出站目標為當前名稱空間內各Pod間通訊,因為網路策略控制的顆粒度是Pod級別的,不是名稱空間級別。
     具體的網路策略,要根據實際需求,來定義ingress 和 egress規則。