K8S之网络深度剖析
一 、关于K8S的网络模型
在K8s的世界上,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作为IP-per-Pod模型。
Kubernetes对集群网络有如下要求:
- 所有容器都可以在不用NAT的方式下同别的容器通讯。
- 所有节点都可以在不用NAT的方式下同别的容器通讯。
- 容器的地址和别人看到的地址是同一个地址。
二、Docker 网络
Docker 使用到的与Linux网络有关的主要技术:
- 网络命名空间(Network Namespace)
- Veth 设备对
- 网桥
- Iptables
- 路由
2.1 网络命名空间
为了支持网络协议栈中的多个实例,Linux 2.6.24内核版本的网络协议栈中引入了网络命名空间,这些独立的协议栈被隔离到不用的命名空间中。处于不用命名空间中的网络栈是完全隔离的,彼此之间无法通讯,就好像两个"平行宇宙"。通过对于网络资源的隔离,就能在一个宿主机上虚拟多个不同的网络环境。Docker 正是利用了网络的命名空间特性,实现了不同容器之间的网络隔离。
在Linux的网络命名空间中可以有自己独立的路由表,及独立的Iptables设置来提供包转发、NAT、及IP包过滤等功能。
为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接字、网络设备等。进程创建的套接字必须属于某个网络命名空间,套接字的操作也必须在命名空间中进行。同样,网络设备也必须属于某个命名空间。因为网络设备属于公共资源,所以可以通过修改属性实现在命名空间之间移动。当然,是否允许移动,和设备的特征有关系。
2.1.1 网络命名空间的实现
Linux的网络协议栈是十分复杂的,为了支持独立的协议栈,相关的这些全局变量都必须被修改为协议栈私有,最好的办法就是让这些全局变量成为一个 Net NameSpace变量的成员,然后修改协议栈的函数调用,在加入一个NameSpace的参数,这就是Linux 实现网络命名空间的核心。
同时,为了保证对已经开发的应用程序,以及内核代码的兼容性,内核代码隐式地使用了网络命名空间中的变量,程序如果没有对网络命名空间有特殊需求,就不需要编写额外的代码,网络命名空间对应用程序而言是透明的。
在建立了新的网络命名空间,并将某个进程关联到这个网络命名空间后,就出现了如下图的数据结构,所有的网络栈变量都没放到了私有的命名空间,和其他进程组并不冲突。
上图来自网络命名空间深度好文
在新生成的理由命名空间中,只有回环设备,(名为"lo" 且是停止状态),其他设备默认都不存在,如果我们需要,则要一一手工建立,Docker容器中的各类网络栈设备都是Docker Daemon 在启动时自动创建和配置的。
所有的网络设备(物理的或虚拟接口、桥等在内核里都叫做Net Device)都只能属于一个命名空间、当然,物理设备(连接实际硬件的设备)通常只能关联到root 这个命名空间。虚拟的网络设备(虚拟的以太网接口或者虚拟网口对)则可以被关联到一个给定的命名空间,而且可以在这些命名空间中移动。
前面提到,由于网络命名空间代表的是一个独立的协议栈,所以它们之间是相互隔离的,彼此无法通讯,在协议栈内部都看不到对方, 那么有没有办法打破这种限制,让处于不同网络命名空间的网络互相通讯,甚至和外部的网络通讯呢? 答案就是“有”,应用 “Veth设备对即可”, Veth 设备对一个重要的作用就是打通互相看不到的协议栈的协议栈之间的壁垒,他就像一条管子,一端连着这个网络命名空间的协议栈,一端连着另一个网络命名空间协议栈,所以想要在两个命名空间之间通讯,就必须有一个 Veth 设备对。
2.1.2 网络命名空间的操作
我们可以使用 Linux iproute2 系列配置工具中的IP 命令来操作网络命名空间,注意: 这个命令需要使用root 用户来执行。
安装:
# alphine
apk add iproute2
# ubuntu
sudo apt install iproute2
# centos
sudo yum -y install iproute2
创建一个网络命名空间:
ip netns add <name>
创建一个名为test1 的网络命名空间
ip netns add test1
在命名空间中执行命令:
ip netns exec <name> <command>
在命名空间test1 执行 ip a s 命令
root@test:~# ip netns exec test1 ip a s
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
也可以先通过切换到对应的网络命名空间,执行各种命令:
ip netns exec <name> bash
执行 exit 或Ctrl +d 退出当前网络命名空间。
上面的操作相当于网络命名空间进行了切换,文件系统还是当前,在Docker 中,其实也做了类似操作,并且将文件系统通过chroot 进行了切换,通过cgroup 对资源进行了隔离。
2.1.3 网络命名空间的实用技巧
操作系统网络命名空间的一些技巧如下:
我们可以在不同的网络命名空间之间转移设备,列如下面会提到的Veth 设备对的转移,因为一个设备只能属于一个命名空间,所以转移后在这个网络命名空间内就看不到这个设备了,具体哪些设备能被转移到不同的网络命名空间, 在设备里面有一个属性: NETIF_F_NETNS_LOCAL,如果这个属性为 on,就不能被转移到其他命名空间中。Veth 设备属于可转移设备,而其他设备如,lo 设备,vxlan,ppp 设备,bridge设备等都是不可转移的。将无法转移的设备移动到别的命名空间时,会得到无效的参数错误提示。
root@test1:~# ip link set lo netns test1
RTNETLINK answers: Invalid argument
如何知道这些设备是否可以转移呢? 可以使用ethtool 工具查看:
root@test1:~# apt install ethtool
root@test1:~# ethtool -k docker0 | grep netns
netns-local: on [fixed]
root@test1:~# ethtool -k veth276e185 | grep netns
netns-local: off [fixed]