内容简介:随着容器技术的发展,越来越多的企业使用了容器,甚至将其应用于生产环境。作为容器编排工具的Kubernetes同样得到了广泛关注。在容器环境中,尤其是容器集群环境,网络通常被认为是相对较复杂的部分。本文将以Kubernetes为例,详细解读容器集群的网络。
1.引言
随着容器技术的发展,越来越多的企业使用了容器,甚至将其应用于生产环境。作为容器编排 工具 的Kubernetes同样得到了广泛关注。
在容器环境中,尤其是容器集群环境,网络通常被认为是相对较复杂的部分。本文将以Kubernetes为例,详细解读容器集群的网络。
2.Kubernetes网络演进
v1.1版本之前,没有标准只有假设(假设每个Pod都有独立的IP,并且所有的Pod都处在一个直连、扁平的网络中,同一Pod内的所有容器共享网络命名空间),用户在部署Kubernetes之前需要先规划好容器互联方案;
v1.1版本,开始全面采用CNI网络标准;
v1.2版本后,内置网络驱动kubernet[1],实现了IP地址的自动分配;
v1.3版本后,开始支持网络策略设置;
v1.7版本后,结束网络策略的Beta阶段,正式成为API的一部分。
3. Linux容器网络标准
1 Docker的CNM
CNM(Container Network Model)是 Docker 提出来的网络规范,目前已被Cisco Contiv, Kuryr, Open Virtual Networking (OVN), Project Calico, VMware 和 Weave等公司和项目采纳。CNM模型示例如下所示:
Libnetwork是CNM的原生实现。它为Docker Daemon和网络驱动程序提供了接口,每个驱动程序负责管理它所拥有的网络并为该网络提供IPAM等服务。根据网络驱动提供者,可以将驱动分为原生驱动和第三方驱动。其中None、Bridge、Overlay以及MACvlan属于原生驱动,被Docker原生支持。
2 CoreOS的CNI
CNI(Container Network Interface)是托管于云原生计算基金会(Cloud Native Computing Foundation,CNCF)的一个项目,项目地址为https://github.com/containernetworking/CNI。它是由一组用于配置 Linux 容器的网络接口规范和库组成,同时还包含了一些插件。
CNI仅关心容器创建时的网络分配以及容器被删除时释放网络资源,CNI模型示例如下图所示:
CNI规定了容器runtime和网络插件之间的接口标准,此标准通过Json语法定义CNI插件所需要提供的输入和输出。CNI非常简单,每个CNI插件只需实现ADD/DEL操作。
4. Kubernetes网络模型
目前Kubernetes网络采用的是CNI标准,对于为什么不采用CNM标准,在Kubernetes的官方blog文档有提到https://Kubernetes.io/blog/2016/01/why-Kubernetes-doesnt-use-libnetwork/。归纳起来核心的原因就是Docker CNM对Docker的依赖很大,而Docker在网络方面的设计又和Kubernetes的理念不一致,而CNI对开发者的约束少,更开放,且符合Kubernetes的设计理念,在对第三方插件的支持上CNI做的比CNM好。
CNI的基本思想是:在创建容器时,先创建好网络命名空间,然后调用CNI插件为这个命名空间配置网络,最后再启动容器内的进程。对应到Kubernetes里的操作为:
① 创建pause容器生成network namespace;
② 调用CNI driver根据配置调用具体的CNI插件;
③ CNI插件给pause容器配置网络;
④ 启动容器,共享pause容器的网络。
在Kubernetes中,Pod是运行应用或服务的最小单元,其设计理念是在一个Pod中支持多个容器共享网络地址和文件系统。Kubernetes集群中的Pod通常会涉及到以下三种通信:
-
同一个Pod内,容器和容器之间的通信
同一个Pod内容器之间的通信,由于其共享网络命名空间、共享Linux协议栈,因此它们之间的通信是最简单的,这些容器好像是运行在同一台机器上,直接使用Linux本地的IPC进行通信,它们之间的互相访问只需要使用localhost加端口号就可以。
例如,使用test.yaml创建同属一个Pod的两个容器 redis 和nginx。
apiVersion: v1
kind: Pod
metadata:
name: redis-nginx
labels:
app: web
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
- name: nginx
image: nginx:1.9
ports:
- containerPort: 80
(小贴士:部分代码需滑动查看→ →)
# kubectl create -f test.yaml pod/redis-nginx created
进入redis容器,执行如下命令:
# ifconfig | grep inet | grep -v 127.0.0.1
inet 10.244.0.42 netmask 255.255.255.0 broadcast 0.0.0.0
# netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1/nginx: master pro
tcp6 0 0 :::6379 :::* LISTEN -
进入nginx容器,执行如下命令:
# ifconfig | grep inet | grep -v 127.0.0.1
inet addr:10.244.0.42 Bcast:0.0.0.0 Mask:255.255.255.0
# netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1/nginx: master pro
tcp6 0 0 :::6379 :::* LISTEN -
由上可知两个容器不但IP地址相同,里面开启的进程也一致,如果要在nginx容器里访问redis服务只需使用localhost加端口6379即可。
# telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
^]
telnet>
-
同一个主机内不同Pod之间的通信
同一个主机上不同的Pod通过veth连接在同一个docker0网桥上,每个Pod从docker0动态获取IP地址,该IP地址和docker0的IP地址是处于同一网段的。这些Pod的默认路由都是docker0的IP地址,所有非本地的网络数据都会默认送到docker0网桥上,由docker0网桥直接转发,相当于一个本地的二层网络。
例如,在同一主机上使用如下nginx.yaml以及redis.yaml创建两个不同Pod。
nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9
ports:
- containerPort: 80
nodeSelector:
node: node2
r edis.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis
labels:
app: redis
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
nodeSelector:
node: node2
# kubectl create -f /tmp/nginx.yaml
pod/nginx created
# kubectl create -f /tmp/redis.yaml
pod/redis created
在nginx容器里执行:
# ethtool -S eth0
NIC statistics:
peer_ifindex: 156
在redis容器里执行:
# ethtool -S eth0
NIC statistics:
peer_ifindex: 157
在主机上执行:
# ip link | grep 156
156: vetha75b9e88@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
# ip link | grep 157
157: veth4afe37c8@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
# brctl show docker0
bridge name bridge id STP enabled interfaces
docker0 8000.0a580af40001 no veth1be503c2
veth36f101c1
veth4afe37c8
vetha75b9e88
其中veth4afe37c8和vetha75b9e88正是docker0网桥上的接口,veth4afe37c8与redis容器里的eth0构成一对虚拟接口对,vetha75b9e88与nginx里的eth0构成一对虚拟接口对,容器nginx与容器redis的通信过程为 :
数据包通过nginx容器的eth0发出,经由veth对到达docker0网桥上的vetha75b9e88接口,由docker0网桥直接转发出去到达redis容器的eth0接口。
-
跨主机Pod之间通信
跨主机的Pod之间通信较复杂,每个Pod的地址和其所在主机的docker0在同一个网段,而docker0和主机的物理网络是属于不同网段的,对于Kubernetes的网络模型来说本身是支持跨主机的Pod通信,但是默认却没有提供这种网络实现,需要借助于诸如Flannel、Calico等第三方插件来实现跨主机的Pod通信。具体的通信过程详见第5节的Flannel网络插件。
5. Flannel网络插件
下面将采用Flannel插件,详细解析Kubernetes集群中不同主机Pod间的通信过程。
2节点Kubernetes集群(node1和node2),执行kubectl run创建2个Pod,2个Pod分别起在node1和node2上。
# kubectl run nginx-test --image=nginx --replicas=2 --port=9097
deployment.apps/nginx-test created
在node1上执行docker ps | grep nginx
# docker ps | grep nginx
dcf6f33bcf8e nginx "nginx -g 'daemon of…" 15 seconds ago Up 14 seconds k8s_nginx-test_nginx-test-7778bb6848-w5nz6_default_1e164ac2-a44a-11e8-a15c-c81f66f3c543_0
f2bb1b2b7aa2 k8s.gcr.io/pause:3.1 "/pause" 23 seconds ago Up 21 seconds k8s_POD_nginx-test-7778bb6848-w5nz6_default_1e164ac2-a44a-11e8-a15c-c81f66f3c543_0
由上可知,两节点分别启动了2个容器, pause容器和nginx容器,其中node1上nginx容器id为dcf6f33bcf8e。
# docker inspect dcf6f33bcf8e | grep NetworkMode
"NetworkMode": "container:f2bb1b2b7aa29692b98f3a4a83f3c812e4ca743971125e977cae3b48c82f67c1"
在创建的这个nginx-test Pod中,nginx容器的NetworkMode是container:f2bb1b2b7aa2,这里的container id正是pause容器的id,因此nginx容器与pause容器共享网络命名空间。
那么,pause容器的IP是从哪里分配得到的呢?这就得依赖于Kubernetes选用的Flannel插件。对于Flannel插件而言,有两种为Pod分配IP的方式,一种是直接与Docker结合,通过docker0网桥来为Pod内容器分配IP;另一种是采用Kubernetes推荐的基于CNI的方式来为pause容器分配IP。 这里只介绍基于CNI的方式,容器IP的分配步骤如下所示:
① kubelet先创建pause容器生成网络命名空间;
② 使用CNI drvier调用具体的CNI插件Flannel;
③ Flannel给pause容器配置网络;
④ Pod中的其它容器共享pause容器网络。
整个集群的网络拓扑图如下所示:
在node1和node2上执行分别执行route -n
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
......
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.8.0 10.244.8.0 255.255.255.0 UG 0 0 0 flannel.1
......
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
.....
10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1
10.244.8.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
.....
由上可以发现在集群节点上分别多了cni0以及flannel.1网络接口,同时增加了2条路由规则,那么node1上的nginx容器(10.244.0.160)怎么与node2上的nginx容器(10.244.8.143)进行通信呢?(以下操作均在node1上执行)
# ip -d link show flannel.1
27: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether e2:d2:94:c8:45:40 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 192.168.19.13 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 addrgenmode eui64
由上可以看出flannel.1是一个vxlan网络设备,这也就与flannel的backend mechanism(udp、vxlan等)一致,来完成跨主机通信。
执行ip -d link show cni0
# ip -d link show cni0
28: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 0a:58:0a:f4:00:01 brd ff:ff:ff:ff:ff:ff promiscuity 0
bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q addrgenmode eui64
由上可以看出cni0是一个Linux网桥设备
# brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.0a580af40001 no veth021347a8
veth9ed8c967
……
发现cni0网桥上挂载了veth021347a8、veth9ed8c967等接口
我们进入到nginx容器,在容器里执行ethtool -S eth0
# ethtool -S eth0
NIC statistics:
peer_ifindex: 882
由上可知和nginx容器里的eth0对应的veth设备的 peer_ifindex为882,因此在node1主机上执行ip link | grep 882
# ip link | grep 882
882: veth9ed8c967@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
由上可知veth的名称为 veth9ed8c967,这不正是挂载在cni0网桥上的接口么,由此可知此虚拟接口对一端在nginx容器里,另一端桥接在cni0网桥上。
因此整个通信过程为:
数据包通过node1上nginx容器的eth0发出,经由veth对到达cni0网桥上的veth9ed8c967接口,经由cni0网桥发送出去,查路由规则可知,到10.244.8.0/24网段的数据包需要使用flannel.1设备,flanneld进程接收到flannel.1发过来的数据,查etcd可知10.244.8.0/24子网在主机node2上,然后经过node1上的eth0将数据包转发出去, node2上flanneld接收到数据包,解包后转发给相应的容器。
6.小结
Kubernetes网络采用CNI标准,目前支持CNI标准的第三方插件比较多,由于篇幅有限本文只针对Flannel插件展开,希望通过笔者的介绍让您对Kubernetes中Pod间的通信有更进一步的认识。
参考文献:
[1]https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/#kubenet
[2]https://Kubernetes.io/docs/concepts/cluster-administration/networking/
[3]https://github.com/containernetworking/CNI/blob/master/SPEC.md
[4]https://github.com/coreos/flannel
内容编辑:云安全实验室 李欣 责任编辑:肖晴
往 期回顾
本公众号原创文章仅代表作者观点,不代表绿盟科技立场。所有原创内容版权均属绿盟科技研究通讯。未经授权,严禁任何媒体以及微信公众号复制、转载、摘编或以其他方式使用,转载须注明来自绿盟科技研究通讯并附上本文链接。
关于我们
绿盟科技研究通讯由绿盟科技创新中心负责运营,绿盟科技创新中心是绿盟科技的前沿技术研究部门。包括云安全实验室、安全大数据分析实验室和物联网安全实验室。团队成员由来自清华、北大、哈工大、中科院、北邮等多所重点院校的博士和硕士组成。
绿盟科技创新中心作为“中关村科技园区海淀园博士后工作站分站”的重要培养单位之一,与清华大学进行博士后联合培养,科研成果已涵盖各类国家课题项目、国家专利、国家标准、高水平学术论文、出版专业书籍等。
我们持续探索信息安全领域的前沿学术方向,从实践出发,结合公司资源和先进技术,实现概念级的原型系统,进而交付产品线孵化产品并创造巨大的经济价值。
长按上方二维码,即可关注我们
以上所述就是小编给大家介绍的《Kubernetes 网络初探》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 深入浅出Kubernetes网络:容器网络初探
- 卷积神经网络初探——LeNet-5的原理与手写数字识别的实现
- 当量子计算遇上神经网络与深度学习,QNN初探( Quantum Neural Networks),David 9的量子计算系列#1
- 前后端完全分离初探
- thrift 初探
- Java反射机制初探
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
SHA 加密
SHA 加密工具
HSV CMYK 转换工具
HSV CMYK互换工具