浅谈 K8s CRI

这篇具有很好参考价值的文章主要介绍了浅谈 K8s CRI。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 概述

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

K8s CRI(Container Runtime Interface) 是 K8s 定义的一组与容器运行时进行交互的接口,用于将 K8s 平台与特定的容器运行时实现解耦。CRI 在 Kubernetes 1.5 中引入,并充当 kubelet 和容器运行时之间的桥梁。目前实现了 CRI spec 的 Runtime 有 Docker Engine、containerd、CRI-O、Mirantis Container Runtime(Docker 企业版) 等。

2020 年,K8s 宣布弃用 dockershim[1],标志着容器运行时正式向 CRI 切换,一方面是为了将 kubelet 核心主干代码与 Runtime 相关代码解耦,便于更好的维护;另一方面则是为了便于生态圈按 CRI spec 实现自己的运行时插件,提供个性化的运行时扩展能力,以满足对更多 Runtime 的支持,提高 K8s 生态的开放性和扩展性。

本文将从 Docker Engine、Kubelet 启动、Pod 创建/删除、Container 创建/删除、CRI RPC 调用等核心流程,对 CRI 实现机制进行了解析。

流程概览如下:

浅谈 K8s CRI

K8s-CRI

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


2. 从 Docker 说起

2.1 Docker Engine

Docker Engine 是用来运行和管理容器的核心软件。通常人们会简单地将其代指为 Docker 或 Docker 平台。

Docker Engine 主要的组件构成:Docker 客户端(Docker Client)、Docker 守护进程(Docker daemon)、Docker APIs、containerd 以及 runc,它们共同负责容器的创建和运行。

浅谈 K8s CRI

docker-engine

2.2 OCI

OCI(Open Container Initiative,开放容器计划),是在 2015 年由 Docker、CoreOS 等公司共同成立的项目,并由 Linux 基金会进行管理,致力于 container runtime 标准的制定和 runc 的开发等工作。

所谓 container runtime,主要负责的是容器的生命周期的管理。OCI 主要分为容器运行时规范(runtime-spec) 和镜像规范(image-spec) 两部分,runtime-spec 标准对容器的创建、删除、查看、状态等操作进行了定义,image-spec 对镜像格式、打包(Bundle)、存储等进行了定义。

2.3 runc

runc,是由 Docker 贡献的对于 OCI 标准的一个参考实现,是一个可以用于创建和运行容器的 CLI(command-line interface) 工具。runc 直接与容器所依赖的 Cgroup/OS 等进行交互,负责为容器配置 Cgroup/namespace 等启动容器所需的环境,创建启动容器的相关进程。

为了兼容 OCI 标准,Docker 也做了架构调整。将容器运行时相关的程序从 Docker daemon 剥离出来,形成了containerd。containerd 向 Docker 提供运行容器的 API,二者通过 gRPC 进行交互。containerd 最后会通过 runc 来实际运行容器。

浅谈 K8s CRI

runc

3. CRI

CRI(Container Runtime Interface,容器运行时接口)是 K8s 定义的一组与容器运行时进行交互的接口,用于将 K8s 平台与特定的容器实现解耦。在 K8s 早期的版本中,对于容器环境的支持是通过 Dockershim(hard code) 方式直接调用 Docker API 的,后来为了支持更多的容器运行时和更精简的容器运行时,K8s 在遵循 OCI 基础上提出了CRI。

3.1 dockershim

dockershim 是 Kubernetes 的一个组件,主要目的是为了通过 CRI 操作 Docker。Kubernetes 在创建之初便采用Docker 作为它的默认容器进行时,后续代码当中包含了很多对 Docker 相关的操作逻辑。后期 Kubernetes 为了能够做解耦,兼容更多的容器进行时,将操作 Docker 相关逻辑整体独立起来组成了 dockershim。

2020 年,K8s 宣布弃用 dockershim[2],标志着容器运行时正式向 CRI 切换,以满足对更多 Runtime 的支持,提高 K8s 生态的开放性和扩展性。

3.2 CRI shim

当前实现了 CRI 的 remote shim 有如下:

•containerd[3]:由 Docker 公司创建,并且在 2017 年捐赠给了 CNCF,2019 年毕业。•CRI-O[4]:基于 OCI 规范的作为 CRI 和 OCI 之间的一座桥梁。•Docker Engine[5]:Docker 运行时的支持,由 cri-dockerd 进行实现。•Mirantis Container Runtime[6]:Docker 企业版(Enterprise Edition) 运行时的支持,由 Mirantis Container Runtime(MCR) 进行实现。

CRI shim 小结如下:

浅谈 K8s CRI

K8s-CRI-shim

3.3 RuntimeClass

