Kubernetes之(十七)网络模型和网络策略

Kubernetes之(十七)网络模型和网络策略

Kubernetes网络模型和CNI插件

在Kubernetes中设计了一种网络模型,要求无论容器运行在集群中的哪个节点,所有容器都能通过一个扁平的网络平面进行通信,即在同一IP网络中。需要注意的是:在K8S集群中,IP地址分配是以Pod对象为单位,而非容器,同一Pod内的所有容器共享同一网络名称空间。

Docker网络模型

Docker容器的原生网络模型主要有3种:Bridge(桥接)、Host(主机)、none。

  • Bridge:借助虚拟网桥设备为容器建立网络连接。
  • Host:设置容器直接共享当前节点主机的网络名称空间。
  • none:多个容器共享同一个网络名称空间。
[root@master ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
ac364a575cb8        bridge              bridge              local
e3bdb1c29df6        host                host                local
4161accaf430        none                null                local
#none网络,在该网络下的容器仅有lo网卡,属于封闭式网络,通常用于对安全性要求较高并且不需要联网的应用
#host网络,共享宿主机的网络名称空间,容器网络配置和host一致,但是存在端口冲突的问题
#bridge网络,Docker安装完成时会创建一个名为docker0的linux bridge,不指定网络时,创建的网络默认为桥接网络,都会桥接到docker0上。

桥接式网络是目前较为流行和默认的解决方案。但是这种方案的弊端是无法跨主机通信的,仅能在宿主机本地进行,而解决该问题的方法就是NAT。所有接入到该桥接设备上的容器都会被NAT隐藏,它们发往Docker主机外部的所有流量都会经过源地址转换后发出,并且默认是无法直接接受节点之外的其他主机发来的请求。当需要接入Docker主机外部流量,就需要进行目标地址转换甚至端口转换将其暴露在外部网络当中。如下图:

容器内的属于私有地址,需要在左侧的主机上的eth0上进行源地址转换,而右侧的地址需要被访问,就需要将eth0的地址进行NAT转换。SNAT---->DNAT
这样的通信方式会比较麻烦,从而需要借助第三方的网络插件实现这样的跨主机通信的网络策略。
容器内的属于私有地址,需要在左侧的主机上的eth0上进行源地址转换,而右侧的地址需要被访问,就需要将eth0的地址进行NAT转换。SNAT---->DNAT
这样的通信方式会比较麻烦,从而需要借助第三方的网络插件实现这样的跨主机通信的网络策略。

Kubernetes网络模型

我们知道的是,在K8S上的网络通信包含以下几类:

  • 容器间的通信:同一个Pod内的多个容器间的通信,它们之间通过lo网卡进行通信。
  • 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@master ~]#  cat /etc/cni/net.d/10-flannel.conflist 
{
  "name": "cbr0",
  "plugins": [
    {
      "type": "flannel",
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": true
      }
    },
    {
      "type": "portmap",
      "capabilities": {
        "portMappings": true
      }
    }
  ]
}

​ CNI主要是定义容器网络模型规范,链接容器管理系统和网络插件,两者主要通过上面的JSON格式文件进行通信,实现容器的网络功能。CNI的主要核心是:在创建容器时,先创建好网络名称空间(netns),然后调用CNI插件为这个netns配置网络,最后在启动容器内的进程
常见的CNI网络插件包含以下几种:

  • Flannel:为Kubernetes提供叠加网络的网络插件,基于TUN/TAP隧道技术,使用UDP封装IP报文进行创建叠 加网络,借助etcd维护网络的分配情况,缺点:无法支持网络策略访问控制。
  • Calico:基于BGP的三层网络插件,也支持网络策略进而实现网络的访问控制;它在每台主机上都运行一个虚拟路由,利用Linux内核转发网络数据包,并借助iptables实现防火墙功能。实际上Calico最后的实现就是将每台主机都变成了一台路由器,将各个网络进行连接起来,实现跨主机通信的功能。
  • Canal:由Flannel和Calico联合发布的一个统一网络插件,提供CNI网络插件,并支持网络策略实现。
  • 其他的还包括Weave Net、Contiv、OpenContrail、Romana、NSX-T、kube-router等等。而Flannel和Calico是目前最流行的选择方案。

Flannel网络插件

