Перейти к основному содержимому
Версия: 2.0 (WIP)

Настройка API Gateway

API Gateway (сервис frontend) в Памир построен на основе веб-сервера nginx с динамической генерацией конфигурации через docker-gen. Данный раздел описывает, как работает динамическая конфигурация и в каких случаях её необходимо дополнять или заменять статическим конфигом.

Динамическая конфигурация (на основе лейблов)

API Gateway отслеживает события Docker-демона и при каждом изменении состава запущенных контейнеров пересобирает файл /etc/nginx/conf.d/default.conf из шаблона. После пересборки конфигурации веб-сервер перезапускается автоматически.

Подключение сервиса

Чтобы сервис был доступен через API Gateway, в docker-compose.additional.yml на нём выставляются лейблы:

docker-compose.additional.yml
services:
my-service:
labels:
# Обязательно: включить проксирование
com.github.nginx-proxy.nginx-proxy.expose-service.enable: true
# Путь в URL (по умолчанию — имя сервиса из com.docker.compose.service)
com.github.nginx-proxy.nginx-proxy.expose-service.path: "my-service"
# Порт бэкенда (по умолчанию 80)
com.github.nginx-proxy.nginx-proxy.expose-service.port: "8080"
# Перезапись пути destination (опционально)
com.github.nginx-proxy.nginx-proxy.expose-service.dest: "/"

API Gateway сгенерирует location вида:

location ^~ /my-service/ {
proxy_pass http://my-service:8080/;
}

Аутентификация через auth_request

Если включена аутентификация (auth: true), каждый запрос к сервису сначала проверяется через внутренний location /_pamir_auth:

