Page cover

#️⃣Docker | Docker Compose

Описание базовых команд Docker и примеры использования Docker Compose

1. Контейнеризация vs виртуализация

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

  1. Принцип работы:

    • Виртуализация использует гипервизор для создания и управления виртуальными машинами (VM). Каждая виртуальная машина включает в себя гостевую операционную систему и все необходимые для её работы компоненты (ядро, драйверы и т.д.). Это создает полноценную среду с изолированными операционными системами.

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

  2. Изоляция:

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

    • В контейнерах изоляция достигается через механизм именованных пространств (namespaces) и контрольные группы (cgroups), которые предоставляют изоляцию процессов, но делят ядро ОС.

  3. Ресурсы и производительность:

    • Виртуальные машины требуют больше ресурсов, так как каждая VM включает полную копию операционной системы.

    • Контейнеры используют меньше ресурсов, так как они не требуют отдельной операционной системы и работают на том же ядре, что и хостовая ОС.

  4. Управление и масштабирование:

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

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

  5. Применение:

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

    • Контейнеризация чаще используется для разработки и развертывания приложений, особенно для микросервисной архитектуры, поскольку она упрощает CI/CD процессы и автоматическое масштабирование.

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

2. Объекты docker

Docker использует несколько типов объектов для управления контейнерами и приложениями. Основные объекты Docker включают:

  1. Образы (Images):

    • Описание: Шаблоны только для чтения, из которых создаются контейнеры. Они включают в себя все необходимое для работы приложения: код, среды выполнения, библиотеки и зависимости.

    • Пример использования: docker pull nginx, docker build -t myapp .

  2. Контейнеры (Containers):

    • Описание: Работающие экземпляры образов. Контейнеры представляют собой изолированные процессы на одном и том же ядре ОС.

    • Пример использования: docker run -d -p 80:80 nginx

  3. Сети (Networks):

    • Описание: Способ для контейнеров общаться друг с другом. Docker поддерживает различные драйверы сетей (bridge, host, overlay и др.).

    • Пример использования: docker network create mynetwork, docker network connect mynetwork mycontainer

  4. Тома (Volumes):

    • Описание: Специализированные директории для постоянного хранения данных контейнеров. Они позволяют сохранять данные независимо от жизненного цикла контейнера.

    • Пример использования: docker volume create myvolume, docker run -v myvolume:/data myapp

  5. Сервисы (Services):

    • Описание: Объекты Docker Swarm, которые позволяют масштабировать контейнеры на нескольких хостах. Они являются основной частью Docker для оркестрации контейнеров.

    • Пример использования: docker service create --name myservice --replicas 3 nginx

  6. Стэки (Stacks):

    • Описание: Набор связанных служб, которые можно развернуть с использованием Docker Compose. Они позволяют управлять многоконтейнерными приложениями.

    • Пример использования: docker stack deploy -c docker-compose.yml mystack

  7. Dockerfile:

    • Описание: Скрипт, содержащий инструкции для создания Docker образа. Используется для автоматизации процесса сборки образов.

    • Пример использования: docker build -t myapp .

3. Базовые команды docker

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

docker run -p 9000:80 nginx:1.25
Создание контейнера Nginx

Для примера запускаем контейнер с образом nginx версии 1.25:

Создание контейнера Nginx

Если используется Docker Desktop:

Созданный контейнер в Docker Desktop

Результат создания контейнера:

Запущенный Nginx на порте 9000

Вместо Docker Desktop созданный контейнер можно посмотреть через терминал:

Отображение списка контейнеров

Как видно на скриншоте выше, контейнер принимает подключения с любых IP-адресов (0.0.0.0:9000) и перенаправляет подключения на порт 80 внутри контейнера. Автоматически контейнеру присваивается идентификатор (9d10d4ac19fa) и имя (gracious_diffie). Помимо этого, в том терминале, где мы запустили контейнер, мы не может вводить команды на хостовой ОС. Исправим имя контейнера (через указание опции --name) и возможность ввода команд после запуска (через опцию -d). Создаем новый контейнер:

docker run -d --name nginx-test -p 9001:80 nginx:1.25
Создание нового контейнера

Вновь проверим созданный контейнер:

Список контейнеров

Теперь остановим работу первого контейнера и удалим его:

docker stop 9d10d4ac19fa
docker rm 9d10d4ac19fa
Остановка и удаление контейнера

Теперь попробуем запустить команду внутри контейнера:

docker exec -it 2df0405d4a8d cat /etc/passwd

или

docker exec -it nginx-test cat /etc/passwd
Запуск команды cat /etc/passwd внутри контейнера с использованием ID-контейнера
Запуск команды cat /etc/passwd внутри контейнера с использованием имени контейнера

Также можно использовать вместо `docker exec -it`, например, `docker run`, но это создаст новый контейнер!

А как получить shell, чтобы вводить команды свободно, как в обычном терминале? Крайне просто! Нужно просто запустить оболочку:

Запуск оболочки контейнера

4. Создание контейнера с использованием Dockerfile

Теперь создадим свой кастомный Docker-образ с использованием Dockerfile (простой проект на flask). Структура нашего проекта:

flask_docker_project
    └───app.py
    └───Dockerfile
    └───requirements.txt
# app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# requirements.txt
Flask==3.0.3
# Dockerfile
# Используем базовый образ Python
FROM python:3.10-slim

# Устанавливаем рабочую директорию в контейнере
WORKDIR /app

# Копируем файл зависимостей в контейнер
COPY requirements.txt requirements.txt

# Устанавливаем зависимости
RUN pip install -r requirements.txt

