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提供的服務必須能被互聯網上的用戶所訪問到。
補充:
IPVS 能替代 iptables嗎?
不能,因為ipvs並不能完全支持所有場景,這個我目前理解不深。不做詳述
CNI(容器網絡接口):
這是K8s中提供的一種通用網絡標准規范,因為k8s本身不提供網絡解決方案。
目前比較知名的網絡解決方案有:
flannel
calico
canel
kube-router
.......
所有的網絡解決方案,它們的共通性:
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規則。