2.3 Docker и контейнеризация #
📚 Градация сложности изучения Docker:
- 🟢 Новичок (0-3 месяца): Docker run, основные команды, простые Dockerfile
- 🟡 Базовый (3-6 месяцев): Docker Compose, volumes, networking
- 🟠 Средний (6-12 месяцев): Multi-stage builds, оптимизация образов
- 🔴 Продвинутый (1+ год): Docker Swarm, безопасность, CI/CD интеграция
🟢 Что такое Docker (для новичков) #
Docker — это как “универсальная коробка” для приложений. Вы упаковываете ваше приложение в эту коробку вместе со всем необходимым (код, библиотеки, настройки), и эта коробка будет работать одинаково на любом компьютере.
Аналогия с доставкой:
- Без контейнеров — каждый товар упаковывается по-разному, нужны разные способы доставки
- С контейнерами — стандартные контейнеры, можно перевозить любым транспортом
🔄 Виртуальные машины vs Контейнеры #
Виртуальные машины #
Физический сервер
├── Гипервизор (VMware, VirtualBox)
├── VM 1: Полная ОС + Приложение 1 (2GB RAM)
├── VM 2: Полная ОС + Приложение 2 (2GB RAM)
└── VM 3: Полная ОС + Приложение 3 (2GB RAM)
Итого: 6GB RAM только на ОС
Контейнеры #
Физический сервер
├── Хостовая ОС (Linux)
├── Docker Engine
├── Container 1: Только Приложение 1 (100MB)
├── Container 2: Только Приложение 2 (150MB)
└── Container 3: Только Приложение 3 (200MB)
Итого: 450MB для всех приложений
🛠️ Установка Docker #
Ubuntu/Debian (обновлено для 2025) #
# Удалить старые версии
sudo apt remove docker docker-engine docker.io containerd runc
# Установить зависимости
sudo apt update
sudo apt install ca-certificates curl
# Добавить официальный GPG ключ Docker (новый метод)
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Добавить репозиторий в источники Apt (новый формат)
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Установить Docker (последняя версия)
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Добавить пользователя в группу docker
sudo usermod -aG docker $USER
newgrp docker
# Проверить установку
docker --version
docker compose version # новая команда вместо docker-compose
docker run hello-world
💡 Альтернативный способ установки (упрощенный):
# Для Ubuntu 22.04+ можно использовать snap
sudo snap install docker
# Или официальный convenience script (только для тестирования!)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
🪟 Windows пользователям #
💡 Для Windows рекомендуется WSL2 + Docker Desktop:
Вариант 1: Docker Desktop (простой)
# Скачайте Docker Desktop с официального сайта
# https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe
# Установите и перезагрузите систему
Вариант 2: WSL2 + Docker внутри Linux (удобно потыкаться)
# 1. Включите WSL2 в Windows
wsl --install Ubuntu-22.04
# 2. После установки Ubuntu, зайдите в WSL
wsl
# 3. Установите Docker как на обычном Ubuntu (команды выше)
sudo apt update
sudo apt install ca-certificates curl
# ... далее по инструкции Ubuntu
Преимущества WSL2 подхода:
- ✅ Полноценная Linux среда для изучения
- ✅ Можно экспериментировать с bash скриптами
- ✅ Лучше понимаете как работает Docker в продакшене
- ✅ Удобно переключаться между Windows и Linux контекстом
🟢 Ваш первый контейнер (Hello World) #
Начнем с самого простого примера:
# Запустите эту команду после установки Docker
docker run hello-world
# Вы увидите:
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
Что только что произошло?
- Docker скачал образ
hello-world
из Docker Hub - Создал из него контейнер
- Запустил контейнер, который вывел сообщение
- Контейнер завершил работу
Попробуем что-то полезное:
# Запустим веб-сервер nginx
docker run -d -p 8080:80 --name my-nginx nginx:alpine
# Проверяем что он работает
curl http://localhost:8080
# Или откройте браузер по адресу http://localhost:8080
# Остановим и удалим контейнер
docker stop my-nginx
docker rm my-nginx
📦 Основы работы с Docker #
🟢 Ключевые понятия (простыми словами) #
Image (Образ) — как “фотография” готового приложения со всеми файлами
Container (Контейнер) — запущенная “копия” этой фотографии
Dockerfile — “рецепт” для создания образа
Docker Hub — “магазин” готовых образов в интернете
Основные команды #
# Работа с образами
docker images # список локальных образов
docker pull nginx:latest # скачать образ
docker rmi nginx:latest # удалить образ
docker build -t myapp . # собрать образ из Dockerfile
# Работа с контейнерами
docker ps # запущенные контейнеры
docker ps -a # все контейнеры
docker run nginx # запустить контейнер
docker run -d nginx # запустить в фоне (daemon)
docker run -p 8080:80 nginx # проброс портов
docker stop container_id # остановить контейнер
docker rm container_id # удалить контейнер
docker logs container_id # посмотреть логи
# Интерактивная работа
docker run -it ubuntu bash # запустить с терминалом
docker exec -it container_id bash # подключиться к запущенному контейнеру
📝 Создание Dockerfile #
Простой пример #
# Базовый образ
FROM node:20-alpine
# Установка рабочей директории
WORKDIR /app
# Копирование package.json
COPY package*.json ./
# Установка зависимостей
RUN npm ci --only=production
# Копирование исходного кода
COPY . .
# Открытие порта
EXPOSE 3000
# Команда запуска
CMD ["npm", "start"]
Многоэтапная сборка (Multi-stage build) #
# Этап 1: Сборка
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Этап 2: Продакшн
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
🔧 Как работает Docker под капотом #
Linux Namespaces #
Docker использует namespaces для изоляции процессов:
# Типы namespaces в Docker
PID namespace # Изоляция process ID
NET namespace # Изоляция сети
MNT namespace # Изоляция файловой системы
UTS namespace # Изоляция hostname
IPC namespace # Изоляция межпроцессного взаимодействия
USER namespace # Изоляция пользователей и групп
# Просмотр namespaces контейнера
docker inspect <container_id> | grep -i pid
sudo ls -la /proc/<pid>/ns/
Control Groups (cgroups) #
Cgroups ограничивают ресурсы контейнера:
# Cgroups в action
docker run --cpus="1.5" --memory="512m" nginx
# Проверка ограничений
docker stats <container_id>
# Подробная информация о cgroups
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.shares
Multi-Architecture образы #
Docker поддерживает мультиархитектурные образы:
# Сборка для разных архитектур
docker buildx create --name multiarch
docker buildx use multiarch
# Сборка для AMD64 и ARM64
docker buildx build --platform linux/amd64,linux/arm64 \
-t myapp:multiarch --push .
# Проверка поддерживаемых архитектур
docker manifest inspect nginx:latest
# Пример вывода
{
"architecture": "amd64",
"os": "linux"
},
{
"architecture": "arm64",
"os": "linux"
}
Альтернативные Container Runtime’ы #
1. Podman - Daemonless альтернатива #
# Установка Podman
sudo apt install podman
# Использование (совместимо с Docker CLI)
podman run hello-world
podman build -t myapp .
podman pods create --name mypod # Kubernetes-style pods
# Отличия от Docker:
# - Не требует daemon
# - Запуск от имени обычного пользователя
# - Совместимость с systemd
2. containerd - Низкоуровневый runtime #
# containerd используется в Kubernetes
sudo ctr images pull docker.io/library/nginx:latest
sudo ctr run docker.io/library/nginx:latest nginx1
# Используется как backend для:
# - Docker Desktop
# - Kubernetes (через CRI)
# - AWS Fargate
3. CRI-O - Kubernetes-native runtime #
# Установка CRI-O для Kubernetes
sudo apt install cri-o cri-o-runc
# Конфигурация для Kubernetes
sudo systemctl enable crio
sudo systemctl start crio
# Специально оптимизирован для Kubernetes
# - Поддержка OCI стандарта
# - Lightweight runtime
# - Security-focused
4. LXC/LXD - System containers #
# LXD для полноценных system containers
sudo snap install lxd
lxd init
# Создание system container
lxc launch ubuntu:20.04 mycontainer
lxc exec mycontainer -- bash
# Отличия от Docker:
# - Полная OS виртуализация
# - Больше похоже на VM
# - Подходит для legacy приложений
Best Practices для Dockerfile #
# ✅ Хорошие практики
# 1. Используйте специфичные теги вместо latest
FROM node:20.11-alpine
# 2. Создайте непривилегированного пользователя
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 3. Копируйте файлы в правильном порядке для кэширования
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# 4. Используйте .dockerignore
# node_modules
# .git
# README.md
# 5. Минимизируйте количество слоев
RUN apt-get update && apt-get install -y \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*
# 6. Устанавливайте пользователя в конце
USER nextjs
# 7. Используйте COPY вместо ADD
COPY src/ /app/src/
# 8. Используйте переменные окружения
ENV NODE_ENV=production
ENV PORT=3000
🔗 Docker Compose #
Что такое Docker Compose #
Docker Compose позволяет описать многоконтейнерное приложение в YAML файле и управлять им одной командой.
Пример docker-compose.yml #
version: '3.8'
services:
# Веб-приложение
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=database
depends_on:
- database
- redis
volumes:
- ./logs:/app/logs
restart: unless-stopped
# База данных
database:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
restart: unless-stopped
# Redis для кэширования
redis:
image: redis:6-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
restart: unless-stopped
# Nginx как reverse proxy
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- web
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Команды Docker Compose #
# Запуск всех сервисов
docker-compose up # в foreground
docker-compose up -d # в background
# Остановка и удаление
docker-compose down # остановить и удалить контейнеры
docker-compose down -v # + удалить volumes
# Управление сервисами
docker-compose start web # запустить конкретный сервис
docker-compose stop web # остановить конкретный сервис
docker-compose restart web # перезапустить сервис
# Просмотр логов
docker-compose logs # логи всех сервисов
docker-compose logs -f web # следить за логами web сервиса
# Выполнение команд
docker-compose exec web bash # подключиться к контейнеру
docker-compose run web npm test # запустить разовую команду
# Масштабирование
docker-compose up -d --scale web=3 # запустить 3 экземпляра web
🎯 Практический пример: Веб-приложение #
Структура проекта #
my-web-app/
├── app.js
├── package.json
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
└── .dockerignore
app.js (простое Express приложение) #
const express = require('express');
const redis = require('redis');
const { Pool } = require('pg');
const app = express();
const port = process.env.PORT || 3000;
// Подключение к Redis
const redisClient = redis.createClient({
host: process.env.REDIS_HOST || 'localhost'
});
// Подключение к PostgreSQL
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'myapp',
user: process.env.DB_USER || 'user',
password: process.env.DB_PASSWORD || 'password',
});
app.use(express.json());
// Главная страница
app.get('/', (req, res) => {
res.json({ message: 'Hello Docker!' });
});
// API с использованием кэша
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
const cacheKey = `user:${userId}`;
try {
// Проверяем кэш
const cached = await redisClient.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
// Запрос к базе данных
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
const user = result.rows[0];
if (user) {
// Сохраняем в кэш на 5 минут
await redisClient.setex(cacheKey, 300, JSON.stringify(user));
res.json(user);
} else {
res.status(404).json({ error: 'User not found' });
}
} catch (error) {
console.error('Error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Dockerfile для приложения #
FROM node:20-alpine
# Создание пользователя для безопасности
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodeuser -u 1001
WORKDIR /app
# Копирование package.json для кэширования зависимостей
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# Копирование исходного кода
COPY --chown=nodeuser:nodejs . .
# Переключение на непривилегированного пользователя
USER nodeuser
EXPOSE 3000
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
CMD ["npm", "start"]
nginx.conf #
events {
worker_connections 1024;
}
http {
upstream web_backend {
server web:3000;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://web_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /health {
access_log off;
return 200 "healthy\n";
}
}
}
📊 Мониторинг контейнеров #
Базовая диагностика #
# Статистика использования ресурсов
docker stats # в реальном времени
docker stats --no-stream # одноразовый снимок
# Информация о контейнере
docker inspect container_id # полная информация
docker logs container_id # логи контейнера
docker logs -f --tail 100 container_id # последние 100 строк в реальном времени
# Процессы внутри контейнера
docker exec container_id ps aux
# Использование дискового пространства
docker system df # использование места Docker
docker system prune # очистка неиспользуемых ресурсов
Мониторинг с помощью cAdvisor #
# docker-compose.monitoring.yml
version: '3.8'
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
devices:
- /dev/kmsg
privileged: true
restart: unless-stopped
🔐 Безопасность контейнеров #
Принципы безопасности #
# 1. Не запускайте как root
FROM alpine:latest
RUN adduser -D -s /bin/sh appuser
USER appuser
# 2. Используйте минимальные образы
FROM alpine:3.15 # вместо ubuntu:latest
# 3. Сканируйте образы на уязвимости
# docker scan myapp:latest
# 4. Не включайте секреты в образ
# Используйте Docker secrets или переменные окружения
# 5. Используйте read-only файловую систему
docker run --read-only myapp
# 6. Ограничивайте ресурсы
docker run --memory=512m --cpus=1 myapp
Docker secrets #
# docker-compose.yml с секретами
version: '3.8'
services:
web:
image: myapp
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true # создан через docker secret create
Сканирование безопасности #
# Встроенное сканирование Docker
docker scan myapp:latest
# Trivy - популярный сканер
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image myapp:latest
# Clair - еще один сканер
docker run -d --name clair-db arminc/clair-db:latest
docker run -p 6060:6060 --link clair-db:postgres -d --name clair arminc/clair-local-scan:latest
🚀 CI/CD с Docker #
GitHub Actions пример #
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: myusername/myapp:${{ github.sha }},myusername/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
🎯 Практические задания #
Задание 1: Создание первого Docker контейнера (45 минут) #
# 1. Создайте проект для изучения Docker
mkdir ~/docker-practice && cd ~/docker-practice
# 2. Создайте простое веб-приложение на Python
cat > app.py << 'EOF'
from flask import Flask, jsonify, request
import os
import socket
import time
from datetime import datetime
app = Flask(__name__)
@app.route('/')
def home():
return jsonify({
'message': 'Hello from Docker!',
'container_id': socket.gethostname(),
'timestamp': datetime.now().isoformat(),
'version': os.getenv('APP_VERSION', 'v1.0.0')
})
@app.route('/health')
def health():
return jsonify({
'status': 'healthy',
'uptime': time.time() - start_time
})
@app.route('/info')
def info():
return jsonify({
'python_version': os.sys.version,
'flask_env': os.getenv('FLASK_ENV', 'production'),
'container_name': socket.gethostname(),
'environment_variables': {
key: value for key, value in os.environ.items()
if key.startswith(('FLASK_', 'APP_'))
}
})
if __name__ == '__main__':
start_time = time.time()
app.run(host='0.0.0.0', port=int(os.getenv('PORT', 5000)), debug=False)
EOF
# 3. Создайте файл зависимостей
cat > requirements.txt << 'EOF'
Flask==2.3.3
gunicorn==21.2.0
EOF
# 4. Создайте продвинутый Dockerfile
cat > Dockerfile << 'EOF'
# Используем официальный Python образ
FROM python:3.12-slim
# Устанавливаем системные зависимости
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Создаем пользователя для безопасности
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем и устанавливаем Python зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копируем исходный код
COPY --chown=appuser:appgroup app.py .
# Создаем директорию для логов
RUN mkdir -p /app/logs && chown appuser:appgroup /app/logs
# Переключаемся на непривилегированного пользователя
USER appuser
# Определяем переменные окружения
ENV FLASK_ENV=production
ENV APP_VERSION=v1.0.0
ENV PORT=5000
# Открываем порт
EXPOSE 5000
# Добавляем healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# Запускаем приложение
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
EOF
# 5. Создайте .dockerignore файл
cat > .dockerignore << 'EOF'
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.mypy_cache
.pytest_cache
.hypothesis
.DS_Store
README.md
EOF
# 6. Соберите Docker образ
docker build -t my-flask-app:v1.0.0 .
# 7. Запустите контейнер с различными опциями
echo "Запуск контейнера..."
docker run -d \
--name flask-app-demo \
-p 8080:5000 \
-e APP_VERSION=v1.0.0-demo \
-e FLASK_ENV=development \
--restart unless-stopped \
my-flask-app:v1.0.0
# 8. Проверьте работу приложения
echo "Ожидание запуска приложения..."
sleep 5
echo "Тестирование эндпоинтов:"
curl -s http://localhost:8080/ | python3 -m json.tool
echo -e "\n"
curl -s http://localhost:8080/health | python3 -m json.tool
echo -e "\n"
curl -s http://localhost:8080/info | python3 -m json.tool
# 9. Просмотрите логи контейнера
echo -e "\nЛоги контейнера:"
docker logs flask-app-demo
# 10. Проверьте использование ресурсов
echo -e "\nИспользование ресурсов:"
docker stats flask-app-demo --no-stream
Проверка: Приложение должно отвечать на всех трех эндпоинтах с корректными JSON данными.
Задание 2: Multi-container приложение с Docker Compose (40 минут) #
# 1. Создайте проект для многоконтейнерного приложения
mkdir ~/docker-compose-lab && cd ~/docker-compose-lab
# 2. Создайте веб-приложение с подключением к базе данных и Redis
cat > app.py << 'EOF'
from flask import Flask, jsonify, request
import psycopg2
import redis
import os
import json
from datetime import datetime
app = Flask(__name__)
# Подключение к PostgreSQL
def get_db_connection():
return psycopg2.connect(
host=os.getenv('DB_HOST', 'postgres'),
database=os.getenv('DB_NAME', 'myapp'),
user=os.getenv('DB_USER', 'postgres'),
password=os.getenv('DB_PASSWORD', 'password')
)
# Подключение к Redis
redis_client = redis.Redis(
host=os.getenv('REDIS_HOST', 'redis'),
port=6379,
decode_responses=True
)
@app.route('/')
def home():
try:
# Увеличиваем счетчик посещений в Redis
visits = redis_client.incr('page_views')
return jsonify({
'message': 'Hello from Docker Compose!',
'visits': visits,
'timestamp': datetime.now().isoformat()
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/users', methods=['GET', 'POST'])
def users():
if request.method == 'POST':
try:
data = request.get_json()
conn = get_db_connection()
cur = conn.cursor()
cur.execute(
"INSERT INTO users (name, email) VALUES (%s, %s) RETURNING id",
(data['name'], data['email'])
)
user_id = cur.fetchone()[0]
conn.commit()
cur.close()
conn.close()
return jsonify({'id': user_id, 'message': 'User created successfully'})
except Exception as e:
return jsonify({'error': str(e)}), 500
else: # GET
try:
# Проверяем кэш
cached_users = redis_client.get('users_cache')
if cached_users:
return jsonify({
'users': json.loads(cached_users),
'source': 'cache'
})
# Запрос к базе данных
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name, email, created_at FROM users ORDER BY created_at DESC")
users = []
for row in cur.fetchall():
users.append({
'id': row[0],
'name': row[1],
'email': row[2],
'created_at': row[3].isoformat()
})
cur.close()
conn.close()
# Кэшируем результат на 5 минут
redis_client.setex('users_cache', 300, json.dumps(users))
return jsonify({
'users': users,
'source': 'database'
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/health')
def health():
health_status = {
'web': 'healthy',
'database': 'unknown',
'redis': 'unknown'
}
# Проверка PostgreSQL
try:
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT 1")
cur.close()
conn.close()
health_status['database'] = 'healthy'
except:
health_status['database'] = 'unhealthy'
# Проверка Redis
try:
redis_client.ping()
health_status['redis'] = 'healthy'
except:
health_status['redis'] = 'unhealthy'
overall_status = 'healthy' if all(
status == 'healthy' for status in health_status.values()
) else 'degraded'
return jsonify({
'status': overall_status,
'services': health_status
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# 3. Создайте requirements.txt
cat > requirements.txt << 'EOF'
Flask==2.3.3
psycopg2-binary==2.9.7
redis==4.6.0
gunicorn==21.2.0
EOF
# 4. Создайте Dockerfile для веб-приложения
cat > Dockerfile << 'EOF'
FROM python:3.12-slim
# Установка системных зависимостей для PostgreSQL
RUN apt-get update && apt-get install -y \
libpq-dev \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/*
# Создание пользователя
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
# Установка Python зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копирование кода приложения
COPY --chown=appuser:appgroup app.py .
USER appuser
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
EOF
# 5. Создайте скрипт инициализации базы данных
mkdir init-scripts
cat > init-scripts/01-init.sql << 'EOF'
-- Создание таблицы пользователей
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Вставка тестовых данных
INSERT INTO users (name, email) VALUES
('John Doe', 'john@example.com'),
('Jane Smith', 'jane@example.com'),
('DevOps Engineer', 'devops@example.com')
ON CONFLICT (email) DO NOTHING;
EOF
# 6. Создайте конфигурацию Nginx
mkdir nginx
cat > nginx/nginx.conf << 'EOF'
events {
worker_connections 1024;
}
http {
upstream web_backend {
server web:5000;
}
# Логирование
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
server {
listen 80;
server_name localhost;
# Проксирование запросов к приложению
location / {
proxy_pass http://web_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout настройки
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Статус Nginx
location /nginx-health {
access_log off;
return 200 "nginx healthy\n";
add_header Content-Type text/plain;
}
}
}
EOF
# 7. Создайте docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
# Веб-приложение
web:
build: .
environment:
- DB_HOST=postgres
- DB_NAME=myapp
- DB_USER=postgres
- DB_PASSWORD=secure_password
- REDIS_HOST=redis
- FLASK_ENV=production
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- app_logs:/app/logs
restart: unless-stopped
networks:
- app-network
# PostgreSQL база данных
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- app-network
# Redis для кэширования
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass redis_password
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis_password", "ping"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
networks:
- app-network
# Nginx как reverse proxy
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- nginx_logs:/var/log/nginx
depends_on:
- web
restart: unless-stopped
networks:
- app-network
# Мониторинг с помощью cAdvisor
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
ports:
- "8081:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
devices:
- /dev/kmsg
privileged: true
restart: unless-stopped
networks:
- app-network
volumes:
postgres_data:
redis_data:
app_logs:
nginx_logs:
networks:
app-network:
driver: bridge
EOF
# 8. Обновите приложение для работы с паролем Redis
sed -i 's/decode_responses=True/password="redis_password", decode_responses=True/' app.py
# 9. Запустите приложение
echo "Запуск многоконтейнерного приложения..."
docker-compose up -d
# 10. Дождитесь запуска всех сервисов
echo "Ожидание запуска сервисов..."
sleep 30
# 11. Проверьте статус сервисов
echo "Статус сервисов:"
docker-compose ps
# 12. Тестирование приложения
echo -e "\nТестирование приложения:"
# Проверка главной страницы
echo "1. Главная страница:"
curl -s http://localhost:8080/ | python3 -m json.tool
# Проверка health check
echo -e "\n2. Health check:"
curl -s http://localhost:8080/health | python3 -m json.tool
# Получение списка пользователей
echo -e "\n3. Список пользователей:"
curl -s http://localhost:8080/users | python3 -m json.tool
# Создание нового пользователя
echo -e "\n4. Создание нового пользователя:"
curl -s -X POST -H "Content-Type: application/json" \
-d '{"name":"Test User","email":"test@example.com"}' \
http://localhost:8080/users | python3 -m json.tool
# Повторное получение списка пользователей
echo -e "\n5. Обновленный список пользователей:"
curl -s http://localhost:8080/users | python3 -m json.tool
# 13. Показать логи сервисов
echo -e "\nПоследние логи веб-приложения:"
docker-compose logs --tail=10 web
echo -e "\nДля мониторинга откройте:"
echo "- Приложение: http://localhost:8080"
echo "- cAdvisor: http://localhost:8081"
Проверка: Все сервисы должны быть в статусе “Up”, приложение должно создавать пользователей и кэшировать данные.
Задание 3: Оптимизация Docker образов (30 минут) #
# 1. Создайте проект для оптимизации
mkdir ~/docker-optimization && cd ~/docker-optimization
# 2. Создайте "плохой" Dockerfile
cat > Dockerfile.bad << 'EOF'
FROM ubuntu:20.04
# Установка всего подряд
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
RUN apt-get install -y curl
RUN apt-get install -y wget
RUN apt-get install -y vim
RUN apt-get install -y git
RUN apt-get install -y nodejs
RUN apt-get install -y npm
WORKDIR /app
# Копирование всего проекта
COPY . .
# Установка зависимостей
RUN pip3 install flask gunicorn psycopg2-binary redis
EXPOSE 5000
CMD ["python3", "app.py"]
EOF
# 3. Создайте простое приложение
cat > app.py << 'EOF'
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from optimized Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# 4. Соберите "плохой" образ и проверьте размер
docker build -f Dockerfile.bad -t bad-image .
echo "Размер неоптимизированного образа:"
docker images bad-image
# 5. Создайте оптимизированный Dockerfile
cat > Dockerfile.optimized << 'EOF'
# Многоэтапная сборка
FROM python:3.12-alpine AS builder
# Установка build зависимостей в одном слое
RUN apk add --no-cache gcc musl-dev postgresql-dev
# Создание виртуального окружения
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Копирование и установка зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Финальный этап
FROM python:3.12-alpine
# Установка runtime зависимостей
RUN apk add --no-cache libpq curl
# Копирование виртуального окружения
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Создание пользователя
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
# Копирование только необходимых файлов
COPY --chown=appuser:appgroup app.py .
USER appuser
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/ || exit 1
CMD ["python", "app.py"]
EOF
# 6. Создайте файл зависимостей
cat > requirements.txt << 'EOF'
Flask==2.3.3
gunicorn==21.2.0
EOF
# 7. Создайте .dockerignore
cat > .dockerignore << 'EOF'
Dockerfile*
.dockerignore
.git
.gitignore
README.md
.pytest_cache
.coverage
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
logs
EOF
# 8. Соберите оптимизированный образ
docker build -f Dockerfile.optimized -t optimized-image .
# 9. Сравните размеры образов
echo -e "\nСравнение размеров образов:"
docker images | grep -E "(bad-image|optimized-image)"
# 10. Анализ слоев образа (если установлен dive)
if command -v dive &> /dev/null; then
echo -e "\nАнализ слоев оптимизированного образа:"
dive optimized-image
else
echo -e "\nДля анализа слоев установите dive:"
echo "https://github.com/wagoodman/dive"
fi
# 11. Проверьте безопасность образа
echo -e "\nСканирование безопасности:"
if command -v docker &> /dev/null; then
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image optimized-image
fi
# 12. Тест производительности запуска
echo -e "\nТест производительности запуска:"
echo "Плохой образ:"
time docker run --rm bad-image python3 -c "print('Started')"
echo -e "\nОптимизированный образ:"
time docker run --rm optimized-image python -c "print('Started')"
Проверка: Оптимизированный образ должен быть значительно меньше по размеру и быстрее запускаться.
Задание 4: Docker в продакшене (35 минут) #
# 1. Создайте проект для продакшен деплоя
mkdir ~/docker-production && cd ~/docker-production
# 2. Создайте продакшен приложение с мониторингом
cat > app.py << 'EOF'
from flask import Flask, jsonify, request
import psycopg2
import redis
import os
import logging
import time
from datetime import datetime
from prometheus_client import Counter, Histogram, generate_latest
import threading
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
# Prometheus метрики
REQUEST_COUNT = Counter('flask_requests_total', 'Total Flask requests', ['method', 'endpoint'])
REQUEST_DURATION = Histogram('flask_request_duration_seconds', 'Flask request duration')
@app.before_request
def before_request():
request.start_time = time.time()
@app.after_request
def after_request(response):
REQUEST_COUNT.labels(method=request.method, endpoint=request.endpoint).inc()
REQUEST_DURATION.observe(time.time() - request.start_time)
return response
@app.route('/')
def home():
logger.info("Home page accessed")
return jsonify({
'message': 'Production Docker Application',
'version': os.getenv('APP_VERSION', 'unknown'),
'environment': os.getenv('ENVIRONMENT', 'production')
})
@app.route('/metrics')
def metrics():
return generate_latest()
@app.route('/health')
def health():
return jsonify({'status': 'healthy'})
@app.errorhandler(500)
def internal_error(error):
logger.error(f"Internal server error: {error}")
return jsonify({'error': 'Internal server error'}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# 3. Создайте requirements.txt с мониторингом
cat > requirements.txt << 'EOF'
Flask==2.3.3
gunicorn==21.2.0
psycopg2-binary==2.9.7
redis==4.6.0
prometheus-client==0.17.1
EOF
# 4. Создайте продакшен Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.12-slim
# Метаданные образа
LABEL maintainer="devops@company.com"
LABEL version="1.0.0"
LABEL description="Production Flask Application"
# Установка системных зависимостей
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# Создание пользователя
RUN groupadd -r -g 1001 appgroup && \
useradd -r -u 1001 -g appgroup -s /bin/false appuser
# Создание директорий
RUN mkdir -p /app /app/logs /app/data && \
chown -R appuser:appgroup /app
WORKDIR /app
# Установка Python зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# Копирование кода приложения
COPY --chown=appuser:appgroup app.py .
COPY --chown=appuser:appgroup gunicorn.conf.py .
# Переключение на непривилегированного пользователя
USER appuser
# Переменные окружения
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1
ENV FLASK_ENV=production
EXPOSE 5000
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# Запуск с Gunicorn
CMD ["gunicorn", "--config", "gunicorn.conf.py", "app:app"]
EOF
# 5. Создайте конфигурацию Gunicorn
cat > gunicorn.conf.py << 'EOF'
import multiprocessing
import os
# Сервер настройки
bind = "0.0.0.0:5000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 100
# Таймауты
timeout = 30
keepalive = 5
# Логирование
accesslog = "-"
errorlog = "-"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
loglevel = "info"
# Процесс
preload_app = True
pidfile = "/tmp/gunicorn.pid"
# Мониторинг
def when_ready(server):
server.log.info("Server is ready. Spawning workers")
def worker_int(worker):
worker.log.info("worker received INT or QUIT signal")
def on_exit(server):
server.log.info("Server is shutting down")
EOF
# 6. Создайте docker-compose для продакшена
cat > docker-compose.prod.yml << 'EOF'
version: '3.8'
services:
app:
build: .
environment:
- ENVIRONMENT=production
- APP_VERSION=v1.0.0
volumes:
- app_logs:/app/logs
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
networks:
- app-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx
depends_on:
app:
condition: service_healthy
restart: always
deploy:
resources:
limits:
cpus: '0.5'
memory: 128M
networks:
- app-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
restart: always
networks:
- app-network
volumes:
app_logs:
nginx_logs:
prometheus_data:
networks:
app-network:
driver: bridge
EOF
# 7. Создайте конфигурацию Nginx для продакшена
mkdir -p nginx
cat > nginx/nginx.conf << 'EOF'
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Логирование
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
# Оптимизация производительности
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
client_max_body_size 50M;
# Gzip сжатие
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# Upstream для приложения
upstream app_backend {
server app:5000 max_fails=3 fail_timeout=30s;
keepalive 32;
}
# Основной сервер
server {
listen 80;
server_name localhost;
# Безопасность
server_tokens off;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# Проксирование к приложению
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Таймауты
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Буферизация
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# Health check
location /nginx-health {
access_log off;
return 200 "nginx healthy\n";
add_header Content-Type text/plain;
}
# Статус Nginx
location /nginx-status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
}
EOF
# 8. Создайте конфигурацию Prometheus
mkdir -p prometheus
cat > prometheus/prometheus.yml << 'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'flask-app'
static_configs:
- targets: ['app:5000']
metrics_path: '/metrics'
scrape_interval: 10s
- job_name: 'nginx'
static_configs:
- targets: ['nginx:80']
metrics_path: '/nginx-status'
scrape_interval: 30s
EOF
# 9. Создайте скрипт деплоя
cat > deploy.sh << 'EOF'
#!/bin/bash
set -e
echo "🚀 Запуск продакшен деплоя..."
# Сборка образов
echo "📦 Сборка Docker образов..."
docker-compose -f docker-compose.prod.yml build
# Запуск сервисов
echo "🔄 Запуск сервисов..."
docker-compose -f docker-compose.prod.yml up -d
# Ожидание готовности
echo "⏳ Ожидание готовности сервисов..."
sleep 30
# Проверка здоровья
echo "🏥 Проверка здоровья сервисов..."
if curl -f http://localhost/health > /dev/null 2>&1; then
echo "✅ Приложение работает"
else
echo "❌ Приложение недоступно"
docker-compose -f docker-compose.prod.yml logs app
exit 1
fi
# Показать статус
echo "📊 Статус сервисов:"
docker-compose -f docker-compose.prod.yml ps
echo "🎉 Деплой завершен!"
echo "🌐 Приложение: http://localhost"
echo "📈 Prometheus: http://localhost:9090"
EOF
chmod +x deploy.sh
# 10. Запустите продакшен деплой
./deploy.sh
# 11. Проведите нагрузочное тестирование
echo -e "\n🔥 Нагрузочное тестирование..."
if command -v ab &> /dev/null; then
ab -n 1000 -c 10 http://localhost/
else
echo "Для нагрузочного тестирования установите Apache Bench (apt install apache2-utils)"
fi
# 12. Показать метрики
echo -e "\n📊 Метрики приложения:"
curl -s http://localhost/metrics | grep flask_requests_total
Проверка: Все сервисы должны быть в продакшен режиме с мониторингом, логированием и ограничениями ресурсов.
Задание 5: CI/CD с Docker (25 минут) #
# 1. Создайте проект с CI/CD
mkdir ~/docker-cicd && cd ~/docker-cicd
# 2. Создайте простое приложение для CI/CD
cat > app.py << 'EOF'
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def home():
return jsonify({
'message': 'Hello from CI/CD Pipeline!',
'version': os.getenv('VERSION', 'development'),
'build': os.getenv('BUILD_NUMBER', 'local')
})
@app.route('/health')
def health():
return jsonify({'status': 'healthy'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
# 3. Создайте тесты
mkdir tests
cat > tests/test_app.py << 'EOF'
import pytest
from app import app
@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_home(client):
response = client.get('/')
assert response.status_code == 200
data = response.get_json()
assert 'message' in data
def test_health(client):
response = client.get('/health')
assert response.status_code == 200
data = response.get_json()
assert data['status'] == 'healthy'
EOF
# 4. Создайте requirements.txt
cat > requirements.txt << 'EOF'
Flask==2.3.3
pytest==7.4.0
EOF
# 5. Создайте Dockerfile для CI/CD
cat > Dockerfile << 'EOF'
FROM python:3.12-slim as base
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# Стадия тестирования
FROM base as test
COPY . .
RUN pytest tests/ -v
# Продакшн стадия
FROM base as production
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
EOF
# 6. Создайте GitHub Actions workflow
mkdir -p .github/workflows
cat > .github/workflows/docker-ci.yml << 'EOF'
name: Docker CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: pytest tests/ -v --tb=short
- name: Run linting
run: |
pip install flake8
flake8 app.py tests/ --max-line-length=88 --exclude=.git,__pycache__
build:
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v4
with:
context: .
target: production
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
BUILD_NUMBER=${{ github.run_number }}
security-scan:
needs: build
runs-on: ubuntu-latest
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ needs.build.outputs.image-tag }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
deploy:
needs: [build, security-scan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy to staging
run: |
echo "Deploying to staging environment..."
echo "Image: ${{ needs.build.outputs.image-tag }}"
echo "Digest: ${{ needs.build.outputs.image-digest }}"
# Здесь могут быть команды для деплоя в staging
- name: Run smoke tests
run: |
echo "Running smoke tests..."
sleep 5
# curl -f http://staging.example.com/health
- name: Deploy to production
if: success()
run: |
echo "Deploying to production environment..."
# Здесь могут быть команды для деплоя в production
EOF
# 7. Создайте Docker Compose для разработки
cat > docker-compose.dev.yml << 'EOF'
version: '3.8'
services:
app:
build:
context: .
target: base
ports:
- "5000:5000"
volumes:
- .:/app
environment:
- FLASK_ENV=development
- VERSION=development
command: python app.py
test:
build:
context: .
target: test
volumes:
- .:/app
command: pytest tests/ -v --tb=short
EOF
# 8. Создайте Makefile для удобства
cat > Makefile << 'EOF'
.PHONY: help build test run clean deploy
help: ## Показать справку
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
build: ## Собрать Docker образ
docker build -t docker-cicd:latest .
test: ## Запустить тесты
docker-compose -f docker-compose.dev.yml run --rm test
run: ## Запустить приложение для разработки
docker-compose -f docker-compose.dev.yml up app
clean: ## Очистить Docker ресурсы
docker-compose -f docker-compose.dev.yml down -v
docker system prune -f
lint: ## Проверить код линтером
docker run --rm -v $(PWD):/app python:3.11-slim sh -c "cd /app && pip install flake8 && flake8 app.py tests/ --max-line-length=88"
security-scan: ## Сканировать образ на уязвимости
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image docker-cicd:latest
deploy-local: ## Локальный деплой
docker run -d --name docker-cicd-app -p 8080:5000 docker-cicd:latest
@echo "Приложение доступно по адресу: http://localhost:8080"
stop-local: ## Остановить локальный деплой
docker stop docker-cicd-app || true
docker rm docker-cicd-app || true
EOF
# 9. Создайте скрипт для полного CI/CD pipeline
cat > ci-cd-pipeline.sh << 'EOF'
#!/bin/bash
set -e
echo "🚀 Запуск локального CI/CD pipeline..."
# 1. Линтинг
echo "🔍 Запуск линтера..."
make lint
# 2. Тестирование
echo "🧪 Запуск тестов..."
make test
# 3. Сборка
echo "📦 Сборка образа..."
make build
# 4. Сканирование безопасности
echo "🔒 Сканирование безопасности..."
make security-scan
# 5. Деплой
echo "🚀 Локальный деплой..."
make stop-local || true
make deploy-local
# 6. Smoke тесты
echo "💨 Smoke тесты..."
sleep 5
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
echo "✅ Приложение работает корректно"
else
echo "❌ Приложение недоступно"
exit 1
fi
echo "🎉 CI/CD pipeline завершен успешно!"
echo "🌐 Приложение доступно: http://localhost:8080"
EOF
chmod +x ci-cd-pipeline.sh
# 10. Запустите локальный CI/CD pipeline
./ci-cd-pipeline.sh
# 11. Создайте README с инструкциями
cat > README.md << 'EOF'
# Docker CI/CD Example
Пример настройки CI/CD pipeline с Docker.
## Команды
- `make help` - показать все доступные команды
- `make test` - запустить тесты
- `make build` - собрать Docker образ
- `make run` - запустить для разработки
- `make deploy-local` - локальный деплой
- `./ci-cd-pipeline.sh` - полный pipeline
## Структура проекта
```text
.
├── app.py # Основное приложение
├── tests/ # Тесты
├── Dockerfile # Multi-stage Dockerfile
├── docker-compose.dev.yml # Для разработки
├── .github/workflows/ # GitHub Actions
├── Makefile # Команды автоматизации
└── ci-cd-pipeline.sh # Локальный pipeline
Проверка: Pipeline должен пройти все этапы: линтинг, тесты, сборка, сканирование безопасности и деплой.
📊 Проверочный тест Docker #
- Какая команда создает и запускает контейнер в одной команде?
- Как ограничить использование памяти контейнером до 512MB?
- Какой командой можно посмотреть логи всех сервисов в docker-compose?
- Как создать именованный том в Docker?
- Какая инструкция Dockerfile переключает пользователя?
Ответы:
docker run
docker run --memory=512m image_name
docker-compose logs
docker volume create volume_name
USER username
🛠️ Полезные инструменты #
Lazydocker - TUI для Docker #
# Установка
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
# Использование
lazydocker
Portainer - веб-интерфейс для Docker #
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 \
--name portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
Dive - анализ слоев Docker образа #
# Установка
wget https://github.com/wagoodman/dive/releases/download/v0.10.0/dive_0.10.0_linux_amd64.deb
sudo apt install ./dive_0.10.0_linux_amd64.deb
# Использование
dive myapp:latest
🎯 Заключение #
Docker — это основа современной контейнеризации. Ключевые принципы:
✅ Контейнеризируйте всё - приложения, базы данных, инструменты
✅ Используйте минимальные образы для безопасности и производительности
✅ Применяйте многоэтапную сборку для оптимизации размера
✅ Не запускайте как root в продакшене
✅ Автоматизируйте сборку и деплой через CI/CD
✅ Мониторьте контейнеры в реальном времени
Помните: Docker - это не просто технология, это новый способ мышления об упаковке и доставке приложений. Изучайте постепенно и практикуйтесь на реальных проектах.
Следующий раздел: 2.4 Kubernetes для начинающих