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

查看所有标签

猜你喜欢:

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

知识的边界

知识的边界

[美] 戴维·温伯格 / 胡泳、高美 / 山西人民出版社 / 2014-12-1 / 42.00元

大数据时代反思知识 因为事实不再是事实,专家随处可见 所有确定性都被连根拔起,话题再无边界,没有人对任何事情能达成一致。 在互联网的引领下,知识现在已经具有了社交性,流动且开放。温伯格向我们展示了这些特点如何可以为我们所用。 ——马克•贝尼奥夫(云计算之父,著有《云攻略》) 这本富有洞见的著作,奠定了温伯格作为数字时代最重要的思想家之一的地位。如果你想要理解信息洪流涌......一起来看看 《知识的边界》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

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

HSV CMYK互换工具