docker go源码分析

docker是go应用最经典的例子,一直以来也想看下docker源码具体怎么实现的,正好抽时间简单把docker的go源码大致看了下,整理一下写出来。
docker 源码github地址 https://github.com/docker/docker-ce
分析都基于docker-ce项目分析
下载项目到本地,解压后项目结构如下

图片.png

docker是c/s架构的,也就是本地开启docker服务,然后通过客户端命令进行操作,可以创建容器,拉取镜像等等一些列操作,通过目录也可以大致判断出cli目录是客户端代码集合,engine是服务端代码集合。先从cli目录进入看看。


图片.png

cli目录下还有很多目录,里面有个cmd/docker/docker.go 这个就是整个客户端功能的入口,


图片.png

入口找到了,接下来想想我们常用的docker命令,这是我在命令行直接docker列出来了


图片.png

在components/cli/cli/command目录下就有对应的命令目录
图片.png

由此大致判断出这个目录就是存各种命令操作的目录,我们找一个看看,进入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目录结构图


图片.png

根据上面分析到了客户端通过http服务跟服务端进行通信,那么应该就是调用服务端的api接口获得信息,正好engine目录下有个api目录,我们看看目录结构


图片.png

api/server/router目录里面定义了http路由,
图片.png

我们看下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的运行流程,并没有更深入分析代码的实现。大道至简,先从简单了解。

你可能感兴趣的:(docker go源码分析)