前面讲到了docker容器得镜像,镜像其实是docker容器的静态部分,而docker容器则是docker镜像的动态部分,即启动了一个进程来运行,本篇最要来分析一下怎样创建并运行一个容器。
创建一个容器在客户端实现是在api/client/create.go,其中得CmdCreate()方法,这个函数的作用是通过一个给定的image来启动一个container;其中的createContainer()函数是最主要的实现部分;
//create the container
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
//if image not found try to pull it
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo))
// we don't want to write to stdout anything apart from container.ID
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
return nil, err
}
if trustedRef != nil && !ref.HasDigest() {
repoInfo, err := registry.ParseRepositoryInfo(repo)
if err != nil {
return nil, err
}
if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil {
return nil, err
}
}
// Retry
if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
defer serverResp.body.Close()
var response types.ContainerCreateResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return nil, err
}
for _, warning := range response.Warnings {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if containerIDFile != nil {
if err = containerIDFile.Write(response.ID); err != nil {
return nil, err
}
}
return &response, nil
}
大体的实现逻辑如上,首先调用createContainer来创建一个container;docker启动的时候是需要镜像得,根据前一篇得内容知道,如果镜像已经下载到本地,那么直接从本地获取镜像就好,如果镜像不在本地,那么打印出来打印出 在本地找不到镜像,然后调用 cli.pullImageCustomOut 去获取需要的镜像(其实就是上一篇讲的内容);在pull完镜像之后,接着在retry一下 createContainer函数;
创建容器的操作对应在server端得实现在api/server/container.go中的postContainersCreate()函数;
func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
if err := checkForJSON(r); err != nil {
return err
}
var (
warnings []string
name = r.Form.Get("name")
)
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil {
return err
}
adjustCPUShares := version.LessThan("1.19")
container, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
if err != nil {
return err
}
return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{
ID: container.ID,
Warnings: warnings,
})
}
最主要的是 s.daemon.ContainerCreate() 方法:
func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig, adjustCPUShares bool) (*Container, []string, error) {
if config == nil {
return nil, nil, fmt.Errorf("Config cannot be empty in order to create a container")
}
warnings, err := daemon.verifyContainerSettings(hostConfig, config)
daemon.adaptContainerSettings(hostConfig, adjustCPUShares)
if err != nil {
return nil, warnings, err
}
container, buildWarnings, err := daemon.Create(config, hostConfig, name)
if err != nil {
if daemon.Graph().IsNotExist(err, config.Image) {
_, tag := parsers.ParseRepositoryTag(config.Image)
if tag == "" {
tag = tags.DefaultTag
}
return nil, warnings, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
}
return nil, warnings, err
}
warnings = append(warnings, buildWarnings...)
return container, warnings, nil
}
最终调用的是daemon.Create(config, hostConfig, name)方法,在同一个文件中:
func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.HostConfig, name string) (retC *Container, retS []string, retErr error) {
var (
container *Container
warnings []string
img *image.Image
imgID string
err error
)
if config.Image != "" {
img, err = daemon.repositories.LookupImage(config.Image)
if err != nil {
return nil, nil, err
}
if err = daemon.graph.CheckDepth(img); err != nil {
return nil, nil, err
}
imgID = img.ID
}
if err := daemon.mergeAndVerifyConfig(config, img); err != nil {
return nil, nil, err
}
if hostConfig == nil {
hostConfig = &runconfig.HostConfig{}
}
if hostConfig.SecurityOpt == nil {
hostConfig.SecurityOpt, err = daemon.GenerateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode)
if err != nil {
return nil, nil, err
}
}
if container, err = daemon.newContainer(name, config, imgID); err != nil {
return nil, nil, err
}
defer func() {
if retErr != nil {
if err := daemon.rm(container, false); err != nil {
logrus.Errorf("Clean up Error! Cannot destroy container %s: %v", container.ID, err)
}
}
}()
if err := daemon.Register(container); err != nil {
return nil, nil, err
}
if err := daemon.createRootfs(container); err != nil {
return nil, nil, err
}
if err := daemon.setHostConfig(container, hostConfig); err != nil {
return nil, nil, err
}
if err := container.Mount(); err != nil {
return nil, nil, err
}
defer container.Unmount()
if err := createContainerPlatformSpecificSettings(container, config, img); err != nil {
return nil, nil, err
}
if err := container.ToDisk(); err != nil {
logrus.Errorf("Error saving new container to disk: %v", err)
return nil, nil, err
}
container.LogEvent("create")
return container, warnings, nil
}
一开始是一些获取imgID,校验镜像层数(镜像得层数不能过大,有MaxImageDepth参数用来控制,默认取值127),校验config参数等操作;
daemon.newContainer(name, config, imgID)是来实例化一个新的container;接下来的一些操作就是为启动一个容器所做的操作:
首先是daemon.Register(container):(daemon/daemon.go)
在daemon中注册container,通过daemon.containers 结构,使得daemon可以通过map[containerId]*Container的结构来记录container;还有为container中自带的mountPoint建立volume;
然后是daemon.createRootfs(container)
func (daemon *Daemon) createRootfs(container *Container) error {
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
return err
}
initID := fmt.Sprintf("%s-init", container.ID)
if err := daemon.driver.Create(initID, container.ImageID); err != nil {
return err
}
initPath, err := daemon.driver.Get(initID, "")
if err != nil {
return err
}
if err := setupInitLayer(initPath); err != nil {
daemon.driver.Put(initID)
return err
}
// We want to unmount init layer before we take snapshot of it
// for the actual container.
daemon.driver.Put(initID)
if err := daemon.driver.Create(container.ID, initID); err != nil {
return err
}
return nil
}
建立container.root文件夹,路径是/var/lib/docker/containers/containerId;
创建InitID:容器ID-init
根据InitID和ImageID(此时imageID是InitID的父层)调用aufs.go的Create函数(在上一篇中分析过),在/var/lib/docker/aufs/mnt,/var/lib/docker/aufs/layers,/var/lib/docker/aufs/diff下建立InitID为名称的子文件夹,然后将ImageID和ImageID的所有父亲ImageID写入到layers中;init层是docker在所有镜像之上建立的一层:主要作用是存放一些关于容器的配置信息;
initPath, err := daemon.driver.Get(initID, "") 同样是调用aufs.go中的Get函数;这个函数比较重要,Get函数将所有祖先镜像的数据都挂在到/var/lib/docker/aufs/mnt/initID目录下,所有祖先镜像的实际数据分别在 /var/lib/docker/aufs/diff/imgID目录下;返回的initPath就是/var/lib/docker/aufs/mnt/initID。
setupInitLayer(initPath);创建初始化层,就是创建一个容器需要的基本目录和文件,包括的内容有:
"/dev/pts": "dir",
"/dev/shm": "dir",
"/proc": "dir",
"/sys": "dir",
"/.dockerinit": "file",
"/.dockerenv": "file",
"/etc/resolv.conf": "file",
"/etc/hosts": "file",
"/etc/hostname": "file",
"/dev/console": "file",
"/etc/mtab": "/proc/mounts"
接着 在初始层之上建立容器ID层,同时通过Driver的Put函数减少初始层的引用次数,但此时容器ID层并没有进行mount操作;
daemon.driver.Put(initID)
daemon.driver.Create(container.ID, initID);
到此createRootfs操作结束;
回到Create函数接下来是setHostConfig()(daemon/daemon.go)
func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error {
container.Lock()
if err := parseSecurityOpt(container, hostConfig); err != nil {
container.Unlock()
return err
}
container.Unlock()
// Do not lock while creating volumes since this could be calling out to external plugins
// Don't want to block other actions, like `docker ps` because we're waiting on an external plugin
if err := daemon.registerMountPoints(container, hostConfig); err != nil {
return err
}
container.Lock()
defer container.Unlock()
// Register any links from the host config before starting the container
if err := daemon.RegisterLinks(container, hostConfig); err != nil {
return err
}
container.hostConfig = hostConfig
container.toDisk()
return nil
}
parseSecurityOpt(container, hostConfig); 关于安全的一些参数得解析;
daemon.registerMountPoints(container, hostConfig); 注册所有挂载到容器的数据卷,主要有三种方式和来源:
(1)容器本身原有自带的挂载的数据卷,应该是容器的json镜像文件中 "Volumes"这个key对应得内容;
(2)通过其他数据卷容器(通过--volumes-from)挂载的数据卷;
(3)通过命令行参数(-v参数)挂载的与主机绑定的数据卷,与主机绑定得数据卷在docker中叫做bind-mounts,这种数据卷与一般的正常得数据卷是有些细微区别的;
daemon.RegisterLinks(container, hostConfig) (daemon/daemon_unix.go) 注册互联的容器,容器之间除了可以通过 ip:端口 相互访问,容器之间还可以互联(通过--link 容器名字 的方式),例如一个web容器可以通过这种方式与一个数据库容器互联;互联的容器之间可以相互访问,可以通过环境变量和/etc/hosts 来公开连接信息。容器之间的互联关系要注册到sqlite3数据库中,也就是daemon.containerGraph中,deamon.containerGraph是一个graphdb.Database类型,这个小型的数据库默认的实现方式是sqllite,里面存储这容器之间的互联关系;
然后继续回到daemon.Create()函数,在setHostConfig()之后:
container.Mount()
因为上面提到了,在创建initLayer的时候,虽然建立了containerID的有关文件夹,但是并没有进行对containerID及其父亲镜像进行挂载操作,这里就是将containerID及其父镜像进行挂载操作;
createContainerPlatformSpecificSettings(container, config, img)
这个函数的是处理config中得Volumes的,通过-v参数挂载的数据卷,但不是bind-volumes,bind-volumes是放置在hostConfig中的。config与hostConfig都是配置的结构,区别是config是只与container相关的配置,hostConfig属于与宿主机相关的配置选项;
回到daemon.Create()函数,接下来是container.ToDisk()
func (container *Container) toDisk() error {
data, err := json.Marshal(container)
if err != nil {
return err
}
pth, err := container.jsonPath()
if err != nil {
return err
}
if err := ioutil.WriteFile(pth, data, 0666); err != nil {
return err
}
return container.WriteHostConfig()
}
这段代码就是将container中的config和hostconfig结构体存储到磁盘上,存储的路径是/var/lib/docker/container/containerId/config.json 和 /var/lib/docker/container/containerId/hostConfig.json
到目前为止,一个container需要的文件系统视角已经完全建立起来,但这个container还没有启动,还没有运行container里面的命令,下一篇的内容讨论container是怎样运行起来的。
docker 源码分析 五(基于1.8.2版本),Docker容器的创建的更多相关文章
-
Docker源码分析(六):Docker Daemon网络
1. 前言 Docker作为一个开源的轻量级虚拟化容器引擎技术,已然给云计算领域带来了新的发展模式.Docker借助容器技术彻底释放了轻量级虚拟化技术的威力,让容器的伸缩.应用的运行都变得前所未有的方 ...
-
Docker源码分析(三):Docker Daemon启动
1 前言 Docker诞生以来,便引领了轻量级虚拟化容器领域的技术热潮.在这一潮流下,Google.IBM.Redhat等业界翘楚纷纷加入Docker阵营.虽然目前Docker仍然主要基于Linux平 ...
-
Docker源码分析(四):Docker Daemon之NewDaemon实现
1. 前言 Docker的生态系统日趋完善,开发者群体也在日趋庞大,这让业界对Docker持续抱有极其乐观的态度.如今,对于广大开发者而言,使用Docker这项技术已然不是门槛,享受Docker带来的 ...
-
Docker源码分析(五):Docker Server的创建
1.Docker Server简介 Docker架构中,Docker Server是Docker Daemon的重要组成部分.Docker Server最主要的功能是:接受用户通过Docker Cli ...
-
docker 源码分析 一(基于1.8.2版本),docker daemon启动过程;
最近在研究golang,也学习一下比较火的开源项目docker的源代码,国内比较出名的docker源码分析是孙宏亮大牛写的一系列文章,但是基于的docker版本有点老:索性自己就git 了一下最新的代 ...
-
docker 源码分析 四(基于1.8.2版本),Docker镜像的获取和存储
前段时间一直忙些其他事情,docker源码分析的事情耽搁了,今天接着写,上一章了解了docker client 和 docker daemon(会启动一个http server)是C/S的结构,cli ...
-
Docker源码分析(八):Docker Container网络(下)
1.Docker Client配置容器网络模式 Docker目前支持4种网络模式,分别是bridge.host.container.none,Docker开发者可以根据自己的需求来确定最适合自己应用场 ...
-
Docker源码分析(九):Docker镜像
1.前言 回首过去的2014年,大家可以看到Docker在全球刮起了一阵又一阵的“容器风”,工业界对Docker的探索与实践更是一波高过一波.在如今的2015年以及未来,Docker似乎并不会像其他昙 ...
-
Docker源码分析(一):Docker架构
1 背景 1.1 Docker简介 Docker是Docker公司开源的一个基于轻量级虚拟化技术的容器引擎项目,整个项目基于Go语言开发,并遵从Apache 2.0协议.目前,Docker可以在容器内 ...
随机推荐
-
DNG格式解析
Author:Maddock Date:2015.04.22 转载请注明出处:http://www.cnblogs.com/adong7639/p/4446828.html DNG格式基本概念 DNG ...
-
【HTML5&;CSS3进阶学习01】气泡组件的实现
前言 气泡组件在实际工作中非常普遍,无论是网页中还是app中,比如: 我们这里所谓气泡组件是指列表型气泡组件,这里就其dom实现,css实现,js实现做一个讨论,最后对一些细节点做一些说明,希望对各位 ...
-
主流的单元测试工具之-JAVA新特性-Annotation 写作者:组长 梁伟龙
1:什么是Annotation?Annotation,即“@xxx”(如@Before,@After,@Test(timeout=xxx),@ignore),这个单词一般是翻译成元数据,是JAVA的一 ...
-
DE1-SOC开发板上搭建NIOS II处理器运行UCOS II
DE1-SOC开发板上搭建NIOS II处理器运行UCOS II 今天在DE1-SOC的开发板上搭建NIOS II软核运行了UCOS II,整个开发过程比较繁琐,稍微有一步做的不对,就会导致整个过 ...
-
h-index
https://leetcode.com/problems/h-index/ https://leetcode.com/mockinterview/session/result/xjcpjlh/ 看了 ...
-
[Learn Android Studio 汉化教程]第四章 : Refactoring Code
[Learn Android Studio 汉化教程]第四章 : Refactoring Code 第四章 Refactoring Code 重构代码 在Android Studio中开发,解决 ...
-
php+mysql 除了设置主键防止表单提交内容重复外的另一种方法
感觉好久没有更新博客了,一直在做网站及后台,也没有遇到让我觉得可以整理的内容,之前做的一个系统,已经完成了,后来客户又要求加一个功能,大概就是表单提交的时候,约束有一项不能和以前的内容重复,如图 比如 ...
-
nginx新的站点的配置
每一次配置新的站点的时候,要记得重新启动nginx: sudo -s; nginx -s reload; 配置文件,有涉及到 每一个站点都有一个.conf文件. 域名重定向:Gas Mask的软件的使 ...
-
Python assert(断言)
Python assert(断言)可以分别后面的判断是否正确,如果错误会报错 示例: a = 1 assert type(a) is int print('No problem') 输出结果: No ...
-
Entity Framework Core 生成跟踪列-阴影属性
摘自:https://www.cnblogs.com/tdfblog/p/entity-framework-core-generate-tracking-columns.html Ef Core 官方 ...