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

1、前情回顾

在这里插入图片描述

上次《浅入浅出docker run命令源码》代码看到调用了grpc去让containerd启动容器就没有继续看了.

连一刻都没有为dockerd的无疾而终而哀悼,立刻来到战场的是containerd

在这里插入图片描述

这次,我们先解决下面的问题
1、dockerd是怎么启动的containerd
2、怎么调试containerd的源码
3、containerd架构
4、containerd接口对应的源码在哪里

2. dockerd 是怎么启动 containerd的?

dockerd 作为 Docker 服务的核心程序,在启动时会依赖列表和配置进行流程初始化,其中就包括 containerd 的启动。代码的位置很明显,一进来就可以看到
在这里插入图片描述

在这里插入图片描述

dockerd会先去看containerd是否已经启动,判断逻辑就是看/run/containerd/containerd.sock是否存在。如果不存在,继续往里面走,就会看到下面的代码
在这里插入图片描述

这里通过goroutine去启动containerd,然后创建一个定时器, 如果在15秒内没有启动成功就会会停止掉dockerd进程。这里会导致你debug不了后续的启动代码,这里需要修改以下这个超时时间,方便我们慢慢看参数。
在这里插入图片描述

修改为15分钟,重新编译dockerd代码, 如何编译请看我另一篇文章《docker源码阅读环境搭建》。继续debug, 可见调用了$PATH环境变量中的containerd…, 这是我以前安装docker时候docker安装的。
在这里插入图片描述

看到启动的命令了,接下来就是该怎么debug源码了。

3. 怎样调试 containerd 源码?

前面看到,如果containerd进程存在了,则不会再启动,而是直接使用已经启动的containerd进程,那么我们只需要编译源码后,用debug模式启动,等待dockerd进程访问即可。

因此我们得自己编译源代码,替换掉dockerd默认启动的二进制文件。
containerd的源码在另一个项目,所以得把源码拉下来先 , 拉取源码前需要 先看containerd的版本号,这个要看moby项目的vendor.mod文件,这里我的版本是

在这里插入图片描述

所以我们得clone对应的版本,在远程服务器上以及本地上都clone

   git clone --branch v1.7.24 --single-branch   https://github.com/containerd/containerd.git

官方的安装教程
https://github.com/containerd/containerd/blob/main/BUILDING.md

从安装文档得知,理论上我们还得编译containerd默认依赖的runc,但是由于我们之前通过apt已经安装过docker,所以这次先搞定containerd,等研究到runc再去折腾.

执行构建命令前需要先启动docker的守护进程, 我启动的是我编译好的

./bundles/binary-daemon/dockerd

安装文档里面写了可以用docker镜像来编译containerd
开始编译,我本地go的版本是1.23.3,找了个相近的版本,在containerd源码目录下

docker run -it \
    -v ${PWD}:/src/containerd \
    -w /src/containerd golang:1.23.4

然后在容器内输入命令

GODEBUG=1 make

等待编译完成即可。感觉这容器只是提供了go环境,并没有什么多大卵用,理论上直接在本地执行

GODEBUG=1 make

也是可以的。编译好后,二进制文件在 {源码目录}/bin 目录下。GoLand测试一下是否能debug

在这里插入图片描述

在远程服务器的源码根目录下执行下面的命令,需要加上前面从dockerd得到的参数
但是又不能直接用原来的配置文件,需要将配置文件复制到containerd源码根目目录下后,修改配置文件中xx.socket文件的路径如下
在这里插入图片描述

debug命令

dlv --listen=:8006 --headless=true --api-version=2 --accept-multiclient exec -- ./bin/containerd --config containerd.toml

containerd 源码的主函数位于 cmd/containerd/main.go,
GoLand打上断点运行,可以看到进来了
在这里插入图片描述

注意,需要containerd启动完成后,再启动dockerd

4. containerd 架构是怎样的

这是containerd官方的图,看起来应该就是它的架构图了
在这里插入图片描述

没怎么研究过,先简单看看就行,流程先走通,等需要再去研究。

5. containerd 接口代码在哪里?

containerd 源码的主函数位于 cmd/containerd/main.go

func main() {  
    app := command.App()  
    if err := app.Run(os.Args); err != nil {  
       fmt.Fprintf(os.Stderr, "containerd: %s\n", err)  
       os.Exit(1)  
    }  
}

跟踪进入 app.Run,发现最终调用了app.Action 函数。app.Action是在command.App()里面赋值的,继续看Action的逻辑
在这里插入图片描述

会看到调用了server方法去创建一个goroutine去启动监听服务。这个server就是我们要盯防的gRPC server。

众所周知,gprc需要使用 Protobuf 定义服务和消息格式,然后在用 protoc 编译器生成客户端和服务器的代码,最后在服务器端实现接口中定义的方法。启动流程代码如下:

server := grpc.NewServer()
service.Register(server)
server.Serve(listener)

那么接下来就是要找.proto文件以及service注册的逻辑在哪里了。

.proto文件
easy job,暴力ctrl + shift + f全局搜索后缀,发现在api包里面
在这里插入图片描述

service注册逻辑

在项目启动的时候,go的运行时会自动执行每个脚本的init()函数加载插件,如下图,
在这里插入图片描述

然后在创建 grpc server的时候进行初始化, 逻辑在创建server的函数里,会先把所有注册了的插件形成一个有序的集合,然后for循环处理,

plugins, err := LoadPlugins(ctx, config)
for _, p := range plugins {
	... 注册service
}

注册代码截图如下
在这里插入图片描述

这里的p就是在项目启动时由go运行时执行init函数注册的插件对象,p.Init就是调用对应其结构体中的函数变量InitFn,用来初始化插件的,service.Register里面就是要找的api.RegisterXXXXXServer(server, s)注册代码

最后值得一提的是,在 containerd 的代码结构中,gRPC 的实现通常被分为多个文件,比如 local.goservice.go。据说这种分工是为了将代码逻辑模块化,更清晰地划分功能和职责。具体如何还得看看代码才知道,简而言之就是
local.go 通常用于处理服务的本地实现逻辑,直接与底层模块交互。
service.go 用于定义 gRPC 服务的接口,实现与客户端的通信。

请求过来的时候,执行service.go中方法,其中会调用local.go中的方法来执行相应的处理逻辑。

他喵的,所以我要看的代码在哪里?
假设你再moby项目中,得到了请求路径如下:

/containerd.services.containers.v1.Containers/Create

1、直接搜/containerd.services.containers.v1.Containers/Create就能找到生成的xx_grpc.pb.go.proto文件就放在一起的。

2、接着在.proto文件中获取包名,
在这里插入图片描述

以截图例子,
package是消息的命名空间,是protobuf用来防止命名冲突的冲突的,如果没有go_package,也是生成代码的包路径,但如果有go_package,则以go_package为准, 包路径为在这里插入图片描述
github.com/containerd/containerd/api/services/containers/v1
,全局搜索包路径,看到有这两个组合的,一般处理逻辑代码就在这了

结论

啥都还没开始看呢。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值