RuntimeClass 是 v1.12 引入的新 API 对象,用来支持多个容器运行时,可通过 Pod 字段直接指定。定义一个 RuntimeClass 如下,对应的 CRI handler 即为目标容器运行时,比如 containerd、crio:

apiVersion: node.k8s.io/v1  # RuntimeClass is defined in the node.k8s.io API groupkind: RuntimeClassmetadata:  name: myclass  # The name the RuntimeClass will be referenced by  # RuntimeClass is a non-namespaced resourcehandler: myconfiguration  # The name of the corresponding CRI configuration

在 Pod 中直接指定对应的 runtimeClassName 即可:

apiVersion: v1kind: Podmetadata:  name: mypodspec:  runtimeClassName: myclass  # ...

4. Kubelet 启动

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

启动入口如下:

// kubernetes/cmd/kubelet/kubelet.gofunc 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 会进一步初始化 CRI shim,分别初始化 RuntimeService、ImageService 对容器运行时和镜像生命周期进行管理;然后启动 gRPC CRI server 监听 client 请求,进行具体的操作如 PodSandbox、Container 创建与删除。

kubelet 启动后,会负责:


•Pod、Volume 事件的监听,创建/删除对应的 Pod/Volume;•Node 资源监控与更新、Pod 健康探测(Probe) 及上报;•当 Node 资源紧张时,还负责 Pod Preemption(抢占) 与 Eviction(驱逐);•Metrics 监控采集、Image/Container GC 工作等;

其中,Pod 事件流程图请看上面概述中的流程概览图。

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 事件生产者(producer) - 相关代码:

// kubernetes/pkg/kubelet/pleg/generic.go// 生产者:获取所有 Pods 列表,计算出对应的事件类型,进行 Syncfunc (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 事件消费者(Consumer) - 相关代码:

// kubernetes/pkg/kubelet/kubelet.go// 消费者:根据 channel 获取的各类事件,进行 Pod Syncfunc (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}

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

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


6. Container 创建/删除

当 Pod-Sandbox 创建出来以后,首先会创建基础容器(infra-container,也叫 pause 容器),通过 CNI 机制[7] 配置 Pod 网络环境,创建临时数据目录(存放 container logs),为下一步 Container 创建做好相关准备工作。

Container 目前分为三种类型:


•Ephemeral Container:临时容器,用于 debug 及排障所需的一次性容器。•Init Container:初始化容器,在正常业务容器之前、按序启动,一般做一些准备工作。•Regular Container:普通业务容器,应用使用的主容器。

创建 Container 的过程主要有:

PullImage -> CreateContainer -> StartContainer -> PostStartHook -> Container running


当 Pod 内所有 Container 启动并正常运行起来后,Pod-phase 更新为 Running,表示 Pod 正常运行。

Pod-Container 创建流程小结如下:

浅谈 K8s CRI

K8s-CRI-flow

7. CRI RPC 接口

CRI 标准规范接口,包含了 ImageService、RuntimeService 两方面的接口:


•ImageService:管理镜像的查询、拉取、删除、统计等操作;•RuntimeService:管理 PodSandbox 和容器的生命周期,包括查询、创建、启动、删除、统计等操作,另外还提供版本(Version)、执行命令(Exec)、端口转发(PortForward) 等接口能力;

浅谈 K8s CRI

K8s-CRI-RPC

可以看到,用户只要按照 CRI RPC 接口规范进行具体实现,就可以实现自己的 Runtime 插件,提高了 K8s 生态的高扩展性与灵活性。

8. 小结

本文通过分析 K8s 中 Kubelet 启动、Pod 创建/删除、Container 创建/删除、CRI RPC 调用等核心流程,对 K8s CRI 实现机制进行了解析。通过源码、图文方式说明了相关流程逻辑,以期更好的理解 K8s CRI 实现细节。

K8s CRI 经历了从 in-tree Dockershim 到 CRI remote-shim(out-of-tree) 的 迁移[8],一方面是为了将 kubelet 核心主干代码与 Runtime 相关代码解耦,便于更好的维护;另一方面则是为了便于生态圈按 CRI spec 实现自己的运行时插件,提供个性化的运行时扩展能力,以期达到容器生态圈的开放共赢。

PS: 更多内容请关注 k8s-club[9]

参考资料

1.Docker Engine[10]2.Kubernetes 源码[11]3.OCI spec 及 runc[12]4.Container runtimes[13]5.CRI RPC[14]6.xinkun 的博客[15]

相关链接

