MAATRIX / Блог / Apache APISIX в Docker Compose: готовый файл

Apache APISIX в Docker Compose: готовый файл

MAATRIX

Когда за одним доменом прячется десяток микросервисов, ручная правка nginx.conf под каждый новый маршрут быстро превращается в рутину: правка файла, nginx -t, reload, и молитва, что не уронили остальные апстримы. Apache APISIX снимает эту боль — маршруты, апстримы и плагины меняются через REST API на лету, без перезапуска и без риска синтаксической ошибки в общем конфиге. Ниже — рабочий docker-compose.yml для APISIX поверх etcd, с которым можно поднять шлюз за 10 минут и сразу начать добавлять маршруты и плагины.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что такое APISIX и когда он нужен вместо голого nginx

Apache APISIX построен поверх Nginx и LuaJIT (движок OpenResty), но конфигурация в нём не файл, а данные в etcd — распределённом key-value хранилище. Каждое изменение маршрута — это PUT-запрос к Admin API, который APISIX подхватывает без reload воркеров. Это принципиально другой режим работы по сравнению с классическим nginx, где любое изменение — это правка текстового конфига.

Смысл ставить APISIX появляется, когда у вас:

  • Много бэкендов за одним шлюзом, и маршруты меняются чаще, чем раз в неделю.
  • Нужна авторизация на уровне шлюза (JWT, API-ключи, Basic) без размазывания логики по каждому сервису.
  • Нужны rate-limiting, canary-релизы, A/B-трафик или трансформация запросов/ответов без правок кода бэкенда.
  • Хочется единую точку наблюдаемости (метрики Prometheus, трейсинг) для всех апстримов.

Если у вас один сайт и один бэкенд — обычный nginx или Caddy будет проще в обслуживании, и это честно стоит признать сразу. Ниже — где APISIX ставится в один ряд с альтернативами:

Критерийnginx (голый)Kong GatewayApache APISIX
Хранилище конфигафайлPostgreSQL/Cassandraetcd
Изменение маршрутаreload процессаREST API, без reloadREST API, без reload
Плагины из коробкинет (только модули на этапе сборки)~40+ (часть в Enterprise)80+, большинство open-source
Порог входанизкийсреднийсредний
Ресурсы под управляющую частьтяжёлая СУБДлёгкий etcd-кластер

Если уже присматривались к Kong — у нас есть отдельный разбор Kong Gateway в Docker Compose, стоит сравнить оба варианта на своей задаче перед выбором.

Готовый docker-compose.yml: APISIX + etcd

APISIX сам по себе — это стейтлес-прокси, вся конфигурация живёт в etcd. Значит, минимальный рабочий стек — это два контейнера: apisix и etcd. Структура каталога:

apisix-stack/
├── docker-compose.yml
└── apisix_conf/
    └── config.yaml

docker-compose.yml:

version: "3.8"

services:
  etcd:
    image: bitnami/etcd:3.5
    restart: unless-stopped
    environment:
      ETCD_ENABLE_V2: "true"
      ALLOW_NONE_AUTHENTICATION: "yes"
      ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"
      ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
    volumes:
      - etcd_data:/bitnami/etcd
    networks:
      - apisix-net

  apisix:
    image: apache/apisix:3.9-debian
    restart: unless-stopped
    depends_on:
      - etcd
    volumes:
      - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
    ports:
      - "9080:9080"   # HTTP-шлюз, сюда идёт клиентский трафик
      - "9443:9443"   # HTTPS-шлюз
      - "9180:9180"   # Admin API — наружу лучше не светить
      - "9092:9092"   # Control API / метрики для Prometheus
    networks:
      - apisix-net

networks:
  apisix-net:
    driver: bridge

volumes:
  etcd_data:

apisix_conf/config.yaml:

deployment:
  role: traditional
  role_traditional:
    config_provider: etcd
  etcd:
    host:
      - "http://etcd:2379"
    prefix: "/apisix"
    timeout: 30
  admin:
    admin_key:
      - name: admin
        key: "ЗАМЕНИТЕ_НА_СЛУЧАЙНУЮ_СТРОКУ_32+"
        role: admin
    allow_admin:
      - 127.0.0.1/32
      - 10.0.0.0/8

apisix:
  node_listen: 9080
  enable_control: true
  control:
    ip: "0.0.0.0"
    port: 9092

