浅入浅出docker run命令源码3-containerd续篇

在这里插入图片描述

1.前情回顾

上一篇我们已经知道如何找到对应的gRPC请求接口的逻辑代码了,但是还没有看具体的代码。
在最初的《浅入浅出docker run命令源码》中

在这里插入图片描述

已知,启动容器还需要启动shim进程以及runc进程。但是具体是如何启动的,还不清楚。这篇文章中,主要解决问题是containerd是如何启动shim、runc进程的。

2.前置知识

接着《浅入浅出docker run命令源码》代码腰斩处继续看dockerd的源码,可知,docker run命令执行的过程中,dockerdcontainerd交互时,主要的三个gRPC请求按顺序排列如下:

  • /containerd.services.containers.v1.Containers/Create创建容器的元数据
  • /containerd.services.tasks.v1.Tasks/Create启动shim进程,调用runc创建容器环境
  • /containerd.services.tasks.v1.Tasks/Start 启动任务对应的容器主进程(我们在容器中跑的程序)

很明显我们要关注的是,第2、3个请求。

该篇直接从接口处代码开始,在前面《浅入浅出docker run命令源码2-containerd篇》中已经说明了知道了gRPC请求路径后,如何找到对应的源码,这里不再赘述。

3.Tasks/Create请求源码阅读

在该请求中,containerd会启动一个shim进程,在shim进程中又调用runc去准备好容器启动所需要的namespace环境。

前面有个概念没有说,在containerd中,插件是有类型的,例如servicegRPC插件,localservice插件。在创建gRPC插件的时候,会查找它所依赖的service插件。下面就是负责处理该请求的Tasks插件
在这里插入图片描述

回到正题,该请求是由containerdtasks插件接收请求并处理,正如前面《浅入浅出docker run命令源码2-containerd篇》中说的,service.go中注册的是gRPC插件,负责对外,真正的处理逻辑在其引用的loacl.go中。

local.Create一眼扫过去,大概就能猜到rtime.Create()就是启动shim进程逻辑的代码入口,左下角是到真正启动shim进程时的调用栈
在这里插入图片描述

3.1部分结构体说明

整个调用的过程,其实就是准备各种参数,为启动shim进程准备。刚看这部分的代码,会有种怎么老是在create或者start什么的,如调用栈中方法名所示。其实这里如果了理解TaskManageShimManagerShimTaskshim这些抽象的意义,代码看起来就很清晰了。

3.1.1 TaskManager

taskManager负责所有容器任务的调度和状态管理。它是上层系统(如 Kubernetes 或 Docker)与 containerd 交互的主要入口,是逻辑层面的抽象。

TaskManager只提供了Create、Delete、Get、Tasks这几个方法,对应了容器的创建、删除、查询单个容器、查询所有容器。
在这里插入图片描述

以Create方法为例,代码折叠后,可以看出TaskManager的逻辑就是

  • 1、先让ShimManager搞个shim
  • 2、创建一个task
  • 3、执行task
    没有shim是如何工作的,也没有task是如何工作的逻辑在里面,只负责流程逻辑的串联。只要流程不变,不会影响到TaskManager,我可不管你shim是怎么保存、task是如何实现的。

3.1.2 ShimManager

从前面的TaskManager的实现逻辑来看,ShimManager的定位就是管理容器的。由于containerd为了解耦,通过shim进程来启动容器,而TaskManager又不管事,所以只能搞个 ShimManager来背锅了。ShimManager职责是 shim 进程管理 的抽象层,负责启动、监控和销毁 shim,以及与 shim 之间的通信。

如下图所示,管理shim,其实就是对shim这个抽象对象进行CRUD。其他的小写方法是服务于TaskManager的初始化以及关闭时候清理环境等操作的。
在这里插入图片描述

Start方法为例,可以看到启动前需要先创建一个Bundle对象,然后调用startShim方法返回一个shim对象,最后将shim对象加入到集合中。再看Delete方法中也差不多,具体的实现都是在shim对象中,ShimManager仅仅只是shim对象的维护。
在这里插入图片描述

3.1.3 shimTask与shim

shim记录了shim进程的相关信息,例如runc需要的bundle信息,以及与shim进程通讯用的rpc连接

type shim struct {  
    bundle *Bundle  
    client any  
}

ShimTask是具体任务的实现类,负责单个容器任务的生命周期操作。
在这里插入图片描述

其本质是一个rpc客户端,通过shimclient去完成容器生命周期操作。之所以要弄个shimTask出来,我的理解是containerd中通过shim进程启动容器这逻辑是基本不会变的,因此shim也是基本不变的,其管理功能也是基本不会变的,但容器生命周期操作这些逻辑代码后续可能会变化,为了修改起来更清晰,才搞了shimTask这个东西出来。要是我自己写,可能就直接在shim里面实现草草了事了。。。

shimttrpc通信,ttrpcgRPC为本地通信的优化版本,各个操作对应的请求路径还要再看shimTasktask对象的代码。task是一个 taskClient结构体实例,是ttrpc代码生成的
在这里插入图片描述

3.2 containerd启动shim进程

这里只讲述关于containerd相关的代码,shim进程的代码,请看后续的《浅入浅出docker run命令源码3-shim篇》。

Tasks/Create入口处跟着代码走,会看到下面代码。这里就是真正去启动一个进程的代码位置
在这里插入图片描述

图中的代码是我为了debug shim进程修改过的,会有些细微不一样,但是圈中的代码是没有改动的。

首先cmd.CombineOutput()中会执行启动shim进程的命令并返回了out对象获取命令的输出。
然后parseStartResponse()会处理输出的内容返回下面的结构体

type BootstrapParams struct {  
	Version int `json:"version"`
	Address string `json:"address"`
	Protocol string `json:"protocol"`}

makeConnection()使用结构体中的Address去连接shim进程得到一个ttrpc连接对象
然后返回一个shim对象给shimManger保存起来,然后再返回给TaskManager。

TaskManager得到shim后会创建一个ShimTask,并用ShimTaskshim进程发出请求

shimTask, err := newShimTask(shim)  
if err != nil {  
    return nil, err  
}  
  
t, err := shimTask.Create(ctx, opts)

在shimTask.Create中会通过ttrpc,让shim进程去干活了
在这里插入图片描述

请求的路径如下,执行服务containerd.task.v2.TaskCreate方法
在这里插入图片描述

该请求就是让shim进程去启动runc进程,剩下的逻辑,需要关注的在shim进程的代码里面了。

4.Tasks/Start请求源码阅读

Start方法中,其实就是获取Create方法中创建的shimTask,然后调用它的start方法,最终触发ttrpc请求,让shim进程启动容器,最终返回启动结果给dockerd,涉及的知识点已经在Create方法中写了,这里就不再赘述了。
在这里插入图片描述

在这里插入图片描述

5.总结

本来想叫shim篇的,结果发现要把shim进程的逻辑也放这写,感觉还得费不少时间,还是拆开写吧。擦,本来我只是想随便看看docker run的代码,意思一下得了,怎么给我干到这来了。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值