Docker Run的背后流程

·

3 min read

如果你想启动一个nginx容器,你可能会在命令行输入以下命令

~ docker run -d nginx

然后满怀期望等待奇迹发生,结果看到输出

f021105c2da60f1f3930070e917668c7ffcabc45e2cf64c622a2d68f216d0c2b

注意

-d, --detach Run container in background and print container ID

然后我们发现系统里面多了一个nginx容器,这个过程的背后究竟发生了什么?我们来一探究竟

docker

docker - Docker image and container command line interface

docker是镜像和命令行接口

docker本身其实是一个C/S模型的架构

image.png image.png

它主要由三部分组成

  • 一个Server服务端作为后台守护进程
  • 跟Server端通信的REST API
  • 一个Client客户端工具也就是我们上面用到的docker

run这个命令是告诉docker: 我要新创建一个容器, nginx代表这个容器的启动镜像

第一个接口请求

HEAD /_ping HTTP/1.1
Host: docker
User-Agent: Docker-Client/19.03.4 (darwin)

请求响应

HTTP/1.1 200 OK
Api-Version: 1.40
Cache-Control: no-cache, no-store, must-revalidate
Content-Length: 0
Content-Type: text/plain; charset=utf-8
Date: Fri, 08 Nov 2019 05:40:58 GMT
Docker-Experimental: false
Ostype: linux
Pragma: no-cache
Server: Do

从功能上来讲,是一个探活接口,看看服务端是否正在运行

创建镜像

服务端运行正常,接下来可以发起创建镜像的请求

POST /v1.40/containers/create HTTP/1.1
Host: docker
User-Agent: Docker-Client/19.03.4 (darwin)
Content-Length: 1514
Content-Type: application/json

{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false"
...

事实上我是使用以下dtruss命令来跟踪docker的执行进程

dtruss -saL docker run -d nginx 2> ~/Desktop/dockerrun.log

是的,没错,就是大名鼎鼎的DTrace的Mac版,(但是打印出来的POST请求内容不全,目前还不知道如何完整打印,猜测应该是参数过多,尴尬。)

HTTP/1.1 404 Not Found
Api-Version: 1.40
Content-Length: 42
Content-Type: application/json
Date: Fri, 08 Nov 2019 05:40:58 GMT
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.4 (linux)

{"message":"No such image: nginx:latest"}

从响应结果看客户端向服务器查询是否有nginx:latest的镜像存在,然而并没有

系统信息

接下来有一次系统信息的接口调用

GET /v1.40/info HTTP/1.1
Host: docker
User-Agent: Docker-Client/19.03.4 (darwin)

响应如下

HTTP/1.1 200 OK
Api-Version: 1.40
Content-Type: application/json
Date: Fri, 08 Nov 2019 05:40:58 GMT
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.4 (linux)
Transfer-Encoding: chunked

914
{"ID":"A2GJ:TQBK:V4H5:GFXT:KGB7:QRV5:GWJ7
...

这一步是获取系统信息,比如docker镜像的根目录这些,为镜像下载做准备(虽然多数情况下docker的C/S是在一台机器上,但是这种设计本身就具有比较好的扩展性,C、S两端可以分离,这跟我们本地使用MySQL的客户端服务端都是在本地一样)

镜像下载

HTTP/1.1 200 OK
Api-Version: 1.40
Content-Type: application/json
Date: Fri, 08 Nov 2019 05:41:02 GMT
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.4 (linux)
Transfer-Encoding: chunked

37
{"status":"Pulling from library/nginx","id
...

当然这之前自然少不了读取本地docker的配置文件,获取镜像仓库地址(默认为:index.docker.io/v1),然后开始下载,直到下载完成 再次发起创建容器请求,比较奇怪的是只拿到响应信息,尴尬了

再次创建容器

POST /v1.40/containers/create HTTP/1.1
Host: docker
User-Agent: Docker-Client/19.03.4 (darwin)
Content-Length: 1514
Content-Type: application/json

{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false
...

镜像已存在

HTTP/1.1 201 Created
Api-Version: 1.40
Content-Length: 88
Content-Type: application/json
Date: Fri, 08 Nov 2019 05:41:03 GMT
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.4 (linux)

容器同名ID检查

接下来这一步比较奇怪

POST /v1.40/containers/f021105c2da60f1f3930070e917668c7ffcabc45e2cf64c622a2d68f216d0c2b/wait?condition=next-exit HTTP/1.1
Host: docker
User-Agent: Docker-Client/19.03.4 (darwin)
Content-Length: 0
Content-Type: text/plain

这个接口的功能是等待idf021105c2da60f1f3930070e917668c7ffcabc45e2cf64c622a2d68f216d0c2b的容器退出 这一步应该是为了避免重复创建同名的容器 容器的ID生成代码GenerateRandomID的实现如下,是一段64位的随机字符串,有可能会重复?

func GenerateRandomID() string {
    b := make([]byte, 32)
    for {
        if _, err := rand.Read(b); err != nil {
            panic(err) // This shouldn't happen
        }
        id := hex.EncodeToString(b)
        // if we try to parse the truncated for as an int and we don't have
        // an error then the value is all numeric and causes issues when
        // used as a hostname. ref #3869
        if _, err := strconv.ParseInt(TruncateID(id), 10, 64); err == nil {
            continue
        }
        return id
    }
}

因为没有同名的容器,所以返回正常

HTTP/1.1 200 OK
Api-Version: 1.40
Content-Type: application/json
Date: Fri, 08 Nov 2019 05:41:03 GMT
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.4 (linux)
Transfer-Encoding: chunked

启动容器

此时可以启动容器了

POST /v1.40/containers/f021105c2da60f1f3930070e917668c7ffcabc45e2cf64c622a2d68f216d0c2b/start HTTP/1.1
Host: docker
User-Agent: Docker-Client/19.03.4 (darwin)
Content-Length: 0
Content-Type: text/plain

返回启动成功的响应

HTTP/1.1 204 No Content
Api-Version: 1.40
Date: Fri, 08 Nov 2019 05:41:04 GMT
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.4 (linux)

至此,容器启动完成

总结

以下是我整理的接口调用流程

image.png

可以看到,整个流程是非常简洁清晰而且严谨的,尤其是REST API的接口设计,非常规范,实在是很有借鉴意义,另外,第一次使用dtruss,真的是神器。

Refer