Cloudflare DNS pool balancer
Лёгкий контейнер на Alpine: периодически проверяет доступность бэкендов (ping или HTTP), синхронизирует записи A / AAAA для одного DNS-имени (пул) в Cloudflare через API Token, пишет цветные логи и опционально отдаёт HTML-страницу статуса с ограничением по IP.
Оглавление
- Как это работает
- Требования
- Сборка образа (Linux / bash)
- Быстрый старт
- Переменные окружения
- Cloudflare: API Token
- Примеры запуска
- Docker Compose
- Веб-интерфейс статуса и whitelist
- Логи
- Ограничения
- Устранение неполадок
- Сборка в CI (Gitea Actions)
- Безопасность
- Windows и WSL2
Как это работает
- Для каждой цели из
CHECK_TARGETSопределяется IP (прямой адрес, резолв имени или хоста из URL). - Выполняется проверка: ICMP ping к IP или curl по URL (см.
CHECK_MODE). - IP считается желательным в пуле, только если все цели, которые резолвятся в этот IP, прошли проверку.
- Читаются текущие записи A/AAAA с именем
POOL_DOMAINв Cloudflare. - Лишние записи удаляются, недостающие создаются (TTL и proxied из переменных окружения).
Два разных смысла curl:
- Проверка бэкендов (
CHECK_MODE=curl) — запросы к URL из целей / шаблонуCURL_URL_TEMPLATE. - Порт
STATUS_HTTP_PORT— только встроенная страница статуса внутри контейнера, не health-check.
Требования
- Docker Engine на Linux (или эквивалент с Linux-контейнерами).
- У контейнера должен быть доступ в интернет (Cloudflare API, ping/curl к бэкендам).
- Для ping из пользователя
nobodyв образе выставленcap_net_rawна/usr/bin/ping. Если ping не работает, запускайте с--cap-add=NET_RAW(вdocker-compose.ymlэто уже указано).
Сборка образа (Linux / bash)
Из корня репозитория:
cd /path/to/cloudflare_balancer
docker build -t cloudflare-balancer:latest .
Проверка локально (без реальных секретов контейнер сразу завершится с ошибкой — это нормально):
docker build -t cloudflare-balancer:latest . && echo "сборка OK"
Быстрый старт
Минимально нужны: POOL_DOMAIN, CHECK_TARGETS, CLOUDFLARE_API_TOKEN.
Рекомендуется передавать секреты через --env-file:
cp .env.example .env
# отредактируйте .env
docker run --rm \
--cap-add=NET_RAW \
--env-file .env \
cloudflare-balancer:latest
Переменные окружения
| Переменная | Обязательно | По умолчанию | Описание |
|---|---|---|---|
POOL_DOMAIN |
да | — | DNS-имя пула (все A/AAAA с этим именем управляются скриптом). |
CHECK_TARGETS |
да | — | Список целей через запятую: IP, hostname или http(s)://.... |
CLOUDFLARE_API_TOKEN |
да* | — | Bearer-токен Cloudflare. |
CLOUDFLARE_API_KEY |
нет | — | Алиас для токена (если не задан CLOUDFLARE_API_TOKEN). |
CLOUDFLARE_ZONE_ID |
нет | — | ID зоны; если пусто — поиск по имени зоны. |
CLOUDFLARE_ZONE_NAME |
нет | — | Имя зоны (apex), например example.com; если пусто — эвристика: две последние метки POOL_DOMAIN. |
CHECK_INTERVAL_SEC |
нет | 30 |
Пауза между циклами, сек. |
CHECK_MODE |
нет | ping |
ping или curl. |
PING_COUNT |
нет | 2 |
Число ICMP-запросов. |
PING_TIMEOUT_SEC |
нет | 2 |
Таймаут ping (см. iputils-ping). |
CURL_URL_TEMPLATE |
нет | http://%s/ |
Для не-URL целей: один плейсхолдер %s заменяется на цель. |
CURL_MAX_TIME_SEC |
нет | 5 |
--max-time для curl. |
CURL_CONNECT_TIMEOUT_SEC |
нет | 3 |
--connect-timeout. |
CURL_OK_MIN |
нет | 200 |
Минимальный допустимый HTTP-код. |
CURL_OK_MAX |
нет | 399 |
Максимальный допустимый HTTP-код. |
DEFAULT_TTL |
нет | 300 |
TTL при создании записи (для прокси-записей Cloudflare сам использует авто-TTL). |
DEFAULT_PROXIED |
нет | false |
true / false — оранжевое облако. |
ENABLE_STATUS_HTTP |
нет | 0 |
1 — включить HTTP-страницу статуса. |
STATUS_HTTP_PORT |
нет | 8080 |
Порт внутри контейнера для страницы статуса. |
STATUS_HTTP_ALLOW_IPS |
нет | (пусто) | Whitelist: IP и IPv4 CIDR через запятую. Пусто = слушать только 127.0.0.1. |
* Обязателен токен или алиас.
Важно: эвристика зоны по двум последним меткам не подходит для всех публичных суффиксов (например некоторые зоны второго уровня). В сомнениях задайте
CLOUDFLARE_ZONE_IDилиCLOUDFLARE_ZONE_NAME.
Cloudflare: API Token
- Cloudflare Dashboard → My Profile → API Tokens → Create Token.
- Шаблон Edit zone DNS или кастомный минимум:
- Zone — DNS — Edit
- Zone — Zone — Read
- Ограничить конкретной зоной (рекомендуется).
- Скопируйте токен в
.envкакCLOUDFLARE_API_TOKEN.
Официальная документация API: Cloudflare API.
Примеры запуска
Только ping, без UI
docker run --rm --cap-add=NET_RAW \
-e POOL_DOMAIN=app.example.com \
-e CHECK_TARGETS='10.0.0.1,10.0.0.2' \
-e CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN" \
-e CHECK_INTERVAL_SEC=20 \
cloudflare-balancer:latest
Режим curl и шаблон URL
docker run --rm --cap-add=NET_RAW \
-e POOL_DOMAIN=app.example.com \
-e CHECK_TARGETS='backend1.internal,backend2.internal' \
-e CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN" \
-e CHECK_MODE=curl \
-e CURL_URL_TEMPLATE='http://%s:8080/health' \
cloudflare-balancer:latest
Полный URL как цель
docker run --rm --cap-add=NET_RAW \
-e POOL_DOMAIN=app.example.com \
-e CHECK_TARGETS='https://node1.example.com/health' \
-e CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN" \
-e CHECK_MODE=curl \
cloudflare-balancer:latest
Веб-статус + проброс порта + whitelist
При доступе с хоста Docker клиентский IP в контейнере часто совпадает с адресом шлюза bridge (часто 172.17.0.1 или подсеть 172.17.0.0/16). Добавьте её или конкретный IP в whitelist.
docker run --rm --cap-add=NET_RAW \
-p 8080:8080 \
-e POOL_DOMAIN=app.example.com \
-e CHECK_TARGETS='10.0.0.1' \
-e CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN" \
-e ENABLE_STATUS_HTTP=1 \
-e STATUS_HTTP_PORT=8080 \
-e STATUS_HTTP_ALLOW_IPS='127.0.0.1,172.17.0.0/16,10.0.0.0/8' \
cloudflare-balancer:latest
Откройте в браузере: http://127.0.0.1:8080/ (если whitelist это разрешает).
Docker Compose
cp .env.example .env
# заполните CLOUDFLARE_API_TOKEN и остальное
docker compose build
docker compose up -d
docker compose logs -f
Файл docker-compose.yml подключает .env и добавляет NET_RAW для ping.
Веб-интерфейс статуса и whitelist
STATUS_HTTP_PORT— порт HTTP-сервера статуса, не имеет отношения кCHECK_MODE=curl.- Если
STATUS_HTTP_ALLOW_IPSпустой —socatслушает 127.0.0.1 (доступ с хоста через-pбез NAT к loopback контейнера обычно не попадёт на 127.0.0.1 внутри контейнера). Для просмотра со стороны хоста задайте whitelist и0.0.0.0-bind (это делается автоматически при непустом whitelist). - В whitelist поддерживаются точные IPv4 и IPv4 CIDR; для IPv6 — в основном точное совпадение (CIDR для v6 в обработчике минимальный).
Логи
docker logs -f <container_id>
В логах используются ANSI-цвета (удобно в терминале). Блоки: заголовок цикла, строки OK/FAIL по целям, действия DELETE/POST в API.
Ограничения
- Одна строка в
CHECK_TARGETSс запятой внутри URL может сломать разбор — избегайте запятых в целях или используйте только один хост на цель. - Несколько имён, резолвящихся в один IP, считаются одним бэкендом: IP остаётся в пуле только если все такие проверки успешны.
- Записи A/AAAA для
POOL_DOMAIN, которые не соответствуют IP из текущего резолва целей, не удаляются (в лог и HTML выводится предупреждение «вне списка»).
Устранение неполадок
| Симптом | Что проверить |
|---|---|
| Ошибка авторизации Cloudflare | Токен, срок, зона в области действия токена. |
| «Зона не найдена» | Задайте CLOUDFLARE_ZONE_ID или корректный CLOUDFLARE_ZONE_NAME. |
| Все ping FAIL | NET_RAW / cap_add; маршрутизация сети контейнера до бэкендов; ICMP может быть запрещён файрволом. |
| curl всегда FAIL | URL, TLS, коды ответа (CURL_OK_MIN / CURL_OK_MAX), таймауты. |
| UI отдаёт 403 | Whitelist: добавьте IP шлюза Docker или вашу подсеть. |
| После старта пул «пустой» | При всех FAIL записи удаляются — проверьте доступность целей и корректность IP. |
Сборка в CI (Gitea Actions)
- На сервере Gitea включите Actions и подключите runner (документация).
- Workflow: .gitea/workflows/docker.yml.
- По умолчанию выполняется
docker build. - Опциональная публикация в Container Registry Gitea: задайте секреты репозитория:
REGISTRY_URL— хост (можно сhttps://, скрипт обрежет протокол дляdocker login).REGISTRY_USER— логин пользователя Gitea.PACKAGE_TOKEN— токен Gitea с правом публиковать пакеты (используется как пароль вdocker login; рекомендуемый способ).REGISTRY_PASSWORD— опционально, только если не заданPACKAGE_TOKEN(устаревший/альтернативный секрет).
Имя образа в registry: <REGISTRY>/<owner_lowercase>/cloudflare-balancer:<tag>.
В Gitea создайте Personal Access Token (или токен с нужным scope) с доступом к пакетам / записи в Container Registry и сохраните его в секрете
PACKAGE_TOKEN.
Безопасность
- Не коммитьте
.envи реальные токены. - Минимизируйте права API Token одной зоной.
- Не публикуйте порт статуса в интернет без whitelist и без понимания сетевой модели Docker.
Windows и WSL2
Основные команды из этой инструкции рассчитаны на bash под Linux. На Windows удобнее запускать Docker внутри WSL2 и выполнять те же docker build / docker run из Ubuntu (или другого дистрибутива в WSL).
Лицензия
Проект в репозитории пользователя — при необходимости добавьте файл LICENSE отдельно.