Контейнер и образ
Давайте разделим Docker на две основные части: образ и контейнер.
Давайте вспомним, когда мы покупали ПО на дисках. Образ – это и есть лазерный диск, слепок вашего программного обеспечения. Образ у вас один. Он может находиться у вас, в облаке, вы можете его собрать из исходников.
У диска есть дорожки, а у образа – слои. Каждая команда, которая формирует этот образ, накладывает в образе свой слой. Это позволяет в дальнейшем экономить дисковое пространство за счет переиспользования слоев нескольких образов. Допустим, у вас есть 3-4 образа, каждый из которых использует в качестве первого слоя операционную систему Ubuntu. С Docker вам не надо в вашей системе хранить несколько слоев с Ubuntu: вам достаточно одного слоя, который будут использовать все образы. Это называется переиспользование.
Когда вы начинаете запускать свое приложение на базе образа, вы запускаете контейнер – это, по факту, экземпляр приложения, сущность, которая предоставляет вам сервис.
Контейнер с точки зрения концепции Docker – некая сущность, которая живет недолго. Он должен родиться, поработать, и в дальнейшем уничтожиться. Все данные которые необходимо сохранить, монтируются к контейнеру в качестве подключаемых разделов.
Сборка нового образа
Сборка начинается с создания файла Dockerfile — он содержит инструкции того, что должно быть в контейнере. В качестве примера, соберем свой веб-сервер nginx.
И так, чтобы создать свой образ с нуля, создаем каталог для размещения Dockerfile:
mkdir -p /opt/docker/mynginx
* где /opt/docker/mynginx — полный путь до каталога, где будем создавать образ.
… переходим в данный каталог:
cd /opt/docker/mynginx
… и создаем Dockerfile:
vi Dockerfile
FROM centos:7
MAINTAINER Dmitriy Mosk <master@dmosk.ru>
ENV TZ=Europe/Moscow
RUN yum install -y epel-release && yum install -y nginx
RUN yum clean all
RUN echo «daemon off;» >> /etc/nginx/nginx.conf
RUN sed -i «0,/nginx/s/nginx/docker-nginx/i» /usr/share/nginx/html/index.html
CMD
* в данном файле мы:
- используем базовый образ centos 7;
- в качестве автора образа указываем Dmitriy Mosk;
- задаем временную зону внутри контейнера Europe/Moscow.
- устанавливаем epel-release и nginx;
- чистим систему от метаданных и кэша пакетов после установки;
- указываем nginx запускаться на переднем плане (daemon off);
- в индексном файле меняем первое вхождение nginx на docker-nginx;
- запускаем nginx.
* подробное описание инструкций Dockerfile смотрите .
Запускаем сборку:
docker build -t dmosk/nginx:v1 .
* где dmosk — имя автора; nginx — название для сборки; v1 — тег с указанием версии. Точка на конце указывает, что поиск Dockerfile выполняем в текущей директории.
… начнется процесс сборки образа — после его завершения мы должны увидеть что-то на подобие:
Successfully built eae801eaeff2
Successfully tagged dmosk/nginx:v1
Посмотреть список образов можно командой:
docker images
Создаем и запускаем контейнер из образа:
docker run -d -p 8080:80 dmosk/nginx:v1
* в данном примере мы запустим контейнер из образа dmosk/nginx:v1 и укажем, что необходимо опубликовать внешний порт 8080, который будет перенаправлять трафик на порт 80 внутри контейнера.
Открываем браузер и переходим по адресу http://<IP-адрес нашего докера>:8080 — мы должны увидеть страницу приветствия с нашим docker-nginx:
Посмотреть созданные контейнеры можно командой:
docker ps -a
Запустить или остановить контейнеры можно командами:
docker stop 5fe78aca2e1d
docker start 5fe78aca2e1d
* где 5fe78aca2e1d — идентификатор контейнера.
Другие Типы Монтирования
Есть два других типа томов Docker, которые мы еще не обсуждали: bind mount и tempfs mount.
Bind Mount
Bind mount используются для монтирования существующего пути на хосте в контейнер. Используя —mount совместно с <host path>:<container path>, вы можете указать существующие каталоги, которые будут монтироваться в контейнер. Это очень удобно при использовании информации о конфигурации, такой как каталоги внутри /etc. Это также полезно, когда у вас есть информация, которую вы хотите использовать в контейнере, например, существующие наборы данных или статические файлы веб-сайта.
Tempfs Mount
Задача tempfs монтирования состоит в том, чтобы обеспечить доступное для записи расположение, которое специально не сохраняет информацию после окончания срока службы контейнера. Возможно, вы думаете: «зачем это нужно?” В контейнере, который не имеет подключенного тома, все записи идут в тонкий слой R/W, вставленный во время выполнения. Любая запись, направленная на этот слой, влияет на файловую систему, поскольку эти записи выполняются на базовом хосте. Обычно это не является проблемой, если вы не пишете значительные объемы одноразовых данных (таких как журналы). В этом случае вы можете наблюдать снижение производительности, так как файловая система должна обрабатывать все эти вызовы write. tempfs монтирование было создано для предоставления контейнерам временного пути записи, который не влияет на операции файловой системы. В частности, tempfs это эфемерное монтирование, которое записывается непосредственно в память. Этот том можно создать с помощью —tempfs аргумента.
Драйвера Томов
По умолчанию тома хранят информацию о базовой хост-системе. Docker также имеет концепцию, называемую драйверами томов, которая позволяет указать, как и где хранить тома. Например, вы можете хранить том Docker внутри корзины Amazon S3. Это может быть удобно, если вы хотите, чтобы информация сохранялась не только за пределами срока службы контейнера, но и за пределами срока службы хоста.
Подготовка и запуск
Устройство образов
Минимально рабочий проект состоит из PHP-FPM v7.4.24, Nginx v1.21.1 на базе alpine и MySQL 5.7 на базе centos 8. Все образы, взятые за основу, загружены с dockerhub из официальных репозиториев. Каталог с кодом и ядром битрикса должен располагаться в корне проекта с именем “bitrix”.
Структура проекта представлена на скриншоте ниже:
Nginx
- Пользователь, от имени которого запущен процесс внутри контейнера, имеет UID и GUI 101:101;
- Корневая директория с кодом внутри контейнера – /var/www/bitrix
- Всё взаимодействие с контейнером производится через переменные окружения и envsubst;
- Конфигурационный файл-шаблон для основного сайта расположен в директории conf и упаковывается внутрь контейнера;
- В той же директории располагаются файлы-шаблоны dbconn и settings, которые также зашиваются в контейнер.
PHP-FPM
- Пользователь, от имени которого запущен процесс внутри контейнера, имеет UID и GUI 101:101;
- Корневая директория с кодом внутри контейнера – /var/www/bitrix;
- В конфигурационном файле conf/override.ini указываются серверные настройки PHP перед сборкой образа
- В официальный образ внесены изменения для установки дополнительных PHP-расширений, необходимых для минимальной работы битрикс: gd, mysqli, opcache, xml, zip, ldap.
MySQL
- Пользователь, от имени которого запущен процесс внутри контейнера, имеет UID и GUI 1001:1001;
- Каталог “data” с данными БД должен располагаться на хосте с правами пользователя внутри контейнера, т.е. 1001:1001;
- Конфигурационный файл для MySQL используется по умолчанию, за исключением опций, необходимых для корректной работы битрикс (взят из виртуальной машины битрикс) и размещен по пути config/z_bx_custom.cnf;
- Дамп БД должен быть расположен в каталоге docker-entrypoint-initdb.d – при первом запуске контейнера, все sql.gz файлы в алфавитном порядке будут импортированы в БД после её запуска;
- Файл-шаблон docker-entrypoint-initdb.d/init.sql.template содержит в себе SQL-скрипт, который выполняет замену в БД на основе домена сайта (устанавливает домен в главном модуле и активирует галку “Для разработки”);
- При первом запуске контейнера с MySQL, с помощью встроенных скриптов создаётся сама БД, а также пользователь и пароль для подключения к созданной БД. Все эти данные берутся из файла с переменными .env, который расположен в корне проекта (ls -la).
После сборки докер-образов и запуска контейнеров из этих образов присутствует одно неудобство. Из-за того, что пользователи внутри контейнеров с Nginx и PHP-FPM имеют UID 101, то весь каталог с кодом на хосте, монтируемый в контейнер, также должен иметь владельца с UID 101. Как результат – изменение файлов в процессе разработки будет неудобным, т.к. каждый раз придётся корректировать права.
Простым решением вышеописанной проблемы будет назначить права 0775 и владельца 101:$USER на каталог с кодом, т.е. для веб-сервера все права останутся на месте, и для разработчика будет удобно редактировать файлы. Но для вновь создаваемых файлов каждый раз придётся выполнять команды chown, что опять же неудобно.
А потому на каталог с кодом устанавливается дополнительный атрибут SGID, в результате чего все новые файлы, созданные в каталоге /bitrix, будут иметь сразу необходимые права и владельца – $USER:101, т.е. при создании файлов ничего дополнительно делать не нужно.
Все вышеописанные особенности и нюансы могут показаться избыточными, а потому для подготовки запуска в каталоге проекта имеется скрипт инициализации, который выполняет подготовительные действия для инициализации каталогов, прав и т.д., после чего можно просто билдить образы и запускать свой битрикс-проект в докер.
- Каталог с битриксом (ядро + код) разместить в корне проекта по пути ./bitrix/;
- Дамп БД разместить в каталоге mysql/docker-entrypoint-initdb.d/ (можно пожатый через gzip);
- Сделать скрипт инициализации исполняемым и выполнить один раз со следующими аргументами: – подготовка каталогов MySQL и SQL-файла до первого запуска проекта – установка владельца – установка прав (выполняется 3-5 минут) – очистка управляемого кеша
- Выполнить сборку всех образов командой
- Задать переменную окружения в .env файле, указав домен, который резолвится в 127.0.0.1 на хост-машине. По умолчанию – default.com. При необходимости тут же можно изменить имя БД и логин\пароль, а также настройки для Nginx;
- В docker-compose.yaml для сервиса php-fpm в extra_hosts указать локальный IP-адрес в сети Docker, по умолчанию 172.17.0.1. Уточнить, какой адрес в сети докер можно командой . Данная настройка необходима для прохождения проверки работ сокетов.
- Выполнить запуск
- В браузере открывать сайт по адресу http://default.com:80
Удаление контейнеров
Удаление одного или нескольких конкретных контейнеров
Используйте команду с флагом для поиска имен или идентификаторов контейнеров, которые вы хотите удалить:
Список:
Удаление:
Удаление контейнера при выходе
Если вы создаете контейнер, который вам не будет нужен после завершения его использования, вы можете использовать команду для его автоматического удаления при выходе.
Запуск и удаление:
Удаление всех контейнеров, из которых выполнен выход
Вы можете найти контейнеры с помощью команды и отфильтровать их по статусу: created (создан), restarting (перезапускается), running (работает), paused (пауза) или exited (выполнен выход). Чтобы просмотреть список контейнеров, из которых выполнен выход, используйте флаг для фильтрации по статусу. Убедитесь, что вы хотите удалить эти контейнеры, и используйте флаг для передачи идентификаторов в команду .
Список:
Удаление:
Удаление контейнеров с использованием нескольких фильтров
Фильтры Docker можно комбинировать, повторяя флаг фильтра с дополнительным значением. В результате выводится список контейнеров, соответствующих любому из указанных условий. Например, если вы хотите удалить все контейнеры со статусом Created (статус, который может возникнуть при запуске контейнера недопустимой командой) или Exited, вы можете использовать два фильтра:
Список:
Удаление:
Удаление контейнеров по шаблону
Чтобы найти все контейнеры, соответствующие определенному шаблону, используйте сочетание команд и grep. Когда вы будете удовлетворены списком удаляемых контейнеров, используйте и для передачи идентификаторов в команду
Обратите внимание, что эти утилиты не поставляются Docker и могут быть доступны не во всех системах:
Список:
Удаление:
Остановка и удаление всех контейнеров
Для просмотра контейнеров в системе используется команда . При добавлении флага будет выведен список всех контейнеров. Если вы уверены, что хотите удалить их, добавьте флаг для передачи их идентификаторов командам и :
Список:
Удаление:
Популярные образы на DockerHub
На Docker Hub доступно множество популярных и оптимизированных изображений.
Популярность этих образов зависит от различных факторов, таких как спрос, присутствие на рынке, рейтинги, оценки удовлетворенности и т.д. Для получения подробного списка самых популярных репозиториев перейдем на веб-сайт Docker Hub.
Использование образа также зависит от ОС и ее архитектуры. Если мы знаем, что для какой ОС и ее архитектуры будут использоваться извлеченные образы, то мы должны рассмотреть следующие ключевые факторы, прежде чем извлекать образ.
- Ищите конкретную версию, используя теги (в основном, последние).
- Выберите тот, который имеет максимальные загрузки и звезды.
- Проверьте его обновления (когда он был обновлен последний раз).
- Если возможно, проверьте его тип: проверенный издатель или официальный (Docker Certified).
Volumes
docker cp file <containerID>:/ — Скопировать в корень контейнера filedocker cp <containerID>:/file . — Скопировать file из корня контейнера в текущую директорию командной строки
docker volume create todo-db — Создать volume для постоянного хранения файловdocker run -dp 3000:3000 —name=dev -v todo-db:/etc/todos container-name — Добавить named volumу todo-db к контейнеру (они ok когда мы не заморачиваемся где конкретно хранить данные)
docker run -dp 3000:3000 —name=dev —mount source=todo-db,target=/etc/todos container-name — тоже самое что команда сверху
docker volume ls — Отобразить список всех volume’ов
docker volume inspect — Инспекция volume’ов
docker volume rm — Удалить volume
Тома-контейнеры
Был ещё такой старый паттерн — «контейнеры только с данными». И он делал именно то, как назывался: просто существовал, как правило, выключенный, и единственной целью его жизни было делиться своими томами с другими контейнерами.
Чтобы получить к ним доступ, нужно было указать параметр , и магия получалась сама собой:
Shell
docker run -d —volumes-from mydatacontainer mysql
1 | docker run-d—volumes-from mydatacontainer mysql |
Если честно, то я не вижу никакой выгоды от использования этого подхода, по сравнению с обычными томами данных. Может, до версии 1.8, когда появился нормальный АПИ для томов, том-контейнер был единственным вменяемым решением, то сейчас даже Гугл не уверен, почему они всё ещё существуют.
Инструкции Dockerfile#
- — задаёт базовый (родительский) образ.
- — описывает метаданные. Например — сведения о том, кто создал и поддерживает образ.
- — устанавливает постоянные переменные среды.
- — выполняет команду и создаёт слой образа. Используется для установки в контейнер пакетов.
- — копирует в контейнер файлы и папки которые лежат локально.
- — копирует файлы и папки в контейнер, может распаковывать локальные .tar-файлы, а так же получать на вход URL и скачивать файл внутрь image.
- — описывает команду с аргументами, которую нужно выполнить когда контейнер будет запущен. Аргументы могут быть переопределены при запуске контейнера. В файле может присутствовать лишь одна инструкция .
- — задаёт рабочую директорию для следующей инструкции.
- — задаёт переменные для передачи Docker во время сборки образа.
- — предоставляет команду с аргументами для вызова во время выполнения контейнера. Аргументы не переопределяются.
- — указывает на необходимость открыть порт. Также можно открыть socket, но это тема для отдельной заметки.
- — создаёт точку монтирования для работы с постоянным хранилищем.
Подробности и нюансы можно почерпнуть из официальной документации.
Как Мы Можем Это Опубликовать?
Фу, это было много. Надеюсь, вы узнали много нового о том, как делать вещи с Docker локально и как использовать его для разработки. Однако, если бы вы хотели протолкнуть это в Сеть, не могли бы вы? Да.
Сначала нам нужно будет создать новую машину на DigitalOcean, а затем переключиться на использование этой машины. Затем мы обновим наш Dockerfile, чтобы скопировать исходный код приложения непосредственно в процессе сборки (что все еще позволяет нам перезаписать его через том (чтобы мы могли получить лучшее из обоих миров). Как только мы это сделаем, мы снова запустим наш сценарий создания, и все должно сработать.
Вам нужно будет зарегистрироваться в DO и получить ключ API. После этого вы можете выполнить следующие команды:
$ docker-machine create --driver digitalocean --digitalocean-access-token XXXXXXXXXX flask-tester-do $ eval $(docker-machine env flask-tester-do)
Через несколько минут у вас будет новая машина Docker на вашей учетной записи DO. Команда eval теперь означает, что все ваши команды docker относятся к этой машине, включая scripts.
Затем мы обновим Dockerfile, чтобы скопировать исходный код в контейнер. Здесь кроется странная оговорка — Docker отказывается разрешить вам ссылаться на файлы из-за пределов каталога Dockerfile. Для этого есть несколько причин, но чтобы обойти их, мы можем использовать небольшой взлом и сказать Linux смонтировать каталог в другом каталоге с помощью стандартной команды mount:
$ sudo mount --bind /code/flask-tester-backend images/flask_tester/src
Для Windows вам может потребоваться символическая ссылка или не иметь двух репозиториев, но иметь свой Dockerfile ближе к источнику. Для Mac OS X должна работать команда mount. Посмотрите на строки 18 и 19, где мы запускаем файл и копируем наши исходные файлы в новый каталог, созданный в контейнере.
Все, что нам нужно сделать сейчас, это создать приложение и сказать compose, чтобы запустить его:
$ docker build -t flask_tester:0.2 images/flask_tester # Build image flask_tester:0.2 $ … update compose file … $ ./scripts/stop_app.sh # Stop all containers for this app $ mkdir data/redis # Create folder for persistent redis data $ ./scripts/start_app.sh # Re-create all the containers
Теперь вы должны иметь доступ к приложению на своей новой машине!
Docker и IBM Cloud
Корпоративная контейнерная платформа поддерживает оркестрацию в разных общедоступных и частных облачных средах, обеспечивая унификацию используемых сред с целью улучшения бизнес-показателей и повышения эффективности работы. Это ключевой компонент стратегии открытого гибридного облака, который дает возможность избежать привязки к определенному поставщику, внедрить согласованные процедуры создания и запуска приложений в любой среде, а также оптимизировать и модернизировать все ИТ-процессы.
Сделайте следующий шаг:
- Воспользуйтесь Red Hat OpenShift on IBM Cloud — управляемой услугой OpenShift на основе IBM Cloud с высочайшим уровнем масштабируемости и безопасности — для развертывания полностью управляемых кластеров Kubernetes с высоким уровнем доступности, чтобы автоматизировать процессы обновления, масштабирования и инициализации ресурсов.
- Обеспечьте развертывание и запуск приложений во всех средах, независимо от поставщика облачных услуг, включая локальные среды, периферийные узлы и общедоступные облачные среды, с помощью IBM Cloud Satellite — управляемого распределенного облачного решения.
- Упростите и консолидируйте озера данных за счет слаженного развертывания корпоративных хранилищ с поддержкой контейнеров локально и в общедоступных облачных средах с помощью решений IBM для гибридного облачного хранилища.
- Упростите сложные процессы управления гибридной ИТ-архитектурой с помощью управляемых услуг IBM Cloud.
Начните работать с учетной записью IBM Cloud уже сегодня.
Использование диска контейнерами
Каждый раз, когда создается контейнер, в папке на хост-машине появляется несколько папок и файлов. Среди них:
- Папка (ID — уникальный идентификатор контейнера). Если контейнер использует драйвер логгирования по умолчанию, все логи будут сохранены в файле JSON внутри нее. Создание слишком большого количества логов может повлиять на файловую систему хост-машины.
- Папка в , содержащая слой чтения-записи контейнера (overlay2 является предпочтительным драйвером хранилища в большинстве дистрибутивов Linux). Если контейнер сохраняет данные в своей собственной файловой системе, они будут храниться в на хост-машине.
Представим, что у нас есть совершенно новая система, в которой только что был установлен Docker.
$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 0 0 0B 0BContainers 0 0 0B 0BLocal Volumes 0 0 0B 0BBuild Cache 0 0 0B 0B
Во-первых, запустим контейнер NGINX:
$ docker container run --name www -d -p 8000:80 nginx:1.16
Снова запустив команду , мы увидим:
- один образ размером 126 Мбайт. Его загрузил NGINX: 1.16, когда мы запустили контейнер;
- контейнер , запускающийся из образа NGINX.
$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 1 1 126M 0B (0%)Containers 1 1 2B 0B (0%)Local Volumes 0 0 0B 0BBuild Cache 0 0 0B 0B
Поскольку контейнер запущен, а образ в данный момент используется, освобождаемого пространства пока нет. Так как его размер (2B) незначителен, и поэтому его нелегко отследить в файловой системе, создадим пустой файл размером 100 Мбайт в файловой системе контейнера. Для этого мы используем удобную команду dd из контейнера www.
$ docker exec -ti www \ dd if=/dev/zero of=test.img bs=1024 count=0 seek=$
Этот файл создается в слое чтения-записи, связанном с этим контейнером. Если мы снова проверим вывод команды , то увидим, что он теперь занимает некоторое дополнительное дисковое пространство.
$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 1 1 126M 0B (0%)Containers 1 1 104.9MB 0B (0%)Local Volumes 0 0 0B 0BBuild Cache 0 0 0B 0B
Где находится этот файл на хосте? Проверим:
$ find /var/lib/docker -type f -name test.img/var/lib/docker/overlay2/83f177...630078/merged/test.img/var/lib/docker/overlay2/83f177...630078/diff/test.img
Не вдаваясь глубоко в детали — этот файл был создан на слое чтения-записи контейнера, который управляется драйвером overlay2. Если мы остановим контейнер, используемое им дисковое пространство станет пригодным для восстановления. Посмотрим:
# Остановка контейнера www$ docker stop www# Влияние на использование диска$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 1 1 126M 0B (0%)Containers 1 0 104.9MB 104.9MB (100%)Local Volumes 0 0 0B 0BBuild Cache 0 0 0B 0B
Как можно восстановить это пространство? Путем удаления контейнера, что приведет к удалению связанного с ним слоя чтения-записи.
Следующие команды позволяют нам удалить все остановленные контейнеры сразу и освободить место на диске, которое они используют:
$ docker container pruneWARNING! This will remove all stopped containers.Are you sure you want to continue? [y/N] yDeleted Containers:5e7f8e5097ace9ef5518ebf0c6fc2062ff024efb495f11ccc89df21ec9b4dcc2Total reclaimed space: 104.9MB
Из вывода видно, что больше нет места, используемого контейнерами, и, поскольку образ больше не используется (контейнер не работает), пространство, которое он использует в файловой системе хоста, может быть восстановлено:
$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 1 0 126M 126M (100%)Containers 0 0 0B 0BLocal Volumes 0 0 0B 0BBuild Cache 0 0 0B 0B
Примечание: если образ используется хотя бы одним контейнером, занимаемое им дисковое пространство не может быть освобождено.
Подкоманда , которую мы применяли выше, удаляет остановленные контейнеры. Если нам нужно удалить все — и запущенные, и остановленные, мы можем использовать одну из следующих команд (обе эквивалентны):
# Старая команда$ docker rm -f $(docker ps -aq)# Более современная$ docker container rm -f $(docker container ls -aq)
Примечание: во многих случаях стоит использовать флаг при запуске контейнера, чтобы он автоматически удалялся при остановке процесса PID 1, тем самым немедленно освобождая неиспользуемое пространство.