[1] 宣布弃用 dockershim: https://kubernetes.io/blog/2022/02/17/dockershim-faq/
[2] 宣布弃用 dockershim: https://kubernetes.io/blog/2022/02/17/dockershim-faq/
[3] containerd: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd
[4] CRI-O: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cri-o
[5] Docker Engine: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#docker
[6] Mirantis Container Runtime: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#mcr
[7] CNI 机制: https://github.com/k8s-club/k8s-club/blob/main/articles/K8s%20%E7%B3%BB%E5%88%97(%E5%85%AD)%20-%20%E6%B5%85%E8%B0%88%20CNI.md
[8] 迁移: https://kubernetes.io/blog/2022/02/17/dockershim-faq/
[9] k8s-club: https://github.com/k8s-club/k8s-club
[10] Docker Engine: https://docs.docker.com/engine/
[11] Kubernetes 源码: https://github.com/kubernetes/kubernetes
[12] OCI spec 及 runc: https://github.com/opencontainers
[13] Container runtimes: https://kubernetes.io/docs/setup/production-environment/container-runtimes/
[14] CRI RPC: https://github.com/kubernetes/cri-api/blob/master/pkg/apis/runtime/v1/api.proto
[15] xinkun 的博客: https://www.cnblogs.com/xuxinkun/p/8036832.html
文章来源地址https://www.toymoban.com/news/detail-441390.html

到了这里,关于浅谈 K8s CRI的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 通过docker+cri-dockerd部署k8s集群环境(含harbor镜像仓库)

    目录 一.虚拟机准备 二.基础环境配置(各个节点都做) 1.IP和hosts解析 2.防火墙和selinux 3.安装基本软件 4.配置时间同步 5.禁用swap分区 6.修改内核参数并重载 7.配置ipvs 三.docker环境(各个节点都做) 1.配置软件源并安装docker-ce 2.配置docker加速 四.cri环境配置(各个节点都做)

    2024年02月05日
    浏览(68)
  • k8s初始化报错:[ERROR CRI]: container runtime is not running(已解决)

    如有错误,敬请谅解! 此文章仅为本人学习笔记,仅供参考,如有冒犯,请联系作者删除!!          在网上找了好几天解决方案,大部分都是下述方案:         但是当我们尝试之后仍无法解决问题。 如有错误,请联系作者删除 并恳请同行朋友予以斧正,万分感谢!

    2024年02月07日
    浏览(53)
  • k8s如何进入容器

    首先查看你要进入容器的NAME kubectl get pod | awk \\\'{print $1}\\\' 注:如果有命名空间需要加上命名空间 命名空间查看方式(箭头标注为命名空间) kubectl -n 命名空间 get pod | awk \\\'{print $1}\\\' -n:命名空间 找到容器后进入容器 kubectl exec -it podname[刚刚查到的容器名字] /bin/bash exec:进入容器

    2024年02月09日
    浏览(41)
  • k8s debug 浅谈

    一    k8s debug 浅谈 Kubernetes 官方出品调试工具上手指南(无需安装,开箱即用) debug-application 简化 Pod 故障诊断: kubectl-debug 介绍 1.18 版本之前需要自己安装kubectl debug     下载位置    debug_0.1.1_linux_amd64.tar.gz ①  低版本 安装kubectl-debug工具    ②  临时容器 Debugging using a cop

    2024年02月04日
    浏览(30)
  • k8s 多容器pod进入指定容器

    kubectl exec -it prometheus-prometheus-server-697cccff9c-qtrf7 -c prometheus-server sh

    2024年02月14日
    浏览(62)
  • k8s 更换config文件,并进入pod

    通常情况下,Kubernetes config 文件的默认位置为:

    2024年02月16日
    浏览(59)
  • K8S-概述

    go语言开发的开源的跨主机的容器编排工具;全称是kubernetes; ①kube-apiserver 所有服务统一的访问入口,无论对内还是对外; ②kube-controller-manager 资源控制中心,负责管理pod的资源对象和部署的控制器,确保资源在预期值; ③kube-scheduler 管理pod的资源调度,根据调度算法给

    2024年02月12日
    浏览(25)
  • Kubernetes (K8S)概述

    1、K8S 是什么? K8S 的全称为 Kubernetes (K12345678S),PS:“嘛,写全称也太累了吧,不如整个缩写”。 1.1 作用 用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。 可以理解成 K8S 是负责自动化运维管理多个容器化程序(比如 Docker)的集群,是一个生态极

    2024年02月08日
    浏览(47)
  • K8S概述及用途

    K8S(Kubernetes) 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。 Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩

    2024年02月12日
    浏览(29)
  • 初识k8s(概述、原理、安装)

    本篇内容属于资料整理,非笔者操作笔记,用于学习记录,其中有问题的地方请指出。 kubernetes,简称K8s,是用8代替8个字符“kubernete”而成的缩写。是一个开源的,用于管理云平台中多个主机上的容器化的应用,即我们常说的容器编排工具,K8S的目标是让部署容器化的应用简

    2024年01月23日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包