diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a82501 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Docker Compose local/real environment and config files. +# Copy from the committed *.example counterparts in deploy/docker/. +/deploy/docker/.env +/deploy/docker/.env.* +!/deploy/docker/.env.example +!/deploy/docker/.env.prod.example +/deploy/docker/control-api/control-api.docker.yaml diff --git a/README.md b/README.md index 0e25c6a..8c36136 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ HTTP API control-api. | [docs/DASHBOARD.md](docs/DASHBOARD.md) | Браузерная админ-панель (`admin-dashboard`) — то же самое API, но графически | | [docs/DIAGRAMS.md](docs/DIAGRAMS.md) | Диаграммы потоков данных: control plane, поток проверки до целевого сервера, поток телеметрии | | [docs/LOCAL_E2E.md](docs/LOCAL_E2E.md) | Полностью офлайн-прогон всей системы одним скриптом — без реального облака и интернета | +| [deploy/docker/RUN.txt](deploy/docker/RUN.txt) | Сборка и запуск каждого компонента в Docker: команды `docker build`/`docker run`, переменные окружения | ## Быстрый старт (60 секунд, без OpenStack) @@ -55,3 +56,55 @@ scripts/run-local-e2e.sh ```bash curl -s http://:8080/api/v1/admin/status | python3 -m json.tool ``` + +## Развёртывание в Docker + +Все четыре компонента можно собрать и запустить как отдельные Docker-образы +вместо systemd-юнитов — Dockerfile'ы лежат в `deploy/docker/<компонент>/`, +полные команды сборки/запуска и список переменных окружения — в +[deploy/docker/RUN.txt](deploy/docker/RUN.txt). + +- `prober`, `validator-agent`, `admin-dashboard` — конфиг генерируется + внутри контейнера из переменных окружения (`docker-entrypoint.sh` + + `envsubst`), готовых образов для монтирования не требуется. +- `control-api` — конфиг содержит списки (validators/sites/targets/ + ip_addresses) и имена env-переменных для OpenStack-креденшлов, поэтому + монтируется файлом (`-v .../control-api.yaml:/etc/cloud-ip-validator/control-api.yaml:ro`), + а база данных — отдельным volume для персистентности. + +Пример для `prober`: + +```bash +docker build --platform linux/amd64 -t cloud-ip-validator-prober -f deploy/docker/prober/Dockerfile . +docker run -d --platform linux/amd64 --cap-add NET_RAW --name prober \ + -e PROBER_SITE_ID= \ + -e PROBER_CONTROL_API_URL= \ + cloud-ip-validator-prober +``` + +`site_id` должен быть заранее зарегистрирован на control-api +(`PUT /api/v1/admin/config/sites/{index}`) — иначе контейнер завершится с +ошибкой регистрации. Аналогичные команды для остальных трёх компонентов — +в [deploy/docker/RUN.txt](deploy/docker/RUN.txt). + +### Запуск всех компонентов через docker-compose + +Для совместного запуска (сеть, healthcheck, volume для базы данных) есть +`docker-compose.yml` в `deploy/docker/` — разбит на базовый файл и +окружения: `docker-compose.override.yml` (dev, подхватывается автоматически) +и `docker-compose.prod.yml` (прод). Набор запускаемых сервисов на каждом +хосте задаётся через `COMPOSE_PROFILES` в `.env`-файле (`control-plane`, +`dashboard`, `prober`, `validator`) — так один и тот же compose можно +поднять целиком локально или по частям на разных хостах (управляющая +машина / внешняя площадка с `prober` / ВМ-валидатор), как в реальной +топологии. + +```bash +cd deploy/docker +cp .env.example .env +cp control-api/control-api.docker.example.yaml control-api/control-api.docker.yaml +docker compose up -d --build +``` + +Для прода: `docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build` +(см. комментарии в `.env.prod.example`). diff --git a/deploy/docker/.env.example b/deploy/docker/.env.example new file mode 100644 index 0000000..2a210a5 --- /dev/null +++ b/deploy/docker/.env.example @@ -0,0 +1,19 @@ +# Copy to .env and edit if needed — defaults below work out of the box +# with control-api/control-api.docker.example.yaml (openstack.mode: mock, +# no OpenStack credentials needed). +# +# cp .env.example .env +# cp control-api/control-api.docker.example.yaml control-api/control-api.docker.yaml +# docker compose up -d --build + +COMPOSE_PROFILES=control-plane,dashboard,prober,validator + +# --- prober (must match a site_id in the mounted control-api config) --- +PROBER_SITE_ID=site-1 + +# --- validator-agent (must match a validator_id in the mounted control-api config) --- +VALIDATOR_AGENT_VALIDATOR_ID=validator_01 + +# ADMIN_DASHBOARD_CONTROL_API_URL / PROBER_CONTROL_API_URL / +# VALIDATOR_AGENT_CONTROL_API_URL default to http://control-api:8080 in +# docker-compose.yml (same Docker network) — only set here to override. diff --git a/deploy/docker/.env.prod.example b/deploy/docker/.env.prod.example new file mode 100644 index 0000000..3d88c22 --- /dev/null +++ b/deploy/docker/.env.prod.example @@ -0,0 +1,41 @@ +# Copy to .env.prod per host and fill in only what that host needs. +# +# docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build +# +# COMPOSE_PROFILES controls which services THIS host runs — pick one: +# control-plane host: control-plane,dashboard +# external prober site: prober +# OpenStack validator VM: validator +# single all-in-one host: control-plane,dashboard,prober,validator +COMPOSE_PROFILES=control-plane,dashboard + +# --- control-api (only needed when this host runs the control-plane profile) --- +# Real config with validators/sites/targets/ip_addresses — see +# configs/control-api.example.yaml and docs/SETUP.md. Required. +CONTROL_API_CONFIG_PATH=/etc/cloud-ip-validator/control-api.yaml + +# OpenStack creds — only read when the mounted control-api.yaml has +# openstack.mode: real. Leave blank in mock mode. +OS_AUTH_URL= +OS_PROJECT_ID= +OS_REGION_NAME= +OS_INTERFACE= +OS_TOKEN= +# auth_method: password instead of token: +# OS_USERNAME= +# OS_USER_DOMAIN_NAME= +# OS_PASSWORD= + +# --- prober (only needed on a prober-profile host) --- +PROBER_SITE_ID= +# Real, network-reachable control-api URL — NOT http://control-api:8080 +# unless this host also runs the control-plane profile in the same +# compose invocation. +PROBER_CONTROL_API_URL=https://control-api.internal.example.com + +# --- validator-agent (only needed on a validator-profile host) --- +VALIDATOR_AGENT_VALIDATOR_ID= +VALIDATOR_AGENT_CONTROL_API_URL=https://control-api.internal.example.com + +# --- admin-dashboard (only needed on a dashboard-profile host) --- +ADMIN_DASHBOARD_CONTROL_API_URL=http://control-api:8080 diff --git a/deploy/docker/RUN.txt b/deploy/docker/RUN.txt index d8bc87e..87adaa0 100644 --- a/deploy/docker/RUN.txt +++ b/deploy/docker/RUN.txt @@ -1,3 +1,17 @@ +Для совместного запуска всех компонентов теперь есть docker-compose: +docker-compose.yml (база) + docker-compose.override.yml (dev, подхватывается +автоматически) + docker-compose.prod.yml (прод). Быстрый старт: + + cp .env.example .env + cp control-api/control-api.docker.example.yaml control-api/control-api.docker.yaml + docker compose up -d --build + +Подробности — в README.md, раздел "Развёртывание в Docker". Команды ниже +документируют то же самое на уровне отдельного docker build/docker run — +пригодятся для точечной отладки одного компонента без compose. + +=============================================================================== + Сборка образа (из корня репозитория): docker build --platform linux/amd64 -t cloud-ip-validator-prober -f deploy/docker/prober/Dockerfile . diff --git a/deploy/docker/control-api/control-api.docker.example.yaml b/deploy/docker/control-api/control-api.docker.example.yaml new file mode 100644 index 0000000..9f7bf4f --- /dev/null +++ b/deploy/docker/control-api/control-api.docker.example.yaml @@ -0,0 +1,65 @@ +# Ready-to-copy control-api config for `docker compose` dev use — mock +# OpenStack mode (no credentials needed) with minimal seed data matching +# deploy/docker/.env.example's PROBER_SITE_ID / VALIDATOR_AGENT_VALIDATOR_ID +# defaults, so `docker compose up` works out of the box after: +# +# cp control-api.docker.example.yaml control-api.docker.yaml +# +# For a real deployment, start from configs/control-api.example.yaml +# instead (full schema reference, openstack.mode: real). + +server: + listen_addr: ":8080" + +database: + path: "/var/lib/cloud-ip-validator/control-api.db" + +openstack: + mode: "mock" + +orchestrator: + poll_interval_seconds: 5 + self_check_timeout_seconds: 60 + max_self_check_retries: 3 + checking_window_seconds: 120 + max_retries: 3 + lease_ttl_seconds: 180 + heartbeat_timeout_seconds: 30 + fip_settle_seconds: 0 + +aggregation: + missing_counts_as_fail: true + +validators: + - validator_id: "validator_01" + os_port_id: "mock-port-1" + +sites: + - site_id: "site-1" + index: 1 + +check_types: + - name: "https" + enabled: true + targets: ["default-targets"] + - name: "icmp" + enabled: true + targets: ["default-targets"] + - name: "ssh" + enabled: false + targets: [] + +targets: + default-targets: + - "https://hub.docker.com" + - "https://github.com" + - "https://packages.ubuntu.com" + +inbound_checks: + ports: [22, 80, 443, 8080] + icmp: true + +ip_addresses: + - "203.0.113.10" + - "203.0.113.11" + - "203.0.113.12" diff --git a/deploy/docker/docker-compose.override.yml b/deploy/docker/docker-compose.override.yml new file mode 100644 index 0000000..ceab2c2 --- /dev/null +++ b/deploy/docker/docker-compose.override.yml @@ -0,0 +1,26 @@ +# Dev overlay — auto-loaded by `docker compose up` alongside docker-compose.yml. +# Not used in prod (docker-compose.prod.yml is loaded explicitly instead). + +services: + control-api: + ports: + - "8080:8080" + volumes: + - ./control-api/control-api.docker.yaml:/etc/cloud-ip-validator/control-api.yaml:ro + + admin-dashboard: + ports: + - "8090:8090" + depends_on: + control-api: + condition: service_healthy + + prober: + depends_on: + control-api: + condition: service_healthy + + validator-agent: + depends_on: + control-api: + condition: service_healthy diff --git a/deploy/docker/docker-compose.prod.yml b/deploy/docker/docker-compose.prod.yml new file mode 100644 index 0000000..6269e55 --- /dev/null +++ b/deploy/docker/docker-compose.prod.yml @@ -0,0 +1,35 @@ +# Prod overlay — load explicitly: +# docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build +# +# No ports: (prod is expected to sit behind a reverse proxy / firewalled +# network) and no depends_on: — on a split deployment (control-api on one +# host, prober/validator-agent on others) control-api's profile is not even +# part of that host's compose invocation, and depends_on on an inactive +# profile is a hard `docker compose config` error. restart: unless-stopped +# is the substitute safety net for the startup-order race (see RUN.txt / +# README): prober and validator-agent exit on a failed one-shot +# registration, so they get restarted until control-api is reachable. + +services: + control-api: + restart: unless-stopped + volumes: + - ${CONTROL_API_CONFIG_PATH:-/etc/cloud-ip-validator/UNCONFIGURED-control-api.yaml}:/etc/cloud-ip-validator/control-api.yaml:ro + environment: + OS_AUTH_URL: "${OS_AUTH_URL:-}" + OS_PROJECT_ID: "${OS_PROJECT_ID:-}" + OS_REGION_NAME: "${OS_REGION_NAME:-}" + OS_INTERFACE: "${OS_INTERFACE:-}" + OS_TOKEN: "${OS_TOKEN:-}" + OS_USERNAME: "${OS_USERNAME:-}" + OS_USER_DOMAIN_NAME: "${OS_USER_DOMAIN_NAME:-}" + OS_PASSWORD: "${OS_PASSWORD:-}" + + admin-dashboard: + restart: unless-stopped + + prober: + restart: unless-stopped + + validator-agent: + restart: unless-stopped diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 0000000..82f21d9 --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,94 @@ +name: cloud-ip-validator + +# Base service definitions shared by every environment. Combine with +# docker-compose.override.yml (auto-loaded, dev) or docker-compose.prod.yml +# (prod) — see deploy/docker/RUN.txt for the exact invocations. +# +# Which services actually start is controlled per host via COMPOSE_PROFILES +# in the active .env file, matching the real deployment topology: +# control-plane control-api (one management host) +# dashboard admin-dashboard (optional, anywhere with network access +# to control-api) +# prober prober (one per external test site) +# validator validator-agent (one per validator VM in the cloud) + +networks: + backend: + name: cloud-ip-validator + +volumes: + control-api-db: + # Same volume name deploy/docker/RUN.txt's manual `docker run -v + # cloud-ip-validator-db:...` already uses, so a manual-Docker deployment + # can adopt compose without losing existing data. + name: cloud-ip-validator-db + +services: + control-api: + build: + context: ../.. + dockerfile: deploy/docker/control-api/Dockerfile + image: cloud-ip-validator-control-api + platform: linux/amd64 + profiles: ["control-plane"] + networks: [backend] + volumes: + - control-api-db:/var/lib/cloud-ip-validator + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"] + interval: 5s + timeout: 3s + retries: 10 + start_period: 5s + + admin-dashboard: + build: + context: ../.. + dockerfile: deploy/docker/admin-dashboard/Dockerfile + image: cloud-ip-validator-admin-dashboard + platform: linux/amd64 + profiles: ["dashboard"] + networks: [backend] + environment: + ADMIN_DASHBOARD_CONTROL_API_URL: "${ADMIN_DASHBOARD_CONTROL_API_URL:-http://control-api:8080}" + ADMIN_DASHBOARD_LISTEN_ADDR: "${ADMIN_DASHBOARD_LISTEN_ADDR:-:8090}" + ADMIN_DASHBOARD_CONTROL_API_TIMEOUT_SECONDS: "${ADMIN_DASHBOARD_CONTROL_API_TIMEOUT_SECONDS:-10}" + ADMIN_DASHBOARD_LAST_COMPLETED_COUNT: "${ADMIN_DASHBOARD_LAST_COMPLETED_COUNT:-20}" + ADMIN_DASHBOARD_POLL_INTERVAL_SECONDS: "${ADMIN_DASHBOARD_POLL_INTERVAL_SECONDS:-5}" + + prober: + build: + context: ../.. + dockerfile: deploy/docker/prober/Dockerfile + image: cloud-ip-validator-prober + platform: linux/amd64 + profiles: ["prober"] + networks: [backend] + cap_add: [NET_RAW] + environment: + PROBER_SITE_ID: "${PROBER_SITE_ID:-}" + PROBER_CONTROL_API_URL: "${PROBER_CONTROL_API_URL:-http://control-api:8080}" + PROBER_POLL_INTERVAL_SECONDS: "${PROBER_POLL_INTERVAL_SECONDS:-5}" + PROBER_TCP_TIMEOUT_SECONDS: "${PROBER_TCP_TIMEOUT_SECONDS:-5}" + PROBER_ICMP_TIMEOUT_SECONDS: "${PROBER_ICMP_TIMEOUT_SECONDS:-5}" + PROBER_ICMP_COUNT: "${PROBER_ICMP_COUNT:-3}" + + validator-agent: + build: + context: ../.. + dockerfile: deploy/docker/validator-agent/Dockerfile + image: cloud-ip-validator-validator-agent + platform: linux/amd64 + profiles: ["validator"] + networks: [backend] + cap_add: [NET_RAW] + environment: + VALIDATOR_AGENT_VALIDATOR_ID: "${VALIDATOR_AGENT_VALIDATOR_ID:-}" + VALIDATOR_AGENT_CONTROL_API_URL: "${VALIDATOR_AGENT_CONTROL_API_URL:-http://control-api:8080}" + VALIDATOR_AGENT_POLL_INTERVAL_SECONDS: "${VALIDATOR_AGENT_POLL_INTERVAL_SECONDS:-5}" + VALIDATOR_AGENT_SELF_CHECK_TIMEOUT_SECONDS: "${VALIDATOR_AGENT_SELF_CHECK_TIMEOUT_SECONDS:-10}" + VALIDATOR_AGENT_HTTPS_TIMEOUT_SECONDS: "${VALIDATOR_AGENT_HTTPS_TIMEOUT_SECONDS:-10}" + VALIDATOR_AGENT_ICMP_TIMEOUT_SECONDS: "${VALIDATOR_AGENT_ICMP_TIMEOUT_SECONDS:-5}" + VALIDATOR_AGENT_ICMP_COUNT: "${VALIDATOR_AGENT_ICMP_COUNT:-3}" + VALIDATOR_AGENT_SSH_ENABLED: "${VALIDATOR_AGENT_SSH_ENABLED:-false}" + VALIDATOR_AGENT_SSH_TIMEOUT_SECONDS: "${VALIDATOR_AGENT_SSH_TIMEOUT_SECONDS:-5}"