Два момента, на которых стоит остановиться отдельно:

  1. admin_key нельзя оставлять дефолтным. В официальных примерах APISIX часто фигурирует тестовый ключ вида edd1c9f0... — если скопировать его в продакшен как есть, любой, кто найдёт порт 9180 открытым, получит полный контроль над маршрутами. Сгенерируйте свой: openssl rand -hex 20.
  2. allow_admin ограничивает, откуда принимаются запросы к Admin API на уровне самого APISIX — но пробрасывать порт 9180 наружу через ports: в compose всё равно не стоит, даже с фильтром по IP внутри контейнера это лишняя поверхность атаки. В продакшене этот пункт из compose-файла лучше убрать и ходить в Admin API только изнутри docker-сети или через SSH-туннель.

Версии образов на Docker Hub обновляются регулярно — apache/apisix:3.9-debian актуален на момент написания, но перед разворачиванием стоит свериться с тегами на Docker Hub и взять свежий стабильный минорный релиз ветки 3.x.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Первый запуск и проверка Admin API

Поднимаем стек:

cd apisix-stack
docker compose up -d
docker compose ps

Оба контейнера должны быть в статусе running. Проверяем, что APISIX действительно поднялся и слушает Admin API (ключ — тот, что вы вписали в config.yaml):

curl http://127.0.0.1:9180/apisix/admin/routes \
  -H "X-API-KEY: ЗАМЕНИТЕ_НА_СЛУЧАЙНУЮ_СТРОКУ_32+"

Ответ должен быть JSON с пустым списком маршрутов — это нормально, мы их ещё не создавали. Теперь проверим сам шлюз на порту 9080:

curl -i http://127.0.0.1:9080/

Ожидаемый ответ — HTTP/1.1 404 Not Found с телом {"error_msg":"404 Route Not Found"}. Это правильное поведение: APISIX работает, но ни один маршрут ещё не привязан к запрошенному URI. Если вместо этого соединение обрывается или таймаутит — смотрите логи:

docker compose logs apisix --tail=50

Частая причина падения на этом шаге — синтаксическая ошибка в config.yaml (YAML чувствителен к отступам) или недоступность etcd на момент старта APISIX; depends_on в compose гарантирует только порядок запуска контейнеров, а не готовность etcd принимать соединения, поэтому при холодном старте иногда нужен один docker compose restart apisix.

Создание маршрута и upstream через Admin API

Маршрут в APISIX связывает URI на входе с апстримом (одним или несколькими бэкендами) на выходе. Добавим в тот же apisix-net контейнер с вашим приложением — либо укажем внешний хост. Пример на тестовом сервисе httpbin, добавленном в тот же compose-файл:

  httpbin:
    image: kennethreitz/httpbin
    networks:
      - apisix-net

Создаём маршрут через Admin API:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \
  -H "X-API-KEY: ЗАМЕНИТЕ_НА_СЛУЧАЙНУЮ_СТРОКУ_32+" \
  -X PUT -d '
{
  "uri": "/api/*",
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "httpbin:80": 1
    }
  }
}'

Проверяем, что запрос на /api/get реально доходит до бэкенда через шлюз:

curl http://127.0.0.1:9080/api/get

Если бэкендов несколько — просто добавляете узлы в nodes с весами:

"nodes": {
  "app1:8080": 2,
  "app2:8080": 1
}

APISIX распределит трафик по весам без правки чего-либо ещё — узел можно добавить или убрать тем же PUT-запросом, и изменение применится к следующему запросу, без reload.

Плагины: rate limiting, key-auth, JWT

Главная причина держать APISIX вместо голого nginx — библиотека плагинов, которые вешаются на маршрут декларативно. Три самых востребованных на практике:

Ограничение частоты запросов (защита от абьюза одного клиента):

curl http://127.0.0.1:9180/apisix/admin/routes/1 \
  -H "X-API-KEY: ЗАМЕНИТЕ_НА_СЛУЧАЙНУЮ_СТРОКУ_32+" \
  -X PATCH -d '
{
  "plugins": {
    "limit-req": {
      "rate": 10,
      "burst": 5,
      "key_type": "var",
      "key": "remote_addr"
    }
  }
}'

Это ограничит каждого клиента (по IP) 10 запросами в секунду с допустимым всплеском в 5.

Авторизация по API-ключу (закрыть маршрут от анонимных запросов):

{
  "plugins": {
    "key-auth": {}
  }
}

После включения плагина нужен потребитель (consumer) с ключом:

curl http://127.0.0.1:9180/apisix/admin/consumers \
  -H "X-API-KEY: ЗАМЕНИТЕ_НА_СЛУЧАЙНУЮ_СТРОКУ_32+" \
  -X PUT -d '
{
  "username": "mobile-app",
  "plugins": {
    "key-auth": {
      "key": "секретный-ключ-клиента"
    }
  }
}'

