Назад к документации

Конфигурация

Полный справочник по всем секциям и полям, которые читает код api-gateway. Для каждого поля указаны тип, значение по умолчанию, допустимые значения и короткий пример. Значения по умолчанию применяются, когда ключ не задан в YAML; явно заданные значения имеют приоритет.

Общая структура

Конфиг — один YAML-файл. Путь задаётся флагом -config (по умолчанию /etc/proxy/config.yaml). Секции верхнего уровня:

СекцияНазначение
applicationРежим работы, метрики, пул соединений
serverHTTP-сервер: порт, таймауты, лимит тела
tlsHTTPS и автоматические сертификаты
staticРаздача статики и SPA
targetsБэкенды
jwtПроверка JWT
basic_authBasic Auth на служебные пути
loggingУровень, формат, access log
headersCORS, проброс claims, подпись, заголовки
routingПравила маршрутизации и глобальный лимит
permissionsИнтеграция с permission-сервисом
webhooksПубликация событий (webhook/NATS)
discoveryОбнаружение сервисов по labels

Длительности записываются строками Go duration: 500ms, 5s, 5m, 1h.

Переменные, флаги и сигналы

Подстановка ${VAR}. Перед разбором YAML шлюз раскрывает ${VAR} и $VAR из окружения. Если переменная не задана или пуста, литерал ${VAR} остаётся в тексте как есть — конфиг не «молча» подставит пустоту.

jwt:
  secret_key: "${JWT_SECRET}"
permissions:
  service_url: "${PERMISSIONS_URL}"

Переменные окружения. Их читает не конфиг, а сам процесс:

ПеременнаяЗначениеСмысл
PPROF_ADDRадрес, напр. 127.0.0.1:6060Включает pprof-сервер; пусто — выключен
OTEL_EXPORTER_OTLP_ENDPOINTURL, напр. http://localhost:4318Включает OTLP-трейсинг; пусто — выключен
OTEL_EXPORTER_OTLP_INSECUREtrueОтправлять OTLP без TLS
OTEL_INSECUREtrueТо же, альтернативное имя

Флаги. -config <path> — путь к файлу конфигурации.

Сигналы. SIGHUP перечитывает конфиг и применяет его атомарно (старое состояние сохраняется при ошибке). SIGINT и SIGTERM запускают graceful shutdown с таймаутом 30 секунд.

Неизвестные ключи. Ключи, которых нет в структуре конфига, не считаются ошибкой: шлюз пишет в лог предупреждение Unknown config key с точечным путём (например headers.forward_headers). Строгий режим намеренно не включён.

application

Общие настройки процесса.

ПолеТипПо умолчаниюСмысл и значенияПример
envstring""dev включает мягкий CORS (reflect origin + credentials) и dev-логи. Любое другое значение — обычный режимprod
health_checkboolfalseВключает фоновые health-проверки таргетов, у которых задан health_checktrue
circuit_breakerboolfalseВключает circuit breaker на ошибках транспортаtrue
metrics_enabledboolfalseСобирает метрики и открывает /metricstrue
metrics_allowed_ips[]string[] (все)Allowlist клиентских IP для /metrics["10.0.0.5"]
max_idle_conns_per_hostint1000Размер пула keep-alive соединений к каждому таргету. Должен быть не меньше пиковой конкурентности1000

Метрики выключены по умолчанию, потому что их сбор добавляет работу на каждый запрос. Включайте metrics_enabled осознанно; metrics_allowed_ips ограничивает доступ к /metrics, если эндпоинт смотрит наружу.

server

HTTP-сервер.

ПолеТипПо умолчаниюСмысл и значенияПример
portint8080Порт HTTP-сервера (в TLS-режиме не используется)8080
read_timeoutduration5sТаймаут чтения запроса5s
write_timeoutduration10sТаймаут записи ответа30s
idle_timeoutduration120sТаймаут keep-alive соединения120s
max_request_body_sizeint64 (байты)10485760 (10 MiB), если ключ не заданЛимит тела запроса. 0без лимита, >0 — лимит в байтах1048576

