当前位置: 首页 > news >正文

kubernetes-cni 框架源码分析

深入探索 Kubernetes 网络模型和网络通信

Kubernetes 定义了一种简单、一致的网络模型,基于扁平网络结构的设计,无需将主机端口与网络端口进行映射便可以进行高效地通讯,也无需其他组件进行转发。该模型也使应用程序很容易从虚拟机或者主机物理机迁移到 Kubernetes 管理的 pod 中。

这篇文章主要深入探索 Kubernetes 网络模型,并了解容器、pod 间如何进行通讯。对于网络模型的实现将会在后面的文章介绍。

kubernetes网络模型

该模型定义了:

  • 每个 pod 都有自己的 IP 地址,这个 IP 在集群范围内可达

  • Pod 中的所有容器共享 pod IP 地址(包括 MAC 地址),并且容器之前可以相互通信(使用 localhost

  • Pod 可以使用 pod IP 地址与集群中任一节点上的其他 pod 通信,无需 NAT

  • Kubernetes 的组件之间可以相互通信,也可以与 pod 通信

  • 网络隔离可以通过网络策略实现

上面的定义中提到了几个相关的组件:

  • Pod:Kubernetes 中的 pod 有点类似虚拟机有唯一的 IP 地址,同一个节点上的 pod 共享网络和存储。

  • Container:pod 是一组容器的集合,这些容器共享同一个网络命名空间。pod 内的容器就像虚拟机上的进程,进程之间可以使用 localhost 进行通信;容器有自己独立的文件系统、CPU、内存和进程空间。需要通过创建 Pod 来创建容器。

  • Node:pod 运行在节点上,集群中包含一个或多个节点。每个 pod 的网络命名空间都会连接到节点的命名空间上,以打通网络。

网络命名空间如何工作

在 Kubernetes 的发行版 k3s 创建一个 pod,这个 pod 有两个容器:发送请求的 curl 容器和提供 web 服务的 httpbin 容器。

虽然使用发行版,但是其仍然使用 Kubernetes 网络模型,并不妨碍我们了解网络模型。

apiVersion: v1
kind: Pod
metadata:name: multi-container-pod
spec:containers:- image: curlimages/curlname: curlcommand: ["sleep", "365d"]- image: kennethreitz/httpbinname: httpbin

登录到节点上,通过 lsns -t net 当前主机上的网络命名空间,但是并没有找到 httpbin 的进程。有个命名空间的命令是 /pause,这个 pause 进程实际上是每个 pod 中 不可见sandbox 容器进程。关于 sanbox 容器的作用,将会在下一篇容器网络和 CNI 中介绍。

lsns -t netNS TYPE NPROCS    PID USER     NETNSID NSFS                                                COMMAND4026531992 net     126      1 root  unassigned                                                     /lib/systemd/systemd --system --deserialize 314026532247 net       1  83224 uuidd unassigned                                                     /usr/sbin/uuidd --socket-activation4026532317 net       4 129820 65535          0 /run/netns/cni-607c5530-b6d8-ba57-420e-a467d7b10c56 /pauselsns -t netNS TYPE NPROCS    PID USER     NETNSID NSFS                                                COMMAND4026531992 net     126      1 root  unassigned                                                     /lib/systemd/systemd --system --deserialize 314026532247 net       1  83224 uuidd unassigned                                                     /usr/sbin/uuidd --socket-activation4026532317 net       4 129820 65535          0 /run/netns/cni-607c5530-b6d8-ba57-420e-a467d7b10c56 /pauselsns -t netNS TYPE NPROCS    PID USER     NETNSID NSFS                                                COMMAND4026531992 net     126      1 root  unassigned                                                     /lib/systemd/systemd --system --deserialize 314026532247 net       1  83224 uuidd unassigned                                                     /usr/sbin/uuidd --socket-activation4026532317 net       4 129820 65535          0 /run/netns/cni-607c5530-b6d8-ba57-420e-a467d7b10c56 /pause

   既然每个容器都有独立的进程空间,我们换下命令查看进程类型的空间:

lsns -t pidNS TYPE NPROCS    PID USER            COMMAND
4026531836 pid     127      1 root            /lib/systemd/systemd --system --deserialize 314026532387 pid       1 129820 65535           /pause
4026532389 pid       1 129855 systemd-network sleep 365d
4026532391 pid       2 129889 root            /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent

  通过进程 PID 129889 可以找到其所属的命名空间:

ip netns identify 129889
cni-607c5530-b6d8-ba57-420e-a467d7b10c56

   然后可以在该命名空间下使用 exec 执行命令:

ip netns exec cni-607c5530-b6d8-ba57-420e-a467d7b10c56 ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever2: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group defaultlink/ether f2:c8:17:b6:5f:e5 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 10.42.1.14/24 brd 10.42.1.255 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::f0c8:17ff:feb6:5fe5/64 scope linkvalid_lft forever preferred_lft forever

   

从结果来看 pod 的 IP 地址 10.42.1.14 绑定在接口 eth0 上,而 eth0 被连接到 17 号接口上。

在节点主机上,查看 17 号接口信息。veth7912056b 是主机根命名空间下的虚拟以太接口(vitual ethernet device),是连接 pod 网络和节点网络的 隧道,对端是 pod 命名空间下的接口 eth0

ip link | grep -A1 ^1717: veth7912056b@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group defaultlink/ether d6:5e:54:7f:df:af brd ff:ff:ff:ff:ff:ff link-netns cni-607c5530-b6d8-ba57-420e-a467d7b10c56

上面的结果看到,该 veth 连到了个网桥(network bridge)cni0 上。

网桥工作在数据链路层(OSI 模型的第 2 层),连接多个网络(可多个网段)。当请求到达网桥,网桥会询问所有连接的接口(这里 pod 通过 veth 以网桥连接)是否拥有原始请求中的 IP 地址。如果有接口响应,网桥会将匹配信息(IP -> veth)记录,并将数据转发过去。

那如果没有接口响应怎么办?具体流程就要看各个网络插件的实现了。我准备在后面的文章中介绍常用的网络插件,比如 Calico、Flannel、Cilium 等。

接下来看下 Kubernetes 中的网络通信如何完成,一共有几种类型:

  • 同 pod 内容器间通信

  • 同节点上的 pod 间通信

  • 不同节点上的 pod 间通信

Kubernetes网络如何工作

同POD内的容器间通信

同 pod 内的容器间通信最简单,这些容器共享网络命名空间,每个命名空间下都有 lo 回环接口,可以通过 localhost 来完成通信。

同节点上的POD通信

当我们将 curl 容器和 httpbin 分别在两个 pod 中运行,这两个 pod 有可能调度到同一个节点上。curl 发出的请求根据容器内的路由表到达了 pod 内的 eth0 接口。然后通过与 eth0 相连的隧道 veth1 到达节点的根网络空间。

veth1 通过网桥 cni0 与其他 pod 相连虚拟以太接口 vethX 相连,网桥会询问所有相连的接口是否拥有原始请求中的 IP 地址(比如这里的 10.42.1.9)。收到响应后,网桥会记录映射信息(10.42.1.9 => veth0),同时将数据转发过去。最终数据经过 veth0 隧道进入 pod httpbin 中。

       

不同节点的POD通信

跨节点的 pod 间通信会复杂一些,且 不同网络插件的处理方式不同,这里选择一种容易理解的方式来简单说明下。

前半部分的流程与同节点 pod 间通信类似,当请求到达网桥,网桥询问哪个 pod 拥有该 IP 但是没有得到回应。流程进入主机的路由寻址过程,到更高的集群层面。

在集群层面有一张路由表,里面存储着每个节点的 Pod IP 网段(节点加入到集群时会分配一个 Pod 网段(Pod CIDR),比如在 k3s 中默认的 Pod CIDR 是 10.42.0.0/16,节点获取到的网段是 10.42.0.0/2410.42.1.0/2410.42.2.0/24,依次类推)。通过节点的 Pod IP 网段可以判断出请求 IP 的节点,然后请求被发送到该节点。

      

总结

现在应该对 Kubernetes 的网络通信有初步的了解了吧。

整个传输过程需要各种不同组件的参与才完成,而这些组件与 pod 相同的生命周期,跟随 pod 的创建和销毁。容器的维护由 kubelet 委托给容器运行时(container runtime)来完成,而容器的网络命名空间则是由容器运行时委托网络插件共同完成。

  • 创建 pod(容器)的网络命名空间

  • 创建接口

  • 创建 veth 对

  • 设置命名空间网络

  • 设置静态路由

  • 配置以太网桥接器

  • 分配 IP 地址

  • 创建 NAT 规则

认识一下容器网络接口 CNI

上篇我们也提到不同网络插件对 Kubernetes 网络模型有不同的实现,主要集中在跨节点的 pod 间通信的实现上。用户可以根据需要选择合适的网络插件,这其中离不开 CNI(container network interface)。这些网络插件都实现了 CNI 标准,可以与容器编排系统和运行时良好的集成。

CNI是什么

CNI 是 CNCF 下的一个项目,除了提供了最重要的 规范 、用来 CNI 与应用集成的 库、实行 CNI 插件的 CLI cnitool,以及 可引用的插件。本文发布时,最新版本为 v1.1.2。

CNI 只关注容器的网络连接以及在容器销毁时清理/释放分配的资源,也正因为这个,即使容器发展迅速,CNI 也依然能保证简单并被 广泛支持。        

CNI规范

CNI 的规范涵盖了以下几部分:

  • 网络配置文件格式

  • 容器运行时与网络插件交互的协议

  • 插件的执行流程

  • 将委托其他插件的执行流程

  • 返回给运行时的执行结果数据类型

网络配置格式

这里贴出规范中的配置示例,规范 中定义了网络配置的格式,包括必须字段、可选字段以及各个字段的功能。示例使用定义了名为 dbnet 的网络,配置了插件 bridgetuning,这两个插件。

CNI 的插件一般分为两种:

  • 接口插件(interface plugin):用来创建网络接口,比如示例中的 bridge

  • 链式插件(chained):用来调整已创建好的网络接口,比如示例中的 tuning

{"cniVersion": "1.0.0","name": "dbnet","plugins": [{"type": "bridge",// plugin specific parameters"bridge": "cni0","keyA": ["some more", "plugin specific", "configuration"],"ipam": {"type": "host-local",// ipam specific"subnet": "10.1.0.0/16","gateway": "10.1.0.1","routes": [{"dst": "0.0.0.0/0"}]},"dns": {"nameservers": [ "10.1.0.1" ]}},{"type": "tuning","capabilities": {"mac": true},"sysctl": {"net.core.somaxconn": "500"}},{"type": "portmap","capabilities": {"portMappings": true}}]
}

容器运行时与网络插件交互的协议

CNI 为容器运行时提供 四个不同的操作:

  • ADD 将容器添加到网络,或修改配置

  • DEL 从网络中删除容器,或取消修改

  • CHECK 检查容器网络是否正常,如果容器的网络出现问题,则返回错误

  • VERSION 显示插件的版本

规范对操作的输入和输出内容进行了定义。主要几个核心的字段有:

  • CNI_COMMAND:上面的四个操作之一

  • CNI_CONTAINERID:容器 ID

  • CNI_NETNS:容器的隔离域,如果用的网络命名空间,这里的值是网络命名空间的地址

  • CNI_IFNAME:要在容器中创建的接口名,比如 eth0

  • CNI_ARGS:执行参数时传递的参数

  • CNI_PATH:插件可执行文件的路径

插件的执行流程

CNI 将容器上网络配置的 ADDDELETECHECK 操作,成为附加(attachment)。

容器网络配置的操作,需要一个或多个插件的共同操作来完成,因此插件有一定的执行顺序。比如前面的示例配置中,要先创建接口,才能对接口进行调优。

ADD 操作为例,首先执行的一般是 interface plugin,然后在执行 chained plugin。以前一个插件的 输出 PrevResult 与下一个插件的配置会共同作为下一个插件的 输入。 如果是第一个插件,会将网络配置作为输入的一部分。插件可以将前一个插件的 PrevResult 最为自己的输出,也可以结合自身的操作对 PrevResult 进行更新。最后一个插件的输出 PrevResult 作为 CNI 的执行结果返回给容器运行时,容器运行时会保存改结果并将其作为其他操作的输入

DELETE 的执行与 ADD 的顺序正好相反,要先移除接口上的配置或者释放已经分配的 IP,最后才能删除容器网络接口。DELETE 操作的输入就是容器运行时保存的 ADD 操作的结果。

除了定义单次操作中插件的执行顺序,CNI 还对操作的并行操作、重复操作等进行了说明。

插件委托

有一些操作,无论出于何种原因,都不能合理地作为一个松散的链接插件来实现。相反,CNI 插件可能希望将某些功能委托给另一个插件。一个常见的例子是 IP 地址管理(IP Adress Management,简称 IPAM),主要是为容器接口分配/回收 IP 地址、管理路由等。

CNI 定义了第三种插件 – IPAM 插件。CNI 插件可以在恰当的时机调用 IPAM 插件,IPAM 插件会将执行的结果返回给委托方。IPAM 插件会根据指定的协议(如 dhcp)、本地文件中的数据、或者网络配置文件中 ipam 字段的信息来完成操作:分配 IP、设置网关、路由等等。

"ipam": {"type": "host-local",// ipam specific"subnet": "10.1.0.0/16","gateway": "10.1.0.1","routes": [{"dst": "0.0.0.0/0"}]
}
 

执行结果

插件可以返回一下三种结果之一,规范对 结果的格式 进行了定义。

  • Success:同时会包含 PrevResult 信息,比如 ADD 操作后的 PrevResult 返回给容器运行时。

  • Error:包含必要的错误提示信息。

  • Version:这个是 VERSION 操作的返回结果。

CNI调用流程

Kubelet 监听到 Pod 调度到当前节点后,通过 rpc 调用 CRI(containerd, cri-o 等),CRI 创建 Sandbox 容器,初始化 Cgroup 与 Namespace,然后再调用 CNI 插件分配 IP,最后完成容器创建与启动。

不同于 CRI、CSI 通过 rpc 通信,CNI 是通过二进制接口调用的,通过环境变量和标准输入传递具体网络配置,下图为 Flannel CNI 插件的工作流程,通过链式调用 CNI 插件实现对 Pod 的 IP 分配、网络配置:

CNI接口

CNI 的库是指 libcni,用于 CNI 和应用程序集成,定义了 CNI 相关的接口和配置。

type CNI interface {  AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)  CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error  DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error  GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)  GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)  AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)  CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error  DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error  GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)  GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)  ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)  ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)  
}

