strace - trace system calls and signals

strace在前面的文章其实有用到过。 strace是Linux上内置的一个工具,能非常方便用来诊断问题以及调试程序等,因为它可记录和展示一条命令究竟是如何进行系统调用(system calls) 以及其中的信号处理情况。

docker是如何交互的。

抛开其他不说,单就是命令交互这一层面,其实就是一个cs模型。 安装docker后,其实启动了一个服务,这个进程是dockerd,它是一个服务端进程,会启动一REST server端等待客户端交互(当然这里省略后台的各种job)。而我们平时敲下代码的docker run xxx 中的docker其实只是个客户端,它并不负责后面跟踪的run命令,它把一个HTTP请求发给dockerd,等待返回结果就万事了。 如下图: image.png

一条命令的执行过程

一条命令docker run -d -p 27017:27017 -p 28017:28017 tutum/mongodb 是怎么到达dockerd服务端的?

这个时候可以使用strace把上述命令包装下,

sudo strace -e write \
              -o /tmp/docker.strace \
              -s 10000 \
              -f docker run -d -p 27017:27017 -p 28017:28017 tutum/mongodb

其中 -e: 后面接表达式,表示要追踪什么事件, 格式为[qualifier=][!]value1[,value2]... qualifier可以是trace, abbrev, verbose, raw, signal, read, write其一 这里主要要跟踪写了什么内容,就用writer, 其实高级用法参考man -o: 结果输出到文件 -s: strsize 打印的字符最大长度 -f: 追踪子进程结果,比如由fork,clone出来的子进程

随后打开/tmp/docker.strace 可以看到一堆执行结果:

10911 write(3, "POST /v1.25/containers/create HTTP/1.1\r\nHost: docker\r\nUser-Agent: Docker-Client/1.13.0 (linux)\r\nContent-Length: 1551\r\nContent-Type: application/json\r\n\r\n{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"27017/tcp\":{},\"28017/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[],\"Cmd\":null,\"Image\":\"tutum/mongodb\",\"Volumes\":{},\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{},\"HostConfig\":{\"Binds\":null,\"ContainerIDFile\":\"\",\"LogConfig\":{\"Type\":\"\",\"Config\":{}},\"NetworkMode\":\"default\",\"PortBindings\":{\"27017/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"27017\"}],\"28017/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"28017\"}]},\"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", 1703) = 1703
...
10914 write(5, "POST /v1.25/containers/42844726d8bd925d9903a0922f380c295dd398475b779d7ae2099ec2b4ab494e/start HTTP/1.1\r\nHost: docker\r\nUser-Agent: Docker-Client/1.13.0 (linux)\r\nContent-Length: 0\r\nContent-Type: text/plain\r\n\r\n", 207) = 207

整理下可以看出这是一个标准的HTTP协议通信:

POST /v1.25/containers/create HTTP/1.1
Host: docker
User-Agent: Docker-Client/1.13.0 (linux)
Content-Length: 1551
Content-Type: application/json

{
  "Hostname": "",
  "Domainname": "",
  "User": "",
  "AttachStdin": false,
  "AttachStdout": false,
  "AttachStderr": false,
  "ExposedPorts": {
    "27017/tcp": {},
    "28017/tcp": {}
  },
  "Tty": false,
  "OpenStdin": false,
  "StdinOnce": false,
  "Env": [],
  "Cmd": null,
  "Image": "tutum/mongodb",
  "Volumes": {},
  "WorkingDir": "",
  "Entrypoint": null,
  "OnBuild": null,
  "Labels": {},
  "HostConfig": {
    "Binds": null,
    "ContainerIDFile": "",
    "LogConfig": {
      "Type": "",
      "Config": {}
    },
    "NetworkMode": "default",
    "PortBindings": {
      "27017/tcp": [
        {
          "HostIp": "",
          "HostPort": "27017"
        }
      ],
      "28017/tcp": [
        {
          "HostIp": "",
          "HostPort": "28017"
        }
      ]
    },
    "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": {}
  }
}

