docs(ci): runbook пайплайна и отчёт проверки runner'а (#17)
ci / lint (push) Successful in 42s
contract-openapi / openapi-contract (push) Failing after 48s
contract-openapi / ts-client-is-current (push) Failing after 50s
ci / lint (pull_request) Successful in 42s
contract-openapi / openapi-contract (pull_request) Failing after 40s
contract-openapi / ts-client-is-current (pull_request) Failing after 28s
ci / unit (push) Successful in 49s
ci / security (push) Successful in 3m10s
ci / unit (pull_request) Successful in 48s
ci / security (pull_request) Successful in 2m22s
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
ci / lint (push) Successful in 42s
contract-openapi / openapi-contract (push) Failing after 48s
contract-openapi / ts-client-is-current (push) Failing after 50s
ci / lint (pull_request) Successful in 42s
contract-openapi / openapi-contract (pull_request) Failing after 40s
contract-openapi / ts-client-is-current (pull_request) Failing after 28s
ci / unit (push) Successful in 49s
ci / security (push) Successful in 3m10s
ci / unit (pull_request) Successful in 48s
ci / security (pull_request) Successful in 2m22s
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
Refs #17, Refs #15
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
||||
# 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-контейнер остановлен.
|
||||
|
||||
## 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
|
||||
```
|
||||
Reference in New Issue
Block a user