以添加网络的部分代码为例:

func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {  ...   return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)  
}

执行的逻辑简单来说就是:

  1. 查找可执行文件

  2. 加载网络配置

  3. 执行 ADD 操作

  4. 结果处理

对应的CNI插件中需要实现的接口:

CNI(Container Networking Interface)插件需要实现以下接口函数:

  1. CmdAdd(args *skel.CmdArgs) error

    1. CmdAdd 函数在容器启动时调用,用于添加容器的网络配置。它接收一个 skel.CmdArgs 参数,包含了插件需要的输入信息,如网络配置、接口名称和网络命名空间。

    2. 该函数负责执行网络设置操作,包括创建虚拟网络设备、配置 IP 地址、设置路由规则等。最后,它应返回一个 error 类型,表示操作的成功或失败。

  2. CmdDel(args *skel.CmdArgs) error

    1. CmdDel 函数在容器停止时调用,用于删除容器的网络配置。它接收一个 skel.CmdArgs 参数,包含了插件需要的输入信息。

    2. 该函数应该执行清理操作,包括删除虚拟网络设备、清除 IP 地址、路由规则等。最后,它应返回一个 error 类型,表示操作的成功或失败。

  3. GetCapabilities() (*capabilities.Capabilities, error)

    1. GetCapabilities 函数返回插件的功能能力。它应返回一个 capabilities.Capabilities 对象,该对象描述了插件支持的功能和特性。

    2. 通常,GetCapabilities 函数可以返回支持的 CNI 版本和插件的网络类型("bridge"、"ipvlan" 等)等信息。

  4. Check(args *skel.CmdArgs) error(可选):

    1. Check 函数用于检查插件的配置是否正确。它接收一个 skel.CmdArgs 参数,包含了插件需要的输入信息。

    2. 该函数应该检查配置的有效性,并返回一个 error 类型,表示配置的检查结果(成功或失败)。

