浅谈 K8s 网络模型CNI协议

2024-03-23 20:08

本文主要是介绍浅谈 K8s 网络模型CNI协议,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

f6af010b4eafa1bab0856333adf62db9.gif

作者:successwang(王成),腾讯 CSIG 研发工程师,Kubernetes member。

1. 概述

进入 K8s 的世界,会发现有很多方便扩展的 Interface,包括 CNI, CSI, CRI 等,将这些接口抽象出来,是为了更好的提供开放、扩展、规范等能力。

K8s 网络模型采用 CNI(Container Network Interface, 容器网络接口) 协议,只要提供一个标准的接口,就能为同样满足该协议的所有容器平台提供网络功能。

CNI 是 CoreOS 提出的一种容器网络规范,目前已被 Apache Mesos、Cloud Foundry、Kubernetes、Kurma、rkt 等众多开源项目所采用,同时也是一个 CNCF(Cloud Native Computing Foundation) 项目。可以预见,CNI 将会成为未来容器网络的标准。

本文将从 kubelet 启动、Pod 创建/删除、Docker 创建/删除 Container、CNI RPC 调用、容器网络配置等核心流程,对 CNI 实现机制进行了解析。

流程概览如下:

afcbdd0610445c9b69cc70b2fc205a89.png
K8s-CNI

本文及后续相关文章都基于 K8s v1.22

2. 从网络模型说起

容器的网络技术日新月异,经过多年发展,业界逐渐聚焦到 Docker 的 CNM(Container Network Model, 容器网络模型) 和 CoreOS 的 CNI(Container Network Interface, 容器网络接口)。

2.1 CNM 模型

CNM 是一个被 Docker 提出的规范。现在已经被 Cisco Contiv, Kuryr, Open Virtual Networking (OVN), Project Calico, VMware 和 Weave 这些公司和项目所采纳。

Libnetwork 是 CNM 的原生实现。它为 Docker daemon 和网络驱动程序之间提供了接口。网络控制器负责将驱动和一个网络进行对接。每个驱动程序负责管理它所拥有的网络以及为该网络提供的各种服务,例如 IPAM 等等。由多个驱动支撑的多个网络可以同时并存。原生驱动包括 none, bridge, overlay 以及 MACvlan。

但是,container runtime 会在不同情况下使用到不同的插件,这带来了复杂性。另外,CNM 需要使用分布式存储系统来保存网络配置信息,例如 etcd。

4e1b2b7377d279726a1905d1b294098a.png
CNM-model
  • Network Sandbox:容器内部的网络栈,包括网络接口、路由表、DNS 等配置的管理。Sandbox 可用 Linux 网络命名空间、FreeBSD Jail 等机制进行实现。一个 Sandbox 可以包含多个 Endpoint。

  • Endpoint:用于将容器内的 Sandbox 与外部网络相连的网络接口。可以使用 veth pair、Open vSwitch 的内部 port 等技术进行实现。一个 Endpoint 仅能够加入一个 Network。

  • Network:可以直接互连的 Endpoint 的集合。可以通过 Linux bridge、VLAN 等技术进行实现。一个 Network 包含多个 Endpoint。

2.2 CNI 模型

CNI 是由 CoreOS 提出的一个容器网络规范。已采纳改规范的包括 Apache Mesos, Cloud Foundry, Kubernetes, Kurma 和 rkt。另外 Contiv Networking, Project Calico 和 Weave 这些项目也为 CNI 提供插件。

CNI 对外暴露了从一个网络里面添加和剔除容器的接口。CNI 使用一个 json 配置文件保存网络配置信息。和 CNM 不一样,CNI 不需要一个额外的分布式存储引擎。

一个容器可以被加入到被不同插件所驱动的多个网络之中。一个网络有自己对应的插件和唯一的名称。CNI 插件需要提供两个命令:ADD 用来将网络接口加入到指定网络,DEL 用来将其移除。这两个接口分别在容器被创建和销毁的时候被调用。

