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 也说到了这一点,而这两个插件代码在 calico.go ,代码会编译出两个二进制文件:calico和calico-ipam。calico插件次要用来创立route和virtual interface,而calico-ipam插件次要用来调配pod ip和为worker节点调配pod cidr。
重要问题是,calico是如何做到的?
Sandbox container
kubelet过程在开始启动时,会调用容器运行时的 SyncPod 来创立pod内相干容器,次要做了几件事件 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 :
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 :
// 创立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 :
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 函数。CNI是容器网络标准接口Container Network Interface,这个代码仓库提供了CNI标准接口的相干实现,所有K8s网络插件都必须实现该CNI代码仓库中的接口,K8s网络插件如何实现标准可见 SPEC.md ,咱们也可实现遵循该标准规范实现一个简略的网络插件。
所以kubelet、cni和calico的三者关系就是:kubelet调用cni标准规范代码包,cni调用calico插件二进制文件。cni代码包中的AddNetworkList相干代码如下 AddNetworkList:
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 ,并且启动参数 --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 ,并配有文档阐明见 plugins docs ,比方能够参考学习官网提供的 static IP address management plugin 。
总结
总之,kubelet在创立sandbox container时候,会先调用cni插件命令,如 calico ADD
命令并通过环境变量传递相干命令参数,来给sandbox container创立network相干资源对象,比方calico会创立route和virtual interface,以及为pod调配ip地址,和从集群网段cluster cidr中为以后worker节点调配pod cidr网段,并且会把这些数据写入到calico datastore数据库里。
所以,关键问题,还是得看calico插件代码是如何做的。