云计算百科
云计算领域专业知识百科平台

k8s:CNI网络插件flannel与calico

一、前言:

1、什么是CNI?

        CNI (Container Network Interface,容器网络API接口),是由 CoreOS 提出的容器网络规范,它使用了插件(Plugin)模型创建容器的网络栈。

        CNI用于连接容器管理系统和网络插件,这为K8s提供的一种通用网络标准规范,因为k8s本身不提供网络解决方案。目前比较知名的网络解决方案有:

  • Flannel:能提供ip地址,但不支持网络策略
  • calico:既提供ip地址,又支持网络策略
  • canal:flannel和calico结合,通过flannel提供ip地址,calico提供网络策略
  • 什么叫做网络策略?

            网络策略:可以达到多租户网络隔离,可以控制入网和出网流量,入网和出网ip访问的一种策略

            简单来说,CNI 做两件事,容器创建时的网络分配,和当容器被删除时释放网络资源。

    k8s通过cni配置文件来决定使用什么cni插件,基本使用方法如下:

            首先在每个节点上配置CNI配置文件(/etc/cni/net.d/xxx.conf),xxx.conf表示某一个网络配置文件的名称

            安装CNI配置文件中所对应的二进制插件

            在这个节点创建Pod时,kubelet就会根据CNI配置文件调用对应的CNI插件来完成Pod网络配置

    2、容器虚拟化网络方案

    容器虚拟化网络方案,总体分为2种截然不同的发展路线:

    基于隧道

    1)隧道方案最具普适性,在任何网络环境下都可以正常工作,这与它的原理密不可分。

    2)最常见的隧道方案是flannel vxlan模式,以及calico的ipip模式。

    优势/劣势

    优势就是对物理网络环境没有特殊要求,只要宿主机IP层可以路由互通即可。

    劣势就是性能差,这需要从以下方面看:

    1)封包和解包耗费CPU性能;

    2)额外的封装导致带宽浪费,大约有30%左右的带宽损耗;

    基于路由

    1)路由方案性能最好,原因是该方案不需要封包和解包,所以没有隧道方案的劣势,网络性能很好。

    2)常见的路由方案包括了flannel的host-gw模式,以及calico的bgp模式。

    优势/劣势

    优势就是没有封包和解包过程,完全基于两端宿主机的路由表进行转发。

    劣势包含2方面:

    1)要求宿主机处于同一个2层网络下,也就是连在一台交换机上,这样才能基于MAC通讯,而不需要在IP上动封包/解包的手脚。

    2)路由表膨胀导致性能降低,因为宿主机上每个容器需要在本机添加一条路由规则,而不同宿主机之间需要广播自己的网段路由规则。

    3、Kubernetes主要存在4种类型的通信:

  • container-to-container: 一个Pod内各容器共享同一网络名称空间,它通常由构建Pod对象的基础容器pause所提供。因此同一Pod内的各容器可以直接通过lo接口完成交互;
  • Pod-to-Pod: Pod间的通信,k8s自身并未解决该类通信,而是借助于CNI接口,交给第三方解决方案;CNI之前的接口叫kubenet;
  • Service-to-Pod:借助于kube-proxy生成的iptables或ipvs规则完成;
  • ExternalClients-to-Service:引入集群外部流量 hostPort、hostNletwork、nodeport/service、loadbalancer/service、exteralP/service、Ingress。
  • 1)容器和容器之间的网络

    1.在k8s中每个Pod中管理着一组Docker容器,这些Docker容器共享同一个网络命名空间。

    2.Pod中的每个Docker容器拥有与Pod相同的IP和port地址空间,并且由于他们在同一个网络命名空间,他们之间可以通过localhost相互访问。

            什么机制让同一个Pod内的多个docker容器相互通信那?其实是使用Docker的一种网络模型:–net=container

            container模式指定新创建的Docker容器和已经存在的一个容器共享一个网络命名空间,而不是和宿主机共享。新创建的Docker容器不会创建自己的网卡、配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等

            每个Pod容器有有一个pause容器其有独立的网络命名空间,在Pod内启动Docker容器时候使用 –net=container就可以让当前Docker容器加入到Pod容器拥有的网络命名空间(pause容器)

    2)Pod与Pod之间的网络(同一个Node中的Pod之间的通信)

            k8s中,每个Pod拥有一个ip地址,不同的Pod之间可以直接使用该ip与彼此进行通讯。

            在同一个Node上,从Pod的视角看,它存在于自己的网络命名空间中,并且需要与该Node上的其他网络命名空间上的Pod进行通信。

            Linux以太网桥(Linux Ethernet bridge)是一个虚拟的2层网络设备,目的是把多个pod连接起来,网桥维护了一个转发表(MAC表),通过它传输数据包并决定是否将数据包传递到连接到网桥的其他pod。

            网桥实现了ARP协议用来根据给定的ip地址找到对应机器的数据链路层的mac地址,一开始转发表为空,当一个数据帧被网桥接受后,网桥会广播该帧到所有的连接设备(除了发送方设备),并且把响应这个广播的设备记录到转发表;随后发往相同ip地址的流量会直接从转发表查找正确的mac地址,然后转发包到对应的设备。

    上图的两个Pod通过veth对链接到root网络命名空间,并且通过网桥进行通信。

    同一个Node中的Pod之间的一次通信

            鉴于每个Pod有自己独立的网络命名空间,我们使用虚拟以太网设备把多个Pod的命名空间链接到了root命名空间,并且使用网桥让多个Pod之间进行通信,下面我们看如何在两个pod之间进行通信:

            1.通过网桥这里把veth0和veth1组成为一个以太网,他们直接是可以直接通信的,另外这里通过veth对让pod1的eth0和veth0、pod2的eth0和veth1关联起来,从而让pod1和pod2相互通信。

            2.Pod 1通过自己默认的以太网设备eth0发送一个数据包,eth0把数据传递给veth0,数据包到达网桥后,网桥通过转发表把数据传递给veth1,然后虚拟设备veth1直接把包传递给Pod2网络命名空间中的虚拟设备eth0.

    3)不同Node中的Pod之间通信

            k8s网络模型需要每个pod必须通过ip地址可以进行访问,每个pod的ip地址总是对网络中的其他pod可见,并且每个pod看待自己的ip与别的pod看待的是一样的,下面我们看不同Node间Pod如何交互

            k8s中每个集群中的每个Node都会被分配了一个CIDR块(无类别域间路由选择,把网络前缀都相同的连续地址组成的地址组称为CIDR地址块)用来给该Node上的Pod分配IP地址。(保证pod的ip不会冲突)

    另外还需要把pod的ip与所在的nodeip关联起来

            1.如上图Node1(host1)上的Pod1与Node2(host2)上Pod4之间进行交互。

            2.首先pod1通过自己的以太网设备eth0把数据包发送到关联到root命名空间的veth0上,然后数据包被Node1上的网桥设备cbr0接收到,网桥查找转发表发现找不到pod4的Mac地址,则会把包转发到默认路由(root命名空间的eth0设备),然后数据包经过eth0就离开了Node1,被发送到网络。

            3.数据包到达Node2后,首先会被root命名空间的eth0设备,然后通过网桥cbr0把数据路由到虚拟设备veth1,最终数据会被流转到与veth1配对的另外一端(pod4的eth0)

            每个Node都知道如何把数据包转发到其内部运行的Pod,当一个数据包到达Node后,其内部数据流就和Node内Pod之间的流转类似了。

            对于如何来配置网络,k8s在网络这块自身并没有实现网络规划的具体逻辑,而是制定了一套CNI(Container Network Interface)接口规范,开放给社区来实现。

    Kubernetes跨主机容器之间的通信组件,目前主流的是flannel和calico。

    我们将集中探索与对比目前最流行的CNI插件:Flannel、Calico

    二、Kubernetes-基于flannel的集群网络

    Flannel官网:https://github.com/coreos/flannel

    1、Flannel简介

  • Flannel是由CoreOS开源的针对k8s的网络服务,其目的是为解决k8s集群中各主机上Pod之间的通信问题,其借助etcd维护网络IP地址分配,并为每个Node节点分配一个不同的IP地址段。简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
  • 在默认的Docker配置中,每个节点上的Docker服务会分别负责所在节点容器的IP分配。这样导致的一个问题是,不同节点上容器可能获得相同的IP地址。
  • Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。
  • Flannel实质上是一种“覆盖网络(overlaynetwork)”,也就是将TCP数据包装在另一种网络包里面进行路由转发和通信。
  • 简单总结Flannel特点:
  • 使集群中的不同Node主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
  • 建立一个覆盖网络(overlay network),通过这个覆盖网络,将数据包原封不动的传递到目标容器。覆盖网络是建立在另一个网络之上并由其基础设施支持的虚拟网络。覆盖网络通过将一个分组封装在另一个分组内来将网络服务与底层基础设施分离。在将封装的数据包转发到端点后,将其解封装。
  • 创建一个新的虚拟网卡flannel0接收docker网桥的数据,通过维护路由表,对接收到的数据进行封包和转发(vxlan)。
  • etcd保证了所有node上flannel所看到的配置是一致的。同时每个node上的flannel监听etcd上的数据变化,实时感知集群中node的变化。
  • 2、flannel支持的路由转发方式

            Flannel在每个节点运行一个名为flanneld的二进制代理程序,它负责从预留的网络中按照指定或者默认的掩码长度为当前节点申请分配一个子网,并将网络配置、已分配的子网和辅助数据(比如主机的公网IP等)存储在Kubernetes API或独立的etcd中。Flannel通过不同的后端来实现跨节点Pod间的通信,Flannel支持三种Pod网络模型,每个模型在flannel中称为一种"backend",目前支持的后端如下:

    vxlan:

    1)vxlan: 叠加网络模式,利用内核级别的VXLAN来封装host之间传送的包,Pod与Pod经由隧道封装后通信。各节点彼此间能通信就行,不要求在同一个二层网络。vxlan模型中,flanneld监听udp 8472端口接受和发送封装的数据包; 缺点:由于经过2次封装,吞吐量相对变低,优点:不要求节点处于同一个2层网络

    2)vxlan directrouting:直接路由模式,位于同一个二层网络上的、不同节点上的Pod间通信,无须隧道封装,通过路由模式直接发送,也就是当各个节点位于同一子网时,启用直接路由(类似host-gw),通过宿主机的物理网卡通信;但非同一个二层网络上的节点上的Pod间通信,仍须隧道封装; 最优的方案。

    host-gw:

            类似于VXLAN后端的直接路由模式,但不支持跨二层网络的节点,因此这种模式要求各节点处于同一个二层网络中,不太适用于规模较大的环境,但转发性能较好。

    udp:

            使用常规UDP报文封装完成隧道转发,性能比vxlan和host-gw相比较差,仅在不支持vxlan和host-gw时使用;UDP后端模式中,flanneld监听UDP 8285端口发送报文。

    3、示例1: 部署flannel 以Vxlan类型运行

    查看flannel部署清单yaml文件中有关于网络类型的描述

    [root@k8s-master1 ~]# cat kube-flannel.yml

    ………………………

    kind: ConfigMap

    apiVersion: v1

    metadata:

    name: kube-flannel-cfg

    namespace: kube-system

    labels:

    tier: node

    app: flannel

    data:

    cni-conf.json: |

    {

    "name": "cbr0",

    "cniVersion": "0.3.1",

    "plugins": [

    {

    "type": "flannel", #flannel虚拟网络

    "delegate": {

    "hairpinMode": true,

    "isDefaultGateway": true

    }

    },

    {

    "type": "portmap", #端口映射 如:NodePort

    "capabilities": {

    "portMappings": true

    }

    }

    ]

    }

    net-conf.json: |

    {

    "Network": "10.244.0.0/16",

    "Backend": {

    "Type": "vxlan" #默认为vxlan模式

    }

    }

    ………………………

    如上所示,其配置是json格式,常用的键有如下几个:
  • Network:Flannel全局使用的子网,即pod cidr的值
  • SubnetLen:子网分割的长度,在全局子网掩码小于24时(例如16),默认为24
  • SubnetMin:分配给节点使用的起始子网,默认为切割完成后的第一个子网
  • SubnetMax:分给给节点使用的最大子网,默认为切割完成后的最后一个子网
  • Backend:Flannel使用的后端,以及后端的配置
  • 另外,flannel还会在运行的节点上生成一个环境变量文件,默认是/run/flannel/subnet.env,其包含本节点使用的子网、mtu等信息。例如下面的示例:

  • FLANNEL_NETWORK=10.244.0.0/16 #flannel全局网段
  • FLANNEL_SUBNET=10.244.0.1/24 #本节点子网
  • FLANNEL_MTU=1450 #容器接口mtu值
  • FLANNEL_IPMASQ=true #地址映射
  • vxlan模式下,路由表Pod地址指向flannel.1

    [root@k8s-master1 ~]# route -n

    Kernel IP routing table

    Destination Gateway Genmask Flags Metric Ref Use Iface

    0.0.0.0 192.168.211.2 0.0.0.0 UG 100 0 0 ens33

    10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0 #本机虚拟网络接口

    10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1

    10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1

    172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

    192.168.211.0 0.0.0.0 255.255.255.0 U 10 0 0 ens33

    [root@k8s-node01 ~]# route -n

    Kernel IP routing table

    Destination Gateway Genmask Flags Metric Ref Use Iface

    0.0.0.0 192.168.211.2 0.0.0.0 UG 100 0 0 ens33

    10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1

    10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 flannel.a

    10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1

    172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

    192.168.211.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33

    #抓包flannel网络 其中 udp 8472为flannel网络默认端口

    [root@k8s-node1 ~]# tcpdump -i ens33 -nn udp port 8472

    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

    listening on eth4, link-type EN10MB (Ethernet), capture size 262144 bytes

    17:08:15.113389 IP 192.168.211.11.46879 > 192.168.211.12.8472: OTV, flags [I] (0x08), overlay 0, instance 1

    IP 10.244.1.9 > 10.244.2.5: ICMP echo request, id 2816, seq 61, length 64

    17:08:15.113498 IP 192.168.211.12.55553 > 192.168.211.11.8472: OTV, flags [I] (0x08), overlay 0, instance 1

    IP 10.244.2.5 > 10.244.1.9: ICMP echo reply, id 2816, seq 61, length 64

    ……………………

            可以看到10.244.1.9 > 10.244.2.5 Pod间的传输 通过封装从节点192.168.211.11.46879 传输到节点 192.168.211.12.8472 经过一层数据封装

    Vxlan类型Pod通信分析:

    如上图所示,两个Pod的通信流程大致如下:
    1.在client Pod上向nginx Pod发起请求,因为它们不在同一网段,所以报文会根据默认路由到达cni0,此时

    src-ip:10.224.1.9(client-pod) dst-ip:10.224.2.5(nginx-pod)

    src-mac:1a:d0:98:5f:ff:ce(client-pod) dst-mac:86:8c:53:15:25:d4(cni0)

    在cni0上抓包如下:

    2.cni0发现目的地址并非本机Pod地址,自己不能转发。然后会匹配主机路由,发现到达10.244.2.0/24需要通过flannel.1,将报文转给flannel.1
    3.flannel.1收到报文后会将其封装为vxlan报文内层的数据帧,此时

    src-ip:10.224.1.9(client-pod) dst-ip:10.224.2.5(nginx-pod)

    src-mac:d6:60:8c:f3:ba:37(node-01 flannel.1) dst-mac:4a:4a:ee:b9:cb:2d(node-02 flannel.1)

    flannel.1上抓包如下:

    4. 最后数据包通过内核vxlan模块封装成vxlan报文,然后通过节点网卡ens33发往node-02,此时

    inner-src-ip:10.224.1.9(client-pod ) inner-dst-ip:10.224.2.5(nginx-pod)

    inner-src-mac:d6:60:8c:f3:ba:37(node-01 flannel.1) inner-dst-mac:4a:4a:ee:b9:cb:2d(node-02 flannel.1)

    outer-src-ip:192.168.211.11(node-01 ens33) outer-dst-ip:192.168.211.12(node-02 ens33)

    outer-src-mac:00:0c:29:51:81:05(node-01 ens33) outer-dst-mac:00:0c:29:99:52:7b192.168.211.12(node-02 ens33)

    在ens33上抓包如下图所示:

    5.node-02收到报文后进行vxlan解封装,然后再按照相反的顺序转发到nginx-pod

    此时还存在两个问题:

    node-01 的flannel.1是如何知道node-02的flannel.1的地址来进行报文封装的呢?通过ARP?

            错,Flannel并不依赖ARP进行MAC地址学习,而是由节点上的flanneld进程动态生成相应的解析记录。例如下面的解析记录是在node-01节点上查询的,它们分别指明了集群上其它节点flannel.1接口的MAC地址,PERMANENT表示永久有效

    在node-01 的ens33接口抓取VXLAN报文中,外层报文目标地址是node-02的ens33接口的地址,但是本地节点上并没有任何路由信息帮助指向目标节点,由flanneld生成的路由中仅指明了到达隧道出口时flannel.1接口的IP地址,那么外层报文的目标地址是如何获取的呢?

            事实上Flannel把flannel.1接口也作为网桥设备使用,该设备上附加了一个同样由flanneld维护的称为FDB的转发数据库,FDB指定了到达目标节点flannel.1接口需要经由的下一跳IP,该IP是目标Pod所在节点的某接口的IP(在上面的报文中是node-02的ens33接口IP),即VXLAN外部报文的目标IP。

            例如下图的转发条目是在node-01上查询的,这些条目分别指定了到达集群中其它节点flannel.1接口时需要经由的下一跳IP地址。

    4、示例2: 添加flannel网络类型DirectRouting

            添加DirectRouting后,2层网络节点会使用宿主机网络接口直接通信,3层网络的节点会使用Vxlan 隧道封装后通信,组合使用是flannel最理想的网络类型

    因为测试环境所有节点都处于同一2层网络,所以从路由表无法看到flannel.1接口同时存在。

    [root@k8s-master1 ~]# kubectl get cm -n kube-system

    NAME DATA AGE

    coredns 1 57d

    extension-apiserver-authentication 6 57d

    kube-flannel-cfg 2 57d

    kube-proxy 2 57d

    kubeadm-config 2 57d

    kubelet-config-1.19 1 57d

    [root@k8s-master1 ~]# kubectl edit cm kube-flannel-cfg -n kube-system

    net-conf.json: |

    {

    "Network": "10.244.0.0/16",

    "Backend": {

    "Type": "vxlan", 注意逗号

    "DirectRouting": true #添加

    }

    }

    重启Pod,正式环境用蓝绿更新

    [root@k8s-master1 ~]# kubectl get pod -n kube-system –show-labels

    [root@k8s-master1 ~]# kubectl delete pod -n kube-system -l app=flannel

    再次查看master、node路由表

    [root@k8s-master1 ~]# route -n

    Kernel IP routing table

    Destination Gateway Genmask Flags Metric Ref Use Iface

    0.0.0.0 192.168.211.2 0.0.0.0 UG 100 0 0 ens33

    10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0

    10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 ens33

    10.244.2.0 192.168.54.172 255.255.255.0 UG 0 0 0 ens33

    172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

    192.168.211.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33

    [root@k8s-node01 ~]# route -n

    Kernel IP routing table

    Destination Gateway Genmask Flags Metric Ref Use Iface

    0.0.0.0 192.168.211.2 0.0.0.0 UG 100 0 0 ens33

    10.244.0.0 192.168.211.11 255.255.255.0 UG 0 0 0 ens33

    10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0

    10.244.2.0 192.168.211.112 255.255.255.0 UG 0 0 0 ens33

    172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

    192.168.211.0 0.0.0.0 255.255.255.0 U 101 0 0 ens33

            VXLAN后端支持DirectRouting模式,即在集群的各节点上添加必要的路由信息,让Pod间的报文通过节点的二层网络直接传送。如下图所示,只有通信双方Pod所在节点不在同一个二层网络时才启用传统的VXLAN隧道方式转发流量。假如k8s集群的节点都位于同一个二层网络中,DirectRouting模式下的Pod通信基本接近于直接使用二层网络。即使节点分布在不同的网络中,合理使用也可以节省一部分隧道开销。

            修改kube-flannel(v1.24以后的版本)名称空间下configmap/kube-flannel-cfg资源,为VXLAN后端添加Directrouting: true键值对就可以开启DirectRouting模式。如下图:

            修改完成后可以逐个删除之前的flannel Pod以触发更新。更新完成后节点上的路由规则也会发生相应的变动,到达与本节点同一二层网络的其它节点,Pod子网的下一跳地址会由对端flannel1.1接口的地址变为宿主机的物理接口地址,本地用于发出报文的接口也从flannel.1变为本地物理接口。

    以node-01为例,启用Directrouting模式后,节点上的路由变成如下图

            此时,node-01上的Pod访问node-02上的Pod就不需要通过VXLAN封装报文了,直接通过路由即可实现。例如下面抓取的报文所示:

            但是显然,这种模式无法满足跨二层网络节点上Pod的通信需求,因为到达Pod子网的下一跳地址无法指向另一个二层网络中的节点地址。所以节点上依然保留flannel.1接口用于跨二层网络节点上Pod的通信需求。

    5、示例3: 修改flannel网络类型host-gw 需要注意host-gw只支持2层网络

    因为所有节点都处在2层网络中,理论上和前面添加DirectRouting 效果是一样的。

    [root@k8s-master1 ~]# vim kube-flannel.yml

    net-conf.json: |

    {

    "Network": "10.244.0.0/16",

    "Backend": {

    "Type": "host-gw" #修改类型为host-gw

    }

    }

    #查看路由表

    [root@k8s-master plugin]# kubectl apply -f kube-flannel.yml

    Kernel IP routing table

    Destination Gateway Genmask Flags Metric Ref Use Iface

    0.0.0.0 192.168.211.2 0.0.0.0 UG 100 0 0 ens33

    10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0

    10.244.1.0 192.168.211.11 255.255.255.0 UG 0 0 0 ens33

    10.244.2.0 192.168.211.12 255.255.255.0 UG 0 0 0 ens33

    172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

    192.168.211.0 0.0.0.0 255.255.255.0 U 101 0 0 ens33

            host-gw后端通过添加路由信息,使用节点上的二层网络直接发送Pod间的通信报文,其工作方式类似于VXLAN后端的DirectRouting模式,但不具备DirectRouting模式中VXLAN隧道转发能力,这意味着host-gw后端下所有节点必须位于同一个二层网络中。

    host-gw后端工作模型示意图如下:

            同样,直接修改kube-flannel名称空间下configmap/kube-flannel-cf资源对象,将Backend.Type的值修改为host-gw,然后重建Pod即可启用host-gw后端,如下所示:

            配置完成后,各节点上就会生成类似于VXLAN后端直接路由模式的路由规则,以转发Pod的通信报文,它完全省去了隧道转发的额外开销,所以也不在需要flannel.1接口。但是,如果节点处于不同的二层网络,host-gw后端就无法实现Pod间的通信。因此,相对来说,VXLAN的DirectRouting模式兼具VXLAN后端和host-gw后端的优势,既能保证传输性能,又可以跨二层网络转发Pod报文。

    三、Kubernetes-基于calico的集群网络

    1、calico简介

    Calico官方文档:https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart

            Calico是一套开源的网络和网络安全解决方案,用于容器、虚拟机、宿主机之前的网络连接,它是一个纯三层的虚拟化网络解决方案,它把每个节点都作为一个虚拟路由器,并把每个节点上的Pod当作是节点路由器后的一个终端设备并为其分配一个IP地址。各节点路由器通过BGP协议生成路由规则,从而实现不通节点上Pod间的通信,此外,Calico还实现了 Kubernetes 网络策略,提供ACL功能,即使用 iptables 来做安全访问策略。如下图:

            与Flannel相比,Calico的一个显著优势是对网络策略的支持,它允许用户定义访问控制规则以管控进出Pod的数据报文,从而为Pod间的通信施加安全策略。

    所以Calico提供两种主要服务:

  • 节点之间的网络连接。
  • 节点之间的网络安全策略实施。
  •         BGP是一个去中心化自治路由协议,它通过维护IP路由表或“前缀”来实现自治系统之间的可达性,通常作为大规模数据中心维护不同自治系统之间路由信息的矢量路由协议。Linux内核原生支持BGP,因此可以把一台Linux主机配置为边界网关。

            Calico把每个节点上Pod组成的网络视为一个自治系统(AS),而每个节点就相当于自治系统的边界网关。各节点之间通过BGP协议交换路由信息并生成路由规则。但并非所有的网络环境都能支持BGP,而且BGP路由模型要求所有节点位于同一个二层网络中,所以Calico还支持基于IPIP和VXLAN的Overlay网络模型。

            另外,类似于Flannel的 VXALN后端启用Directrouting时的网络模型,Calico也支持混合使用路由和Overlay网络模型,BGP路由模型用于二层网络的高性能通信,IPIP或VXLAN用于跨二层网络节点间Pod报文的转发,如下图所示:

    2、Calico 网络Node之间通信网络

    1)IPIP(可跨网段通信)

            从字面来理解,就是把一个IP数据包又套在一个IP包里,即把 IP 层封装到 IP 层的一个 tunnel。它的作用其实基本上就相当于一个基于IP层的网桥!一般来说,普通的网桥是基于mac层的,根本不需 IP,而这个 ipip 则是通过两端的路由做一个 tunnel,把两个本来不通的网络通过点对点连接起来。

    类似vxlan 但封装开销比vxlan小 效率相对更高一些,但安全性也更差

    2)Vxlan(可跨网段通信)

    与Flannel Vxlan原理相同

    3)BGP路由模式(二层网络通信)

            边界网关协议(Border Gateway Protocol, BGP)是互联网上一个核心的去中心化自治路由协议。它通过维护IP路由表或‘前缀’表来实现自治系统(AS)之间的可达性,属于矢量路由协议。

            实际上,Calico 项目提供的 BGP 网络解决方案,与 Flannel 的 host-gw 模式几乎一样。也就是说,Calico也是基于路由表实现容器数据包转发,但不同于Flannel使用flanneld进程来维护路由信息的做法,而Calico项目使用BGP协议来自动维护整个集群的路由信息。

    部署推荐方案:

    BGP+Vxlan

    3、calico架构

            如上图所示,Calico的组件主要有Felix、etcd存储系统、BIRD和BGP路由反射器等,各组件的作用如下:

            1)Felix:运行在每一台Host的agent进程,主要负责网络接口管理和监听、路由、ARP管理、ACL管理和同步、状态上报等。Felix会监听Etcd中心的存储,从它获取事件,比如说用户在这台机器上加了一个IP,或者是创建了一个容器等,用户创建Pod后,Felix负责将其网卡、IP、MAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。同样,用户如果制定了隔离策略,Felix同样会将该策略创建到ACL中,以实现隔离。

            2)etcd:分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性,可以与kubernetes共用。

            3)BGP Client(BIRD):Calico为每一台Host部署一个BGP Client,在Calico的角色是监听Host上由Felix注入的路由信息,然后通过BGP协议广播告诉剩余Host节点,从而实现网络互通(即负责将Felix生成的路由信息载入内核并通告到整个网络中)。BIRD是一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,你们路由的时候得到这里来。。

            4)BGP Route Reflector(BGP路由反射器):在大型网络规模中,如果仅仅使用BGP client形成全网互联的方案就会导致规模限制,因为所有节点之间俩俩互联,需要N^2个连接,为了解决这个规模问题,可以采用BGP的Router Reflector的方法,使所有BGP Client仅与特定RR节点互联并做路由同步,从而大大减少连接数。

            另外,Calico可以将关键配置抽象为资源类型,并允许用户按需自定义资源对象已完成系统配置,这些资源对象保存在Datastore中,Datastore可以是独立的etcd,也可以是k8s集群使用的etcd。Calico专有资源类型有十几种,包括IPPool(IP地址池)、NetworkPolicy(网络策略)、BGPConfiguration(BGP配置参数)等。类似于Kubernetes API资源的定义,这些资源的配置格式同样以JSON使用apiVersion、kind、metadata和spec等一级字段进行定义,并能够使用calicoctl客户端工具进行管理,也支持由kubelet借助CRD进行这类资源的管理。

            其中BGP在官方的推荐方案中以50个节点为界区别了不同规模使用不同的部署方案

            小规模网络:BGP peer 一对一网络:每个节点都是有N-1条路由,小型网络适用,当节点数N变多时,路由表更新都需要承受很大的压力,类似网络拓扑结构中的网状拓扑结构

            大规模网络:BGP Reflector 路由反射器:选择一到多个节点做为Reflector,所有节点路由都汇总给Reflector,所有节点都路由都指向Reflector ,适合大型网络,类似网络拓扑结构中的星型网络

    4、Calico部署

    官方链接:

    https://archive-os-3-25.netlify.app/calico/3.25/getting-started/kubernetes/self-managed-onprem/onpremises/

            在k8s集群上实际部署时,Calico分为calico-node和calico-kube-controllers两个组件,它们通过Datastore读取与自身相关的资源定义完成配置

  • calico-node:Calico在k8s集群中每个节点运行的代理程序,负责提供Felxi、bird4、bird6和confd等守护进程
  • calico-kube-controllers:Calico运行在k8s集群上的自定义控制器,是Calico协同k8s的插件
  • 从官网下载部署文件:

    https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/calico.yaml

    修改部署文件以适配k8s集群环境

            首先修改部署文件中CALICO_IPV4POOL_CIDR变量的值,将其设置为初始k8s化集群时设定的–pod-network-cidr,例如:

            然后修改CALICO_IPV4POOL_BLOCK_SIZE变量的值,指定Calico为节点分配地址段的掩码长度,默认26。

            Calico的部署文件默认使用的是IPIP 隧道模式,这里就保持默认,不再进行修改。如果要使用纯BGP路由模式或者混合模式可以修改变量CALICO_IPV4POOL_IPIP的值,可用值如下:

  • Always:只使用IPIP隧道网络,默认值
  • Never:不使用IPIP隧道网络
  • CrossSubnet:启用混合网络模式
  •         如果想要使用VXLAN隧道网络,而不是IPIP隧道网络,可以修改变量CALICO_IPV4POOL_VXLAN的值,可用值和变量CALICO_IPV4POOL_IPIP一致。

    更多变量的配置和介绍可以参考官网介绍:

    https://docs.tigera.io/calico/latest/reference/configure-calico-node

    将部署文件应用到集群之上,等待Calico相关Pod成功运行且无报错就表示Calico部署成功。

    [root@k8s-master1 ~]# kubectl apply -f calico.yaml

    [root@k8s-master1 ~]# kubectl get pods -A

    5、IPIP隧道网络

            工作在IPIP模式的Calico会在每个节点创建一个tunl0接口作为隧道出入口设备来封装IPIP隧道报文。Calico会为每个Pod资源创建一对veth设备,其中一端作为Pod的网络接口,另一端(名为calixxx)留在宿主机的网络名称空间,它不使用虚拟网桥。如下图所示:

            IPIP隧道网络也是依赖BGP来维护节点的路由信息。部署完成后,Calico会通过BGP协议在每个节点上生成到达其它节点Pod子网的路由信息。例如下面node-01上的路由信息,它们是由各节点上的BIRD以点对点的方式向网络中的其它节点进行通告并学习其它节点的通告而生成。

            对于每个Pod,Calico都会在节点上为其生成一个专用路由条目,用于确保以Pod IP为目标的报文可以通过节点上的calixxx接口送达,这是因为Calico没有像Flannel一样使用虚拟网桥进行报文转发导致的。相关路由条目如下所示:

    Pod通信流程分析

    集群中node-02上运行一个nginx-pod(10.244.89.5),在node-01上的client-pod(10.244.169.3)上请求nginx-pod,其过程大致如下:

    1)client-pod发送请求,根据Pod中的默认路由将报文送到主机上对应的calixxx接口,此时

    src-ip:10.244.89.5 dst-ip:10.244.169.3

    src-mac:b2:65:39:08:87:b0 dst-mac:ee:ee:ee:ee:ee:ee

    在calixxx接口抓包如下:

            此时发现一个问题,我们在client-pod内查看路由,发现默认路由是169.254.1.1,如下图所示。但是169.254.1.1这个地址是不存在的,这是什么情况?

            根据网络常识,当数据包目的地址不是本机时,会根据路由表查询网关,查询到网关后发送ARP请求查询网关MAC,然后修改数据的的目标MAC,所以无论这个地址是不是存在,只要ARP请求可以获取它的MAC即可。

    所以,在Pod内查看一下169.254.1.1对应的MAC,如下图:

            169.254.1.1对应的MAC是ee:ee:ee:ee:ee:ee,这是主机上calixxx接口的MAC,但这也不符合逻辑,主机上的calixxx接口没有这个地址,为什么ARP获取到的MAC是它的MAC?

    这其实是因为calixxx接口启用了网卡的ARP代理功能,如下图:

            代理 ARP 是 ARP 协议的一个变种,当 ARP 请求目标跨网段时,网关设备收到此 ARP 请求,会用自己的 MAC 地址返回给请求者,这便是代理 ARP(Proxy ARP)。所以,Pod内发送的ARP请求到达calixxx接口时,calixxx接口直接返回了自己的MAC。

    2)数据报文根据主机路由表,将报文转给tunl0接口进行IPIP封装

    tunl0接口抓包如下:

    此时应该是把外层的MAC头部给去掉了,然后进行IPIP封装

    3)封装好的IPIP数据报文通过主机接口ens33发出 ,此时

    inner-src-ip: 10.224.89.5 inner-dst-ip:10.224.169.3

    outer-src-ip:192.168.211.11 outer-dst-ip:192.168.211.12

    src-mac:00:0c:29:51:81:05 dst-mac:00:0c:29:99:52:7b

    192.168.211.12主机珠宝如下

    捕获主机 192.168.211.11 和主机192.168.211.12的所有通信数据包

    tcpdump -i ens33 -nn host 192.168.211.11 and 192.168.211.12

    4)node-02收到报文后进行解封装,然后发送给ngix-pod

    关于Calico其它模式的配置和使用,可以参考官网:

    https://projectcalico.docs.tigera.io/about/about-calico

    四、k8s网络策略

    1、网络策略介绍

    网络策略官方文档:

    https://kubernetes.io/zh-cn/docs/concepts/services-networking/network-policies/

            网络策略是控制Pod之间如何进行通信的规则,它使用标签来筛选Pod,并在该组Pod之上定义规则来定义管控其流量,从而为k8s提供更精细的流量控制和租户隔离机制。管理员和用户可以通过NetworkPolicy资源按需定义网络访问控制策略

            网络策略的具体实现要依靠CNI网络插件完成,例如Calico、Weave和Antrea等。因此,仅在使用支持网络策略的网络插件时才能让自定义的网络策略生效。不同的网络插件实现网络策略的方式也是不一样的。

            本文以Calico网络插件为例,它的calico-kube-contollers是用于将用户自定义的网络策略进行实现的组件,它主要依赖于在节点上构建iptales规则实现访问控制功能。

            默认情况下,k8s并未对Pod的流量做任何限制。Pod对象能够与集群上其他任何Pod通信,也能够与集群外部的网络端点通信。NetworkPolicy是名称空间级别资源,允许用户在通过标签选择器筛选的一组Pod上分别管理入站(Ingress)和出站(Egress)流量。将NetworkPolicy应用到名称空间中后,被标签选择器选中的Pod将默认拒绝所有流量,仅放行由NetworkPolicy资源明确允许的流量。未被NetworkPolicy资源的标签选择器选中的Pod对象的流量则不受影响。

    NetworkPolicy是Kubernetes API中标准的资源类型,它的部署文件常用字段如下:

    apiVersion: networking.k8s.io/v1

    kind: NetworkPolicy

    metadata:

    name: …

    namespace: …

    spec:

    podSelector: <Object> #Pod标签选择器,用来在当前名称空间下筛选出一组Pod,之后定义的出入站规则对此组Pod生效

    policyTypes: <[]string> #网络策略类型,Ingress表示对入站流量限制;Egress表示对出站流量限制,同时提供表示都限制

    ingress: #允许通过的入站流量的规则定义,空值表示允许所有的入站流量通过

    – from: #允许通过的入站流量的源端点列表,空值表示所有端点

    – ipBlock: #用IP地址范围匹配源端点,不能与下面的namespacesSelector和podSelector同时使用

    cidr: <string> #源端点的IP网段

    except: <[]string> #从cidr中排除的网段范围或地址,这些源端点不能访问指定的Pod

    namespacesSelector: <Object> #由标签选择器筛选的名称空间中的端点,空值表示所有名称空间

    podSelector: <Object> #由标签选择器筛选的Pod端点,空值表示none

    ports: <[]Object> #入站流量的目标端口列表,空值表示所有端口

    egress: #允许通过的出站流量的规则定义,空值表示允许所有出站流量通过

    – to: <[]Object> #允许出站流量访问的目标端点对象列表,空值表示所有端点,可嵌套字段通ingress.from

    ports: <[]Object> #允许出站流量访问的目标端口对象列表,空值表示所有端口

    为了方便理解NetworkPolicy资源及其功能,常用的术语如下:
  • Pod组:由NetworkPolicy通过标签选择器筛选出的一组Pod,它们是网络策略规则管控的目标
  • Egress规则:出站流量相关的规则,负责管控选定的Pod组发往其它端点的流量,可由流量的目标端点(spec.egress.to)和目标端口(spec.egress.ports)来定义
  • Ingress规则:入站流量的相关规则,负责管控选定的Pod组所能接收的流量,可由流量的源端点(spec.ingress.from)和流量的目标端口(spec.ingress.ports)来定义
  • 对端端点:与选定的Pod组交互的对端主机,可以由CIDR格式的地址段(ipBlock)来匹配指定地址段内的Pod对象、名称空间选择器(namespacesSelector)来匹配名称空间内的所有Pod对象,也可以结合Pod选择器(podSelector)在指定名称空间选出一组Pod对象
  •         在Ingress规则中,由from字段指定的端点称为源端点;而在Egress规则中,网络端点也称为目标端点,用to字段标识。对于未启用Ingress或Egress规则的Pod组,出站和入站流量默认均为允许。一旦在networkpolicy.spec中定义了ingress或egress字段,则它们的from或to字段就代表白名单列表,空值意味着选定所有端点,即允许相应方向上所有流量通过,此时ingress和egress字段作用与未启用流量方向设置(即spec.policyTypes字段为空)时相同。

    Ingress和Egress的生效逻辑略微复杂,Egress规则生效逻辑和Ingress类似,以Ingress规则为例:

    1)定义了spec.policyTypes值为Ingress,但未定义spec.ingress字段时,它无法匹配任何流量,因此选定的Pod组不接受任何端点访问

    2)定义了spec.policyTypes值为Ingress,但使用空值的spec.ingress或spec.ingress.from字段,表示匹配所有的端点,因而选出的Pod组可以被任意端点访问

    3)最后,即使Egress规则拒绝所有流量,但由Ingress规则放行的请求流量的响应报文依然能够正常出站,它并不受限于Egress规则的限制,反之亦然

    2、Ingress规则

            在实际环境中,有些Pod提供的服务不需要或不能公开给所有人访问,这时候就需要对它们施加访问控制。在需要管控的Pod对象所在的名称空间创建一个NetworkPolicy资源,使用spec.podSelector筛选要管控的Pod,并使用spec.ingress定义入站规则就实现入站流量管控。

            spec.ingress可嵌套的from和ports均为可选字段,空值表示允许所有入站流量通过。仅定义from字段时表示入站流量的目标端口不做限制,仅定义ports字段时表示入站流量的源端点不做限制。from和ports定义在一个列表项时,它们是逻辑与关系,表示匹配那些同时满足from和ports的入站流量

    ingress.from字段

            from字段的值是一个列表,用于指定访问目标Pod组入站流量的来源,可嵌套使用ipBlock、namespaceSelector和podSelector 3个字段。这3个字段匹配Pod的方式各有不同,且ipBlock不能和另外两个字段同时使用,同时使用namespaceSelector和podSelector这两个字段时它们是逻辑与关系。from下的多个列表项是逻辑或关系,入站流量只要能匹配其中一个就可以。

    1)ipBlock:根据IP地址或网络地址来匹配源端点

    2)namespaceSelector:使用标签选择器筛选名称空间,它将匹配筛选出的名称空间内的所有Pod对象;空值表示匹配所有名称空间,即源端点可以是集群上的任意Pod对象

    3)podSelector:在NetworkPolicy资源所在的名称空间中基于标签选择器筛选Pod对象,空值表示选中当前名称空间所有Pod对象。与namespaceSelector同时使用时,作用域为namespaceSelector筛选出的名称空间

    ingress.ports 字段

            ports字段的值也是一个对象列表,用于定义入站流量可以访问的目标端口,可以嵌套使用port和protocol字段

    1)port:端口号或在container上定义的端口名称,未定义时匹配所有端口

    2)protocol: 协议,TCP或UDP,默认TCP

            下面创建一些资源,用来后续测试网络策略的功能,包括两个名称空间project1和project2,在project1名称空间下部署一个nginx和tomcat,在project2名称空间下部署一个nginx,部署文件如下:

    [root@k8s-master1 ~]# cat ingress.yaml

    apiVersion: v1

    kind: Namespace

    metadata:

    name: project1

    labels:

    name: project1

    apiVersion: v1

    kind: Namespace

    metadata:

    name: project2

    labels:

    name: project2

    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: nginx-project1

    namespace: project1

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: nginx

    project: project1

    template:

    metadata:

    labels:

    app: nginx

    project: project1

    tier: fronted

    spec:

    containers:

    – name: nginx

    image: nginx

    imagePullPolicy: IfNotPresent

    ports:

    – name: http

    containerPort: 80

    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: tomcat-project1

    namespace: project1

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: tomcat

    project: project1

    tier: backend

    template:

    metadata:

    labels:

    app: tomcat

    project: project1

    tier: backend

    spec:

    containers:

    – name: tomcat

    image: tomcat:10.1.2

    imagePullPolicy: IfNotPresent

    ports:

    – name: http

    containerPort: 8080

    apiVersion: v1

    kind: Service

    metadata:

    name: nginx-svc-project1

    namespace: project1

    spec:

    type: NodePort

    selector:

    app: nginx

    project: project1

    ports:

    – name: http

    port: 80

    targetPort: 80

    protocol: TCP

    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: nginx-project2

    namespace: project2

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: nginx

    project: project2

    template:

    metadata:

    labels:

    app: nginx

    project: project2

    spec:

    containers:

    – name: nginx

    image: nginx

    imagePullPolicy: IfNotPresent

    ports:

    – name: http

    containerPort: 80

    apiVersion: v1

    kind: Service

    metadata:

    name: nginx-svc-project2

    namespace: project2

    spec:

    type: NodePort

    selector:

    app: nginx

    project: project2

    ports:

    – name: http

    port: 80

    targetPort: 80

    protocol: TCP

    [root@k8s-master1 ~]# kubectl apply -f ingress.yaml

    [root@k8s-master1 ~]# kubectl get pod -n project1 -o wide –show-labels

    [root@k8s-master1 ~]# kubectl get pod -n project2 -o wide –show-labels

    在tomcat容器中创建测试页

    [root@k8s-master1 ~]# kubectl -n project1 exec -it tomcat-project1-df99dd58c-ncc82 — sh

    # mkdir /usr/local/tomcat/webapps/ROOT

    # echo "project1 tomcat web page" > index.html

    # curl localhost:8080

    project1 tomcat web page

    示例1:限定指定的标签的Pod才能访问project1名称空间下的tomcat pod

    [root@k8s-master1 ~]# cat ingress_networkpolicy1.yaml

    apiVersion: networking.k8s.io/v1

    kind: NetworkPolicy

    metadata:

    name: network-policy-demo1

    namespace: project1

    spec:

    policyTypes: ["Ingress"]

    podSelector: #规则针对tomcat Pod生效

    matchLabels:

    app: tomcat

    project: project1

    tier: backend

    ingress:

    – from:

    – podSelector: #拥有下面指定标签的Pod才能访问tomcat Pod,默认是在当前名称空间下筛选

    matchLabels:

    app: nginx

    project: project1

    tier: fronted

    ports: #只能访问tomcat Pod的8080端口

    – port: 8080

    protocol: TCP

    创建并查看networkpPolicy资源

    [root@k8s-master1 ~]# kubectl apply -f ingress_networkpolicy1.yaml

    networkpolicy.networking.k8s.io/network-policy-demo1 created

    [root@k8s-master1 ~]# kubectl get networkpolicy -n project1

    NAME POD-SELECTOR AGE

    network-policy-demo1 app=tomcat,project=project1,tier=backend 32s

    [root@k8s-master1 ~]# kubectl describe networkpolicy/network-policy-demo1 -n project1

    Name: network-policy-demo1

    Namespace: project1

    Created on: 2023-03-06 13:46:14 +0800 CST

    Labels: <none>

    Annotations: <none>

    Spec:

    PodSelector: app=tomcat,project=project1,tier=backend

    Allowing ingress traffic:

    To Port: 8080/TCP

    From:

    PodSelector: app=nginx,project=project1,tier=fronted

    Not affecting egress traffic

    Policy Types: Ingress

            用同名称空间下nginx pod访问tomcat Pod,可以访问,因为nginx pod的标签符合NetworkPolicy的设定。  

    [root@k8s-master1 ~]# kubectl -n project1 exec -it pods/nginx-project1-7b9c9d6f59-rx7v8 — sh

    # curl 10.224.85.200:8080

    project1 tomcat web page

    #

            在default名称空间中使用pod访问project1名称空间下的tomcat,无法访问,因为pod的标签不符合NetworkPolicy的设定

    [root@k8s-master1 ~]# kubectl exec -it pods/nginx-5df7494dc-qlvlm — sh

    # curl 10.224.85.200:8080

    curl: (28) Failed to connect to 10.224.85.200 port 8080: Connection timed out

    示例2:通过ip地址限定可以访问tomcat的Pod

            在node-01和node-02上分别运行一个pod,然后去访问tomcat,node-01上的pod可以访问,node-02上的pod不能访问,因为它属于10.224.58.192/26网段

    创建并查看networkpolicy

    [root@k8s-master1 ~]# cat ingress_networkpolicy2.yaml

    apiVersion: networking.k8s.io/v1

    kind: NetworkPolicy

    metadata:

    name: network-policy-demo2

    namespace: project1

    spec:

    policyTypes: ["Ingress"]

    podSelector:

    matchLabels:

    app: tomcat

    project: project1

    tier: backend

    ingress:

    – from:

    – ipBlock:

    cidr: 10.224.0.0/16

    except:

    – 10.224.58.192/26

    ports:

    – port: 8080

    protocol: TCP

    创建networkpolicy

    [root@k8s-master1 ~]# kubectl apply -f ingress_networkpolicy2.yaml

    networkpolicy.networking.k8s.io/network-policy-demo2 created

    [root@k8s-master1 ~]# kubectl describe networkpolicy/network-policy-demo2 -n project1

    Name: network-policy-demo2

    Namespace: project1

    Created on: 2023-03-06 15:26:07 +0800 CST

    Labels: <none>

    Annotations: <none>

    Spec:

    PodSelector: app=tomcat,project=project1,tier=backend

    Allowing ingress traffic:

    To Port: 8080/TCP

    From:

    IPBlock:

    CIDR: 10.224.0.0/16

    Except: 10.224.58.192/26

    Not affecting egress traffic

    Policy Types: Ingress

    在node-01和node-02上分别运行一个pod

            然后去访问tomcat,node-01上的pod可以访问,node-02上的pod不能访问,因为它属于10.224.58.192/26网段

    示例3:通过名称空间限定可以访问tomcat Pod

    [root@k8s-master1 ~]# cat ingress_networkpolicy3.yaml

    apiVersion: networking.k8s.io/v1

    kind: NetworkPolicy

    metadata:

    name: network-policy-demo3

    namespace: project1

    spec:

    policyTypes: ["Ingress"]

    podSelector:

    matchLabels:

    app: tomcat

    project: project1

    tier: backend

    ingress:

    – from: #限定只有project2名称空间下具有app=nginx的Pod才能访问tomcat

    – podSelector: #podSlector筛选app=nginx的Pod

    matchLabels:

    app: nginx

    namespaceSelector: #namespaceSelector和podSlector是逻辑与关系,表示在namespaceSelector筛选出的名称空间下用podSlector筛选Pod

    matchLabels:

    name: project2

    ports:

    – port: 8080

    protocol: TCP

    [root@k8s-master1 ~]# kubectl apply -f ingress_networkpolicy3.yaml

    networkpolicy.networking.k8s.io/network-policy-demo3 created

    [root@k8s-master1 ~]# kubectl describe networkpolicy/network-policy-demo3 -n project1

    Name: network-policy-demo3

    Namespace: project1

    Created on: 2023-03-06 16:27:41 +0800 CST

    Labels: <none>

    Annotations: <none>

    Spec:

    PodSelector: app=tomcat,project=project1,tier=backend

    Allowing ingress traffic:

    To Port: 8080/TCP

    From:

    NamespaceSelector: name=project2

    PodSelector: app=nginx

    Not affecting egress traffic

    Policy Types: Ingress

    在project1名称空间下用nginx-pod访问tomcat-pod,不能访问

    [root@k8s-master1 ~]# kubectl delete -f ingress_networkpolicy1.yaml

    networkpolicy.networking.k8s.io "network-policy-demo1" deleted

    [root@k8s-master1 ~]# kubectl -n project1 exec -it pod/nginx-project1-7b9c9d6f59-rx7v8 — sh

    # curl 10.224.85.200:8080

    curl: (28) Failed to connect to 10.224.85.200 port 8080: Connection timed out

    #

    在project2名称空间下用nginx-pod访问tomcat-pod,可以访问

    [root@k8s-master1 ~]# kubectl -n project2 exec -it pod/nginx-project2-7f9c77bdb6-l9cfv — sh

    # curl 10.224.85.200:8080

    project1 tomcat web page

    #

    在project2名称空间下使用其它pod访问tomcat,无法访问

    3、Egress规则

            大多数情况下,一个名称空间下的Pod资源总有对外请求的需求,例如向CoreDns请求名称解析等。因此,通常应该将出站流量的默认策略设置为允许通过。但如果要实现更精细的控制,仅放行有对外请求必要的Pod对象的出站流量,可以通过Egress实现。

            spec.egress字段用于定义出站流量规则,它可以嵌套使用to和ports字段,to用于定义选定的Pod组的出站流量可访问的目标端点,其格式和逻辑与ingress.from一致;ports用于定义选定的Pod组的出站流量可访问的目标端口。

    下面是一个示例:

    [root@k8s-master1 ~]# cat egress_networkpolicy1.yaml

    apiVersion: networking.k8s.io/v1

    kind: NetworkPolicy

    metadata:

    name: network-policy-egress-demo1

    namespace: project1

    spec:

    policyTypes: ["Egress"] #网络策略类型为Egress

    podSelector: #对project1名称空间下的nginx-pod进行限制

    matchLabels:

    app: nginx

    project: project1

    tier: fronted

    egress:

    – to: #Egress条件1,限制nginx只能访问同名称空间下的tomcat-pod的8080端口

    – podSelector:

    matchLabels:

    app: tomcat

    project: project1

    tier: backend

    ports:

    – port: 8080

    protocol: TCP

    – to: #Egress条件2,限制nginx只能访问192.168.30.0/24网段的主机的任何端口(不定义ports字段表示不限制)

    – ipBlock:

    cidr: 192.168.30.0/24

    [root@k8s-master1 ~]# kubectl apply -f egress_networkpolicy1.yaml

    networkpolicy.networking.k8s.io/network-policy-egress-demo1 created

    [root@k8s-master1 ~]# kubectl -n project1 describe networkpolicy/network-policy-egress-demo1

    Name: network-policy-egress-demo1

    Namespace: project1

    Created on: 2023-03-06 17:28:52 +0800 CST

    Labels: <none>

    Annotations: <none>

    Spec:

    PodSelector: app=nginx,project=project1,tier=fronted

    Not affecting ingress traffic

    Allowing egress traffic:

    To Port: 8080/TCP

    To:

    PodSelector: app=tomcat,project=project1,tier=backend

    ———-

    To Port: <any> (traffic allowed to all ports)

    To:

    IPBlock:

    CIDR: 192.168.30.0/24

    Except:

    Policy Types: Egress

    测试在project1名称空间下nginx-pod访问同名称空间下的tomcat-pod,可以正常访问

    测试在project1名称空间下nginx-pod访问192.168.30.20:80,可以正常访问

            首先搭建测试环境,在192.168.30.20主机上通过epel源安装nginx软件包,并启动nginx服务。

    [root@k8s-node01 ~]# wget -O /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo

    [root@k8s-node01 ~]# yum -y install nginx

    [root@k8s-node01 ~]# systemctl start nginx

    [root@k8s-master1 ~]# kubectl -n project1 exec -it pod/nginx-project1-7b9c9d6f59-rx7v8 — sh

    测试在project1名称空间下nginx-pod访问coredns服务,解析域名失败,无法访问

    4、综合示例

    下面是一个综合示例,用于实现名称空间隔离。它大致实现了以下几点需求:

    1.本名称空间内的Pod可以互相访问

    2. 集群上管理类应用所在名称空间中的Pod(例如日志收集、监控、dashboard等)可以访问本名称空间中的Pod

    3. 本名称空间中的Pod可以访问CoreDns、Kubernetes API等必要的服务

    [root@k8s-master1 ~]# cat ingress_egress_networkpolicy.yaml

    apiVersion: networking.k8s.io/v1

    kind: NetworkPolicy

    metadata:

    name: default-networkpolicy

    namespace: project1

    spec:

    policyTypes: ["Ingress", "Egress"]

    podSelector: {} #project1名称空间下所有Pod对象

    ingress:

    – from: #入站规则1,允许指定名称空间中的Pod访问任意端口

    – namespaceSelector:

    matchExpressions:

    – {key: "name", operator: In, values: ["kube-system", "kube-dashboard", "log", "monitor", "project1"]}

    egress:

    – to: #出战规则1,允许访问同名称空间下Pod的任意端口

    – namespaceSelector:

    matchLabels:

    name: project1

    – to: [] #出站规则2,允许访问任何端点的UDP 53端口,DNS服务

    ports:

    – port: 53

    protocol: UDP

    – to: #出站规则3,允许访问kube-apierver

    – podSelector:

    matchLabels:

    component: kube-apiserver

    namespaceSelector:

    matchLabels:

    kubernetes.io/metadata.name: kube-system

    ports:

    – port: 443

    protocol: TCP

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » k8s:CNI网络插件flannel与calico
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!