- 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, чек-лист безопасности
12 KiB
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:
- В Gitea создайте OAuth2-приложение:
redirect URI
https://ci.example.ru/authorize. - Заполните
GITEA_CLIENT,GITEA_SECRET,WOODPECKER_AGENT_SECRET(например,openssl rand -hex 32),WOODPECKER_HOST,GITEA_URL. - Если Gitea и Woodpecker на одном хосте, разрешите webhook на loopback в
/etc/gitea/conf/app.ini:Проверьте, что Gitea отдаёт API-страницы не меньше 50 элементов.[webhook] ALLOWED_HOST_LIST = external,loopback - Поднимите server и зайдите в UI через Gitea-аккаунт.
- Включите репозиторий LMS в Woodpecker (Settings → Repositories).
- В настройках репозитория включите 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 fetchmaster: CI-клон может быть shallow.smoke/releaseне получают хост-Docker сокет;docker composeработает с вложенным демоном.
6. Первый прогон
- На CI VM соберите образ:
./scripts/ci-build-image.sh lms-ci:1. - Push в ветку или MR →
check, затемsmoke. - Убедитесь, что оба workflow зелёные и выполнялись на агенте с меткой
isolation=dedicated-vm. - Проверьте ручной запуск: UI → Run pipeline → event
manual→ workflowrelease. - Для релиза:
git tag vX.Y.Z && git push origin vX.Y.Z→release. - После релиза/по расписанию пересоздайте 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.