Files
awg-dash/docs/ci/CI.md
T
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

11 KiB
Raw Blame History

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'а

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:

curl -H "Authorization: token $GITEA_TOKEN" \
  http://192.168.1.111:3000/api/v1/repos/embeddevio/awg-dash/actions/jobs/<job_id>/logs

Перезапуск конкретного run'а без нового коммита:

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-)

Gitea-registry 192.168.1.111:3000 работает по HTTP, поэтому Docker-демон на build-хосте должен знать его как insecure:

// /etc/docker/daemon.json
{ "log-driver": "journald", "insecure-registries": ["192.168.1.111:3000"] }

Применяется без перезапуска контейнеров:

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); если он падает, следующий шаг забирает исходники архивом:

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'ы падали на создании контейнера.

Лечение (безопасно, только сети без контейнеров):

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. Локальное воспроизведение гейтов

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

Проверка, что гейты действительно роняют пайплайн:

# покрытие: временно сузить --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