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容器网络标准

1Docker的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原生支持。

2CoreOS的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

redis.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

内容编辑:云安全实验室 李欣 责任编辑:肖晴

本文分享自微信公众号 - 绿盟科技研究通讯(nsfocus_research),作者:云安全实验室

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 容器安全的全球威胁分析

    容器技术被广泛接受和使用的同时,容器以及容器运行环境的安全成为了亟待研究和解决的问题。为了进一步了解容器以及容器环境的安全威胁,为使用容器的用户提供安全防护建议...

    绿盟科技研究通讯
  • 【云原生攻防研究】容器逃逸技术概览

    近年来,容器技术持续升温,全球范围内各行各业都在这一轻量级虚拟化方案上进行着积极而富有成效的探索,使其能够迅速落地并赋能产业,大大提高了资源利用效率和生产力。随...

    绿盟科技研究通讯
  • Docker安全配置分析

    容器技术基于容器主机操作系统的内核,通过对CPU、内存和文件系统等资源的隔离、划分和控制,实现进程之间透明的资源使用。因此,容器主机的安全性对整个容器环境的安全...

    绿盟科技研究通讯
  • 前方高能!保护Docker容器须知

    容器技术(尤其是Docker)正继续以其自有的方式在企业中发展着。它们与其他任何技术一样,IT专业人士们的任务就是为确保Docker容器的安全性而制定出一份策略...

    静一
  • 如何理解LXC与Docker之间的主要区别

    这篇文章从两个部分来探讨LXC,LXC和Docker的容器托管,以及轻便的容器技术将取代虚拟技术的可能性。 LXC有可能会改变我们如何运行和缩放应用程序。Dr...

    小小科
  • TKE操作指南 - 部署wordpress 容器服务上(十)

    根据TKE操作指南 - 自动化构建,生成Wordpress Docker业务镜像(四)我们已经知道道nginx和php wordpress业务镜像地址

    亮哥说TKE
  • 如何选择合适的云计算顾问

    随着云计算应用的增长,云计算咨询业务也随之大幅增长。这是不可避免的,也是必要的。许多企业最初都是自己将业务迁移到云端,但却因此遇到了一些问题和困难。他们不了解云...

    静一
  • Nginx 容器教程

    春节前,我看到 Nginx 加入了 HTTP/2 的 server push 功能,就很想试一下。 正好这些天,我在学习 Docker,就想到可以用 Nginx...

    ruanyf
  • Docker容器构建过程的安全性分析

    DevOps概念的流行跟近些年微服务架构的兴起有很大关系,DevOps是Dev(Development)和Ops(Operations)的结合,Dev负责开发,...

    小小科
  • 为什么说Linux容器对于物联网而言很重要

    Linux容器已成为云开发和部署工作流中的标准工具。使用它的好处有很多,包括跨平台的可移植性,最小的开销,以及开发人员对他们代码运行方式的更多控制。容器的普及率...

    Noah____________________

扫码关注云+社区

领取腾讯云代金券