2.3 Docker и контейнеризация

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.

Что только что произошло?

  1. Docker скачал образ hello-world из Docker Hub
  2. Создал из него контейнер
  3. Запустил контейнер, который вывел сообщение
  4. Контейнер завершил работу

Попробуем что-то полезное:

# Запустим веб-сервер 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 #

  1. Какая команда создает и запускает контейнер в одной команде?
  2. Как ограничить использование памяти контейнером до 512MB?
  3. Какой командой можно посмотреть логи всех сервисов в docker-compose?
  4. Как создать именованный том в Docker?
  5. Какая инструкция Dockerfile переключает пользователя?

Ответы:

  1. docker run
  2. docker run --memory=512m image_name
  3. docker-compose logs
  4. docker volume create volume_name
  5. 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 для начинающих