How the docker container creation process works (from docker run to runc)


Over the past few months I’ve been investing a good bit of personal time studying how Linux containers work. Specifically, what does docker run actually do. In this post I’m going to walk through what I’ve observed and try to demystify how all the pieces fit togther. To start our adventure I’m going to create an alpine container with docker run:

$ docker run -i -t --name alpine alpine ash

This container will be used in the output below. When the docker run command is invoked it parses the options passed on the command line and creates a JSON object to represent the object it wants docker to create. The object is then sent to the docker daemon through the /var/run/docker.sock UNIX domain socket. We can use the strace utility to observe the API calls:

$ strace -s  -e trace=read,write -f docker run -d alpine

[pid ] write(, "GET /_ping HTTP/1.1\r\nHost: docker\r\nUser-Agent: Docker-Client/1.13.1 (linux)\r\n\r\n", ) =
[pid ] read(, "HTTP/1.1 200 OK\r\nApi-Version: 1.26\r\nDocker-Experimental: false\r\nServer: Docker/1.13.1 (linux)\r\nDate: Mon, 19 Feb 2018 16:12:32 GMT\r\nContent-Length: 2\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nOK", ) =
[pid ] write(, "POST /v1.26/containers/create HTTP/1.1\r\nHost: docker\r\nUser-Agent: Docker-Client/1.13.1 (linux)\r\nContent-Length: 1404\r\nContent-Type: application/json\r\n\r\n{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[],\"Cmd\":null,\"Image\":\"alpine\",\"Volumes\":{},\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{},\"HostConfig\":{\"Binds\":null,\"ContainerIDFile\":\"\",\"LogConfig\":{\"Type\":\"\",\"Config\":{}},\"NetworkMode\":\"default\",\"PortBindings\":{},\"RestartPolicy\":{\"Name\":\"no\",\"MaximumRetryCount\":0},\"AutoRemove\":false,\"VolumeDriver\":\"\",\"VolumesFrom\":null,\"CapAdd\":null,\"CapDrop\":null,\"Dns\":[],\"DnsOptions\":[],\"DnsSearch\":[],\"ExtraHosts\":null,\"GroupAdd\":null,\"IpcMode\":\"\",\"Cgroup\":\"\",\"Links\":null,\"OomScoreAdj\":0,\"PidMode\":\"\",\"Privileged\":false,\"PublishAllPorts\":false,\"ReadonlyRootfs\":false,\"SecurityOpt\":null,\"UTSMode\":\"\",\"UsernsMode\":\"\",\"ShmSize\":0,\"ConsoleSize\":[0,0],\"Isolation\":\"\",\"CpuShares\":0,\"Memory\":0,\"NanoCpus\":0,\"CgroupParent\":\"\",\"BlkioWeight\":0,\"BlkioWeightDevice\":null,\"BlkioDeviceReadBps\":null,\"BlkioDeviceWriteBps\":null,\"BlkioDeviceReadIOps\":null,\"BlkioDeviceWriteIOps\":null,\"CpuPeriod\":0,\"CpuQuota\":0,\"CpuRealtimePeriod\":0,\"CpuRealtimeRuntime\":0,\"CpusetCpus\":\"\",\"CpusetMems\":\"\",\"Devices\":[],\"DiskQuota\":0,\"KernelMemory\":0,\"MemoryReservation\":0,\"MemorySwap\":0,\"MemorySwappiness\":-1,\"OomKillDisable\":false,\"PidsLimit\":0,\"Ulimits\":null,\"CpuCount\":0,\"CpuPercent\":0,\"IOMaximumIOps\":0,\"IOMaximumBandwidth\":0},\"NetworkingConfig\":{\"EndpointsConfig\":{}}}\n", ) =
[pid ] read(, "HTTP/1.1 201 Created\r\nApi-Version: 1.26\r\nContent-Type: application/json\r\nDocker-Experimental: false\r\nServer: Docker/1.13.1 (linux)\r\nDate: Mon, 19 Feb 2018 16:12:32 GMT\r\nContent-Length: 90\r\n\r\n{\"Id\":\"b70b57c5ae3e25585edba898ac860e388582391907be4070f91eb49f4db5c433\",\"Warnings\":null}\n", ) =

Now here is were the real fun begins. Once the docker daemon receives the request it will parse the output and contact containerd via the gRPC API to set up the container runtime using the options passed on the command line. We can use the ctr utility to observe this interaction:

