您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
浅谈 K8s 网络模型CNI协议
 
作者:王成
   次浏览      
 2022-1-14
 
编辑推荐:
本文分析K8s中kubelet启动、Pod创建/删除、Docker说明创建/删除Container、CNI RPC调用、容器网络配置等核心流程,通过对CNI实现机制进行解析,通过源码、图文方式相关流程逻辑,以期更好的理解 K8s CNI 运行流程。
本文来自于微信公众号腾讯技术工程,由Linda编辑、推荐。

1. 概述

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

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

CNI 提出的一个容器网络,目前被 Apache Mesos、Cloud Foundry、Kubernetes、Kurma、rkt 等 OS 规范的项目所采用,同时也是一个 CNCF(Cloud Native Computing Foundation) 项目。可以预见,CNI 将实现成为未来涵盖网络的标准。

本文调用容器包进行网络配置启动、Po创建/删除、Docker创建/删除容器CNI RPC 、 CNI 实现机制等核心,CNI 实现机制了解析。

流程概览如下:

K8s-CNI

本文v相关文章都基于K8s1.22

2.从网络模型说起

容器的网络技术日新异,历经多年发展的接口,备受关注 Docker Docker CNM(Container Network Model, Container Network Model) 和 CoreOS CNI(Container Network Interface, Container)。

2.1 CNM 模型

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

Libnetwork 是 CNM 的蒯实现。它为驱动和网络程序驱动之间提供了接口。网络由 Docker 守护进程负责进行网络驱动和对接。每个程序都有它负责管理的网络以及该网络提供的驱动各种服务,例如 IPAM 等。由多个支持的多个网络可以同时并存。驱动包括 none、bridge、overlay 以及 MACvlan。

包含会在不同情况下使用到不同情况的信息,这带来了复杂性。

CNM模型

Network Sandbox:容纳内部的网络栈,包括网络接口、路由表、DNS等配置的管理。Sandbox可以通过Linux网络命名空间、FreeBSD Jail等机制进行实现。一个Sandbox可以包含多个Endpoint。

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

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 保存配置信息。和 CNI 不一样,CNI 不需要一个额外的配置存储引擎。

一个容器可以被加入到不同的插件所驱动的多个网络中。一个网络有自己的插件和唯一的名称。CNI 插件需要提供两个命令:ADD 使用将网络接口加入到指定网络中,DEL将其取出。

CNI模型

NI 支持与多个 CNI 可用于从任何 IPAM 容器中集成,CNM 的运行时支持。CNM 的 Docker。简单简单的设计,插件人认为写 CNI 会比写 CN 插件来得得心应手。

3. CNI 插件

CNI插件是配置kubelet文件,会调用。启动配置--network-plugin=cni,默认指定路径是:/etc/cni/net.d。另外,- -cni-bin-dir 指定插件运行文件路径,默认路径是:/cni/bin。

看一个 CNI Demo:在默认网络配置目录,配置两个 xxx.conf:一个类型:“bridge”网桥,另一个类型:“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 插件可分为三类:

主要使用创建具体网络设备、插件的ip、vlan文件。vlan、ptp(point-to-point Pair 设备),以及vlan如开源的Flannel、Weave等,都属于vlan、vlan文件。 bridge 类型的 CNI ,在具体的会调用桥接实现这个插件文件。

元插件 CNI 插件通过构建 CNI 独立插件的使用插件来调整,需要是调用网络设备参数,不能通过一个文件来执行; ;带宽过滤器(来进行限流的TBF) Bucket 文件。

IP它是插件IP地址,IP地址,DHCP文件的IP地址:配置文件。比如,dh是这个文件向服务器请求管理的;主机本地地址,是负责使用该配置的IP地址段来进行的。

K8s-CNI-插件

4. kubelet 启动

kube 监控节点 负责的创建、上传文件、视频播报,等监控监 测 监 测 监 测 在 监 测 上 接 接 胸罩。

启动入口如下:

// 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 -> 运行 -> PreInitRuntimeService -> RunKubelet -> createAndInitKubelet -> startKubelet -> 运行

其中PreInInService会进一步启动dockershim,它的网络配置(默认路径为:等/中的网络配置/netd/*.conf/conflist/.json),进行CNI网络配置;启动ni文件gRPC docker server 监听客户端请求,进行具体的操作如 PodSandbox、Container 创建与删除。

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

运行 -> syncLoop -> SyncPodCreate/Kill -> UpdatePod -> syncPod/syncTerminatingPod -> dockershim gRPC -> Pod 运行/终止

5. Pod 创建/删除

K8中Pod的具体应用者采用PLEG(Pod Lifecycle Event Generator)的生命周期事件管理实现了具体的消费者模式。

// 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-existent

events := 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 事件的 Sync

handler.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 内相关 Container

if e.Type == pleg.ContainerDied {

if containerID, ok := e.Data.(string); ok {

kl.cleanUpContainersInPod(e.ID, containerID)

}

}

...

}

return true

}

6. Docker 忙起来

上一步生产的消费上传,PodWorkers 与 Podbox 事件创建事件返回为 gRPC 客户端,然后调用他 gRPC 服务器,进行 PodSand、infra-container(也称为暂停容器)的。

然后,会调用 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 := defaultSandboxImage

if 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

}

小结如下:

K8s-CNI-流程

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

7. CNI RPC接口

CNI添加了、、、删除网络接口,并提供了按或每个进行网络配置规范的接口等检查,方便用户灵活使用。

CNI从这些容器管理系统(dockershim)获取处处运行时信息(Container Runtime),包括网络命名空间的路径、容器ID以及网络接口名称,再从容器网络的配置文件中加载网络配置信息,再将信息传递给的插件,由插件具体的网络配置,并将配置的结果再返回到容器系统中进行管理。

K8s-CNI-RPC

用户若要编写自己的 CNI 插件,则可专注于显示然后这些 RPC 接口启用,可以与官方维护的三类基础组合,形成多种包含网络的自由解决方案。

8.小结

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

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

   
次浏览       
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]
 
最新文章
云原生架构概述
K8S高可用集群架构实现
容器云管理之K8S集群概述
k8s-整体概述和架构
十分钟学会用docker部署微服务
最新课程
云计算、微服务与分布式架构
企业私有云原理与构建
基于Kubernetes的DevOps实践
云平台架构与应用(阿里云)
Docker部署被测系统与自动化框架实践
更多...   
成功案例
北京 云平台与微服务架构设计
通用公司GE Docker原理与实践培训
某军工研究单位 MDA(模型驱动架构)
知名消费金融公司 领域驱动设计
深圳某汽车企业 模型驱动的分析设计
更多...   
 
 
 
 
 
相关文章

云计算的架构
对云计算服务模型
云计算核心技术剖析
了解云计算的漏洞
相关文档

云计算简介
云计算简介与云安全
下一代网络计算--云计算
软浅析云计算
相关课程

云计算原理与应用
云计算应用与开发
CMMI体系与实践
基于CMMI标准的软件质量保证