龙空技术网

docker源码分析之容器的创建:/containers/create

Go语言之美 382

前言:

如今看官们对“docker构建容器”大体比较关心,朋友们都需要了解一些“docker构建容器”的相关内容。那么小编也在网上汇集了一些对于“docker构建容器””的相关内容,希望姐妹们能喜欢,兄弟们快快来了解一下吧!

原创是我码字的基本原则

docker想必大家都是耳熟能详的,本人工作中也经常会用到,感觉docker的成功不是没有道理的。由于本人的好奇心很重,所以接触到一个东西,一定要看一下源代码,或者至少要自己明白这里面是如何实现的,今天我就说一下我看docker源码之后所明白的一点点东西,希望对大家有帮助。

docker的基本命令大家都很熟悉,比如:

docker run; docker create

今天我主要说一下docker中容器创建的过程。这两个是我们对容器操作的最常用的命令,docker run 命令是创建并启动容器,docker create 是创建但是不启动容器。创建容器对于docker来说是一次请求,而启动容器是另外一个请求,如果我们执行docker run 命令其实和先发送一个创建容器的请求,再发送一个启动容器的请求是一样的。本质都是两个post。

今天不演示发送请求的代码,如果感兴趣可以看我之前的文章,有说过使用docker编译启动sever的文章。今天和大家分享一下docker内部如何接收请求的。

先看一下docker一共有多少的路由:

// initRoutes initializes the routes in container routerfunc (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), }}

这些是关于容器的所有的接口,我们创建容器用的是 /containers/create 接口。r.postContainersCreate 方法是容器创建的具体实现,代码我这里不写出来了,这个方法主要是对请求的参数进行解析,然后配置,最终再创建容器。其最终目的是实例化了一个 Container 的结构体我们先看一下容器的结构:

// Container holds the structure defining a container object.type Container struct { StreamConfig *stream.Config // embed for Container to support states directly. *State `json:"State"` // Needed for Engine API version <= 1.11 Root string `json:"-"` // Path to the "home" of the container, including metadata. BaseFS containerfs.ContainerFS `json:"-"` // interface containing graphdriver mount RWLayer layer.RWLayer `json:"-"` ID string Created time.Time Managed bool Path string Args []string Config *containertypes.Config ImageID image.ID `json:"Image"` NetworkSettings *network.Settings LogPath string Name string Driver string OS string // MountLabel contains the options for the 'mount' command MountLabel string ProcessLabel string RestartCount int HasBeenStartedBefore bool HasBeenManuallyStopped bool // used for unless-stopped restart policy MountPoints map[string]*volumemounts.MountPoint HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable ExecCommands *exec.Store `json:"-"` DependencyStore agentexec.DependencyGetter `json:"-"` SecretReferences []*swarmtypes.SecretReference ConfigReferences []*swarmtypes.ConfigReference // logDriver for closing LogDriver logger.Logger `json:"-"` LogCopier *logger.Copier `json:"-"` restartManager restartmanager.RestartManager attachContext *attachContext // Fields here are specific to Unix platforms AppArmorProfile string HostnamePath string HostsPath string ShmPath string ResolvConfPath string SeccompProfile string NoNewPrivileges bool // Fields here are specific to Windows NetworkSharedContainerID string `json:"-"` SharedEndpointList []string `json:"-"`}

(头条对代码的支持让人头疼啊,希望要深入学习的可以自己下载源码看一下)

这是docker的container的结构,首先说ID字段,这个是每一个容器独有的ID信息,每次创建都会生成下面这样一个ID:

82303d4d4bc0b0c71b789e2160d0c27237489221202f28813e456248056b76a8

一个64位的十六进制字符串,但是我们看到的只是前12位,大家可能会问,64位的可以不重复,但是截取前12位不就可能重复了吗?是的,所以在截取这个ID时,docker内部是做了检查的,只有判断是已有的容器唯一的ID才可以。

ImageID字段,这个就是创建容器使用的镜像的ID,在我们创建容器发送请求时我们已经指定了容器的名字,这里就是这个镜像的ID。

我们看一下创建一个base container的代码:

// NewBaseContainer creates a new container with its// basic configuration.func NewBaseContainer(id, root string) *Container { return &Container{ ID: id, State: NewState(), ExecCommands: exec.NewStore(), Root: root, MountPoints: make(map[string]*volumemounts.MountPoint), StreamConfig: stream.NewConfig(), attachContext: &attachContext{}, }}

NewState(): 创建一个默认状态对象,其中包含一个用于状态更改的新信道(里面的具体实现不在这里讲解,如果大家感兴趣,以后我会慢慢的都写一遍的)。

这里是创建一个最基础的容器,每一个容器都是这样,创建了这个base container之后,再根据具体的需求,修改这个容器。

容器创建好了之后,我们的守护线程(daemon)就会保存起来了,在运行的时候我们就可以直接用了。容器的创建过程的细节还有很多,如果要把所有都说一遍,恐怕要几篇文章才能说的完。

其实创建容器的目的就是实例化了这样一个结构体,结构体的基本信息有一些是我们指定的,比如使用的镜像,mount的目录,映射的端口等,还有一些就是docker自己管理的信息。我们创建了一个容器实质上就是有了这个结构体的实例,接下来我们就可以启动这个容器了,启动容器今天不做过多介绍,下一篇文章我再继续和大家分享。

其实创建的过程是很简单的,只不过是实例化了一个container的结构体。但是运行的过程要比这个复杂了。

今天没有说里面的细节,只是和大家说一下大体实现过程,等以后我会将内部的具体实现都会写出来。大体说一下有助于我们理解,如果要做类似docker的产品的开发就需要深究了。

后续会有更多的模式和算法以及区块链相关的,如果你是想学习go语言或者是对设计模式或者算法感兴趣亦或是区块链开发工作者,都可以关注一下。(微信公众号:Go语言之美,更多go语言知识信息等)。公众号会持续为大家分享更多干货。

标签: #docker构建容器