在各节点上的Docker主机在docker0上默认使用同一个子网,不同节点的容器都有可能会获取到相同的地址,那么在跨节点通信时就会出现地址冲突的问题。并且在多个节点上的docker0使用不同的子网,也会因为没有准确的路由信息导致无法准确送达报文。
而为了解决这一问题,Flannel的解决办法是,预留一个使用网络,如10.244.0.0/16,然后自动为每个节点的Docker容器引擎分配一个子网,如10.244.1.0/24和10.244.2.0/24,并将分配信息保存在etcd持久存储。
第二个问题的解决,Flannel是采用不同类型的后端网络模型进行处理。其后端的类型有以下几种:

  • VxLAN:使用内核中的VxLAN模块进行封装报文。也是flannel推荐的方式.
  • host-gw:即Host GateWay,通过在节点上创建目标容器地址的路由直接完成报文转发,要求各节点必须在同一个2层网络,对报文转发性能要求较高的场景使用。
  • UDP:使用普通的UDP报文封装完成隧道转发。

VxLAN后端和direct routing

VxLAN(Virtual extensible Local Area Network)虚拟可扩展局域网,采用MAC in UDP封装方式,具体的实现方式为:

  1. 将虚拟网络的数据帧添加到VxLAN首部,封装在物理网络的UDP报文中
  2. 以传统网络的通信方式传送该UDP报文
  3. 到达目的主机后,去掉物理网络报文的头部信息以及VxLAN首部,并交付给目的终端

跨节点的Pod之间的通信就是以上的一个过程,整个过程中通信双方对物理网络是没有感知的。如下网络图:

flannel运行后,在各Node宿主机多了一个网络接口:

