Files
LMS/deploy/WOODPECKER.md
Vladislav Kopnin f3d614bb33 ci: изолировать пайплайны на отдельной VM
- Woodpecker workflows требуют агент с меткой isolation=dedicated-vm
- шаги выполняются в lms-ci:1 с nested dockerd, без хост-Docker сокета
- ci/Dockerfile: прогретый immutable-образ (Java, Python, Node, Gradle-кэш)
- ci-dind.sh поднимает dockerd внутри privileged-шага; ci-build-image.sh собирает образ
- deploy/WOODPECKER.md: отдельная VM, firewall, отсутствие production-секретов,
  trusted repo, чек-лист безопасности
2026-10-29 12:01:44 +03:00

12 KiB
Raw Permalink Blame History

CI на Gitea + Woodpecker: изолированный контур

Репозиторий использует Woodpecker CI 3.x. Конфигурация — в .woodpecker/:

Workflow Событие Что делает
check push, pull_request тесты, ktlint, detekt, evidenceCheck
smoke push, pull_request после check: полный стек, health, вход админа, один курс
release tag, manual миграции, mutation, load, Chromium/Firefox/WebKit E2E, финальный evidence

1. Модель изоляции

CI не должен работать на хосте LMS и не должен иметь доступ к production. Разработчики не считаются доверенными: пайплайн может содержать произвольный код, включая privileged-контейнеры.

Схема:

Gitea + Woodpecker server          отдельная CI VM
┌───────────────────────┐          ┌──────────────────────────────┐
│ Gitea (production)    │  webhook │ Woodpecker agent (Docker)    │
│ Woodpecker server     │◄────────►│  └─ pipeline step            │
│                       │   gRPC   │      └─ nested dockerd (dind)│
└───────────────────────┘          │          ├─ postgres         │
                                   │          ├─ LMS-сервисы      │
                                   │          └─ Playwright       │
                                   └──────────────────────────────┘
                                      нет prod-сети и секретов

Правила:

  • Отдельная VM (или отдельный физический хост) для агента. Не запускайте агент на сервере LMS/Gitea.
  • На VM нет production-БД, production-томов, deploy-ключей и LMS_INTERNAL_TOKEN/паролей production. Только одноразовые тестовые значения, которые создаёт сам пайплайн.
  • Сетевой firewall: VM может ходить в Gitea/Woodpecker и к registry/пакетным зеркалам; не может в production-подсеть и к production-БД.
  • VM должна пересоздаваться из чистого снапшота после релиза или по расписанию. Всё, что осталось на диске, считается недоверенным.
  • Пайплайн выполняется в образе lms-ci:1 (на базе docker:27-dind) с собственным nested dockerd. Хост-Docker сокет в шаг не монтируется. privileged нужен только этому вложенному демону; на отдельной VM это допустимо.
  • Агент помечен isolation=dedicated-vm; workflows требуют эту метку. Это не даёт им случайно попасть на агент, стоящий рядом с production.
  • В Woodpecker не должно быть production-secrets. Если позже понадобится деплой — отдельный защищённый агент/пайплайн с ручным подтверждением, не этот.

Важно: privileged-шаг может скомпрометировать VM, но не production. Именно поэтому VM обязательна. Не ослабляйте это правило.

2. Требования к CI VM

  • Docker Engine + Compose v2.
  • 4+ CPU, 8+ GB RAM, 40+ GB диска (Gradle, Docker-образы, браузеры).
  • Исходящий доступ к Gitea, registry и пакетным зеркалам.
  • Нет входящих портов, кроме SSH для администрирования.
  • Нет монтирования production-томов и нет production-сетей.

3. Woodpecker server

Server можно держать рядом с Gitea (он не выполняет код пайплайнов). Пример docker-compose.woodpecker.yml (секреты не коммитить):