location = /_pamir_auth {
internal;
proxy_pass http://<auth-service>:80/v3/internal/user;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}

location ^~ /my-service/ {
auth_request /_pamir_auth;
proxy_pass http://my-service:8080/;
}

Auth-сервис отвечает кодом 2xx (allow) или 401/403 (deny). Контейнер с auth-сервисом обнаруживается автоматически по лейблу:

docker-compose.additional.yml
services:
my-service:
labels:
com.github.nginx-proxy.nginx-proxy.auth-service.enable: "true"

Если auth-сервис недоступен или имеет статус unhealthy — ни один location с auth: true в конфиге не создаётся.

Проброс заголовков аутентификации

При auth-headers: true API Gateway извлекает из ответа auth-сервиса заголовки с идентификатором пользователя и его ролью и пробрасывает их в сервис:

docker-compose.additional.yml
services:
my-service:
labels:
com.github.nginx-proxy.nginx-proxy.expose-service.auth-headers: "true"
auth_request_set $p_user  $upstream_http_x_user_login;
auth_request_set $p_roles $upstream_http_x_user_opensearch_role;
proxy_set_header X-User-Login $p_user;
proxy_set_header X-User-Opensearch-Role $p_roles;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Используется для сервисов, которым необходимо знать идентификатор пользователя — например, OpenSearch Dashboards (с последующей авторизацией в OpenSearch по заголовку, см. Ролевая модель OpenSearch).

Дополнительные заголовки

С помощью лейблов proxy-set-headers и add-headers можно добавить произвольные заголовки запроса и ответа (значения разделяются |):

docker-compose.additional.yml
services:
my-service:
labels:
# Заголовки запроса к бэкенду
com.github.nginx-proxy.nginx-proxy.expose-service.proxy-set-headers: >-
X-Custom-Header "value"|Authorization "Basic dXNlcjpwYXNz"
# Заголовки ответа клиенту
com.github.nginx-proxy.nginx-proxy.expose-service.add-headers: >-
Set-Cookie "session=abc;"

Сервисы, проксируемые из коробки

СервисПутьПортАутентификацияПроброс заголовков
opensearch-dashboards/os_dashboards/5601✓ (X-User-Login, X-User-Opensearch-Role)
rabbitmq/rabbitmq/15672— (инжект статичных credentials)
alertmanager/alertmanager/9093
flower/flower/5555
metrics-storage/metrics-storage/8428
metrics-agent/metrics-agent/8429
metrics-ruler/metrics-ruler/8880

Когда нужен статический конфиг

Динамическая конфигурация работает только с контейнерами, доступными на том же Docker-узле, что и API Gateway. В следующих сценариях необходим статический конфиг.

Структура статического конфига

Директива /_pamir_auth и все динамически создаваемые location-блоки находятся внутри server-блока с server_name backend (значение DEFAULT_HOST из .env). Этот же server-блок является default_server — он обрабатывает все входящие запросы.

При написании статической конфигурации нельзя размещать location-блоки в отдельном server { server_name _; } — такой блок никогда не получит запросы, поскольку default_server перехватывает их раньше. Кроме того, /_pamir_auth доступен только внутри того же server-блока, в котором определён.

Для корректного добавления статических locations используется механизм vhost.d: шаблон nginx включает файл /etc/nginx/vhost.d/backend (где backend — значение DEFAULT_HOST) внутри нужного server-блока. Это стандартный способ расширения конфигурации.

Статический конфиг разбивается на два файла:

ФайлКуда монтироватьЧто содержит
metrics-cluster.conf/etc/nginx/conf.d/metrics-cluster.confupstream-блоки (http-уровень)
backend/etc/nginx/vhost.d/backendlocation-блоки (внутри server-блока)
docker-compose.additional.yml
services:
frontend:
volumes:
- ./data/nginx/conf.d/metrics-cluster.conf:/etc/nginx/conf.d/metrics-cluster.conf:ro
- ./data/nginx/vhost/backend:/etc/nginx/vhost.d/backend:ro
примечание

Если в .env задано значение DEFAULT_HOST, отличное от backend, или настроен LETSENCRYPT_HOST=pamir.example.com, имя файла в vhost.d/ должно совпадать с этим именем (например, vhost.d/pamir.example.com).

Сценарий 1: сервисы Metrics на смежных серверах

Если компоненты Metrics (metrics-storage, metrics-agent, metrics-ruler) развёрнуты на отдельных Docker-узлах, docker-gen их не видит и не создаёт locations.

Шаг 1. Отключить сервисы Metrics на узле с API Gateway в docker-compose.additional.yml:

docker-compose.additional.yml
services:
metrics-storage:
profiles: !override [never]
metrics-agent:
profiles: !override [never]
metrics-ruler:
profiles: !override [never]
Сброс лейблов

Также возможно отключить построение динамической конфигурации для сервиса без его отключения сбросом лейблов:

docker-compose.additional.yml
services:
metrics-storage:
labels: !override []
metrics-agent:
labels: !override []
metrics-ruler:
labels: !override []

Шаг 2. Смонтировать оба файла статического конфига в контейнер через docker-compose.additional.yml:

docker-compose.additional.yml
services:
frontend:
volumes:
- ./data/nginx/conf.d/metrics-cluster.conf:/etc/nginx/conf.d/metrics-cluster.conf:ro
- ./data/nginx/vhost/backend:/etc/nginx/vhost.d/backend:ro

Шаг 3. Написать файл upstream-блоков data/nginx/conf.d/metrics-cluster.conf:

data/nginx/conf.d/metrics-cluster.conf
upstream metrics-storage {
server <IP-узла-storage-1>:8428;
server <IP-узла-storage-2>:8428 backup;
keepalive 4;
}

upstream metrics-agent {
server <IP-узла-agent-1>:8429;
server <IP-узла-agent-2>:8429 backup;
keepalive 4;
}

upstream metrics-ruler {
server <IP-узла-ruler>:8880;
keepalive 4;
}

Шаг 4. Написать файл location-блоков data/nginx/vhost/backend:

data/nginx/vhost/backend
location ^~ /metrics-storage/ {
auth_request /_pamir_auth;
proxy_pass http://metrics-storage/;
}

location ^~ /metrics-agent/ {
auth_request /_pamir_auth;
proxy_pass http://metrics-agent/;
}

location ^~ /metrics-ruler/ {
auth_request /_pamir_auth;
proxy_pass http://metrics-ruler/;
}

Сценарий 2: кластер OpenSearch на смежных серверах (с вынесенным Dashboards)

Если opensearch-dashboards развёрнут на отдельном Docker-узле:

Шаг 1. Отключить сервис opensearch-dashboards на узле с API Gateway:

docker-compose.additional.yml
services:
opensearch-dashboards:
profiles: !override [never]

Шаг 2. Смонтировать статические конфиги в контейнер:

docker-compose.additional.yml
services:
frontend:
volumes:
- ./data/nginx/conf.d/opensearch-cluster.conf:/etc/nginx/conf.d/opensearch-cluster.conf:ro
- ./data/nginx/vhost/backend:/etc/nginx/vhost.d/backend:ro

Шаг 3. Написать файл upstream-блоков data/nginx/conf.d/opensearch-cluster.conf:

data/nginx/conf.d/opensearch-cluster.conf
upstream opensearch-dashboards {
server <IP-узла-dashboards>:5601;
keepalive 4;
}

Шаг 4. Написать файл location-блоков data/nginx/vhost/backend:

data/nginx/vhost/backend
location ^~ /os_dashboards/ {
auth_request /_pamir_auth;

# Извлечь идентификационные заголовки из ответа auth-сервиса
auth_request_set $p_user $upstream_http_x_user_login;
auth_request_set $p_roles $upstream_http_x_user_opensearch_role;

# Пробросить в Dashboards для авторизации в OpenSearch
proxy_set_header X-User-Login $p_user;
proxy_set_header X-User-Opensearch-Role $p_roles;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://opensearch-dashboards/;
}

Сценарий 3: изменение учётных данных RabbitMQ

По умолчанию API Gateway инжектирует в запросы к RabbitMQ Management UI фиксированные credentials пользователя через заголовок Authorization (и соответствующую cookie для UI). Это реализовано через лейблы в docker-compose.deps.yml сервиса rabbitmq:

labels:
com.github.nginx-proxy.nginx-proxy.expose-service.proxy-set-headers: >-
Authorization "Basic <base64(login:password)>"
com.github.nginx-proxy.nginx-proxy.expose-service.add-headers: >-
Set-Cookie "m=<uid>:<base64(login:password)>;"

При изменении пароля пользователя в RabbitMQ необходимо обновить соответствующие Base64-значения в этих лейблах.

Способ 1 — через лейблы (рекомендуется для одного узла):

  1. Закодировать в base64 новые credentials:

    echo -n 'login:new_password' | base64
    # Пример вывода: bG9naW46bmV3X3Bhc3N3b3Jk
  2. URL-закодировать Base64-строку для cookie (заменить = на %3D):

    echo -n 'login:new_password' | base64 | python3 -c \
    "import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))"
    # Пример вывода: bG9naW46bmV3X3Bhc3N3b3Jk (если нет = в конце)
  3. Переопределить лейблы в docker-compose.additional.yml:

    docker-compose.additional.yml
    services:
    rabbitmq:
    labels:
    com.github.nginx-proxy.nginx-proxy.expose-service.proxy-set-headers: >-
    Authorization "Basic bG9naW46bmV3X3Bhc3N3b3Jk"
    com.github.nginx-proxy.nginx-proxy.expose-service.add-headers: >-
    Set-Cookie "m=<uid>:bG9naW46bmV3X3Bhc3N3b3Jk%3D%3D;"
  4. Перезапустить сервис:

    pamirctl start rabbitmq frontend

Способ 2 — через статический конфиг (для кластерного развёртывания):

Аналогично сценариям 1 и 2: сбросить лейблы (labels: !override []), определить upstream в conf.d/*.conf и разместить location с нужными заголовками proxy_set_header / add_header в vhost/backend.

Применение изменений

Любые изменения в docker-compose.additional.yml (лейблы, volumes) применяются командой:

pamirctl start frontend   # перезапустить только API Gateway
# или
pamirctl start # перезапустить все сервисы

Монтирование нового .conf-файла требует пересоздания контейнера API Gateway (pamirctl start frontend). Изменения уже смонтированного файла применяются перезагрузкой конфига без пересоздания:

pamirctl restart frontend

Связанные разделы