# Копируем все файлы проекта в контейнер
COPY . .

# Указываем команду для запуска приложения
CMD ["python", "app.py"]

Теперь создаем docker-образ:

docker build -t flask_app .

И запустим контейнер:

docker run -d -p 5000:5000 flask_app
Создание собственного Docker-образа и запуск контейнера

Таким образом, наш мини-проект будет запускаться в изолированной среде, собираться сам, подтягивая все необходимые зависимости из `requirements.txt`

5. Развертывание образов с использованием docker-compose, добавление БД к проекту

Теперь реализуем развертывание контейнера с использованием docker-compose. Переделаем структуру проекта:

└───flask_docker_project
    │───docker-compose.yaml
    └───sources
            │───app.py
            │───Dockerfile
            └───requirements.txt

docker-compose.yaml выглядит следующим образом:

# docker-compose.yaml
version: '3.8'

services:
  flask-service:
    build:
      context: ./sources
      dockerfile: ./Dockerfile
      labels:
        maintainer: "o1d_bu7_go1d"
        description: "This is a custom Docker image created by o1d_bu7_go1d"
        version: "1.0"
    container_name: flask-service
    command: python app.py
    volumes:
      - ./sources:/app
    environment:
      - ADMIN_PANEL_USERNAME=admin
      - ADMIN_PANEL_PASSWORD=admin
    ports:
      - 127.0.0.1:5000:5000
      - 5001:5000
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:14.1-alpine
    container_name: flask-service-db
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=game
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d postgres -U postgres"]
      interval: 20s
      timeout: 3s
      retries: 5

volumes:
  postgres_data:
    driver: local

Производим запуск следующей командой, благодаря которой docker сам создаст все образы и запустит контейнеры (запуск из директории flask_docker_project):

docker-compose up -d --build
Процесс создания образов и запуска контейнеров

Контейнеры:

Созданные контейнеры

Образы:

Созданные образы

Тома:

Созданные тома

Из интересного - порты:

Контейнеры

Доступ к созданному нами веб-серверу можно получить двумя способами:

  • Локально (127.0.0.1 или localhost) на порте 5000

  • Отовсюду (0.0.0.0) на порте 5001

В примере созданы два порта, но можно оставить один из двух вариантов в зависимости от ситуации

Веб-сервер

Теперь добавим к нашему проекту подключение к БД. Для начала обновим requirements.txt:

# requirements.txt
Flask==3.0.3
psycopg2-binary==2.9.9
Flask-SQLAlchemy==3.1.1

Обновим исходный код:

# app.py
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import os

app = Flask(__name__)

# Настройки для подключения к базе данных
app.config['SQLALCHEMY_DATABASE_URI'] = f"postgresql://postgres:postgres@db/game"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/db_version')
def db_version():
    with db.engine.connect() as connection:
        result = connection.execute(text('SELECT version();'))
        version = result.fetchone()
        return jsonify({'PostgreSQL Version': version[0]})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Сделаем ребилд с помощью следующей команды:

docker-compose build --no-cache

И вновь запустим проект:

docker-compose up -d
Ребилд и запуск контейнеров

Проверяем, могут ли обменяться информацией два созданный контейнера:

Успешный запрос к контейнеру с БД от контейнера с веб-сервером

Как видим, запрос на версию БД успешно реализован. Хочется отметить один момент в исходном коде веб-сервера:

app.config['SQLALCHEMY_DATABASE_URI'] = f"postgresql://postgres:postgres@db/game"

Обращаем внимание на `@db`. Это указание на имя контейнера. Почему это работает? В концепции Docker, при использовании Docker Compose, сервисы могут взаимодействовать друг с другом по именам сервисов, указанным в docker-compose.yaml. Это возможно благодаря встроенной сети Docker Compose, которая создает виртуальную сеть и DNS для всех сервисов, указанных в файле конфигурации.

Также можно обратить внимание на то, что логин и пароль указаны в явном виде, но мы можем поменять файл docker-compose.yaml, чтобы получить логин и пароль из переменных окружений.

Заменим переменные окружения:

version: '3.8'

services:
  flask-service:
    build:
      context: ./sources
      dockerfile: ./Dockerfile
      labels:
        maintainer: "o1d_bu7_go1d"
        description: "This is a custom Docker image created by o1d_bu7_go1d"
        version: "1.0"
    container_name: flask-service
    command: python app.py
    volumes:
      - ./sources:/app
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=game
    ports:
      - 127.0.0.1:5000:5000
      - 5001:5000
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:14.1-alpine
    container_name: flask-service-db
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=game
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d postgres -U postgres"]
      interval: 20s
      timeout: 3s
      retries: 5

volumes:
  postgres_data:
    driver: local

Также изменим немного исходный код веб-сервера:

# app.py
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import os

app = Flask(__name__)

# Настройки для подключения к базе данных
app.config['SQLALCHEMY_DATABASE_URI'] = f"postgresql://{os.environ['POSTGRES_USER']}:{os.environ['POSTGRES_PASSWORD']}@db/{os.environ['POSTGRES_DB']}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/db_version')
def db_version():
    with db.engine.connect() as connection:
        result = connection.execute(text('SELECT version();'))
        version = result.fetchone()
        return jsonify({'PostgreSQL Version': version[0]})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Вновь сделаем ребилд и запустим контейнеры:

Ребилд и перезапуск контейнеров

Как итог, все также работает:

Результат

Кстати, чтобы зайти в БД, можно выполнить команду:

docker exec -it <db-container-id> psql -U <db-container-username> <db-name>
Вход в БД

6. Другие полезные команды

Last updated