services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:v3
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"   # UI за nginx
      - "127.0.0.1:9000:9000"   # gRPC для агента
    environment:
      WOODPECKER_OPEN: "false"
      WOODPECKER_HOST: ${WOODPECKER_HOST:?https://ci.example.ru}
      WOODPECKER_GITEA: "true"
      WOODPECKER_GITEA_URL: ${GITEA_URL:?https://git.example.ru}
      WOODPECKER_GITEA_CLIENT: ${GITEA_CLIENT:?}
      WOODPECKER_GITEA_SECRET: ${GITEA_SECRET:?}
      WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:?}
      WOODPECKER_ADMIN: ${WOODPECKER_ADMIN:?admin}
    volumes:
      - woodpecker-data:/var/lib/woodpecker

volumes:
  woodpecker-data:
  1. В Gitea создайте OAuth2-приложение: redirect URI https://ci.example.ru/authorize.
  2. Заполните GITEA_CLIENT, GITEA_SECRET, WOODPECKER_AGENT_SECRET (например, openssl rand -hex 32), WOODPECKER_HOST, GITEA_URL.
  3. Если Gitea и Woodpecker на одном хосте, разрешите webhook на loopback в /etc/gitea/conf/app.ini:
    [webhook]
    ALLOWED_HOST_LIST = external,loopback
    
    Проверьте, что Gitea отдаёт API-страницы не меньше 50 элементов.
  4. Поднимите server и зайдите в UI через Gitea-аккаунт.
  5. Включите репозиторий LMS в Woodpecker (Settings → Repositories).
  6. В настройках репозитория включите Trusted (нужно для privileged и volumes). Доверять можно только этому приватному репозиторию.

4. Агент на отдельной VM

Агент — в Docker, backend docker, с доступом к Docker daemon этой VM. Шаги пайплайна получают не хостовый сокет, а собственный nested dockerd.

docker-compose.agent.yml на CI VM:

services:
  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:v3
    restart: unless-stopped
    environment:
      WOODPECKER_SERVER: ${WOODPECKER_SERVER:?https://ci.example.ru:9000}
      WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:?}
      WOODPECKER_BACKEND: docker
      WOODPECKER_MAX_WORKFLOWS: "1"
      # Только этот агент должен брать workflows с этой меткой.
      WOODPECKER_AGENT_LABELS: isolation=dedicated-vm
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
docker compose -f docker-compose.agent.yml up -d
docker compose -f docker-compose.agent.yml logs -f

Дополнительно в UI откройте Settings → Agents и задайте агенту фильтр isolation=dedicated-vm — это серверная проверка, которую агент не может подделать.

Общего кэша между пайплайнами нет. Каждый шаг получает чистый контейнер, а вложенный Docker storage живёт только внутри него. Это важно: разработчик не может оставить артефакт, который повлияет на следующий pipeline или релиз.

Пайплайны используют предварительно прогретый образ lms-ci:1. Соберите его на VM из проверенного администратором коммита до первого запуска:

git checkout <проверенный-коммит>
./scripts/ci-build-image.sh lms-ci:1

Сборка образа выполняет Gradle-код из репозитория. Не собирайте образ из непроверенной ветки/PR: это запуск недоверенного кода на CI VM.

Образ содержит Docker-in-Docker, Java 21, Python, bash, Node и прогретый Gradle-кэш; исходники всё равно берутся из checkout. Пересобирайте образ при изменении зависимостей или базовых версий. Не монтируйте в шаги общие writable-кэши: это канал влияния одного пайплайна на другой.

5. Как устроены workflows

  • check, smoke и release требуют labels.isolation=dedicated-vm.
  • smoke ждёт успешного check (depends_on).
  • Все workflows имеют concurrency: {limit: 1, group: lms-ci}, потому что smoke/release поднимают стек на портах 8080–8090.
  • check и smoke/release используют lms-ci:1 + ./scripts/ci-dind.sh: скрипт поднимает вложенный dockerd. Образ собирается из ci/Dockerfile.
  • release перед миграционным тестом делает git fetch master: CI-клон может быть shallow.
  • smoke/release не получают хост-Docker сокет; docker compose работает с вложенным демоном.

6. Первый прогон

  1. На CI VM соберите образ: ./scripts/ci-build-image.sh lms-ci:1.
  2. Push в ветку или MR → check, затем smoke.
  3. Убедитесь, что оба workflow зелёные и выполнялись на агенте с меткой isolation=dedicated-vm.
  4. Проверьте ручной запуск: UI → Run pipeline → event manual → workflow release.
  5. Для релиза: git tag vX.Y.Z && git push origin vX.Y.Z → release.
  6. После релиза/по расписанию пересоздайте VM из чистого снапшота.

Если pipeline не появляется:

  • репозиторий включён в Woodpecker;
  • у агента и server совпадает WOODPECKER_AGENT_SECRET;
  • агент видит Docker: docker compose version на VM;
  • метка агента isolation=dedicated-vm и серверный фильтр совпадают;
  • путь конфигурации проекта — .woodpecker/ (по умолчанию);
  • образ lms-ci:1 собран на VM (иначе шаг не стартует);
  • репозиторий помечен trusted, иначе privileged будет отклонён.

7. Чек-лист безопасности

  • Агент не на production-хосте.
  • На VM нет production-секретов, БД и томов.
  • Firewall запрещает VM → production.
  • В Woodpecker нет production-secrets.
  • Включён trusted только для LMS, не для чужих репозиториев.
  • WOODPECKER_AGENT_SECRET уникален и хранится вне репозитория.
  • VM пересоздаётся из снапшота; кэш не считается доверенным.
  • privileged разрешён только на этой VM, не на общем runner.