Управление контейнерами Docker
Услуги установки настройки и поддержки контейнеров docker, [email protected]
Контейнеры докер могут находится в нескольких состояниях, перечислим их:
- Исполняется>(running) >– контейнер работает. В выводе docker ps увидите статус «Up» и время, в течение которого он исполняется.
- Создан> (created) > – контейнер создан, но в настоящий момент не выполняется. Такое состояние будет у контейнера после команды docker create.
- Завершил исполнение> (exited) > – контейнер завершил исполнение.
В отличие от bash, команда busybox в образе, из которого запускался контейнер, присутствует. Успешно завершив выполнение команды busybox, контейнер завершил работу. Тот же результат (остановленный контейнер) мы получаем, если для работающего контейнера дадим команду docker stop. Второй способ остановить контейнер – docker kill. По умолчанию эта команда отправляет сигнал SIGKILL, однако при помощи опции -s можно отправить контейнеру и другие стандартные сигналы. Заново запустить остановленный контейнер можно командой docker start. В общем случае остановленный контейнер от созданного отличается тем, что первый уже когда-то запускался и на файловой системе остановленного контейнера могут быть уже произведены какие-то изменения. Файловая же система созданного контейнера аналогична образу, из которого создавался контейнер.
- Поставлен на паузу> (paused) > – процесс контейнера остановлен, но существует. Поставить контейнер на паузу можно при помощи команды docker pause. При этом Docker использует контрольную группу freezer для того, чтобы «заморозить» процессы в контейнере. Проведем простой эксперимент. Запустим контейнер и убедимся, что он работает:
1 2 |
$ docker run -it --name ubuntu ubuntu /bin/bash root@9b0117ecb82d:/# |
Контейнер с идентификатором 9b0117ecb82d запущен. Откроем на узле вторую консоль и проверим статус контейнера:
1 2 3 4 5 |
$ docker ps CONTAINER ID IMAGE COMMAND 9b0117ecb82d ubuntu "/bin/bash" CREATED STATUS PORTS NAMES About a minute ago Up About a minute ubuntu |
Теперь поставим его на паузу:
1 2 3 4 5 6 7 |
$ docker pause ubuntu ubuntu $ docker ps CONTAINER ID IMAGE COMMAND 9b0117ecb82d ubuntu "/bin/bash" CREATED STATUS PORTS NAMES 2 minutes ago Up 2 minutes ubuntu |
Убедимся, что виртуальная файловая система для контроллера freezer смонтирована:
1 2 |
# mount | grep freezer cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) |
Посмотрим содержимое поддиректории system.slice:
1 2 3 4 5 6 |
# ls /sys/fs/cgroup/freezer/system.slice/ cgroup.clone_children cgroup.procs freezer.parent_freezing freezer.state tasks cgroup.event_control docker-9b0117ecb82d6b792c42479d868f 9c2b33409f7887cc4b419a02dde676637955.scope freezer.self_ freezing notify_on_release |
Мы видим виртуальную поддиректорию docker-*, соответствующую контейнеру 9b0117ecb82d для контроллера freezer. Очевидно, другие контейнеры не запущены. Проверим, что в настоящий момент контейнер «разморожен» (THAWED):
1 2 3 4 |
# cat /sys/fs/cgroup/freezer/system.slice/ ↵ docker-9b0117ecb82d6b792c42479d868f9c2b33409f7887 ↵ cc4b419a02dde676637955.scope/freezer.state THAWED |
Теперь поставим контейнер на паузу и убедимся, что это реализовано средствами контрольных групп:
1 2 3 4 5 6 7 8 9 10 |
$ docker pause ubuntu $ docker ps CONTAINER ID IMAGE COMMAND 9b0117ecb82d ubuntu "/bin/bash" CREATED STATUS PORTS NAMES 16 minutes ago Up 16 minutes (Paused) ubuntu # cat /sys/fs/cgroup/freezer/system.slice/ ↵ docker-9b0117ecb82d6b792c42479d868f9c2b33409f7887 ↵ cc4b419a02dde676637955.scope/freezer.state FROZEN |
В выводе команды ps процессы контейнера будут отображаться с состоянием disk sleep:
1 2 3 4 5 |
$ ps aux | grep Ds root 4659 0.0 0.1 18236 1892 pts/1 Ds+ 15:18 0:00 /bin/bash andrey 4785 0.0 0.0 112648 964 pts/0 R+ 15:37 0:00 grep --color=auto Ds |
- Рестартует (restarting) > – контейнер рестартует. Остановленный контейнер можно рестартовать. Рестарт означает, что контейнер заново запустится с теми же идентификатором и настройками сети, что были до остановки. Однако это будет уже другой процесс с другим PID.
- «Умер» (dead) > – контейнер перестал функционировать вследствие сбоя.
Подробную схему, описывающую состояния контейнеров, можно найти в официальной документации.
На рис. 1 представлен пример перехода контейнера из состояния в состояние.
Обратите внимание, что на рисунке показано то, что после рестарта контейнера создается новый процесс. Изначальный процесс контейнера завершил работу после команды docker stop.
Последняя команда на рисунке – docker rm – используется для удаления контейнера. После удаления самого контейнера можно удалить и образ, из которого он был запущен. Запущенные контейнеры удалять нельзя.
Если необходимо произвести действия сразу со всеми контейнерами или образами, необходимо в командах docker images и docker ps добавлять -q. При этом будут выведены только идентификаторы контейнеров или образов:
1 2 3 4 5 6 7 8 9 |
$ docker ps -aq 9b0117ecb82d 00e5695fa62f f568491c665c $ docker images -q 0766572b4bac 104bec311bcd 594dc21de8de |
Далее можно подставить вывод этих команд в команды, манипулирующие соответственно контейнерами и образами. В следующем примере удаляются все контейнеры, а затем все образы на узле при помощи команды rmi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ docker rm $(docker ps -aq) 9b0117ecb82d 00e5695fa62f f568491c665c $ docker rmi $(docker images -q) Untagged: docker.io/alpine:latest Untagged: docker.io/alpine@sha256:a4104316f43c73146f1c0af4747 d88047a808e58238bcad6506a7fbbf3b30b90 Deleted: sha256:0766572b4bacfaee9a8eb6bae79e6f6dbcdfac0805c7c 6ec8b6c2c0ef097317a Deleted: sha256:7cbcbac42c44c6c38559e5df3a494f44987333c8023a4 0fec48df2fce1fc146b ... |
Обмен данными с контейнером по сети
Мы научились запускать контейнер с демоном, работающим внутри. Но толку от такого контейнера немного. Нужно так- же уметь подключаться к службе по внешней сети. Один из возможных способов – это использование проброса пор- тов из контейнеров на хост. Давайте поэкспериментируем с более наглядным примером – веб-сервером Apache. Стра- ничка официального образа на Docker Hub. Из описания можно узнать, что корневая директория для веб-сервера /usr/local/apache2/htdocs/.
Загрузим образ и запустим с новой для нас опцией -p:
1 2 3 4 5 |
$ docker run -d -p 8888:80 --name my-httpd httpd Unable to find image 'httpd:latest' locally Trying to pull repository docker.io/library/httpd ... latest: Pulling from docker.io/library/httpd ... |
Данная опция позволяет перенаправить TCP-порт 80 контейнера на 8888-й порт хоста. Данное перенаправление будет также отображено в выводе команды docker ps:
1 2 3 4 5 6 7 |
$ docker ps CONTAINER ID IMAGE COMMAND 92722dc668b8 httpd "httpd-foreground" CREATED STATUS PORTS 7 seconds ago Up 6 seconds 0.0.0.0:8888->80/tcp NAMES my-httpd |
Организуется это при помощи правил брандмауэра:
1 2 3 4 5 6 |
# iptables -L DOCKER -t nat Chain DOCKER (2 references) target prot opt source destination RETURN all -- anywhere anywhere DNAT tcp -- anywhere anywhere tcp dpt:8888 to:172.17.0.2:80 |
В нашем примере у контейнера IP-адрес 172.17.0.2. Подробнее об организации сети контейнеров мы поговорим дальше, а сейчас протестируем работоспособность веб-сервера. Обратимся на порт 8888 хоста либо по его IP-адресу (в примере 10.0.2.7), либо на localhost:
1 2 |
$ curl http://10.0.2.7:8888 <html><body><h1>It works!</h1></body></html> |
Отлично! Контейнер доступен. Попробуем заменить стандартное сообщение своим, изменив index.html:
1 2 3 4 5 6 |
$ docker exec -it my-httpd bash root@92722dc668b8:/usr/local/apache2# echo "My Apache ↵ server" > /usr/local/apache2/htdocs/index.html root@92722dc668b8:/usr/local/apache2# exit $ curl http://10.0.2.7:8888 My Apache server |
В качестве альтернативы можно возложить ответственность за выбор порта на сам Docker, отдав команду docker run с ключом -P:
1 2 |
$ docker run -d -P --name my-httpd httpd 46f8a898d6c7f26d03c0c6df97c53e6a459c45499583277aa5b32df85038d77b |
В этом случае узнать порт, который был выделен контейнеру, можно командой docker port:
1 2 |
$ docker port my-httpd 80/tcp -> 0.0.0.0:32768 |
В данном примере порт 32768 был выбран случайным образом Docker, а порт 80 был определен автором образа. Как это задается, мы изучим, когда будем разбирать описание образов при помощи файла Dockerfile.
Просмотр информации о контейнере Познакомимся с тем, как получить информацию о контейнере. Посмотрим на вывод команды docker inspect. Вывод команды в формате JSON отображен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
$ docker inspect my-httpd2 | nl 1 [ 2 { 3 "Id": "ca5600147d04bf6508449b90afdf4e68ba399ab3565 fbc04fa0e6b5532836b66", 4 "Created": "2017-01-01T17:24:01.36351199Z", 5 "Path": "httpd-foreground", 6 "Args": [], 7 "State": { 8 "Status": "running", 9 "Running": true, 10 "Paused": false, 11 "Restarting": false, 12 "OOMKilled": false, 13 "Dead": false, 14 "Pid": 1425, 15 "ExitCode": 0, 16 "Error": "", 17 "StartedAt": "2017-01-01T17:24:01.861572436Z", 18 "FinishedAt": "0001-01-01T00:00:00Z" 19 }, 20 "Image": "sha256:0c4363ef5f1221bd275f00b8b8a97344 a5829a06f16b98354a665a6c611e7cf1", 21 "ResolvConfPath": "/var/lib/docker/containers/ ca5600147d04bf6508449b90afdf4 e68ba399ab3565fbc04fa0e6 b5532836b66/resolv.conf", 22 "HostnamePath": "/var/lib/docker/containers/ ca5600147d04bf6508449b90afdf4e68 ba399ab3565fbc04fa0e6b5532836b66/ hostname", 23 "HostsPath": "/var/lib/docker/containers/ ca5600147d04bf6508449b90afdf4e68 ba399ab3565fbc04fa0e6b5532836b66/ hosts", 24 "LogPath": "", 25 "Name": "/my-httpd2", 26 "RestartCount": 0, 27 "Driver": "devicemapper", 28 "MountLabel": "system_u:object_r: svirt_sandbox_file_t:s0:c726,c995", 29 "ProcessLabel": "system_u:system_r: svirt_lxc_net_t:s0:c726,c995", 30 "AppArmorProfile": "", 31 "ExecIDs": null, 32 "HostConfig": { 33 "Binds": [ 34 "/home/andrey/mywww:/usr/local/apache2/ htdocs/" 35 ], 36 "ContainerIDFile": "", 37 "LogConfig": { 38 "Type": "journald", 39 "Config": {} 40 }, 41 "NetworkMode": "default", 42 "PortBindings": { 43 "80/tcp": [ 44 { 45 "HostIp": "", 46 "HostPort": "8889" 47 } 48 ] 49 }, ... 117 "Config": { 118 "Hostname": "ca5600147d04", 119 "Domainname": "", 120 "User": "", 121 "AttachStdin": false, 122 "AttachStdout": false, 123 "AttachStderr": false, 124 "ExposedPorts": { 125 "80/tcp": {} 126 }, 127 "Tty": false, 128 "OpenStdin": false, 129 "StdinOnce": false, 130 "Env": [ 131 "PATH=/usr/local/apache2/bin:/usr/local/ sbin:/usr/local/bin:/usr/sbin:/usr/ bin:/sbin:/bin", 132 "HTTPD_PREFIX=/usr/local/apache2", 133 "NGHTTP2_VERSION=1.17.0-1", 134 "HTTPD_VERSION=2.4.25", 135 "HTTPD_SHA1=bd6d138c31c109297da2346c6e7 b93b9283993d2", 136 "HTTPD_BZ2_URL=https://www.apache.org/dyn/ closer.cgi?action=download\ u0026filename=httpd/ httpd-2.4.25.tar.bz2", 137 "HTTPD_ASC_URL=https://www.apache.org/ dist/httpd/httpd-2.4.25. tar.bz2.asc" 138 ], 139 "Cmd": [ 140 "httpd-foreground" 141 ], ... 163 "SandboxKey": "/var/run/docker/netns/ 8f0e7a4a7ea0", 164 "SecondaryIPAddresses": null, 165 "SecondaryIPv6Addresses": null, 166 "EndpointID": "daa65657ac97f7c7d4691ae4e101 f2057f85b304a05c6556491b7be889 b64b99", 167 "Gateway": "172.17.0.1", 168 "GlobalIPv6Address": "", 169 "GlobalIPv6PrefixLen": 0, 170 "IPAddress": "172.17.0.2", ... |
- Опишем ряд полученных параметров:
>>строка>3> – идентификатор контейнера; - >строка>4> – время и дата создания контейнера;
- >>строки>7-18> – информация о статусе контейнера. Текущий статус running и PID на хосте – 1425;
- >>строка> 20> – идентификатор образа, из которого запущен контейнер. Обратите внимание, что тут указан полный ID. При выводе команды docker images идентификаторы обрезаются до двенадцати символов. Используйте команду docker images —digests для вывода полного ID;
- >>строки>21-23> – расположение на файловой системе хоста файлов resolv.conf, hostname и hosts контейнера;
- >>строки>28-29> – метка SELinux файловой системы и контекст процесса;
- >>строка>34> – целевая и монтируемая с хоста директории;
- >>строки>42-49> – описание перенаправления портов;
- >>строки>124-126> – объявленные доступными снаружи порты. В данном случае только один – http;
- >>строки>130-138> – переменные окружения;
- >>строки> 139-141 > – определение запускаемой команды в контейнере;
- >>строка>170> – IP-адрес контейнера.
Для того чтобы вывести только часть информации, можно воспользоваться шаблоном языка Go при помощи опции -f. Например, для того, чтобы получить IP-адрес контейнера, можно воспользоваться командой:
1 2 |
$ docker inspect -f '{{ .NetworkSettings.IPAddress}}' my-httpd2 172.17.0.2 |
Подключение к контейнеру постоянного хранилища
Все работает, однако, если удалите контейнер, изменения в index.html будут потеряны. На странице с описанием httpd на Dockrer Hub приведен пример использования контейнера:
1 |
$ docker run -dit --name my-apache-app -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4 |
Из нового тут опция -v. Эта опция позволяет смонтировать директорию хоста внутри контейнера. Целевая директория в примере /usr/local/apache2/htdocs, а монтируемая – текущая рабочая, путь которой содержится в переменной окружения $PWD. Если в команде опустить целевую директорию, то Docker создаст ее в /var/lib/docker/volumes/.
Используем предложенный синтаксис, создав index.html в специально выделенной директории:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ mkdir mywww $ echo "My Apache server - 2" > mywww/index.html $ docker run -d -p 8889:80 -v /home/andrey/mywww: ↵ /usr/local/apache2/htdocs/ --name my-httpd2 httpd $ curl http://10.0.2.7:8889 <html><head> <title>403 Forbidden</title> </head><body> <h1>Forbidden</h1> <p>You don't have permission to access on this server.<br /> </p> </body></html> |
Похоже, что у нас проблемы с разрешениями. Дело в том, что в CentOS, а также в ряде других дистрибутивов, по умолчанию используется система мандатного контроля доступа SELinux. Для того чтобы контейнер или виртуальная машина получила доступ к файловой системе хоста, нужно задать правильный тип для соответствующих файлов и директорий:
1 |
# chcon -R -t svirt_sandbox_file_t ~andrey/mywww/ |
Повторяем эксперимент:
1 2 3 4 5 |
$ docker stop my-httpd2 $ docker rm my-httpd2 $ docker run -itd -p 8889:80 -v /home/andrey/mywww: /usr/local/apache2/htdocs/ --name my-httpd2 httpd $ curl http://10.0.2.7:8889 My Apache server - 2 |
На этот раз веб-сервер успешно отобразил наш файл index.html. Когда директория контейнера, в которую монтируется директория хоста, существует, ее содержимое заменяется, но не удаляется.
Полезным приемом может оказаться использование выделенных контейнеров только под хранение данных. Идея заключается в том, что у нас будет остановленный контейнер, тома которого будут смонтированы в другой контейнер. При этом работающий контейнер с приложением можно удалять и пересоздавать столько раз, сколько необходимо.
Проиллюстрируем на примере. Создадим контейнер для хранения данных под именем my-data, смонтировав в директорию контейнера /usr/local/apache2/htdocs/ директорию хоста с файлом index.html:
1 2 3 4 5 |
$ docker run -v /home/andrey/mywww:/usr/local/apache2/htdocs/ --name my-data httpd echo "Data container" Data container $ docker ps -a 365ce6faaf9a httpd "echo 'Data container" 10 seconds ago Exited (0) 9 seconds ago my-data |
Контейнер my-data остановлен. При помощи опции —volumes-from во время запуска другого контейнера можно смонтировать тома из первого. Проверим это при помощи тестового контейнера test:
1 2 |
$ docker run --volumes-from my-data --name test httpd cat /usr/local/apache2/htdocs/index.html My Apache server - 2 |
Теперь тестовый контейнер нам не нужен. Удалим его и запустим контейнер с веб-сервером, который будет
использовать том с контейнера my-data:
1 2 3 4 5 6 |
$ docker rm test test $ docker run -d -p 8889:80 --volumes-from my-data ↵ --name my-httpd3 httpd d4a7e958731f1110e45043d2a2e1480ffe2d820623442b6e0e80f24c18a7 d93c |
1 2 |
$ curl http://10.0.2.7:8889 My Apache server - 2 |
Ну и убедимся при помощи docker inspect, из какого контейнера используются тома:
1 2 |
$ docker inspect -f '{{ .HostConfig.VolumesFrom}}' my-httpd3 [my-data] |
Если теперь удалить контейнер my-data, используемый контейнером my-httpd3 том c index.html удален не будет. Удалить тома вместе с контейнером можно, используя опцию -v команды docker rm, и только если тома не используются другими контейнерами. При запуске контейнера может быть полезной опция —rm.
Контейнер, запущенный с такой опцией, удаляется сразу после остановки. Покажем на примере контейнера, предназначенного исключительно для архивирования данных. Создадим директорию для резервного хранения данных:
1 2 |
$ mkdir wwwbackup $ chcon -R -t svirt_sandbox_file_t ~andrey/wwwbackup |
Теперь запустим контейнер, единственная цель которого – сохранить данные контейнера в директории хоста.
В нашем случае данные – это файл index.html:
1 2 3 4 5 |
$ docker run --rm --volumes-from my-httpd3 -v ↵ /home/andrey/wwwbackup:/backup httpd cp ↵ /usr/local/apache2/htdocs/index.html /backup $ ls wwwbackup/ index.html |
Как мы видим, контейнер сделал свое дело.