1. 概述
本文将指导我们如何通过Docker引擎API从Docker容器内部获取信息。
2. Setup
我们将从多个方式连接到Docker引擎,并讨论在Linux系统上最常用的方法,这些方法在其他操作系统上同样适用。
然而,在进行设置时,我们需要非常小心,因为启用远程访问代表了一种安全风险。当容器能够访问引擎时,它会破坏与主机操作系统的隔离。
在本节中,我们将假定对主机具有完全控制权。
2.1 通过默认Unix套接字转发
默认情况下,Docker引擎使用位于主机操作系统上的/var/run/docker.sock
路径下的Unix套接字:
$ ss -xan | grep var
u_str LISTEN 0 4096 /var/run/docker/libnetwork/dd677ae5f81a.sock 56352 * 0
u_dgr UNCONN 0 0 /var/run/chrony/chronyd.sock 24398 * 0
u_str LISTEN 0 4096 /var/run/nscd/socket 23131 * 0
u_str LISTEN 0 4096 /var/run/docker/metrics.sock 42876 * 0
u_str LISTEN 0 4096 /var/run/docker.sock 53704 * 0
...
通过这种方式,我们可以严格控制哪些容器可以访问API。这就是Docker CLI在幕后的工作方式。
让我们启动名为alpine
的Docker容器,并使用\-v
标志将此路径挂载:
$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock alpine
(alpine) $
接下来,让我们在容器中安装一些实用工具:
(alpine) $ apk add curl && apk add jq
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
(1/4) Installing ca-certificates (20191127-r2)
(2/4) Installing nghttp2-libs (1.40.0-r1)
...
现在让我们使用带有--unix-socket
标志的curl
和Jq来获取并过滤一些容器数据:
(alpine) $ curl -s --unix-socket /var/run/docker.sock http://dummy/containers/json | jq '.'
[
{
"Id": "483c5d4aa0280ca35f0dbca59b5d2381ad1aa455ebe0cf0ca604900b47210490",
"Names": [
"/wizardly_chatelet"
],
"Image": "alpine",
"ImageID": "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
"Command": "/bin/sh",
"Created": 1595882408,
"Ports": [],
...
在这里,我们对/containers/json
端点执行GET
操作并获取当前运行的容器。然后我们使用jq
美化输出。
稍后我们会详细介绍引擎API的细节。
2.2 启用TCP远程访问
我们还可以通过TCP套接字启用远程访问。
对于使用systemd
的Linux发行版,我们需要自定义Docker服务单元。对于其他Linux发行版,我们需要自定义通常位于/etc/docker
的daemon.json
。
我们只需处理第一种设置,因为大多数步骤相似。
默认的Docker设置包括一个桥接网络。这是所有容器连接的地方,除非另有指定。
因为我们只允许容器访问引擎API,首先我们需要识别它们的网络:
$ docker network ls
a3b64ea758e1 bridge bridge local
dfad5fbfc671 host host local
1ee855939a2a none null local
让我们查看其详细信息:
$ docker network inspect a3b64ea758e1
[
{
"Name": "bridge",
"Id": "a3b64ea758e1f02f4692fd5105d638c05c75d573301fd4c025f38d075ed2a158",
...
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
...
接下来,让我们查看Docker服务单元的位置:
$ systemctl status docker.service
docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
...
CGroup: /system.slice/docker.service
├─6425 /usr/bin/dockerd --add-runtime oci=/usr/sbin/docker-runc
└─6452 docker-containerd --config /var/run/docker/containerd/containerd.toml --log-level warn
现在让我们看看服务单元的定义:
$ cat /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
After=network.target lvm2-monitor.service SuSEfirewall2.service
[Service]
EnvironmentFile=/etc/sysconfig/docker
...
Type=notify
ExecStart=/usr/bin/dockerd --add-runtime oci=/usr/sbin/docker-runc $DOCKER_NETWORK_OPTIONS $DOCKER_OPTS
ExecReload=/bin/kill -s HUP $MAINPID
...
ExecStart
属性定义了由systemd
(dockerd
可执行文件)运行的命令。我们在其中传递了\-H
标志,并指定了相应的网络和监听端口。
我们可以直接修改这个服务单元(不推荐),但让我们使用$DOCKER_OPTS
变量(在EnvironmentFile=/etc/sysconfig/docker
中定义):
$ cat /etc/sysconfig/docker
## Path : System/Management
## Description : Extra cli switches for docker daemon
## Type : string
## Default : ""
## ServiceRestart : docker
#
DOCKER_OPTS="-H unix:///var/run/docker.sock -H tcp://172.17.0.1:2375"
在这里,我们使用桥接网络的网关地址作为绑定地址。这对应于主机上的docker0
接口:
$ ip address show dev docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:6c:7d:9c:8d brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:6cff:fe7d:9c8d/64 scope link
valid_lft forever preferred_lft forever
我们还启用了本地Unix套接字,以便Docker CLI仍可以在主机上正常工作。
最后一步是我们需要做的。让我们允许我们的容器包到达主机:
$ iptables -I INPUT -i docker0 -j ACCEPT
在这里,我们将Linux防火墙设置为接受通过docker0
接口的所有包。
现在,让我们重启Docker服务:
$ systemctl restart docker.service
$ systemctl status docker.service
docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
...
CGroup: /system.slice/docker.service
├─8110 /usr/bin/dockerd --add-runtime oci=/usr/sbin/docker-runc -H unix:///var/run/docker.sock -H tcp://172.17.0.1:2375
└─8137 docker-containerd --config /var/run/docker/containerd/containerd.toml --log-level wa
再次运行alpine
容器:
(alpine) $ curl -s http://172.17.0.1:2375/containers/json | jq '.'
[
{
"Id": "45f13902b710f7a5f324a7d4ec7f9b934057da4887650dc8fb4391c1d98f051c",
"Names": [
"/unruffled_cray"
],
"Image": "alpine",
"ImageID": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
"Command": "/bin/sh",
"Created": 1596046207,
"Ports": [],
...
应 意识到所有连接到桥接网络的容器都可以访问守护进程API。
此外,我们的TCP连接没有加密。
4. 结论
在本文中,我们学习了如何使用Docker引擎远程API。
我们首先设置了远程访问,既可以来自UNIX套接字或TCP,然后展示了如何使用远程API。
通过这种方式,我们可以获取容器的详细信息、监听Docker守护进程事件、创建和管理容器以及探索日志输出等操作。