Files
vasyanskandClaude Sonnet 5 273cd54415
Build / Tests (push) Successful in 13s
Build / Build image (push) Failing after 15s
Build / Notify on failure (push) Failing after 0s
feat: traefik-selectel-dns — A-записи Selectel по роутам Traefik + CI сборка образа
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 21:50:07 +07:00

68 lines
6.2 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.
# traefik-selectel-dns
Демон, который создаёт A-записи в Selectel DNS (API v2) для хостов из роутеров Traefik.
Сертификаты выписывает сам Traefik (встроенный `dnsChallenge.provider: selectelv2`) — кода для этого не нужно.
## Почему не плагин Traefik
Плагин (Yaegi) работает внутри цепочки обработки запросов: он не видит создание/удаление роутов
и не должен хранить токен с правом записи в DNS. Отдельный демон читает Traefik API
(`/api/http/routers`), поэтому одинаково покрывает Docker labels и file-provider.
## Как работает
1. Раз в `interval` (и сразу после событий Docker) читает `GET /api/http/routers`.
2. Из `rule` берёт все `Host(...)` у роутеров со статусом `enabled` (`HostRegexp`, `!Host(...)` игнорируются, `*.x` пропускаются с warning).
3. Хосты, попадающие в `zones` из конфига (равны зоне или её поддомен; при вложенных зонах — самая длинная), получают A-запись `ip`/`ttl`.
4. Записи, созданные демоном, помечаются `comment: managed-by=traefik-selectel-dns`. Меняются/удаляются только они. Существующая A-запись без метки (и имя с CNAME) не трогается — только warning.
5. Удаление выключено по умолчанию. При `delete.enabled: true` запись удаляется после того, как роут отсутствует непрерывно `delete.gracePeriod` (состояние в памяти; после рестарта отсчёт начинается заново). Роутеры со статусом `warning` считаются присутствующими. При ошибке Traefik API или пустом списке (после непустого) цикл пропускается и ничего не удаляется.
## Настройка
Пример полного стенда: `deploy/` (`docker-compose.example.yml`, `traefik.example.yml`, `config.example.yaml`, `dynamic/`).
1. Создайте в Selectel сервисного пользователя с минимальной ролью на DNS проекта.
2. Положите креды в файлы секретов (`deploy/secrets/*`, не коммитить) — см. комментарий в compose.
3. Поправьте `zones`, `ip`, `traefik.apiURL` в `config.example.yaml`; wildcard `tls.domains` — в `traefik.example.yml`.
4. Для первого запуска включите `dryRun: true` и проверьте логи.
Сборка: `docker build -t traefik-selectel-dns .` (distroless, non-root, HEALTHCHECK через `-healthcheck`).
## Конфигурация
YAML (`-config` или `TSD_CONFIG`), неизвестные поля — ошибка. Переопределение через env:
| YAML | env | По умолчанию |
|---|---|---|
| `zones` | `TSD_ZONES` (через запятую) | обязательно |
| `ip` | `TSD_IP` | обязательно, IPv4 |
| `ttl` | `TSD_TTL` | 300 (60..604800) |
| `interval` | `TSD_INTERVAL` | 30s |
| `dryRun` | `TSD_DRY_RUN` | false |
| `traefik.apiURL` / `.timeout` | `TSD_TRAEFIK_API_URL` / `TSD_TRAEFIK_TIMEOUT` | http://traefik:8080 / 10s |
| `docker.enabled` / `.host` | `TSD_DOCKER_ENABLED` / `TSD_DOCKER_HOST` | true / unix:///var/run/docker.sock (`tcp://` тоже) |
| `delete.enabled` / `.gracePeriod` | `TSD_DELETE_ENABLED` / `TSD_DELETE_GRACE_PERIOD` | false / 10m |
| `selectel.baseURL` / `.authURL` / `.timeout` | `TSD_SELECTEL_BASE_URL` / `_AUTH_URL` / `_TIMEOUT` | api.selectel.ru/domains/v2, cloud.api.selcloud.ru/identity/v3, 30s |
| `listen` | `TSD_LISTEN` | :9090 (`/healthz`) |
| `logLevel` / `logFormat` | `TSD_LOG_LEVEL` / `TSD_LOG_FORMAT` | info / json |
Секреты — только env (каждая поддерживает суффикс `_FILE`; одновременно обе — ошибка):
`SELECTEL_USERNAME`, `SELECTEL_PASSWORD`, `SELECTEL_ACCOUNT_ID`, `SELECTEL_PROJECT_ID`.
Для Traefik (lego) те же значения передаются как `SELECTELV2_USERNAME_FILE`, `SELECTELV2_PASSWORD_FILE`, `SELECTELV2_ACCOUNT_ID_FILE`, `SELECTELV2_PROJECT_ID_FILE`.
## Безопасность
- Секреты не попадают в конфиг, образ и логи; IAM-токен (Keystone) кэшируется в памяти и обновляется заранее.
- Traefik API не публикуется наружу и ограничен `ipAllowList` подсетью сети `control`; пользовательские контейнеры в неё не подключать.
- Docker socket = root на хосте. Демону нужны только события, поэтому в примере используется `docker-socket-proxy` (`EVENTS=1`), а не прямой сокет. Без Docker events демон работает по опросу.
- Чужие записи не перезаписываются; удаление выключено по умолчанию.
## Ограничения
- Только A-записи, один фиксированный IPv4 для всех хостов; AAAA нет.
- Только `Host(...)` с ASCII-именами (IDN — в punycode); `HostRegexp` и wildcard-хосты не поддерживаются.
- Метка владения — exact-match комментария; если её вручную изменить/стереть, запись считается чужой.
- Grace-состояние в памяти; несколько реплик демона одновременно не поддерживаются.
- Поток Docker events без общего таймаута (переподключается при обрыве; страховка — периодический опрос).
- Реальный Selectel API не проверялся (нет кредов) — см. отчёт в `swarm-report/`.