Семантика max_request_body_size. Различаются «ключ не задан» и «явный ноль»: не задан — 10 MiB, 0 — без ограничения, положительное число — точный лимит. Тот же лимит применяется к чтению тела для аудита, чтобы вебхуки не вычитывали тело неограниченно.

tls

HTTPS с автоматическими сертификатами Let's Encrypt (ACME).

ПолеТипПо умолчаниюСмысл и значенияПример
enabledboolfalseВключает HTTPS-серверtrue
portint443HTTPS-порт443
http_portint80HTTP-порт для ACME-challenge и редиректа80
domains[]stringДомены сертификата; обязательны при enabled: true. Поддерживаются wildcard["api.example.com"]
emailstringEmail для регистрации в Let's Encrypt; обязателенadmin@example.com
cache_dirstring/var/lib/api-gateway/certsКаталог кеша сертификатов; при staging используется подкаталог staging/var/lib/api-gateway/certs
stagingboolfalsetrue — ACME staging CA (для отладки), false — production Let's Encryptfalse
directory_urlstring""Свой ACME directory URL; если задан, перекрывает выбор CA по staginghttps://acme-staging-v02.api.letsencrypt.org/directory
redirect_httpboolfalseРедиректит HTTP на HTTPS; /health и /ready на HTTP-порту отвечают 200true

При включённом TLS запускаются два сервера: HTTPS на port и HTTP на http_port для ACME-challenge и редиректа. staging: true использует реальный staging CA и отдельный подкаталог кеша, чтобы staging-сертификаты не смешивались с production.

static

Раздача статики и SPA-приложений. Обрабатывается до проксирования, но после глобального лимита.

ПолеТипПо умолчаниюСмысл и значенияПример
apps[]object[]Список SPA/статических приложенийсм. ниже
skip_prefixes[]string[]Пути, которые не отдаются статикой (уходят в прокси)["/api"]

Поля элемента apps:

ПолеТипПо умолчаниюСмысл и значенияПример
path_prefixstringURL-префикс приложения/
root_dirstringКаталог со статикой/app/static
index_filestringindex.htmlFallback-файл SPAindex.html
max_ageint (сек)3600Значение Cache-Control: max-age3600
static:
  skip_prefixes: ["/api"]
  apps:
    - path_prefix: "/"
      root_dir: "/app/static"
      index_file: "index.html"
      max_age: 3600

Если маршрут совпадает с правилом, у которого задан Host, статика пропускается — так host-based роутинг работает при смонтированной статике. Для несуществующих путей отдаётся index_file (SPA fallback); поддерживаются и плоские .html-файлы (about.html для /about).

targets

Список бэкендов. Имя и URL обязательны; имена уникальны.

ПолеТипПо умолчаниюСмысл и значенияПример
namestringУникальное имя таргетаapi
urlstringБазовый URL бэкендаhttp://127.0.0.1:9001
timeoutduration30sТаймаут запроса к таргету10s
path_prefixstring""Префикс; при пустых routing.rules из него создаётся правило/api
strip_prefixboolfalseУдалять префикс при проксированииfalse
weightint1, если ключ не заданВес в пуле маршрута: nil — 1, 0 и отрицательные исключают таргет, >0 — вес2
health_checkstring""URL health-проверки; работает при application.health_check: truehttp://127.0.0.1:9001/health

Семантика weight. Таргеты, чьи правила совпадают по (host, path_prefix, methods), образуют один пул и распределяются взвешенным round-robin по здоровым кандидатам. Отсутствие ключа weight — это вес 1, а явный weight: 0 (или отрицательный) исключает таргет из выбора. Различайте эти два случая.

jwt

Проверка JWT. Токен берётся из Authorization: Bearer … или из cookie cml_access.

