Содержание
Разработчик делает изменение, пушит в GitHub — и через 5 минут оно уже на продакшн-сервере. Тесты прошли, Docker-образ собран, деплой выполнен. Всё автоматически. Звучит как мечта? Это реальность с GitHub Actions, и настроить это можно за пару часов.
1. Что такое CI/CD и зачем это нужно
CI (Continuous Integration) — автоматический запуск тестов при каждом изменении кода. Разработчик узнаёт о проблеме через 5 минут, а не через 2 дня когда уже всё сломалось.
CD (Continuous Delivery/Deployment) — автоматическая доставка кода на сервер после успешных тестов. Деплой перестаёт быть страшным ритуалом раз в месяц.
Без CI/CD типичная проблема выглядит так:
- Разработчик пушит код в пятницу вечером
- Деплой вручную — копирование файлов по SSH, перезапуск сервисов
- Что-то сломалось — непонятно что именно и как откатить
- Поддержка получает жалобы в выходные
2. Как работает GitHub Actions
GitHub Actions — это встроенная CI/CD платформа в GitHub. Бесплатно для публичных репозиториев, для приватных — 2000 минут/месяц бесплатно.
Конфигурация хранится в файлах YAML в папке .github/workflows/. Каждый файл — это workflow (рабочий процесс). Workflow состоит из jobs (задач), задачи из steps (шагов).
# Структура workflow-файла name: CI/CD Pipeline # Название on: # Триггеры запуска push: branches: [main, develop] pull_request: branches: [main] jobs: # Задачи test: # Имя задачи runs-on: ubuntu-latest # Тип runner'а steps: # Шаги - name: Checkout code uses: actions/checkout@v4
3. Первый workflow: запуск тестов
Создаём файл .github/workflows/ci.yml:
name: CI — Tests on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: # 1. Получаем код репозитория - name: Checkout uses: actions/checkout@v4 # 2. Устанавливаем Python - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' # 3. Кэшируем зависимости (ускоряет последующие запуски) - name: Cache pip uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} # 4. Устанавливаем зависимости - name: Install dependencies run: | pip install -r requirements.txt pip install pytest pytest-cov # 5. Запускаем линтер - name: Lint with flake8 run: | pip install flake8 flake8 . --max-line-length=100 --exclude=venv # 6. Запускаем тесты с покрытием - name: Run tests run: pytest --cov=. --cov-report=xml # 7. Загружаем отчёт о покрытии - name: Upload coverage uses: codecov/codecov-action@v4 with: file: ./coverage.xml
4. Добавляем сборку Docker
Создаём Dockerfile для нашего Python-приложения:
# Dockerfile FROM python:3.11-slim WORKDIR /app # Копируем зависимости отдельно для кэширования COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Копируем код COPY . . # Создаём непривилегированного пользователя RUN adduser --disabled-password --gecos '' appuser USER appuser EXPOSE 8000 CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Добавляем сборку и пуш Docker-образа в workflow:
# Добавляем job после тестов build: needs: test # Запускается только после успешных тестов runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Checkout uses: actions/checkout@v4 # Авторизация в Docker Hub - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} # Сборка и пуш образа - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | myapp/backend:latest myapp/backend:${{ github.sha }}
5. Автодеплой на сервер
После сборки образа деплоим на продакшн-сервер через SSH:
deploy: needs: build runs-on: ubuntu-latest steps: - name: Deploy to server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} script: | # Обновляем образ docker pull myapp/backend:latest # Останавливаем старый контейнер docker stop myapp-backend || true docker rm myapp-backend || true # Запускаем новый docker run -d \ --name myapp-backend \ --restart unless-stopped \ -p 8000:8000 \ --env-file /home/deploy/.env \ myapp/backend:latest # Проверяем что запустился sleep 5 docker ps | grep myapp-backend # Уведомление в Telegram о деплое - name: Notify Telegram if: always() run: | STATUS="${{ job.status }}" EMOJI=$([ "$STATUS" = "success" ] && echo "✅" || echo "❌") curl -s -X POST "https://api.telegram.org/bot${{ secrets.TG_BOT_TOKEN }}/sendMessage" \ -d "chat_id=${{ secrets.TG_CHAT_ID }}" \ -d "text=$EMOJI Деплой $STATUS: ${{ github.repository }} @ ${{ github.sha }}"
6. Работа с секретами
Пароли, токены и ключи SSH нельзя хранить в коде. GitHub Secrets — правильное место для них.
Добавляем секреты: Settings → Secrets and variables → Actions → New repository secret.
Нужные секреты для нашего пайплайна:
DOCKER_USERNAME— логин Docker HubDOCKER_TOKEN— токен Docker Hub (не пароль)SERVER_HOST— IP или домен сервераSERVER_USER— пользователь для SSHSERVER_SSH_KEY— приватный SSH-ключTG_BOT_TOKEN— токен Telegram-бота для уведомленийTG_CHAT_ID— chat_id для уведомлений
7. Советы по оптимизации
Кэшируйте зависимости
Без кэша каждый запуск устанавливает зависимости заново — это 1–3 минуты. С кэшем — 10 секунд.
Запускайте jobs параллельно
Линтер и тесты можно запускать параллельно — это сокращает время пайплайна вдвое.
jobs: lint: runs-on: ubuntu-latest steps: [...] test: runs-on: ubuntu-latest steps: [...] # Build запускается только когда оба выше завершились успешно build: needs: [lint, test] steps: [...]
Используйте matrix для тестирования на нескольких версиях
jobs: test: strategy: matrix: python-version: ['3.10', '3.11', '3.12'] runs-on: ubuntu-latest steps: - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }}
Нужна настройка CI/CD под ключ?
Настраиваем GitHub Actions, GitLab CI и Jenkins для команд любого размера. От 40 000 ₽, срок 1–2 недели.
Обсудить проект