目录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名称空间等,这类具备管理功能的附件所在的名称空间和每个特定的名称空间的出入流量也是须要被放行的。