ПолеТипПо умолчаниюСмысл и значенияПример
secret_keystring""Симметричный ключ для HMAC"${JWT_SECRET}"
public_key_filestring""PEM-файл публичного ключа для RSA/ECDSA/Ed25519/etc/proxy/public.pem
algorithmstringHS256HS256/384/512, RS256/384/512, ES256/384/512, EdDSA/ED25519RS256
validate_expboolfalseПроверять срок действия; принудительно true, если required: truetrue
validate_issboolfalseПроверять issuerfalse
expected_issstring""Ожидаемый issuer"https://auth.example"
validate_audboolfalseПроверять audiencefalse
expected_audstring""Ожидаемый audience"api"
claim_mappings[]string["sub"]Claims, извлекаемые в контекст; при пустом значении дополнительно маппится sub → X-User-ID["id", "email", "roles"]
requiredboolfalseТребовать токен глобальноfalse

Глобальный required перекрывается настройкой маршрута auth.required. Предъявленный токен всегда проверяется криптографически: на маршруте без требования невалидный токен игнорируется (запрос идёт как анонимный), на требующем — возвращается 401.

basic_auth

Basic Auth для служебных путей. Проверка — constant-time по SHA-256 от логина и пароля.

ПолеТипПо умолчаниюСмысл и значенияПример
enabledboolfalseВключает Basic Authtrue
usernamestring""Логинinternal
passwordstring""Пароль"${BASIC_PASS}"
skip_paths[]string[]Пути без Basic Auth; совпадение точное или по префиксу путь/["/health"]

Basic Auth стоит во внешнем слое цепочки и защищает всё, включая /metrics и статику, кроме skip_paths. При успехе заголовок Authorization удаляется перед проксированием.

logging

ПолеТипПо умолчаниюСмысл и значенияПример
levelstringinfodebug, info, warn, error, panic, fataldebug
formatstringtextconsole, text (алиас console), json; регистр не важен. Иное значение — ошибка запускаjson
access_logboolfalseПострочный лог каждого запроса; выключен по умолчанию из-за аллокацийtrue

Неверный format не «молча» превращается в консоль: загрузка конфига завершается ошибкой logging.format must be one of console, text, json.

headers

ПолеТипПо умолчаниюСмысл и значенияПример
strip_authorizationboolfalseУдалять Authorization перед проксированиемtrue
claim_to_headermap[string]string{}Claim → имя заголовка; при пустых claim_mappings по умолчанию sub → X-User-ID{id: X-User-ID}
add_headersmap[string]string{}Заголовки, добавляемые в каждый проксируемый запрос{X-Gateway: sarnas}
sign_headerstring""Имя заголовка для HMAC-SHA256(id, permissions.api_key) в hexX-User-Signature
corsobjectнетНастройки CORS (см. ниже)см. ниже

Поля cors:

ПолеТипПо умолчаниюСмысл и значенияПример
enabledboolfalseВключает CORStrue
allowed_origins[]string[]* или точные origin; при * credentials не выставляются["https://app.example"]
allowed_methods[]stringвстроенный списокРазрешённые методы["GET", "POST"]
allowed_headers[]stringвстроенный списокРазрешённые заголовки["Authorization"]
expose_headers[]stringвстроенный списокЗаголовки, видимые браузеру["X-Request-ID"]
max_ageint (сек)86400Время кеширования preflight86400
headers:
  strip_authorization: true
  claim_to_header:
    id: "X-User-ID"
    roles: "X-User-Roles"
  add_headers:
    X-Gateway-Version: "1.0.0"
  sign_header: "X-User-Signature"
  cors:
    enabled: true
    allowed_origins: ["https://app.example"]
    max_age: 86400

Правила CORS: при точном совпадении origin он отражается и выставляется Access-Control-Allow-Credentials; при * возвращается * без credentials; не попавший в allowlist origin CORS-заголовков не получает. Любой OPTIONS обрабатывается как preflight и возвращает 200. В env: "dev" при отсутствии секции cors включается мягкий режим с отражением origin.

sign_header вычисляется только если заданы и sign_header, и permissions.api_key, и в токене есть claim id.

routing

ПолеТипПо умолчаниюСмысл и значенияПример
rules[]object[]Правила маршрутизации; при пустом списке генерируются из targets с path_prefixсм. ниже
global_limitobjectнетГлобальный лимит на весь процесссм. ниже