这些接口函数是 CNI 插件必须实现的核心函数。具体的插件实现可能会根据需要包含其他的辅助函数或结构体。CNI 插件还应该提供一个入口函数,例如 main 函数,用于初始化插件并根据命令行参数调用适当的功能函数。

CNI插件用例

https://qingwave.github.io/how-to-write-k8s-cni/

https://github.com/qingwave/mycni

CNI源码分析

源码版本

release/1.7

创建sandbox

criService.RunPodSandbox

// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
// the sandbox is in ready state.
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {config := r.GetConfig()log.G(ctx).Debugf("Sandbox config %+v", config)// Generate unique id and name for the sandbox and reserve the name.id := util.GenerateID()metadata := config.GetMetadata()if metadata == nil {return nil, errors.New("sandbox config must include metadata")}// sanbox的名称是使用meatada中的name,namespace, id等拼接成的name := makeSandboxName(metadata)log.G(ctx).WithField("podsandboxid", id).Debugf("generated id for sandbox name %q", name)// cleanupErr records the last error returned by the critical cleanup operations in deferred functions,// like CNI teardown and stopping the running sandbox task.// If cleanup is not completed for some reason, the CRI-plugin will leave the sandbox// in a not-ready state, which can later be cleaned up by the next execution of the kubelet's syncPod workflow.var cleanupErr error// Reserve the sandbox name to avoid concurrent `RunPodSandbox` request starting the// same sandbox.if err := c.sandboxNameIndex.Reserve(name, id); err != nil {return nil, fmt.Errorf("failed to reserve sandbox name %q: %w", name, err)}defer func() {// Release the name if the function returns with an error and all the resource cleanup is done.// When cleanupErr != nil, the name will be cleaned in sandbox_remove.if retErr != nil && cleanupErr == nil {c.sandboxNameIndex.ReleaseByName(name)}}()// Create initial internal sandbox object.// 创建sanbox实例sandbox := sandboxstore.NewSandbox(sandboxstore.Metadata{ID:             id,Name:           name,Config:         config,RuntimeHandler: r.GetRuntimeHandler(),},sandboxstore.Status{State:     sandboxstore.StateUnknown,CreatedAt: time.Now().UTC(),},)// 确保创建sandbox时需要的pause镜像存在// Ensure sandbox container image snapshot.image, err := c.ensureImageExists(ctx, c.config.SandboxImage, config)if err != nil {return nil, fmt.Errorf("failed to get sandbox image %q: %w", c.config.SandboxImage, err)}containerdImage, err := c.toContainerdImage(ctx, *image)if err != nil {return nil, fmt.Errorf("failed to get image from containerd %q: %w", image.ID, err)}// 获取 runtimeociRuntime, err := c.getSandboxRuntime(config, r.GetRuntimeHandler())if err != nil {return nil, fmt.Errorf("failed to get sandbox runtime: %w", err)}log.G(ctx).WithField("podsandboxid", id).Debugf("use OCI runtime %+v", ociRuntime)runtimeStart := time.Now()// Create sandbox container.// NOTE: sandboxContainerSpec SHOULD NOT have side// effect, e.g. accessing/creating files, so that we can test// it safely.// NOTE: the network namespace path will be created later and update through updateNetNamespacePath functionspec, err := c.sandboxContainerSpec(id, config, &image.ImageSpec.Config, "", ociRuntime.PodAnnotations)if err != nil {return nil, fmt.Errorf("failed to generate sandbox container spec: %w", err)}log.G(ctx).WithField("podsandboxid", id).Debugf("sandbox container spec: %#+v", spew.NewFormatter(spec))sandbox.ProcessLabel = spec.Process.SelinuxLabeldefer func() {if retErr != nil {selinux.ReleaseLabel(sandbox.ProcessLabel)}}()// handle any KVM based runtime// kvm runtime 需要特殊处理,例如 kata runtime, 这是基于虚拟化技术启动的容器if err := modifyProcessLabel(ociRuntime.Type, spec); err != nil {return nil, err}if config.GetLinux().GetSecurityContext().GetPrivileged() {// If privileged don't set selinux label, but we still record the MCS label so that// the unused label can be freed later.spec.Process.SelinuxLabel = ""}// 开始创建pause 容器// Generate spec options that will be applied to the spec later.specOpts, err := c.sandboxContainerSpecOpts(config, &image.ImageSpec.Config)if err != nil {return nil, fmt.Errorf("failed to generate sandbox container spec options: %w", err)}sandboxLabels := buildLabels(config.Labels, image.ImageSpec.Config.Labels, containerKindSandbox)runtimeOpts, err := generateRuntimeOptions(ociRuntime, c.config)if err != nil {return nil, fmt.Errorf("failed to generate runtime options: %w", err)}sOpts := []snapshots.Opt{snapshots.WithLabels(snapshots.FilterInheritedLabels(config.Annotations))}extraSOpts, err := sandboxSnapshotterOpts(config)if err != nil {return nil, err}sOpts = append(sOpts, extraSOpts...)opts := []containerd.NewContainerOpts{containerd.WithSnapshotter(c.runtimeSnapshotter(ctx, ociRuntime)),customopts.WithNewSnapshot(id, containerdImage, sOpts...),containerd.WithSpec(spec, specOpts...),containerd.WithContainerLabels(sandboxLabels),containerd.WithContainerExtension(sandboxMetadataExtension, &sandbox.Metadata),containerd.WithRuntime(ociRuntime.Type, runtimeOpts)}container, err := c.client.NewContainer(ctx, id, opts...)if err != nil {return nil, fmt.Errorf("failed to create containerd container: %w", err)}// pause是在pod中,但是却别于业务的特殊容器,这里索引与sandbox关联// Add container into sandbox store in INIT state.sandbox.Container = container//...if !hostNetwork(config) && userNsEnabled {// If userns is enabled, then the netns was created by the OCI runtime// when creating "task". The OCI runtime needs to create the netns// because, if userns is in use, the netns needs to be owned by the// userns. So, let the OCI runtime just handle this for us.// If the netns is not owned by the userns several problems will happen.// For instance, the container will lack permission (even if// capabilities are present) to modify the netns or, even worse, the OCI// runtime will fail to mount sysfs://  https://github.com/torvalds/linux/commit/7dc5dbc879bd0779924b5132a48b731a0bc04a1e#diff-4839664cd0c8eab716e064323c7cd71fR1164netStart := time.Now()/// 设置命名空间// If it is not in host network namespace then create a namespace and set the sandbox// handle. NetNSPath in sandbox metadata and NetNS is non empty only for non host network// namespaces. If the pod is in host network namespace then both are empty and should not// be used.var netnsMountDir = "/var/run/netns"if c.config.NetNSMountsUnderStateDir {netnsMountDir = filepath.Join(c.config.StateDir, "netns")}sandbox.NetNS, err = netns.NewNetNSFromPID(netnsMountDir, task.Pid())if err != nil {return nil, fmt.Errorf("failed to create network namespace for sandbox %q: %w", id, err)}// Verify task is still in created state.if st, err := task.Status(ctx); err != nil || st.Status != containerd.Created {return nil, fmt.Errorf("failed to create pod sandbox %q: err is %v - status is %q and is expected %q", id, err, st.Status, containerd.Created)}sandbox.NetNSPath = sandbox.NetNS.GetPath()defer func() {// Remove the network namespace only if all the resource cleanup is done.if retErr != nil && cleanupErr == nil {if cleanupErr = sandbox.NetNS.Remove(); cleanupErr != nil {log.G(ctx).WithError(cleanupErr).Errorf("Failed to remove network namespace %s for sandbox %q", sandbox.NetNSPath, id)return}sandbox.NetNSPath = ""}}()// Update network namespace in the container's specc.updateNetNamespacePath(spec, sandbox.NetNSPath)if err := container.Update(ctx,// Update spec of the containercontainerd.UpdateContainerOpts(containerd.WithSpec(spec)),// Update sandbox metadata to include NetNS infocontainerd.UpdateContainerOpts(containerd.WithContainerExtension(sandboxMetadataExtension, &sandbox.Metadata))); err != nil {return nil, fmt.Errorf("failed to update the network namespace for the sandbox container %q: %w", id, err)}// Define this defer to teardownPodNetwork prior to the setupPodNetwork function call.// This is because in setupPodNetwork the resource is allocated even if it returns error, unlike other resource creation functions.defer func() {// Teardown the network only if all the resource cleanup is done.if retErr != nil && cleanupErr == nil {deferCtx, deferCancel := util.DeferContext()defer deferCancel()// Teardown network if an error is returned.if cleanupErr = c.teardownPodNetwork(deferCtx, sandbox); cleanupErr != nil {log.G(ctx).WithError(cleanupErr).Errorf("Failed to destroy network for sandbox %q", id)}}}()// Setup network for sandbox.// Certain VM based solutions like clear containers (Issue containerd/cri-containerd#524)// rely on the assumption that CRI shim will not be querying the network namespace to check the// network states such as IP.// In future runtime implementation should avoid relying on CRI shim implementation details.// In this case however caching the IP will add a subtle performance enhancement by avoiding// calls to network namespace of the pod to query the IP of the veth interface on every// SandboxStatus request.// 设置sandbox的网络if err := c.setupPodNetwork(ctx, &sandbox); err != nil {return nil, fmt.Errorf("failed to setup network for sandbox %q: %w", id, err)}sandboxCreateNetworkTimer.UpdateSince(netStart)}err = c.nri.RunPodSandbox(ctx, &sandbox)if err != nil {return nil, fmt.Errorf("NRI RunPodSandbox failed: %w", err)}defer func() {if retErr != nil {deferCtx, deferCancel := util.DeferContext()defer deferCancel()c.nri.RemovePodSandbox(deferCtx, &sandbox)}}()// 启动sandbox containerif err := task.Start(ctx); err != nil {return nil, fmt.Errorf("failed to start sandbox container task %q: %w", id, err)}if err := sandbox.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) {// Set the pod sandbox as ready after successfully start sandbox container.status.Pid = task.Pid()status.State = sandboxstore.StateReadystatus.CreatedAt = info.CreatedAtreturn status, nil}); err != nil {return nil, fmt.Errorf("failed to update sandbox status: %w", err)}if err := c.sandboxStore.Add(sandbox); err != nil {return nil, fmt.Errorf("failed to add sandbox %+v into store: %w", sandbox, err)}// Send CONTAINER_CREATED event with both ContainerId and SandboxId equal to SandboxId.// Note that this has to be done after sandboxStore.Add() because we need to get// SandboxStatus from the store and include it in the event.c.generateAndSendContainerEvent(ctx, id, id, runtime.ContainerEventType_CONTAINER_CREATED_EVENT)// start the monitor after adding sandbox into the store, this ensures// that sandbox is in the store, when event monitor receives the TaskExit event.//// TaskOOM from containerd may come before sandbox is added to store,// but we don't care about sandbox TaskOOM right now, so it is fine.c.eventMonitor.startSandboxExitMonitor(context.Background(), id, task.Pid(), exitCh)// Send CONTAINER_STARTED event with both ContainerId and SandboxId equal to SandboxId.c.generateAndSendContainerEvent(ctx, id, id, runtime.ContainerEventType_CONTAINER_STARTED_EVENT)sandboxRuntimeCreateTimer.WithValues(ociRuntime.Type).UpdateSince(runtimeStart)return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
}