Setting up the container runtime is a pretty substantial undertaking. Namespaces need to be configured, the Image needs to be mounted, security controls (app armor profiles, seccomp profiles, capabilities) need to be enabled, etc , etc. You can get a pretty good idea of everything that is required to set up the runtime by reviewing the output of docker inspect containerid and the config.json runtime specification file (more on that in a moment).

Containerd doesn’t actually create the container runtime. It sets up the environment and then invokes containerd-shim to start the container runtime via the configured OCI runtime (controlled with the containerd “–runtime” option) . For most modern systems the container runtime is based on runc. We can see this first hand with the pstree utility:

$ pstree -l -p -s -T

systemd, --switched-root --system --deserialize
├─docker-containe, --listen unix:///run/containerd.sock --shim /usr/libexec/docker/docker-containerd-shim-current --start-timeout 2m --debug
│ ├─docker-containe, 93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /var/run/docker/libcontainerd/93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /usr/libexec/docker/docker-runc-current

Since pstree truncates the process name we can verify the PIDs with ps:

$ ps auxwww | grep []

root       0.0  0.2   ?        Ssl  :   : /usr/libexec/docker/docker-containerd-current --listen unix:///run/containerd.sock --shim /usr/libexec/docker/docker-containerd-shim-current --start-timeout 2m --debug

$ ps auxwww | grep []

root       0.0  0.0    ?        Sl   :   : /usr/libexec/docker/docker-containerd-shim-current 93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /var/run/docker/libcontainerd/93a619715426f613646359863e77cc06fa85502273df931517ec3f4aaae50d5a /usr/libexec/docker/docker-runc-current

When I first started researching the interaction between dockerd, containerd and the shim I wasn’t real sure what purpose the shim served. Luckily Google took me to a great write up by Michael Crosby. The shim serves a couple of purposes:

  1. It allows you to run daemonless containers.
  2. STDIO and other FDs are kept open in the event that containerd and docker die.
  3. Reports the containers exit status to containerd.

The first and second bullet points are super important. These features allows the container to be decoupled from the docker daemon allowing dockerd to be upgraded or restarted w/o impacting the running containers. Nifty! I mentioned that the shim is responsible for kicking off runc to actually run the container. Runc needs two things to do its job: a specification file and a path to a root file system image (the combination of the two is referred to as a bundle). To see how this works we can create a rootfs by exporting the alpine docker image:

$ mkdir -p alpine/rootfs

$ cd alpine

$ docker export d1a6d87886e2 | tar -C rootfs -xvf -

time="2018-02-19T12:54:13.082321231-05:00" level=debug msg="Calling GET /v1.26/containers/d1a6d87886e2/export"
.dockerenv
bin/
bin/ash
bin/base64
bin/bbconfig
.....

The export option takes a container if which you can find in the docker ps -a output. To generate a specificationfile you can use the runc spec command:

$ runc spec

This will create a specification file named config.json in your current directory. This file can be customized to suit your needs and requirements. Once you are happy with the file you can run runc with the rootfs directory as its sole argument (the container configuration will be read from the file config.json file):

$ runc run rootfs

This simple example will spawn an alpine ash shell:

$ runc run rootfs

/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.7.
PRETTY_NAME="Alpine Linux v3.7"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"

Being able to create containers and play with the runc runtime specification is incredibly powerful. You can evaluate different apparmor profiles, test out Linux capabilities and play around with every facet of the container runtime environment without needing to install docker. I just barely scratched the surface here and would highly recommend reading through the runc and containerd documentation. Super cool stuff!

转载自:https://prefetch.net/blog/2018/02/19/how-the-docker-container-creation-process-works-from-docker-run-to-runc/

