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

205 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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