Услуги установки настройки и поддержки контейнеров docker, [email protected]
Контейнеры докер могут находится в нескольких состояниях, перечислим их:n
- Исполняется>(running) >– контейнер работает. В выводе docker ps увидите статус «Up» и время, в течение которого он исполняется.
- Создан> (created) > – контейнер создан, но в настоящий момент не выполняется. Такое состояние будет у контейнера после команды docker create.
- Завершил исполнение> (exited) > – контейнер завершил исполнение.
В отличие от bash, команда busybox в образе, из которого запускался контейнер, присутствует. Успешно завершив выполнение команды busybox, контейнер завершил работу. Тот же результат (остановленный контейнер) мы получаем, если для работающего контейнера дадим команду docker stop. Второй способ остановить контейнер – docker kill. По умолчанию эта команда отправляет сигнал SIGKILL, однако при помощи опции -s можно отправить контейнеру и другие стандартные сигналы. Заново запустить остановленный контейнер можно командой docker start. В общем случае остановленный контейнер от созданного отличается тем, что первый уже когда-то запускался и на файловой системе остановленного контейнера могут быть уже произведены какие-то изменения. Файловая же система созданного контейнера аналогична образу, из которого создавался контейнер.n
- Поставлен на паузу> (paused) > – процесс контейнера остановлен, но существует. Поставить контейнер на паузу можно при помощи команды docker pause. При этом Docker использует контрольную группу freezer для того, чтобы «заморозить» процессы в контейнере. Проведем простой эксперимент. Запустим контейнер и убедимся, что он работает:
$ docker run -it --name ubuntu ubuntu /bin/bashnroot@9b0117ecb82d:/#
Контейнер с идентификатором 9b0117ecb82d запущен. Откроем на узле вторую консоль и проверим статус контейнера:n
$ docker psnCONTAINER ID IMAGE COMMANDn9b0117ecb82d ubuntu "/bin/bash"nCREATED STATUS PORTS NAMESnAbout a minute ago Up About a minute ubuntu
Теперь поставим его на паузу:n
$ docker pause ubuntunubuntun$ docker psnCONTAINER ID IMAGE COMMANDn9b0117ecb82d ubuntu "/bin/bash"nCREATED STATUS PORTS NAMESn2 minutes ago Up 2 minutes ubuntu
Убедимся, что виртуальная файловая система для контроллера freezer смонтирована:n
# mount | grep freezerncgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
Посмотрим содержимое поддиректории system.slice:n
# ls /sys/fs/cgroup/freezer/system.slice/ncgroup.clone_children cgroup.procs freezer.parent_freezingnfreezer.state tasksncgroup.event_control docker-9b0117ecb82d6b792c42479d868fn9c2b33409f7887cc4b419a02dde676637955.scope freezer.self_nfreezing notify_on_release
Мы видим виртуальную поддиректорию docker-*, соответствующую контейнеру 9b0117ecb82d для контроллера freezer. Очевидно, другие контейнеры не запущены. Проверим, что в настоящий момент контейнер «разморожен» (THAWED):n
# cat /sys/fs/cgroup/freezer/system.slice/ ↵ndocker-9b0117ecb82d6b792c42479d868f9c2b33409f7887 ↵ncc4b419a02dde676637955.scope/freezer.statenTHAWED
Теперь поставим контейнер на паузу и убедимся, что это реализовано средствами контрольных групп:n
$ docker pause ubuntun$ docker psnCONTAINER ID IMAGE COMMANDn9b0117ecb82d ubuntu "/bin/bash"nCREATED STATUS PORTS NAMESn16 minutes ago Up 16 minutes (Paused) ubuntun# cat /sys/fs/cgroup/freezer/system.slice/ ↵ndocker-9b0117ecb82d6b792c42479d868f9c2b33409f7887 ↵ncc4b419a02dde676637955.scope/freezer.statenFROZEN
В выводе команды ps процессы контейнера будут отображаться с состоянием disk sleep:n
$ ps aux | grep Dsnroot 4659 0.0 0.1 18236 1892 pts/1 Ds+ 15:18n0:00 /bin/bashnandrey 4785 0.0 0.0 112648 964 pts/0 R+ 15:37n0:00 grep --color=auto Ds
- Рестартует (restarting) > – контейнер рестартует. Остановленный контейнер можно рестартовать. Рестарт означает, что контейнер заново запустится с теми же идентификатором и настройками сети, что были до остановки. Однако это будет уже другой процесс с другим PID.
- «Умер» (dead) > – контейнер перестал функционировать вследствие сбоя.
Подробную схему, описывающую состояния контейнеров, можно найти в официальной документации.nnНа рис. 1 представлен пример перехода контейнера из состояния в состояние.nnОбратите внимание, что на рисунке показано то, что после рестарта контейнера создается новый процесс. Изначальный процесс контейнера завершил работу после команды docker stop.nnПоследняя команда на рисунке – docker rm – используется для удаления контейнера. После удаления самого контейнера можно удалить и образ, из которого он был запущен. Запущенные контейнеры удалять нельзя.nnЕсли необходимо произвести действия сразу со всеми контейнерами или образами, необходимо в командах docker images и docker ps добавлять -q. При этом будут выведены только идентификаторы контейнеров или образов:n
$ docker ps -aqn9b0117ecb82dn00e5695fa62fnf568491c665cnn$ docker images -qn0766572b4bacn104bec311bcdn594dc21de8de
Далее можно подставить вывод этих команд в команды, манипулирующие соответственно контейнерами и образами. В следующем примере удаляются все контейнеры, а затем все образы на узле при помощи команды rmi:n
$ docker rm $(docker ps -aq)n9b0117ecb82dn00e5695fa62fnf568491c665cnn$ docker rmi $(docker images -q)nUntagged: docker.io/alpine:latestnUntagged: docker.io/alpine@sha256:a4104316f43c73146f1c0af4747nd88047a808e58238bcad6506a7fbbf3b30b90nDeleted: sha256:0766572b4bacfaee9a8eb6bae79e6f6dbcdfac0805c7cn6ec8b6c2c0ef097317anDeleted: sha256:7cbcbac42c44c6c38559e5df3a494f44987333c8023a4n0fec48df2fce1fc146bn...
Обмен данными с контейнером по сети
Мы научились запускать контейнер с демоном, работающим внутри. Но толку от такого контейнера немного. Нужно так- же уметь подключаться к службе по внешней сети. Один из возможных способов – это использование проброса пор- тов из контейнеров на хост. Давайте поэкспериментируем с более наглядным примером – веб-сервером Apache. Стра- ничка официального образа на Docker Hub. Из описания можно узнать, что корневая директория для веб-сервера /usr/local/apache2/htdocs/.nnЗагрузим образ и запустим с новой для нас опцией -p:n
$ docker run -d -p 8888:80 --name my-httpd httpdnUnable to find image 'httpd:latest' locallynTrying to pull repository docker.io/library/httpd ...nlatest: Pulling from docker.io/library/httpdn...
Данная опция позволяет перенаправить TCP-порт 80 контейнера на 8888-й порт хоста. Данное перенаправление будет также отображено в выводе команды docker ps:n
$ docker psnCONTAINER ID IMAGE COMMANDn92722dc668b8 httpd "httpd-foreground"nCREATED STATUS PORTSn7 seconds ago Up 6 seconds 0.0.0.0:8888->80/tcpnNAMESnmy-httpd
Организуется это при помощи правил брандмауэра:n
# iptables -L DOCKER -t natnChain DOCKER (2 references)ntarget prot opt source destinationnRETURN all -- anywhere anywherenDNAT tcp -- anywhere anywherentcp dpt:8888 to:172.17.0.2:80
В нашем примере у контейнера IP-адрес 172.17.0.2. Подробнее об организации сети контейнеров мы поговорим дальше, а сейчас протестируем работоспособность веб-сервера. Обратимся на порт 8888 хоста либо по его IP-адресу (в примере 10.0.2.7), либо на localhost:n
$ curl http://10.0.2.7:8888n<html><body><h1>It works!</h1></body></html>
Отлично! Контейнер доступен. Попробуем заменить стандартное сообщение своим, изменив index.html:n
$ docker exec -it my-httpd bashnroot@92722dc668b8:/usr/local/apache2# echo "My Apache ↵nserver" > /usr/local/apache2/htdocs/index.htmlnroot@92722dc668b8:/usr/local/apache2# exitn$ curl http://10.0.2.7:8888nMy Apache server
В качестве альтернативы можно возложить ответственность за выбор порта на сам Docker, отдав команду docker run с ключом -P:n
$ docker run -d -P --name my-httpd httpdn46f8a898d6c7f26d03c0c6df97c53e6a459c45499583277aa5b32df85038d77b
В этом случае узнать порт, который был выделен контейнеру, можно командой docker port:n
$ docker port my-httpdn80/tcp -> 0.0.0.0:32768
В данном примере порт 32768 был выбран случайным образом Docker, а порт 80 был определен автором образа. Как это задается, мы изучим, когда будем разбирать описание образов при помощи файла Dockerfile.nПросмотр информации о контейнере Познакомимся с тем, как получить информацию о контейнере. Посмотрим на вывод команды docker inspect. Вывод команды в формате JSON отображен ниже:n
$ docker inspect my-httpd2 | nln1 [n2 {n3 "Id": "ca5600147d04bf6508449b90afdf4e68ba399ab3565nfbc04fa0e6b5532836b66",n4 "Created": "2017-01-01T17:24:01.36351199Z",n5 "Path": "httpd-foreground",n6 "Args": [],n7 "State": {n8 "Status": "running",n9 "Running": true,n10 "Paused": false,n11 "Restarting": false,n12 "OOMKilled": false,n13 "Dead": false,n14 "Pid": 1425,n15 "ExitCode": 0,n16 "Error": "",n17 "StartedAt": "2017-01-01T17:24:01.861572436Z",n18 "FinishedAt": "0001-01-01T00:00:00Z"n19 },n20 "Image": "sha256:0c4363ef5f1221bd275f00b8b8a97344na5829a06f16b98354a665a6c611e7cf1",n21 "ResolvConfPath": "/var/lib/docker/containers/nca5600147d04bf6508449b90afdf4ne68ba399ab3565fbc04fa0e6nb5532836b66/resolv.conf",n22 "HostnamePath": "/var/lib/docker/containers/nca5600147d04bf6508449b90afdf4e68nba399ab3565fbc04fa0e6b5532836b66/nhostname",n23 "HostsPath": "/var/lib/docker/containers/nca5600147d04bf6508449b90afdf4e68nba399ab3565fbc04fa0e6b5532836b66/nhosts",n24 "LogPath": "",n25 "Name": "/my-httpd2",n26 "RestartCount": 0,n27 "Driver": "devicemapper",n28 "MountLabel": "system_u:object_r:nsvirt_sandbox_file_t:s0:c726,c995",n29 "ProcessLabel": "system_u:system_r:nsvirt_lxc_net_t:s0:c726,c995",n30 "AppArmorProfile": "",n31 "ExecIDs": null,n32 "HostConfig": {n33 "Binds": [n34 "/home/andrey/mywww:/usr/local/apache2/nhtdocs/"n35 ],n36 "ContainerIDFile": "",n37 "LogConfig": {n38 "Type": "journald",n39 "Config": {}n40 },n41 "NetworkMode": "default",n42 "PortBindings": {n43 "80/tcp": [n44 {n45 "HostIp": "",n46 "HostPort": "8889"n47 }n48 ]n49 },n...n117 "Config": {n118 "Hostname": "ca5600147d04",n119 "Domainname": "",n120 "User": "",n121 "AttachStdin": false,n122 "AttachStdout": false,n123 "AttachStderr": false,n124 "ExposedPorts": {n125 "80/tcp": {}n126 },n127 "Tty": false,n128 "OpenStdin": false,n129 "StdinOnce": false,n130 "Env": [n131 "PATH=/usr/local/apache2/bin:/usr/local/nsbin:/usr/local/bin:/usr/sbin:/usr/nbin:/sbin:/bin",n132 "HTTPD_PREFIX=/usr/local/apache2",n133 "NGHTTP2_VERSION=1.17.0-1",n134 "HTTPD_VERSION=2.4.25",n135 "HTTPD_SHA1=bd6d138c31c109297da2346c6e7nb93b9283993d2",n136 "HTTPD_BZ2_URL=https://www.apache.org/dyn/ncloser.cgi?action=download\nu0026filename=httpd/nhttpd-2.4.25.tar.bz2",n137 "HTTPD_ASC_URL=https://www.apache.org/ndist/httpd/httpd-2.4.25.ntar.bz2.asc"n138 ],n139 "Cmd": [n140 "httpd-foreground"n141 ],n...n163 "SandboxKey": "/var/run/docker/netns/n8f0e7a4a7ea0",n164 "SecondaryIPAddresses": null,n165 "SecondaryIPv6Addresses": null,n166 "EndpointID": "daa65657ac97f7c7d4691ae4e101nf2057f85b304a05c6556491b7be889nb64b99",n167 "Gateway": "172.17.0.1",n168 "GlobalIPv6Address": "",n169 "GlobalIPv6PrefixLen": 0,n170 "IPAddress": "172.17.0.2",n...
- Опишем ряд полученных параметров:n>>строка>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-адрес контейнера, можно воспользоваться командой:n
$ docker inspect -f '{{ .NetworkSettings.IPAddress}}' my-httpd2n172.17.0.2
Подключение к контейнеру постоянного хранилища
Все работает, однако, если удалите контейнер, изменения в index.html будут потеряны. На странице с описанием httpd на Dockrer Hub приведен пример использования контейнера:n
$ 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/.nИспользуем предложенный синтаксис, создав index.html в специально выделенной директории:n
$ mkdir mywwwn$ echo "My Apache server - 2" > mywww/index.htmln$ docker run -d -p 8889:80 -v /home/andrey/mywww: ↵n/usr/local/apache2/htdocs/ --name my-httpd2 httpdn$ curl http://10.0.2.7:8889n<html><head>n<title>403 Forbidden</title>n</head><body>n<h1>Forbidden</h1>n<p>You don't have permission to access on this server.<br />n</p>n</body></html>
Похоже, что у нас проблемы с разрешениями. Дело в том, что в CentOS, а также в ряде других дистрибутивов, по умолчанию используется система мандатного контроля доступа SELinux. Для того чтобы контейнер или виртуальная машина получила доступ к файловой системе хоста, нужно задать правильный тип для соответствующих файлов и директорий:n
# chcon -R -t svirt_sandbox_file_t ~andrey/mywww/
Повторяем эксперимент:n
$ docker stop my-httpd2n$ docker rm my-httpd2n$ docker run -itd -p 8889:80 -v /home/andrey/mywww: /usr/local/apache2/htdocs/ --name my-httpd2 httpdn$ curl http://10.0.2.7:8889nMy Apache server - 2
На этот раз веб-сервер успешно отобразил наш файл index.html. Когда директория контейнера, в которую монтируется директория хоста, существует, ее содержимое заменяется, но не удаляется.nnПолезным приемом может оказаться использование выделенных контейнеров только под хранение данных. Идея заключается в том, что у нас будет остановленный контейнер, тома которого будут смонтированы в другой контейнер. При этом работающий контейнер с приложением можно удалять и пересоздавать столько раз, сколько необходимо.nnПроиллюстрируем на примере. Создадим контейнер для хранения данных под именем my-data, смонтировав в директорию контейнера /usr/local/apache2/htdocs/ директорию хоста с файлом index.html:n
$ docker run -v /home/andrey/mywww:/usr/local/apache2/htdocs/ --name my-data httpd echo "Data container"nData containern$ docker ps -an365ce6faaf9a httpd "echo 'Data container" 10 seconds agonExited (0) 9 seconds ago my-data
Контейнер my-data остановлен. При помощи опции —volumes-from во время запуска другого контейнера можно смонтировать тома из первого. Проверим это при помощи тестового контейнера test:n
$ docker run --volumes-from my-data --name test httpd cat /usr/local/apache2/htdocs/index.htmlnMy Apache server - 2
Теперь тестовый контейнер нам не нужен. Удалим его и запустим контейнер с веб-сервером, который будетnиспользовать том с контейнера my-data:n
$ docker rm testntestn$ docker run -d -p 8889:80 --volumes-from my-data ↵n--name my-httpd3 httpdnd4a7e958731f1110e45043d2a2e1480ffe2d820623442b6e0e80f24c18a7nd93c
$ curl http://10.0.2.7:8889nMy Apache server - 2
Ну и убедимся при помощи docker inspect, из какого контейнера используются тома:n
$ docker inspect -f '{{ .HostConfig.VolumesFrom}}' my-httpd3n[my-data]
Если теперь удалить контейнер my-data, используемый контейнером my-httpd3 том c index.html удален не будет. Удалить тома вместе с контейнером можно, используя опцию -v команды docker rm, и только если тома не используются другими контейнерами. При запуске контейнера может быть полезной опция —rm.nКонтейнер, запущенный с такой опцией, удаляется сразу после остановки. Покажем на примере контейнера, предназначенного исключительно для архивирования данных. Создадим директорию для резервного хранения данных:n
$ mkdir wwwbackupn$ chcon -R -t svirt_sandbox_file_t ~andrey/wwwbackup
Теперь запустим контейнер, единственная цель которого – сохранить данные контейнера в директории хоста.nВ нашем случае данные – это файл index.html:n
$ docker run --rm --volumes-from my-httpd3 -v ↵n/home/andrey/wwwbackup:/backup httpd cp ↵n/usr/local/apache2/htdocs/index.html /backupn$ ls wwwbackup/nindex.html
Как мы видим, контейнер сделал свое дело.