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, чек-лист безопасности
This commit is contained in:
@@ -10,6 +10,7 @@ oldLMS
|
|||||||
.env
|
.env
|
||||||
.env.prod
|
.env.prod
|
||||||
node_modules
|
node_modules
|
||||||
|
e2e
|
||||||
__pycache__
|
__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
*.iml
|
*.iml
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
# Merge gate: тесты, линтеры и evidence.
|
# Merge gate: тесты, линтеры и evidence в изолированном Docker-in-Docker.
|
||||||
# Выполняется на Woodpecker-агенте с local backend: нужны Java 21, Docker
|
# Агент с меткой isolation=dedicated-vm работает на отдельной VM; хост-Docker
|
||||||
# (Testcontainers), Docker Compose v2, Python 3 и bash.
|
# сокет в шаг не монтируется.
|
||||||
|
labels:
|
||||||
|
isolation: dedicated-vm
|
||||||
|
|
||||||
when:
|
when:
|
||||||
- event: [push, pull_request]
|
- event: [push, pull_request]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: check
|
- name: check
|
||||||
image: bash
|
image: lms-ci:1
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
commands:
|
commands:
|
||||||
- ./scripts/ci-preflight.sh
|
- ./scripts/ci-dind.sh
|
||||||
- ./gradlew --no-daemon test ktlintCheck detekt evidenceCheck
|
- ./gradlew --no-daemon test ktlintCheck detekt evidenceCheck
|
||||||
|
|
||||||
# Один общий слот: smoke/release поднимают реальный стек на localhost,
|
|
||||||
# параллельный запуск занял бы порты 8080–8090.
|
|
||||||
concurrency:
|
concurrency:
|
||||||
limit: 1
|
limit: 1
|
||||||
group: lms-ci
|
group: lms-ci
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
# Полный release gate: тесты, миграции, mutation, runtime/scale и
|
# Полный release gate в изолированном Docker-in-Docker-шаге на отдельной VM.
|
||||||
# Chromium/Firefox/WebKit E2E. Запускается на тег или вручную.
|
# Запускается на тег или вручную. Хост-Docker сокет не монтируется.
|
||||||
|
labels:
|
||||||
|
isolation: dedicated-vm
|
||||||
|
|
||||||
when:
|
when:
|
||||||
- event: [tag, manual]
|
- event: [tag, manual]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: release
|
- name: release
|
||||||
image: bash
|
image: lms-ci:1
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
commands:
|
commands:
|
||||||
|
# ldd-migration-upgrade.sh сравнивает ветку с master; в CI клон может быть shallow.
|
||||||
|
- git fetch --unshallow origin || true
|
||||||
|
- git fetch origin master:refs/heads/master || true
|
||||||
|
- ./scripts/ci-dind.sh
|
||||||
- cd e2e && npm ci && cd ..
|
- cd e2e && npm ci && cd ..
|
||||||
- ./scripts/ci-preflight.sh --release
|
- ./scripts/ci-preflight.sh --release
|
||||||
- ./scripts/ldd-release.sh
|
- ./scripts/ldd-release.sh
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Дымовой тест на каждый push/MR: полный стек, health, вход админа,
|
# Дымовой тест на каждый push/MR в изолированном Docker-in-Docker-шаге.
|
||||||
# один доменный сценарий. Полный release gate — в release.yaml.
|
# Шаг сам поднимает dockerd внутри себя, поэтому не получает хост-Docker сокет.
|
||||||
|
labels:
|
||||||
|
isolation: dedicated-vm
|
||||||
|
|
||||||
when:
|
when:
|
||||||
- event: [push, pull_request]
|
- event: [push, pull_request]
|
||||||
|
|
||||||
@@ -8,8 +11,12 @@ depends_on:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: smoke
|
- name: smoke
|
||||||
image: bash
|
image: lms-ci:1
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
commands:
|
commands:
|
||||||
|
- ./scripts/ci-dind.sh
|
||||||
- ./scripts/ci-preflight.sh
|
- ./scripts/ci-preflight.sh
|
||||||
- ./scripts/ldd-smoke.sh
|
- ./scripts/ldd-smoke.sh
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ progress `:8083`, web `:8090`. Интерфейс: `http://localhost:8090`.
|
|||||||
./scripts/ldd-browser.sh # Playwright E2E: Chromium/Firefox/WebKit
|
./scripts/ldd-browser.sh # Playwright E2E: Chromium/Firefox/WebKit
|
||||||
./scripts/ldd-release.sh # полный release gate: test/upgrade/mutation/runtime/E2E
|
./scripts/ldd-release.sh # полный release gate: test/upgrade/mutation/runtime/E2E
|
||||||
./scripts/ci-preflight.sh --release # проверка окружения runner без установки
|
./scripts/ci-preflight.sh --release # проверка окружения runner без установки
|
||||||
|
./scripts/ci-build-image.sh # прогретый CI-образ lms-ci:1 для Woodpecker
|
||||||
./scripts/legacy-assets.sh # инвентаризация бесхозных файлов на стенде (admin)
|
./scripts/legacy-assets.sh # инвентаризация бесхозных файлов на стенде (admin)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,8 +89,8 @@ progress `:8083`, web `:8090`. Интерфейс: `http://localhost:8090`.
|
|||||||
workflow `check` (`test ktlintCheck detekt evidenceCheck`) и `smoke`
|
workflow `check` (`test ktlintCheck detekt evidenceCheck`) и `smoke`
|
||||||
(`scripts/ldd-smoke.sh`) на каждом push/MR; полный `release` —
|
(`scripts/ldd-smoke.sh`) на каждом push/MR; полный `release` —
|
||||||
`scripts/ldd-release.sh` — на тег или вручную. Smoke/release идут на
|
`scripts/ldd-release.sh` — на тег или вручную. Smoke/release идут на
|
||||||
Woodpecker-агенте с local backend: Java 21, Docker Compose v2, Python 3,
|
отдельной CI VM: Woodpecker Docker backend, nested dind, Java 21,
|
||||||
Node и Chromium/Firefox/WebKit. Установка —
|
Docker Compose v2, Python 3, Node и Chromium/Firefox/WebKit. Установка —
|
||||||
[`deploy/WOODPECKER.md`](deploy/WOODPECKER.md).
|
[`deploy/WOODPECKER.md`](deploy/WOODPECKER.md).
|
||||||
Baseline: **51 claim, 346 тестов, 18/18 browser E2E**.
|
Baseline: **51 claim, 346 тестов, 18/18 browser E2E**.
|
||||||
|
|
||||||
|
|||||||
24
ci/Dockerfile
Normal file
24
ci/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Предварительно прогретый CI-образ для Woodpecker.
|
||||||
|
# Сборка на CI VM (доверенный администратором коммит):
|
||||||
|
# ./scripts/ci-build-image.sh lms-ci:1
|
||||||
|
# Образ содержит только зависимости; исходники в пайплайне берутся из checkout.
|
||||||
|
FROM docker:27-dind
|
||||||
|
|
||||||
|
RUN apk add --no-cache openjdk21-jdk python3 bash
|
||||||
|
|
||||||
|
WORKDIR /opt/lms-warmup
|
||||||
|
|
||||||
|
COPY gradle gradle
|
||||||
|
COPY gradlew settings.gradle.kts build.gradle.kts gradle.properties ./
|
||||||
|
COPY buildSrc buildSrc
|
||||||
|
COPY config config
|
||||||
|
COPY libs libs
|
||||||
|
COPY services services
|
||||||
|
COPY web web
|
||||||
|
|
||||||
|
# Прогреваем Gradle-кэш и плагины. Это ускоряет check/smoke/release,
|
||||||
|
# при этом кэш остаётся внутри неизменяемого образа, а не в общем volume.
|
||||||
|
RUN ./gradlew --no-daemon testClasses --console=plain
|
||||||
|
|
||||||
|
# Исходники нужны были только для прогрева кэша; в образе их не оставляем.
|
||||||
|
RUN rm -rf /opt/lms-warmup
|
||||||
@@ -150,8 +150,9 @@ workflow `check` (`./gradlew --no-daemon test ktlintCheck detekt evidenceCheck`)
|
|||||||
курс). Полный workflow `release` (`./scripts/ldd-release.sh`) обязателен для
|
курс). Полный workflow `release` (`./scripts/ldd-release.sh`) обязателен для
|
||||||
тегов и запускается вручную для веток. Smoke/release начинаются с
|
тегов и запускается вручную для веток. Smoke/release начинаются с
|
||||||
`scripts/ci-preflight.sh`, который проверяет Java 21, Docker Compose v2,
|
`scripts/ci-preflight.sh`, который проверяет Java 21, Docker Compose v2,
|
||||||
Python 3, Node и Playwright. Агент — local backend на хосте с Docker:
|
Python 3, Node и Playwright. Агент — Docker backend на отдельной CI VM,
|
||||||
установка и systemd-юнит — [`WOODPECKER.md`](WOODPECKER.md).
|
шаги — в nested dind без хост-Docker сокета; установка и модель изоляции —
|
||||||
|
[`WOODPECKER.md`](WOODPECKER.md).
|
||||||
|
|
||||||
## 8. Резервное копирование
|
## 8. Резервное копирование
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,70 @@
|
|||||||
# CI на Gitea + Woodpecker
|
# CI на Gitea + Woodpecker: изолированный контур
|
||||||
|
|
||||||
Репозиторий использует **Woodpecker CI 3.x**. Конфигурация лежит в
|
Репозиторий использует **Woodpecker CI 3.x**. Конфигурация — в `.woodpecker/`:
|
||||||
`.woodpecker/`:
|
|
||||||
|
|
||||||
| Workflow | Событие | Что делает |
|
| Workflow | Событие | Что делает |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `check` | push, pull_request | `ci-preflight.sh`, `test ktlintCheck detekt evidenceCheck` |
|
| `check` | push, pull_request | тесты, ktlint, detekt, `evidenceCheck` |
|
||||||
| `smoke` | push, pull_request | после `check`: полный стек, health, вход админа, один курс |
|
| `smoke` | push, pull_request | после `check`: полный стек, health, вход админа, один курс |
|
||||||
| `release` | tag, manual | `npm ci`, preflight, полный `ldd-release.sh` (миграции, mutation, load, Chromium/Firefox/WebKit) |
|
| `release` | tag, manual | миграции, mutation, load, Chromium/Firefox/WebKit E2E, финальный evidence |
|
||||||
|
|
||||||
Все три workflow используют `concurrency: {limit: 1, group: lms-ci}`: smoke и
|
## 1. Модель изоляции
|
||||||
release поднимают реальный стек на портах 8080–8090, параллельный запуск занял
|
|
||||||
бы порты.
|
|
||||||
|
|
||||||
## Почему local backend
|
CI **не должен работать на хосте LMS и не должен иметь доступ к production**.
|
||||||
|
Разработчики не считаются доверенными: пайплайн может содержать произвольный
|
||||||
|
код, включая `privileged`-контейнеры.
|
||||||
|
|
||||||
`check` может работать в Docker-бэкенде, но `smoke`/`release` запускают
|
Схема:
|
||||||
`docker compose` и Java-процессы на `localhost`, поэтому им нужен доступ к
|
|
||||||
Docker и портам хоста. Проще всего один агент с **local backend** на том же
|
|
||||||
сервере, где стоит Docker.
|
|
||||||
|
|
||||||
> **Безопасность.** Local backend выполняет пайплайн прямо на хосте без
|
```
|
||||||
> изоляции. Он допустим только для **приватного** репозитория с доверенными
|
Gitea + Woodpecker server отдельная CI VM
|
||||||
> авторами. Не включайте публичные форки/PR от недоверенных пользователей на
|
┌───────────────────────┐ ┌──────────────────────────────┐
|
||||||
> этом агенте: код пайплайна сможет прочитать окружение агента. Если появится
|
│ Gitea (production) │ webhook │ Woodpecker agent (Docker) │
|
||||||
> публичный контур — вынесите `check` на отдельный Docker-backend агент, а
|
│ Woodpecker server │◄────────►│ └─ pipeline step │
|
||||||
> smoke/release оставьте на изолированной машине.
|
│ │ gRPC │ └─ nested dockerd (dind)│
|
||||||
|
└───────────────────────┘ │ ├─ postgres │
|
||||||
## 1. Требования к серверу агента
|
│ ├─ LMS-сервисы │
|
||||||
|
│ └─ Playwright │
|
||||||
- Java 21 в `PATH` (не Java 25 — см. README).
|
└──────────────────────────────┘
|
||||||
- Docker Engine + Compose v2, пользователь агента в группе `docker`.
|
нет prod-сети и секретов
|
||||||
- Python 3, bash, git, curl, `ss` (`iproute2`), Node.js 22 + npm.
|
|
||||||
- Системные библиотеки для Chromium/Firefox/WebKit. На Fedora проще один раз
|
|
||||||
выполнить `sudo npx playwright install-deps`; WebKit при их отсутствии
|
|
||||||
автоматически запускается в Docker-образе Playwright.
|
|
||||||
- Свободные порты 8080–8090 на время smoke/release.
|
|
||||||
- Достаточно места: Gradle-кэш, Docker-образы, Playwright-браузеры.
|
|
||||||
|
|
||||||
Проверка окружения:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ci-preflight.sh # для smoke/check
|
|
||||||
./scripts/ci-preflight.sh --release
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Woodpecker server
|
Правила:
|
||||||
|
|
||||||
Удобно запустить server в Docker рядом с Gitea. Пример
|
- **Отдельная VM** (или отдельный физический хост) для агента. Не запускайте
|
||||||
`docker-compose.woodpecker.yml` (не коммитьте секреты):
|
агент на сервере 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
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -84,81 +100,105 @@ volumes:
|
|||||||
[webhook]
|
[webhook]
|
||||||
ALLOWED_HOST_LIST = external,loopback
|
ALLOWED_HOST_LIST = external,loopback
|
||||||
```
|
```
|
||||||
Также проверьте, что Gitea отдаёт API-страницы не меньше 50 элементов
|
Проверьте, что Gitea отдаёт API-страницы не меньше 50 элементов.
|
||||||
(значение по умолчанию подходит).
|
|
||||||
4. Поднимите server и зайдите в UI через Gitea-аккаунт.
|
4. Поднимите server и зайдите в UI через Gitea-аккаунт.
|
||||||
5. Включите репозиторий LMS в Woodpecker (Settings → Repositories).
|
5. Включите репозиторий LMS в Woodpecker (Settings → Repositories).
|
||||||
|
6. В настройках репозитория включите **Trusted** (нужно для `privileged` и
|
||||||
|
volumes). Доверять можно только этому приватному репозиторию.
|
||||||
|
|
||||||
## 3. Агент на хосте
|
## 4. Агент на отдельной VM
|
||||||
|
|
||||||
Server в контейнере, агент — **на хосте**, иначе local backend не увидит
|
Агент — в Docker, backend `docker`, с доступом к Docker daemon **этой VM**.
|
||||||
Java/Docker/Node хоста.
|
Шаги пайплайна получают не хостовый сокет, а собственный nested dockerd.
|
||||||
|
|
||||||
```bash
|
`docker-compose.agent.yml` на CI VM:
|
||||||
sudo useradd --system --create-home --shell /bin/bash woodpecker
|
|
||||||
sudo usermod -aG docker woodpecker
|
|
||||||
sudo install -m 0755 woodpecker-agent /usr/local/bin/woodpecker-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
`woodpecker-agent` скачивается из релизов Woodpecker (Linux amd64):
|
```yaml
|
||||||
`https://github.com/woodpecker-ci/woodpecker/releases`.
|
services:
|
||||||
|
woodpecker-agent:
|
||||||
`/etc/woodpecker-agent.env` (права `0600`, владелец `root`):
|
image: woodpeckerci/woodpecker-agent:v3
|
||||||
|
restart: unless-stopped
|
||||||
```dotenv
|
environment:
|
||||||
WOODPECKER_SERVER=localhost:9000
|
WOODPECKER_SERVER: ${WOODPECKER_SERVER:?https://ci.example.ru:9000}
|
||||||
WOODPECKER_AGENT_SECRET=тот-же-секрет-что-у-server
|
WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:?}
|
||||||
WOODPECKER_BACKEND=local
|
WOODPECKER_BACKEND: docker
|
||||||
WOODPECKER_MAX_WORKFLOWS=1
|
WOODPECKER_MAX_WORKFLOWS: "1"
|
||||||
# Кэш Gradle и браузеров Playwright переживает запуски. Допустимо только для
|
# Только этот агент должен брать workflows с этой меткой.
|
||||||
# приватного репозитория с доверенными авторами (см. предупреждение выше).
|
WOODPECKER_AGENT_LABELS: isolation=dedicated-vm
|
||||||
WOODPECKER_BACKEND_LOCAL_ISOLATED_HOME=false
|
volumes:
|
||||||
```
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
`/etc/systemd/system/woodpecker-agent.service`:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[Unit]
|
|
||||||
Description=Woodpecker CI agent (LMS)
|
|
||||||
After=network-online.target docker.service
|
|
||||||
Wants=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=woodpecker
|
|
||||||
EnvironmentFile=/etc/woodpecker-agent.env
|
|
||||||
ExecStart=/usr/local/bin/woodpecker-agent
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl daemon-reload
|
docker compose -f docker-compose.agent.yml up -d
|
||||||
sudo systemctl enable --now woodpecker-agent
|
docker compose -f docker-compose.agent.yml logs -f
|
||||||
journalctl -u woodpecker-agent -f
|
|
||||||
```
|
```
|
||||||
|
|
||||||
В UI агент появится через несколько секунд. Репозиторий должен быть помечен
|
Дополнительно в UI откройте **Settings → Agents** и задайте агенту фильтр
|
||||||
как trusted, если вы захотите использовать volumes/privileged (текущему
|
`isolation=dedicated-vm` — это серверная проверка, которую агент не может
|
||||||
конфигу они не нужны).
|
подделать.
|
||||||
|
|
||||||
## 4. Первый прогон
|
**Общего кэша между пайплайнами нет.** Каждый шаг получает чистый контейнер,
|
||||||
|
а вложенный Docker storage живёт только внутри него. Это важно: разработчик не
|
||||||
|
может оставить артефакт, который повлияет на следующий pipeline или релиз.
|
||||||
|
|
||||||
1. Push в ветку или MR → pipeline `check` → `smoke`.
|
Пайплайны используют предварительно прогретый образ `lms-ci:1`. Соберите его
|
||||||
2. Убедиться, что зелёные оба workflow.
|
на VM из **проверенного администратором** коммита до первого запуска:
|
||||||
3. Проверить ручной запуск: UI → Run pipeline → event `manual` → workflow
|
|
||||||
|
```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`.
|
`release`.
|
||||||
4. Для релиза: `git tag vX.Y.Z && git push origin vX.Y.Z` → workflow
|
4. Для релиза: `git tag vX.Y.Z && git push origin vX.Y.Z` → `release`.
|
||||||
`release`; полный отчёт печатается в лог, файлы остаются в
|
5. После релиза/по расписанию пересоздайте VM из чистого снапшота.
|
||||||
`build/evidence/` рабочей копии агента.
|
|
||||||
|
|
||||||
Если pipeline не появляется:
|
Если pipeline не появляется:
|
||||||
|
|
||||||
- проверьте, что репозиторий включён в Woodpecker;
|
- репозиторий включён в Woodpecker;
|
||||||
- проверьте `WOODPECKER_SERVER` и общий `WOODPECKER_AGENT_SECRET`;
|
- у агента и server совпадает `WOODPECKER_AGENT_SECRET`;
|
||||||
- проверьте, что агент видит Docker (`docker compose version` от пользователя
|
- агент видит Docker: `docker compose version` на VM;
|
||||||
`woodpecker`) и Java 21;
|
- метка агента `isolation=dedicated-vm` и серверный фильтр совпадают;
|
||||||
- проверьте, что путь конфигурации в настройках проекта — `.woodpecker/`
|
- путь конфигурации проекта — `.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.
|
||||||
|
|||||||
@@ -190,8 +190,9 @@ legacy-активов на production, сдачи по выбранной лаб
|
|||||||
push/MR выполняет workflow `check` (`test ktlintCheck detekt evidenceCheck`)
|
push/MR выполняет workflow `check` (`test ktlintCheck detekt evidenceCheck`)
|
||||||
и `smoke` (`scripts/ldd-smoke.sh`: полный стек, health, вход админа, один
|
и `smoke` (`scripts/ldd-smoke.sh`: полный стек, health, вход админа, один
|
||||||
курс). Полный `release` обязателен для тегов и запускается вручную для
|
курс). Полный `release` обязателен для тегов и запускается вручную для
|
||||||
веток. Агент — local backend с Java 21, Docker Compose v2, Python 3, Node и
|
веток. Агент — Docker backend на отдельной VM, шаги в nested dind; Java 21,
|
||||||
Chromium/Firefox/WebKit; установка — [`deploy/WOODPECKER.md`](../deploy/WOODPECKER.md).
|
Docker Compose v2, Python 3, Node и Chromium/Firefox/WebKit; установка —
|
||||||
|
[`deploy/WOODPECKER.md`](../deploy/WOODPECKER.md).
|
||||||
- [ ] Заданы сроки сессии: `LMS_ACCESS_TOKEN_TTL=15m`,
|
- [ ] Заданы сроки сессии: `LMS_ACCESS_TOKEN_TTL=15m`,
|
||||||
`LMS_REFRESH_TOKEN_TTL=7d`, `LMS_SESSION_TIMEOUT=7d`,
|
`LMS_REFRESH_TOKEN_TTL=7d`, `LMS_SESSION_TIMEOUT=7d`,
|
||||||
`LMS_SESSION_ABSOLUTE_TIMEOUT=30d`, `LMS_REFRESH_TOKEN_RETENTION=30d`
|
`LMS_SESSION_ABSOLUTE_TIMEOUT=30d`, `LMS_REFRESH_TOKEN_RETENTION=30d`
|
||||||
@@ -323,10 +324,10 @@ legacy-активов на production, сдачи по выбранной лаб
|
|||||||
1. **Прогнать инвентаризацию legacy-активов на production/staging** через
|
1. **Прогнать инвентаризацию legacy-активов на production/staging** через
|
||||||
`/admin/legacy-assets` или `scripts/legacy-assets.sh`, привязать нужные
|
`/admin/legacy-assets` или `scripts/legacy-assets.sh`, привязать нужные
|
||||||
файлы к статьям, остальные удалить.
|
файлы к статьям, остальные удалить.
|
||||||
2. **Развернуть Woodpecker и проверить CI.** Поднять server и local-backend
|
2. **Развернуть Woodpecker и проверить CI.** Поднять server и Docker-backend
|
||||||
агент по [`deploy/WOODPECKER.md`](../deploy/WOODPECKER.md), включить
|
агент на отдельной VM по [`deploy/WOODPECKER.md`](../deploy/WOODPECKER.md),
|
||||||
репозиторий, дождаться зелёных `check`/`smoke` на push-MR и `release` на
|
включить репозиторий, дождаться зелёных `check`/`smoke` на push-MR и
|
||||||
теге.
|
`release` на теге.
|
||||||
3. **Дальнейшее расширение E2E** — визуальная регрессия и остальные мобильные
|
3. **Дальнейшее расширение E2E** — визуальная регрессия и остальные мобильные
|
||||||
страницы, если UI продолжит расти.
|
страницы, если UI продолжит расти.
|
||||||
4. **Пакетные сдачи ведомости**, если появятся курсы, где открытие ведомости
|
4. **Пакетные сдачи ведомости**, если появятся курсы, где открытие ведомости
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# 17. Handoff: состояние проекта после закрытия продуктовых рисков
|
# 17. Handoff: состояние проекта после закрытия продуктовых рисков
|
||||||
|
|
||||||
**Дата:** 2026-10-28.
|
**Дата:** 2026-10-28.
|
||||||
**Ветка:** `feature/groups-courses` (последняя история — `git log --oneline`).
|
**Ветка:** `feature/groups-courses`.
|
||||||
**HEAD до переделки CI:** `d98c3b0` (`feat: закрыть остаточный backlog`);
|
**История:** `git log --oneline`; последние изменения — Woodpecker CI и
|
||||||
история CI-правки — следующий коммит в `git log`.
|
изоляция тестовой среды на отдельной VM.
|
||||||
**Пуш:** не выполнялся.
|
**Пуш:** не выполнялся.
|
||||||
**Рабочее дерево:** чистое. `exports/` и `texput.log` игнорируются и в коммиты не входят.
|
**Рабочее дерево:** чистое. `exports/` и `texput.log` игнорируются и в коммиты не входят.
|
||||||
|
|
||||||
@@ -129,6 +129,8 @@
|
|||||||
вход админа, один курс;
|
вход админа, один курс;
|
||||||
- `release` на тег или вручную: тесты, миграции, mutation, runtime/load,
|
- `release` на тег или вручную: тесты, миграции, mutation, runtime/load,
|
||||||
browser E2E, финальный evidence.
|
browser E2E, финальный evidence.
|
||||||
|
- workflows требуют метку `isolation=dedicated-vm`; шаги идут в nested dind
|
||||||
|
без хост-Docker сокета, агент — на отдельной VM без production-доступа.
|
||||||
- `scripts/ci-preflight.sh` проверяет Java 21, Docker Compose v2, Python 3,
|
- `scripts/ci-preflight.sh` проверяет Java 21, Docker Compose v2, Python 3,
|
||||||
Node и Playwright. Установка server/agent — `deploy/WOODPECKER.md`.
|
Node и Playwright. Установка server/agent — `deploy/WOODPECKER.md`.
|
||||||
- Первый реальный Woodpecker pipeline ещё не подтверждён.
|
- Первый реальный Woodpecker pipeline ещё не подтверждён.
|
||||||
@@ -211,11 +213,10 @@ cat build/reports/evidence/index.md
|
|||||||
у обычных пользователей.
|
у обычных пользователей.
|
||||||
|
|
||||||
2. **Первый реальный CI-прогон.**
|
2. **Первый реальный CI-прогон.**
|
||||||
Развернуть Woodpecker server и local-backend агент по
|
Развернуть Woodpecker server и Docker-backend агент на отдельной VM по
|
||||||
`deploy/WOODPECKER.md`, включить репозиторий в Woodpecker и дождаться
|
`deploy/WOODPECKER.md`, включить репозиторий в Woodpecker и дождаться
|
||||||
зелёных workflow `check`/`smoke` на push-MR и `release` на теге. Агенту
|
зелёных workflow `check`/`smoke` на push-MR и `release` на теге. Шаги идут
|
||||||
нужны Java 21, Docker Compose v2, Python 3, Node и браузеры
|
в nested dind; VM не имеет доступа к production и production-секретам.
|
||||||
Chromium/Firefox/WebKit.
|
|
||||||
|
|
||||||
3. **Визуальная регрессия и мобильный набор E2E.**
|
3. **Визуальная регрессия и мобильный набор E2E.**
|
||||||
Сейчас 6 сценариев × 3 браузера, без screenshot-diff. Добавлять по мере
|
Сейчас 6 сценариев × 3 браузера, без screenshot-diff. Добавлять по мере
|
||||||
@@ -299,7 +300,9 @@ cat build/reports/evidence/index.md
|
|||||||
`SessionCookieRefreshFilter.kt`.
|
`SessionCookieRefreshFilter.kt`.
|
||||||
- `web/.../admin/AdminAuditController.kt`, `LegacyAssetsController.kt`.
|
- `web/.../admin/AdminAuditController.kt`, `LegacyAssetsController.kt`.
|
||||||
- `services/gateway/.../JwtAuthenticationFilter.kt`.
|
- `services/gateway/.../JwtAuthenticationFilter.kt`.
|
||||||
- `scripts/ldd-*.sh`, `scripts/ci-preflight.sh`, `scripts/legacy-assets.sh`.
|
- `scripts/ldd-*.sh`, `scripts/ci-preflight.sh`, `scripts/ci-dind.sh`,
|
||||||
|
`scripts/ci-build-image.sh`, `scripts/legacy-assets.sh`.
|
||||||
- `.woodpecker/{check,smoke,release}.yaml` — CI на Gitea + Woodpecker.
|
- `.woodpecker/{check,smoke,release}.yaml` — CI на Gitea + Woodpecker.
|
||||||
|
- `ci/Dockerfile` — прогретый образ `lms-ci:1` для изолированного dind.
|
||||||
- `deploy/backup.sh`, `deploy/deploy.sh`, `deploy/DEPLOY.md`,
|
- `deploy/backup.sh`, `deploy/deploy.sh`, `deploy/DEPLOY.md`,
|
||||||
`deploy/WOODPECKER.md`.
|
`deploy/WOODPECKER.md`.
|
||||||
|
|||||||
@@ -240,6 +240,8 @@ mutation, runtime + scale и browser E2E, затем финальный `evidenc
|
|||||||
на каждом push/MR, после `check`;
|
на каждом push/MR, после `check`;
|
||||||
- `release` — полный `ldd-release.sh` с Chromium/Firefox/WebKit и Node:
|
- `release` — полный `ldd-release.sh` с Chromium/Firefox/WebKit и Node:
|
||||||
обязателен для тегов, для веток запускается вручную.
|
обязателен для тегов, для веток запускается вручную.
|
||||||
|
- workflows требуют метку `isolation=dedicated-vm`; агент работает на
|
||||||
|
отдельной VM, шаги — в `lms-ci:1` с nested dockerd и без хост-Docker сокета.
|
||||||
- smoke/release начинаются с `scripts/ci-preflight.sh`, который проверяет
|
- smoke/release начинаются с `scripts/ci-preflight.sh`, который проверяет
|
||||||
Java 21, Docker Compose v2, Python 3, Node и Playwright.
|
Java 21, Docker Compose v2, Python 3, Node и Playwright.
|
||||||
- Первый реальный прогон на Woodpecker-агенте ещё не подтверждён; локальные
|
- Первый реальный прогон на Woodpecker-агенте ещё не подтверждён; локальные
|
||||||
@@ -293,8 +295,8 @@ optional json fields`.
|
|||||||
6. **Инвентаризация legacy-активов на production не выполнена.** UI, API и
|
6. **Инвентаризация legacy-активов на production не выполнена.** UI, API и
|
||||||
скрипт готовы, но реальные файлы нужно привязать или удалить вручную.
|
скрипт готовы, но реальные файлы нужно привязать или удалить вручную.
|
||||||
7. **Первый CI-прогон не подтверждён.** Конфигурация `.woodpecker/` и
|
7. **Первый CI-прогон не подтверждён.** Конфигурация `.woodpecker/` и
|
||||||
preflight готовы, но Woodpecker-сервер и local-backend агент ещё нужно
|
preflight готовы, но Woodpecker-сервер и Docker-backend агент на отдельной
|
||||||
развернуть и проверить на первом push-MR и теге.
|
VM ещё нужно развернуть и проверить на первом push-MR и теге.
|
||||||
8. **Полный release не на каждом коммите.** На push/MR идёт smoke,
|
8. **Полный release не на каждом коммите.** На push/MR идёт smoke,
|
||||||
а mutation/browser/scale — для тегов или вручную.
|
а mutation/browser/scale — для тегов или вручную.
|
||||||
9. **Интеграция «Контингент» исключена из плана** — без нового ТЗ и доступа к
|
9. **Интеграция «Контингент» исключена из плана** — без нового ТЗ и доступа к
|
||||||
|
|||||||
9
scripts/ci-build-image.sh
Executable file
9
scripts/ci-build-image.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Собирает предварительно прогретый CI-образ на CI VM.
|
||||||
|
# Образ immutable: исходники пайплайна берутся из checkout, кэш Gradle — из образа.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
tag="${1:-lms-ci:1}"
|
||||||
|
docker build -f ci/Dockerfile -t "$tag" .
|
||||||
|
echo "Готово: $tag"
|
||||||
|
echo "Образ используется в .woodpecker/*.yaml как image: $tag"
|
||||||
47
scripts/ci-dind.sh
Executable file
47
scripts/ci-dind.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Подготовка изолированного Docker-in-Docker шага Woodpecker.
|
||||||
|
# Запускается внутри privileged-контейнера docker:*-dind: поднимает локальный
|
||||||
|
# dockerd и доустанавливает Java/Python/bash, если их нет в образе.
|
||||||
|
# Хост-Docker сокет не используется.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if ! command -v dockerd >/dev/null 2>&1; then
|
||||||
|
echo "ci-dind.sh: нужен образ docker:<version>-dind" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}"
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo ">> Запускаю изолированный dockerd..."
|
||||||
|
dockerd-entrypoint.sh >/tmp/ci-dockerd.log 2>&1 &
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 90 ]; do
|
||||||
|
docker info >/dev/null 2>&1 && break
|
||||||
|
sleep 1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo "dockerd не поднялся:" >&2
|
||||||
|
tail -50 /tmp/ci-dockerd.log >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
missing=""
|
||||||
|
command -v bash >/dev/null 2>&1 || missing="$missing bash"
|
||||||
|
command -v java >/dev/null 2>&1 || missing="$missing openjdk21-jdk"
|
||||||
|
command -v python3 >/dev/null 2>&1 || missing="$missing python3"
|
||||||
|
command -v node >/dev/null 2>&1 || missing="$missing nodejs"
|
||||||
|
command -v npm >/dev/null 2>&1 || missing="$missing npm"
|
||||||
|
command -v git >/dev/null 2>&1 || missing="$missing git"
|
||||||
|
command -v curl >/dev/null 2>&1 || missing="$missing curl"
|
||||||
|
command -v ss >/dev/null 2>&1 || missing="$missing iproute2"
|
||||||
|
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo ">> Устанавливаю в изолированный шаг:$missing"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
apk add --no-cache $missing
|
||||||
|
fi
|
||||||
|
|
||||||
|
java -version 2>&1 | head -n 1
|
||||||
|
docker info --format 'Docker server {{.ServerVersion}} (изолированный dind)'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Проверка окружения Woodpecker-агента (local backend) перед check/smoke/release.
|
# Проверка окружения изолированного dind-шага Woodpecker перед smoke/release.
|
||||||
# Ничего не устанавливает.
|
# Ничего не устанавливает.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ mkdir -p build/evidence
|
|||||||
ensure_playwright_browsers
|
ensure_playwright_browsers
|
||||||
set +e
|
set +e
|
||||||
if host_playwright_usable; then
|
if host_playwright_usable; then
|
||||||
log "Запускаю Playwright на хосте (Chromium/Firefox/WebKit)..."
|
log "Запускаю Playwright в текущем окружении (Chromium/Firefox/WebKit)..."
|
||||||
(
|
(
|
||||||
cd e2e
|
cd e2e
|
||||||
LDD_WEB_URL="http://localhost:8090" \
|
LDD_WEB_URL="http://localhost:8090" \
|
||||||
@@ -123,10 +123,10 @@ if host_playwright_usable; then
|
|||||||
else
|
else
|
||||||
version=$(playwright_version)
|
version=$(playwright_version)
|
||||||
if [[ -z "$version" ]]; then
|
if [[ -z "$version" ]]; then
|
||||||
echo "WebKit не запускается на хосте, а версию Playwright определить не удалось." >&2
|
echo "WebKit не запускается в текущем окружении, а версию Playwright определить не удалось." >&2
|
||||||
E2E_STATUS=1
|
E2E_STATUS=1
|
||||||
else
|
else
|
||||||
log "WebKit не запускается на хосте; запускаю Chromium/Firefox/WebKit в Docker-образе Playwright ${version}."
|
log "WebKit не запускается в текущем окружении; запускаю Chromium/Firefox/WebKit в Docker-образе Playwright ${version}."
|
||||||
docker run --rm --network host \
|
docker run --rm --network host \
|
||||||
-v "$PWD:/work" \
|
-v "$PWD:/work" \
|
||||||
-w /work/e2e \
|
-w /work/e2e \
|
||||||
|
|||||||
Reference in New Issue
Block a user