Привет, друзья! Хочу поделиться с вами заметками о Docker. Заметки состоят из 4 частей: 2 теоретических и 2 практических. Если быть более конкретным: Если вам это интересно, прошу под кат. Начало работы с Docker на английском. Перевод на русский от Microsoft. Docker — это открытая платформа для разработки, доставки и запуска приложений. Он позволяет отделить приложения от инфраструктуры и управлять инфраструктурой по аналогии с тем, как мы управляем приложениями. Docker предоставляет возможность упаковывать и запускать приложение в слабо изолированной среде — контейнере. Изоляция и безопасность позволяют одновременно запускать несколько контейнеров на одном хосте (хостом может быть наша локальная машина, дата центр, облачный провайдер или их микс). Контейнеры являются легковесными и содержат все необходимое для запуска приложения, что избавляет нас от необходимости полагаться на то, что установлено на хосте. Для чего Docker может использоваться? Быстрая и согласованная доставка приложений Docker рационализирует жизненный цикл разработки, позволяя разработчикам работать в стандартизированной среде через локальные контейнеры, предоставляющие приложения и сервисы. Контейнеры отлично подходят для рабочих процессов непрерывной интеграции и непрерывной доставки (continuous integration/continuous delivery, CI/CD). Отзывчивая разработка и масштабирование Платформа, основанная на контейнерах, позволяет легко портировать приложения. Контейнеры могут запускаться на локальной машине разработчика, в физических или виртуальных дата-центрах, облачных провайдерах или смешанных средах. Запуск большего количества приложений на одной машине Docker является легковесным и быстрым. Он предоставляет работоспособную и экономичную альтернативу виртуальным машинам на основе гипервизора, что позволяет использовать больше вычислительных мощностей для решения аналогичных задач. Docker использует клиент-серверную архитектуру. Клиент (Docker client) обращается к демону (Docker daemon), который поднимает (собирает), запускает и распределяет контейнеры. Клиент и демон могут быть запущены в одной системе или клиент может быть подключен к удаленному демону. Клиент и демон общаются через REST API поверх UNIX-сокетов или сетевого интерфейса. Другим клиентом является Docker Compose, позволяющий работать с приложениями, состоящими из нескольких контейнеров. 
 Демон Демон (dockerd) регистрирует (слушает) запросы, поступающие от Docker API, и управляет такими объектами как образы, контейнеры, сети и тома. Демон может общаться с другими демонами для управления сервисами. Клиент Клиент (docker) — основной способ коммуникации с Docker. При выполнении такой команды, как docker run, клиент отправляет эту команду демону, который, собственно, эту команду и выполняет. Команда docker использует Docker API. Клиент может общаться с несколькими демонами. Docker Desktop Docker Desktop — это десктопное приложение для Mac и Windows, позволяющее создавать и распределять контейнерные приложения и микросервисы. Docker Desktop включает в себя демона, клиента, Docker Compose, Docker Content Trust, Kubernetes и Credential Helper. Реестр В реестре (registry) хранятся образы контейнеров. Docker Hub — это публичный реестр, который (по умолчанию) используется Docker для получения образов. Имеется возможность создания частных (закрытых) реестров. При выполнении таких команд, как docker pull или docker run, необходимые образы загружаются из настроенного реестра. А при выполнении команды docker push образ загружается в реестр. Объекты При использовании Docker мы создаем и используем образы, контейнеры, сети, тома, плагины и другие объекты. Рассмотрим некоторые из них. Образы (Images) Образ — это доступный только для чтения шаблон с инструкциями по созданию контейнера. Часто образ представляет собой модификацию другого образа. Можно создавать свои образы или использовать образы, созданные другими и опубликованные в реестре. Для создания образа используется Dockerfile, содержащий инструкции по созданию образа и его запуску (см. ниже). Ряд инструкций в Dockerfile приводит к созданию в образе нового слоя (раньше новый слой создавался для каждой инструкции). При изменении Dockerfile и повторной сборке образа пересобираются только модифицированные слои. Это делает образы легковесными, маленькими и быстрыми. Контейнеры (Containers) Контейнер — это запускаемый экземпляр образа. Мы создаем, запускаем, перемещаем и удаляем контейнеры с помощью Docker API или CLI (command line interface, интерфейс командной строки). Мы можем подключать контейнеры к сетям, добавлять в них хранилища данных и даже создавать новые образы на основе текущего состояния. По умолчанию контейнеры хорошо изолированы от других контейнеров и хоста. Однако мы можем управлять тем, насколько изолированы сеть, хранилище данных или другая подсистема контейнера. Контейнер определяется образом и настройками, указанными при его создании и запуске. При удалении контейнера его состояние также удаляется. Этого можно избежать с помощью хранилища данных. Пример команды docker run Следующая команда запускает контейнер ubuntu, интерактивно подключается к локальному сеансу командной строки и выполняет в ней команду /bin/bash: При выполнении этой команды происходит следующее: docker run Команда docker run используется для запуска контейнера. Это основная и потому наиболее часто используемая команда. Следующая команда запускает контейнер postgres: docker build Команда docker build используется для создания образа на основе файла Dockerfile и контекста. Контекст — это набор файлов, находящихся в локации, определенной с помощью PATH или URL. PATH — это директория в нашей локальной системе, а URL — это удаленный репозиторий. Контекст сборки обрабатывается рекурсивно, поэтому PATH включает как директорию, там и все ее поддиректории, а URL — как репозиторий, так и все его субмодули. Для исключения файлов из сборки образа используется .dockerignore (синтаксис этого файла похож на .gitignore). Создание образа: Использование репозитория в качестве контекста (предполагается, что Dockerfile находится в корневой директории репозитория): В данном случае http://server/ctx.tar.gz отправляется демону, которые загружает и извлекает файлы. Параметр -f ctx/Dockerfile определяет путь к Dockerfile внутри ctx.tar.gz. Чтение Dockerfile из STDIN без контекста: Добавление тега к образу: Определение Dockerfile: Экспорт файлов сборки в директорию out: Экспорт файлов сборки в файл out.tar: docker exec Команда docker exec используется для выполнения команды в запущенном контейнере. Пример: В данном случае в контейнере postgres будет запущен интерактивный терминал psql. Выполним парочку команд: docker ps Команда docker ps используется для получения списка (по умолчанию только запущенных) контейнеров. Для получения списка образов используется команда docker images. Команды управления Другие команды Для получения логов запущенного контейнера используется команда docker logs: Для удаления всех неиспользуемых данных (контейнеры, сети, образы и, опционально, тома) используется команда docker system prune. Основные флаги: Предостережение: применять эту команду следует с крайней осторожностью, поскольку удаленные данные не подлежат восстановлению. Полный список команд и флагов. Dockerfile — это документ (без расширения), содержащий инструкции, которые используются для создания образа при выполнении команды docker build. Предостережение: не используйте / в качестве PATH для контекста сборки. Это приведет к передаче демону всего содержимого жесткого диска вашей машины. Инструкции выполняются по одной. Результаты наиболее важных инструкций фиксируются в виде отдельных слоев образа. Обратите внимание: каждая инструкция выполняется независимо от других. Это означает, что выполнение RUN cd /tmp не будет иметь никакого эффекта для последующих инструкций. Dockerfile может содержать следующие инструкции: Справка по Dockerfile. Рассмотрим следующий Dockerfile: Выполнение каждой инструкции (кроме CMD) из этого файла приводит к созданию нового слоя: При запуске образа и генерации контейнера мы добавляем новый слой, доступный для записи, поверх остальных. Все изменения в запущенном контейнере, такие как создание новых файлов, их модификация или удаление записываются в этот слой. Создание эфемерных контейнеров Генерируемые контейнеры должны быть максимально эфемерными. Под эфемерностью понимается возможность остановки, уничтожения, повторной сборки и замены контейнеров без необходимости дополнительной настройки процесса их генерации. Понимание контекста сборки При выполнении команды docker build контекстом сборки, как правило, является текущая директория. Предполагается, что Dockerfile находится в этой директории. Путь к Dockerfile, находящемуся в другом месте, можно указать с помощью флага -f. Независимо от того, где находится Dockerfile, все файлы и директории из текущей директории отправляются демону в качестве контекста сборки. В следующем примере мы Размещаем Dockerfile и hello в разных директориях и собираем вторую версию образа без использования кеша предыдущей сборки (-f определяет путь к Dockerfile): .dockerignore В файле .dockerignore указываются файлы, не имеющие отношения к сборке и поэтому не включаемые в нее. Синтаксис .dockerignore похож на синтаксис .gitignore или .npmignore. Многоэтапная сборка Многоэтапная сборка позволяет существенно уменьшить размер финального образа без необходимости изучения процесса сборки на предмет наличия промежуточных слоев и файлов, которые можно удалить. Если процесс сборки состоит из нескольких слоев, мы можем упорядочить их от редко модифицируемых до часто модифицируемых: Пример Dockerfile для Go-приложения: Лишние библиотеки Для уменьшения сложности, количества зависимостей и времени сборки следует избегать установки дополнительных и ненужных библиотек "на всякий случай". Разделение приложений Каждый контейнер должен иметь одну ответственность (single responsibility). Разделение приложений на несколько контейнеров облегчает горизонтальное масштабирование и переиспользуемость контейнеров. Например, стек веб-приложения может состоять из 3 отдельных контейнеров, каждый со своим уникальным образом, для управления приложением, базы данных и сервера или распределенного кеша, хранящегося в памяти. Если контейнеры зависят друг от друга для обеспечения возможности их коммуникации следует использовать сети. Минимизация количества слоев В старых версиях Docker каждая инструкция в Dockerfile приводила к созданию нового слоя. Сейчас новые слои создаются только инструкциями RUN,COPY и ADD. Другие инструкции создают временные промежуточные образы, которые не приводят к увеличению размера сборки. Сортировка многострочных аргументов Многострочные аргументы рекомендуется сортировать в алфавитном порядке. Также рекомендуется добавлять пробел перед обратным слэшем (\). Пример: Использование кеша сборки При сборке образа Docker изучает все инструкции в порядке, определенном в Dockerfile. После изучения инструкции Docker обращается к своему кешу. Если в кеше имеется соответствующий образ, новый образ не создается. Для сборки образа без обращения к кешу используется настройка --no-cache=true. FROM В качестве основы для создания образа рекомендуется использовать официальные образы из DockerHub версии alpine. LABEL Подписи позволяют структурировать образы проекта, добавлять информацию о лицензиях, могут использоваться для автоматизации и т.д. RUN Длинные и сложные инструкции RUN рекомендуется разделять на несколько строк с помощью обратного слэша (\). Это делает Dockerfile более читаемым, облегчает его понимание и поддержку. CMD Инструкция CMD используется для запуска программ в контейнере вместе с аргументами. CMD должна использоваться в форме CMD ["executable", "param1", "param2"]. В большинстве случаев первым элементом должен быть интерактивный терминал, такой как bash, python или perl. Например, CMD ["perl", "-de0"], CMD ["python"] или CMD ["php", "-a"]. При использовании ENTRYPOINT следует убедиться, что пользователи понимают, как работает эта инструкция. EXPOSE Инструкция EXPOSE определяет порты, на которых контейнер регистрирует соединения. Рекомендуется использовать порты, которые являются традиционными для приложения. Например, образ, содержащий веб-сервер Apache, должен использовать EXPOSE 80, а образ, содержащий MonghoDB — EXPOSE 27017. ENV Для облегчения запуска программы можно использовать ENV для обновления переменной среды окружения PATH для приложения, устанавливаемого контейнером. Например, ENV PATH=/usr/local/nginx/bin:$PATH обеспечивает, что CMD ["nginx"] просто работает. Инструкция ENV также может быть полезна для предоставления обязательных для сервиса переменных, таких как PGDATA для Postgres. Наконец, ENV может использоваться для установки номеров версий, что облегчает их обновление. ADD или COPY Хотя ADD и COPY имеют похожий функционал, в большинстве случаев следует использовать COPY, поскольку эта инструкция является более прозрачной, чем ADD. COPY поддерживает копирование в контейнер только локальных файлов, а ADD также позволяет извлекать файлы из локальных архивов и получать файлы по URL, но вместо последнего лучше использовать curl или wget: это позволяет удалять ненужные файлы после извлечения. Если Dockerfile состоит из нескольких этапов, на которых используются разные файлы из контекста, эти файлы рекомендуется копировать индивидуально. Это позволяет обеспечить инвалидацию кеша только для модифицированных файлов. Например: ENTRYPOINT ENTRYPOINT определяет основную команду для образа, что позволяет запускать образ без этой команды. Рассмотрим пример образа для инструмента командной строки s3cmd: Данный образ может быт запущен следующим образом: Это приведет к выводу справки. Либо мы может передать параметры для выполнения команды: Это может быть полезным при совпадении названия образа со ссылкой на исполняемый файл. VOLUME Инструкция VOLUME следует использовать для доступа к любой области хранения базы данных, хранилищу настроек или файлам/директориям, созданным контейнером. Крайне не рекомендуется использовать VOLUME для мутабельных и/или пользовательских частей образа. WORKDIR Для ясности и согласованности для WORKDIR всегда следует использовать абсолютные пути. Также WORKDIR следует использовать вместо инструкций типа RUN cd ... && do-something, которые трудно читать, отлаживать и поддерживать. По умолчанию файлы, создаваемые в контейнере сохраняются в слое, доступном для записи (writable layer). Это означает следующее: Docker предоставляет 2 возможности для постоянного хранения данных на хосте: тома (volumes) и bind mount. Для пользователей Linux также доступен tmpfs mount, а для пользователей Windows — named pipe. Выбор правильного типа монтирования Независимо от выбранного типа монтирования, для контейнера данные выглядят одинаково. Они представляют собой директорию или файл в файловой системе контейнера. Разница между томами, bind mount и tmpfs mount заключается в том, где хранятся данные на хосте. 
 Управление данными в Docker. Тома (Volumes) Тома — предпочтительный способ хранения данных, генерируемых и используемых контейнерами. Они полностью управляются Docker, в отличие от bind mount, которые зависят от структуры директории и операционной системы хоста. Преимущества томов состоят в следующем: Тома также являются более предпочтительными перед хранением данных в слое контейнера, доступном для записи, поскольку тома не увеличивают размер контейнера. Содержимое контейнера существует за пределами жизненного цикла контейнера. Флаги -v и --mount --mount является более явным и многословным. Основное отличие состоит в том, что при использовании -v все настройки комбинируются вместе, а при использовании --mount они указываются раздельно. Движок тома может быть определен только с помощью --mount. -v и --mount принимают одинаковые настройки. При использовании томов с сервисами, доступен только флаг --mount. Создание и управление томами В отличие от bind mount, тома могут создаваться и управляться за пределами любого контейнера. Создание тома Список томов Анализ тома Удаление тома Запуск контейнера с томом При запуске контейнера с несуществующим томом, он создается автоматически. В следующем примере том myvol2 монтируется в директорию app контейнера: В данном случае том будет доступен как для чтения, так и для записи. Использование тома с Docker Compose Единичный сервис (single service) Compose с томом может выглядеть так: Том будет создан при первом вызове docker compose up. При последующих вызовах данный том будет использоваться повторно. Том может быть создан отдельно с помощью docker volume create. В этом случае в docker-compose.yml может быть указана ссылка на внешний (external) том: Популяция тома с помощью контейнера При запуске нового контейнера, создающего том, когда контейнер содержит файлы и директории в монтируемой директории, содержимое этой директории копируется в том. После этого контейнер монтирует и использует том. Другие контейнеры, использующие том, будут иметь доступ к его предварительно заполненному (pre-populated) содержимому. В следующем примере мы создаем контейнер nginxtest и заполняем новый том nginx-vol содержимым из директории /usr/share/nginx/html, в которой хранится дефолтная разметка: Пример монтирования тома, доступного только для чтения Резервное копирование, восстановление и передача данных томов Для создания контейнера, монтирующего определенный том, используется флаг --volumes-from. Резервное копирование тома Создаем контейнер dbstore: Следующая команда Восстановление данных тома Создаем новый контейнер: Распаковываем архив в том нового контейнера: Удаление томов Данные томов сохраняются после удаления контейнеров. Существует 2 типа томов: Для удаления анонимного контейнера можно использовать флаг --rm. Например, здесь мы создаем анонимный том /foo и именованный том awesome:/bar: После удаления этого контейнера будет удален только том /foo. Для удаления всех неиспользуемых томов используется команда docker volume prune. Более подробную информацию о томах можно получить здесь. Сети позволяют контейнерам общаться между собой. Сетевой интерфейс Docker не зависит от платформы. Основная функциональность сетей представлена следующими драйверами: Резюме Обзор сетей. Использование дефолтной сети bridge Сейчас мы запустим 2 контейнера alpine и посмотрим, как они могут взаимодействовать между собой. Получаем список сетей bridge, host и none. Проверяем, что оба контейнера запущены: Видим созданные нами контейнеры в разделе "Containers" вместе с их IP-адресами (172.17.0.2 для alpine1 и 172.17.0.3 для alpine2). Видим приглашение #, свидетельствующее о том, что мы являемся пользователем root внутри контейнера. Взглянем на сетевой интерфейс alpine1: Первый интерфейс (lo) нас не интересует. Нас интересует IP-адрес второго интерфейса (172.17.0.2). Флаг -c 2 означает 2 попытки ping. Работает. Теперь попробуем обратиться по названию контейнера: Не работает. Обратите внимание: дефолтная сеть bridge не рекомендуется для использования в продакшне. Использование кастомной сети bridge В следующем примере мы снова создаем 2 контейнера alpine, но подключаем их к кастомной сети alpine-net. Эти контейнеры не подключены к дефолтной сети bridge. Затем мы запускаем третий alpine, подключаемый к bridge, но не к alpine-net, и четвертый alpine, подключаемый к обеим сетям. Анализируем сеть alpine-net: Получаем IP-адрес сети и пустой список подключенных контейнеров. Обратите внимание на сетевой шлюз, который отличается от шлюза bridge. Получаем список запущенных контейнеров: Видим, что к данной сети подключены контейнеры apline3 и alpine4. А к этой сети подключены контейнеры alpine1, alpine2 и alpine4. Это конец первой части. Благодарю за внимание и happy coding!  JavaScript Developer