k8s企业级DevOps实践-容器集群网络
本文会再谈Docker网络、Pod网络、CNI模式、CNI选型。
Docker网络
一览表:
容器网络模式 | 简介 |
---|---|
bridge | 容器具有独立的network namespace,会将容器连接到docker0虚拟网桥,并配置IP地址,docker的默认网络模式。 |
host | 容器没有独立的network namespace,和宿主机共用网络。 |
none | 容器具有独立的network namespace,与宿主机网络隔离,但并没有对其进行任何网络设置。 |
container | 容器和某一个已存在的容器共享network namespace。 |
bridge
bridge模式是 docker 的默认网络模式,不写–net
参数,就是bridge模式。使用docker run -p
时,docker 实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables -t nat -vnL
查看。
1 | # 创建一个新的 Docker 网络。-d参数指定 Docker 网络类型 |
host
在这种模式下,容器不会获得独立的Network Namespace
,与宿主机操作系统共用一个Network Namespace
。即:容器不会虚拟自己的网卡,不会配置自己的IP。宿主机的IP就是容器的IP,容器的端口就是宿主机的端口,所以同一个宿主机下的多个容器不能暴露同一个端口。
1 | $ docker run -tid --net=host --name docker_host1 ubuntu-base:v3 |
none
这种模式一般又被称为“隔离模式”,常被用于在容器内完成CPU密集型任务,计算完成后将结果保留到磁盘上。在此过程中,不与外界进行网络连接,从网络层面(断开)保证计算执行过程的安全性。
1 | $ docker run -i -t --rm --net=none base /bin/bash |
container
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。
1 | # 使用下面的命令创建出来的容器都是bridge模式,二者的ip以及网络配置都是一样的(默认bridge模式) |
小结:
docker的网络模式分为4种,最常用的为bridge
和host
模式。bridge
模式通过docker0
网桥,启动容器的时候通过创建一对虚拟网卡,将容器连接在桥上,同时维护了虚拟网卡与网桥端口的关系,实现容器间的通信。容器与宿主机之间的通信通过iptables
端口映射的方式,docker利用iptables
的PREROUTING
和POSTROUTING
的nat功能,实现了SNAT与DNAT,使得容器内部的服务被完美的保护起来。
Pod网络和CNI插件
Pod相当于是k8s平台中的虚拟机,它是K8s的最小调度单元。Pod网络就是能够保证K8s集群中的所有Pods(包括同一节点上的,也包括不同节点上的Pods),逻辑上看起来都在同一个平面网络内,能够相互做IP寻址和通信的网络,下图是Pod网络的简化概念模型:
同一节点上的Pod网络
前面提到,Pod相当于是K8s中的“虚拟机”,实际一个Pod中可以住一个或者多个(大多数场景住一个)应用容器,这些容器共享Pod的网络栈和其它资源如Volume。那么什么是共享网络栈?同一节点上的Pod之间如何寻址和互通?下图样例来解释:
网络层面:
上图节点上展示了Pod网络所依赖的3个网络设备:
eth0
是节点主机上的网卡,这个是支持该节点流量出入的设备,也是支持集群节点间IP寻址和互通的设备。docker0
是一个虚拟网桥,可以简单理解为一个虚拟交换机,它是支持该节点上的Pod之间进行IP寻址和互通的设备。veth0
则是Pod1的虚拟网卡,是支持该Pod内容器互通和对外访问的虚拟设备。docker0
网桥和veth0
网卡,都是linux支持和创建的虚拟网络设备。
容器层面:
- 上图Pod1内部住了3个容器,它们都共享一个虚拟网卡
veth0
。- 内部的这些容器可以通过
localhost
相互访问 - 且它们三个不能在同一端口上同时开启服务,否则就会有端口冲突,这就是共享网络栈的意思。
- 内部的这些容器可以通过
- Pod1中还有一个比较特殊的叫
pause
的容器,这个容器运行的唯一目的是为Pod建立共享的veth0
网络接口。如果你SSH到K8s集群中一个有Pod运行的节点上去,然后运行docker ps
,可以看到通过pause
命令运行的容器。
Pod的IP是由docker0
网桥分配的,例如上图docker0
网桥的IP是172.17.0.1
,它给第一个Pod1分配IP为172.17.0.2
。如果该节点上再启一个Pod2,那么相应的分配IP为172.17.0.3
,如果再启动Pod可依次类推。因为这些Pods都连在同一个网桥上,在同一个网段内,它们可以进行IP寻址和互通,如下图所示:
节点内Pod网络在
172.17.0.0/24
这个地址空间内,而节点主机在10.100.0.0/24
这个地址空间内,也就是说Pod网络和节点网络不在同一个网络内,不同节点间的Pod该如何IP寻址和互通呢?
不同节点间的Pod网络
假设我们有两个节点主机:
host1(10.100.0.2
)和host2(10.100.0.3
),它们在10.100.0.0/24
这个地址空间内。
host1上有一个PodX(172.17.0.2
),host2上有一个PodY(172.17.1.3
),Pod网络在172.17.0.0/16
这个地址空间内。
注意,Pod网络的地址,是由K8s统一管理和分配的,保证集群内Pod的IP地址唯一。我们发现节点网络和Pod网络不在同一个网络地址空间内,那么host1上的PodX该如何与host2上的PodY进行互通?
实际上不同节点间的Pod网络互通,有很多技术实现方案,底层的技术细节也很复杂。为了简化描述,这里大致吧这些方案大体分为两类,一类是路由方案,另外一类是覆盖(Overlay)网络
方案。当然还有第三类Underlay模式
,这一类严重依赖底层网络。只有用公有云商用k8s或者idc中(bilibili机房中容器集群用了mac vlan)时才会碰到。例如金山。。。今年四月才支持VPC-ENI模式(即podip分在单独的子网上)
路由方案
如果底层的网络是你可以控制的,比如说企业内部自建的数据中心,并且你和运维团队的关系比较好,可以采用路由方案,如下图所示:
这个方案简单理解,就是通过路由设备为K8s集群的Pod网络单独划分网段,并配置路由器支持Pod网络的转发。例如上图中,对于目标为172.17.1.0/24
这个范围内的包,转发到10.100.0.3
这个主机上,同样,对于目标为172.17.0.0/24
这个范围内的包,转发到10.100.0.2
这个主机上。当主机的eth0接口接收到来自Pod网络的包,就会向内部网桥转发,这样不同节点间的Pod就可以相互IP寻址和通信。这种方案依赖于底层的网络设备,但是不引入额外性能开销。
覆盖(Overlay)网络方案
如果底层的网络是你无法控制的,比如说公有云网络,或者企业的运维团队不支持路由方案,可以采用覆盖(Overlay)网络方案,如下图所示:
所谓覆盖网络,就是在现有网络之上再建立一个虚拟网络,实现技术有很多,例如flannel/weavenet等等,这些方案大都采用隧道封包技术。简单理解,Pod网络的数据包,在出节点之前,会先被封装成节点网络的数据包,当数据包到达目标节点,包内的Pod网络数据包会被解封出来,再转发给节点内部的Pod网络。这种方案对底层网络没有特别依赖,但是封包解包会引入额外性能开销。
NB的CNI插件介绍
考虑到Pod网络实现技术众多,为了简化集成,K8s支持CNI(Container Network Interface)
标准,不同的Pod网络技术可以通过CNI插件形式和K8s进行集成。节点上的Kubelet通过CNI标准接口操作Pod网路,例如添加或删除网络接口等,它不需要关心Pod网络的具体实现细节。
K8s 通过 CNI 配置文件来决定使用什么 CNI。
基本的使用方法为:
- 首先在每个结点上配置 CNI 配置文件(/etc/cni/net.d/xxnet.conf),其中 xxnet.conf 是某一个网络配置文件的名称;
- 安装 CNI 配置文件中所对应的二进制插件;
- 在这个节点上创建 Pod 之后,Kubelet 就会根据 CNI 配置文件执行前两步所安装的 CNI 插件;
上步执行完之后,Pod 的网络就配置完成了。
具体的流程如下图所示:
在集群里面创建一个 Pod 的时候:
- 首先会通过 apiserver 将 Pod 的配置写入。apiserver 的一些管控组件(比如 Scheduler)会调度到某个具体的节点上去。
- Kubelet 监听到这个 Pod 的创建之后,会在本地进行一些创建的操作。
- 当执行到创建网络这一步骤时,首先它会读取刚才我们所说的配置目录中的配置文件,配置文件里面会声明所使用的是哪一个插件。
- 然后去执行具体的 CNI 插件的二进制文件,再由 CNI 插件进入 Pod 的网络空间去配置 Pod 的网络。
- 配置完成之后,Kuberlet 也就完成了整个 Pod 的创建过程,这个 Pod 就在线了。
大家可能会觉得上述流程有很多步(比如要对 CNI 配置文件进行配置、安装二进制插件等等),看起来比较复杂。
但如果我们只是作为一个用户去使用 CNI 插件的话就比较简单,因为很多 CNI 插件都已提供了一键安装的能力。以我们常用的 Flannel 为例,如链接中所示:只需要我们使用 kubectl apply Flannel
的一个 Deploying
模板,它就能自动地将配置、二进制文件安装到每一个节点上去。
安装完之后,整个集群的 CNI 插件就安装完成了:二进制安装集群,并部署flannel实践
因此,如果我们只是去使用 CNI 插件的话,那么其实很多 CNI 插件已经提供了一键安装的脚本,无需大家关心 Kubernetes 内部是如何配置的以及如何调用 API 的。
NB的CNI插件选型
社区有很多的 CNI 插件,比如 Calico, flannel, Terway 等等。那么在一个真正具体的生产环境中,我们要选择哪一个 CNI 插件呢?
这就要从 CNI 的几种实现模式说起。我们需要根据不同的场景选择不同的实现模式,再去选择对应的具体某一个插件。
通常来说,CNI 插件可以分为三种:Overlay
、路由
及 Underlay
。
容器网络模式 | 简介 |
---|---|
Overlay | flannel、华为云CCE-容器隧道网络、阿里云ACK-flannel… |
路由 | 华为云CCE-VPC网络、百度云CCE-VPC路由… |
Underlay | Terway、百度云CCE_VPC-ENI、华为云CCE-云原生网络2.0、百度云CCE_VPC-Hybird、MAC VLAN、IP VLAN… |
Overlay 模式
的典型特征是容器独立于主机的 IP 段,这个 IP 段进行跨主机网络通信时是通过在主机之间创建隧道的方式,将整个容器网段的包全都封装成底层的物理网络中主机之间的包。该方式的好处在于它不依赖于底层网络;路由模式
中主机和容器也分属不同的网段,它与Overlay
模式的主要区别在于它的跨主机通信是通过路由打通,无需在不同主机之间做一个隧道封包。但路由打通就需要部分依赖于底层网络,比如说要求底层网络有二层可达的一个能力;Underlay 模式
中容器和宿主机位于同一层网络,两者拥有相同的地位。容器之间网络的打通主要依靠于底层网络。因此该模式是强依赖于底层能力的。
ps:每个模式具体的实现细节,这里暂时不赘述。后面有机会去专业做容器网络时可以再详细讲解。这里我们了解三种模式的区别,方便我们在云资源交付工作中为业务选型集群网络模式提供依据即可
了解了以上三种常用的实现模式之后,再根据自己的环境、需求判断可由哪一种模式进行实现,再在对应的模式中去找 CNI 插件。不过社区中有那么多插件,它们又都属于哪种模式?如何进行选择呢?怎么挑选适合自己的呢?我们可以从以下 3 个方面来考虑。
环境限制
不同环境中所支持的底层能力是不同的。
- 虚拟化环境(例如 OpenStack)中的网络限制较多,比如不允许机器之间直接通过二层协议访问,必须要带有 IP 地址这种三层的才能去做转发,限制某一个机器只能使用某些 IP 等。在这种被做了强限制的底层网络中,只能去选择
Overlay
的插件,常见的有Flannel-vxlan
,Calico-ipip
,Weave
等等;
- 物理机环境中底层网络的限制较少,比如说我们在同一个交换机下面直接做一个二层的通信。对于这种集群环境,我们可以选择
Underlay
或者路由模式的插件。Underlay
意味着我们可以直接在一个物理机上插多个网卡或者是在一些网卡上做硬件虚拟化;路由模式就是依赖于 Linux 的路由协议做一个打通。这样就避免了像vxlan
的封包方式导致的性能降低。这种环境下我们可选的插件包括clico-bgp
,flannel-hostgw
,sriov
等等;
- 公有云环境也是虚拟化,因此底层限制也会较多。但每个公有云都会考虑适配容器,提升容器的性能,因此每家公有云可能都提供了一些 API 去配置一些额外的网卡或者路由这种能力。在公有云上,我们要尽量选择公有云厂商提供的 CNI 插件以达到兼容性和性能上的最优。比如 Aliyun 就提供了一个高性能的 Terway 插件。
环境限制考虑完之后,我们心中应该都有一些选择了,知道哪些能用、哪些不能用。在这个基础上,我们再去考虑功能上的需求。
功能需求
- 首先是安全需求;
K8s 支持 NetworkPolicy
,就是说我们可以通过 NetworkPolicy
的一些规则去支持“Pod 之间是否可以访问”这类策略。但不是每个 CNI 插件都支持 NetworkPolicy
的声明,如果大家有这个需求,可以选择支持 NetworkPolicy
的一些插件,比如 Calico
, Weave
等等。
- 第二个是是否需要集群外的资源与集群内的资源互联互通;
大家的应用最初都是在虚拟机或者物理机上,容器化之后,应用无法一下就完成迁移,因此就需要传统的虚拟机或者物理机能跟容器的 IP 地址互通。为了实现这种互通,就需要两者之间有一些打通的方式或者直接位于同一层。此时可以选择 Underlay
的网络,比如 sriov
这种就是 Pod 和以前的虚拟机或者物理机在同一层。我们也可以使用 calico-bgp
,此时它们虽然不在同一网段,但可以通过它去跟原有的路由器做一些 BGP 路由的一个发布,这样也可以打通虚拟机与容器。
- 最后考虑的就是 K8s 的服务发现与负载均衡的能力。
K8s 的服务发现与负载均衡就是我们前面所介绍的 K8s 的 Service
,但并不是所有的 CNI
插件都能实现这两种能力。比如很多 Underlay
模式的插件,在 Pod 中的网卡是直接用的 Underlay
的硬件,或者通过硬件虚拟化插到容器中的,这个时候它的流量无法走到宿主机所在的命名空间,因此也无法应用 kube-proxy
在宿主机配置的规则。
这种情况下,插件就无法访问到 K8s 的服务发现。因此大家如果需要服务发现与负载均衡,在选择 Underlay
的插件时就需要注意它们是否支持这两种能力。
经过功能需求的过滤之后,能选的插件就很少了。经过环境限制和功能需求的过滤之后,如果还剩下 3、4 种插件,可以再来考虑性能需求。
性能需求
我们可以从 Pod 的创建速度和 Pod 的网络性能来衡量不同插件的性能。
- Pod 的创建速度
当我们创建一组 Pod 时,比如业务高峰来了,需要紧急扩容,这时比如说我们扩容了 1000 个 Pod,就需要 CNI
插件创建并配置 1000 个网络资源。Overlay
和路由模式在这种情况下的创建速度是很快的,因为它是在机器里面又做了虚拟化,所以只需要调用内核接口就可以完成这些操作。但对于 Underlay
模式,由于需要创建一些底层的网络资源,所以整个 Pod 的创建速度相对会慢一些。因此对于经常需要紧急扩容或者创建大批量的 Pod 这些场景,我们应该尽量选择 Overlay
或者路由模式的网络插件。
- Pod 的网络性能
主要表现在两个 Pod 之间的网络转发、网络带宽、PPS 延迟等这些性能指标上。Overlay
模式的性能较差,因为它在节点上又做了一层虚拟化,还需要去封包,封包又会带来一些包头的损失、CPU 的消耗等,如果大家对网络性能的要求比较高,比如说机器学习、大数据这些场景就不适合使用 Overlay
模式。这种情形下我们通常选择 Underlay
或者路由模式的CNI
插件。
小结
在我们云上交付时,各云考虑更好的网络规划、网工侧不用单独打通路由、网络转发性能
模式选择:Overlay
< 路由
< Underlay
各云差异化
- 阿里云:flannel < Terway
- 百度云:VPC路由 < VPC-ENI
- 华为云:容器隧道网络 < VPC网络(VPC路由) < 云原生网络2.0(类阿里Terway和百度VPC-ENI、仅CCE Turbo集群支持使用云原生网络2.0)
- 腾讯云:Cilium-Overlay(隧道网络) < GlobalRouter(VPC路由)< VPC-CNI(类阿里Terway和百度VPC-ENI)