Files
embeddevio 80b33d5132
ci / lint (push) Successful in 47s
ci / lint (pull_request) Successful in 1m29s
contract-openapi / openapi-contract (push) Failing after 35s
contract-openapi / ts-client-is-current (push) Failing after 35s
contract-openapi / openapi-contract (pull_request) Failing after 33s
contract-openapi / ts-client-is-current (pull_request) Failing after 36s
ci / unit (push) Successful in 57s
ci / security (push) Successful in 7m12s
ci / unit (pull_request) Successful in 46s
ci / security (pull_request) Successful in 4m25s
ci / build (push) Has been skipped
ci / image-scan (push) Has been skipped
ci / build (pull_request) Has been skipped
ci / push (push) Has been skipped
ci / image-scan (pull_request) Has been skipped
ci / push (pull_request) Has been skipped
docs(ci): дефекты 6.4 (артефакты, #62) и 6.5 (contract-openapi checkout, #59)
Refs #17, Refs #59, Refs #62
2026-10-28 23:40:43 +03:00

200 lines
11 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/CD: пайплайн с гейтами (`ci.yml`)
**Владелец:** devops · **Issue:** #17 (Refs #15) · **Файл:** `.gitea/workflows/ci.yml`
**Runner:** `awg-dash-147-repo` (repo-scoped) на Docker-хосте `192.168.1.147`
---
## 1. Триггеры
| Событие | Ветки | Что запускается |
|---|---|---|
| `push` | `feat/**`, `fix/**`, `chore/**`, `ci/**`, `main` | полный пайплайн |
| `pull_request` (sync) | базис `main` | полный пайплайн |
| `workflow_dispatch` | любая | полный пайплайн (ручной перезапуск) |
---
## 2. Стадии и гейты
```
lint ──┬── unit ────────┐
│ ├── build ── image-scan ── push (только push-событие)
└── security ────┘
```
| Job | Что делает | Гейт (роняет pipeline) | Артефакты |
|---|---|---|---|
| `lint` | `ruff check backend`, `ruff format --check backend`, `mypy backend` (strict) | любая ошибка линтера/типов | — |
| `unit` | `pytest --cov=awg_agent --cov=awg_api --cov-fail-under=70` | **покрытие < 70 %** | `coverage.xml`, `htmlcov/` |
| `security` | `bandit -lll -r backend`, `pip-audit -r requirements.txt` | **bandit severity HIGH**, любая известная уязвимость в зависимостях | `bandit-report.txt/.json`, `pip-audit-report.json` |
| `build` | `docker build -f deploy/Dockerfile -t <registry>/<repo>:staging-<sha8>` | ошибка сборки | `build.log`, `image-id.txt` |
| `image-scan` | `docker run aquasec/trivy:<ver> image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1` (по сокету) | **HIGH/CRITICAL в образе** | `trivy-report.txt`, `trivy-report.json` |
| `push` | `docker login` + `docker push ...:staging-<sha8>` | ошибка авторизации/пуша | `push.log`, `pushed-image.txt` |
**trivy** запускается официальным образом через docker-сокет (бинарь с github releases
в homelab качается крайне медленно), БД кэшируется в `/opt/trivy-cache` на build-хосте.
**Токен для push**: приоритет — секрет `REGISTRY_TOKEN` (PAT с правом `write:package`),
fallback — встроенный `secrets.GITHUB_TOKEN`. Проверено: PAT без `write:package` получает
`401 Unauthorized` на `POST /v2/<repo>/blobs/uploads/` — если push падает с 401, заведите секрет.
**Порядок гарантий:**
- `build` стартует только после зелёных `lint` + `unit` + `security`.
- `image-scan` — только после `build`; `push` — только после зелёного `image-scan`.
- Тег образа: `staging-<первые 8 символов GITHUB_SHA>` в registry `192.168.1.111:3000/embeddevio/awg-dash`.
## 3. Условная активация build/image-scan/push
`build`/`image-scan`/`push` выполняются, только если в репозитории есть `deploy/Dockerfile`
(вычисляется в `lint` → output `has_dockerfile`). До merge #18 (PR #49) они **скипаются**
с предупреждением в логе — это ожидаемое поведение, а не сбой.
## 4. Runner
| Параметр | Значение |
|---|---|
| Контейнер | `gitea-runner-awg-repo` (`docker.io/gitea/act_runner:latest`, v0.6.1) на `192.168.1.147` |
| Область | **repo-scoped** (зарегистрирован repo-runner-token'ом для `embeddevio/awg-dash`) |
| Label | `ubuntu-latest` → образ `catthehacker/ubuntu:act-22.04` |
| Параллелизм | `capacity: 1`, `maxParallel=1` |
| Сеть job-контейнеров | `container.network: "host"` (см. п. 7 — иначе исчерпывается пул адресов Docker) |
| Docker socket | проброшен (`/var/run/docker.sock`), `container.options: ""` — v0.6.1 монтирует сокет сам, дубль в `options` даёт `Duplicate mount point` |
| Кэш инструментов | volume `act-toolcache` → `/opt/hostedtoolcache` (Python 3.12 скачивается один раз) |
| Конфиг | `/opt/gitea-runner-awg-repo/config.yaml`, состояние — `/opt/gitea-runner-awg-repo/.runner` |
### Диагностика runner'а
```bash
ssh root@192.168.1.147
docker ps --filter name=gitea-runner # жив ли
docker logs --tail 50 gitea-runner-awg-repo # задачи, ошибки
docker logs gitea-runner-awg-repo 2>&1 | grep -E "task|Error|failed"
```
Лог job'а с его шагами и результатом доступен через API Gitea:
```bash
curl -H "Authorization: token $GITEA_TOKEN" \
http://192.168.1.111:3000/api/v1/repos/embeddevio/awg-dash/actions/jobs/<job_id>/logs
```
Перезапуск конкретного run'а без нового коммита:
```bash
curl -X POST -H "Authorization: token $GITEA_TOKEN" \
http://192.168.1.111:3000/api/v1/repos/embeddevio/awg-dash/actions/runs/<run_id>/rerun
```
## 5. Registry (push staging-<sha>)
Gitea-registry `192.168.1.111:3000` работает по **HTTP**, поэтому Docker-демон на
build-хосте должен знать его как insecure:
```json
// /etc/docker/daemon.json
{ "log-driver": "journald", "insecure-registries": ["192.168.1.111:3000"] }
```
Применяется **без перезапуска контейнеров**:
```bash
systemctl reload docker # SIGHUP, контейнеры продолжают работать
docker info | grep -A3 "Insecure Registries"
```
Авторизация в job'е — `${{ secrets.GITHUB_TOKEN }}` (выдаётся Gitea Actions автоматически), пользователь `${{ github.actor }}`.
## 6. Известные дефекты инфраструктуры
### 6.1 Gitea git-http отдаёт пустой pack → `actions/checkout` не работает
**Симптом:** `git clone`/`git fetch` с любого хоста падает:
```
fatal: early EOF
fatal: fetch-pack: invalid index-pack output
```
**Причина (измерено):** ответ `git-upload-pack` содержит `Content-Length: 90` — только
`shallow-info` + заголовок `packfile`, без самих объектов. То есть обрыв на стороне
Gitea (git-http), а не у клиента. `ls-remote` и эндпоинт `archive/<sha>.tar.gz` при этом
работают (архив 200 OK, валидный tar).
**Обход в `ci.yml`:** основной путь — `actions/checkout@v4` (`continue-on-error: true`);
если он падает, следующий шаг забирает исходники архивом:
```bash
curl -fsSL -o /tmp/src.tar.gz "http://192.168.1.111:3000/${IMAGE_NAME}/archive/${GITHUB_SHA}.tar.gz"
tar -xzf /tmp/src.tar.gz --strip-components=1 -C .
```
Когда git-http починят, fallback перестанет срабатывать сам (условие `outcome == 'failure'`).
Исправление на стороне Gitea-хоста (`192.168.1.111`) — вне доступа агентов (нет SSH).
### 6.2 «all predefined address pools have been fully subnetted»
Каждый job создаёт Docker-сеть `GITEA-ACTIONS-*`; прерванные job'ы оставляли сети,
и пул `172.17.0.0/16`-диапазонов исчерпался → job'ы падали на создании контейнера.
Лечение (безопасно, только сети без контейнеров):
```bash
for n in $(docker network ls --filter name=GITEA-ACTIONS -q); do
[ "$(docker network inspect "$n" --format '{{len .Containers}}')" = "0" ] && docker network rm "$n"
done
```
Профилактика: `container.network: "host"` в конфиге runner'а (job-сети не создаются).
Кардинальное решение — `default-address-pools` в `daemon.json`, но оно требует
**перезапуска** демона (в отличие от `insecure-registries`), поэтому только по согласованию.
### 6.3 Instance-scoped runner не отчитывается о статусах
Runner, зарегистрированный instance-токеном (`gitea-runner-awg`), забирал задачи, но
Gitea не фиксировал результаты: jobs вечно в `queued`, `runner_id: 0`, логов нет.
Repo-scoped runner (`gitea-runner-awg-repo`, id 2) с тем же образом пишет статусы и логи
корректно. Legacy-контейнер остановлен.
### 6.4 Артефакты Actions не публикуются
`actions/upload-artifact@v3` в логе рапортует `Artifact has been successfully uploaded!`,
но `GET /actions/runs/<id>/artifacts` → `{"artifacts":[],"total_count":0}` и в UI пусто;
`@v4` зависает на старте экшена (>6 мин). Платформенный дефект Gitea 1.26.0 + act_runner v0.6.1 — issue #62.
**Мера в пайплайне:** `unit` печатает coverage-таблицу (`coverage report --sort=cover`), `security` — вывод
`bandit`/`pip-audit`, `image-scan` — trivy-таблицу. Эти строки в логе job'а и есть доступный
артефакт: ссылка на job лежит в commit status. Скачать `coverage.xml`/`htmlcov`/`trivy-report.json`
из UI нельзя, пока #62 не закрыт.
### 6.5 `contract-openapi.yml` — тот же дефект git-http
Оба job'а (`openapi-contract`, `ts-client-is-current`) падали на шаге `checkout` за 28–50 с
(`bad pack header`, exit 128) и делали combined status любого PR = `failure`, хотя `ci / *` зелёные.
В ветке `fix/59-checkout-fallback` (PR #63) применён тот же archive-fallback; `ts-client-is-current`
после fallback сравнивает `sha256sum` перегенерированного `ui/src/api/generated/schema.d.ts`
вместо `git diff` (в распакованном архиве нет `.git`). Проверено: run #64 — success.
## 7. Локальное воспроизведение гейтов
```bash
python3.12 -m venv .venv && . .venv/bin/activate
pip install -r requirements-dev.txt
ruff check backend && ruff format --check backend && mypy backend # lint
pytest # unit + coverage >= 70%
bandit -lll -r backend && pip-audit -r requirements.txt # security
docker build -f deploy/Dockerfile -t awg-dash:local . # build
trivy image --severity HIGH,CRITICAL --exit-code 1 awg-dash:local # image-scan
```
Проверка, что гейты действительно роняют пайплайн:
```bash
# покрытие: временно сузить --cov до одного модуля или добавить непокрытый код
pytest --cov=awg_agent --cov-fail-under=70 # FAIL: покрытие ниже порога
# security: HIGH-находка (eval / shell=True / weak hash)
bandit -lll -r backend # exit 1 при Severity: High
```