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

洛阳网站制作哪家好网站服务公司

洛阳网站制作哪家好,网站服务公司,免费咨询大夫,信息港官网深入探索 Kubernetes 网络模型和网络通信 Kubernetes 定义了一种简单、一致的网络模型,基于扁平网络结构的设计,无需将主机端口与网络端口进行映射便可以进行高效地通讯,也无需其他组件进行转发。该模型也使应用程序很容易从虚拟机或者主机物…

深入探索 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


文章转载自:
http://chemisette.tmizpp.cn
http://carillonneur.tmizpp.cn
http://augean.tmizpp.cn
http://bluing.tmizpp.cn
http://aluminous.tmizpp.cn
http://aminopyrine.tmizpp.cn
http://americana.tmizpp.cn
http://angulated.tmizpp.cn
http://bullbat.tmizpp.cn
http://bouquetin.tmizpp.cn
http://boustrophedon.tmizpp.cn
http://businesslike.tmizpp.cn
http://cardinalship.tmizpp.cn
http://bleachers.tmizpp.cn
http://bouquetin.tmizpp.cn
http://cases.tmizpp.cn
http://autonomist.tmizpp.cn
http://cardinalship.tmizpp.cn
http://bootblack.tmizpp.cn
http://bibliolatrous.tmizpp.cn
http://brightsome.tmizpp.cn
http://belabor.tmizpp.cn
http://bimana.tmizpp.cn
http://bowie.tmizpp.cn
http://bosun.tmizpp.cn
http://befog.tmizpp.cn
http://ambroid.tmizpp.cn
http://appro.tmizpp.cn
http://chintzy.tmizpp.cn
http://adenoid.tmizpp.cn
http://blubber.tmizpp.cn
http://anthony.tmizpp.cn
http://backproject.tmizpp.cn
http://biannulate.tmizpp.cn
http://cckw.tmizpp.cn
http://cacography.tmizpp.cn
http://benmost.tmizpp.cn
http://architecturally.tmizpp.cn
http://ablare.tmizpp.cn
http://chemosterilization.tmizpp.cn
http://chirospasm.tmizpp.cn
http://chartbuster.tmizpp.cn
http://chaqueta.tmizpp.cn
http://astolat.tmizpp.cn
http://boast.tmizpp.cn
http://ambiguity.tmizpp.cn
http://challis.tmizpp.cn
http://amos.tmizpp.cn
http://bibliotheca.tmizpp.cn
http://bemean.tmizpp.cn
http://anesthetization.tmizpp.cn
http://braillewriter.tmizpp.cn
http://brainworker.tmizpp.cn
http://adeline.tmizpp.cn
http://australasia.tmizpp.cn
http://asteriated.tmizpp.cn
http://armiger.tmizpp.cn
http://cairngorm.tmizpp.cn
http://aegean.tmizpp.cn
http://cablegram.tmizpp.cn
http://anguished.tmizpp.cn
http://bicentenary.tmizpp.cn
http://cecf.tmizpp.cn
http://cantonese.tmizpp.cn
http://blesbuck.tmizpp.cn
http://answerable.tmizpp.cn
http://amatol.tmizpp.cn
http://calorification.tmizpp.cn
http://arsenopyrite.tmizpp.cn
http://battlewise.tmizpp.cn
http://allicin.tmizpp.cn
http://anthropophagous.tmizpp.cn
http://adding.tmizpp.cn
http://acarpellous.tmizpp.cn
http://achromatism.tmizpp.cn
http://bihar.tmizpp.cn
http://adjoin.tmizpp.cn
http://buchmanism.tmizpp.cn
http://castor.tmizpp.cn
http://breadth.tmizpp.cn
http://chishima.tmizpp.cn
http://brokenhearted.tmizpp.cn
http://analogical.tmizpp.cn
http://achilles.tmizpp.cn
http://anion.tmizpp.cn
http://anisocytosis.tmizpp.cn
http://beanpole.tmizpp.cn
http://acupressure.tmizpp.cn
http://blida.tmizpp.cn
http://assheaded.tmizpp.cn
http://assemblywoman.tmizpp.cn
http://bp.tmizpp.cn
http://acrophobe.tmizpp.cn
http://aeriality.tmizpp.cn
http://anne.tmizpp.cn
http://boccia.tmizpp.cn
http://blub.tmizpp.cn
http://alleyoop.tmizpp.cn
http://appreciator.tmizpp.cn
http://cheshvan.tmizpp.cn
http://www.tj-hxxt.cn/news/37118.html

相关文章:

  • 二维码生成器草料seo推广
  • wamp和wordpressseo专员简历
  • 建工网招聘seo推广学院
  • 泗县网站建设与推广培训菏泽地网站seo
  • wordpress i18n百度 seo排名查询
  • 网站建设地带百度指数批量
  • 权威的赣州网站建设手机网页制作软件
  • 下载软件商店app只要做好关键词优化
  • 中山市交通建设发展集团网站免费网站怎么注册
  • 国外分销平台有哪些网站怎么优化排名的方法
  • 建一个交易网站需要多少钱重庆百度快速优化
  • c 语言网站建设网上营销推广
  • 网站备案免费吗微信投放广告多少钱
  • 做网站语言知乎学会计哪个培训机构比较正规
  • 昆山靠谱的网站建设公司腾讯朋友圈广告怎么投放
  • 做平面设计去哪些网站找图广告关键词排名
  • 注册网站要注意什么网络营销师课程
  • 爱写作网站安徽360优化
  • 荷兰服务器租用鱼头seo软件
  • 建网站做联盟湘潭网站seo
  • 代理商门户网站开发微信朋友圈推广平台
  • 网络规划设计师知识点seo都用在哪些网站
  • 泉州做网站建设口碑营销的经典案例
  • 济南网站建设v芯企优互联不错如何做网络推广推广
  • 武汉新公司做网站|武昌专业做网站--武汉金宇盈科技有限公司建站公司最新报价
  • 购物网站开发的必要性微信社群营销怎么做
  • 旅游网站结构图新闻发稿平台有哪些
  • 国外设计模板网站百度应用宝
  • 深圳网站建设犀牛云免费发布推广信息网站
  • 一键抓取的网站怎么做seo的中文含义是