#️⃣Docker | Docker Compose
Описание базовых команд Docker и примеры использования Docker Compose
1. Контейнеризация vs виртуализация
Контейнеризация и виртуализация — это технологии, которые используются для изоляции и управления программным обеспечением, но они имеют разные подходы и области применения:
Принцип работы:
Виртуализация использует гипервизор для создания и управления виртуальными машинами (VM). Каждая виртуальная машина включает в себя гостевую операционную систему и все необходимые для её работы компоненты (ядро, драйверы и т.д.). Это создает полноценную среду с изолированными операционными системами.
Контейнеризация использует ядро операционной системы хоста и изолирует приложения друг от друга через контейнеры. Контейнеры делят общее ядро, но имеют собственные библиотеки и окружение выполнения, что делает их легковеснее по сравнению с виртуальными машинами.
Изоляция:
В виртуальных машинах изоляция достигается через аппаратную виртуализацию и гипервизор, что предоставляет полную виртуальную машину с отдельной операционной системой.
В контейнерах изоляция достигается через механизм именованных пространств (namespaces) и контрольные группы (cgroups), которые предоставляют изоляцию процессов, но делят ядро ОС.
Ресурсы и производительность:
Виртуальные машины требуют больше ресурсов, так как каждая VM включает полную копию операционной системы.
Контейнеры используют меньше ресурсов, так как они не требуют отдельной операционной системы и работают на том же ядре, что и хостовая ОС.
Управление и масштабирование:
Виртуальные машины обычно медленнее запускаются и требуют больше времени на настройку и управление.
Контейнеры запускаются значительно быстрее, что делает их более удобными для масштабирования и разработки микросервисов.
Применение:
Виртуализация часто используется для создания изолированных сред для разработки, тестирования, и развертывания серверных приложений.
Контейнеризация чаще используется для разработки и развертывания приложений, особенно для микросервисной архитектуры, поскольку она упрощает CI/CD процессы и автоматическое масштабирование.
В итоге, выбор между контейнеризацией и виртуализацией зависит от конкретных требований к производительности, изоляции и управлению приложениями.
2. Объекты docker
Docker использует несколько типов объектов для управления контейнерами и приложениями. Основные объекты Docker включают:
Образы (Images):
Описание: Шаблоны только для чтения, из которых создаются контейнеры. Они включают в себя все необходимое для работы приложения: код, среды выполнения, библиотеки и зависимости.
Пример использования:
docker pull nginx
,docker build -t myapp .
Контейнеры (Containers):
Описание: Работающие экземпляры образов. Контейнеры представляют собой изолированные процессы на одном и том же ядре ОС.
Пример использования:
docker run -d -p 80:80 nginx
Сети (Networks):
Описание: Способ для контейнеров общаться друг с другом. Docker поддерживает различные драйверы сетей (bridge, host, overlay и др.).
Пример использования:
docker network create mynetwork
,docker network connect mynetwork mycontainer
Тома (Volumes):
Описание: Специализированные директории для постоянного хранения данных контейнеров. Они позволяют сохранять данные независимо от жизненного цикла контейнера.
Пример использования:
docker volume create myvolume
,docker run -v myvolume:/data myapp
Сервисы (Services):
Описание: Объекты Docker Swarm, которые позволяют масштабировать контейнеры на нескольких хостах. Они являются основной частью Docker для оркестрации контейнеров.
Пример использования:
docker service create --name myservice --replicas 3 nginx
Стэки (Stacks):
Описание: Набор связанных служб, которые можно развернуть с использованием Docker Compose. Они позволяют управлять многоконтейнерными приложениями.
Пример использования:
docker stack deploy -c docker-compose.yml mystack
Dockerfile:
Описание: Скрипт, содержащий инструкции для создания Docker образа. Используется для автоматизации процесса сборки образов.
Пример использования:
docker build -t myapp .
3. Базовые команды docker
Чтобы создать docker-контейнер в простейшем виде, необходимо сделать следующее:
docker run -p 9000:80 nginx:1.25

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

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

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

Вместо 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


Также можно использовать вместо `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

Таким образом, наш мини-проект будет запускаться в изолированной среде, собираться сам, подтягивая все необходимые зависимости из `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