cfd44af4fcb73052da028b29dcc3ae1c.png
CNI-model

CNI 支持与第三方 IPAM 的集成,可以用于任何容器 runtime。CNM 从设计上就仅仅支持 Docker。由于 CNI 简单的设计,许多人认为编写 CNI 插件会比编写 CNM 插件来得简单。

3. CNI 插件

CNI 插件是二进制可执行文件,会被 kubelet 调用。启动 kubelet --network-plugin=cni, --cni-conf-dir 指定 networkconfig 配置,默认路径是:/etc/cni/net.d。另外,--cni-bin-dir 指定 plugin 可执行文件路径,默认路径是:/opt/cni/bin。

看一个 CNI Demo:在默认网络配置目录,配置两个 xxx.conf:一个 type: "bridge" 网桥,另一个 type: "loopback" 回环网卡。

$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{"cniVersion": "0.2.0", // CNI Spec 版本"name": "mynet", // 自定义名称"type": "bridge", // 插件类型 bridge"bridge": "cni0", // 网桥名称"isGateway": true, // 是否作为网关"ipMasq": true, // 是否设置 IP 伪装"ipam": {"type": "host-local", // IPAM 类型 host-local"subnet": "10.22.0.0/16", // 子网段"routes": [{ "dst": "0.0.0.0/0" } // 目标路由段]}
}
EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{"cniVersion": "0.2.0", // CNI Spec 版本"name": "lo", // 自定义名称"type": "loopback" // 插件类型 loopback
}
EOF

CNI 插件可分为三类:

  • Main 插件:用来创建具体网络设备的二进制文件。比如,bridge、ipvlan、loopback、macvlan、ptp(point-to-point, Veth Pair 设备),以及 vlan。如开源的 Flannel、Weave 等项目,都属于 bridge 类型的 CNI 插件,在具体的实现中,它们往往会调用 bridge 这个二进制文件。

  • Meta 插件:由 CNI 社区维护的内置 CNI 插件,不能作为独立的插件使用,需要调用其他插件。tuning,是一个通过 sysctl 调整网络设备参数的二进制文件;portmap,是一个通过 iptables 配置端口映射的二进制文件;bandwidth,是一个使用 Token Bucket Filter (TBF) 来进行限流的二进制文件。

  • IPAM 插件:IP Address Management,它是负责分配 IP 地址的二进制文件。比如,dhcp,这个文件会向 DHCP 服务器发起请求;host-local,则会使用预先配置的 IP 地址段来进行分配。

5f9df794135c4683558386fcf847fb19.png
K8s-CNI-plugins

4. kubelet 启动

kubelet 在 Node 节点上负责 Pod 的创建、销毁、监控上报等核心流程,通过 Cobra 命令行解析参数启动二进制可执行文件。

启动入口如下:

// kubernetes/cmd/kubelet/kubelet.go
func main() {command := app.NewKubeletCommand()// kubelet uses a config file and does its own special// parsing of flags and that config file. It initializes// logging after it is done with that. Therefore it does// not use cli.Run like other, simpler commands.code := run(command)os.Exit(code)
}

接着,一路往下进行初始化:

cmd -> Run -> PreInitRuntimeService -> RunKubelet -> createAndInitKubelet -> startKubelet -> Run

