目錄html
在Kubernetes中設計了一種網絡模型,要求不管容器運行在集羣中的哪一個節點,全部容器都能經過一個扁平的網絡平面進行通訊,即在同一IP網絡中。須要注意的是:在K8S集羣中,IP地址分配是以Pod對象爲單位,而非容器,同一Pod內的全部容器共享同一網絡名稱空間。node
瞭解Docker的友友們都應該清楚,Docker容器的原生網絡模型主要有3種:Bridge(橋接)、Host(主機)、none。linux
#使用如下命令查看docker原生的三種網絡 [root@localhost ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 0efec019c899 bridge bridge local 40add8bb5f07 host host local ad94f0b1cca6 none null local #none網絡,在該網絡下的容器僅有lo網卡,屬於封閉式網絡,一般用於對安全性要求較高而且不須要聯網的應用 [root@localhost ~]# docker run -it --network=none busybox / # ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) #host網絡,共享宿主機的網絡名稱空間,容器網絡配置和host一致,可是存在端口衝突的問題 [root@localhost ~]# docker run -it --network=host busybox / # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:0c:29:69:a7:23 brd ff:ff:ff:ff:ff:ff inet 192.168.1.4/24 brd 192.168.1.255 scope global dynamic eth0 valid_lft 84129sec preferred_lft 84129sec inet6 fe80::20c:29ff:fe69:a723/64 scope link valid_lft forever preferred_lft forever 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue link/ether 02:42:29:09:8f:dd brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:29ff:fe09:8fdd/64 scope link valid_lft forever preferred_lft forever / # hostname localhost #bridge網絡,Docker安裝完成時會建立一個名爲docker0的linux bridge,不指定網絡時,建立的網絡默認爲橋接網絡,都會橋接到docker0上。 [root@localhost ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.024229098fdd no [root@localhost ~]# docker run -d nginx #運行一個nginx容器 c760a1b6c9891c02c992972d10a99639d4816c4160d633f1c5076292855bbf2b [root@localhost ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.024229098fdd no veth3f1b114 一個新的網絡接口veth3f1b114橋接到了docker0上,veth3f1b114就是新建立的容器的虛擬網卡。進入容器查看其網絡配置: [root@localhost ~]# docker exec -it c760a1b6c98 bash root@c760a1b6c989:/# apt-get update root@c760a1b6c989:/# apt-get iproute root@c760a1b6c989:/# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 38: eth0@if39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
從上能夠看到容器內有一個網卡eth0@if39
,實際上eth0@if39
和veth3f1b114
是一對veth pair
。veth pair
是一種成對出現的特殊網絡設備,能夠想象它們由一根虛擬的網線進行鏈接的一對網卡,eth0@if39
在容器中,veth3f1b114
掛在網橋docker0
上,最終的效果就是eth0@if39
也掛在了docker0上
。nginx
橋接式網絡是目前較爲流行和默認的解決方案。可是這種方案的弊端是沒法跨主機通訊的,僅能在宿主機本地進行,而解決該問題的方法就是NAT。全部接入到該橋接設備上的容器都會被NAT隱藏,它們發往Docker主機外部的全部流量都會通過源地址轉換後發出,而且默認是沒法直接接受節點以外的其餘主機發來的請求。當須要接入Docker主機外部流量,就須要進行目標地址轉換甚至端口轉換將其暴露在外部網絡當中。以下圖:git
容器內的屬於私有地址,須要在左側的主機上的eth0上進行源地址轉換,而右側的地址須要被訪問,就須要將eth0的地址進行NAT轉換。SNAT---->DNATgithub
這樣的通訊方式會比較麻煩,從而須要藉助第三方的網絡插件實現這樣的跨主機通訊的網絡策略。docker
咱們知道的是,在K8S上的網絡通訊包含如下幾類:json
容器間的通訊:同一個Pod內的多個容器間的通訊,它們之間經過lo網卡進行通訊。vim
Pod之間的通訊:經過Pod IP地址進行通訊。後端
Pod和Service之間的通訊:Pod IP地址和Service IP進行通訊,二者並不屬於同一網絡,實現方式是經過IPVS或iptables規則轉發。
Service和集羣外部客戶端的通訊,實現方式:Ingress、NodePort、Loadbalance
K8S網絡的實現不是集羣內部本身實現,而是依賴於第三方網絡插件----CNI(Container Network Interface)
flannel、calico、canel等是目前比較流行的第三方網絡插件。
這三種的網絡插件須要實現Pod網絡方案的方式一般有如下幾種:
虛擬網橋、多路複用(MacVLAN)、硬件交換(SR-IOV)
不管是上面的哪一種方式在容器當中實現,都須要大量的操做步驟,而K8S支持CNI插件進行編排網絡,以實現Pod和集羣網絡管理功能的自動化。每次Pod被初始化或刪除,kubelet都會調用默認的CNI插件去建立一個虛擬設備接口附加到相關的底層網絡,爲Pod去配置IP地址、路由信息並映射到Pod對象的網絡名稱空間。
在配置Pod網絡時,kubelet會在默認的/etc/cni/net.d/目錄中去查找CNI JSON配置文件,而後經過type屬性到/opt/cni/bin中查找相關的插件二進制文件,以下面的"portmap"。而後CNI插件調用IPAM插件(IP地址管理插件)來配置每一個接口的IP地址:
[root@k8s-master ~]# cat /etc/cni/net.d/10-flannel.conflist { "name": "cbr0", "plugins": [ { "type": "flannel", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }, { "type": "portmap", "capabilities": { "portMappings": true } } ] } kubelet調用第三方插件,進行網絡地址的分配
CNI主要是定義容器網絡模型規範,連接容器管理系統和網絡插件,二者主要經過上面的JSON格式文件進行通訊,實現容器的網絡功能。CNI的主要核心是:在建立容器時,先建立好網絡名稱空間(netns),而後調用CNI插件爲這個netns配置網絡,最後在啓動容器內的進程。
常見的CNI網絡插件包含如下幾種:
在各節點上的Docker主機在docker0上默認使用同一個子網,不一樣節點的容器都有可能會獲取到相同的地址,那麼在跨節點通訊時就會出現地址衝突的問題。而且在多個節點上的docker0使用不一樣的子網,也會由於沒有準確的路由信息致使沒法準確送達報文。
而爲了解決這一問題,Flannel的解決辦法是,預留一個使用網絡,如10.244.0.0/16,而後自動爲每一個節點的Docker容器引擎分配一個子網,如10.244.1.0/24和10.244.2.0/24,並將分配信息保存在etcd持久存儲。
第二個問題的解決,Flannel是採用不一樣類型的後端網絡模型進行處理。其後端的類型有如下幾種:
VxLAN(Virtual extensible Local Area Network)虛擬可擴展局域網,採用MAC in UDP封裝方式,具體的實現方式爲:
跨節點的Pod之間的通訊就是以上的一個過程,整個過程當中通訊雙方對物理網絡是沒有感知的。以下網絡圖:
VxLAN的部署能夠直接在官方上找到其YAML文件,以下:
[root@k8s-master:~# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml clusterrole.rbac.authorization.k8s.io/flannel created clusterrolebinding.rbac.authorization.k8s.io/flannel created serviceaccount/flannel created configmap/kube-flannel-cfg created daemonset.extensions/kube-flannel-ds-amd64 created daemonset.extensions/kube-flannel-ds-arm64 created daemonset.extensions/kube-flannel-ds-arm created daemonset.extensions/kube-flannel-ds-ppc64le created daemonset.extensions/kube-flannel-ds-s390x created #輸出以下結果表示flannel能夠正常運行了 [root@k8s-master ~]# kubectl get daemonset -n kube-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-flannel-ds 3 3 3 3 3 beta.kubernetes.io/arch=amd64 202d kube-proxy 3 3 3 3 3 beta.kubernetes.io/arch=amd64 202d
運行正常後,flanneld會在宿主機的/etc/cni/net.d目錄下生成自已的配置文件,kubelet將會調用它。
網絡插件運行成功後,Node狀態才Ready。
[root@k8s-master ~]# kubectl get node NAME STATUS ROLES AGE VERSION k8s-master Ready master 202d v1.11.2 k8s-node01 Ready <none> 202d v1.11.2 k8s-node02 Ready <none> 201d v1.11.2
flannel運行後,在各Node宿主機多了一個網絡接口:
#master節點的flannel.1網絡接口,其網段爲:10.244.0.0 [root@k8s-master ~]# ifconfig flannel.1 flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 10.244.0.0 netmask 255.255.255.255 broadcast 0.0.0.0 inet6 fe80::31:5dff:fe01:4bc0 prefixlen 64 scopeid 0x20<link> ether 02:31:5d:01:4b:c0 txqueuelen 0 (Ethernet) RX packets 1659239 bytes 151803796 (144.7 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2115439 bytes 6859187242 (6.3 GiB) TX errors 0 dropped 10 overruns 0 carrier 0 collisions 0 #node1節點的flannel.1網絡接口,其網段爲:10.244.1.0 [root@k8s-node01 ~]# ifconfig flannel.1 flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 10.244.1.0 netmask 255.255.255.255 broadcast 0.0.0.0 inet6 fe80::2806:4ff:fe71:2253 prefixlen 64 scopeid 0x20<link> ether 2a:06:04:71:22:53 txqueuelen 0 (Ethernet) RX packets 136904 bytes 16191144 (15.4 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 180775 bytes 512637365 (488.8 MiB) TX errors 0 dropped 8 overruns 0 carrier 0 collisions 0 #node2節點的flannel.1網絡接口,其網段爲:10.244.2.0 [root@k8s-node02 ~]# ifconfig flannel.1 flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 10.244.2.0 netmask 255.255.255.255 broadcast 0.0.0.0 inet6 fe80::58b7:7aff:fe8d:2d prefixlen 64 scopeid 0x20<link> ether 5a:b7:7a:8d:00:2d txqueuelen 0 (Ethernet) RX packets 9847824 bytes 12463823121 (11.6 GiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2108796 bytes 185073173 (176.4 MiB) TX errors 0 dropped 13 overruns 0 carrier 0 collisions 0
從上面的結果能夠知道 :
舉個實際例子
#啓動一個nginx容器,副本爲3 [root@k8s-master ~]# kubectl run nginx --image=nginx:1.14 --port=80 --replicas=3 deployment.apps/nginx created #查看Pod [root@k8s-master ~]# kubectl get pods -o wide |grep nginx nginx-5bd76bcc4f-8s64s 1/1 Running 0 2m 10.244.2.85 k8s-node02 nginx-5bd76bcc4f-mr6k5 1/1 Running 0 2m 10.244.1.146 k8s-node01 nginx-5bd76bcc4f-pb257 1/1 Running 0 2m 10.244.0.17 k8s-master
能夠看到,3個Pod都分別運行在各個節點之上,其中master上的Pod的ip爲:10.244.0.17,在master節點上查看網絡接口能夠發如今各個節點上多了一個虛擬接口cni0,其ip地址爲10.244.0.1。它是由flanneld建立的一個虛擬網橋叫cni0,在Pod本地通訊使用。 這裏須要注意的是,cni0虛擬網橋,僅做用於本地通訊!!!!
[root@k8s-master ~]# ifconfig cni0 cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 10.244.0.1 netmask 255.255.255.0 broadcast 0.0.0.0 inet6 fe80::848a:beff:fe44:4959 prefixlen 64 scopeid 0x20<link> ether 0a:58:0a:f4:00:01 txqueuelen 1000 (Ethernet) RX packets 2772994 bytes 300522237 (286.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 3180562 bytes 928687288 (885.6 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
flanneld爲每一個Pod建立一對veth虛擬設備,一端放在容器接口上,一端放在cni0橋上。 使用brctl查看該網橋:
#能夠看到有一veth的網絡接口橋接在cni0網橋上 [root@k8s-master ~]# brctl show cni0 bridge name bridge id STP enabled interfaces cni0 8000.0a580af40001 no veth020fafae #宿主機ping測試訪問Pod ip [root@k8s-master ~]# ping 10.244.0.17 PING 10.244.0.17 (10.244.0.17) 56(84) bytes of data. 64 bytes from 10.244.0.17: icmp_seq=1 ttl=64 time=0.291 ms 64 bytes from 10.244.0.17: icmp_seq=2 ttl=64 time=0.081 ms ^C --- 10.244.0.17 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3000ms rtt min/avg/max/mdev = 0.055/0.129/0.291/0.094 ms
在現有的Flannel VxLAN網絡中,兩臺主機上的Pod間通訊,也是正常的,如master節點上的Pod訪問node01上的Pod:
[root@k8s-master ~]# kubectl exec -it nginx-5bd76bcc4f-pb257 -- /bin/bash root@nginx-5bd76bcc4f-pb257:/# ping 10.244.1.146 PING 10.244.1.146 (10.244.1.146) 56(84) bytes of data. 64 bytes from 10.244.1.146: icmp_seq=1 ttl=62 time=1.44 ms 64 bytes from 10.244.1.146: icmp_seq=2 ttl=62 time=0.713 ms 64 bytes from 10.244.1.146: icmp_seq=3 ttl=62 time=0.713 ms 64 bytes from 10.244.1.146: icmp_seq=4 ttl=62 time=0.558 ms ^C --- 10.244.1.146 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 0.558/0.858/1.448/0.346 ms
能夠看到容器跨主機是能夠正常通訊的,那麼容器的跨主機通訊是如何實現的呢?????master上查看路由表信息:
[root@k8s-master ~]# ip route ...... 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink ......
發送到10.244.1.0/24
和10.244.20/24
網段的數據報文發給本機的flannel.1接口,即進入二層隧道,而後對數據報文進行封裝(封裝VxLAN首部-->UDP首部-->IP首部-->以太網首部),到達目標Node節點後,由目標Node上的flannel.1進行解封裝。使用tcpdump
進行 抓一下包,以下:
#在宿主機和容器內都進行ping另一臺主機上的Pod ip並進行抓包 [root@k8s-master ~]# ping -c 10 10.244.1.146 [root@k8s-master ~]# kubectl exec -it nginx-5bd76bcc4f-pb257 -- /bin/bash root@nginx-5bd76bcc4f-pb257:/# ping 10.244.1.146 [root@k8s-master ~]# tcpdump -i flannel.1 -nn host 10.244.1.146 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes #宿主機ping後抓包狀況以下: 22:22:35.737977 IP 10.244.0.0 > 10.244.1.146: ICMP echo request, id 29493, seq 1, length 64 22:22:35.738902 IP 10.244.1.146 > 10.244.0.0: ICMP echo reply, id 29493, seq 1, length 64 22:22:36.739042 IP 10.244.0.0 > 10.244.1.146: ICMP echo request, id 29493, seq 2, length 64 22:22:36.739789 IP 10.244.1.146 > 10.244.0.0: ICMP echo reply, id 29493, seq 2, length 64 #容器ping後抓包狀況以下: 22:33:49.295137 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 837, seq 1, length 64 22:33:49.295933 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 837, seq 1, length 64 22:33:50.296736 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 837, seq 2, length 64 22:33:50.297222 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 837, seq 2, length 64 22:33:51.297556 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 837, seq 3, length 64 22:33:51.298054 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 837, seq 3, length 64 能夠看到報文都是通過flannel.1網絡接口進入2層隧道進而轉發
VXLAN是Linux內核自己支持的一種網絡虛擬化技術,是內核的一個模塊,在內核態實現封裝解封裝,構建出覆蓋網絡,其實就是一個由各宿主機上的Flannel.1設備組成的虛擬二層網絡。
因爲VXLAN因爲額外的封包解包,致使其性能較差,因此Flannel就有了host-gw模式,即把宿主機看成網關,除了本地路由以外沒有額外開銷,性能和calico差很少,因爲沒有疊加來實現報文轉發,這樣會致使路由表龐大。由於一個節點對應一個網絡,也就對應一條路由條目。
host-gw雖然VXLAN網絡性能要強不少。,可是種方式有個缺陷:要求各物理節點必須在同一個二層網絡中。物理節點必須在同一網段中。這樣會使得一個網段中的主機量會很是多,萬一發一個廣播報文就會產生干擾。在私有云場景下,宿主機不在同一網段是很常見的狀態,因此就不能使用host-gw了。
VXLAN還有另一種功能,VXLAN也支持相似host-gw的玩法,若是兩個節點在同一網段時使用host-gw通訊,若是不在同一網段中,即 當前pod所在節點與目標pod所在節點中間有路由器,就使用VXLAN這種方式,使用疊加網絡。 結合了Host-gw和VXLAN,這就是VXLAN的Direct routing模式
Flannel VxLAN的Direct routing模式配置
修改kube-flannel.yml文件,將flannel的configmap對象改成:
[root@k8s-master ~]# vim kube-flannel.yml ...... net-conf.json: | { "Network": "10.244.0.0/16", #默認網段 "Backend": { "Type": "vxlan", "Directrouting": true #增長 } } ...... [root@k8s-master ~]# kubectl apply -f kube-flannel.yml clusterrole.rbac.authorization.k8s.io/flannel configured clusterrolebinding.rbac.authorization.k8s.io/flannel configured serviceaccount/flannel unchanged configmap/kube-flannel-cfg configured daemonset.extensions/kube-flannel-ds-amd64 created daemonset.extensions/kube-flannel-ds-arm64 created daemonset.extensions/kube-flannel-ds-arm created daemonset.extensions/kube-flannel-ds-ppc64le created daemonset.extensions/kube-flannel-ds-s390x created #查看路由信息 [root@k8s-master ~]# ip route ...... 10.244.1.0/24 via 192.168.56.12 dev eth0 10.244.2.0/24 via 192.168.56.13 dev eth0 ......
從上面的結果能夠看到,發往10.244.1.0/24
和10.244.1.0/24
的包都是直接通過eth0
網絡接口直接發出去的,這就是Directrouting。若是兩個節點是跨網段的,則flannel自動降級爲VxLAN模式。
此時,在各 個 集羣節點上執行「iptables -nL」
命令 能夠 看到, iptables filter 表 的 FORWARD 鏈 上 由其 生成 了 以下 兩條 轉發 規則, 它 顯 式 放行 了10. 244. 0. 0/ 16
網絡 進出 的 全部 報文, 用於 確保 由 物理 接口 接收 或 發送 的 目標 地址 或 源 地址 爲10. 244. 0. 0/ 16
網絡 的 全部 報文 均能 夠 正常 通行。 這些 是 Direct Routing 模式 得以 實現 的 必要條件:
target prot opt source destination ACCEPT all -- 10. 244. 0. 0/ 16 0. 0. 0. 0/ 0 ACCEPT all -- 0. 0. 0. 0/ 0 10. 244. 0. 0/ 16
再在此以前建立的Pod和宿主機上進行ping測試,能夠看到在flannel.1接口上已經抓不到包了,在eth0上能夠用抓到ICMP的包,以下:
[root@k8s-master ~]# tcpdump -i flannel.1 -nn host 10.244.1.146 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes ^C 0 packets captured 0 packets received by filter 0 packets dropped by kernel [root@k8s-master ~]# tcpdump -i eth0 -nn host 10.244.1.146 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 22:48:52.376393 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 839, seq 1, length 64 22:48:52.376877 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 839, seq 1, length 64 22:48:53.377005 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 839, seq 2, length 64 22:48:53.377621 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 839, seq 2, length 64 22:50:28.647490 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 46141, seq 1, length 64 22:50:28.648320 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 46141, seq 1, length 64 22:50:29.648958 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 46141, seq 2, length 64 22:50:29.649380 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 46141, seq 2, length 64
Flannel除了上面2種數據傳輸的方式之外,還有一種是host-gw
的方式,host-gw
後端是經過添加必要的路由信息使用節點的二層網絡直接發送Pod的通訊報文。它的工做方式相似於Directrouting的功能,可是其並不具有VxLan的隧道轉發能力。
編輯kube-flannel的配置清單,將ConfigMap資源kube-flannel-cfg的data字段中網絡配置進行修改,以下:
[root@k8s-master ~]# vim kube-flannel.yml ...... net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "host-gw" } } ...... [root@k8s-master ~]# kubectl apply -f kube-flannel.yml
配置完成後,各節點會生成相似directrouting同樣的 路由和iptables規則,用於實現二層轉發Pod網絡的通訊報文,省去了隧道轉發模式的額外開銷。可是存在的問題點是,對於不在同一個二層網絡的報文轉發,host-gw
是沒法實現的。延續上面的例子,進行抓包查看:
master上的Pod:10.244.0.17向node01節點上的Pod:10.244.1.146發送ICMP報文
#查看路由表信息,能夠看到其報文的發送方向都是和Directrouting是同樣的 [root@k8s-master ~]# ip route ...... 10.244.1.0/24 via 192.168.56.12 dev eth0 10.244.2.0/24 via 192.168.56.13 dev eth0 ..... #進行ping包測試 [root@k8s-master ~]# ping -c 2 10.244.1.146 #在eth0上進行抓包 [root@k8s-master ~]# tcpdump -i eth0 -nn host 10.244.1.146 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 23:11:05.556972 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 59528, seq 1, length 64 23:11:05.557794 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 59528, seq 1, length 64 23:11:06.558231 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 59528, seq 2, length 64 23:11:06.558610 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 59528, seq 2, length 64
該模式下,報文轉發的相關流程以下:
一、Pod(10.244.0.17)向Pod(10.244.1.146)發送報文,查看到報文中的目的地址爲:10.244.1.146,並不是本地網段,會直接發送到網關(192.168.56.11);
二、網關發現該目標地址爲10.244.1.146,要到達10.244.1.0/24網段,須要送達到node2 的物理網卡,node2接收之後發現該報文的目標地址屬於本機上的另外一個虛擬網卡,而後轉發到相對應的Pod(10.244.1.146)
工做模式流程圖以下:
以上就是Flannel網絡模型的三種工做模式,可是flannel自身並不具有爲Pod網絡實現網絡策略和網絡通訊隔離的功能,爲此只能藉助於Calico聯合統一的項目Calnal項目進行構建網絡策略的功能。
網絡策略(Network Policy )是 Kubernetes 的一種資源。Network Policy 經過 Label 選擇 Pod,並指定其餘 Pod 或外界如何與這些 Pod 通訊。
Pod的網絡流量包含流入(Ingress)和流出(Egress)兩種方向。默認狀況下,全部 Pod 是非隔離的,即任何來源的網絡流量都可以訪問 Pod,沒有任何限制。當爲 Pod 定義了 Network Policy,只有 Policy 容許的流量才能訪問 Pod。
Kubernetes的網絡策略功能也是由第三方的網絡插件實現的,所以,只有支持網絡策略功能的網絡插件才能進行配置網絡策略,好比Calico、Canal、kube-router等等。
PS:Kubernetes自1.8版本才支持Egress網絡策略,在該版本以前僅支持Ingress網絡策略。
Calico能夠獨立地爲Kubernetes提供網絡解決方案和網絡策略,也能夠和flannel相結合,由flannel提供網絡解決方案,Calico僅用於提供網絡策略,此時將Calico稱爲Canal。結合flannel工做時,Calico提供的默認配置清單式以flannel默認使用的10.244.0.0/16爲Pod網絡,所以在集羣中kube-controller-manager啓動時就須要經過--cluster-cidr選項進行設置使用該網絡地址,而且---allocate-node-cidrs的值應設置爲true。
#設置RBAC [root@k8s-master ~]# kubectl apply -f https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/canal/rbac.yaml #部署Canal提供網絡策略 [root@k8s-master ~]# kubectl apply -f https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/canal/canal.yaml [root@k8s-master ~]# kubectl get ds canal -n kube-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE canal 3 3 0 3 0 beta.kubernetes.io/os=linux 2m 部署canal須要的鏡像,建議先拉取鏡像,避免耗死資源: quay.io/calico/node:v3.2.6 quay.io/calico/cni:v3.2.6 quay.io/coreos/flannel:v0.9.1 [root@k8s-master ~]# kubectl get pods -n kube-system -o wide |grep canal canal-2hqwt 3/3 Running 0 1h 192.168.56.11 k8s-master canal-c5pxr 3/3 Running 0 1h 192.168.56.13 k8s-node02 canal-kr662 3/3 Running 6 1h 192.168.56.12 k8s-node01
Canal
做爲DaemonSet
部署到每一個節點,屬於kube-system
這個名稱空間。須要注意的是,Canal
只是直接使用了Calico
和flannel
項目,代碼自己沒有修改,Canal
只是一種部署的模式,用於安裝和配置項目。
在Kubernetes系統中,報文的流入和流出的核心組件是Pod資源,它們也是網絡策略功能的主要應用對象。NetworkPolicy
對象經過podSelector
選擇 一組Pod資源做爲控制對象。NetworkPolicy
是定義在一組Pod資源之上用於管理入站流量,或出站流量的一組規則,有能夠是出入站規則一塊兒生效,規則的生效模式一般由spec.policyTypes
進行 定義。以下圖:
默認狀況下,Pod對象的流量控制是爲空的,報文能夠自由出入。在附加網絡策略以後,Pod對象會由於NetworkPolicy而被隔離,一旦名稱空間中有任何NetworkPolicy對象匹配了某特定的Pod對象,則該Pod將拒絕NetworkPolicy規則中不容許的全部鏈接請求,可是那些未被匹配到的Pod對象依舊能夠接受全部流量。
就特定的Pod集合來講,入站和出站流量默認是放行狀態,除非有規則能夠進行匹配。還有一點須要注意的是,在spec.policyTypes
中指定了生效的規則類型,可是在networkpolicy.spec
字段中嵌套定義了沒有任何規則的Ingress或Egress時,則表示拒絕入站或出站的一切流量。定義網絡策略的基本格式以下:
apiVersion: networking.k8s.io/v1 #定義API版本 kind: NetworkPolicy #定義資源類型 metadata: name: allow-myapp-ingress #定義NetwokPolicy的名字 namespace: default spec: #NetworkPolicy規則定義 podSelector: #匹配擁有標籤app:myapp的Pod資源 matchLabels: app: myapp policyTypes ["Ingress"] #NetworkPolicy類型,能夠是Ingress,Egress,或者二者共存 ingress: #定義入站規則 - from: - ipBlock: #定義能夠訪問的網段 cidr: 10.244.0.0/16 except: #排除的網段 - 10.244.3.0/24 - podSelector: #選定當前default名稱空間,標籤爲app:myapp能夠入站 matchLabels: app: myapp ports: #開放的協議和端口定義 - protocol: TCP port: 80 該網絡策略就是將default名稱空間中擁有標籤"app=myapp"的Pod資源開放80/TCP端口給10.244.0.0/16網段,並排除10.244.3.0/24網段的訪問,而且也開放給標籤爲app=myapp的全部Pod資源進行訪問。
爲了看出Network Policy的效果,先部署一個httpd的應用。配置清單文件以下:
[root@k8s-master ~]# mkdir network-policy-demo [root@k8s-master ~]# cd network-policy-demo/ [root@k8s-master network-policy-demo]# vim httpd.yaml apiVersion: apps/v1beta1 kind: Deployment metadata: name: httpd spec: replicas: 3 template: metadata: labels: run: httpd spec: containers: - name: httpd image: httpd:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: httpd-svc spec: type: NodePort selector: run: httpd ports: - protocol: TCP nodePort: 30000 port: 8080 targetPort: 80
建立三個副本,經過NodePort類型的Service對外方服務,部署應用:
[root@k8s-master network-policy-demo]# kubectl apply -f httpd.yaml deployment.apps/httpd unchanged service/httpd-svc created [root@k8s-master network-policy-demo]# kubectl get pods -o wide |grep httpd httpd-75f655479d-882hz 1/1 Running 0 4m 10.244.0.2 k8s-master httpd-75f655479d-h7lrr 1/1 Running 0 4m 10.244.2.2 k8s-node02 httpd-75f655479d-kzr5g 1/1 Running 0 4m 10.244.1.2 k8s-node01 [root@k8s-master network-policy-demo]# kubectl get svc httpd-svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE httpd-svc NodePort 10.99.222.179 <none> 8080:30000/TCP 4m
當前沒有定義任何Network Policy,驗證應用的訪問:
#啓動一個busybox Pod,能夠訪問Service,也能夠ping副本的Pod [root@k8s-master ~]# kubectl run busybox --rm -it --image=busybox /bin/sh If you don't see a command prompt, try pressing enter. / # wget httpd-svc:8080 Connecting to httpd-svc:8080 (10.99.222.179:8080) index.html 100% |*********************************************************************************************| 45 0:00:00 ETA / # ping -c 2 10.244.1.2 PING 10.244.1.2 (10.244.1.2): 56 data bytes 64 bytes from 10.244.1.2: seq=0 ttl=63 time=0.507 ms 64 bytes from 10.244.1.2: seq=1 ttl=63 time=0.228 ms --- 10.244.1.2 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.228/0.367/0.507 ms #集羣節點也能夠訪問Sevice和ping通副本Pod [root@k8s-node01 ~]# curl 10.99.222.179:8080 <html><body><h1>It works!</h1></body></html> [root@k8s-node01 ~]# ping -c 2 10.244.2.2 PING 10.244.2.2 (10.244.2.2) 56(84) bytes of data. 64 bytes from 10.244.2.2: icmp_seq=1 ttl=63 time=0.931 ms 64 bytes from 10.244.2.2: icmp_seq=2 ttl=63 time=0.812 ms --- 10.244.2.2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 0.812/0.871/0.931/0.066 ms #集羣外部訪問192.168.56.11:30000也是通的 [root@localhost ~]# curl 192.168.56.11:30000 <html><body><h1>It works!</h1></body></html>
那麼下面再去設置不一樣的Network Policy來管控Pod的訪問。
NetworkPolicy資源屬於名稱空間級別,它的做用範圍爲其所屬的名稱空間。
一、設置默認的Ingress策略
用戶能夠建立一個NetworkPolicy來爲名稱空間設置一個默認的隔離策略,該策略選擇全部的Pod對象,而後容許或拒絕任何到達這些Pod的入站流量,以下:
[root@k8s-master network-policy-demo]# vim policy-demo.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-ingress spec: podSelector: {} policyTypes: ["Ingress"] #指明瞭Ingress生效規則,但不定義任何Ingress字段,所以不能匹配任何源端點,從而拒絕全部的入站流量 [root@k8s-master network-policy-demo]# kubectl apply -f policy-demo.yaml networkpolicy.networking.k8s.io/deny-all-ingress created [root@k8s-master network-policy-demo]# kubectl get networkpolicy NAME POD-SELECTOR AGE deny-all-ingress <none> 11s #此時再去訪問測試,是沒法ping通,沒法訪問的 [root@k8s-master ~]# kubectl run busybox --rm -it --image=busybox /bin/sh If you don't see a command prompt, try pressing enter. / # wget httpd-svc:8080 Connecting to httpd-svc:8080 (10.99.222.179:8080) wget: can't connect to remote host (10.99.222.179): Connection timed out
若是要將默認策略設置爲容許全部入站流量,只須要定義Ingress字段,並將這個字段設置爲空,以匹配全部源端點,但自己不設定網絡策略,就已是默認容許全部入站流量訪問的,下面給出一個定義的格式:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-ingress spec: podSelector: {} policyTypes: ["Ingress"] ingress: - {}
實踐中,一般將默認的網絡策略設置爲拒絕全部入站流量,而後再放行容許的源端點的入站流量。
二、放行特定的入站流量
[root@k8s-master network-policy-demo]# vim policy-demo.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: access-httpd spec: podSelector: matchLabels: run: httpd policyTypes: ["Ingress"] ingress: - from: - ipBlock: cidr: 10.244.0.0/16 except: - 10.244.2.0/24 - 10.244.1.0/24 - podSelector: matchLabels: access: "true" ports: - protocol: TCP port: 80 [root@k8s-master network-policy-demo]# kubectl apply -f policy-demo.yaml networkpolicy.networking.k8s.io/access-httpd created [root@k8s-master network-policy-demo]# kubectl get networkpolicy NAME POD-SELECTOR AGE access-httpd run=httpd 6s
驗證NetworkPolicy的有效性:
#建立帶有標籤的busybox pod訪問,是能夠正常訪問的,可是由於僅開放了TCP協議,因此PING是沒法ping通的 [root@k8s-master ~]# kubectl run busybox --rm -it --labels="access=true" --image=busybox /bin/sh If you don't see a command prompt, try pressing enter. / # wget httpd-svc:8080 Connecting to httpd-svc:8080 (10.99.222.179:8080) index.html 100% |*********************************************************************************************| 45 0:00:00 ETA / # ping -c 3 10.244.0.2 PING 10.244.0.2 (10.244.0.2): 56 data bytes --- 10.244.0.2 ping statistics --- 3 packets transmitted, 0 packets received, 100% packet loss
一般,出站的流量默認策略應該是容許經過的,可是當有精細化需求,僅放行那些有對外請求須要的Pod對象的出站流量,也能夠先爲名稱空間設置「禁止全部」的默認策略,再細化制定准許的策略。networkpolicy.spec
中嵌套的Egress字段用來定義出站流量規則。
一、設定默認Egress策略
和Igress同樣,只須要經過policyTypes
字段指明生效的Egress
類型規則,而後不去定義Egress字段,就不會去匹配到任何目標端點,從而拒絕全部的出站流量。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-egress spec: podSelector: {} policyTypes: ["Egress"]
實踐中,須要進行嚴格隔離的環境一般將默認的策略設置爲拒絕全部出站流量,再去細化配置容許到達的目標端點的出站流量。
二、放行特定的出站流量
下面舉個例子定義一個Egress規則,對標籤run=httpd
的Pod對象,到達標籤爲access=true
的Pod對象的80端口的流量進行放行。
[root@k8s-master network-policy-demo]# vim egress-policy.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: httpd-egress spec: podSelector: matchLabels: run: httpd policyTypes: ["Egress"] egress: - to: - podSelector: matchLabels: access: "true" ports: - protocol: TCP port: 80 #NetworkPolicy檢測,一個帶有access=true標籤,一個不帶 [root@k8s-master ~]# kubectl run busybox --rm -it --labels="access=true" --image=busybox /bin/sh If you don't see a command prompt, try pressing enter. / # wget httpd-svc:8080 Connecting to httpd-svc:8080 (10.99.222.179:8080) index.html 100% |*********************************************************************************************| 45 0:00:00 ETA / # exit Session ended, resume using 'kubectl attach busybox-686cb649b6-6j4qx -c busybox -i -t' command when the pod is running deployment.apps "busybox" deleted [root@k8s-master ~]# kubectl run busybox2 --rm -it --image=busybox /bin/sh If you don't see a command prompt, try pressing enter. / # wget httpd-svc:8080 Connecting to httpd-svc:8080 (10.99.222.179:8080) wget: can't connect to remote host (10.99.222.179): Connection timed out
從上面的檢測結果能夠看到,帶有標籤access=true
的Pod才能訪問到httpd-svc
,說明上面配置的Network Policy已經生效。
實踐中,一般須要彼此隔離全部的名稱空間,可是又須要容許它們能夠和kube-system
名稱空間中的Pod資源進行流量交換,以實現監控和名稱解析等各類管理功能。下面的配置清單示例在default
名稱空間定義相關規則,在出站和入站都默認均爲拒絕的狀況下,它用於放行名稱空間內部的各Pod對象之間的通訊,以及和kube-system
名稱空間內各Pod間的通訊。
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-allow 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"]
須要注意的是,有一些額外的系統附件可能會單獨部署到獨有的名稱空間中,好比將prometheus
監控系統部署到prom名稱空間等,這類具備管理功能的附件所在的名稱空間和每個特定的名稱空間的出入流量也是須要被放行的。