#master
[root@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::bc1b:fdff:fece:7506  prefixlen 64  scopeid 0x20<link>
        ether be:1b:fd:ce:75:06  txqueuelen 0  (Ethernet)
        RX packets 1274  bytes 1105723 (1.0 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 668  bytes 275033 (268.5 KiB)
        TX errors 0  dropped 8 overruns 0  carrier 0  collisions 0
		
#node01
[root@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::10f0:d4ff:fe41:bd69  prefixlen 64  scopeid 0x20<link>
        ether 12:f0:d4:41:bd:69  txqueuelen 0  (Ethernet)
        RX packets 2867  bytes 280059 (273.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2886  bytes 353550 (345.2 KiB)
        TX errors 0  dropped 8 overruns 0  carrier 0  collisions 0

#node02
[root@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::e8de:4ff:fe2a:cadc  prefixlen 64  scopeid 0x20<link>
        ether ea:de:04:2a:ca:dc  txqueuelen 0  (Ethernet)
        RX packets 3512  bytes 605029 (590.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3386  bytes 1333288 (1.2 MiB)
        TX errors 0  dropped 8 overruns 0  carrier 0  collisions 0

从上面的结果可以知道 :

  1. flannel默认就是VXLAN模式,即Overlay Network。
  2. flanneld创建了一个flannel.1接口,它是专门用来封装隧道协议的,默认分给集群的Pod网段为10.244.0.0/16。
  3. flannel给k8s-master节点配置的Pod网络为10.244.0.0段,给k8s-node01节点配置的Pod网络为10.244.1.0段,给k8s-node01节点配置的Pod网络为10.244.2.0段,如果有更多的节点,以此类推。

举个实际例子

#启动一个nginx容器,副本为3
[root@master manifests]#  kubectl run nginx --image=nginx:1.14-alpine --port=80 --replicas=3

[root@master manifests]# kubectl get pods -o wide |grep nginx
nginx-7849c4bbcd-8srph   1/1     Running   0          22s     10.244.1.62   node01   <none>           <none>
nginx-7849c4bbcd-brsrv   1/1     Running   0          22s     10.244.2.74   node02   <none>           <none>
nginx-7849c4bbcd-vjh4w   1/1     Running   0          22s     10.244.2.73   node02   <none>           <none>

查看网络接口可以发现在各个节点上多了一个虚拟接口cni0,其ip地址为10.244.0.1。它是由flanneld创建的一个虚拟网桥叫cni0,在Pod本地通信使用。 这里需要注意的是,cni0虚拟网桥,仅作用于本地通信.

[root@node02 ~]# ifconfig cni0
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.244.2.1  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::3c3d:dcff:fe84:c3a4  prefixlen 64  scopeid 0x20<link>
        ether 0a:58:0a:f4:02:01  txqueuelen 1000  (Ethernet)
        RX packets 125902  bytes 13768322 (13.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 128191  bytes 12079793 (11.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

容器跨主机是可以正常通信的,那么容器的跨主机通信是如何实现的呢?????master上查看路由表信息:

[root@master manifests]# ip route
default via 10.0.0.2 dev ens33 proto static metric 100 
10.0.0.0/24 dev ens33 proto kernel scope link src 10.0.0.10 metric 100 
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 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
#在宿主机和容器内都进行ping另外一台主机上的Pod ip并进行抓包
[root@master manifests]# kubectl exec nginx-7849c4bbcd-8srph -it -- /bin/sh  #进入node01的10.244.1.62 IP的pod内

[root@master ~]# kubectl exec -it nginx-7849c4bbcd-brsrv -- /bin/sh #另外开终端进入node02的10.244.2.74 IP的pod内

#使用node01的pod  ping10.244.2.74 在node02节点抓包

[root@node02 ~]#  tcpdump -i flannel.1 -nn host 10.244.2.74
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes
17:16:06.779840 IP 10.244.1.62 > 10.244.2.74: ICMP echo request, id 2816, seq 66, length 64
17:16:06.779904 IP 10.244.2.74 > 10.244.1.62: ICMP echo reply, id 2816, seq 66, length 64
17:16:07.780045 IP 10.244.1.62 > 10.244.2.74: ICMP echo request, id 2816, seq 67, length 64
17:16:07.780080 IP 10.244.2.74 > 10.244.1.62: ICMP echo reply, id 2816, seq 67, length 64
17:16:08.780127 IP 10.244.1.62 > 10.244.2.74: ICMP echo request, id 2816, seq 68, length 64
17:16:08.780173 IP 10.244.2.74 > 10.244.1.62: ICMP echo reply, id 2816, seq 68, length 64
17:16:09.780576 IP 10.244.1.62 > 10.244.2.74: ICMP echo request, id 2816, seq 69, length 64

#使用master节点ping 10.244.2.74 在node02节点抓包
[root@node02 ~]#  tcpdump -i flannel.1 -nn host 10.244.2.74
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes
17:17:51.003946 IP 10.244.0.0 > 10.244.2.74: ICMP echo request, id 30700, seq 4, length 64
17:17:51.004017 IP 10.244.2.74 > 10.244.0.0: ICMP echo reply, id 30700, seq 4, length 64
17:17:52.004634 IP 10.244.0.0 > 10.244.2.74: ICMP echo request, id 30700, seq 5, length 64
17:17:52.004688 IP 10.244.2.74 > 10.244.0.0: ICMP echo reply, id 30700, seq 5, length 64
17:17:53.005045 IP 10.244.0.0 > 10.244.2.74: ICMP echo request, id 30700, seq 6, length 64
17:17:53.005098 IP 10.244.2.74 > 10.244.0.0: ICMP echo reply, id 30700, seq 6, length 64
17:17:54.005302 IP 10.244.0.0 > 10.244.2.74: ICMP echo request, id 30700, seq 7, length 64
17:17:54.005359 IP 10.244.2.74 > 10.244.0.0: ICMP echo reply, id 30700, seq 7, length 64

#可以看到报文都是经过flannel.1网络接口进入2层隧道进而转发

发送到10.244.1.0/24和10.244.20/24网段的数据报文发给本机的flannel.1接口,即进入二层隧道,然后对数据报文进行封装(封装VxLAN首部-->UDP首部-->IP首部-->以太网首部),到达目标Node节点后,由目标Node上的flannel.1进行解封装。
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模式

Direct routing模式配置

修改kube-flannel.yml文件,将flannel的configmap对象改为:

[root@master manifests]# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml #下载原版配置文件进行修改后apply执行

[root@master manifests]# vim kube-flannel.yml
......
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan",  #注意,号
        "Directrouting": true  #增加字段
      }
    }
......


#此时需要删除flannel网络后重建,但是会影响所有pod,生产中不建议这么使用kubectl delete -f kube-flannel.yaml ,kubectl apply -f kube-flannel.yaml 
#查看已经生成相应规则
[root@master manifests]# ip route show
default via 10.0.0.2 dev ens33 proto static metric 100 
10.0.0.0/24 dev ens33 proto kernel scope link src 10.0.0.10 metric 100 
10.244.1.0/24 via 10.0.0.11 dev ens33 
10.244.2.0/24 via 10.0.0.12 dev ens33 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 

配置完成后,各节点会生成类似directrouting一样的 路由和iptables规则,用于实现二层转发Pod网络的通信报文,省去了隧道转发模式的额外开销。但是存在的问题点是,对于不在同一个二层网络的报文转发,host-gw是无法实现的。延续上面的例子,进行抓包查看:

[root@master manifests]# kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
myapp-deploy-65df64765c-85k8x   1/1     Running   0          28s   10.244.1.66   node01   <none>           <none>
myapp-deploy-65df64765c-8trf2   1/1     Running   0          28s   10.244.1.65   node01   <none>           <none>
myapp-deploy-65df64765c-cxgq4   1/1     Running   0          28s   10.244.2.78   node02   <none>           <none>
myapp-deploy-65df64765c-l646w   1/1     Running   0          28s   10.244.2.79   node02   <none>           <none>
pod-demo                        2/2     Running   0          43s   10.244.2.77   node02   <none>           <none>

#进入myapp-deploy-65df64765c-85k8x 容器ping另一个节点的容器10.244.2.79

[root@master manifests]# kubectl exec -it myapp-deploy-65df64765c-85k8x -- /bin/sh
/ # ping 10.244.2.79
PING 10.244.2.79 (10.244.2.79): 56 data bytes
64 bytes from 10.244.2.79: seq=0 ttl=62 time=2.812 ms
64 bytes from 10.244.2.79: seq=1 ttl=62 time=0.398 ms
64 bytes from 10.244.2.79: seq=2 ttl=62 time=0.362 ms

#node02节点抓包
[root@node02 ~]# tcpdump -i ens33 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
08:56:37.256880 IP 10.244.1.66 > 10.244.2.79: ICMP echo request, id 2816, seq 38, length 64
08:56:37.256948 IP 10.244.2.79 > 10.244.1.66: ICMP echo reply, id 2816, seq 38, length 64
08:56:38.256295 IP 10.244.1.66 > 10.244.2.79: ICMP echo request, id 2816, seq 39, length 64
08:56:38.256371 IP 10.244.2.79 > 10.244.1.66: ICMP echo reply, id 2816, seq 39, length 64
08:56:39.255704 IP 10.244.1.66 > 10.244.2.79: ICMP echo request, id 2816, seq 40, length 64

从上面的结果可以看到,发往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模式得以实现的必要条件:
再在此之前创建的Pod和宿主机上进行ping测试,可以看到在flannel.1接口上已经抓不到包了,在eth0上可以用抓到ICMP的包

Host-gw后端

​ Flannel除了上面2种数据传输的方式以外,还有一种是host-gw的方式,host-gw后端是通过添加必要的路由信息使用节点的二层网络直接发送Pod的通信报文。它的工作方式类似于Directrouting的功能,但是其并不具备VxLan的隧道转发能力。

​ 编辑kube-flannel的配置清单,将ConfigMap资源kube-flannel-cfg的data字段中网络配置进行修改,如下:

[root@master ~]# vim kube-flannel.yml
......
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "host-gw"
      }
    }
......

配置完成后,各节点会生成类似directrouting一样的 路由和iptables规则,用于实现二层转发Pod网络的通信报文,省去了隧道转发模式的额外开销。但是存在的问题点是,对于不在同一个二层网络的报文转发,host-gw是无法实现的

[root@master manifests]# ip route
default via 10.0.0.2 dev ens33 proto static metric 100 
10.0.0.0/24 dev ens33 proto kernel scope link src 10.0.0.10 metric 100 
10.244.1.0/24 via 10.0.0.11 dev ens33 
10.244.2.0/24 via 10.0.0.12 dev ens33 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 

/ # ping -c 2 10.244.1.66 
PING 10.244.1.66 (10.244.1.66): 56 data bytes
64 bytes from 10.244.1.66: seq=0 ttl=62 time=0.433 ms
64 bytes from 10.244.1.66: seq=1 ttl=62 time=0.334 ms

--- 10.244.1.66 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.334/0.383/0.433 ms

[root@master mainfest]# tcpdump -i ens33 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
09:09:39.496407 IP 10.244.2.78 > 10.244.1.66: ICMP echo request, id 3072, seq 0, length 64
09:09:39.496588 IP 10.244.1.66 > 10.244.2.78: ICMP echo reply, id 3072, seq 0, length 64
09:09:40.497129 IP 10.244.2.78 > 10.244.1.66: ICMP echo request, id 3072, seq 1, length 64
09:09:40.497356 IP 10.244.1.66 > 10.244.2.78: ICMP echo reply, id 3072, seq 1, length 64

该模式下,报文转发的相关流程如下:

  1. Pod10.244.2.78向node02的Pod10.244.1.66发送报文,查看到报文中的目的地址为10.244.1.66,并非本地网段,会直接发送到网关10.0.0.12
  2. 网关发现目标的地址为10.244.1.66,要到达10.244.1.0/24网段,需要送达到node01的物理网卡,node01接收以后发现该报文的目标地址属于本机上的另一个虚拟网卡,然后转发到相对应的Pod10.244.1.66

以上就是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等等。

部署Canal提供网络策略功能

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。

#下载文件到本地
[root@master calico]# wget https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/canal/rbac.yaml
[root@master calico]# wget https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/canal/canal.yaml

#应用
[root@master calico]# kubectl apply -f rbac.yaml 
[root@master calico]# kubectl apply -f canal.yaml 

Canal作为DaemonSet部署到每个节点,属于kube-system这个名称空间。需要注意的是,Canal只是直接使用了Calico和flannel项目,代码本身没有修改,Canal只是一种部署的模式,用于安装和配置项目。

[root@master calico]# kubectl get pods -n kube-system -w
NAME                                   READY   STATUS              RESTARTS   AGE
canal-nbspn                            0/3     ContainerCreating   0          2m16s
canal-pj6rx                            2/3     Running             0          2m16s
canal-rgsnp                            3/3     Running             0          2m16s

配置策略

在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资源进行访问。

Ingress管控

创建

[root@master manifests]# mkdir networkpolicy
#创建名称空间 dev  prod
[root@master manifests]# kubectl create namespace dev
namespace/dev created
[root@master manifests]# kubectl create namespace prod
namespace/prod created
[root@master manifests]# kubectl get ns
NAME            STATUS   AGE
default         Active   12d
dev             Active   6s
ingress-nginx   Active   7d16h
kube-public     Active   12d
kube-system     Active   12d
prod            Active   3s

#定义规则
[root@master manifests]# cd networkpolicy/
[root@master networkpolicy]# vim ingress-def.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress


#执行 指定在dev名称空间生效
[root@master networkpolicy]# kubectl apply -f ingress-def.yaml -n dev
networkpolicy.networking.k8s.io/deny-all-ingress created

[root@master networkpolicy]# kubectl get netpol -n dev
NAME               POD-SELECTOR   AGE
deny-all-ingress   <none>         33s

运行Pod

[root@master networkpolicy]# vim pod-a.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1

[root@master networkpolicy]# kubectl apply -f pod-a.yaml -n dev
pod/pod1 created
[root@master networkpolicy]# kubectl get pods -n dev -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          21s   10.244.2.3   node02   <none>           <none>

尝试访问刚刚创建的pod1

[root@master networkpolicy]# curl 10.244.2.3
curl: (7) Failed connect to 10.244.2.3:80; 连接超时

尝试访问在prod名称空间的pod1

[root@master networkpolicy]# kubectl apply -f pod-a.yaml -n prod
pod/pod1 created
[root@master networkpolicy]# kubectl get pods -o wide -n prod
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          11s   10.244.2.4   node02   <none>           <none>
[root@master networkpolicy]# curl 10.244.2.4
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

总结
此时dev名称空间使用了Ingress规则,但是为空,代表拒绝所有入站请求,而prod名称空间没有定义Ingress规则,允许所有访问。

定义dev名称空间可以对外被访问

[root@master networkpolicy]# vim ingress-def.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

#应用后尝试访问dev名称空间中的pod1
[root@master networkpolicy]# kubectl apply -f ingress-def.yaml  -n dev
networkpolicy.networking.k8s.io/deny-all-ingress configured
[root@master networkpolicy]# kubectl get pods -n dev -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          11m   10.244.2.3   node02   <none>           <none>
[root@master networkpolicy]# curl 10.244.2.3 
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

dev名称空间中的pod1可以被访问,因为增加了ingress规则 {}表示映射所有。

放行特定的入栈流量:
可以使用标签选择器来选择指定的一组pod来定义规则

#给pod1打标签
[root@master networkpolicy]# kubectl label pods pod1 app=myapp -n dev
pod/pod1 labeled

#定义
[root@master networkpolicy]# vim allow-netpol-demo.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: all-myapp-ingress
spec:
  podSelector:    
    matchLabels:   #使用标签选择器匹配
      app: myapp
  ingress:
  - from:
    - ipBlock: #允许网段
        cidr: 10.244.0.0/16
        except:  #排除IP  也可是网段
        - 10.244.1.88/32
    ports:  #定义入栈规则允许被访问的端口和协议
    - protocol: TCP
      port: 80


#应用
[root@master networkpolicy]# kubectl get netpol -n dev
NAME                  POD-SELECTOR   AGE
allow-myapp-ingress   app=myapp      38s
deny-all-ingress      <none>         26m

尝试访问dev中pod1的80和443端口,验证,(myapp没有443端口,如果被阻挡应提示连接超时,如果可以访问应提示 拒绝连接)

[root@master networkpolicy]# curl 10.244.2.3
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
[root@master networkpolicy]# curl 10.244.2.3:443
curl: (7) Failed connect to 10.244.2.3:443; 连接超时

Egress管控

通常,出站的流量默认策略应该是允许通过的,但是当有精细化需求,仅放行那些有对外请求需要的Pod对象的出站流量,也可以先为名称空间设置“禁止所有”的默认策略,再细化制定准许的策略。networkpolicy.spec中嵌套的Egress字段用来定义出站流量规则。

实践中,需要进行严格隔离的环境通常将默认的策略设置为拒绝所有出站流量,再去细化配置允许到达的目标端点的出站流量。

举例,禁止prod名称空间所有出站规则,入栈全部允许

#配置禁止出站规则
[root@master networkpolicy]# vim egress-def.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress

#应用并查看
[root@master networkpolicy]# kubectl apply -f egress-def.yaml -n prod
networkpolicy.networking.k8s.io/deny-all-egress created
[root@master networkpolicy]# kubectl get netpol -n prod
NAME              POD-SELECTOR   AGE
deny-all-egress   <none>         13s


#在prod内创建pod
[root@master networkpolicy]# kubectl get pods -n prod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          29m   10.244.2.4   node02   <none>           <none>

#进入pod1内部,尝试ping集群内dashboard的ip地址 10.244.2.72
[root@master networkpolicy]# kubectl exec -it pod1 -n prod -- /bin/sh
/ # ping 10.244.2.72
PING 10.244.2.72 (10.244.2.72): 56 data bytes
#无法ping通,现在修改配置清单允许所有出站流量
[root@master networkpolicy]# vim egress-def.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-egress
spec:
  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress

#应用后再次进入pod1内尝试pingdashboard的ip地址 10.244.2.72
[root@master networkpolicy]# kubectl apply -f egress-def.yaml -n prod
networkpolicy.networking.k8s.io/deny-all-egress configured

[root@master networkpolicy]# kubectl exec -it pod1 -n prod -- /bin/sh
/ # ping 10.244.2.72
PING 10.244.2.72 (10.244.2.72): 56 data bytes
64 bytes from 10.244.2.72: seq=144 ttl=63 time=0.189 ms
64 bytes from 10.244.2.72: seq=145 ttl=63 time=0.084 ms
64 bytes from 10.244.2.72: seq=146 ttl=63 time=0.086 ms
64 bytes from 10.244.2.72: seq=147 ttl=63 time=0.092 ms
64 bytes from 10.244.2.72: seq=148 ttl=63 time=0.079 ms

隔离名称空间

实践中,通常需要彼此隔离所有的名称空间,但是又需要允许它们可以和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名称空间等,这类具有管理功能的附件所在的名称空间和每一个特定的名称空间的出入流量也是需要被放行的。

参考资料

https://www.cnblogs.com/linuxk
马永亮. Kubernetes进阶实战 (云计算与虚拟化技术丛书)
Kubernetes-handbook-jimmysong-20181218

posted @ 2019-04-12 10:18  微落不落  阅读(1326)  评论(0编辑  收藏  举报