尽管其中包含了很多默认配置,他们其实可以省略 转换为curl命令,直接在终端发给dockerd也是可以的

$ sudo curl -v -H "Content-Type: application/json" -d '{"ExposedPorts":{"27017/tcp":{},"28017/tcp":{}},"Image":"tutum/mongodb","HostConfig":{"PortBindings":{"27017/tcp":[{"HostIp":"","HostPort":
"27017"}],"28017/tcp":[{"HostIp":"","HostPort":"28017"}]}}}' --unix-socket /var/run/docker.sock http://docker/containers/create                                                                                    
*   Trying /var/run/docker.sock...
* Connected to docker (/var/run/docker.sock) port 80 (#0)
> POST /containers/create HTTP/1.1
> Host: docker
> User-Agent: curl/7.50.3
> Accept: */*
> Content-Type: application/json
> Content-Length: 198
> 
* upload completely sent off: 198 out of 198 bytes
< HTTP/1.1 201 Created
< Api-Version: 1.25
< Content-Type: application/json
< Docker-Experimental: false
< Server: Docker/1.13.0 (linux)
< Date: Tue, 31 Jan 2017 05:03:34 GMT
< Content-Length: 90
< 
{"Id":"be10c2bb8f07e36717a10cdf102a304aaf2ee072661df0265e322bdeb5fc1d78","Warnings":null}
* Curl_http_done: called premature == 0
* Connection #0 to host docker left intact

验证:

$ sudo docker ps -a                                                                                                                                                                               
CONTAINER ID        IMAGE                   COMMAND                  CREATED              STATUS                    PORTS               NAMES
be10c2bb8f07        tutum/mongodb           "/run.sh"                About a minute ago   Created                                       inspiring_shannon

结论是,确实是可以自行构造请求,向docker服务端创建容器 需要值得一提的是这里通过socket去和服务端通信的,--unix-socket /var/run/docker.sock。 除此之外没什么特别的。

那修改下让他启动起来如何? 结合第一次trace结果,可以看到请求的路径应该为/containers/[UUID]/start HTTP 方法是POST,而UUID前面有输出。显然这样的命令也可以构造

$ sudo curl -X POST -v --unix-socket /var/run/docker.sock http://docker/containers/be10c2bb8f07e36717a10cdf102a304aaf2ee072661df0265e322bdeb5fc1d78/start                                         
*   Trying /var/run/docker.sock...
* Connected to docker (/var/run/docker.sock) port 80 (#0)
> POST /containers/be10c2bb8f07e36717a10cdf102a304aaf2ee072661df0265e322bdeb5fc1d78/start HTTP/1.1
> Host: docker
> User-Agent: curl/7.50.3
> Accept: */*
> 
< HTTP/1.1 204 No Content
< Api-Version: 1.25
< Docker-Experimental: false
< Server: Docker/1.13.0 (linux)
< Date: Tue, 31 Jan 2017 05:05:46 GMT
< 
* Curl_http_done: called premature == 0
* Connection #0 to host docker left intact

完成后验证下状态也是ok的

$ sudo docker ps   
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                                NAMES
be10c2bb8f07        tutum/mongodb       "/run.sh"           2 minutes ago       Up 8 seconds        0.0.0.0:27017->27017/tcp, 0.0.0.0:28017->28017/tcp   inspiring_shannon

扩展

但说几个常用的API固然是没什么用,毕竟覆盖不了太多场景。 其实docker是有它的API文档的,可以参考官方链接 具体要选择自己版本。 当时API文档是有,具体调试还是可以结合上面说的trace办法来观测和调整参数 这都有哪些应用场景:

  1. 可以做更多或者更方便的自动化测试,(比如docker cli不存在或者不可用的场景)
  2. 定制化开发
  3. 结合容器管理平台做CICD,比如使用portainer管理容器组
  4. ...