立即领取|华润集团、宁德核电、东风岚图等 20+ 标杆企业数字化人才培养实践案例 了解详情
写点什么

Kubernetes 学习笔记之 Calico CNI Plugin 源码解析 (一)

  • 2021-05-10
  • 本文字数:5726 字

    阅读完需:约 19 分钟

Kubernetes学习笔记之Calico CNI Plugin源码解析(一)

Overview

之前在Kubernetes学习笔记之kube-proxy service实现原理学习到 calico 会在 worker 节点上为 pod 创建路由 route 和虚拟网卡 virtual interface,并为 pod 分配 pod ip,以及为 worker 节点分配 pod cidr 网段。


我们生产 k8s 网络插件使用 calico cni,在安装时会安装两个插件:calico 和 calico-ipam,官网安装文档 Install the plugin(https://docs.projectcalico.org/getting-started/kubernetes/hardway/install-cni-plugin#install-the-plugin)也说到了这一点,而这两个插件代码在 calico.go(https://github.com/projectcalico/cni-plugin/blob/release-v3.17/cmd/calico/calico.go) ,代码会编译出两个二进制文件:calico 和 calico-ipam。calico 插件主要用来创建 route 和 virtual interface,而 calico-ipam 插件主要用来分配 pod ip 和为 worker 节点分配 pod cidr。


重要问题是,calico 是如何做到的?

Sandbox container

kubelet 进程在开始启动时,会调用容器运行时 SyncPod(https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/kubelet/kubelet.go#L1692) 来创建 pod 内相关容器,

主要做了几件事情 L657-L856(https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L657-L856)


  • 创建 sandbox container,这里会调用 cni 插件创建 network 等步骤,同时考虑了边界条件,创建失败会 kill sandbox container 等等。

  • 创建 ephemeral containers、init containers 和普通的 containers。


这里只关注创建 sandbox container 过程,只有这一步会创建 pod network,这个 sandbox container 创建好后,其余 container 都会和其共享同一个 network namespace,所以一个 pod 内各个容器看到的网络协议栈是同一个,ip 地址都是相同的,通过 port 来区分各个容器。具体创建过程,会调用容器运行时服务创建容器,这里会先准备好 pod 的相关配置数据,创建 network namespace 时也需要这些配置数据 L36-L138(https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go#L36-L138) :



func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string, string, error) { // 生成pod相关配置数据 podSandboxConfig, err := m.generatePodSandboxConfig(pod, attempt) // ... // 这里会在宿主机上创建pod logs目录,在/var/log/pods/{namespace}_{pod_name}_{uid}目录下 err = m.osInterface.MkdirAll(podSandboxConfig.LogDirectory, 0755) // ... // 调用容器运行时创建sandbox container,我们生产k8s这里是docker创建 podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig, runtimeHandler) // ... return podSandBoxID, "", nil}
复制代码


k8s 使用 cri(container runtime interface)来抽象出标准接口,目前 docker 还不支持 cri 接口,所以 kubelet 做了个适配模块 dockershim,代码在 pkg/kubelet/dockershim。上面代码中的 runtimeService 对象就是 dockerService 对象,所以可以看下 dockerService.RunPodSandbox()代码实现 L76-L197(https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/kubelet/dockershim/docker_sandbox.go#L76-L197)



// 创建sandbox container,以及为该container创建networkfunc (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) { config := r.GetConfig() // Step 1: Pull the image for the sandbox. // 1. 拉取镜像 image := defaultSandboxImage podSandboxImage := ds.podSandboxImage if len(podSandboxImage) != 0 { image = podSandboxImage } if err := ensureSandboxImageExists(ds.client, image); err != nil { return nil, err } // Step 2: Create the sandbox container. // 2. 创建sandbox container createResp, err := ds.client.CreateContainer(*createConfig) // ... resp := &runtimeapi.RunPodSandboxResponse{PodSandboxId: createResp.ID} ds.setNetworkReady(createResp.ID, false) // Step 3: Create Sandbox Checkpoint. // 3. 创建checkpoint if err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil { return nil, err } // Step 4: Start the sandbox container. // Assume kubelet's garbage collector would remove the sandbox later, if // startContainer failed. // 4. 启动容器 err = ds.client.StartContainer(createResp.ID)
// ... // Step 5: Setup networking for the sandbox. // All pod networking is setup by a CNI plugin discovered at startup time. // This plugin assigns the pod ip, sets up routes inside the sandbox, // creates interfaces etc. In theory, its jurisdiction ends with pod // sandbox networking, but it might insert iptables rules or open ports // on the host as well, to satisfy parts of the pod spec that aren't // recognized by the CNI standard yet. // 5. 这一步为sandbox container创建网络,主要是调用calico cni插件创建路由和虚拟网卡,以及为pod分配pod ip,为该宿主机划分pod网段 cID := kubecontainer.BuildContainerID(runtimeName, createResp.ID) networkOptions := make(map[string]string) if dnsConfig := config.GetDnsConfig(); dnsConfig != nil { // Build DNS options. dnsOption, err := json.Marshal(dnsConfig) if err != nil { return nil, fmt.Errorf("failed to marshal dns config for pod %q: %v", config.Metadata.Name, err) } networkOptions["dns"] = string(dnsOption) }
// 这一步调用网络插件来setup sandbox pod // 由于我们网络插件都是cni(container network interface),所以代码在 pkg/kubelet/dockershim/network/cni/cni.go err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions) // ... return resp, nil}
复制代码


由于我们网络插件都是 cni(container network interface),代码 ds.network.SetUpPod 继续追下去发现实际调用的是 cniNetworkPlugin.SetUpPod(),代码在 pkg/kubelet/dockershim/network/cni/cni.go(https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/kubelet/dockershim/network/cni/cni.go#L300-L321) :



func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.ContainerID, annotations, options map[string]string) error { // ... netnsPath, err := plugin.host.GetNetNS(id.ID) // ... // Windows doesn't have loNetwork. It comes only with Linux if plugin.loNetwork != nil { // 添加loopback if _, err = plugin.addToNetwork(cniTimeoutCtx, plugin.loNetwork, name, namespace, id, netnsPath, annotations, options); err != nil { return err } } // 调用网络插件创建网络相关资源 _, err = plugin.addToNetwork(cniTimeoutCtx, plugin.getDefaultNetwork(), name, namespace, id, netnsPath, annotations, options) return err}
func (plugin *cniNetworkPlugin) addToNetwork(ctx context.Context, network *cniNetwork, podName string, podNamespace string, podSandboxID kubecontainer.ContainerID, podNetnsPath string, annotations, options map[string]string) (cnitypes.Result, error) { // 这一步准备网络插件所需相关参数,这些参数最后会被calico插件使用 rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath, annotations, options) // ... // 这里会调用调用cni标准库里的AddNetworkList函数,最后会调用calico二进制命令 res, err := cniNet.AddNetworkList(ctx, netConf, rt) // ... return res, nil}
// 这些参数主要包括container id,pod等相关参数func (plugin *cniNetworkPlugin) buildCNIRuntimeConf(podName string, podNs string, podSandboxID kubecontainer.ContainerID, podNetnsPath string, annotations, options map[string]string) (*libcni.RuntimeConf, error) { rt := &libcni.RuntimeConf{ ContainerID: podSandboxID.ID, NetNS: podNetnsPath, IfName: network.DefaultInterfaceName, CacheDir: plugin.cacheDir, Args: [][2]string{ {"IgnoreUnknown", "1"}, {"K8S_POD_NAMESPACE", podNs}, {"K8S_POD_NAME", podName}, {"K8S_POD_INFRA_CONTAINER_ID", podSandboxID.ID}, }, } // port mappings相关参数 // ... // dns 相关参数 // ... return rt, nil}
复制代码


addToNetwork()函数会调用 cni 标准库里的 AddNetworkList(https://github.com/containernetworking/cni/blob/master/libcni/api.go#L400-L440)函数。CNI 是容器网络标准接口 Container Network Interface,这个代码仓库提供了 CNI 标准接口的相关实现,所有 K8s 网络插件都必须实现该 CNI 代码仓库中的接口,K8s 网络插件如何实现规范可见 SPEC.md(https://github.com/containernetworking/cni/blob/master/SPEC.md) ,我们也可实现遵循该标准规范实现一个简单的网络插件。所以 kubelet、cni 和 calico 的三者关系就是:kubelet 调用 cni 标准规范代码包,cni 调用 calico 插件二进制文件。cni 代码包中的 AddNetworkList 相关代码如下 AddNetworkList(https://github.com/containernetworking/cni/blob/master/libcni/api.go#L400-L440)



func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { c.ensureExec() pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) // ... // pluginPath就是calico二进制文件路径,这里其实就是调用 calico ADD命令,并传递相关参数,参数也是上文描述的已经准备好了的 // 参数传递也是写入了环境变量,calico二进制文件可以从环境变量里取值 return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)}
// AddNetworkList executes a sequence of plugins with the ADD commandfunc (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { // ... for _, net := range list.Plugins { result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt) // ... } // ... return result, nil}
复制代码


以上 pluginPath 就是 calico 二进制文件路径,这里 calico 二进制文件路径参数是在启动 kubelet 时通过参数 --cni-bin-dir 传进来的,可见官网 kubelet command-line-tools-reference(https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) ,并且启动参数 --cni-conf-dir 包含 cni 配置文件路径,该路径包含 cni 配置文件内容类似如下:



{ "name": "k8s-pod-network", "cniVersion": "0.3.1", "plugins": [ { "type": "calico", "log_level": "debug", "log_file_path": "/var/log/calico/cni/cni.log", "datastore_type": "kubernetes", "nodename": "minikube", "mtu": 1440, "ipam": { "type": "calico-ipam" }, "policy": { "type": "k8s" }, "kubernetes": { "kubeconfig": "/etc/cni/net.d/calico-kubeconfig" } }, { "type": "portmap", "snat": true, "capabilities": {"portMappings": true} }, { "type": "bandwidth", "capabilities": {"bandwidth": true} } ]}
复制代码


cni 相关代码是个标准骨架,核心还是需要调用第三方网络插件来实现为 sandbox 创建网络资源。cni 也提供了一些示例 plugins,代码仓库见 containernetworking/plugins(https://github.com/containernetworking/plugins),并配有文档说明见 plugins docs(https://www.cni.dev/plugins/) ,比如可以参考学习官网提供的 static IP address management plugin(https://www.cni.dev/plugins/ipam/static/) 。

总结

总之,kubelet 在创建 sandbox container 时候,会先调用 cni 插件命令,如 calico ADD 命令并通过环境变量传递相关命令参数,来给 sandbox container 创建 network 相关资源对象,比如 calico 会创建 route 和 virtual interface,以及为 pod 分配 ip 地址,和从集群网段 cluster cidr 中为当前 worker 节点分配 pod cidr 网段,并且会把这些数据写入到 calico datastore 数据库里。所以,关键问题,还是得看 calico 插件代码是如何做的。

参考链接

  • https://docs.projectcalico.org/networking/use-specific-ip

  • https://mp.weixin.qq.com/s/lyfeZh6VWWjXuLY8fl3ciw

  • https://www.yuque.com/baxiaoshi/tyado3/lvfa0b

  • https://github.com/containernetworking/cni

  • https://github.com/projectcalico/cni-plugin


本文转载自:360 技术(ID:qihoo_tech)

原文链接:Kubernetes学习笔记之Calico CNI Plugin源码解析(一)

2021-05-10 08:001877

评论

发布
暂无评论
发现更多内容

两步实现让antd与IDE和睦相处的处理案例

袋鼠云数栈

大数据 开源

衡石BI产品预置明道云数据连接器

明道云

透过「人月神话」,看清软件开发问题

架构精进之路

软件开发 人月神话 4月日更 4月月更

实时数仓建设

五分钟学大数据

实时计算 4月月更

Flink 实践教程-进阶(11):SQL 关联:Regular Join

腾讯云大数据

flink 流计算 流计算 Oceanus

RabbitMQ 实现延时队列(订单定时取消为例)

Ayue、

Rabbit MQ 4月月更

Ribbon从入门到源码解析

李子捌

微服务 SpringCloud Ribbon

百度信誉保障服务架构全解析

百度Geek说

后端

龙蜥开源Plugsched:首次实现 Linux kernel 调度器热升级 | 龙蜥技术

OpenAnolis小助手

Linux 内核 龙蜥社区 Plugsched

打通产销对接,构建新型数智化农副产品供应链

数商云

产业互联网 数字化

重塑企业数字化能力,端点科技重磅发布Erda2.0

科技热闻

Improvements of Job Scheduler and Query Execution on Flink OLAP

Apache Flink

大数据 flink 编程 实时计算 OLAP

数栈在湖仓一体上的探索与实践

袋鼠云数栈

数据库 大数据 数据湖 湖仓一体

Kubernetes官方java客户端之四:内部应用

程序员欣宸

Kubernetes client 4月月更

ffmpeg实现web在线转码

lo

4月月更

云效制品仓库 Packages,不限容量免费用

阿里云云效

云计算 maven 阿里云 npm 制品仓库

腾讯云CDW-ClickHouse云原生实践

腾讯云大数据

Clickhouse 云数据仓库

北京市东城区赵海东副区长一行莅临博睿数据参观指导

博睿数据

Spring 完美导入 IDEA

阿Q说代码

spring IDEA 4月月更

GaussDB(for Redis)助力《余烬风暴》实力上线,给您沉浸式魔幻体验

华为云数据库小助手

GaussDB GaussDB ( for Redis )

Flink 实践教程-入门(10):Python作业的使用

腾讯云大数据

Hoo虎符研究院|从多个方面了解公链Tezos和它的 Ithaca 2 升级

区块链前沿News

Hoo 虎符交易所 研究院 tezos

阿里Maven仓库不限容量,免费用

阿里云云效

云计算 阿里云 npm Maven仓库 制品仓库

恒源云(Gpushare)_没有你想要的镜像?技巧大放送5!

恒源云

镜像仓库 显卡、gpu

netty系列之:netty中的核心MessageToMessage编码器

程序那些事

Java Netty 程序那些事 4月月更

Web 3.0的未来产业趋势

王强

Web 3.0

恒源云(Gpushare)_如何安装包/pip加速等?技巧大放送3!

恒源云

安装 pip Ubuntu apt

恒源云(Gpushare)_JupyterLab/TensorBoard使用问题?技巧大放送4!

恒源云

人工智能 深度学习 PyTorch

一个公式告诉你:如何提升团队的研发效率?

凌晞

技术管理 研发效率

2022首场MASA技术团队黑客松大赛完美落幕!精彩集锦

MASA技术团队

C# .net 微软

2022华为软件精英挑战赛复赛名单公布,快来看看都有哪些优秀赛队晋级

科技热闻

Kubernetes学习笔记之Calico CNI Plugin源码解析(一)_架构_360技术_InfoQ精选文章