Какой тип ресурсов содержит ссылку на образ нашего приложения из registry
Некоторые категории команд работают с Docker registry, и требуют соответствующей авторизации:
-
werf может делать pull образов из Docker registry. werf создает и обновляет образы в Docker registry. werf удаляет образы из Docker registry. werf требует доступа к образам в Docker registry и стадиям, которые также могут находиться в Docker registry.
Поддерживаемые имплементации
Build и Publish | Cleanup | |
---|---|---|
AWS ECR | ок | ок (с нативным API) |
Azure CR | ок | ок (с нативным API) |
Default | ок | ок |
Docker Hub | ок | ок (с нативным API) |
GCR | ок | ок |
GitHub Packages | ок | ок (с нативным API и только в приватных GitHub репозиториях) |
GitLab Registry | ок | ок |
Harbor | ок | ок |
JFrog Artifactory | ок | ок |
Quay | ок | ок |
В ближайшее время планируется добавить поддержку для Nexus Registry
Следующие имплементации полностью поддерживаются и от пользователя требуется только выполнить авторизацию Docker:
Azure CR, AWS ECR, Docker Hub и GitHub Packages имплементации поддерживают Docker Registry API, но не полностью. Для перечисленных имплементаций необходимо использовать нативное API для удаления тегов. Поэтому при очистке для werf может потребоваться дополнительные пользовательские данные.
Часть имплементаций не поддерживает вложенные репозитории (Docker Hub, GitHub Packages и Quay) или как в случае с AWS ECR пользователь должен создать репозитории вручную, используя UI или API, перед использованием werf.
Как хранить образы
Параметры images repo и images repo mode определяют где и как хранятся образы в репозитории (подробнее в разделе именование образов).
Параметр images repo может быть как адресом registry, так и repository.
Конечное имя Docker-образа зависит от images repo mode:
- IMAGES_REPO:IMAGE_NAME-TAG шаблон для monorepo мода;
- IMAGES_REPO/IMAGE_NAME:TAG шаблон для multirepo мода.
Docker-образы не могут храниться в registry. Поэтому комбинация registry и monorepo не имеет никакого смысла. Также, по той же причине, пользователь должен быть внимательным и не использовать registry с безымянным образов ( image:
).
При переходе на использование registry с multirepo важно не забыть переименовать безымяный образ в werf.yaml и удалить связанный managed image ( werf managed-images rm '
Таким образом, остаётся три возможных комбинации использования images repo и images repo mode.
registry + multirepo | repository + monorepo | repository + multirepo | |
---|---|---|---|
AWS ECR | ок | ок | ок |
Azure CR | ок | ок | ок |
Default | ок | ок | ок |
Docker Hub | ок | ок | не поддерживается |
GCR | ок | ок | ок |
GitHub Packages | ок | ок | не поддерживается |
GitLab Registry | ок | ок | ок |
Harbor | ок | ок | ок |
JFrog Artifactory | ок | ок | ок |
Quay | ок | ок | не поддерживается |
Большинство имплементаций поддерживают вложенные репозитории и по умолчанию используют multirepo images repo mode. Для остальных имплементаций значение по умолчанию зависит от указанного images repo.
AWS ECR
Как хранить образы
Хранение образов в AWS ECR не отличается от остальных имплементаций, но пользователь должен самостоятельно создать репозитории перед использованием werf.
Как чистить stages и images
werf использует AWS SDK для удаления тегов, поэтому перед использованием команд очистки пользователь должен:
-
( aws configure ) или
- Определить AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY переменные окружения.
Azure CR
Как чистить stages и images
Для того, чтобы werf начал удалять теги, пользователю необходимо выполнить следующие шаги:
- Установить Azure CLI ( az ).
- Выполнить авторизацию ( az login ).
Для удаления тегов пользователь должен иметь одну из следующих ролей: Owner , Contributor или AcrDelete (подробнее Azure CR roles and permissions)
Docker Hub
Как хранить образы
Если используется один Docker Hub аккаунт для всего проекта и есть необходимость хранения образов в отдельных репозиториях, то в качестве images repo необходимо указывать аккаунт:
Будьте бдительны и не используйте безымянные образы в таком случае, т.к. это может приводить к сложнодиагностируемым ошибкам.
При переходе на такой подход пользователь должен переименовать безымянный образ в werf.yaml и удалить соответствующий managed image в stages storage ( werf managed-images rm '
Если в werf конфигурации используется безымянный образ ( image:
) или пользователь хочет хранить все образы в одном репозитории, то необходимо использовать конкретный репозиторий в качестве параметра для images repo:
Пример
Пользователь имеет следующее:
Есть два возможных способа хранения конечных образов:
-
Параметр images repofoo приведёт к следующим тегам: foo/frontend:tag и foo/backend:tag .
Как чистить stages и images
Для чистки тегов в Docker Hub репозитории werf использует Docker Hub API и для работы требуются дополнительные параметры.
Пользователь должен определить token или username и password.
Используя следующий скрипт, пользователь может получить token самостоятельно:
В качестве токена нельзя использовать personal access token, т.к. удаление ресурсов возможно только при использовании основных учётных данных
Для того, чтобы задать параметры, следует использовать следующие опции и соответствующие переменные окружения:
- Для stages storage: --stages-storage-repo-docker-hub-token или --stages-storage-repo-docker-hub-username и --stages-storage-repo-docker-hub-password .
- Для images repo: --images-repo-docker-hub-token или --images-repo-docker-hub-username и --images-repo-docker-hub-password .
- И то и другое: --repo-docker-hub-token или --repo-docker-hub-username и --repo-docker-hub-password .
GitHub Packages
Как хранить образы
Если есть необходимость хранения образов в отдельных packages, то пользователь не должен указывать package в images repo:
Будьте бдительны и не используйте безымянные образы в таком случае, т.к. это может приводить к сложнодиагностируемым ошибкам.
При переходе на такой подход, пользователь должен переименовать безымянный образ в werf.yaml и удалить соответствующий managed image в stages storage ( werf managed-images rm '
Если в werf конфигурации используется безымянный образ ( image:
) или пользователь хочет хранить все образы в одном package, то необходимо использовать конкретный package в качестве параметра для images repo:
Пример
Пользователь имеет следующее:
Есть два возможных способа хранения конечных образов:
Как чистить stages и images
Для удаления версий package приватного репозитория используется GraphQL. От пользователя требуется token с read:packages , write:packages , delete:packages и repo scopes.
GitHub не поддерживает удание версий package в публичных репозиториях
Для того, чтобы задать параметры, следует использовать следующие опции и соответствующие переменные окружения:
- Для stages storage: --stages-storage-repo-github-token .
- Для images repo: --images-repo-github-token .
- И то и другое: --repo-github-token .
Как хранить образы
Если есть необходимость хранения образов в отдельных репозиториях, то не нужно указывать репозиторий images repo:
Будьте бдительны и не используйте безымянные образы в таком случае, т.к. это может приводить к сложнодиагностируемым ошибкам.
При переходе на такой подход, пользователь должен переименовать безымянный образ в werf.yaml и удалить соответствующий managed image в stages storage ( werf managed-images rm '
Если в werf конфигурации используется безымянный образ ( image:
) или пользователь хочет хранить все образы в одном репозиторий, то необходимо использовать конкретный репозиторий в качестве параметра для images repo:
Пример
Пользователь имеет следующее:
- Два образа в werf.yaml: frontend, backend.
- Организацию company в quay.io: quay.io/company.
Есть два возможных способа хранения конечных образов:
-
Параметр images repoquay.io/company приведёт к следующим тегам: quay.io/company/frontend:tag и quay.io/company/backend:tag .
Авторизация Docker
Все команды, требующие авторизации в Docker registry, не выполняют ее сами, а используют подготовленную конфигурацию Docker.
Конфигурация Docker — это папка, в которой хранятся данные авторизации используемые для доступа вразличные Docker registry и другие настройки Docker. По умолчанию, werf использует стандартную для Docker папку конфигурации:
/.docker . Другую используемую папку конфигурации можно указать с помощью параметра --docker-config , либо с помощью переменных окружения $DOCKER_CONFIG или $WERF_DOCKER_CONFIG . Все параметры и опции в файле конфигурации стандартны для Docker, их список можно посмотреть с помощью команды docker --config .
Для подготовки конфигурации Docker вы можете использовать команду docker login , либо, если вы выполняете werf в рамках CI-системы, вызвать команду werf ci-env (более подробно о подключении werf к CI-системам читай в соответствующем разделе).
Использование docker login при параллельном выполнении заданий в CI-системе может приводить к ошибкам выполнения заданий из-за работы с временными правами и состояния race condition (одно задание влияет на другое, переопределяя конфигурацию Docker). Поэтому, необходимо обеспечивать независимую конфигурацию Docker между заданиями, используя docker --config или werf ci-env
Контейнеры стали предпочтительным средством упаковки приложения со всеми зависимостями программного обеспечения и операционной системы, а затем доставки их в различные среды.
В этой статье рассматриваются различные способы контейнеризации приложения Spring Boot:
- создание образа Docker с помощью файла Docker,
- создание образа OCI из исходного кода с помощью Cloud-Native Buildpack,
- и оптимизация изображения во время выполнения путем разделения частей JAR на разные уровни с помощью многоуровневых инструментов.
Пример кода
Эта статья сопровождается примером рабочего кода на GitHub .
Терминология контейнеров
Мы начнем с терминологии контейнеров, используемой в статье:
Чтобы поместить приложение в контейнер, мы заключаем наше приложение в образ контейнера и публикуем этот образ в общий реестр. Среда выполнения контейнера извлекает этот образ из реестра, распаковывает его и запускает приложение внутри него.
Версия 2.3 Spring Boot предоставляет плагины для создания образов OCI.
Построение образа контейнера традиционным способом
Создавать образы Docker для приложений Spring Boot очень легко, добавив несколько инструкций в файл Docker.
Сначала мы создаем исполняемый файл JAR и, как часть инструкций файла Docker, копируем исполняемый файл JAR поверх базового образа JRE после применения необходимых настроек.
Давайте создадим наше приложение Spring на Spring Initializr с зависимостями web , lombok и actuator . Мы также добавляем rest контроллер, чтобы предоставить API с GET методом.
Создание файла Docker
Затем мы помещаем это приложение в контейнер, добавляя Dockerfile :
Наш файл Docker содержит базовый образ, из adoptopenjdk , поверх которого мы копируем наш файл JAR, а затем открываем порт, 8080 который будет прослушивать запросы.
Сборка приложения
Сначала нужно создать приложение с помощью Maven или Gradle. Здесь мы используем Maven:
Это создает исполняемый JAR-файл приложения. Нам нужно преобразовать этот исполняемый JAR в образ Docker для работы в движке Docker.
Создание образа контейнера
Затем мы помещаем этот исполняемый файл JAR в образ Docker, выполнив команду docker build из корневого каталога проекта, содержащего файл Docker, созданный ранее:
Мы можем увидеть наше изображение в списке с помощью команды:
Результат выполнения вышеуказанной команды включает в себя наш образ usersignup вместе с базовым изображением, adoptopenjdk , указанным в нашем файле Docker.
Просмотр слоев внутри изображения контейнера
Давайте посмотрим на стопку слоев внутри изображения. Мы будем использовать инструмент dive, чтобы просмотреть эти слои:
Вот часть результатов выполнения команды Dive:
Как мы видим, прикладной уровень составляет значительную часть размера изображения. Мы хотим уменьшить размер этого слоя в следующих разделах в рамках нашей оптимизации.
Создание образа контейнера с помощью Buildpack
Преимущество облачных сборочных пакетов
Одним из основных преимуществ использования Buildpack для создания образов является то, что изменениями конфигурации образа можно управлять централизованно (builder) и распространять на все приложения, использующие builder.
Сборочные пакеты были тесно связаны с платформой. Cloud-Native Buildpacks обеспечивают стандартизацию между платформами, поддерживая формат образа OCI, который гарантирует, что образ может запускаться движком Docker.
Использование плагина Spring Boot
Плагин Spring Boot создает образы OCI из исходного кода с помощью Buildpack. Образы создаются с использованием bootBuildImage задачи (Gradle) или spring-boot:build-image цели (Maven) и локальной установки Docker.
Мы можем настроить имя образа, необходимого для отправки в реестр Docker, указав имя в image tag :
Давайте воспользуемся Maven для выполнения build-image цели по созданию приложения и созданию образа контейнера. Сейчас мы не используем никаких файлов Docker.
Результат будет примерно таким:
Из выходных данных мы видим, что paketo Cloud-Native buildpack используется для создания работающего образа OCI. Как и раньше, мы можем увидеть образ, указанный как образ Docker, выполнив команду:
Создание образа контейнера с помощью Jib
Настраиваем jib-maven-plugin в pom.xml:
Далее мы запускаем плагин Jib с помощью команды Maven, чтобы построить приложение и создать образ контейнера. Как и раньше, здесь мы не используем никаких файлов Docker:
После выполнения указанной выше команды Maven мы получаем следующий вывод:
Выходные данные показывают, что образ контейнера создан и помещен в реестр.
Мотивации и методы создания оптимизированных изображений
У нас есть две основные причины для оптимизации:
- Производительность: в системе оркестровки контейнеров образ контейнера извлекается из реестра образов на хост, на котором запущен механизм контейнера. Этот процесс называется планированием. Извлечение образов большого размера из реестра приводит к длительному времени планирования в системах оркестровки контейнеров и длительному времени сборки в конвейерах CI.
- Безопасность: изображения большого размера также имеют большую область для уязвимостей.
Образ Docker состоит из стека слоев, каждый из которых представляет инструкцию в нашем Dockerfile. Каждый слой представляет собой дельту изменений нижележащего слоя. Когда мы извлекаем образ Docker из реестра, он извлекается слоями и кэшируется на хосте.
Spring Boot использует «толстый JAR» в качестве формата упаковки по умолчанию. Когда мы просматриваем толстый JAR, мы видим, что приложение составляет очень маленькую часть всего JAR. Это часть, которая меняется чаще всего. Оставшаяся часть состоит из зависимостей Spring Framework.
Формула оптимизации сосредоточена вокруг изоляции приложения на отдельном уровне от зависимостей Spring Framework.
Слой зависимостей, формирующий основную часть толстого JAR-файла, загружается только один раз и кэшируется в хост-системе.
Только тонкий слой приложения вытягивается во время обновлений приложения и планирования контейнеров, как показано на этой диаграмме:
В следующих разделах мы рассмотрим, как создавать эти оптимизированные образы для приложения Spring Boot.
Создание оптимизированного образа контейнера для приложения Spring Boot с помощью Buildpack
Spring Boot 2.3 поддерживает многоуровневость путем извлечения частей толстого JAR-файла в отдельные слои. Функция наслоения по умолчанию отключена, и ее необходимо явно включить с помощью плагина Spring Boot Maven:
Мы будем использовать эту конфигурацию для создания нашего образа контейнера сначала с помощью Buildpack, а затем с помощью Docker в следующих разделах.
Давайте запустим build-image цель Maven для создания образа контейнера:
Если мы запустим Dive, чтобы увидеть слои в результирующем изображении, мы увидим, что уровень приложения (обведен красным) намного меньше в диапазоне килобайт по сравнению с тем, что мы получили с использованием толстого формата JAR:
Создание оптимизированного образа контейнера для приложения Spring Boot с помощью Docker
Вместо использования плагина Maven или Gradle мы также можем создать многоуровневый образ JAR Docker с файлом Docker.
Когда мы используем Docker, нам нужно выполнить два дополнительных шага для извлечения слоев и копирования их в окончательный образ.
Содержимое полученного JAR после сборки с помощью Maven с включенной функцией наслоения будет выглядеть следующим образом:
В выходных данных отображается дополнительный JAR с именем spring-boot-jarmode-layertools и layersfle.idx файл. Этот дополнительный JAR-файл предоставляет возможность многоуровневой обработки, как описано в следующем разделе.
Извлечение зависимостей на отдельных слоях
Чтобы просмотреть и извлечь слои из нашего многоуровневого JAR, мы используем системное свойство -Djarmode=layertools для запуска spring-boot-jarmode-layertools JAR вместо приложения:
Выполнение этой команды дает вывод, содержащий доступные параметры команды:
Вывод показывает команды list , extract и help с help быть по умолчанию. Давайте запустим команду с list опцией:
Мы видим список зависимостей, которые можно добавить как слои.
Слои по умолчанию:
любая зависимость, версия которой не содержит SNAPSHOT
Классы загрузчика JAR
любая зависимость, версия которой содержит SNAPSHOT
классы приложений и ресурсы
Слои определены в layers.idx файле в том порядке, в котором они должны быть добавлены в образ Docker. Эти слои кэшируются в хосте после первого извлечения, поскольку они не меняются. На хост загружается только обновленный уровень приложения, что происходит быстрее из-за уменьшенного размера .
Построение образа с зависимостями, извлеченными в отдельные слои
Мы построим финальный образ в два этапа, используя метод, называемый многоэтапной сборкой . На первом этапе мы извлечем зависимости, а на втором этапе мы скопируем извлеченные зависимости в окончательный образ .
Давайте модифицируем наш файл Docker для многоэтапной сборки:
Собираем образ Docker с помощью команды:
После выполнения этой команды мы получаем такой вывод:
Мы видим, что образ Docker создается с идентификатором изображения, а затем тегируется.
Наконец, мы запускаем команду Dive, как и раньше, чтобы проверить слои внутри сгенерированного образа Docker. Мы можем указать идентификатор изображения или тег в качестве входных данных для команды Dive:
Как видно из выходных данных, уровень, содержащий приложение, теперь занимает всего 11 КБ, а зависимости кэшируются в отдельных слоях.
Извлечение внутренних зависимостей на отдельных слоях
Мы можем дополнительно уменьшить размер уровня приложения, извлекая любые из наших пользовательских зависимостей в отдельный уровень вместо того, чтобы упаковывать их вместе с приложением, объявив их в yml подобном файле с именем layers.idx :
В этом файле layers.idx мы добавили настраиваемую зависимость с именем, io.myorg содержащим зависимости организации, полученные из общего репозитория.
Вывод
В этой статье мы рассмотрели использование Cloud-Native Buildpacks для создания образа контейнера непосредственно из исходного кода. Это альтернатива использованию Docker для создания образа контейнера обычным способом: сначала создается толстый исполняемый файл JAR, а затем упаковывается его в образ контейнера, указав инструкции в файле Docker.
Мы также рассмотрели оптимизацию нашего контейнера, включив функцию наслоения, которая извлекает зависимости в отдельные уровни, которые кэшируются на хосте, а тонкий слой приложения загружается во время планирования в механизмах выполнения контейнера.
Вы можете найти весь исходный код, использованный в статье на Github .
Справочник команд
Вот краткое изложение команд, которые мы использовали в этой статье для быстрого ознакомления.
Создание образа контейнера с помощью файла Docker:
Собираем образ контейнера из исходного кода (без Dockerfile):
Просмотр слоев зависимостей. Перед сборкой JAR-файла приложения убедитесь, что функция наслоения включена в spring-boot-maven-plugin:
Извлечение слоев зависимостей. Перед сборкой JAR-файла приложения убедитесь, что функция наслоения включена в spring-boot-maven-plugin:
Просмотр списка образов контейнеров
Просмотр слев внутри образа контейнера (убедитесь, что установлен инструмент для погружения):
Docker — один из самых известных инструментов по работе с контейнерами. В статье мы расскажем что такое Docker-контейнеры, где они применяются и чем могут быть вам полезны.
Managed Kubernetes помогает разворачивать контейнерные приложения в инфраструктуре Selectel. Сосредоточьтесь на разработке, а мы займемся рутинными операциями по обеспечению работы вашего кластера Kubernetes.
Также будет практическая часть: мы создадим небольшое приложение, обернем его в образ и запустим. Все действия будем показывать на примере виртуальной машины на платформе Selectel.
Контейнеры — хорошая альтернатива аппаратной виртуализации. Они позволяют запускать приложения в изолированном окружении, но при этом потребляют намного меньше ресурсов.
В первую очередь эта статья будет полезна тем, кто вообще не знаком с контейнерами или Docker. Мы расскажем самые базовые вещи, а наш пример по созданию приложения будет довольно простым. Но это позволит вам понять основы Docker и затем двигаться дальше — изучать более сложные материалы.
Что такое контейнеры
Прежде чем рассказывать про Docker, нужно сказать несколько слов о технологии контейнеризации.
Контейнеры — это способ упаковать приложение и все его зависимости в единый образ. Этот образ запускается в изолированной среде, не влияющей на основную операционную систему. Контейнеры позволяют отделить приложение от инфраструктуры: разработчикам не нужно задумываться, в каком окружении будет работать их приложение, будут ли там нужные настройки и зависимости. Они просто создают приложение, упаковывают все зависимости и настройки в единый образ. Затем этот образ можно запускать на других системах, не беспокоясь, что приложение не запустится.
Docker — это платформа для разработки, доставки и запуска контейнерных приложений. Docker позволяет создавать контейнеры, автоматизировать их запуск и развертывание, управляет жизненным циклом. Он позволяет запускать множество контейнеров на одной хост-машине.
Контейнеризация похоже на виртуализацию, но это не одно и то же. Виртуализация работает как отдельный компьютер, со своим виртуальным оборудованием и операционной системой. При этом внутри одной ОС можно запустить другую ОС. В случае контейнеризации виртуальная среда запускается прямо из ядра основной операционной системы и не виртуализирует оборудование. Это означает, что контейнер может работать только в той же ОС, что и основная. При этом так как контейнеры не виртуализируют оборудование, они потребляют намного меньше ресурсов.
Преимущества использования контейнеров Docker
Контейнеры в целом упрощают работу как программистам, так администраторам, которые развертывают эти приложения.
Docker решает проблемы зависимостей и рабочего окружения
Контейнеры позволяют упаковать в единый образ приложение и все его зависимости: библиотеки, системные утилиты и файлы настройки. Это упрощает перенос приложения на другую инфраструктуру.
Например, разработчики создают приложение в системе разработки, там все настроено и приложение работает. Когда приложение готово, его нужно перенести в систему тестирования и затем в продуктивную среду. И если в этих системах будет не хватать какой-нибудь зависимости, то приложение не будет работать. В этом случае программистам придется отвлечься от разработки и совместно с командой поддержки разбираться в ситуации.
Контейнеры позволяют избежать такой проблемы, потому что они содержат в себе все необходимое для запуска приложения. Программисты смогут сосредоточиться на разработке, а не решении инфраструктурных проблем.
Изоляция и безопасность
Контейнер — это набор процессов, изолированных от основной операционной системы. Приложения работают только внутри контейнеров, и не имеют доступа к основной операционной системе. Это повышает безопасность приложений, потому что они не смогут случайно или умышленно навредить основной системе. Если приложение в контейнере завершится с ошибкой или зависнет, это никак не затронет основную ОС.
Ускорение и автоматизация развертывания приложений и масштабируемость
Контейнеры упрощают развертывание приложений. В классическом подходе для установки программы может потребоваться выполнить несколько действий: выполнить скрипт, изменить файлы настроек и так далее. В этом процессе не исключена вероятность человеческой ошибки: пользователь запустит скрипт два раза, перепутает последовательность или что-то не поймет. Контейнеры позволяют полностью автоматизировать этот процесс, так как включают в себя все нужные зависимости и порядок выполнения действий.
Также контейнеры упрощают развертывание на нескольких серверах. В классическом подходе для того, чтобы развернуть одно и то же приложение на нескольких машинах, нужно будет повторять одни и те же действия. Контейнеры избавляют от этой рутинной работы и позволяют автоматизировать развертывание.
Контейнеры приближают к микросервисной архитектуре
Контейнеры хорошо вписываются в микросервисную архитектуру. Это подход к разработке, при котором приложение разбивается на небольшие компоненты, по возможности независимые. Обычно противопоставляется монолитной архитектуре, где все части системы сильно связаны друг с другом.
Это позволяет разрабатывать новую функциональность быстрее, ведь в случае с монолитной архитектурой изменение какой-то части может затронуть всю остальную систему.
Docker compose — одновременно развернуть несколько контейнеров
Docker-compose позволяет разворачивать и настраивать несколько контейнеров одновременно. Например, для веб-приложения нужно развернуть стек LAMP: Linux, Apache, MySQL, PHP. Каждое из приложений — это отдельный контейнер. Но в этой ситуации нам нужны именно все контейнеры вместе, а не отдельно взятое приложение. Docker-compose позволяет развернуть и настроить все приложения одной командой, а без него пришлось разворачивать и настраивать каждый контейнер отдельно.
Хранение данных в Docker
Она из главных особенностей контейнеров — эфемерность. Это означает, что контейнеры могут быть в любой момент остановлены, перезапущены или уничтожены. При этом все накопленные данные в контейнере будут потеряны. Поэтому приложения нужно разрабатывать так, чтобы они не полагались на хранилище данных в контейнере, это называется принципом Stateless.
Это хорошо подходит для приложений или сервисов, которые не сохраняют результаты своей работы. Например, функции расчета или преобразования данных: им на вход поступил один набор данных, они его преобразовали или рассчитали и вернули результат. Все, ничего никуда сохранять не нужно.
Но далеко не все приложения такие, и есть много данных, которые нужно сохранить. В контейнерах для этого предусмотрены несколько способов.
Тома (Docker volumes)
Это способ, при котором докер сам создает директории для хранения данных. Их можно сделать доступными для разных контейнеров, чтобы они могли обмениваться данными. По умолчанию эти директории создаются на хост-машине, но можно использовать и удаленные хранилища: файловый сервер или объектное хранилище.
Монтирование каталога (bind mount)
В этом случае директория сначала создается в хост-системе, а уже потом монтируется в докер контейнеры.
Но этот способ не рекомендуется, потому что он усложняет резервное копирование, миграцию и совместное использование данных несколькими контейнерами.
Архитектура (компоненты) Docker
Теперь расскажем подробнее про компоненты, из которых состоит Docker.
Docker daemon
Это сервис, через который осуществляется все взаимодействие с контейнерами: создание и удаление, запуск и остановка. Этот сервис работает в фоновом режиме и получает команды от интерфейса командной строки или API.
Docker client (клиент)
Это интерфейс командной строки для управления docker daemon. Мы пользуемся этим клиентом, когда создаем и разворачиваем контейнеры, а клиент отправляет эти запросы в docker daemon.
Docker image (образ)
Это неизменяемый файл (образ), из которого разворачиваются контейнеры. Приложения упаковываются именно в образы, из которых потом уже создаются контейнеры.
Приведем аналогию на примере установки операционной системы. В дистрибутиве (образе) ОС есть все, что необходимо для ее установки. Но этот образ нельзя запустить, для начала его нужно «развернуть» в готовую ОС. Так вот, дистрибутив для установки ОС — это Docker image, а установленная и работающая ОС — это Docker container. Но контейнеры обычно разворачиваются одной командой — это намного проще и быстрее, чем установка ОС.
Docker container (контейнер)
Это уже развернутое из образа и работающее приложение.
Docker Registry
Это репозиторий с докер-образами. Разработчики создают образы своих программ и выкладывают их в репозиторий, чтобы их можно было скачать и воспользоваться ими. Распространенный публичный репозиторий — Docker Hub. В нем собраны образы множества популярных программ или платформ: базы данных, веб-серверы, компиляторы, операционные системы и так далее. Также можно создать свой приватный репозиторий, например внутри компании. Разработчики будут размещать там образы, которые будут использоваться всей компанией.
Dockerfile
Dockerfile — это инструкция для сборки образа. Это простой текстовый файл, содержащий по одной команде в каждой строке. В нем указываются все программы, зависимости и образы, которые нужны для разворачивания образа.
Для примера рассмотрим dockerfile, который мы будем использовать далее в этой статье чтобы развернуть собственное приложение:
Первая строчка означает, что за основу мы берем образ с названием python версии 3 это называется базовый образ. Docker найдет его в docker registry, скачает и будет использовать за основу. Вторая строчка означает, что нужно скопировать файл main.py в корень файловой системы контейнера. Третья строчка означает, что нужно запустить python и передать ему в качестве параметра файл main.py.
Далее рассмотрим примеры нескольких команд докер и что происходит, когда мы их выполняем.
Все эти команды выполняются в Docker client, который отправляет их в docker daemon:
- Команда docker build (зеленая стрелка) читает dockerfile и собирает образ.
- Команда docker pull (красная стрелка) скачивает образ из docker registry. По умолчанию docker скачивает образы из публичного репозитория Docker Hub. Но можно создать свой репозиторий и настроить докер, чтобы он работал с ним.
- Команда docker run (черная стрелка) берет образ и разворачивает его в контейнер.
Создаем виртуальную машину для работы с докер
Перейдем к практической части. Мы установим докер, создадим приложение, обернем его в контейнер и запустим. Мы для примера будем использовать виртуальную машину на платформе Selectel.
В панели управления заходим в раздел «Облачная платформа» — «Серверы», нажимаем кнопку «Создать сервер».
На следующем экране выбираем параметры сервера: имя, регион, ОС, параметры производительности и так далее. Сейчас для нас важны параметры «Источник» — выбираем ОС Ubuntu 20.04 и «Конфигурация» — выбираем 2 vCPU и 8 ГБ оперативной памяти.
Далее обратите внимание на разделы «Сеть» и «Доступ». В разделе «Сеть» нужно выбрать подсеть с публичным адресом, чтобы к виртуальной машине можно было подключаться из интернета. В разделе «Доступ» будет указан пароль для root-пользователя, а также необходимо загрузить SSH-ключ, чтобы подключаться к виртуальной машине. Подробную инструкцию о подключении смотрите в Базе знаний.
После этого внизу страницы нажимаем кнопку «Создать». Виртуальная машина создается за несколько минут, и после того, как она перейдет в статус ACTIVE, к ней можно подключаться по SSH.
Установка Docker
Мы рассмотрим установку докера на примере Ubuntu. Если у вас другой дистрибутив Linux или операционная система — ищите соответствующую инструкцию на официальном сайте.
Для начала синхронизируем пакетную базу apt и установим нужные зависимости:
Далее импортируем GPG-ключ для репозитория docker:
Теперь добавим новый репозиторий в список apt:
Теперь можно устанавливать докер:
По умолчанию, доступ к docker daemon есть только у пользователя root. Чтобы с докером могли работать и другие пользователи, их нужно добавить в специальную группу — docker. Выполните эту команду из под обычного пользователя:
После этого необходимо перелогиниться, чтобы изменение вступило в силу.
Запуск контейнера
Теперь попробуем запустить какое-нибудь готовое приложение. Выполните команду:
После выполнения команды в терминале появится строка hello from ubuntu, и контейнер сразу остановится. Теперь выполним другую команду:
Эта команда запустит контейнер в интерактивном режиме, то есть контейнер запустится и будет ждать дальнейших команд. При этом мы окажемся внутри операционной системы контейнера: запустится оболочка (bash), и мы сможем выполнять какие-то команды внутри контейнера. Чтобы выйти из контейнера, введите команду exit.
Создание собственного образа и запуск контейнера
Теперь создадим HelloWorld-приложение на Python, обернем его в образ и запустим.
Для начала создадим директорию, в которой мы будем работать и перейдем в нее:
Создадим файл main.py и запишем в него одну строчку кода:
Проверим, что наша программа работает. Для этого выполним команду:
В первой строке мы указываем образ, который берем за основу. Так мы пишем приложение на Python, нужно чтобы в нашем образе он уже был установлен. Самый простой способ это сделать — использовать готовый официальный образ с Docker Hub. Цифра 3 — это тег. Он означает, что нужно использовать третью версию Python. Вместо этого можно было бы использовать тег latest, который означает самую последнюю версию, или можно было указать номер конкретной версии, например 3.8.8.
Во второй строчке мы копируем наш файл main.py в корневую директорию образа.
Третья строчка — запускаем python и передаем ему в качестве параметра имя нашего файла.
Теперь из этого докер-файла можно собирать образ. Выполним команду:
Параметр -t обозначает имя нашего образа, мы назвали его first-docker-app.
Так как у нас еще нет скачанного образа python, то докер сам скачает его из Docker Hub и затем будет использовать его в качестве основы для создания нашего образа.
Проверим список установленных у нас образов:
Мы увидим, что у нас установлено три образа:
first-docker-app — это наш образ, который мы только что создали. python — это образ python, который докер автоматически скачал чтобы собрать наш образ. ubuntu — образ, который мы пробовали для запуска готового приложения.
Теперь создадим контейнер из нашего образа и запустим его:
В результате нам выведется результат: Hello from python.
Итог: Мы создали свое приложение, упаковали его в докер-образ и запустили. Конечно, это очень простой пример. Наша программа состоит всего из одной строчки, а dockerfile из трех. Но это позволяет понять базовые принципы работы докера, как он устроен, как создавать свои образы и запускать контейнеры.
Список полезных команд
Теперь приведем список полезных команд, которые могут пригодиться при работе с докером.
Посмотреть список всех контейнеров
Эта команда выведет список всех докер контейнеров:
Но по умолчанию выводятся только работающие контейнеры. Чтобы вывести все, в том числе и остановленные, используйте опцию -a:
Остановить и удалить все докер контейнеры
Чтобы удалить контейнеры, сначала их нужно остановить. Первая команда остановит запущенные контейнеры, если они есть. А вторая команда — удалит их.
Запустить контейнер с последующим удалением
Посмотреть список всех скачанных образов
Удалить докер образ
Чтобы принудительно удалить образ, добавьте флаг -f:
Получить список всех контейнеров, созданных из определенного образа
В статье мы рассмотрели, что такое контейнеры и Docker, как они работают и чем отличаются от виртуализации. Также мы создали простое python-приложение, обернули его в докер-образ и запустили контейнер.
Мы рассказали основы технологий, но не затронули более сложные темы, вроде Docker Swarm, настройку сети или настройки процессов CI/CD. Но этого вполне достаточно, чтобы погрузиться в основы технологий.
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents Loading
Copy raw contents
Copy raw contents
Веб-приложения и Докер
Супер! Теперь мы научились работать с docker run , поиграли с несколькими контейнерами и разобрались в терминологии. Вооруженные этими знаниями, мы готовы переходить к реальным штукам: деплою веб-приложений с Докером!
Давайте начнем с малого. Вначале рассмотрим самый простой статический веб-сайт. Скачаем образ из Docker Hub, запустим контейнер и посмотрим, насколько легко будет запустить веб-сервер.
Поехали. Для одностраничного сайта нам понадобится образ, который я заранее создал для этого пособия и разместил в регистре - prakhar1989/static-site. Можно скачать образ напрямую командой docker run .
В нашем случае клиент не открывает никакие порты, так что нужно будет перезапустить команду docker run чтобы сделать порты публичными. Заодно давайте сделаем так, чтобы терминал не был прикреплен к запущенному контейнеру. В таком случае можно будет спокойно закрыть терминал, а контейнер продолжит работу. Это называется detached mode.
Флаг -d открепит (detach) терминал, флаг -P сделает все открытые порты публичными и случайными, и, наконец, флаг --name это имя, которое мы хотим дать контейнеру. Теперь можно увидеть порты с помощью команды docker port [CONTAINER] .
Замечание: Если вы используете docker-toolbox, то, возможно, нужно будет использовать `docker-machine ip default чтобы получить IP-адрес.
Также можете обозначить свой порт. Клиент будет перенаправлять соединения на него.
Чтобы остановить контейнер запустите docker stop и укажите идентификатор (ID) контейнера.
Согласитесь, все было очень просто. Чтобы задеплоить это на реальный сервер, нужно просто установить Докер и запустить команду выше. Теперь, когда вы увидели, как запускать веб-сервер внутри образа, вам, наверное, интересно — а как создать свой Докер-образ? Мы будем изучать эту тему в следующем разделе.
Мы касались образов ранее, но в этом разделе мы заглянем глубже: что такое Докер-образы и как создавать собственные образы. Наконец, мы используем собственный образ чтобы запустить приложение локально, а потом задеплоим его на AWS, чтобы показать друзьям. Круто? Круто! Давайте начнем.
Образы это основы для контейнеров. В прошлом примере мы скачали (pull) образ под названием Busybox из регистра, и попросили клиент Докера запустить контейнер, основанный на этом образе. Чтобы увидеть список доступных локально образов, используйте команду docker images .
Это список образов, которые я скачал из регистра, а также тех, что я сделал сам (скоро увидим, как это делать). TAG — это конкретный снимок или снэпшот (snapshot) образа, а IMAGE ID — это соответствующий уникальный идентификатор образа.
Для простоты, можно относиться к образу как к git-репозиторию. Образы можно коммитить с изменениями, и можно иметь несколько версий. Если не указывать конкретную версию, то клиент по умолчанию использует latest . Например, можно скачать определенную версию образа ubuntu :
Чтобы получить новый Докер-образ, можно скачать его из регистра (такого, как Docker Hub) или создать собственный. На Docker Hub есть десятки тысяч образов. Можно искать напрямую из командной строки с помощью docker search .
Важно понимать разницу между базовыми и дочерними образами:
- Base images (базовые образы) — это образы, которые не имеют родительского образа. Обычно это образы с операционной системой, такие как ubuntu, busybox или debian.
- Child images (дочерние образы) — это образы, построенные на базовых образах и обладающие дополнительной функциональностью.
Существуют официальные и пользовательские образы, и любые из них могут быть базовыми и дочерними.
- Официальные образы — это образы, которые официально поддерживаются командой Docker. Обычно в их названии одно слово. В списке выше python , ubuntu , busybox и hello-world — базовые образы.
- Пользовательские образы — образы, созданные простыми пользователями вроде меня и вас. Они построены на базовых образах. Обычно, они называются по формату user/image-name .
Наш первый образ
Теперь, когда мы лучше понимаем, что такое образы и какие они бывают, самое время создать собственный образ. Цель этого раздела — создать образ с простым приложением на Flask. Для этого пособия я сделал маленькое приложение, которое выводит случайную гифку с кошкой. Ну, потому что, кто не любит кошек? Склонируйте этот репозиторий к себе на локальную машину.
Вначале давайте проверим, что приложение работает локально. Войдите в директорию flask-app командой cd и установите зависимости.
Замечание: Если команда pip install падает с ошибками "permission denied", то попробуйте запустить ее с sudo . Если не хотите устанавливать пользовательские пакеты на уровне системы, то используйте команду pip install --user -r requirements.txt .
Выглядит отлично, правда? Теперь нужно создать образ с приложением. Как говорилось выше, все пользовательские образы основаны на базовом образе. Так как наше приложение написано на Питоне, нам нужен базовый образ Python 3. В частности, нам нужна версия python:3-onbuild базового образа с Питоном.
Что за версия onbuild , спросите вы?
Эти образы включают несколько триггеров ONBUILD, которых обычно достаточно чтобы быстро развернуть приложение. При сборке будет скопирован файл requirements.txt , будет запущен pip install с этим файлом, а потом текущая директория будет скопирована в /usr/src/app .
Другими словами, версия onbuild включает хелперы, которые автоматизируют скучные процессы запуска приложения. Вместо того, чтобы вручную выполнять эти задачи (или писать скрипты), образы делают все за вас. Теперь у нас есть все ингредиенты для создания своего образа: работающее веб-приложение и базовый образ. Как это сделать? Ответ: использовать Dockerfile.
Dockerfile — это простой текстовый файл, в котором содержится список команд Докер-клиента. Это простой способ автоматизировать процесс создания образа. Самое классное, что команды в Dockerfile почти идентичны своим аналогам в Linux. Это значит, что в принципе не нужно изучать никакой новый синтаксис чтобы начать работать с докерфайлами.
В директории с приложением есть Dockerfile, но так как мы делаем все впервые, нам нужно создать его с нуля. Создайте новый пустой файл в любимом текстовом редакторе, и сохраните его в той же директории, где находится flask-приложение. Назовите файл Dockerfile .
Для начала укажем базовый образ. Для этого нужно использовать ключевое слово FROM .
Дальше обычно указывают команды для копирования файлов и установки зависимостей. Но к счастью, onbuild-версия базового образа берет эти задачи на себя. Дальше нам нужно указать порт, который следует открыть. Наше приложение работает на порту 5000, поэтому укажем его:
Последний шаг — указать команду для запуска приложения. Это просто python ./app.py . Для этого используем команду CMD:
Главное предназначение CMD — это сообщить контейнеру какие команды нужно выполнить при старте. Теперь наш Dockerfile готов. Вот как он выглядит:
Теперь можно создать образ. Команда docker build занимается сложной задачей создания образа на основе Dockerfile.
Листинг ниже демонстрирует процесс. Перед тем, как запустите команду сами (не забудьте точку в конце), проверьте, чтобы там был ваш username вместо моего. Username должен соответствовать тому, что использовался при регистрации на Docker hub. Если вы еще не регистрировались, то сделайте это до выполнения команды. Команда docker build довольно проста: она принимает опциональный тег с флагом -t и путь до директории, в которой лежит Dockerfile .
Если у вас нет образа python:3-onbuild , то клиент сначала скачает его, а потом возьмется за создание вашего образа. Так что, вывод на экран может отличаться от моего. Посмотрите внимательно, и найдете триггеры onbuild . Если все прошло хорошо, то образ готов! Запустите docker images и увидите свой образ в списке.
Последний шаг — запустить образ и проверить его работоспособность (замените username на свой):
Зайдите на указанный URL и увидите приложение в работе.
Поздравляю! Вы успешно создали свой первый образ Докера!
Что хорошего в приложении, которое нельзя показать друзьям, правда? Так что в этом разделе мы научимся деплоить наше офигенное приложение в облако. Будем использовать AWS Elastic Beanstalk чтобы решить эту задачу за пару кликов. Мы увидим, как с помощью Beanstalk легко управлять и масштабировать наше приложение.
Первое, что нужно сделать перед деплоем на AWS это опубликовать наш образ в регистре, чтобы можно было скачивать его из AWS. Есть несколько Docker-регистров (или можно создать собственный). Для начала, давайте используем Docker Hub. Просто выполните:
Если это ваша первая публикация, то клиент попросит вас залогиниться. Введите те же данные, что используете для входа в Docker Hub.
Не забудьте заменить название образа на свое. Очень важно сохранить формат username/image_name , чтобы клиент понимал, куда публиковать образ.
После этого можете посмотреть на свой образ на Docker Hub. Например, вот страница моего образа.
Замечание: один важный момент, который стоит прояснить перед тем, как продолжить — не обязательно хранить образ в публичном регистре (или в любом другом регистре вообще) чтобы деплоить на AWS. Если вы пишете код для следующего многомиллионного стартапа-единорога, то можно пропустить этот шаг. Мы публикуем свой образ чтобы упростить деплой, пропустив несколько конфигурационных шагов.
Теперь наш образ онлайн, и любой докер-клиент может поиграться с ним с помощью простой команды:
Если в прошлом вы мучались с установкой локального рабочего окружения и попытками поделиться своей конфигурацией с коллегами, то понимаете, как круто это звучит. Вот почему Докер — это сила!
AWS Elastic Beanstalk (EB) это PaaS (Platform as a Service — платформа как сервис) от Amazon Web Services. Если вы использовали Heroku, Google App Engine и т.д., то все будет привычно. Как разработчик, вы сообщаете EB как запускать ваше приложение, а EB занимается всем остальным, в том числе масштабированием, мониторингом и даже апдейтами. В апреле 2014 в EB добавили возможность запускать Докер-контейнеры, и мы будем использовать именно эту возможность для деплоя. У EB очень понятный интерфейс командной строки, но он требует небольшой конфигурации, поэтому для простоты давайте используем веб-интерфейс для запуска нашего приложения.
Чтобы продолжать, вам потребуется работающий аккаунт на AWS. Если у вас его нет, то создайте его. Для этого потребуется ввести данные кредитной карты. Но не волнуйтесь, эта услуга бесплатна, и все, что будет происходить в рамках этого пособия тоже бесплатно.
- Войдите в свою консоль AWS.
- Нажмите на Elastic Beanstalk. Ссылка находится в секции compute, в левом верхнем углу. Или просто перейдите сюда.
- Нажмите на "Create New Application" в верхнем правом углу.
- Дайте своему приложению запоминающееся (но уникальное) имя и, если хотите, добавьте описание.
- на экране New Environment выберите Web Server Environment.
- Следующий экран показан ниже. Выберите Docker из готовых вариантов конфигурации. Можно оставить Environment type как есть. Нажмите Next.
- Тут мы будем сообщать системе EB о нашем образе. Откройте файл Dockerrun.aws.json в директории flask-app и измените Name образа, чтобы оно соответствовало названию вашего образа. Не волнуйтесь, я опишу содержание файла попозже. Потом выберите вариант "upload your own" и выберите файл.
- Далее, выберите название окружения и URL. Этот URL как раз можно будет давать друзьям, так что постарайтесь придумать что-нибудь попроще.
- Пока не будем вносить никаких правок в секцию Additional Resources. Нажмите Next и переходите к Configuration Details.
- В этой секции вам нужно выбрать тип инстанса t1.micro . Это очень важно, потому что это бесплатный тип от AWS. Если хотите, можно выбрать пару ключей для входа. Если вы не знаете, что это значит, то не волнуйтесь и просто пропустите эту часть. Все остальное можно оставить по умолчанию и продолжать.
- Также не нужно указывать никакие Environment Tags and Permissions, так что просто жмите Next два раза подряд. В конце будет экран Review. Если все выглядит нормально, то нажимайте кнопку Launch.
- На последнем экране будет несколько спиннеров. Это поднимается и настраивается ваше окружение. Обычно, нужно около пяти минут для первой настройки.
Пока ждем, давайте быстренько взглянем на файл Dockerrun.aws.json . Это файл для AWS, в котором находится информация о приложении конфигурации Докера. EB получает информацию из этого файла.
Файл довольно понятный, но всегда можно обратиться к официальной документации. Мы указываем название образа, и EB будет использовать его заодно с портом.
К этому моменту инстанс уже должен быть готов. Зайдите на страницу EB и увидите зеленый индикатор успешного запуска приложения.
Зайдите на указанный URL в браузере и увидите приложение во все красе. Пошлите адрес своим друзьям, чтобы все могли насладиться гифками с кошками.
Поздравляю! Вы задеплоили свое первое Докер-приложение! Может показаться, что было очень много шагов, но с командной утилитой EB можно имитировать функциональность Хероку несколькими нажатиями клавиш. Надеюсь, вы согласитесь, что Докер сильно упрощает процесс и минимизирует болезненные моменты деплоя в облако. Я советую вам почитать документацию AWS про single-container Docker environment чтобы понимать, какие существуют возможности в EB.
В следующей, последней части пособия, мы пойдем немного дальше и задеплоим приложение, приближенное к реальному миру. В нем будет постоянное бэкэнд-хранилище. Поехали!
Читайте также: