背景
提到k8s网络就不得不提 CNI(容器网络接口),本文主要是说下CNI是什么,以及在哪里被调用,不对CNI插件的具体实现做分析。
k8s网络
k8s要求每个Pod都有自己独立的Ip地址,同个Host下 Pod和Pod之间访问,跨Host 下 Pod之间的访问可以在一个虚拟的网络层中,虚拟的网络层在实际网络层之上。满足这个要求的实现方式有很多种,所以K8s就将网络实现独立出来,以插件的形式加载,CNI 容器网络接口就定义了一种实现k8s网络的规范接口。cni官方定义
CNI
CNI 是一个标准和约定,只要实现CNI标准的网络解决方案都可以被k8s使用。CNI的实现有Bridge,Flannel,Calico,terway(阿里云基于VPC的实现)
网上一般对CNI的调用分析(也就是CNI是怎么被调用的),会基于 /pkg/kubelet/dockershim/network/cni.go和/pkg/kubelet/dockershim/docker_sandbox.go,但是新版本的k8s kubelet模块移除了dockershim。
移除的原因是因为K8s遵循CRI(Container Runtime Interface),CRI的目的是为了让K8s和容器实现解耦,只要符合CRI规范的容器实现都可以被K8s使用,早期因为CRI未出现,或者CRI出现后未壮大,所以K8s会内置容器实现,比如dockershim。
之前CNI的调用代码在dockershim也说明一个定位,就是CNI不是K8s kubelet的一部分,CNI是给容器定的规范,
所以官方也说CRI自己管理CNI 出自:this is only true for Docker, as CRI manages its own CNI plugins。
综上得出,CRI基于CNI管理K8s网络,所以看CNI的调用,我们可以去看CRI某个具体实现的调用。
containerd
containerd是OCI的标准实现,并在后来直接内置了CRI(因为k8s成为了标准),containerd通过runC运行容器(曾经的libcontainer)。
kubelet 创建Pod时会请求 containerd 经过一系列代码,最终会到 sandbox_run.go 的 RunPodSandbox方法, RunPodSandbox方法会调用setupPodNetwork。
https://github.com/containerd/containerd/blob/d4641e1ce1e07393115cd52bd71041ee8a99a180/pkg/cri/server/sandbox_run.go#L61
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {
....
if err := c.setupPodNetwork(ctx, &sandbox); err != nil {...
}
可以看到setupPodNetwork 的 netPlugin = c.getNetworkPlugin(sandbox.RuntimeHandler) 正是CNI插件
//https://github.com/containerd/containerd/blob/d4641e1ce1e07393115cd52bd71041ee8a99a180/pkg/cri/server/sandbox_run.go#L377
func (c *criService) setupPodNetwork(ctx context.Context, sandbox *sandboxstore.Sandbox) error {
var (
id = sandbox.ID
config = sandbox.Config
path = sandbox.NetNSPath
netPlugin = c.getNetworkPlugin(sandbox.RuntimeHandler)
)
....
result, err := netPlugin.Setup(ctx, id, path, opts...)
}
// getNetworkPlugin returns the network plugin to be used by the runtime class
// defaults to the global CNI options in the CRI config
func (c *criService) getNetworkPlugin(runtimeClass string) cni.CNI {
if c.netPlugin == nil {
return nil
}
i, ok := c.netPlugin[runtimeClass]
if !ok {
if i, ok = c.netPlugin[defaultNetworkPlugin]; !ok {
return nil
}
}
return i
}
到这里就是CNI插件的逻辑了,那么会问,插件安装在哪里?多个插件如何选择?
插件安装在哪里?
在 /opt/cni/bin,在该目录下可以看到很多插件的可执行文件,比如bridge
多个插件如何选择?
在 /etc/cni/net.d/目录下的配置文件定义
CNI会加载/etc/cni/net.d/目录下的配置文件定义,来决定使用哪几个插件。
比如下面配置文件使用bridge插件和host-local插件
$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
"cniVersion": "0.2.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.22.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
bridge的网络模型如图所示

bridge插件实际干的事件就是配置出图1的网络模型。
方法就是通过几个系统调用(创建bride,创建veth对,启用,设置Ip,设置路由等)
比如
创建 bridge:
brctl addbr Bridge
创建两对 veth:
//veth1 和 veth1_peer 是一对 它们相互连通。
ip link add veth type veth1 peer name veth1_peer
ip link add veth type veth2 peer name veth2_peer
也就是bridge插件通过这些系统命令创建和配置出类似图1的网络模型完成Pod的网络配置。
所以理解容器网络需要对linux网络和相关的系统调用都比较熟悉。
综上,CNI是给CRI实现的,kubelet 不涉及CNI实现,kubelet请求CRI创建容器时,CRI会负责通过CNI配置网络。
以containerd为例,通过 /etc/cni/net.d/ 下的配置确定CNI的具体插件,在netPlugin.Setup方法调用CNI的插件。
k8s体系庞大,了解流程后,关于CNI插件的具体实现,比如bridge或Flannel可以单独去看。
网友评论