其中 PreInitRuntimeService 会进一步初始化 dockershim,一方面探测环境中的网络配置文件(默认路径为:/etc/cni/net.d/*.conf/.conflist/.json),进行 CNI 网络配置;另一方面启动 gRPC docker server 监听 client 请求,进行具体的操作如 PodSandbox、Container 创建与删除。

当监听到 Pod 事件时,进行对应 Pod 的创建或删除,流程如下:

Run -> syncLoop -> SyncPodCreate/Kill -> UpdatePod -> syncPod/syncTerminatingPod -> dockershim gRPC -> Pod running/teminated

5. Pod 创建/删除

K8s 中 Pod 的调谐采用 channel 生产者-消费者模型实现,具体通过 PLEG(Pod Lifecycle Event Generator) 进行 Pod 生命周期事件管理。

// kubernetes/pkg/kubelet/pleg/pleg.go
// 通过 PLEG 进行 Pod 生命周期事件管理
type PodLifecycleEventGenerator interface {Start() // 通过 relist 获取所有 Pods 并计算事件类型Watch() chan *PodLifecycleEvent // 监听 eventChannel,传递给下游消费者Healthy() (bool, error)
}

Pod 事件生产者 - 相关代码:

// kubernetes/pkg/kubelet/pleg/generic.go
// 生产者:获取所有 Pods 列表,计算出对应的事件类型,进行 Sync
func (g *GenericPLEG) relist() {klog.V(5).InfoS("GenericPLEG: Relisting")...// 获取当前所有 Pods 列表podList, err := g.runtime.GetPods(true)if err != nil {klog.ErrorS(err, "GenericPLEG: Unable to retrieve pods")return}for pid := range g.podRecords {allContainers := getContainersFromPods(oldPod, pod)for _, container := range allContainers {// 计算事件类型:running/exited/unknown/non-existentevents := computeEvents(oldPod, pod, &container.ID)for _, e := range events {updateEvents(eventsByPodID, e)}}}// 遍历所有事件for pid, events := range eventsByPodID {for i := range events {// Filter out events that are not reliable and no other components use yet.if events[i].Type == ContainerChanged {continue}select {case g.eventChannel <- events[i]: // 生产者:发送到事件 channel,对应监听的 goroutine 会消费default:metrics.PLEGDiscardEvents.Inc()klog.ErrorS(nil, "Event channel is full, discard this relist() cycle event")}}}...
}

Pod 事件消费者 - 相关代码:

// kubernetes/pkg/kubelet/kubelet.go
// 消费者:根据 channel 获取的各类事件,进行 Pod Sync
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {select {...// 消费者:监听 plegCh 的事件case e := <-plegCh:if e.Type == pleg.ContainerStarted {// 更新容器的最后启动时间kl.lastContainerStartedTime.Add(e.ID, time.Now())}if isSyncPodWorthy(e) {if pod, ok := kl.podManager.GetPodByUID(e.ID); ok {klog.V(2).InfoS("SyncLoop (PLEG): event for pod", "pod", klog.KObj(pod), "event", e)// 进行相关 Pod 事件的 Synchandler.HandlePodSyncs([]*v1.Pod{pod})} else {// If the pod no longer exists, ignore the event.klog.V(4).InfoS("SyncLoop (PLEG): pod does not exist, ignore irrelevant event", "event", e)}}// 容器销毁事件处理:清除 Pod 内相关 Containerif e.Type == pleg.ContainerDied {if containerID, ok := e.Data.(string); ok {kl.cleanUpContainersInPod(e.ID, containerID)}}...}return true
}

6. Docker 忙起来

经过上一步 Pod 事件的生产与消费传递,PodWorkers 会将事件转化为 gRPC client 请求,然后调用 dockershim gRPC server,进行 PodSandbox、infra-container(也叫 pause 容器) 的创建。

接着,会调用 CNI 接口 SetUpPod 进行相关网络配置与启动,此时建立起来的容器网络,就可以直接用于之后创建的业务容器如 initContainers、containers 进行共享网络。

相关代码如下:

// kubernetes/pkg/kubelet/dockershim/docker_sandbox.go
// 启动运行 Pod Sandbox
func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {config := r.GetConfig()// Step 1: 拉取基础镜像(infra-container: k8s.gcr.io/pause:3.6)image := defaultSandboxImageif err := ensureSandboxImageExists(ds.client, image); err != nil {return nil, err}// Step 2: 创建 Sandbox 容器createConfig, err := ds.makeSandboxDockerConfig(config, image)if err != nil {return nil, fmt.Errorf("failed to make sandbox docker config for pod %q: %v", config.Metadata.Name, err)}createResp, err := ds.client.CreateContainer(*createConfig)if err != nil {createResp, err = recoverFromCreationConflictIfNeeded(ds.client, *createConfig, err)}// Step 3: 创建 Sandbox 检查点(用于记录当前执行到哪一步了)if err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {return nil, err}// Step 4: 启动 Sandbox 容器err = ds.client.StartContainer(createResp.ID)if err != nil {return nil, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.Name, err)}// Step 5: 对 Sandbox 容器进行网络配置err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions)if err != nil {// 如果网络配置失败,则回滚:删除建立起来的 Pod 网络err = ds.network.TearDownPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID)if err != nil {errList = append(errList, fmt.Errorf("failed to clean up sandbox container %q network for pod %q: %v", createResp.ID, config.Metadata.Name, err))}// 停止容器运行err = ds.client.StopContainer(createResp.ID, defaultSandboxGracePeriod)...}return resp, nil
}

流程图小结如下:

b0c108c301126e9f2d890272f239e46e.png
K8s-CNI-flow

根据社区讨论Dockershim Deprecation FAQ,dockershim 相关代码将会在 2021 底左右移出 K8s 主干代码,之后将统一使用 CRI(Container Runtime Interface, 容器运行时接口) 进行容器生命周期管理。

7. CNI RPC 接口

CNI 标准规范接口,包含了添加、检查、验证、删除网络等接口,并提供了按列表或单个进行网络配置的两组接口,方便用户灵活使用。

CNI 从容器管理系统(dockershim) 处获取运行时信息(Container Runtime),包括 network namespace 的路径,容器 ID 以及 network interface name,再从容器网络的配置文件中加载网络配置信息,再将这些信息传递给对应的插件,由插件进行具体的网络配置工作,并将配置的结果再返回到容器管理系统中。

abfdfb5e2e1055c656daf6143d2ced84.png
K8s-CNI-RPC

用户若要编写自己的 CNI 插件,则可专注于实现图中这些 RPC 接口即可,然后可以与官方维护的三类基础插件自由组合,形成多种多样的容器网络解决方案。

8. 小结

本文通过分析 K8s 中 kubelet 启动、Pod 创建/删除、Docker 创建/删除 Container、CNI RPC 调用、容器网络配置等核心流程,对 CNI 实现机制进行了解析,通过源码、图文方式说明了相关流程逻辑,以期更好的理解 K8s CNI 运行流程。

K8s 网络模型采用 CNI(Container Network Interface, 容器网络接口) 协议,只要提供一个标准的接口,就能为同样满足该协议的所有容器平台提供网络功能。CNI 目前已被众多开源项目所采用,同时也是一个 CNCF(Cloud Native Computing Foundation) 项目。可以预见,CNI 将会成为未来容器网络的标准。

参考资料

  1. CNI 规范

  2. Kubernetes 源码

  3. CNI 插件介绍

  4. CNI 插件源码

  5. CNI and CNM model

最近热文:

提速 30%!腾讯TQUIC 网络传输协议

大牛书单 | 消息队列方向的好书

微信 ClickHouse 实时数仓的最佳实践

腾讯程序员视频号直播

258305b03b8a3b01de5fafba440cad22.png

这篇关于浅谈 K8s 网络模型CNI协议的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/839394

相关文章

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

k8s部署MongDB全过程

《k8s部署MongDB全过程》文章介绍了如何在Kubernetes集群中部署MongoDB,包括环境准备、创建Secret、创建服务和Deployment,并通过Robo3T工具测试连接... 目录一、环境准备1.1 环境说明1.2 创建 namespace1.3 创建mongdb账号/密码二、创建Sec

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

centos7基于keepalived+nginx部署k8s1.26.0高可用集群

《centos7基于keepalived+nginx部署k8s1.26.0高可用集群》Kubernetes是一个开源的容器编排平台,用于自动化地部署、扩展和管理容器化应用程序,在生产环境中,为了确保集... 目录一、初始化(所有节点都执行)二、安装containerd(所有节点都执行)三、安装docker-

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G