Почти все, что вы хотели бы знать про Docker

В этой статье мы расскажем про базовые техники работы с Docker, а также погрузим читателя в основы докеризации приложений.

Предполагается, что читатель что-то слышал про Docker и хотел бы начать знакомство с технологией. Мы постараемся упростить этот процесс.

Введение

Docker — это платформа, позволяющая запускать приложения в изолированных контейнерах. Контейнеры обеспечивают приложениям стабильную и предсказуемую среду, где бы они ни запускались, будь то компьютер разработчика/Linux-сервер в облаке/кластер Kubernetes.

Docker обеспечивает повторяемость и консистентность проекта. Благодаря этому разработчики могут сосредоточиться непосредственно на разработке приложения, не беспокоясь о проблемах совместимости и настройке окружения.

Чтобы понять, как работает Docker, нужно иметь представление о его двух основных единицах: образ и контейнер.

Контейнеры

Контейнеры — это легковесные, изолированные среды выполнения, внутри которых работают приложения.

В отличие от виртуальных машин, контейнеры используют общее ядро операционной системы, что делает их менее затратными с точки зрения ресурсов. Это позволяет запускать больше контейнеров на одном сервере по сравнению с количеством виртуальных машин.

Контейнеры предоставляют множество преимуществ, включая:

  • Изоляцию. Каждое приложение (в идеале) работает в своей собственной, изолированной среде внутри контейнера;

  • Повторяемость. Docker гарантирует, что контейнер, который работает на машине разработчика, будет также работать и на сервере, без каких-либо неожиданностей;

  • Простоту доставки. Образы могут быть легко перенесены между различными окружениями, будь то локальное окружение, тестовые сервера или облачная инфраструктура.

Образы

Образ Docker — это статичное описание содержимого контейнера, включающее в себя все зависимости, настройки окружения, библиотеки и бинарные файлы, необходимые для выполнения приложения. Можно сказать, что образ является готовым к использованию шаблоном для создания контейнеров.

Образы часто создаются на базе других образов. Это происходит благодаря системе слоев, которая позволяет создавать и сохранять изменения поверх базового образа.

Например, вы можете взять официальный образ Go и добавить в него свой код, получив новый образ, готовый к развертыванию (подробнее в разделе о Dockerfile).

Процесс создания нового образа (из официальной документации Docker)

Процесс создания нового образа (из официальной документации Docker)

Запуск первого контейнера с Docker

Предположим, что вы уже установили Docker CLI или Docker Desktop для своей системы и, возможно, попытались запустить свой первый hello world контейнер командой docker run hello-world.

Разберем подробнее, какие действия выполняет эта команда:

  1. Docker ищет образ hello-world в локальном хранилище. Если образ не найдется, то Docker скачает его с Docker Hub;

  2. Далее Docker создает контейнер на основе этого образа и запускает его;

  3. Контейнер выполняет скрипт, который выводит на экран приветственное сообщение и завершает работу.

Весь описанный процесс можно наблюдать в терминале, в котором выполняется команда. Если все прошло успешно, вы увидите сообщение, подтверждающее успешный запуск контейнера и его работу:

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

Эта команда делает следующее:

-i (--interactive) означает, что запускаемый контейнер будет получать стандартный поток ввода с хоста и направлять его в приложение, работающее в контейнере. По-умолчанию контейнеры стартуют изолированно, и stdin запущенного приложения не имеет связи с внешним миром.

-t (--tty) указывает докеру создать для запущенного приложения псевдотерминал, что позволит удобно работать с ним из вашего терминала.

  • ubuntu — название образа, который мы запускаем Ubuntu;

  • bash — команда внутри контейнера ubuntu.

Чтобы остановить контейнер, используется команда: docker stop

Где  — это идентификатор контейнера, который вы хотите остановить. Вы можете определить идентификатор контейнера с помощью следующей команды: docker ps. Эта команда выводит список запущенных контейнеров вместе с их идентификаторами.

Если вам нужно перезапустить контейнер, используйте команду docker restart: docker restart . ID контейнера можно получить из вывода команды ps, однако большинство команд, работающих с ID контейнеров, могут работать и с названиями.

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

Для одновременной остановки и удаления контейнера можно использовать флаг -f (force): docker rm -f

Управление образами Docker

Для загрузки образа без его запуска можно использовать команду pull, например, docker pull ubuntu. Эта команда загрузит в локальное хранилище последний (latest) образ Ubuntu, однако при необходимости можно указать конкретную версию образа: docker pull ubuntu:20.04

Чтобы увидеть все доступные на вашем компьютере образы, используйте команду: docker images

Для удаления образа используйте команду docker rmi (remove image): docker rmi

Получить идентификатор образа можно с помощью команды 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 your_dockerhub_username/repo_name: tag

Теперь можно загрузить образ: docker push
your_dockerhub_username/repo_name: tag

Docker Hub предоставляет множество готовых образов для популярных инструментов, которые могут значительно упростить разработку и развертывание ваших проектов.

Рассмотрим несколько из них:

  1. Alpine Linux (alpine) — это крошечный дистрибутив Linux на основе BusyBox, его образ имеет размер всего 5 МБ;

  2. PHP (php-cli, php-fpm) — образы для интерпретатора php, включает все необходимое для разработки под этот язык;

  3. MySQL (mysql) — здесь понятно, всем известная БД;

  4. NGINX (nginx) — пригодится для создания обратного прокси-сервера;

  5. Redis (redis) — высокопроизводительная in-memory база данных, используемая для кеширования и управления сессиями;

  6. 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

Чтобы удалить сеть, используйте команду: docker network rm

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

  • Перезапуск контейнера: docker compose restart

Заключение

В этой статье мы постарались дать инструкцию по использованию базовых техник работы с Docker для тех, кто только начинает знакомство с данной технологией.

Конечно, рассказать о Docker еще можно много. Пишите, что вас интересует, и, возможно, именно ваш комментарий станет темой для нашей следующей статьи.

© Habrahabr.ru