每一个POD中都有一个特殊的容器且永远是POD中创建的第一个容器,只是我们查看的时候看不到,因为它完成作用就暂停了。这个容器就是Infra容器,使用汇编语言编写。当创建POD的时kubernetes先创建名称空间,然后把其中的网络名称空间和这个Infra关联,后面属于你的创建的容器都是通过Join的方式与这个Infra容器关联在一起,这样这些容器和Infra都属于一个网络名称空间,这也就是为什么POD中多个容器可以使用本地通信的原因,所以严格意义来说网络名称空间属于Infra。我们看下面的演示
我们启动POD

登陆Srv03

查看这个进程所属的名称空间,如下图:

上图说容器名称空间,其实是那个进程的名称空间,名称空间名称–>该名称空间的文件描述符。
就拿net来说,它为进程提供了一个完全独立的网络协议栈视图,包括网络接口、IPV4/6协议栈、IP路由表、防火墙规则等,也就是为进程提供一个独立的网络环境。
进入POD中的容器,查看这个名称空间

可以看到链接的文件的指向的都是一样的,我这里的POD只有一个容器,如果设置2个你就可以分别进入查看,指向都是一样的。
上面的结果其实为进程创建的名称空间,Infra容器抓住网络名称空间,然后POD中的容器JOIN到Infra中,这样POD中的容器就能使用这个网络名称空间。所以Kubernetes的网络插件考虑的不是POD中容器的网络配置,而是配置这个POD的Network Namespace。
Infra是为了给容器共享网络名称空间,当然也可以共享volume。
理解了为什么POD里面的容器可以进行本地通信的原因,我们就要向下看一层,是谁给这个Infra使用的网络名称空间提供的各种网络栈配置呢?
谁为容器提供的网络配置
我们这里以网桥模式为例来说明。容器就是进程,容器拥有隔离的网络名称空间,就意味着进程所在的网络名称空间也是隔离的且和外界不能联系,那么如何让实现和其他容器交互呢?你就理解为2台独立的主机要想通信就把它们连接到一个交换机上,通过一个中间设备来连接两个独立的网络。这个就是能起到交换机作用的网桥,Linux中就有这样一个虚拟设备。有了这样一个设备那么就要解决谁来创建、谁来连接两个容器的网络名称空间的问题。
Docker项目
在Dcoker项目中,这个网桥是由docker来创建的且安装完之后就会默认创建一个这样的设备,默认叫做docker0,这种网桥并不是docker的专利,是属于Linux内核就支持的功能,docker只是通过某些接口调用创建这样一个网桥并且命名为docker0。
网桥是一个二层设备,传统网桥在处理报文时只有2个操作,一个是转发;一个是丢弃。当然网桥也会做MAC地址学习就像交换机一样。但是Linux中的虚拟网桥除了具有传统网桥的功能外还有特殊的地方,因为运行虚拟网桥的是一个运行着Linux内核的物理主机,有可能网桥收到的报文的目的地址就是这个物理主机本身,所以这时候处理转发和丢弃之外,还需要一种处理方式就是交给网络协议栈的上层也就是网络层,从而被主机本身消化,所以Linux的虚拟网桥可以说是一个二层设备也可以说是一个三层设备。另外还有一个不同的地方是虚拟网桥可以有IP地址。
充当连接容器到网桥的虚拟介质叫做Veth Pair。这个东西一头连接在容器的eth0上,一头连接在网桥上,而且连接在网桥上的这一端没有网络协议栈功能可以理解为就是一个普通交换机的端口,只有一个MAC地址;而连接在容器eth0上的一端则具有网站的网络协议栈功能。
完成上述功能就是通过Libnetwork来实现的:
Libnetwork是CNM原生实现的,它为Docker daemon和网络驱动之间提供了接口,网络控制器负责将驱动和一个网络对接,每个驱动程序负责管理它拥有的网络以及为网络提供各种负责,比如IPAM,这个IPAM就是IP地址管理。
Docker daemon启动也就是dockerd这个进程,它会创建docker0,而docker0默认用的驱动就是bridge,也就是创建一个虚拟网桥设备,且具有IP地址,所以dockerd启动后你通过ifconfig 或者 ip addr可以看到docker0的IP地址。
接下来就可以创建容器,我们使用docker这个客户端工具来运行容器,容器的网络栈会在容器启动前创建完成,这一系列的工作都由Libnetwork的某些接口(底层还是Linux系统调用)来实现的,比如创建虚拟网卡对(Veth pair),一端连接容器、一端连接网桥、创建网络名称空间、关联容器中的进程到其自己的网络名称空间中、设置IP地址、路由表、iptables等工作。
在dockerd这个程序中有一个参数叫做–bridige和–bip,它就是设置使用哪个网桥以及网桥IP,如下:
dockerd --bridge=docker0 --bip=X.X.X.X/XX
通过上面的描述以及这样的设置,启动的容器就会连接到docker0网桥,不写–bridge默认也是使用docker0,–bip则是网桥的IP地址,而容器就会获得和网桥IP同网段的IP地址。
上述关于网络的东西也没有什么神秘的,其实通过Linux命令都可以实现,如下就是在主机上创建网桥和名称空间,然后进入名称空间配置该空间的网卡对、IP等,最后退出空间,将虚拟网卡对的一端连接在网桥上。
# 创建网桥
brctl addbr
# 创建网络名称空间
ip netns add
# 进入名称空间
ip netns exec bash
# 启动lo
ip link set lo up
# 在创建的名称空间中建立veth pair
ip link add eth0 type veth peer name veth00001
# 为eth0设置IP地址
ip addr add x.x.x.x/xx dev eth0
# 启动eth0
ip link set eth0 up
# 把网络名称空间的veth pair的一端放到主机名称空间中
ip link set veth00001 netns
# 退出名称空间
exit
# 把主机名称空间的虚拟网卡连接到网桥上
brctl addif veth0001
# 在主机上ping网络名称空间的IP地址
ping x.x.x.x
Kubernetes项目
但是对于Kubernetes项目稍微有点不同,它自己不提供网络功能(早期版本有,后来取消了)。所以就导致了有些人的迷惑尤其是初学者,自己安装的Kubernetes集群使用的cni0这个网桥,有些人则使用docker0这个网桥。其实这就是因为Kubernetes项目本身不负责网络管理也不为容器提供具体的网络设置。而网络插件的目的就是为了给容器设置网络环境。
这就意味着在Kubernetes中我可以使用docker作为容器引擎,然后也可以使用docker的网络驱动来为容器提供网络设置。所以也就是说当你的POD启动时,由于infra是第一个POD中的容器,那么该容器的网络栈设置是由Docker daemon调用Libnetwork接口来做的。那么我也可以使用其他的能够完成为infra设置网络的其他程序来执行这个操作。所以CNI Plugin它的目的就是一个可执行程序,在容器需要为容器创建或者删除网络是进行调用来完成具体的操作。
所以这就是为什么之前要说一下POD中多容器是如何进行本地通信的原因,你设置POD的网络其实就是设置Infra这个容器的Network Namespace的网络栈。
在Kubernetes中,kubelet主要负责和容器打交道,而kubelet并不是直接去操作容器,它和容器引擎交互使用的是CRI(Container Runtime Interface,它规定了运行一个容器所必须的参数和标准)接口,所以只要容器引擎提供了符合CRI标准的接口那么可以被Kubernetes使用。那么容器引擎会把接口传递过来的数据进行翻译,翻译成对Linux的系统调用操作,比如创建各种名称空间、Cgroups等。而kubelet默认使用的容器运行时是通过kubelet命令中的
--container-runtime=来设置的,默认就是docker。
如果我们不使用docker为容器设置网络栈的话还可以怎么做呢?这就是CNI,kubelet使用CNI规范来配置容器网络,那么同理使用CNI这种规范也是通过设置Infra容器的网络栈实现POD内容器共享网络的方式。那么具体怎么做呢?在kubelet启动的命令中有如下参数:
-
--cni-bin-dir=STRING这个是用于搜索CNI插件目录,默认/opt/cni/bin -
--cni-conf-dir=STRING这个是用于搜索CNI插件配置文件路径,默认是/opt/cni/net.d -
--network-plugin=STRING这个是要使用的CNI插件名,它就是去--cni-bin-dir目录去搜索