玖叶教程网

前端编程开发入门

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

原创是我码字的基本原则

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 router
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),
 }
}

这些是关于容器的所有的接口,我们创建容器用的是 /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语言知识信息等)。公众号会持续为大家分享更多干货。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言