Поля правила rules[]:

ПолеТипПо умолчаниюСмысл и значенияПример
hoststring""Матч по Host; поддерживается wildcard *.example.comapi.example.com
path_prefixstringПрефикс пути; обязателен/api
target_namestringИмя таргета; обязателен и должен существоватьapi
methods[]stringвсеФильтр HTTP-методов (регистр не важен)["GET", "POST"]
strip_pathboolfalseУдалять префикс при проксированииtrue
authobjectнетАутентификация маршрутасм. ниже
rate_limitobjectнетЛимит маршрутасм. ниже

auth: required (bool, по умолчанию наследует глобальный jwt.required), roles ([]string, должны присутствовать все), strip_token (bool, наследует headers.strip_authorization).

rate_limit: requests_per_second (float, token bucket), burst (int). global_limit имеет те же поля, но применяется ко всем запросам процесса, а не на IP.

routing:
  global_limit:
    requests_per_second: 1000
    burst: 2000
  rules:
    - path_prefix: "/api"
      target_name: "api"
      strip_path: true
      auth:
        required: true
        roles: ["user"]
      rate_limit:
        requests_per_second: 50
        burst: 100

Выбирается правило с самым длинным совпадающим path_prefix среди тех, у кого совпали host и methods. Если ничего не совпало — 404. Правила, совпадающие по (host, path_prefix, methods), делят один пул балансировки и обязаны совпадать по auth, strip_path и rate_limit, иначе запуск завершается ошибкой.

permissions

Интеграция с внешним permission-сервисом. Требует JWT с claim id, приводимым к целому.

ПолеТипПо умолчаниюСмысл и значенияПример
enabledboolfalseВключает модульtrue
service_urlstringБазовый URL permission-сервиса; обязателен при enabledhttp://permissions:8080
cache_ttlduration300sTTL кеша разрешений по пользователю300s
header_namestringX-User-PermissionsЗаголовок с разрешениями (через запятую)X-User-Permissions
invalidate_tokenstringзначение api_keyТокен для инвалидации кеша; если пуст — равен api_key"${INVALIDATE_TOKEN}"
api_keystring""API-ключ сервиса; также секрет для headers.sign_header"${PERMISSIONS_KEY}"

Заголовок выставляется, только если у пользователя есть непустой список разрешений. Инвалидация: POST /_cache/permissions/invalidate с заголовком X-Invalidate-Token; параметр user_id инвалидирует одного пользователя, без него — весь кеш.

webhooks

Публикация событий на каждый запрос или ответ. Транспорты — HTTP webhook и NATS.

ПолеТипПо умолчаниюСмысл и значенияПример
namestringУникальное имя вебхукаaudit
transportstringwebhook или natswebhook
webhook_urlstring""URL приёмника; обязателен для webhookhttps://audit.example/events
nats_urlstring""URL NATS; обязателен для natsnats://nats:4222
subjectstring""NATS subject; обязателен для natsaudit.events
triggerstringon_request или on_responseon_response
methods[]stringвсеФильтр методов (регистр не важен)["POST", "PUT", "PATCH", "DELETE"]
on_status_codes[]intвсеДля on_response: публиковать только эти коды[200, 201]
exclude_paths[]string[]Префиксы путей, исключённые из публикации["/health"]
asyncboolfalseОтправлять в горутине, не блокируя запросtrue
include_request_bodybooltrue, если ключ не заданПубликовать тело запроса как changes (только JSON)false
include_response_bodyboolfalseПубликовать тело ответа как response_body (только JSON, до 64 KiB)true
batch_sizeint0/1>1 включает батчинг HTTP-вебхука1000
flush_intervalduration200msМаксимальная задержка перед отправкой неполной пачки100ms

Событие содержит method, path, query, user_id, user_email, user_roles, request_id, status_code, timestamp, changes и response_body. Батчинг (batch_size > 1) шлёт один POST с телом {"count":N,"events":[...]}. Очередь батчера ограничена (8192 события); при переполнении постановка блокируется — события не теряются, но запросы замедляются (backpressure). Соединение NATS устанавливается по nats_url первого вебхука, поэтому у всех NATS-вебхуков URL должен совпадать.