Дальше клиент передаёт заголовок apikey: секретный-ключ-клиента — без него APISIX ответит 401.

JWT-авторизация — вариант, когда токены выпускает ваш auth-сервис, а APISIX только проверяет подпись:

{
  "plugins": {
    "jwt-auth": {}
  }
}

Секрет для проверки подписи привязывается к consumer'у аналогично key-auth, только полем jwt-auth.key и jwt-auth.secret. Список всех доступных плагинов (CORS, IP-restriction, response-rewrite, proxy-cache и другие) отдаёт сам APISIX:

curl http://127.0.0.1:9180/apisix/admin/plugins/list \
  -H "X-API-KEY: ЗАМЕНИТЕ_НА_СЛУЧАЙНУЮ_СТРОКУ_32+"

Dashboard, SSL и что учитывать в продакшене

Дёргать curl для каждого маршрута удобно для автоматизации, но неудобно для повседневной работы. Официальный apisix-dashboard даёт веб-интерфейс поверх того же etcd — добавляется отдельным сервисом в тот же compose-файл со своим conf/conf.yaml, где указывается адрес etcd. Для одного администратора это оправданно; если маршрутов немного, можно обойтись и Admin API напрямую.

По TLS у APISIX два пути: терминировать HTTPS самим шлюзом через SSL-объекты Admin API (/apisix/admin/ssl), либо поставить перед APISIX отдельный терминатор — например, Traefik или nginx — и пускать на 9080 уже расшифрованный HTTP-трафик из внутренней сети. Второй вариант проще в связке с Let's Encrypt: у нас есть материал про Traefik как reverse proxy для Docker и отдельно про частые грабли с Let's Encrypt SSL на сервере — оба применимы к связке "терминатор перед APISIX".

Что ещё важно перед боевым запуском:

  • Бэкап etcd — это бэкап всей конфигурации шлюза. Потеря etcd без бэкапа означает восстановление всех маршрутов и плагинов вручную. Снимок делается штатной утилитой:
  docker compose exec etcd etcdctl snapshot save /bitnami/etcd/backup.db

Добавьте это в cron или в свой уже существующий бэкап-пайплайн.

  • Метрики. Порт 9092 (control API) отдаёт данные для плагина prometheus — включается на маршруте так же, как limit-req. Дальше метрики забирает Prometheus, а визуализация — по уже знакомой схеме из статьи про установку Grafana и Prometheus на Ubuntu 24.04.
  • Ресурсы. Сам APISIX (Nginx-воркеры) на лёгкой нагрузке ест немного, основной вес — у etcd на диске под WAL-логи. Ориентировочно для небольшой связки (до нескольких десятков маршрутов, невысокий RPS) хватает 1 CPU / 1–2 ГБ RAM на оба контейнера — но это именно ориентир, под конкретную нагрузку стоит смотреть docker stats в первые дни.
  • Admin API наружу — нет. Повторим отдельно: порт 9180 не должен быть доступен из интернета ни при каких обстоятельствах, это полный контроль над трафиком.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Чем APISIX принципиально отличается от nginx с Lua-модулями?

Тем, что конфигурация хранится не в файле, а в etcd, и меняется через API без reload воркеров. Технически APISIX и есть Nginx + LuaJIT, но с готовой моделью управления и плагинами из коробки.

Можно ли обойтись без etcd?

Есть режим standalone с YAML-файлом конфигурации вместо etcd, но тогда теряется главное преимущество — изменение маршрутов на лету через API. Для одного статического набора маршрутов это допустимо, для динамичной инфраструктуры — нет.

APISIX упадёт, если etcd недоступен?

Нет, APISIX кэширует последнюю известную конфигурацию локально и продолжит обслуживать уже созданные маршруты, но не сможет принять изменения через Admin API, пока etcd не вернётся.

Нужен ли кластер etcd из нескольких узлов?

Для одного сервера — нет, один инстанс etcd в docker-compose достаточен. Кластер из 3 и более узлов etcd имеет смысл только при нескольких инстансах APISIX за балансировщиком, где важна отказоустойчивость конфигурации.

Как перенести маршруты на другой сервер?

Снять снапшот etcd (etcdctl snapshot save), поднять новый etcd из снапшота (etcdctl snapshot restore) на целевом сервере и указать на него APISIX в config.yaml — маршруты и плагины переедут вместе с данными.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →