docker是go应用最经典的例子,一直以来也想看下docker源码具体怎么实现的,正好抽时间简单把docker的go源码大致看了下,整理一下写出来。
docker 源码github地址 https://github.com/docker/docker-ce
分析都基于docker-ce项目分析
下载项目到本地,解压后项目结构如下
docker是c/s架构的,也就是本地开启docker服务,然后通过客户端命令进行操作,可以创建容器,拉取镜像等等一些列操作,通过目录也可以大致判断出cli目录是客户端代码集合,engine是服务端代码集合。先从cli目录进入看看。
cli目录下还有很多目录,里面有个cmd/docker/docker.go 这个就是整个客户端功能的入口,
入口找到了,接下来想想我们常用的docker命令,这是我在命令行直接docker列出来了
在components/cli/cli/command目录下就有对应的命令目录
由此大致判断出这个目录就是存各种命令操作的目录,我们找一个看看,进入container目录(容器目录),里面有个cmd.go文件,打开看看
cmd.AddCommand(
NewAttachCommand(dockerCli),
NewCommitCommand(dockerCli),
NewCopyCommand(dockerCli),
NewCreateCommand(dockerCli),
NewDiffCommand(dockerCli),
NewExecCommand(dockerCli),
NewExportCommand(dockerCli),
NewKillCommand(dockerCli),
NewLogsCommand(dockerCli),
NewPauseCommand(dockerCli),
NewPortCommand(dockerCli),
NewRenameCommand(dockerCli),
NewRestartCommand(dockerCli),
NewRmCommand(dockerCli),
NewRunCommand(dockerCli),
NewStartCommand(dockerCli),
NewStatsCommand(dockerCli),
NewStopCommand(dockerCli),
NewTopCommand(dockerCli),
NewUnpauseCommand(dockerCli),
NewUpdateCommand(dockerCli),
NewWaitCommand(dockerCli),
newListCommand(dockerCli),
newInspectCommand(dockerCli),
NewPruneCommand(dockerCli),
)
可以看到每个名字都和 通过 docker container COMMAND --help 查看的命令参数有对应方法,所以这里就是每个命令的具体实现了。看随便找个看看代码实现
//创建新容器方法
func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
...
cmd := &cobra.Command{
Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
Short: "Create a new container",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
copts.Image = args[0]
if len(args) > 1 {
copts.Args = args[1:]
}
return runCreate(dockerCli, cmd.Flags(), &opts, copts)
},
}
...
copts = addFlags(flags)
return cmd
}
代码太长做了精简,看到调用runCreate方法,再 进入runCreate方法看看
太长不一一贴出来,就贴出最后调用代码吧,调用文件路径
docker-ce-master/components/cli/vendor/github.com/docker/docker/client
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
var response container.ContainerCreateCreatedBody
...
query := url.Values{}
if containerName != "" {
query.Set("name", containerName)
}
body := configWrapper{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}
//这里是最终请求server代码
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return response, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err
}
注意serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)这段代码,可以看到客户端发送了一个http请求去server,并得到返回信息。由此我们可以判断出docker客户端和服务端之间是通过http进行通信的。
接下来分析server端代码也就是engine目录
下面是engine目录结构图
根据上面分析到了客户端通过http服务跟服务端进行通信,那么应该就是调用服务端的api接口获得信息,正好engine目录下有个api目录,我们看看目录结构
api/server/router目录里面定义了http路由,
我们看下container/container.go 文件
func (r *containerRouter) initRoutes() {
r.routes = []router.Route{
// HEAD
router.NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
// GET
router.NewGetRoute("/containers/json", r.getContainersJSON),
router.NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
router.NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
router.NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
router.NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
router.NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs),
router.NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats),
router.NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
router.NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
router.NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
// POST
router.NewPostRoute("/containers/create", r.postContainersCreate),
router.NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
router.NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
router.NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
router.NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
router.NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
router.NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
router.NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), // Deprecated since 1.8, Errors out since 1.12
router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
router.NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
router.NewPostRoute("/containers/{name:.*}/update", r.postContainerUpdate),
router.NewPostRoute("/containers/prune", r.postContainersPrune),
router.NewPostRoute("/commit", r.postCommit),
// PUT
router.NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
// DELETE
router.NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
}
}
可以看到里面定义了相应的请求对应的处理方法
找到我们在客户端代码查看时最后定位的请求代码
erverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
对应的服务端路由
router.NewPostRoute("/containers/create", r.postContainersCreate),
查看postContainersCreate最终可以看到这段调用
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
Name: name,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares,
})
ContainerCreate 在docker-ce-master/components/engine/daemon/create.go 中实现。具体实现代码比较长,可以自行研究。
整个流程大致就是这样。客户端取得命令行参数后,调用api接口请求server端创建容器,并得到是否创建成功的返回信息。由此就是整个docker的实现,当然今天只是简单走了下docker的运行流程,并没有更深入分析代码的实现。大道至简,先从简单了解。