Files
cloudflare_balancer/README.md
T
Denozordec 2671e90d20
Docker / build (push) Successful in 31s
Init Commit
2026-03-23 17:31:26 +07:00

273 lines
14 KiB
Markdown
Raw 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.
# Cloudflare DNS pool balancer
Лёгкий контейнер на **Alpine**: периодически проверяет доступность бэкендов (**ping** или **HTTP**), синхронизирует записи **A / AAAA** для одного DNS-имени (**пул**) в Cloudflare через **API Token**, пишет цветные логи и опционально отдаёт **HTML-страницу статуса** с ограничением по IP.
---
## Оглавление
- [Как это работает](#как-это-работает)
- [Требования](#требования)
- [Сборка образа (Linux / bash)](#сборка-образа-linux--bash)
- [Быстрый старт](#быстрый-старт)
- [Переменные окружения](#переменные-окружения)
- [Cloudflare: API Token](#cloudflare-api-token)
- [Примеры запуска](#примеры-запуска)
- [Docker Compose](#docker-compose)
- [Веб-интерфейс статуса и whitelist](#веб-интерфейс-статуса-и-whitelist)
- [Логи](#логи)
- [Ограничения](#ограничения)
- [Устранение неполадок](#устранение-неполадок)
- [Сборка в CI (Gitea Actions)](#сборка-в-ci-gitea-actions)
- [Безопасность](#безопасность)
- [Windows и WSL2](#windows-и-wsl2)
---
## Как это работает
1. Для каждой цели из `CHECK_TARGETS` определяется IP (прямой адрес, резолв имени или хоста из URL).
2. Выполняется проверка: **ICMP ping** к IP или **curl** по URL (см. `CHECK_MODE`).
3. IP считается **желательным в пуле**, только если **все** цели, которые резолвятся в этот IP, прошли проверку.
4. Читаются текущие записи **A/AAAA** с именем `POOL_DOMAIN` в Cloudflare.
5. Лишние записи удаляются, недостающие создаются (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)
Из корня репозитория:
```bash
cd /path/to/cloudflare_balancer
docker build -t cloudflare-balancer:latest .
```
Проверка локально (без реальных секретов контейнер сразу завершится с ошибкой — это нормально):
```bash
docker build -t cloudflare-balancer:latest . && echo "сборка OK"
```
---
## Быстрый старт
Минимально нужны: `POOL_DOMAIN`, `CHECK_TARGETS`, `CLOUDFLARE_API_TOKEN`.
Рекомендуется передавать секреты через **`--env-file`**:
```bash
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
1. Cloudflare Dashboard → **My Profile****API Tokens****Create Token**.
2. Шаблон **Edit zone DNS** или кастомный минимум:
- **Zone** — **DNS****Edit**
- **Zone** — **Zone****Read**
- Ограничить **конкретной зоной** (рекомендуется).
3. Скопируйте токен в `.env` как `CLOUDFLARE_API_TOKEN`.
Официальная документация API: [Cloudflare API](https://developers.cloudflare.com/api/).
---
## Примеры запуска
### Только ping, без UI
```bash
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
```bash
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 как цель
```bash
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.
```bash
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
```bash
cp .env.example .env
# заполните CLOUDFLARE_API_TOKEN и остальное
docker compose build
docker compose up -d
docker compose logs -f
```
Файл [docker-compose.yml](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 в обработчике минимальный).
---
## Логи
```bash
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)
1. На сервере Gitea включите **Actions** и подключите **runner** ([документация](https://docs.gitea.com/usage/actions/overview)).
2. Workflow: [.gitea/workflows/docker.yml](.gitea/workflows/docker.yml).
3. По умолчанию выполняется **`docker build`**.
4. Опциональная публикация в **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` отдельно.