Docker代码更新很快,网上各位大神的源码解析很多已经是几年前的版本了。实现上有了很大改变,加之Docker项目更名为moby,其中很多组件又从moby中拆分了出来,一开始看简直是一脸懵逼啊。在这里跟大家分享了一下最近看的docker/cli的源码,抛砖引玉,欢迎大家批评指正。
分析的docker client
版本是17.06.2-ce
。
docker client
是Docker的客户端程序,也就是我们敲的docker * *
命令,我们通过他与docker deamon
程序进行交互。可以将他看成一个普通的客户端程序,docker的核心namespace
和cgroup
等技术都不在这里。
搭建docker-client开发环境
docker_client
的开发环境也是在容器中,项目已经给我们做好了封装,具体可以参考项目Readme的Development章节。
问几个问题,大家可以思考一下:
1. 开发是在容器中,可是容器里面连编辑器都没有,怎么开发?
答: 准确来说应该是在容器中调试,开发还是在本地开发,容器中看到的目录是我们本地目录mount进去的
|
|
从shell里面还是很容器看出来的,run的时候mount了$(CURDIR)
2. 容器里面是不是还装了一个docker啊,为什么我在里面敲命令有响应?
答:不是的,里面只有你编译生成的docker client程序,是没有docker deamon程序,至于为什么能响应,还是看上面的shell脚本,-v /var/run/docker.sock:/var/run/docker.sock
这一行将docker.sock
文件也mount了进去,docker.sock
是docker deamon
默认监听的Unix套接字(Unix domain socket),容器中的进程可以通过他与docker deamon进行通信。所以这里docker client通信的是你本机的docker daemon。
Note: 关于
/var/run/docker.sock
更多内容可以看这里
所以我们的开发流程是:本地改代码–>到container中编译–>运行docker client看效果
源码
是时候表演真正的技术了!
😆,开个玩笑,下面的分析如果有问题,还请大家不吝赐教!
我尽量把文件路径列出来,会贴一些代码,但主要还是路径,建议大家把代码clone下来照着看。
docker—client是基于cobra写的,建议大家先看一下cobra,至少写个hello world
熟悉一下基本用法。
我们开始了!
入口文件在cmd/docker/docker.go
:
|
|
还是这个文件,newDockerCommand
函数调用了commands.AddCommands(cmd, dockerCli)
来添加命令。
|
|
这行上面的cmd.AddCommand(newDaemonCommand())
是为docker daemon
命令进行的输出,newDaemonCommand
定义在cmd/docker/daemon_none.go
中,从他的RunE
方法可以看出来,是输出了不能运行的提示,runtime.GOOS
是输出当前系统的名称,比如mac是Darwin
, ubuntu是linux
,完整看来就是:
`docker daemon` is not supported on Darwin. Please run `dockerd` directly
让我们来到commands.AddCommands
定义的地方,cli/command/commands/commands.go
文件,可以看到cmd.AddCommand
的调用,这里就是在添加我们看到的二级命令,也就是紧跟着docker
后面的命令。
|
|
我们从上看到下,整齐划一中发现最后好多命令被用hide包裹了。这些命令是一些旧命令,在新的版本中可以使用新的命令来代替,如果设置了环境变量DOCKER_HIDE_LEGACY_COMMANDS
不为空,那么docker的提示将不会输出这些。
这里的命令太多了,我们通过docker ps
命令来了解一下大致的执行流程。
docker ps
命令也是旧版的命令,hide(container.NewPsCommand(dockerCli))
就是在处理他。在新版本中对应的是docker container ls
命令。实现代码在cli/command/container/list.go
.
|
|
NewPsCommand
对应的是docker ps
, newListCommand
对应的是docker container ls
,可以看出来, newListCommand
里的command就是NewPsCommand
返回的,只是加了一下说明而已。
这里还漏了一点,docker container ls是3个命令,我们知道docker是root命令,按照cobra的写法,container和ls应该是分开的。他们的关系是这样的,在
cli/command/commands/commands.go
文件中container.NewContainerCommand(dockerCli)
加入了container
命令,NewContainerCommand
的实现在cli/command/container/cmd.go
中,在NewContainerCommand
函数中通过newListCommand(dockerCli)
加入了ls命令,可以看到还添加了很多别的方法,那些都是container
支持的子命令。这样就回到了我们上面提到的内容了,没有魔法
我们继续看NewPsCommand
:
里面定义了docker ps
(下面所有的内容对docker container ls
都适用)支持的一些选项(flag),选项被放在了psOptions这个结构体中,最终传给runPs函数.
|
|
listOptions, err := buildContainerListOptions(options)
对选项进行了处理,重点在dockerCli.Client().ContainerList(ctx, *listOptions)
,ContainerList
是一个interface。
实现在vendor/github.com/docker/docker/client/container_list.go
(vendor在当前目录下,我是go新手,所以担心大家不知道,高手忽略)
|
|
前面是在拼请求的参数,resp, err := cli.get(ctx, "/containers/json", query, nil)
这里发起了请求,返回了container的列表。我们可以通过curl或nc来获得原始的数据:
|
|
可以看到数据是以json格式返回的,是一个列表。
回到cli/command/container/list.go
,接收到返回值之后,这时候还是上面看到的json,下面进行了格式化输出,大家应该都有做打印9*9乘法表
的经历,这里的格式化是同样的道理,不明白的同学可以继续往挖,我偷个懒,就不继续了。
至此docker ps
的流程就走完了,大家可以对源码做一些修改,然后进到容器中执行
|
|
就可以生产新的docker client,执行一下就能得到想要的输出啦。
谢谢大家,欢迎大家指正🙂