阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

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:001640

评论

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

登陆 Azure、发布新版本……Zilliz 昨夜今晨发生了什么?

Zilliz

azure Milvus Zilliz zillizcloud

铭文聚合交易平台 Scorpio:铭文赛道狂潮的新引领者

股市老人

体育数据服务商提供API接口,有哪些赛事数据

软件开发-梦幻运营部

E往无前 | 海量数据ES 扩展难?腾讯云大数据ES 扩展百万级分片也“So Easy~”

腾讯云大数据

ES

软件测试/人工智能|教你如何使用ChatGPT的API

霍格沃兹测试开发学社

喜讯!云起无垠成为国家信息安全漏洞库(CNNVD)技术支撑单位

云起无垠

为什么 Mac 适合编程?

代码生成器研究

智能汽车的山海之盾

脑极体

智能汽车

跃见书单 | 一文带你读懂《人工智能简史》

码上跃见

AIGC #人工智能

只需3分钟!组织架构图如何简单快速制作

Geek_09ea8e

组织架构图

IDC最新报告,增速减缓+AI增势,阿里云视频云中国市场第一

阿里云视频云

云计算 视频云

百度大模型安全解决方案获WitAwards 2023 年度大奖

百度安全

安全 大模型安全

2023年datafun随手记(1)

Hua

大数据 AI GPT LLM

超级应用平台(HAP)起航

明道云

北京同仁堂签署鸿蒙生态合作协议,加速推进鸿蒙原生应用开发

最新动态

如何系统、科学地自学编程知识?

代码生成器研究

淘宝商品详情API接口文档(API SDK)

tbapi

淘宝商品详情数据接口 淘宝API接口 淘宝商品详情页面数据 淘宝商品详情数据采集方法 天猫数据接口

核药供应链创新:远大医药策略与明道云实践

明道云

鸿蒙学堂·创新实训营再度启航深圳,中国移动、国家电网等40余家企业参与

最新动态

软件测试/人工智能|AutoGPT原理与架构介绍

霍格沃兹测试开发学社

我干嘛要去学Python???!!!

代码生成器研究

SQL 通配符:用于模糊搜索和匹配的 SQL 关键技巧

小万哥

MySQL 数据库 程序员 sql 后端开发

全链协同,链接未来|端点科技联合IDC重磅发布新一代ERP白皮书

科技热闻

C/C++ 开发SCM服务管理组件

不在线第一只蜗牛

c 开发语言 c++、

CART算法解密:从原理到Python实现

快乐非自愿限量之名

Python 算法 PyTorch

几种常见的排序算法总结

不在线第一只蜗牛

算法 排序算法 教程分享

软件测试/人工智能|一文教你如何配置自己的AutoGPT

霍格沃兹测试开发学社

12 月 3 日北京,时序数据管理前沿技术+行业应用尽在 IoTDB 用户大会!

Apache IoTDB

拼多多商品详情数据接口应用在哪些场景?

tbapi

拼多多 拼多多商品详情接口 拼多多API接口

电竞游戏主播直播系统平台,在市场该如何变现?

软件开发-梦幻运营部

一文弄懂竞品分析 - 竞品分析是什么| 从哪些方面分析 | 竞品分析报告怎么写?

彭宏豪95

效率工具 产品经理 在线白板 竞品分析 SWOT

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