1. Обзор
В этом руководстве мы увидим, как получить доступ к информации о контейнере Docker изнутри контейнера с помощью API Docker Engine.
2. Настройка
Мы можем подключиться к движку Docker несколькими способами. Мы рассмотрим наиболее полезные из них в Linux, но они также работают и в других операционных системах.
Однако мы должны быть очень осторожны , поскольку включение удаленного доступа представляет угрозу безопасности . Когда контейнер может получить доступ к движку, он нарушает изоляцию от основной операционной системы .
В части настройки мы будем считать, что у нас есть полный контроль над хостом.
2.1. Переадресация сокета Unix по умолчанию
По умолчанию механизм Docker использует сокет Unix, смонтированный в /var/run/docker.sock
в ОС хоста:
$ 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)
...
Теперь давайте используем curl
с флагом –unix-socket
и 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": [],
...
Здесь мы отправляем GET
на конечную точку /containers/json
и получаем запущенные в данный момент контейнеры . Затем мы украшаем вывод с помощью jq
.
Мы рассмотрим детали API движка чуть позже.
2.2. Включение удаленного доступа TCP
Мы также можем включить удаленный доступ с помощью сокета TCP.
Для дистрибутивов Linux, которые поставляются с systemd
, нам нужно настроить сервисный модуль Docker. Для других дистрибутивов Linux нам нужно настроить файл daemon.json
, который обычно находится в /etc/docker
.
Мы рассмотрим только первый тип настройки, поскольку большинство шагов аналогичны.
Настройка 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-соединение не зашифровано .
3. API движка Docker
Теперь, когда мы настроили удаленный доступ, давайте взглянем на API.
Мы рассмотрим только несколько интересных вариантов, но мы всегда можем проверить полную документацию , чтобы узнать больше.
Давайте получим некоторую информацию о нашем контейнере :
(alpine) $ curl -s http://172.17.0.1:2375/containers/"$(hostname)"/json | jq '.'
{
"Id": "45f13902b710f7a5f324a7d4ec7f9b934057da4887650dc8fb4391c1d98f051c",
"Created": "2020-07-29T18:10:07.261589135Z",
"Path": "/bin/sh",
"Args": [],
"State": {
"Status": "running",
...
Здесь мы используем URL-адрес /containers/{container-id}/json
для получения подробной информации о нашем контейнере.
В этом случае мы запускаем команду hostname
, чтобы получить идентификатор контейнера
.
Далее послушаем события на демоне Docker :
(alpine) $ curl -s http://172.17.0.1:2375/events | jq '.'
Теперь в другом терминале запустим контейнер hello-world :
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
Вернувшись в наш альпийский
контейнер, мы получаем кучу событий:
{
"status": "create",
"id": "abf881cbecfc0b022a3c1a6908559bb27406d0338a917fc91a77200d52a2553c",
"from": "hello-world",
"Type": "container",
"Action": "create",
...
}
{
"status": "attach",
"id": "abf881cbecfc0b022a3c1a6908559bb27406d0338a917fc91a77200d52a2553c",
"from": "hello-world",
"Type": "container",
"Action": "attach",
...
До сих пор мы занимались ненавязчивыми вещами. Время немного встряхнуться.
Давайте создадим и запустим контейнер. Во-первых, мы определяем его манифест:
(alpine) $ cat > create.json << EOF
{
"Image": "hello-world",
"Cmd": ["/hello"]
}
EOF
Теперь давайте вызовем конечную точку /containers/create
с помощью манифеста :
(alpine) $ curl -X POST -H "Content-Type: application/json" -d @create.json http://172.17.0.1:2375/containers/create
{"Id":"f96a6360ad8e36271cc75a3cff05348761569cf2f089bbb30d826bd1e2d52f59","Warnings":[]}
Затем мы используем идентификатор для запуска контейнера:
(alpine) $ curl -X POST http://172.17.0.1:2375/containers/f96a6360ad8e36271cc75a3cff05348761569cf2f089bbb30d826bd1e2d52f59/start
Наконец, мы можем изучить журналы:
(alpine) $ curl http://172.17.0.1:2375/containers/f96a6360ad8e36271cc75a3cff05348761569cf2f089bbb30d826bd1e2d52f59/logs?stdout=true --output -
Hello from Docker!
KThis message shows that your installation appears to be working correctly.
;To generate this message, Docker took the following steps:
3 1. The Docker client contacted the Docker daemon.
...
Обратите внимание, что в начале каждой строки появляются странные символы. Это происходит потому, что поток, по которому передаются журналы, мультиплексируется , чтобы различать stderr
и stdout
.
В результате вывод нуждается в дальнейшей обработке.
Мы можем избежать этого, просто включив опцию TTY
при создании контейнера:
(alpine) $ cat create.json
{
"Tty":true,
"Image": "hello-world",
"Cmd": ["/hello"]
}
4. Вывод
В этом руководстве мы узнали, как использовать удаленный API Docker Engine.
Мы начали с настройки удаленного доступа либо через сокет UNIX, либо через TCP, а затем пошли дальше, показав, как мы можем использовать удаленный API.