Связаться по:
vkarabedyants Telegram Viber
+7 (499) 350-10-69

Блог о системном администрировании серверов и сайтов

Установка, настройка программного обеспечения Linux, Windows операционных систем

Управление контейнерами 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 для того, чтобы «заморозить» процессы в контейнере. Проведем простой эксперимент. Запустим контейнер и убедимся, что он работает:
$ docker run -it --name ubuntu ubuntu /bin/bash
[email protected]:/#

Контейнер с идентификатором 9b0117ecb82d запущен. Откроем на узле вторую консоль и проверим статус контейнера:

$ docker ps
CONTAINER ID IMAGE COMMAND
9b0117ecb82d ubuntu "/bin/bash"
CREATED STATUS PORTS NAMES
About a minute ago Up About a minute ubuntu

Теперь поставим его на паузу:

$ 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 смонтирована:

# mount | grep freezer
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)

Посмотрим содержимое поддиректории system.slice:

# 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):

# cat /sys/fs/cgroup/freezer/system.slice/ ↵
docker-9b0117ecb82d6b792c42479d868f9c2b33409f7887 ↵
cc4b419a02dde676637955.scope/freezer.state
THAWED

Теперь поставим контейнер на паузу и убедимся, что это реализовано средствами контрольных групп:

$ 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:

$ 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. При этом будут выведены только идентификаторы контейнеров или образов:

$ docker ps -aq
9b0117ecb82d
00e5695fa62f
f568491c665c

$ docker images -q
0766572b4bac
104bec311bcd
594dc21de8de

Далее можно подставить вывод этих команд в команды, манипулирующие соответственно контейнерами и образами. В следующем примере удаляются все контейнеры, а затем все образы на узле при помощи команды rmi:

$ docker rm $(docker ps -aq)
9b0117ecb82d
00e5695fa62f
f568491c665c

$ docker rmi $(docker images -q)
Untagged: docker.io/alpine:latest
Untagged: docker.io/[email protected]:a4104316f43c73146f1c0af4747
d88047a808e58238bcad6506a7fbbf3b30b90
Deleted: sha256:0766572b4bacfaee9a8eb6bae79e6f6dbcdfac0805c7c
6ec8b6c2c0ef097317a
Deleted: sha256:7cbcbac42c44c6c38559e5df3a494f44987333c8023a4
0fec48df2fce1fc146b
...

Обмен данными с контейнером по сети

Мы научились запускать контейнер с демоном, работающим внутри. Но толку от такого контейнера немного. Нужно так- же уметь подключаться к службе по внешней сети. Один из возможных способов – это использование проброса пор- тов из контейнеров на хост. Давайте поэкспериментируем с более наглядным примером – веб-сервером Apache. Стра- ничка официального образа на Docker Hub. Из описания можно узнать, что корневая директория для веб-сервера /usr/local/apache2/htdocs/.

Загрузим образ и запустим с новой для нас опцией -p:

$ 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:

$ 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

Организуется это при помощи правил брандмауэра:

# 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:

$ curl http://10.0.2.7:8888
<html><body><h1>It works!</h1></body></html>

Отлично! Контейнер доступен. Попробуем заменить стандартное сообщение своим, изменив index.html:

$ docker exec -it my-httpd bash
[email protected]:/usr/local/apache2# echo "My Apache ↵
server" > /usr/local/apache2/htdocs/index.html
[email protected]:/usr/local/apache2# exit
$ curl http://10.0.2.7:8888
My Apache server

В качестве альтернативы можно возложить ответственность за выбор порта на сам Docker, отдав команду docker run с ключом -P:

$ docker run -d -P --name my-httpd httpd
46f8a898d6c7f26d03c0c6df97c53e6a459c45499583277aa5b32df85038d77b

В этом случае узнать порт, который был выделен контейнеру, можно командой docker port:

$ docker port my-httpd
80/tcp -> 0.0.0.0:32768

В данном примере порт 32768 был выбран случайным образом Docker, а порт 80 был определен автором образа. Как это задается, мы изучим, когда будем разбирать описание образов при помощи файла Dockerfile.
Просмотр информации о контейнере Познакомимся с тем, как получить информацию о контейнере. Посмотрим на вывод команды docker inspect. Вывод команды в формате JSON отображен ниже:

$ 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-адрес контейнера, можно воспользоваться командой:

$ docker inspect -f '{{ .NetworkSettings.IPAddress}}' my-httpd2
172.17.0.2

Подключение к контейнеру постоянного хранилища

Все работает, однако, если удалите контейнер, изменения в index.html будут потеряны. На странице с описанием httpd на Dockrer Hub приведен пример использования контейнера:

$ 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 в специально выделенной директории:

$ 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. Для того чтобы контейнер или виртуальная машина получила доступ к файловой системе хоста, нужно задать правильный тип для соответствующих файлов и директорий:

# chcon -R -t svirt_sandbox_file_t ~andrey/mywww/

Повторяем эксперимент:

$ 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:

$ 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:

$ docker run --volumes-from my-data --name test httpd cat /usr/local/apache2/htdocs/index.html
My Apache server - 2

Теперь тестовый контейнер нам не нужен. Удалим его и запустим контейнер с веб-сервером, который будет
использовать том с контейнера my-data:

$ docker rm test
test
$ docker run -d -p 8889:80 --volumes-from my-data ↵
--name my-httpd3 httpd
d4a7e958731f1110e45043d2a2e1480ffe2d820623442b6e0e80f24c18a7
d93c
$ curl http://10.0.2.7:8889
My Apache server - 2

Ну и убедимся при помощи docker inspect, из какого контейнера используются тома:

$ docker inspect -f '{{ .HostConfig.VolumesFrom}}' my-httpd3
[my-data]

Если теперь удалить контейнер my-data, используемый контейнером my-httpd3 том c index.html удален не будет. Удалить тома вместе с контейнером можно, используя опцию -v команды docker rm, и только если тома не используются другими контейнерами. При запуске контейнера может быть полезной опция —rm.
Контейнер, запущенный с такой опцией, удаляется сразу после остановки. Покажем на примере контейнера, предназначенного исключительно для архивирования данных. Создадим директорию для резервного хранения данных:

$ mkdir wwwbackup
$ chcon -R -t svirt_sandbox_file_t ~andrey/wwwbackup

Теперь запустим контейнер, единственная цель которого – сохранить данные контейнера в директории хоста.
В нашем случае данные – это файл index.html:

$ 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

Как мы видим, контейнер сделал свое дело.

Оставить комментарий

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.