В этой статье мы расскажем про базовые техники работы с Docker, а также погрузим читателя в основы докеризации приложений.
Предполагается, что читатель что-то слышал про Docker и хотел бы начать знакомство с технологией. Мы постараемся упростить этот процесс.
Введение
Docker — это платформа, позволяющая запускать приложения в изолированных контейнерах. Контейнеры обеспечивают приложениям стабильную и предсказуемую среду, где бы они ни запускались, будь то компьютер разработчика/сервер/облако/кластер Kubernetes.
Docker обеспечивает повторяемость и консистентность проекта. Благодаря этому разработчики могут сосредоточиться непосредственно на разработке приложения, не беспокоясь о проблемах совместимости и настройке окружения.
Чтобы понять, как работает Docker, нужно иметь представление о его двух основных единицах: образ и контейнер.
Контейнеры
Контейнеры — это легковесные, изолированные среды выполнения, внутри которых работают приложения.
В отличие от виртуальных машин, контейнеры используют общее ядро операционной системы, что делает их менее затратными с точки зрения ресурсов. Это позволяет запускать больше контейнеров на одном сервере по сравнению с количеством виртуальных машин.
Контейнеры предоставляют множество преимуществ, включая:
Изоляцию. Каждое приложение (в идеале) работает в своей собственной, изолированной среде внутри контейнера;
Повторяемость. Docker гарантирует, что контейнер, который работает на машине разработчика, будет также работать и на сервере, без каких-либо неожиданностей;
Простоту доставки. Образы могут быть легко перенесены между различными окружениями, будь то локальное окружение, тестовые сервера или облачная инфраструктура.
Образы
Образ Docker — это статичное описание содержимого контейнера, включающее в себя все зависимости, настройки окружения, библиотеки и бинарные файлы, необходимые для выполнения приложения. Можно сказать, что образ является готовым к использованию шаблоном для создания контейнеров.
Образы часто создаются на базе других образов. Это происходит благодаря системе слоев, которая позволяет создавать и сохранять изменения поверх базового образа.
Например, вы можете взять официальный образ Go и добавить в него свой код, получив новый образ, готовый к развертыванию (подробнее в разделе о Dockerfile).
Запуск первого контейнера с Docker
Предположим, что вы уже установили Docker CLI или Docker Desktop для своей системы и, возможно, попытались запустить свой первый hello world контейнер командой docker run hello-world.
Разберем подробнее, какие действия выполняет эта команда:
Docker ищет образ hello-world в локальном хранилище. Если образ не найдется, то Docker скачает его с Docker Hub;
Далее Docker создает контейнер на основе этого образа и запускает его;
Контейнер выполняет скрипт, который выводит на экран приветственное сообщение и завершает работу.
Весь описанный процесс можно наблюдать в терминале, в котором выполняется команда. Если все прошло успешно, вы увидите сообщение, подтверждающее успешный запуск контейнера и его работу:
Hello from Docker!
This message shows that your installation appears to be working correctly.
Основные команды
Docker предоставляет широкие возможности для управления контейнерами и образами с помощью команд CLI. В этом разделе мы рассмотрим основные команды Docker, которые помогут вам эффективно управлять контейнерами.
Запуск контейнера — это основное действие, которое вы будете выполнять в Docker. Мы уже запускали контейнер hello-world в предыдущем разделе.
Теперь попробуем запустить более сложное приложение. Например, официальный образ операционной системы Ubuntu: docker run -it ubuntu bash
Эта команда делает следующее:
флаг --it объединяет две опции: -i (interactive) и -t (tty)
-i (--interactive) означает, что запускаемый контейнер будет получать стандартный поток ввода с хоста и направлять его в приложение, работающее в контейнере. По-умолчанию контейнеры стартуют изолированно, и stdin запущенного приложения не имеет связи с внешним миром.
-t (--tty) указывает докеру создать для запущенного приложения псевдотерминал, что позволит удобно работать с ним из вашего терминала.
ubuntu — название образа, который мы запускаем Ubuntu;
bash — команда внутри контейнера ubuntu.
Чтобы остановить контейнер, используется команда: docker stop <container_id>
Где <container_id> — это идентификатор контейнера, который вы хотите остановить. Вы можете определить идентификатор контейнера с помощью следующей команды: docker ps. Эта команда выводит список запущенных контейнеров вместе с их идентификаторами.
Если вам нужно перезапустить контейнер, используйте команду docker restart: docker restart <container_id>. ID контейнера можно получить из вывода команды ps, однако большинство команд, работающих с ID контейнеров, могут работать и с названиями.
Чтобы удалить контейнер, необходимо сначала остановить его, а затем использовать команду rm: docker rm <container_id>
Для одновременной остановки и удаления контейнера можно использовать флаг -f (force): docker rm -f <container_id>
Управление образами Docker
Для загрузки образа без его запуска можно использовать команду pull, например, docker pull ubuntu. Эта команда загрузит в локальное хранилище последний (latest) образ Ubuntu, однако при необходимости можно указать конкретную версию образа: docker pull ubuntu:20.04
Чтобы увидеть все доступные на вашем компьютере образы, используйте команду: docker images
Для удаления образа используйте команду docker rmi (remove image): docker rmi <image_id>
Получить идентификатор образа можно с помощью команды docker images.
Dockerfile и образы Docker
В этом разделе мы подробно рассмотрим, что такое образы Docker, их роль в контейнеризации, а также процесс создания собственных образов с помощью Dockerfile. Мы также разберем контекст Dockerfile и многоступенчатую сборку.
Что такое образы Docker?
Образ Docker — это лёгкий, автономный и исполняемый пакет, включающий всё необходимое для запуска части программного обеспечения, включая код, среды выполнения, библиотеки и системные зависимости. Образы Docker служат шаблоном для создания контейнеров. Образы описываются с помощью Dockerfile.
Dockerfile — это текстовый файл специального формата, содержащий команды для сборки Docker-образа. Эти команды описывают шаги, необходимые для установки зависимостей и конфигурации вашего приложения с учетом контекста приложения.
Контекст Dockerfile — это набор файлов, которые будут отправлены на Docker daemon для сборки образа. Часто это директория, в которой находится сам Dockerfile и любые другие файлы, необходимые для сборки (в основном, код).
Простой пример Dockerfile
Рассмотрим простой пример Dockerfile для приложения на Node.js:
# Указываем базовый образ
FROM node:14
# Устанавливаем рабочую директорию внутри будущего контейнера
WORKDIR /app
# Копируем package.json и package-lock.json в /app (./ из-за WORKDIR)
COPY package*.json ./
# Устанавливаем зависимости
RUN npm install
# Копируем файлы приложения (с хоста (контекст) в образ (/app))
COPY . .
# Открываем порт
EXPOSE 3000
# Запускаем приложение
CMD ["node", "server.js"]
Теперь можно попробовать собрать приложение: docker build -t node-app:latest
-t указывает docker собрать образ с тегом
node-app — название образа
latest — тег
После того, как Docker завершит сборку успехом, можно запустить приложение: docker run node-app
Крайне важное замечание про Dockerfile: каждая команда создает свой собственный слой образа. Из-за этого образы могут раздуваться до огромных размеров. Для того, чтобы этого не происходило, существует поэтапная сборка.
Поэтапная (multistage) сборка
multistage -сборка позволяет уменьшить размер итоговых образов, используя несколько команд FROM.
В качестве примера рассмотрим сборку простого Go-приложения:
# BUILD STAGE
FROM golang:1.16 AS build
WORKDIR /go/src/app
COPY . .
RUN go build -o myapp
# RUN STAGE
FROM alpine:latest
WORKDIR /root/
COPY --from=build /go/src/app/myapp .
CMD ["./myapp"]
В итоговый образ попадет только то, что было в образе alpine плюс исполняемый файл myapp.
Docker Hub, репозитории образов
Docker Hub — это репозиторий, который предоставляет разработчикам возможность легко обмениваться и управлять контейнерными образами.
С помощью Docker Hub вы можете:
- Искать и загружать публичные образы, предоставляемые сообществом;
- Создавать и делиться собственными изображениями;
- Управлять автоматическими сборками и интеграциями с системой контроля версий.
Docker Hub предлагает огромное количество публичных образов, таких как образы операционных систем, баз данных, веб-серверов и различных приложений. Использование этих образов позволяет экономить время и усилия при настройке и развертывании приложений.
Один из основных процессов работы с Docker Hub — это загрузка (pull) и выгрузка (push) образов. Начнем с того, как загрузить образ из Docker Hub.
Команда docker pull позволяет скачать нужный образ на локальную машину.
docker pull ubuntu:latest
Эта команда загрузит последнюю версию образа Ubuntu. После загрузки образа, вы можете запустить контейнер на его основе:
docker run -it ubuntu:latest /bin/bash
Для выгрузки образов, сначала необходимо создать аккаунт на Docker Hub и авторизоваться в командной строке: docker login
После успешной авторизации, вы можете загрузить собственный образ. Сначала убедитесь, что image отмечен тегом:
docker tag <image_id> your_dockerhub_username/repo_name:tag
Теперь можно загрузить образ: docker push
your_dockerhub_username/repo_name:tag
Docker Hub предоставляет множество готовых образов для популярных инструментов, которые могут значительно упростить разработку и развертывание ваших проектов.
Рассмотрим несколько из них:
Alpine Linux (alpine) — это крошечный дистрибутив Linux на основе BusyBox, его образ имеет размер всего 5 МБ;
PHP (php-cli, php-fpm) — образы для интерпретатора php, включает все необходимое для разработки под этот язык;
MySQL (mysql) — здесь понятно, всем известная БД;
NGINX (nginx) — пригодится для создания обратного прокси-сервера;
Redis (redis) — высокопроизводительная in-memory база данных, используемая для кеширования и управления сессиями;
Node.js (node) — среда выполнения JavaScript, необходимая для запуска серверного кода на базе Node.js.
Более того, почти у каждого популярного образа есть alpine- и slim-версии, отличающиеся от обычных базой в виде alpine, а также уменьшенным объемом (обычно, slim-версии не включают в себя инструменты для сборки, а предназначены только для исполнения).
Любой образ из Docker Hub можно подтянуть с помощью команды docker pulll. Использование готовых образов сокращает время на настройку окружения.
Также стоит отметить, что Docker Hub — не единственный репозиторий образов.
Так, Gitlab (по крайней мере, self-hosted-версия) предлагает вам свое хранилище, которое очень удобно использовать в связке с Gitlab CI.
Сети
Работа с сетями — это одна из ключевых составляющих контейнеризации в Docker. Отсутствие настройки сетевого взаимодействия контейнеров может привести к проблемам с доступом к вашим сервисам.
Docker предоставляет несколько драйверов сетевого взаимодействия, из которых наиболее распространённые — bridge, host и overlay.
Bridge
Этот сетевой режим используется по умолчанию. В нем создается виртуальный мост (bridge), который позволяет контейнерам общаться друг с другом и с хост-машиной.
При запуске контейнера создается виртуальный интерфейс и подключается к мосту, предоставляя контейнерам IP-адреса из определенного диапазона. Bridge-сеть позволяет изолировать контейнеры от других сетевых интерфейсов хост-машины.
Для подключения контейнера к сети, укажите имя сети при запуске контейнера с использованием флага --network
docker network create --driver bridge app_network
docker run -d --network app_network --name app nginx
Host
В этом режиме контейнер использует сетевой стек хост-машины. Это означает, что контейнер и хост имеют общий IP-адрес и порты. Host-сеть полезна для уменьшения сетевой задержки, однако она уменьшает изоляцию между контейнером и хостом.
docker run -d --network host nginx
Overlay
Этот режим в основном используется в кластерных средах и Docker Swarm.
Overlay-сети позволяют контейнерам, работающим на разных физических или виртуальных машинах, общаться друг с другом так, будто они находятся на одной сети. Это достигается путем создания распределенной сети поверх существующей физической инфраструктуры.
docker network create --driver overlay --subnet 10.0.9.0/24 my_overlay_network
Коммуникация между контейнерами является ключевым аспектом для микросервисной архитектуры и распределенных систем. В Docker вы можете легко настроить взаимодействие между контейнерами, используя созданные вами сети.
После подключения к одной сети, контейнеры могут общаться друг с другом по именам хоста: docker exec container2 ping container1. Это становится возможным благодаря встроенному DNS-сервису Docker.
Для списка доступных сетей используйте команду: docker network ls
Для отключения контейнера от сети используйте команду: docker network disconnect <network_name> <container_id>
Чтобы удалить сеть, используйте команду: docker network rm <network_name>
Docker Volumes и связывание контейнера с файловой системой хоста (bind mounts)
Volumes и bind mounts — два ключевых механизма для работы с данными в контейнерах. Они необходимы, чтобы эффективно управлять данными, обеспечивать их сохранность и доступность.
Docker volumes существуют, чтобы хранить данные отдельно от контейнера. Даже в случае, если контейнер удалится, данные, хранящиеся в volume, останутся нетронутыми, что важно, когда проект уже развернут на площадке.
Bind Mounts немного отличаются от volumes. Этот подход представляет собой простое монтирование директорий с хоста в директории внутри контейнера. Это позволяет контейнерам иметь прямой доступ к данным на хосте, что удобно для среды разработки и тестирования.
Когда вы используете bind mounts, Docker не управляет содержимым целевой директории. Это означает, что изменения, внесенные в файлы на хосте, будут немедленно отражаться внутри контейнера, и наоборот.
Примеры использования bind mount и volume с указанием --mountв команде run:
volume: type=volume,src=my_volume,target=/usr/local/data
bind mount: type=bind,src=/path/to/data,target=/usr/local/data
Можно заметить, что volume и bind mounts отличаются только типом и значением src. В случае с volumes вы указываете название тома, а в случае с bind mounts указывается путь на хосте, который нужно опрокинуть в контейнер.
Volumes | Bind mounts | |
Путь на хосте | Выбирает Docker | Указывается разработчиком |
Создает новый volume | Да | Нет |
Поддерживает драйверы volumes | Да | Нет |
Для создания volumes и bind mount также может использоваться следующий синтаксис команды docker run: docker run -d -v /path/on/host:/path/in/container my_image в случае использования bind mounts.
Или docker volume create my_volume && docker run -d -v my_volume:/data my_image в случае использования томов.
Теперь данные по пути /data внутри контейнера будут храниться в my_volume. Volume можно отключать, заменять и делать еще много всего. Разово создав volume, пересоздавать его не нужно.
Docker Compose
Docker Compose — это мощный инструмент, разработанный для упрощения работы с многоконтейнерными приложениями. Docker Compose позволяет вам описать и запустить сложные приложения, состоящие из нескольких контейнеров, с минимальными усилиями. В этом разделе мы погрузимся в основы Docker Compose и его применение.
Основные возможности Docker Compose включают:
Декларативное описание сервисов, volumes и networks в формате yaml;
Управление всеми службами, указанными в конфигурационном файле, при помощи единой утилиты docker compose;
Управление жизненным циклом контейнеров.
Рассмотрим пример простого веб-приложения, состоящего из веб-сервера и базы данных.
Без Docker Compose запуск такого приложения потребовал бы выполнения серии команд для каждого контейнера, ручной настройки сетей и volumes. Docker Compose позволяет автоматизировать этот процесс, описав конфигурацию проекта в одном файле.
Для начала работы с Docker Compose необходимо создать файл docker-compose.yml, в котором будет описана конфигурация вашего приложения. Рассмотрим пример файла, в котором описаны два контейнера: web и db.
services:
web:
image: nginx:latest
ports:
- "8000:80"
networks:
- app-network
app:
build:
args:
user: www-data
uid: 33
app_mode: development
context: .
dockerfile: Dockerfile
restart: always
image: app
container_name: app
working_dir: /var/www/
volumes:
-'./:/var/www'
networks:
- app-network
db:
image: mysql:latest
volumes:
-'app-db:/var/lib/mysql'
environment:
DB_PASSWORD: password
networks:
- app-network
networks:
app-network:
name: app-network
driver: bridge
volumes:
app-db:
driver: local
Структура docker-compose.yml
services содержит описание всех служб (контейнеров), участвующих в работе приложения.
web: Определяет контейнер с веб-сервером Nginx, который будет доступен на порту 80.
Синтаксис "8000:80" читается так: "host_port:container_port". Докер будет слушать 8000 порт на хосте и проксировать его в nginx-контейнер и обратно.
app: главный контейнер с приложением. Описывается в Dockerfile, использует контекст текущей директории (привычная всем точка). Делает bind mount "./" в "/var/www" в контейнере.
db: Определяет контейнер с базой данных MySQL, в который передается переменная среды DB_PASSWORD. А также данные базы данных хранятся в volume app-db, чтобы не потерять информацию в случае остановки контейнера.
networks: Определяет пользовательские сети, в данном случае bridge-сеть app-network, используемую всеми контейнерами для связи друг с другом.
volumes: Определяет все необходимые тома, в данном случае том для базы данных.
Для запуска всех служб, описанных в docker-compose.yml, используйте команду: docker compose up (если у вас установлена старая версия Docker Compose, то скорее всего нужно запускать docker-compose, через дефис).
Эта команда построит, запустит и свяжет все контейнеры, описанные в файле. После выполнения команды вы будете видеть все логи в своем stdout. Добавив флаг -d, контейнеры запустятся в фоновом режиме: docker compose up -d
Чтобы остановить все контейнеры и сети, используйте команду: docker compose down
Управление контейнерами
Для управления отдельными службами Docker Compose предоставляет удобные команды.
Просмотр списка запущенных контейнеров: docker compose ps
Просмотр всех контейнеров: docker compose ps -a
Просмотр логов приложения: docker compose logs <container_name>
Перезапуск контейнера: docker compose restart <container_name>
Заключение
В этой статье мы постарались дать инструкцию по использованию базовых техник работы с Docker для тех, кто только начинает знакомство с данной технологией.
Конечно, рассказать о Docker еще можно много. Пишите, что вас интересует, и, возможно, именно ваш комментарий станет темой для нашей следующей статьи.