k8s網絡CNI之flannel
k8s網絡模型
- Container to Container
- Pod to Pod
- Service to Pod
- external to Service
常見CNI項目
- Flannel,提供疊加網絡,基於linux TUN/TAP,使用UDP封裝IP報
文來創建疊加網絡,並借助etcd維護網絡分配情況 - Calico,基於BGP的三層網絡,支持網絡策略實現網絡的訪問控制。在每台機器上運行一個vRouter,利用內核轉發數據包,並借助iptables實現防火牆等功能
- Canal,由Flannel和Calico聯合發布的一個統一網絡插件,支持網絡策略
- Weave Net,多主機容器的網絡方案,支持去中心化的控制平面,數據平面上,通過UDP封裝實現L2 Overlay
- Contiv,思科方案,直接提供多租戶網絡,支持L2(VLAN)、L3(BGP)、Overlay(VXLAN)
- OpenContrail,Juniper開源
- kube-router,K8s網絡一體化解決方案,可取代kube-proxy實現基於ipvs的Service,支持網絡策略、完美兼容BGP的高級特性
重點了解Flannel
, Calico
, Canal
, kube-router
Flannel插件
首先,flannel會利用Kubernetes API或者etcd用於存儲整個集群的網絡配置,其中最主要的內容為設置集群的網絡地址空間,例如,設定整個集群內所有容器的IP都取自網段“10.1.0.0/16”。接着,flannel會在每個主機中運行flanneld作為agent,它會為所在主機從集群的網絡地址空間中,獲取一個小的網段subnet,本主機內所有容器的IP地址都將從中分配。然后,flanneld再將本主機獲取的subnet以及用於主機間通信的Public IP,同樣通過kubernetes API或者etcd存儲起來。最后,flannel利用各種backend mechanism,例如udp,vxlan等等,跨主機轉發容器間的網絡流量,完成容器間的跨主機通信。 大家都知道Kubernetes是通過CNI標准對接網絡插件的,但是當你去看Flannel(coreos/flannel)的代碼時,並沒有發現它實現了CNI的接口。如果你玩過其他CNI插件,你會知道還有一個二進制文件用來供kubele調用,並且會調用后端的網絡插件。對於Flannel(coreos/flannel)來說,這個二進制文件是什么呢? 這個二進制文件就對應宿主機的/etc/cni/net.d/flannel |
flannel原理說明
現在,我們來簡單看一下,如果上方Machine A中IP地址為10.1.15.2/24的容器要與下方Machine B中IP地址為10.1.16.2/24的容器進行通信,封包是如何進行轉發的。從上文可知,每個主機的flanneld會將自己與所獲取subnet的關聯信息存入etcd中,例如,subnet 10.1.15.0/24所在主機可通過IP 192.168.0.100訪問,subnet 10.1.16.0/24可通過IP 192.168.0.200訪問。反之,每台主機上的flanneld通過監聽etcd,也能夠知道其他的subnet與哪些主機相關聯。如下圖,Machine A上的flanneld通過監聽etcd已經知道subnet 10.1.16.0/24所在的主機可以通過Public 192.168.0.200訪問,而且熟悉docker橋接模式的同學肯定知道,目的地址為10.1.16.2/24的封包一旦到達Machine B,就能通過cni0網橋轉發到相應的pod,從而達到跨宿主機通信的目的。
因此,flanneld只要想辦法將封包從Machine A轉發到Machine B就OK了,而上文中的backend就是用於完成這一任務。不過,達到這個目的的方法是多種多樣的,所以我們也就有了很多種backend。在這里我們舉例介紹的是最簡單的一種方式`hostgw`:因為`Machine A和Machine B處於同一個子網內`,它們原本就能直接互相訪問。因此最簡單的方法是:在Machine A中的容器要訪問Machine B的容器時,我們可以將Machine B看成是網關,當有封包的目的地址在subnet 10.1.16.0/24范圍內時,就將其直接轉發至B即可。而這通過下圖中那條紅色標記的路由就能完成,對於Machine B同理可得。由此,在滿足仍有subnet可以分配的條件下,我們可以將上述方法擴展到任意數目位於同一子網內的主機。而任意主機如果想要訪問主機X中subnet為S的容器,只要在本主機上添加一條目的地址為R,網關為X的路由即可。
flannel配置參數
Network,全局CIDR格式的IPv4網絡,字符串格式,必選
SubnetLen,子網,默認為24位
SubnetMin,分配給節點的起始子網
SubnetMax,分配給節點的最大子網
Backend,flannel要使用的后端
flannel初始配置
[root@master bin]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
flannel后端實現原理
host-gw
hostgw是最簡單的backend,它的原理非常簡單,直接添加路由,將目的主機當做網關,直接路由原始封包。例如,我們從etcd中監聽到一個EventAdded事件:subnet為10.1.15.0/24被分配給主機Public IP 192.168.0.100,hostgw要做的工作非常簡單,在本主機上添加一條目的地址為10.1.15.0/24,網關地址為192.168.0.100,輸出設備為上文中選擇的集群間交互的網卡即可。對於EventRemoved事件,刪除對應的路由即可。
VxLAN
當有一個EventAdded到來時,flanneld如何進行配置的,以及封包是如何在flannel網絡中流動的。 |
如上圖所示,當主機B加入flannel網絡時,和其他所有backend一樣,它會將自己的subnet 10.1.16.0/24和Public IP 192.168.0.101寫入etcd中,和其他backend不一樣的是,它還會將vtep設備flannel.1的mac地址也寫入etcd中。
之后,主機A會得到EventAdded事件,並從中獲取上文中B添加至etcd的各種信息。這個時候,它會在本機上添加三條信息:
-
路由信息:所有通往目的地址10.1.16.0/24的封包都通過vtep設備flannel.1設備發出,發往的網關地址為10.1.16.0,即主機B中的flannel.1設備。
-
fdb信息:MAC地址為MAC B的封包,都將通過vxlan首先發往目的地址192.168.0.101,即主機B
-
arp信息:網關地址10.1.16.0的地址為MAC B
現在有一個容器網絡封包要從A發往容器B,和其他backend中的場景一樣,封包首先通過網橋轉發到主機A中。此時通過,查找路由表,該封包應當通過設備flannel.1發往網關10.1.16.0。通過進一步查找arp表,我們知道目的地址10.1.16.0的mac地址為MAC B。到現在為止,vxlan負載部分的數據已經封裝完成。由於flannel.1是vtep設備,會對通過它發出的數據進行vxlan封裝(這一步是由內核完成的,相當於udp backend中的proxy),那么該vxlan封包外層的目的地址IP地址該如何獲取呢?事實上,對於目的mac地址為MAC B的封包,通過查詢fdb,我們就能知道目的主機的IP地址為192.168.0.101。 最后,封包到達主機B的eth0,通過內核的vxlan模塊解包,容器數據封包將到達vxlan設備flannel.1,封包的目的以太網地址和flannel.1的以太網地址相等,三層封包最終將進入主機B並通過路由轉發達到目的容器。 |
虛擬網絡數據幀添加到VxLAN首部后,封裝在物理網絡UDP報文中,到達目地主機后,去掉物理網絡報文頭部及VxLAN首部,再將報文交付給目的終端
VxLAN后端使用隧道網絡轉發會導致一定和流量開銷,VxLAN DirectRouting模式,通過添加必要的路由信息使用節點的二層網絡直接發送Pod通信報文,僅在跨IP網絡時,才啟用隧道方式。這樣,在不跨IP網絡時,性能基本接近二層物理網絡
已經創建的flannel的網絡配置修改后不會生效, 只能刪掉flannel, 修改yaml文件后重新創建, 所以一定要提前確定好網絡配置 |
canal安裝
curl https://docs.projectcalico.org/v3.9/manifests/canal.yaml -O
kubectl apply -f canal.yaml
NetworkPolicy相關術語
kubectl explain networkpolicy.spec講解:
- egress 出站流量規則 可以根據ports和to去定義規則。ports下可以指定目標端口和協議。to(目標地址):目標地址分為ip地址段、pod、namespace
- ingress 入站流量規則 可以根據ports和from。ports下可以指定目標端口和協議。from(來自那個地址可以進來):地址分為ip地址段、pod、namespace
- podSelector 定義NetworkPolicy的限制范圍。直白的說就是規則應用到那個pod上。podSelector: {},留空就是定義對當前namespace下的所有pod生效。沒有定義白名單的話 默認就是Deny ALL (拒絕所有)
- policyTypes 指定那個規則 那個規則生效,不指定就是默認規則。
實驗
創建兩個namespace
kubectl create namespace dev
kubectl create namespace prod
創建pod
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
管理入站流量
kubectl explain networkpolicy.spec.ingress
NetworkPolicy屬於名稱空間級別 參數: from,源地址對象列表,多個項目間邏輯關系為或,若為空,表示匹配一切源地址;若至少有一個值,則僅允許列表中流量通過 ports,可被訪問的端口對象列表,多個項目間為邏輯或,若為空,表示匹配Pod的所有端口;若至少有一個值,則僅允許訪問指定的端口 |
拒絕所有入站流量的規則:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-policy
spec:
podSelector: {}
policyTypes:
- Ingress
沒有定義Ingress規則, 但是寫到了policyTypes里, 就表示默認拒絕入方向訪問, 沒有寫Egress, 表示默認允許出方向訪問 |
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {} # 匹配所有Pod
ingress:
- {} # 定義為空, 表示允許訪問
policyTypes: ["Ingress"]
放入特定入站流量
僅定義from將默認允許本地Pod所有端口;僅定義ports將默認允許所有源端點;同時定義from和ports時,是邏輯與關系 多個from之間是邏輯或關系 多個ports之間是邏輯或關系 from與ports間是邏輯與關系 from下ipBlock、namespaceSelector、podSelector同時使用多個時,為邏輯或關系 |
# 為pod打標簽
[root@master manifests]# kubectl label pod myapp -n dev app=myapp --overwrite
pod/myapp labeled
[root@master manifests]# kubectl get pod -n dev --show-labels
NAME READY STATUS RESTARTS AGE LABELS
myapp 1/1 Running 0 29m app=myapp
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-myapp-ingress
name: default
spec:
podSelector: # 該規則只在當前的namespace下,攜帶app: myapp標簽的pod生效。限制請求的類型包括Ingress和Egress
matchLabels:
app: myapp
policyTypes: ["Ingress"]
ingress:
- from:
- ipBlock: # 網絡地址塊
cidr: 10.244.0.0/16 # 允許某個網段訪問
except: # 排除某個網段或ip訪問(只拒絕掉10.244.1.5)
- 10.244.1.5/32
- podSelector: # 攜帶了app: myapp標簽的pod可以訪問
matchLabels:
app: myapp
ports:
- protocol: TCP
port: 80
管理出站流量
拒絕所有出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
spec:
podSelector: {}
policyTypes: ["Egress"]
放行特定的出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-tomcat-egress
spec:
podSelector:
matchLabels:
app: tomcat
policyTypes: ["Egress"]
egress:
- to:
- podSelector:
matchLabels:
app: nginx
- ports:
- protocol: TCP
port: 80
- to:
- podSelector:
matchLabels:
app: mysql
ports:
- protocol: TCP
port: 3306
對app=tomcat的Pod,限制只能訪問app=nginx的80端口和app=mysql的3306端口 |
隔離名稱空間
隔離名稱空間,應該放行與kube-system名稱空間中Pod的通信,以實現監控和名稱解析等各種管理功能
kubectl explain networkpolicy.spec.ingress.from.namespaceSelector.matchExpressions
kubectl explain networkpolicy.spec.egress.to.namespaceSelector.matchExpressions
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-deny-all
namespace: default
spec:
policyTypes: ["Ingress","Egress"]
podSelector: {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-
namespace: default
spec:
policyTypes: ["Ingress","Egress"]
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchExpressions:
- key: name
operator: In
values: ["default","kube-system"]
egress:
- to:
- namespaceSelector:
matchExpressions:
- key: name
operator: In
values: ["default","kube-system"]
參考鏈接
https://pdf.us/2019/03/27/3129.html
https://my.oschina.net/jxcdwangtao/blog/1624486