如何从底层调试docker的更多相关文章

  1. 如何解决Visual Studio 首次调试 docker 的 vs2017u5 exists, deleting Opening stream failed, trying again with proxy settings

    前言 因为之前我电脑安装的是windows10家庭版,然而windows10家庭没有Hyper-v功能. 搜索了几篇windows10家庭版安装docker相关的博客,了解一些前辈们走过的坑. 很多人 ...

  2. pycharm远程调试docker容器内程序

    文章链接: https://blog.csdn.net/hanchaobiao/article/details/84069299 参考链接: https://blog.csdn.net/github_ ...

  3. dotnet core调试docker下生成的dump文件

    最近公司预生产环境.net core应用的docker容器经常出现内存暴涨现象,有时会突然吃掉几个G,触发监控预警,造成容器重启. 分析了各种可能原因,修复了可能发生的内存泄露,经测试本地正常,但是发 ...

  4. macOS 下 PHPStorm + Xdebug 调试 Docker 环境中的代码

    0x00 描述 宿主机是 mac mini,构建的项目在 docker 中,所以需要在 PHPStorm 上配置 Xdebug 进行远程代码调试. 0x01 环境 宿主机:macOS High Sie ...

  5. phpStorm中使用xdebug工具调试docker容器中的程序

    前提准备 phpstorm开发软件 + dnmp(docker + nginx + mysql +php) 配置好hosts 映射比如 /etc/hosts      127.0.0.1 tp5.de ...

  6. 远程调试docker构建的weblogic

    环境信息 OSType: CentOS Linux 7 (Core) x86_64 3.10.0-957.21.3.el7.x86_64 DockerVersion: 19.03.8 Mirrors: ...

  7. 保姆级教程:VsCode调试docker中的NodeJS程序

    最近在写NodeJS相关的项目,运行在docker容器中,也是想研究一下断点调试,于是查阅相关资料,最终顺利配置好了. 首先我选择了VsCode作为ide,并用VsCode来做NodeJS可视化deb ...

  8. 使用 pycharm调试docker环境运行的Odoo

    2019日 星期一 安装docker windows系统,参考 docker官方文档 Mac系统,参考 docker官方文档 构建自定义ODOO镜像 标准ODOO镜像可能不包含特别的python模块, ...

  9. 如何调试 Docker

    开启 Debug 模式 在 dockerd 配置文件 daemon.json(默认位于 /etc/docker/)中添加 { "debug": true } 重启守护进程. $ s ...

随机推荐

  1. java 获取请求的完整url地址

    String url=request.getRequestURL()+"?"+request.getQueryString();

  2. java 例子

    1. 本章学习总结 今天主要学习了三个知识点 封装 继承 多态 2. 书面作业 Q1. java HelloWorld命令中,HelloWorld这个参数是什么含义? 今天学了一个重要的命令javac ...

  3. java主要集合类的数据结构学习

    http://www.cnblogs.com/beanmoon/archive/2012/11/22/2782442.html 在程序中,集合类每天都在使用,以致于某些代码充斥着List和Map,一直 ...

  4. BZOJ 1787: [Ahoi2008]Meet 紧急集合(lca+贪心)

    [Ahoi2008]Meet 紧急集合 Description Input Output Sample Input 6 4 1 2 2 3 2 4 4 5 5 6 4 5 6 6 3 1 2 4 4 ...

  5. 【Luogu】P3750分手是祝愿(期望DP)

    题目链接 这题好喵啊…… 设f[i]是最少用i次才能全关上转移到最少用i-1次才能全关上灯的期望值,那么n个灯里有i个是正确的,剩下的都是不正确的 因此期望是$f[i]=frac{n}{i}+frac ...

  6. chrome浏览器无法安装非应用商店插件的解决办法

    不久前,安装了一个非chrome应用商店的第三方应用,今天突然发现无法使用,打开chrome的扩展程序后,发现该插件以及被禁用,在网上查找了解决方法,设置“开发者模式”,修改了chrome的参数,仍然 ...

  7. [luogu1707] 刷题比赛 [矩阵快速幂]

    题面: 传送门 思路: 一眼看上去是三个递推......好像还挺麻烦的 仔细观察一下,发现也就是一个线性递推,但是其中后面的常数项比较麻烦 观察一下,这里面有以下三个递推是比较麻烦的 第一个是$k^2 ...

  8. BZOJ3571 [Hnoi2014]画框 【分治 + KM算法】

    题目链接 BZOJ3571 题解 如果知道最小乘积生成树,那么这种双权值乘积最小就是裸题了 将两权值和作为坐标,转化为二维坐标系下凸包上的点,然后不断划分分治就好了 这里求的是最小匹配值,每次找点套一 ...

  9. BZOJ2396 神奇的矩阵 【随机化 + 矩乘】

    题目链接 BZOJ2396 题解 一种快速判断两个矩阵是否相等的方法: 对于两个\(n * n\)矩阵,两边同时乘一个\(n * 1\)的随机矩阵,如果结果相等,那么有很大概率两个矩阵相等 如果左边是 ...

  10. C语言位运算符:与、或、异或、取反、左移与右移详细介绍

    位运算是指按二进制进行的运算.在系统软件中,常常需要处理二进制位的问题.C语言提供了6个位操作运算符.这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型 ...