Kubernetes 网络初探

栏目: 服务器 · 发布时间: 6年前

内容简介:随着容器技术的发展,越来越多的企业使用了容器,甚至将其应用于生产环境。作为容器编排工具的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模型示例如下所示:

Kubernetes 网络初探

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模型示例如下图所示:

Kubernetes 网络初探

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内,容器和容器之间的通信

Kubernetes 网络初探

同一个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之间的通信

Kubernetes 网络初探

同一个主机上不同的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之间通信

Kubernetes 网络初探

跨主机的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容器网络。

整个集群的网络拓扑图如下所示:

Kubernetes 网络初探

在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 网络初探》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Clean Code

Clean Code

Robert C. Martin / Prentice Hall / 2008-8-11 / USD 49.99

Even bad code can function. But if code isn’t clean, it can bring a development organization to its knees. Every year, countless hours and significant resources are lost because of poorly written code......一起来看看 《Clean Code》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具