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
kube-router
.......
等等,目前比較常用的時flannel和calico,flannel的功能比較簡單,不具備複雜網路的配置能力,calico是比較出色的網路管理外掛,單具備複雜網路配置能力的同時,往往意味著本身的配置比較複雜,所以相對而言,比較小而簡單的叢集使用flannel,考慮到日後擴容,未來網路可能需要加入更多裝置,配置更多策略,則使用calico更好
所有的網路解決方案,它們的共通性:
1. 虛擬網橋
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規則。