這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
- docker的網路方案
- k8s的flannel模式
- flannel模式原理
- flannel模式的優缺點
- 部署及驗證
kubernets的網路,從設計上來講是“扁平、直接”的,即要求:
- 所有容器可以不使用NAT技術就可以與其他容器通訊
- 所有節點(物理機 虛擬機器 容器)都可以不使用NAT同容器通訊
- 容器看到的IP地址和別的機器看到的IP是一致的
docker的網路方案
docker的網路支援如下四種:
- none
- host,與宿主機共用,佔用宿主機資源
- container,使用某容器的namespace,例如k8s的同一pod內的各個容器
- bridge,掛到橋接器docker0上,走iptables做NAT
實際上還有一種方法:先以none的方式run起來容器,然後使用pipework為容器增加一個veth網卡,該veth的另一端掛到建立的橋接器br0上;再將宿主機的物理網卡掛到該橋接器br0上:
[容器內eth0]--veth--br0--en0-->
需要注意這種方法與bridge的不同。docker0的網段是172.17.0.1/16,當bridge模式時,容器的ip地址均為此網段下的,報文是走NAT出去的。但pipework自訂網路時,br0、[eth0]均與宿主機的en0同一網段,報文走橋接器轉寄出去。
docker的網路能否滿足需求呢?
bridge模式下,不同物理機的容器ip是完全的平行空間,可能相同,不能滿足k8s扁平的要求;pipework方式能夠滿足k8s的要求,但是需要為每個容器都指定ip地址,比較囉嗦。
k8s的flannel模式
k8s本身並不提供網路方案,而是交給flannel,ovs等add-on來處理。這裡只對flannel做說明。
flannel模式原理
flannel是一種over-lay網路。簡單來說,over-lay即報文在進入實際物理網路之前,會經過一層UDP封裝,作為payload到達對端;對端拿到UDP報文後解包,得到真實的使用者報文後,再轉到真實的接收方。
以綠色線的一個具體報文來說:
1、Pod內的一個容器使用pod的網路namespace,發送報文;該網路namespace上的網卡類型為veth,其pair網卡為宿主機網路namespace空間上的veth網卡veth0。veth是一種類似管道的網路裝置,總是成對出現,報文從一端的veth網卡發送後,另一端的veth網卡會收到該報文。通常容器、虛擬機器,會建立一對veth網卡,並將其中一端加到自己的namespace中。因此,宿主機的veth0網卡會收到容器發出的報文。
2、veth0拿到後,由於目的地址10.1.20.x與veth不在同一網段,因此會將報文交給橋接器來轉寄。官方圖示為docker0,但出於網路地址規劃的原因,實際在k8s上會建立一個cni0橋接器,cni0橋接器負責本node容器的ip分配(24位元遮罩)。這裡有一個問題:各個node都有自己的cni0橋接器,怎麼保證地址不會分配重複呢?這裡就是靠flannel了,flannel會根據全域統一的etcd來為每個node分配全叢集唯一的網段,避免地址分配衝突。cnio拿到報文後,查詢本機路由,匹配的是16位元遮罩的flannel.1,因此將報文丟給flannel.1。
[root@note2 ~]# route -nKernel IP routing tableDestination Gateway Genmask Flags Metric Ref Use Iface0.0.0.0 192.168.181.254 0.0.0.0 UG 100 0 0 eno1678003210.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.110.1.15.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0192.168.181.0 0.0.0.0 255.255.255.0 U 100 0 0 eno16780032
3、flannel.1是個什麼類型的網卡呢?圖示的下一跳是flanneld,一個使用者態進程(當然,這個進程已經封裝在docker容器裡了),這是怎麼實現的呢?這裡需要重提一下linux上使用者態和核心態通訊的手段。一般來說,有以下幾種:
- netlink socket
- syscall,例如調用使用者態的read/write介面
- IOCTL
- procfs,例如讀取/proc目錄下的ip統計計數
還有一種手段,使用TUN/TAP介面。
tun/tap驅動程式實現了虛擬網卡的功能,tun表示虛擬是點對點裝置,tap表示虛擬是乙太網路裝置,這兩種裝置針對網路包實施不同的封裝。利用tun/tap驅動,可以將tcp/ip協議棧處理好的網路分包傳給任何一個使用tun/tap驅動的進程,由進程重新處理後再發到物理鏈路中。開源項目openvpn( http://openvpn.sourceforge.net)和Vtun( http://vtun.sourceforge.net)都是利用tun/tap驅動實現的隧道封裝
具體到k8s上,flanneld封裝在flannel-git容器中,該容器與宿主機是同一網路namespace;flanneld啟動時會建立flannel.1網卡,用來接收所有發送到10.1.0.0/16網路的報文;上面第2步報文轉給flannel.1後,核心會將報文上送給flanneld。
[root@node1 ~]# docker ps92197740eeef quay.io/coreos/flannel-git:v0.6.1-62-g6d631ba-amd64 "/opt/bin/flanneld --" 22 hours ago Up 22 hours k8s_kube-flannel.135690a3_kube-flannel-ds-ze30q_kube-system_ce4936c7-dd2c-11e6-9af1-000c29906342_1faf7ca4
4、flanneld維護了一份全域的node網路資訊,根據報文目的地址查詢得到該地址對應的node資訊後,將報文封裝到udp中(新報文的目的地址為對應node的地址),再將封裝後的udp報文查詢路由後經過物理網路(eno16780032)發送給目的node。
5、對端node收到報文後,走普通的查詢路由為本機後上送使用者態流程,封裝報文交給flanneld。之後,報文解包、根據新包目的地址查路由,交給目的pod的容器。
[root@node1 ~]# netstat -anup|moreActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name udp 0 0 0.0.0.0:8472 0.0.0.0:*
flannel模式的優缺點
最大的缺點是,所有報文都需要走flanneld這個使用者態進程進行一次封裝後,才能出去。當網路流量較大時,flanneld將會成為瓶頸;相對來說,open vswitch可能穩定性、可靠性會更好一些。但flannel也有ovs所不具備的優點:flannel能夠通過etcd感知k8s的service變動,動態維護自己的路由表(第4步)。
部署及驗證
1、部署flannel部署比較簡單。在master上kubeadm init完成後,執行下面的命令。該yaml定義了flannel容器以及相關的配置容器。有一些材料沒有使用容器部署,相對來說複雜一點。
kubectl create -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
之後查看kube-flannel的狀態,Running則成功。此時k8s叢集只有master一台機器,再登陸到2個node上,執行kubeadm join --token={token_id} master_ip將node加入到k8s叢集中。最終在master上查看結果如下。
[root@localhost k8s]# kubectl get nodesNAME STATUS AGElocalhost.localdomain Ready 1dnode1 Ready 23hnote2 Ready 23h[root@localhost k8s]#[root@localhost k8s]# kubectl get pods -n kube-systemNAME READY STATUS RESTARTS AGEdummy-2088944543-vafe7 1/1 Running 0 1detcd-localhost.localdomain 1/1 Running 1 1dkube-apiserver-localhost.localdomain 1/1 Running 1 1dkube-controller-manager-localhost.localdomain 1/1 Running 0 1dkube-discovery-982812725-5j9ri 1/1 Running 0 1dkube-dns-2247936740-gqifl 3/3 Running 0 1dkube-flannel-ds-kfcpe 2/2 Running 0 1dkube-flannel-ds-klmfz 2/2 Running 7 23hkube-flannel-ds-ze30q 2/2 Running 4 23hkube-proxy-amd64-2yx0g 1/1 Running 0 1dkube-proxy-amd64-hcj9t 1/1 Running 0 23hkube-proxy-amd64-vhevz 1/1 Running 0 23hkube-scheduler-localhost.localdomain 1/1 Running 1 1d
在node上,可以看到flannel.1等網卡資訊。
2、驗證
將下面的RC儲存為alpine.yaml。
apiVersion: v1kind: ReplicationControllermetadata: name: alpine labels: name: alpinespec: replicas: 2 selector: name: alpine template: metadata: labels: name: alpine spec: containers: - image: mritd/alpine:3.4 imagePullPolicy: Always name: alpine command: - "bash" - "-c" - "while true;do echo test;done" ports: - containerPort: 8080 name: alpine
並建立namespace、apply。
kubectl create namespace alpinekubectl apply -n alpine -f alpine.yml
等待一段時間後,在master上查看apply情況(-o wide可以看到更多資訊,即IP/node):
[root@localhost k8s]# kubectl get pods -n alpine -o wideNAME READY STATUS RESTARTS AGE IP NODEalpine-4zmey 1/1 Running 0 23h 10.244.1.2 note2alpine-55zej 1/1 Running 0 23h 10.244.2.2 node1
2個pod分別跑在2個node上(前面yml定義的replicas為2)。
登陸到其中一個node上,進入對應的容器docker exec -it {docker_id} bash,ping對端的ip地址看是否通。如果不通,可能你使用的也是CentOS作業系統,它的iptables預設會丟棄所有報文並回複icmp-host-prohibited,所以需要將flanneld監聽的8472連接埠/udp協議的報文加到INPUT鏈上,各個物理node上都要執行。當然,我覺得更好的做法是flannel自己來加這條規則。
[root@node1 ~]# iptables -I INPUT -p udp -m udp --dport 8472 -j ACCEPT[root@node1 ~]#[root@node1 ~]# iptables -L -n|moreChain INPUT (policy ACCEPT)target prot opt source destination ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:8472...REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
在物理網卡上抓了個包,可以看到該UDP包的payload中黑色橫線標記的2個ip地址。
以上。