女主宣言

今天小编为大家分享Kubernets Calico CNI Plugin的源码学习笔记,希望对正在学习k8s相关部分的同学有所帮助;

PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!

1

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是如何做到的?

2

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创建network
func (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 := defaultSandboxImagepodSandboxImage := ds.podSandboxImageif len(podSandboxImage) != 0 {image = podSandboxImage}if err := ensureSandboxImageExists(ds.client, image); err != nil {return nil, err}// Step 2: Create the sandbox container.// 2. 创建sandbox containercreateResp, err := ds.client.CreateContainer(*createConfig)// ...resp := &runtimeapi.RunPodSandboxResponse{PodSandboxId: createResp.ID}ds.setNetworkReady(createResp.ID, false)// Step 3: Create Sandbox Checkpoint.// 3. 创建checkpointif 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.goerr = 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 Linuxif plugin.loNetwork != nil {// 添加loopbackif _, 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 command
func (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/) 。

3

总结

总之,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云计算

由360云平台团队打造的技术分享公众号,内容涉及数据库、大数据、微服务、容器、AIOps、IoT等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

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

  1. Kubernetes学习笔记之Calico CNI Plugin源码解析(二)

    女主宣言 今天小编继续为大家分享Kubernetes Calico CNI Plugin学习笔记,希望能对大家有所帮助. PS:丰富的一线技术.多元化的表现形式,尽在"360云计算" ...

  2. [OC学习笔记]分类和关联对象源码解析

    我们平时在开发的时候经常会使用分类来添加方法.协议.属性,但在添加属性的时候属性是不会自动生成成员变量的,这时候我们就需要关联对象来动态存储属性值. 分类 @interface NSObject(St ...

  3. Qt学习笔记,再次分析EVA源码之后得出的结论-QListView,QListViewItem(Qt3);Q3ListView,Q3ListViewItem(Qt4)...

    Qt学习笔记,再次分析EVA源码之后得出的结论-QListView,QListViewItem(Qt3);Q3ListView,Q3ListViewItem(Qt4) 今天再次分析了Eva的源码,也看 ...

  4. Ui学习笔记---EasyUI的EasyLoader组件源码分析

    Ui学习笔记---EasyUI的EasyLoader组件源码分析 技术qq交流群:JavaDream:251572072   1.问题1:为什么只使用了dialog却加载了那么多的js   http: ...

  5. Netty网络框架学习笔记-16(心跳(heartbeat)服务源码分析)

    Netty网络框架学习笔记-16(心跳(heartbeat)服务源码分析_2020.06.25) 前言: Netty 作为一个网络框架,提供了诸多功能,比如编码解码等,Netty 还提供了非常重要的一 ...

  6. Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析

      我们经常在Spring项目中或者Spring Boot项目中使用RabbitMQ,一般使用的时候,己经由前人将配置配置好了,我们只需要写一个注解或者调用一个消息发送或者接收消息的监听器即可,但是底 ...

  7. 《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)

    OpenHarmony开源鸿蒙学习入门–系统相机应用源码解析(一) 一.源码解析的目的: 为什么要去做源码解析这件事?我个人认为,首先可以提高我们对代码书写的能力,毕竟官方系统级的应用,会比demo的 ...

  8. Kubernetes学习笔记之Calico Startup源码解析

    女主宣言 我们目前生产k8s和calico使用ansible二进制部署在私有机房,没有使用官方的calico/node容器部署,并且因为没有使用network policy只部署了confd/bird ...

  9. NODEMCU学习笔记-01 esp8266 WIFI杀手 源码上传版

    NODEMCU学习笔记-01 esp8266WIFI杀手 动手前的准备 NODEMCU和ESP8266 ARDUINO IDE GITHUB CSDN 让我们开始吧 连接开发板并安装驱动 安装ardu ...

最新文章

  1. java 启动加载顺序_Java 程序的加载运行过程 | 学步园
  2. Hadoop详解(七):YARYN完全分布式环境搭建
  3. C++对C的加强之struct类型加强
  4. 云计算机教室安装学生软件,新东方云教室1.6版本
  5. Object关于属性property的静态方法
  6. idea 内存溢出解决方法
  7. Android 帧动画 xml 方式实现
  8. 2019最新k8s集群搭建教程 (centos k8s 搭建)
  9. 问题六十三:怎么用ray tracing画sphere sweeping图形
  10. PSP联机插件pro online
  11. 彻底理解js中的闭包
  12. 2019Deecamp面试经验分享
  13. 解决小米手机无法安装证书问题
  14. STM32初始化产生低电平引起的问题
  15. 大四计算机系的,毕设该怎么下手?
  16. Linux环境搭建 - update https://apt.repos.intel.com 报错
  17. 【飞桨】GAN:U-GAT-IT【2020 ICLR】论文研读
  18. JAVA练习165-复数乘法
  19. DNSPod十问纪中展:从摇滚文青到科学队长
  20. Python:K折交叉验证,将数据集分成训练集与测试集

热门文章

  1. 我整理的一个经典分页程序(JSP的)
  2. Linux学习笔记(四)之用户登录
  3. Shell自动上传下载文件到SFTP服务器
  4. Mac安装prometheus node_exporter
  5. 解决Mac没有任何来源问题
  6. 用C语言编程计算下列表达式:s=1! 2...,2012年全国计算机等级二级C语言模拟试题及答案(3)...
  7. 职工考勤管理信息系统数据库课设_职工考勤管理信息系统数据库课程设计
  8. mysql查看sql代价_mysql 代价
  9. git commit命令
  10. idea复制maven项目,source root为原项目的解决方法