Перейти к основному содержимому

Получение контейнера Docker из API Docker Engine

· 7 мин. чтения

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.