feat: traefik-selectel-dns — A-записи Selectel по роутам Traefik + CI сборка образа
Build / Tests (push) Successful in 13s
Build / Build image (push) Failing after 15s
Build / Notify on failure (push) Failing after 0s

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 21:50:07 +07:00
co-authored by Claude Sonnet 5
commit 273cd54415
35 changed files with 3722 additions and 0 deletions
@@ -0,0 +1,38 @@
# traefik-selectel-dns — отчёт (2026-09-21)
## Что сделано
Go-демон `traefik-selectel-dns` (модуль `github.com/realmanual/traefik-selectel`, Go 1.27.1, единственная зависимость — `gopkg.in/yaml.v3`):
- `internal/hostparse` — разбор `Host(...)` (несколько аргументов, `||`, `&&`, кавычки, отрицания `!Host`/`!(...)`, `HostRegexp`/`HostSNI` не считаются), валидация DNS-имён, сопоставление зон (longest match).
- `internal/traefik` — клиент `GET /api/http/routers` с пагинацией (`page`/`per_page`, `X-Next-Page`).
- `internal/selectel` — Keystone-токен (кэш в памяти, обновление за 10 минут до `expires_at`), DNS API v2 (зоны с кэшем id, rrset list/create/PATCH/delete), повтор при 401 со сбросом токена, 404 на delete = успех.
- `internal/reconciler` — create/update/skip-foreign/skip-CNAME/delete-grace/safe-guard, dry-run, цикл с debounce по событиям Docker.
- `internal/dockerevents` — raw HTTP `/events` по unix-сокету или tcp (без docker SDK), переподключение с backoff, недоступность не фатальна.
- `internal/config` (YAML strict + env `TSD_*` + секреты `*_FILE`), `internal/httpx` (ретраи 429/5xx с backoff и Retry-After), `internal/health` (`/healthz`, флаг `-healthcheck`).
- Dockerfile (multi-stage, distroless static nonroot 65532), `.dockerignore`, `deploy/` (compose, traefik.yml, config, dynamic/, whoami-пример), README.
Проверено: `go vet ./...` — чисто; `go test -race ./...` — 79 тестов проходят; `docker build` собирается, образ запускается (`-version`), USER 65532; `docker compose config -q` валиден.
## Сверка с документацией
- DNS API v2: base URL `https://api.selectel.ru/domains/v2` (docs.selectel.ru/api/urls), `X-Auth-Token` (по исходникам lego selectelv2), пути `/zones`, `/zones/{id}/rrset[/{rrset_id}]`, тело `{name,type,ttl,records:[{content,disabled}],comment}`, PATCH — `ttl`, `records`, `comment`; имена — FQDN с точкой.
- Токен: `POST https://cloud.api.selcloud.ru/identity/v3/auth/tokens`, токен в заголовке `X-Subject-Token`, срок 24 ч.
- Переменные lego `selectelv2`: `SELECTELV2_USERNAME/PASSWORD/ACCOUNT_ID/PROJECT_ID` (+`_FILE`) — сверено через context7 (/go-acme/lego).
## Принятые решения / отступления
- Роутеры со статусом `warning` не создают записи, но считаются «присутствующими» (не запускают grace-удаление) — чтобы временный warning не удалил запись.
- Список rrset читается целиком (все типы) и фильтруется на клиенте: формат query-параметра `rrset_types` в доке не уточнён; так же позволяет обнаружить CNAME-конфликт.
- Зона ищется по `filter` + точное сравнение имени на клиенте (семантика filter не описана).
- Traefik API: вместо `api.insecure: true` — роутер `api@internal` на entrypoint `traefik` с `ipAllowList` подсети control, т.к. Traefik также сидит в сети proxy с пользовательскими контейнерами. Docker events демон получает через `docker-socket-proxy`, а не прямой сокет.
- Добавлены сверх ТЗ: `dryRun`, `-healthcheck`, детекция CNAME-конфликта.
## Риски
- Остаточный: подсеть `172.30.250.0/24` дублируется в compose и dynamic/api-internal.yml — при расхождении демон получит 403.
- Docker socket, смонтированный в Traefik (штатно для Docker provider) — root-эквивалент; в примере ro-монтирование не ограничивает API.
- POST при 5xx может повториться (возможен дубль) — reconcile идемпотентен, но при дубле API вернёт ошибку до следующего цикла.
- Метка владения — exact-match комментария (до 255 символов ок).
- Образы `traefik:v3`, `docker-socket-proxy:latest`, `golang:1.27-alpine`, distroless — не закреплены по digest; в проде закрепить.
- Имя образа `ghcr.io/realmanual/traefik-selectel-dns` в compose — заглушка.
## Не проверено
- Реальный Selectel API (нет кредов): формат ответа Keystone (`expires_at`; при ошибке разбора берётся 1 час), точное поведение `filter` у зон, поведение POST на дубликат, признак конца пагинации (используется `len(result) < limit`), значение `managed_by` у записей, созданных через API.
- Реальный Traefik (структура роутеров, пагинация, статус `warning`), реальное Docker events, выпуск сертификатов и wildcard `tls.domains` — не запускалось, деплой не выполнялся.
- Не проверялись права сервисного пользователя Selectel (минимальная роль на DNS).