discovery

Обнаружение бэкендов по labels контейнеров Docker/Podman. Обнаруженные таргеты и правила дополняют статические; при конфликте имени таргета побеждает статика. Если обнаруженный сервис описывает тот же маршрут (host, path_prefix, methods), он попадает в общий пул с статикой.

ПолеТипПо умолчаниюСмысл и значенияПример
enabledboolfalseВключает discoverytrue
providerstringdockerdocker или podman (один клиент)docker
hoststringunix:///var/run/docker.sockSocket Docker/Podman APIunix:///var/run/docker.sock
api_versionstringv1.41Версия API; "" — запросы без версииv1.41
label_prefixstringgatewayПрефикс labelsgateway
service_name_labels[]stringcompose-сервис Docker и PodmanЦепочка фолбэков имени таргета["com.docker.compose.service"]
networkstring""Учитывать только контейнеры этой сети; пусто — всеproxy
debounceduration500msЗадержка перед ре-синком по событиям500ms
resync_intervalduration5mПериод полного ре-синка5m
default_timeoutduration30sТаймаут обнаруженного таргета по умолчанию30s
state_filestring/var/lib/api-gateway/discovery-state.jsonФайл последнего удачного результата (аварийный фолбэк); "" отключает персист/var/lib/api-gateway/discovery-state.json

Если discovery.enabled: true, статические targets и routing.rules можно не заполнять. При включённом discovery достаточно даже пустой секции discovery: { enabled: true } — остальные поля подставятся.

Аварийный фолбэк. Последний удачный результат discovery атомарно пишется в state_file и применяется при старте, поэтому маршруты переживают рестарт при недоступном Docker/Podman. Каталог state_file должен быть writable (смонтируйте volume). Ограничение: первый холодный старт без файла состояния и с недоступным Docker останется без обнаруженных маршрутов.

Labels

Маркер контейнера — gateway.enable: "true" (принимаются true/1/yes, регистр не важен).

LabelОбязателенСмыслПо умолчанию
gateway.enableдаОпт-ин контейнера
gateway.nameнетИмя таргетаservice_name_labels, иначе имя контейнера
gateway.portдаПортединственный exposed TCP-порт
gateway.schemeнетhttp или httpshttp
gateway.timeoutнетТаймаут запроса к целиdiscovery.default_timeout
gateway.healthнетHealth-путь или полный URL
gateway.weightнетВес таргета (0/отрицательный исключает)1

Роутеры задаются коротко (gateway.<field> → роутер default) или именованно (gateway.router.<id>.<field>): host, path_prefix, methods, strip_path, auth.required, auth.roles, auth.strip_token, rate_limit.rps, rate_limit.burst. Роутер без host и path_prefix пропускается.

Поведение и оговорки

  • Порядок middleware. Снаружи внутрь: инвалидация кеша permissions, Basic Auth, recovery, request id, tracing, метрики активных запросов, /metrics, глобальный лимит, статика, CORS-preflight, проксирование. Поэтому Basic Auth защищает и /metrics, и статику.
  • Рейт-лимиты. Per-route лимит — token bucket на IP клиента; IP берётся из последнего непустого значения X-Forwarded-For, иначе из RemoteAddr. Глобальный лимит — один на процесс. Per-route отдаёт 429 без Retry-After; глобальный — 429 с Retry-After: 1. Лимитеры по IP очищаются после часа простоя.
  • Access log. Выключен по умолчанию: каждая запись — это аллокации на горячем пути. Включайте для отладки, а не в проде под нагрузкой.
  • Тело запроса для аудита. Читается только для POST, PUT, PATCH, QUERY и ограничено max_request_body_size; в событие попадает только JSON-объект короче 64 KiB.
  • Метрики. /metrics в формате expvar; при metrics_allowed_ips доступ ограничен по IP.