Refs #17, Refs #15
9.8 KiB
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>в registry192.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-контейнер остановлен.
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