criService.setupPodNetwork

pod的网络主要是在sandbox创建过程中,由cni插件创建的。

// setupPodNetwork setups up the network for a pod
func (c *criService) setupPodNetwork(ctx context.Context, sandbox *sandboxstore.Sandbox) error {var (id        = sandbox.IDconfig    = sandbox.Configpath      = sandbox.NetNSPath// 获取加载的CNI插件netPlugin = c.getNetworkPlugin(sandbox.RuntimeHandler)err       errorresult    *cni.Result)if netPlugin == nil {return errors.New("cni config not initialized")}opts, err := cniNamespaceOpts(id, config)if err != nil {return fmt.Errorf("get cni namespace options: %w", err)}log.G(ctx).WithField("podsandboxid", id).Debugf("begin cni setup")netStart := time.Now()// 使用CNI插件创建网络if c.config.CniConfig.NetworkPluginSetupSerially {result, err = netPlugin.SetupSerially(ctx, id, path, opts...)} else {result, err = netPlugin.Setup(ctx, id, path, opts...)}networkPluginOperations.WithValues(networkSetUpOp).Inc()networkPluginOperationsLatency.WithValues(networkSetUpOp).UpdateSince(netStart)if err != nil {networkPluginOperationsErrors.WithValues(networkSetUpOp).Inc()return err}logDebugCNIResult(ctx, id, result)// Check if the default interface has IP configif configs, ok := result.Interfaces[defaultIfName]; ok && len(configs.IPConfigs) > 0 {sandbox.IP, sandbox.AdditionalIPs = selectPodIPs(ctx, configs.IPConfigs, c.config.IPPreference)sandbox.CNIResult = resultreturn nil}return fmt.Errorf("failed to find network info for sandbox %q", id)
}

CNI插件

CNI插件的加载

NewCRIService

cni 的加载是在kubelet 中进行加载的

// NewCRIService returns a new instance of CRIService
func NewCRIService(config criconfig.Config, client *containerd.Client, nri *nri.API, warn warning.Service) (CRIService, error) {...c.cniNetConfMonitor = make(map[string]*cniNetConfSyncer)for name, i := range c.netPlugin {path := c.config.NetworkPluginConfDirif name != defaultNetworkPlugin {if rc, ok := c.config.Runtimes[name]; ok {path = rc.NetworkPluginConfDir}}if path != "" {// 开始按照默认流程加载CNI相关m, err := newCNINetConfSyncer(path, i, c.cniLoadOptions())if err != nil {return nil, fmt.Errorf("failed to create cni conf monitor for %s: %w", name, err)}c.cniNetConfMonitor[name] = m}}// Preload base OCI specsc.baseOCISpecs, err = loadBaseOCISpecs(&config)if err != nil {return nil, err}// Load all sandbox controllers(pod sandbox controller and remote shim controller)c.sandboxControllers[criconfig.ModePodSandbox] = podsandbox.New(config, client, c.sandboxStore, c.os, c, c.baseOCISpecs)c.sandboxControllers[criconfig.ModeShim] = client.SandboxController()c.nri = nrireturn c, nil
}

 defaultCNIConfig

用户未配置cni目录时将使用默认配置, 可见默认cni配置文件存放路径

func defaultCNIConfig() *libcni {return &libcni{config: config{// 默认加载cni插件资源的目录pluginDirs:       []string{DefaultCNIDir},pluginConfDir:    DefaultNetDir,pluginMaxConfNum: DefaultMaxConfNum,prefix:           DefaultPrefix,},cniConfig: cnilibrary.NewCNIConfig([]string{DefaultCNIDir,},&invoke.DefaultExec{RawExec:       &invoke.RawExec{Stderr: os.Stderr},PluginDecoder: version.PluginDecoder{},},),networkCount: 1,}
}
const (DefaultNetDir        = "/etc/cni/net.d"DefaultCNIDir        = "/opt/cni/bin"VendorCNIDirTemplate = "%s/opt/%s/bin"
)

CNI插件的调用

CNIConfig.addNetwork

根据libcni.Setup ->attachNetworks->asynchAttach->Attach->AddNetworkList->addNetwork

可见执行cni的底层函数如下

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)if err != nil {return nil, err}if err := utils.ValidateContainerID(rt.ContainerID); err != nil {return nil, err}if err := utils.ValidateNetworkName(name); err != nil {return nil, err}if err := utils.ValidateInterfaceName(rt.IfName); err != nil {return nil, err}newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)if err != nil {return nil, err}// 调用路径下的cni插件执行文件// 传入参数ADD表示调用其中网络的cmdAdd 方法return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
}

参考文章

https://atbug.com/how-kubelete-container-runtime-work-with-cni/

https://atbug.com/deep-dive-k8s-network-mode-and-communication/

https://www.cnblogs.com/lianngkyle/p/15171630.html

https://blog.csdn.net/weixin_40056921/article/details/129157735

相关文章:

kubernetes-cni 框架源码分析

深入探索 Kubernetes 网络模型和网络通信 Kubernetes 定义了一种简单、一致的网络模型&#xff0c;基于扁平网络结构的设计&#xff0c;无需将主机端口与网络端口进行映射便可以进行高效地通讯&#xff0c;也无需其他组件进行转发。该模型也使应用程序很容易从虚拟机或者主机物…...

AI Agent有哪些痛点问题

AI Agent有哪些痛点问题 目录 AI Agent有哪些痛点问题AI Agent领域有哪些知名的论文缺乏一个将智能多智能体技术和在真实环境中学习的两个适用流程结合起来的统一框架LLM的代理在量化和客观评估方面存在挑战自主代理在动态环境中学习、推理和驾驭不确定性存在挑战AI Agent领域有…...

使用Java爬虫获取京东JD.item_sku API接口数据

在电商领域&#xff0c;商品的SKU&#xff08;Stock Keeping Unit&#xff09;信息是运营和管理的关键数据。SKU信息包括商品的规格、价格、库存等&#xff0c;对于商家的库存管理、定价策略和市场分析至关重要。京东作为国内领先的电商平台&#xff0c;提供了丰富的API接口&am…...

华为云+硅基流动使用Chatbox接入DeepSeek-R1满血版671B

华为云硅基流动使用Chatbox接入DeepSeek-R1满血版671B 硅基流动 1.1 注册登录 1.2 实名认证 1.3 创建API密钥 1.4 客户端工具 OllamaChatboxCherry StudioAnythingLLM 资源包下载&#xff1a; AI聊天本地客户端 接入Chatbox客户端 点击设置 选择SiliconFloW API 粘贴1.3创…...

平方数列与立方数列求和的数学推导

先上结论&#xff1a; 平方数列求和公式为&#xff1a; S 2 ( n ) n ( n 1 ) ( 2 n 1 ) 6 S_2(n) \frac{n(n1)(2n1)}{6} S2​(n)6n(n1)(2n1)​ 立方数列求和公式为&#xff1a; S 3 ( n ) ( n ( n 1 ) 2 ) 2 S_3(n) \left( \frac{n(n1)}{2} \right)^2 S3​(n)(2n(n1)​…...

Java中的synchronized关键字与锁升级机制

在多线程编程中&#xff0c;线程同步是确保程序正确执行的关键。当多个线程同时访问共享资源时&#xff0c;如果不进行同步管理&#xff0c;可能会导致数据不一致的问题。为了避免这些问题&#xff0c;Java 提供了多种同步机制&#xff0c;其中最常见的就是 synchronized 关键字…...

告别传统校准!GNSS模拟器在计量行业的应用

随着GNSS技术的不断进步&#xff0c;各类设备广泛采用该技术实现高精度定位&#xff0c;并推动了其在众多领域的广泛应用。对于关键行业如汽车制造和基础设施&#xff0c;设备的可用性和可靠性被视为基本准则&#xff0c;GNSS作为提供“绝对位置”信息的关键传感器&#xff0c;…...

数据结构结尾

1.二叉树的分类 搜索二叉树&#xff0c;平衡二叉树&#xff0c;红黑树&#xff0c;B树&#xff0c;B树 2.Makefile文件管理 注意&#xff1a; 时间戳&#xff1a;根据时间戳&#xff0c;只编译发生修改后的文件 算法&#xff1a; 算法有如上五个要求。 算法的时间复杂度&am…...

【golang】量化开发学习(一)

均值回归策略简介 均值回归&#xff08;Mean Reversion&#xff09;假设价格会围绕均值波动&#xff0c;当价格偏离均值一定程度后&#xff0c;会回归到均值。 基本逻辑&#xff1a; 计算一段时间内的移动均值&#xff08;如 20 天均线&#xff09;。当当前价格高于均值一定比…...

AI前端开发:跨领域合作的新引擎

随着人工智能技术的飞速发展&#xff0c;AI代码生成器等工具的出现正深刻地改变着软件开发的模式。 AI前端开发的兴起&#xff0c;不仅提高了开发效率&#xff0c;更重要的是促进了跨领域合作&#xff0c;让数据科学家、UI/UX设计师和前端工程师能够更紧密地协同工作&#xff0…...

数组练习(深入理解、实践数组)

1.练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚 编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h> int main() {//解题思路&#xff1a;//根据题意再…...

Bigemap Pro如何进行面裁剪

一般在处理矢量数据&#xff0c;制图过程中&#xff0c;常常会用到面文件的裁剪功能&#xff0c;那么有没有一个工具可以同时实现按照线、顶点、网格以及面来裁剪呢&#xff1f;今天给大家介绍一个宝藏工具&#xff0c;叫做Bigemap Pro&#xff0c;在这里工具里面可以实现上述面…...

acwing算法全总结-数学知识

快速幂 原题链接&#xff1a;快速幂 ac代码&#xff1a; #include<iostream> #include<algorithm> using namespace std; typedef long long LL; LL qmi(int a,int b,int p) {LL res1%p;while(b)//这里本应该分两次进行&#xff0c;不过只有一次询问{if(b&1)…...

SpringMVC学习使用

一、SpringMVC简单理解 1.1 Spring与Web环境集成 1.1.1 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的&#xff0c;但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(sp…...

10、《文件上传与下载:MultipartFile与断点续传设计》

文件上传与下载&#xff1a;MultipartFile与断点续传设计 一、基础文件上传与MultipartFile解析 1.1 Spring MVC文件上传基础 PostMapping("/upload") public String handleFileUpload(RequestParam("file") MultipartFile file) {if (!file.isEmpty())…...

DeepSeek 本地部署(电脑安装)

1.先安装Ollama 开源框架 网址链接为:Ollama 2.点中间的下载 3.选系统 4.下载好就安装 5.输入命令ollama -v 6.点击Model 7.选如下 8.选版本 9.复杂对应命令 10.控制台粘贴下载 11.就可以问问题啦 12.配置UI界面(在扩展里面输入) 13.配置完即可打开 14.选择刚才安装的就好啦…...

DeepSeek、Kimi、文心一言、通义千问:AI 大语言模型的对比分析

在人工智能领域&#xff0c;DeepSeek、Kimi、文心一言和通义千问作为国内领先的 AI 大语言模型&#xff0c;各自展现出了独特的特点和优势。本文将从技术基础、应用场景、用户体验和价格与性价比等方面对这四个模型进行对比分析&#xff0c;帮助您更好地了解它们的特点和优势。…...

Docker compose 以及镜像使用

Docker compose 以及镜像使用 高级配置 使用 Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。以下是一个 docker-compose.yml 示例&#xff1a; version: 3 services:web:image: my-appbuild: .ports:- "8000:8000"volumes:- …...

HCIA项目实践--RIP相关原理知识面试问题总结回答

9.4 RIP 9.4.1 补充概念 什么是邻居&#xff1f; 邻居指的是在网络拓扑结构中与某一节点&#xff08;如路由器&#xff09;直接相连的其他节点。它们之间可以直接进行通信和数据交互&#xff0c;能互相交换路由信息等&#xff0c;以实现网络中的数据转发和路径选择等功能。&am…...

使用Python进行云计算:AWS、Azure、和Google Cloud的比较

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行云计算&#xff1a;AWS、Azure、和Google Cloud的比较 随着云计算的普及&am…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...