您好,登录后才能下订单哦!
Kubernetes网络方案之 Calico策略实践
案例:由于k8s集群部署之前的方案是flannel网络策略,所以这里将flannel策略切换成calico网络策略
Calico是一个纯三层的数据中心网络方案,Calico支持广泛的平台,包括Kubernetes、OpenStack等。
Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器( vRouter) 来负责数据转发,而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。
此外,Calico 项目还实现了 Kubernetes 网络策略,提供ACL功能。
Calico会使用kernel来实现一个虚拟的路由器,来维护这些路由表,然后每个节点作为虚拟的路由器之后,再根据BGP来相互交换这些路由信息,导致能让它在整个集群节点这些数据之间的交换,而且calico来实现k8s网络策略提供ACL功能
1、BGP概述
实际上,Calico项目提供的网络解决方案,与Flannel的host-gw模式几乎一样。也就是说,Calico也是基于路由表实现容器数据包转发,但不同于Flannel使用flanneld进程来维护路由信息的做法,而Calico项目使用BGP协议来自动维护整个集群的路由信息。
BGP英文全称是Border Gateway Protocol,即边界网关协议,它是一种自治系统间的动态路由发现协议,与其他 BGP 系统交换网络可达信息。
这里使用BGP来实现整个网络的数据交换,这个其实和flannel的host-gw的模式几乎一样,只不过是使用BGP来实现数据的交换的,flannel是自己来维护这些,而BGP是一个在大型网络中采用的一个动态协议,所以calico使用这个BGP作为一个路由的交换,它也是性能和数据包,数据表和集群规模到达一定量时,也能保证好一个好的性能
一般管机房服务器会说:我们家机房时采用BGP多线的。" 这个说的也就是边界网关协议,路由也就是选择一个路径,转发出去时根据路由表的,路由表又分为了,动态和静态,静态就是人工去添加那些路由表信息,动态就是相互的能感知,当网络中设备很多,vlan很多,跨公司实现通信,那如果手动的去配置这些路由表信息,显然人工量是很大的,所以当这个到达一定规模的时候,基本上都会采用动态的路由协议,BGP就是其中一个,比如OSPF、RIP都是路由表的学习,动态的去感知网络的拓扑的,也就是在这个互联网大型网络中,各个路由设备他们之间学习都是通过动态去学习的,那BGP就是其中一个协议
为了能让你更清楚理解BGP,举个例子:
在这个图中,有两个自治系统(autonomous system,简称为AS):AS 1 和 AS 2。
自治系统,可以想成公司的网络和其他公司的网络,两个就可以理解为两个自治系统,每个自治系统是由自己交换机,路由器来组成的,而这些交换机路由器单独去运行,它不依赖于别的公司,别的公司也不依赖你公司的网络,都可以去独立的单独的去运行,一个大学,一个企业都可以说成是一个自治系统,但是这个自治系统也没什么交际,你家的网络和邻居家的网络也没什么来往,但是如果他们想通信,他们本身就不在一个网络里面,你家的网络和它家的网络上层的网络出口的交换机必须相互的学习到,而且我们用到的电脑都是私网IP,邻居家也用的是私网IP,甚至这些IP都是冲突的,如果想实现两家的网络内网能够通信,首先保证用到的ip地址不能冲突,还要保证上层的路由出口之间能够相互学到自己的路由表,比如你家的路由器能够学习到当前路由表的信息
。
BGP简单来说就是将两个自治系统进行连接起来,两个能够相互的通信,这就是BGP的作用,这里有两个AS,可以好比两个学校两个公司,两个都不是同网络,现在要是想AS 1下的192.168与AS 2下的172.17进行去通信,应该怎么走?
192.168.1.10先走交换机,再到路由器,从路由器的A口进去,他们两个公司要是想通信,路由器必须是可达的,是可以通信的,只要建立了通信之后,数据包到达了B节点,然后它怎么知道转发到路由器2呢?所以就需要路由表的存在,路由器1根据转发的目的地址,172.17.1.20这个网段,看本地有没有路由表,它会发现本地有个路由表,是转发到路由器2的,是从B口出去的,然后就会转发到路由器2上面,路由器2肯定知道它自己管辖的网络是多少,因为它本身就学习到了自己的目的地址,不过没有下一跳,因为在自己的管辖之内,比如1.20,有这个IP,那么就不需要下一跳,然后之间根据接口转发到A口里面,A口里面正好接的是交换机,然后交换机从二层传输到对应的目的地址上了,那么这样节点就可以通信了
那么BGP在这个环境中启动了什么作用?这个路由表也可以手动的进行添加,然后指向下一跳就是这个route的路由器,它也会转发过来,只要他们的网络是通的,如果节点很多,vlan很多,这样的话添加路由表就很大,而且在互联网中还会有别的路由器,可能还会用到别的网络进行去通信,所以这里的BGP就是可以相互的去学习到相互的路由表信息,那么route1为什么有route2的信息,其实就是在route学到的,那么AS 2要访问AS 2的节点,首先它的路由表也能学到目标地址,那么就能之间转发到路由器中,然后转发到目的的服务器上
这种动态路由协议跟我们的flannel中host-gw模式有点类似,好比服务器就是我们k8s中的容器,AS 1,AS 2都当成k8s的node,
之前是把当前节点当作一个网关,现在这个Calico引入这个BGP,它是将每个node,都做成一个虚拟路由器,他们通过BGP实现相互路由信息的交换,而flannel是由一个守护进程维护的每个路由表,发送到本地的节点,而calico采用的是BGP来实现数据交换,但路由表也要写到每个节点上。
在互联网中,一个自治系统(AS)是一个有权自主地决定在本系统中应采用何种路由协议的小型单位。这个网络单位可以是一个简单的网络也可以是一个由一个或多个普通的网络管理员来控制的网络群体,它是一个单独的可管理的网络单元(例如一所大学,一个企业或者一个公司个体)。一个自治系统有时也被称为是一个路由选择域(routing domain)。一个自治系统将会分配一个全局的唯一的16位号码,有时我们把这个号码叫做自治系统号(ASN)。
在正常情况下,自治系统之间不会有任何来往。如果两个自治系统里的主机,要通过 IP 地址直接进行通信,我们就必须使用路由器把这两个自治系统连接起来。BGP协议就是让他们互联的一种方式。
2、Calico BGP实现
这是calico的架构图,也是官方的一张图10.0.0.1是容器1,10.0.0.2是容器2,这里会有一个cali的接口,这两个设备也是veth的设备,这里它把宿主机当作一个虚拟路由器,然后这个虚拟路由器走的就是BGP协议,里面涉及到两个组件一个是Client,一个是Felix,主要负责写入机器的理由表信息,之前写入路由表信息的是flannel的守护进程去写的,calico是使用Felix去写的。
另外也是使用daemonset方式部署到每个节点上的,calico也是使用etcd来保持calico设置的网络策略以及配置状态,在这里面最关键的是BGP的client,它主要来提供这个协议的,也就是每个节点都有一个BGP client它们之间建立一个BGP的连接,然后走BGP的连接,把各自的路由表信息交换一下,这样整个机器的每个节点容器就形成了一个完整的拓扑规则了,所以他们是走了一个BGP的规则,flannel就是一个简单的TCP的规则去做的。
在了解了 BGP 之后,Calico 项目的架构就非常容易理解了,Calico主要由三个部分组成:
Felix:以DaemonSet方式部署,运行在每一个Node节点上,主要负责维护宿主机上路由规则以及ACL规则。
BGP Client(BIRD):主要负责把 Felix 写入 Kernel 的路由信息分发到集群 Calico 网络。
Etcd:分布式键值存储,保存Calico的策略和网络配置状态。
calicoctl:允许您从简单的命令行界面实现高级策略和网络。
3、Calico 部署git clone git@gitee.com:zhaocheng172/calico.git
这里需要将你的公钥给我,才能拉下来,不然没有权限
下载完后还需要修改里面配置项:
因为Calico使用的etcd一些策略一些网络配置信息的,还有一些calico的属性信息都是保存在etcd中的,而etcd也在k8s集群中部署,所以我们之间使用现有的k8s的etcd就可以了,如果使用https还要配置一下证书,然后选择一些pod的网络,还有工作模式
具体步骤如下:
配置连接etcd地址,如果使用https,还需要配置证书。
(ConfigMap,Secret)
根据实际网络规划修改Pod CIDR(CALICO_IPV4POOL_CIDR)
选择工作模式(CALICO_IPV4POOL_IPIP),支持BGP,IPIP
calico也是使用configmap保存配置文件的,secret是存储etcd它的https的证书的,分为3项
etcd-key: null
etcd-cert: null
etcd-ca: null
指定etcd连接的地址: etcd_endpoints: "http://<ETCD_IP>:<ETCD_PORT>"
当启动secret挂载到容器中时,它的文件是挂载哪个文件下,在这里指定好
etcd_ca: "" # "/calico-secrets/etcd-ca"
etcd_cert: "" # "/calico-secrets/etcd-cert"
etcd_key: "" # "/calico-secrets/etcd-key"
现在进行一下切换网络到calico
一、所以修改etcd一共修改3个位置
1、etcd的证书
我放证书的位置是在/opt/etcd/ssl下,但是我们需要放到secret里面,需要要转换成base64编码才能去存储,而这样执行也是由换行的,必须将它拼接成一个整体的字符串[root@k8s-master1 ~]# cat /opt/etcd/ssl/ca.pem |base64 -w 0
将对应的都添进去,将注释去掉
# etcd-key: null 将对应ssl下的证书转换成base64编码放进来,并去掉注释
# etcd-cert: null
# etcd-ca: null
2、要读取secret落地到容器中位置,直接将注释去掉就可以了
etcd_ca: "/calico-secrets/etcd-ca"
etcd_cert: "/calico-secrets/etcd-cert"
etcd_key: "/calico-secrets/etcd-key"
3、连接etcd的字符串,这与k8s连接API的字符串是一样的
这个是在[root@k8s-master1 ~]# cat /opt/kubernetes/cfg/kube-apiserver.conf 这个目录下,因为每个集群都是自己部署的,位置可能不一样etcd_endpoints: "https://10.4.7.11:2379,https://10.4.7.12:2379,https://10.4.7.21:2379"
将这个证书放进放进calico配置中
二、根据实际网络规划修改Pod CIDR
这个位置在这个是默认的,需要改成自己的
- name: CALICO_IPV4POOL_CIDR
value: "192.168.0.0/16"
可以在控制器配置的默认的也就是这个10.244.0.0.16这个地址
[root@k8s-master1 ~]# cat /opt/kubernetes/cfg/kube-controller-manager.conf
--cluster-cidr=10.244.0.0/16 \
在配置中改成这个
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
三、选择工作模式
IPIP
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
value: "Always"
这个变量问你要不要开启IPIP,因为有两种模式,第一种就是IPIP,第二种就是BGP
其实它应用最多就是BGP,将这个Always改成Never,就是将它关闭的意思
现在就可以删除flannel网络[root@k8s-master1 k8s]# kubectl delete -f kube-flannel.yaml
然后删除一下flannel生成的虚拟网卡还有网桥cni,这个最好删除掉,因为我们使用的子网和flannel的子网一样,也会出现冲突
[root@k8s-master1 calico]# ip route
default via 10.4.7.1 dev eth0 proto static metric 100
10.4.7.0/24 dev eth0 proto kernel scope link src 10.4.7.11 metric 100
10.244.0.0/24 via 10.4.7.21 dev eth0
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1
10.244.2.0/24 via 10.4.7.12 dev eth0
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
开始删除,这个每个节点都要删除路由表和网桥,这是之前部署flannel留下的,也是避免和calico冲突
[root@k8s-node1 ~]# ip link delete cni0
[root@k8s-node1 ~]# ip link delete flannel.1
[root@k8s-node1 ~]# ip route delete 10.244.0.0/24 via 10.4.7.21 dev eth0
[root@k8s-node1 ~]# ip route delete 10.244.1.0/24 via 10.4.7.11 dev eth0
开始部署calico,这里会帮你启动两个角色,calico-node其实就是calico的client和felix,这个会在每个节点都启动一个
calico-kube-controllers这个是calico主要在etcd中动态的获取一些网络规则,处理一些策略都是由这个控制器去完成的,
[root@k8s-master1 calico]# kubectl create -f calico.yaml
[root@k8s-master1 calico]# kubectl get pod -o wide -n kube-system
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
calico-kube-controllers-f68c55884-q7bsl 1/1 Running 0 2m17s 10.4.7.21 k8s-node2 <none> <none>
calico-node-g9tfw 1/1 Running 0 2m17s 10.4.7.21 k8s-node2 <none> <none>
calico-node-tskkw 1/1 Running 0 2m17s 10.4.7.11 k8s-master1 <none> <none>
calico-node-vldl8 1/1 Running 0 2m17s 10.4.7.12 k8s-node1 <none> <none>
目前为止去查看网络,会发现不到calico的路由表,因为目前的pod没有使用当前的calico的网络,需要重建才会应用到,所以这也是会受到一些影响的,这个需要提前做好准备
重建这些pod之后,网络就会根据calico的规则生成路由表,你会发现之前的pod,使用flannel部署的pod已经无法互通了,所以切换网络也是一个比较大的事情,需要注意安排时间去做这件事
[root@k8s-master1 ~]# ip route
default via 10.4.7.1 dev eth0 proto static metric 100
10.4.7.0/24 dev eth0 proto kernel scope link src 10.4.7.11 metric 100
10.244.113.128 dev calibe9d0ccbf7b scope link
blackhole 10.244.113.128/26 proto bird
10.244.203.64/26 via 10.4.7.21 dev eth0 proto bird
10.244.245.0/26 via 10.4.7.12 dev eth0 proto bird
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
4、Calico 管理工具
这里会用到calico的管理工具,用它管理一些calico的配置,比如切换成ipip模式,
下载工具:https://github.com/projectcalico/calicoctl/releases
# wget -O /usr/local/bin/calicoctl https://github.com/projectcalico/calicoctl/releases/download/v3.9.1/calicoctl
# chmod +x /usr/local/bin/calicoctl
安装好这个管理工具之后就可以查看当前节点BGP的节点状态
[root@k8s-master1 ~]# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+------------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+------------+-------------+
| 10.4.7.12 | node-to-node mesh | up | 2019-12-27 | Established |
| 10.4.7.21 | node-to-node mesh | up | 2019-12-27 | Established |
+--------------+-------------------+-------+------------+-------------+
IPv6 BGP status
No IPv6 peers found.
这个工具主要也是往etcd里去操作,主要再etcd去获得,这个表只不过是帮你从etcd中拿出来格式化输出
可以通过这个命令可以看出,进行长链接输出出来
[root@k8s-master1 ~]# netstat -anpt |grep bird
tcp 0 0 0.0.0.0:179 0.0.0.0:* LISTEN 221854/bird
tcp 0 0 10.4.7.11:179 10.4.7.12:28396 ESTABLISHED 221854/bird
tcp 0 0 10.4.7.11:179 10.4.7.21:51460 ESTABLISHED 221854/bird
如果想使用calicoctl get node,就需要指定配置文件了,默认在/etc/calico/calicoctl.cfg下
主要修改etcd的路径,还有它连接的证书,它主要操作etcd
# mkdir /etc/calico
# vim /etc/calico/calicoctl.cfg
apiVersion: projectcalico.org/v3
kind: CalicoAPIConfig
metadata:
spec:
datastoreType: "etcdv3"
etcdEndpoints: "https://10.4.7.11:2379,https://10.4.7.12:2379,https://10.4.7.21:2379"
etcdKeyFile: "/opt/etcd/ssl/server-key.pem"
etcdCertFile: "/opt/etcd/ssl/server.pem"
etcdCACertFile: "/opt/etcd/ssl/ca.pem"
这样的话就能使用calicocatl get node了,这样的话就是在etcd中去拿的数据了
# calicoctl get nodes
NAME
k8s-master
k8s-node1
k8s-node2
查看 IPAM的IP地址池:
[root@k8s-master1 ~]# calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ipv4-ippool 10.244.0.0/16 true Never Never false all()
5、Calico BGP 原理剖析
看一下默认的BGP是怎么工作的?
这个也是跨节点之间的通信,相比于flannel类似,其实这张图相比于flannel,通过一个路由器的图标,将flannel的cni,将flannel.1就相比于vxlan模式去掉了,所以会发现这里是没有网桥存在的,完全就是通过路由来实现的,这个数据包也是先从veth的设备对的另一口发出,到达宿主机上的cali开头的虚拟网卡上,到达这一头也就达到了宿主机上的网络协议栈,另外就是当创建一个pod时帮你先起一个infra containers的容器,然后调用calico的二进制帮你去配置容器的网络,然后会根据路由表决定这个数据包到底发送到哪里去,可以从ip route看到路由表信息,这里显示是目的cni分配的子网络和目的宿主机的网络,也就是当进行跨主机通信的时候之间转发到下一跳地址走宿主机的eth0网卡出去,也就是一个直接的静态路由,这个下一跳就跟host-gw的形式一样了,这个和host-gw最大的区别calico使用的BGP路由的交换,而host-gw是使用的自己的路由交换,而BGP这个方案比较成熟,在大型网络中用的也比较多,所以要比flannel的方式好很多,而这些路由信息都是由BGP client传输过来,使用BGP协议传输而来的。
这个为什么叫边界网关协议呢?
这个跟host-gw工作模式基本上是一样的,只不过BGP交换路由规则,BGP就成了一个边界路由器,主要是在每个自治系统的最边界与其他自治系统传输规则,这个由来也是这么来的,而这些节点之间组成的BGP网络它是一个全网通的网络,这个网络就称为一个BGP Peer
可以看到启动文件也是在/opt/cni/bin,这个目录是yaml文件有两个镜像专门去写进去的
这是子网的相关配置信息[root@k8s-master1 ~]# cat /etc/cni/net.d/10-calico.conflist
Pod 1 访问 Pod 2大致流程如下:
数据包从容器1出到达Veth Pair另一端(宿主机上,以cali前缀开头);
宿主机根据路由规则,将数据包转发给下一跳(网关);
到达Node2,根据路由规则将数据包转发给cali设备,从而到达容器2。
其中,这里最核心的“下一跳”路由规则,就是由 Calico 的 Felix 进程负责维护的。这些路由规则信息,则是通过 BGP Client 也就是 BIRD 组件,使用 BGP 协议传输而来的。
不难发现,Calico 项目实际上将集群里的所有节点,都当作是边界路由器来处理,它们一起组成了一个全连通的网络,互相之间通过 BGP 协议交换路由规则。这些节点,我们称为 BGP Peer。
而host-gw和calico的唯一不一样的地方就是当数据包下一跳到达node2节点的容器的时候发生变化了,并且出数据包也发生变化了,我们知道它是从veth的设备对让容器里面的数据包到达宿主机上数据包,这个数据包到达node2之后,它又根据一个特殊的路由规则,这个会记录目的通信地址的cni网络,然后通过cali的设备进去容器,这个就跟网线一样,数据包通过这个网线发到容器中,这也是一个二层的网络互通才能实现,如果二层不通的就可以使用IPIP模式了,
calico没有网桥数据包是怎么出去的?
pod1的数据包从veth的设备对到到宿主机的一段eth0上,之前的数据包其实是走的默认宿主机的网关将流量转发到calico的cali的设备上的,通过路由表信息下一跳地址到宿主机然后转发到对应的容器中
6、Route Reflector 模式(RR)(路由反射)
https://docs.projectcalico.org/master/networking/bgp
Calico 维护的网络在默认是(Node-to-Node Mesh)全互联模式,Calico集群中的节点之间都会相互建立连接,用于路由交换。但是随着集群规模的扩大,mesh模式将形成一个巨大服务网格,连接数成倍增加。
这时就需要使用 Route Reflector(路由器反射)模式解决这个问题。
确定一个或多个Calico节点充当路由反射器,让其他节点从这个RR节点获取路由信息。
在BGP中可以通过calicoctl node status看到启动是node-to-node mesh网格的形式,这种形式是一个全互联的模式,默认的BGP在k8s的每个节点担任了一个BGP的一个喇叭,一直吆喝着扩散到其他节点,随着集群节点的数量的增加,那么上百台节点就要构建上百台链接,就是全互联的方式,都要来回建立连接来保证网络的互通性,那么增加一个节点就要成倍的增加这种链接保证网络的互通性,这样的话就会使用大量的网络消耗,所以这时就需要使用Route reflector,也就是找几个大的节点,让他们去这个大的节点建立连接,也叫RR,也就是公司的员工没有微信群的时候,找每个人沟通都很麻烦,那么建个群,里面的人都能收到,所以要找节点或着多个节点充当路由反射器,建议至少是2到3个,一个做备用,一个在维护的时候不影响其他的使用。
具体步骤如下:
1、关闭 node-to-node BGP网格
添加 default BGP配置,调整 nodeToNodeMeshEnabled和asNumber:
[root@k8s-master1 calico]# cat bgp.yaml
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: false
asNumber: 63400
直接应用一下,当我们禁用node-to-node mesh的时候,网络立马就会断,所以断的话要提前做好影响的范围,也就是切换这个网络是需要断网的,使用node-to-node BGP这种也是建议100个节点以下,当超过100台节点一定要使用路由反射RR模式
[root@k8s-master1 calico]# calicoctl apply -f bgp.yaml
Successfully applied 1 'BGPConfiguration' resource(s)
查看bgp网络配置情况,false为关闭
[root@k8s-master1 calico]# calicoctl get bgpconfig
NAME LOGSEVERITY MESHENABLED ASNUMBER
default Info false 63400
去查看pod的网络测试已经断开了,这里是因为我们使用caclico的配置禁用了node-to-node mesh了
[root@k8s-master1 calico]# ping 10.244.245.2
PING 10.244.245.2 (10.244.245.2) 56(84) bytes of data.
ASN号可以通过获取 # calicoctl get nodes --output=wide
这里有个编号,ASN64300,一个编号就是一个自治系统
[root@k8s-master1 calico]# calicoctl get nodes --output=wide
NAME ASN IPV4 IPV6
k8s-master1 (63400) 10.4.7.11/24
k8s-node1 (63400) 10.4.7.12/24
k8s-node2 (63400) 10.4.7.21/24
2、配置指定节点充当路由反射器
为方便让BGPPeer轻松选择节点,通过标签选择器匹配,也就是可以去调用k8s里面的标签进行关联,我们可以给哪个节点作为路由发射器打个标签
给路由器反射器节点打标签,我这将node1打上标签[root@k8s-master1 calico]# kubectl label node k8s-node1 route-reflector=true
查看node BJP的节点状态,因为禁用了网格,所以这里都关闭了,所以也就不通了。
[root@k8s-master1 calico]# calicoctl node status
Calico process is running.
IPv4 BGP status
No IPv4 peers found.
IPv6 BGP status
No IPv6 peers found.
然后配置路由器反射器节点routeReflectorClusterID,增加一个集群节点的ID
下面的可以通过-o yaml输出出来
[root@k8s-master1 calico]# calicoctl get node k8s-node2 -o yaml > node.yaml
apiVersion: projectcalico.org/v3
kind: Node
metadata:
annotations:
projectcalico.org/kube-labels: '{"beta.kubernetes.io/arch":"amd64","beta.kubernetes.io/os":"linux","kubernetes.io/arch":"amd64","kubernetes.io/hostname":"k8s-node2","kubernetes.io/os":"linux"}'
creationTimestamp: null
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: k8s-node2
kubernetes.io/os: linux
name: k8s-node2
spec:
bgp:
ipv4Address: 10.4.7.12/24
routeReflectorClusterID: 244.0.0.1 # 集群ID
orchRefs:
- nodeName: k8s-node2
orchestrator: k8s
应用一下[root@k8s-master1 calico]# calicoctl apply -f node.yaml
现在,很容易使用标签选择器将路由反射器节点与其他非路由反射器节点配置为对等:现在也就是将其他的节点去连接这个k8s-node1打标签的路由发射器
[root@k8s-master1 calico]# cat bgp1.yaml
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: peer-with-route-reflectors
spec:
nodeSelector: all() #所以的节点
peerSelector: route-reflector == 'true'
#就是带route-reflector的都去连接匹配这个,刚才我们不是打上标签了嘛,所以需要我们去连接这个路由反射器
查看节点的BGP规则与连接状态,这样的话就显示一个路由反射器的节点
[root@k8s-master1 calico]# calicoctl apply -f bgp1.yaml
Successfully applied 1 'BGPPeer' resource(s)
[root@k8s-master1 calico]# calicoctl get bgppeer
NAME PEERIP NODE ASN
peer-with-route-reflectors all() 0
[root@k8s-master1 calico]# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+---------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+---------------+-------+----------+-------------+
| 10.4.7.12 | node specific | up | 08:22:22 | Established |
+--------------+---------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
查看容器网络联通性
[root@k8s-master1 calico]# ping 10.244.203.80
PING 10.244.203.80 (10.244.203.80) 56(84) bytes of data.
64 bytes from 10.244.203.80: icmp_seq=1 ttl=63 time=1.71 ms
添加多个路由反射器
现在进行对路由反射器添加多个,100个节点以内建议2-3个路由反射器
1)进行对集群节点打标签
[root@k8s-master1 calico]# kubectl label node k8s-node2 route-reflector=true
node/k8s-node2 labeled
2)对k8s-node2添加然后配置路由器反射器节点
[root@k8s-master1 calico]# calicoctl get node k8s-node2 -o yaml
3)查看节点状态
[root@k8s-master1 calico]# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+---------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+---------------+-------+----------+-------------+
| 10.4.7.12 | node specific | up | 08:22:22 | Established |
| 10.4.7.21 | node specific | up | 08:44:44 | Established |
+--------------+---------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
4)测试网络连通性
[root@k8s-master1 calico]# ping 10.244.203.81
PING 10.244.203.81 (10.244.203.81) 56(84) bytes of data.
64 bytes from 10.244.203.81: icmp_seq=1 ttl=63 time=12.7 ms
64 bytes from 10.244.203.81: icmp_seq=2 ttl=63 time=1.40 ms
所以这是使用路由反射器来解决节点增多BGP带来的消耗
7、IPIP模式
ipip模式与flannel的vxlan模式类似,这个也是对数据包的一个封装
在前面提到过,Flannel host-gw 模式最主要的限制,就是要求集群宿主机之间是二层连通的。而这个限制对于 Calico 来说,也同样存在,也是不能跨vlan的,主要局限就是数据包主要封装的是容器,源IP和目的IP,因为它们工作都是使用的二层,所以二层它不会考虑容器之间进行数据包转发的,但如果添加路由表,将目的的IP通过静态路由的方式也能实现,不同vlan的数据的通信,不过这种方式目前没有测试。
另外还有一个弊端,会发现calico的路由表比flannel的多一些,因为它里面还要加一些传入过来的到设备的路由表信息,就是每个pod都加一个路由表,所以它的路由表的量也比flannel大不少。
修改为IPIP模式:
calicoctl get ippool -o yaml > ipip.yaml
vi ipip.yaml
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
blockSize: 26
cidr: 10.244.0.0/16
ipipMode: Always
natOutgoing: true
calicoctl apply -f ipip.yaml
创建好之后查看详细信息,已经开启ippool
[root@k8s-master1 calico]# calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ipv4-ippool 10.244.0.0/16 true Always Never false all()
查看网络设备会增加一个tunl0,增加了一个隧道的网卡
tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
inet 10.244.113.131/32 brd 10.244.113.131 scope global tunl0
valid_lft forever preferred_lft forever
IPIP示意图:
那么有这么一个需求,有两个不同vlan,因为需要突破2层,现在有两个vlan,这两个vlan它本身是三层可达的,三层可达就必须要借助路由器,也就是这两个节点,部署k8s集群可能是两个vlan里面的,但是它们网络是通的,你也可以组建成一个集群,但是要使用calico的BGP,如果只让它们三层可达的话,与BJP也是不可以用的,因为BJP使用的二层,因为数据源IP和目的IP都是pod的IP,而这个路由器并不知道这个源IP和目的IP应该发给谁,因为这没有写这个路由表信息,如果写了,理论上来讲节点不同网段也是可以通信的,那么不添加这个路由表的话,这个BGP是过不去的,那么这种情况下就得去启用ipip模式了,IPIP是linux内核的驱动程序,可以对数据包进行隧道,那么它看到两个不同的网络vlan1和vlan2,启动ipip模式也有个前提,它是属于4层的,因为它是基于现有的以太网的网络将你原来包里的原始IP,也是进行一次封装,因为现有的网络已经通了,三层的路由现实不同的vlan进行通信,所以通过tunl0解包,这个tunl0类似于ipip模块,这个就跟vxlan的veth类似,所以这个模式跟vxlan的模式大致是一样的
Pod 1 访问 Pod 2大致流程如下:
数据包从容器1出到达Veth Pair另一端(宿主机上,以cali前缀开头);
进入IP隧道设备(tunl0),由Linux内核IPIP驱动封装在宿主机网络的IP包中(新的IP包目的地之是原IP包的下一跳地址,即192.168.31.63),这样,就成了Node1 到Node2的数据包;
此时包的类型:
原始IP包:
源IP:10.244.1.10
目的IP:10.244.2.10
TCP:
源IP: 192.168.31.62
目的iP:192.168.32.63
那么这个IPIP本身和vxlan一样,工作在三层的,因为它用现在的以太网进行传输的,现在物理机传输的,路由器这个方面达到另一个vlan2,这个网络之间肯定是可以访问的,那么IPIP之间就能和三层的路由到目的另一个vlan中
数据包经过路由器三层转发到Node2;
Node2收到数据包后,网络协议栈会使用IPIP驱动进行解包,从中拿到原始IP包;
然后根据路由规则,根据路由规则将数据包转发给cali设备,从而到达容器2。
路由表:
[root@k8s-node1 ~]# ip route
default via 10.4.7.1 dev eth0 proto static metric 100
10.4.7.0/24 dev eth0 proto kernel scope link src 10.4.7.12 metric 100
10.244.113.128/26 via 10.4.7.11 dev tunl0 proto bird onlink
10.244.203.64/26 via 10.4.7.21 dev tunl0 proto bird onlink
blackhole 10.244.245.0/26 proto bird
10.244.245.1 dev calie1d6cd79d22 scope link
10.244.245.2 dev calid6a1fb2294e scope link
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
不难看到,当 Calico 使用 IPIP 模式的时候,集群的网络性能会因为额外的封包和解包工作而下降。所以建议你将所有宿主机节点放在一个子网里,避免使用 IPIP。
8、CNI 网络方案优缺点及最终选择
先考虑几个问题:
需要细粒度网络访问控制?这个flannel是不支持的,calico支持,所以做多租户网络方面的控制ACL,那么要选择calico
追求网络性能?这两个方案无疑是flannel和calico的路由方案是最高的,也就是flannel的host-gw和calico的BGP。
服务器之前是否可以跑BGP协议?很多的公有云是不支持跑BGP协议的,那么使用calico的BGP模式自然是不行的。
集群规模多大?如果规模不大,100节点以下维护起来比较方面之间可以使用flannel就可以
是否有维护能力?calico的路由表很多,而且走BGP协议,一旦出现问题排查起来也比较困难,上百台的,路由表去排查也是很麻烦,这个具体的需求也是跟自己饿的情况而定。
小话题:办公网络与k8s网络如何互通
现在架构也是微服务也比较流行,测试环境是在k8s集群中,开发人员是在办公网络中,网络显然是不同的,微服务是使用pod去通信的,办公网络访问pod自然是不同的,那么就需要将这个网络打通。
比如开发开发了一个微服务,不想启动整套,注册中心,服务发现了等等,但是它只想跑一个模块,直接调用测试环境中注册中心数据库了直接去用,那么就要考虑将这个网络打通了
比如一块是办公网络,然后开发人家使用的开发电脑,另一块是k8s集群跑着很多的微服务,那么pod的ip是10.244.0.0/16这个网段,那么service是10.0.0.10/24,宿主机是172.17.0.0/24,那么办公网络是192.168.1.0/24
----------------------------------
| 「pc」 「pc」 |
| 「pc」 「pc」 | 办公网络:192.168.1.0/24
| 办公 |
----------------------------------
----------------------------------
| 「pod」 「pod」 | pod IP 10.244.0.0/16
| 「pod」 「pod」 | service IP 10.0.0.10/24
| k8s集群 | 宿主机 IP 172.17.0.0/24
----------------------------------
那么办公网络去访问pod的IP肯定是不通的,除非做了路由,即使办公网络和k8s的宿主机网络能通,但是也需要做一些特殊的转发策略,现在解决的问题是办公网络可以访问pod的IP,访问service的IP,也就是让k8s内部的网络暴露出来,办公网络就像之间访问虚拟机一样去访问,所以去解决这个问题,分为两种情况。
第一种情况,k8s集群测试环境在办公网络的子网里面,那么这个实现就比较简单了,只需要在子网中上层的路由器添加一个规则就行了,ip route add,目的IP为10.244.0.0/16,到达的下一跳via为其中的k8s节点,比如k8s-node1 dev 接口A
ip route add 10.244.0.0/16 via <k8s-node1> dev A
添加这么一个规则,办公网络就能直接访问k8s的节点,直接就能访问pod IP,也就是下一跳地址之后,就能直接访问都podIP了。
第二种情况,k8s集群与办公网络在不同VLAN中,不同机房
前提是k8s集群与办公网络是互通的,三层可达
有两种方案1)在路由器上添加路由表,10.244.0.0/16 <k8s-node1>
2) 采用BGP,如果三层的路由支持BGP协议的话,直接就可以让路由器BGP与路由反射器BGP建立连接,这样的话路由器上的BGP就能获取到了k8s上的路由表信息了,然后经过下一跳来转发到目的的node的pod中。
总结:只要是不同vlan,必须是三层可达,能在上层的路由器上,访问集群的网段,pod网段还是service网段,一定要告知它,帮它转发到下一跳是谁,下一跳如果是目的的节点,那就直接转发数据包。
4.5 网络策略
1、为什么需要网络隔离?
CNI插件插件解决了不同Node节点Pod互通问题,从而形成一个扁平化网络,默认情况下,Kubernetes 网络允许所有 Pod 到 Pod 的流量,也就是在k8s网络中都是相互ping通,都是可以访问传输数据包的,在一些场景中,我们不希望Pod之间默认相互访问,例如:
应用程序间的访问控制。例如微服务A允许访问微服务B,微服务C不能访问微服务A
开发环境命名空间不能访问测试环境命名空间Pod
当Pod暴露到外部时,需要做Pod白名单
多租户网络环境隔离
比如这个命名空间与其他的命名空间进行互通,将你的pod暴露在外面了暴露在办公网络中了,为了方便,但是提高一些安全性,那么谁能访问,谁不能访问,直接做白名单也可以,然后微服务部署的也比较多,也希望做一些隔离,那么也可以使用网络隔离,那么就可以使用network policy进行pod网络的隔离。
既然说到了网络的限制也就是ACP访问控制,自然是有两个方向,一个是入口方向,一个是出口方向
一个用户去访问虚拟机,客户端访问这是入方向,对于客户端来说这是出方向,虚拟机访问外网自然是出方向,做ACL一个是入方向,一个是出方向,我们针对pod做谁能访问pod,pod能访问谁。
所以,我们需要使用network policy对Pod网络进行隔离。支持对Pod级别和Namespace级别网络访问控制。
Pod网络入口方向隔离
基于Pod级网络隔离:只允许特定对象访问Pod(使用标签定义),允许白名单上的IP地址或者IP段访问Pod
基于Namespace级网络隔离:多个命名空间,A和B命名空间Pod完全隔离。
Pod网络出口方向隔离
拒绝某个Namespace上所有Pod访问外部
基于目的IP的网络隔离:只允许Pod访问白名单上的IP地址或者IP段
基于目标端口的网络隔离:只允许Pod访问白名单上的端口
2、网络策略概述
一个NetworkPolicy例子:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
配置解析:
podSelector:用于选择策略应用到的Pod组,也就是对哪个pod进行网络隔离
policyTypes:其可以包括任一Ingress,Egress或两者。该policyTypes字段指示给定的策略用于Pod的入站流量、还是出站流量,或者两者都应用。如果未指定任何值,则默认值为Ingress,如果网络策略有出口规则,则设置egress,这个也就是入口方向和出口方向,限制入口或者限制出口方向
Ingress:from是可以访问的白名单,可以来自于IP段、命名空间、Pod标签等,ports是可以访问的端口。入口的策略是什么,入口的策略是这么限制的,
Egress:这个Pod组可以访问外部的IP段和端口。出的策略是这么限制的,pod出去是这么限制的,是访问百度的ip,还是访问其他的ip,是哪个ip段还是哪个命名空间。
在172.17.0.0/16这个大子网里面除了这个172.17.1.0/24这个不能访问,其他的都能访问,命名空间也是可以哪个可以访问,哪个不可以访问。
cidr: 172.17.0.0/16
except:
根据上面的yaml的规则来说,结构是这样的,对pod命名空间的携带标签role:db的pod进行网络隔离,只有172.17.0.0/16子网下除了172.17.1.0/24其他的都可以访问我,
kubectl create deployment nginx --image=nginx
kubectl run client1 --generator=run-pod/v1 --image=busybox --command -- sleep 36000
kubectl run client2 --generator=run-pod/v1 --image=busybox --command -- sleep 36000
kubectl get pods --show-labels
需求:将default命名空间携带run=nginx标签的Pod隔离,只允许default命名空间携带run=client1标签的Pod访问80端口
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
app: nginx
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
project: default
- podSelector:
matchLabels:
run: client1
ports:
- protocol: TCP
port: 80
隔离策略配置:
Pod对象:default命名空间携带run=nginx标签的Pod
允许访问端口:80
允许访问对象:default命名空间携带run=client1标签的Pod
拒绝访问对象:除允许访问对象外的所有对象
测试查看现在client的网络是不能通信的,而这个组件calico支持,像其他的flannel组件是不支持的
命名空间隔离
需求:default命名空间下所有pod可以互相访问,但不能访问其他命名空间Pod,其他命名空间也不能访问default命名空间Pod。
[root@k8s-master1 ~]# kubectl run client3 --generator=run-pod/v1 --image=busybox -n kube-system --command -- sleep 36000
default的pod都能互通,kube-system的pod不能与default的pod互通,default也不能访问kube-system的pod,也就是自己隔离到了自己网络命名空间下了
现在我们实现一下这个需求,创建好之后因为我们没有限制网络的隔离,所以默认情况下不同命名空间的网络也是互通的
创建一个default下的pod名字为nginx的pod
[root@k8s-master1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-86c57db685-cv627 1/1 Running 0 5m57s 10.244.113.132 k8s-master1 <none> <none
[root@k8s-master1 ~]# kubectl exec -it client3 -n kube-system /bin/sh
/ # ping 10.244.113.132
PING 10.244.113.132 (10.244.113.132): 56 data bytes
64 bytes from 10.244.113.132: seq=0 ttl=62 time=3.105 ms
64 bytes from 10.244.113.132: seq=1 ttl=62 time=13.029 ms
创建网络隔离yaml,实现default和kube-system下的pod不能互通
[root@k8s-master1 ~]# cat ns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-from-other-namespaces
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
[root@k8s-master1 ~]# kubectl apply -f ns.yaml
networkpolicy.networking.k8s.io/deny-from-other-namespaces created
测试之后现在已经无法访问default下的pod了,这也就实现的网络隔离,相反反过来也不通
[root@k8s-master1 ~]# kubectl exec -it client3 -n kube-system /bin/sh
/ # ping 10.244.113.132
PING 10.244.113.132 (10.244.113.132): 56 data bytes
podSelector: {}:default命名空间下所有Pod
from.podSelector: {} : 如果未配置具体的规则,默认不允许
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。