Восемь шагов для развертывания шлюза API NGINX Plus

Микросервисы Nginx API

Приветствую всех вОблако Tencent + сообщество, получить больше крупной технической практики Tencent по галантерее ~

Эта статья взята изОблако + Сообщество Бюро переводов,авторArrayZoneYour

Nginx часто является незаменимой частью создания микросервисов.Из этой статьи вы можете узнать, как использовать Nginx в качестве шлюза API.

API-интерфейсы HTTP лежат в основе современных архитектур приложений. Протокол HTTP позволяет разработчикам быстрее создавать приложения и упрощает обслуживание приложений. HTTP API предоставляет набор общих интерфейсов, что позволяет нам построить полное функциональное целое из базового микросервиса с помощью HTTP API в любом масштабе приложения. С помощью HTTP обычные веб-приложения также могут предоставлять высокопроизводительные API с высокой доступностью в огромном Интернете.

Если вы не понимаете важность шлюзов API для приложений микрослужб, вы можете обратиться кBuilding Microservices: Using an API Gateway

В качестве ведущего высокопроизводительного, легкого обратного прокси-сервера и нагрузки, Nginx Plus имеет расширенные возможности обработки HTTP, необходимые для обработки трафика API. Это делает NGINX PLUS идеальной платформой для построения шлюзов API. В этой статье мы будем использовать некоторые общие шлюзы API в качестве примеров, чтобы показать, как настроить NGINX PLUS, чтобы обработать их эффективным, масштабируемым и поддерживаемым образом. В конце у нас будет полная конфигурация, которую можно использовать в качестве базы для развертывания производства.

Примечание. Если не указано иное, все конфигурации в этой статье относятся как к NGINX, так и к NGINX Plus.

Знакомство с примером API (в качестве примера возьмем опыт складирования)

Основная функция API Gateway — предоставлять отдельные и согласованные точки входа для разных API, и его реализация не имеет ничего общего с реализацией и развертыванием серверной части. В реальных сценариях не все API часто реализуются в виде микросервисов. Наш шлюз API должен управлять обоими существующими API, API Big Mac (monoliths, шутливый термин для обозначения бегемота в отличие от микросервисов) и приложений, которые начинают частично переходить на микросервисы и т. д.

В этой статье в качестве примера мы используем API для управления запасами (WareHouse API). Мы используем пример кода конфигурации, чтобы проиллюстрировать различные варианты использования. Наш гипотетический API — это RESTful API, который принимает запросы JSON и генерирует данные JSON в ответ на запрос. Хотя в качестве примера в этой статье мы используем RESTful API, NGINX Plus не требует и не ограничивает использование JSON при развертывании в качестве шлюза API; сам NGINX Plus не знает схемы или формата данных, используемых API.

Склад API реализован как один из наборов независимых микросервисов и опубликован как единый API. Ресурсы инвентаризации и ценообразования в соответствии с ним интегрированы как отдельные услуги и развернуты на разных жителях. Из этого можно нарисовать следующую структуру пути API:

api
	└── warehouse
		├── inventory
    └── pricing

Например, если мы хотим получить склад инвентарной информации, нужно отправить клиенту по HTTPGETЗапрос в /API/СКЛАД/Инвентаризация

img

Организация конфигурационных файлов для NGINX

Преимущество использования NGINX Plus в качестве шлюза API заключается в том, что он может одновременно действовать как обратный прокси-сервер, балансировщик нагрузки и веб-сервер для существующего HTTP-трафика. Если NGINX Plus уже является частью вашего стека доставки приложений, вам не нужно развертывать с ним отдельный шлюз API. Однако поведение по умолчанию, ожидаемое шлюзом API, отличается от поведения по умолчанию, ожидаемого для трафика на основе браузера, поэтому нам необходимо отделить конфигурацию шлюза API от существующих (будущих) требуемых файлов конфигурации трафика на основе браузера.

Чтобы выполнить вышеуказанные требования, мы создали следующую структуру каталогов для файлов конфигурации для поддержки многоцелевых экземпляров NGINX Plus, что также облегчает автоматическую настройку и развертывание через конвейеры CI/CD.

etc/
	└── nginx/
  		  ├── api_conf.d/ ....................................... API配置的子目录
        │ └── warehouse_api.conf ...... Warehouse API 的定义及配置
        ├── api_backends.conf ..................... 后端服务配置 (upstreams)
        ├── api_gateway.conf ........................ API网关服务器的顶级配置
        ├── api_json_errors.conf ............ JSON格式的HTTP错误响应配置
        ├── conf.d/
        │ ├── ...
        │ └── existing_apps.conf
        └── nginx.conf

Каталог конфигурации Gateway API и имена файлов добавляются ** API _ ** префикс. Каждый из вышеуказанных каталогов и файлов, соответствующих различным функциям и функциям шлюза API, один за другим, мы подробно объясним ниже.

Определите конфигурацию верхнего уровня шлюза API.

NGINX считывает конфигурацию из основного конфигурационного файлаnginx.confНачинать. Чтобы прочитать конфигурацию шлюза API, нам нужноnginx.confсерединаhttpДобавьте в блок директиву для ссылки на файл, содержащий конфигурацию шлюза.api_gateway.conf(вероятно, около строки 28). Из содержимого файла мы видимnginx.confпо умолчанию изconf.dПодкаталог считывает конфигурацию HTTP на основе браузера. Эта статья будет широко использоватьсяincludeКоманда для повышения читаемости и внедрения некоторой автоматизации конфигурации.

    include /etc/nginx/api_gateway.conf; # 所有的API网关配置
    include /etc/nginx/conf.d/*.conf;    # 正常的web流量配置

api_gateway.confФайл определяет конфигурацию виртуального сервера, который предоставляет клиентам NGINX Plus в качестве шлюза API. Эта конфигурация предоставит доступ ко всем API, опубликованным шлюзом API, запись находится по адресуhttps://api.example.com/, зашифрованное с помощью протокола TLS. Обратите внимание, что используемый здесь файл конфигурации предназначен для HTTPS, а не для HTTP в открытом виде. Это означает, что мы по умолчанию и требуем, чтобы клиенты API знали правильную точку входа и подключались с использованием HTTPS.

log_format api_main '$remote_addr - $remote_user [$time_local] "$request"'
                    '$status $body_bytes_sent "$http_referer" "$http_user_agent"'
                    '"$http_x_forwarded_for" "$api_name"';
include api_backends.conf;
include api_keys.conf;
server {
    set $api_name -; # Start with an undefined API name, each API will update this value
    access_log /var/log/nginx/api_access.log api_main; # Each API may also log to a separate file
    listen 443 ssl;
    server_name api.example.com;
    # TLS 配置
    ssl_certificate      /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key  /etc/ssl/private/api.example.com.key;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  5m;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_protocols        TLSv1.1 TLSv1.2;
    # API 定义, 每个文件对应一个
    include api_conf.d/*.conf;
    # 错误响应
    error_page 404 = @400;         # 处理非法URI路径的请求
    proxy_intercept_errors on;     # 不将后端的错误消息发送给客户端
    include api_json_errors.conf;  # 定义返回给客户端的JSON响应数据
    default_type application/json; # 如果不指定 content-type 则默认为 JSON
}

Приведенная выше конфигурация является статической, и сведения о каждом отдельном API и серверной службе ответа отображаются черезincludeКоманды реализуются путем обращения к соответствующему файлу. Последние четыре строки приведенного выше файла отвечают за обработку вывода журнала по умолчанию, а также за обработку ошибок. Мы обсудим это отдельно в разделе «Ответ на ошибку» позже.

Единая служба или серверная часть API микросервиса

Некоторые API могут быть реализованы через одну бэкэнду, но обычно мы ожидаем более одной репутации для упругости или балансировки нагрузки. Через API Micro-Service мы можем определить отдельный бэкэнд для каждой службы, объединяя их, образующую полный API. В этой статье наша складирование API развернута в виде двух отдельных служб, каждый из которых имеет множество брюк.

upstream warehouse_inventory {
    zone inventory_service 64k;
    server 10.0.0.1:80;
    server 10.0.0.2:80;
    server 10.0.0.3:80;
}
upstream warehouse_pricing {
    zone pricing_service 64k;
    server 10.0.0.7:80;
    server 10.0.0.8:80;
    server 10.0.0.9:80;
}

Все серверные службы API для всех API, опубликованных API Gateway, доступны по адресуapi_backends.confопределяется в. Здесь мы использовали несколько пар IP-адрес-порт в каждом блоке, чтобы указать, где развернут код API, мы также можем использовать имена хостов вместо IP-адресов. Подписчики NGINX Plus также могут использовать динамическую балансировку нагрузки DNS для автоматического добавления новых бэкэндов в действующую конфигурацию.

Определение API хранилища

Эта часть конфигурации сначала определяет допустимые URI для API хранилища, а затем определяет общую стратегию обработки запросов API хранилища.

# API 定义
#
location /api/warehouse/inventory {
    set $upstream warehouse_inventory;
    rewrite ^ /_warehouse last;
}
location /api/warehouse/pricing {
    set $upstream warehouse_pricing;
    rewrite ^ /_warehouse last;
}
# 策略
#
location = /_warehouse {
    internal;
    set $api_name "Warehouse";
    # 在这里配置相应的策略 (认证, 限速, 日志记录, ...)
    proxy_pass http://$upstream$request_uri;
}

API хранилища определяется с помощью ряда блоков конфигурации. NGINX Plus имеет гибкую и эффективную систему, которая позволяет ему сопоставлять запрошенные URI с соответствующими блоками конфигурации. Как правило, запросы будут сопоставляться с определенными префиксами пути.locationПорядок инструкции не важен. В приведенной выше конфигурации мы определяем два префикса пути в третьей и восьмой строках. В каждой конфигурации,$upstreamПеременные задаются для представления серверных API-сервисов для инвентаризации и ценообразования соответственно.

Цель этой конфигурации — отделить определение API от логики доставки API. Для этого мы свели к минимуму содержание конфигурации в разделе определения API. Как только мы определили соответствующую восходящую группу для каждого местоположения, мы можем использовать директивы для поиска соответствующей политики API.

img

rewriteРезультатом директивы является то, что NGINX Plus ищет блок местоположения, соответствующий URI, начинающемуся с **/_warehouse**. Модификатор = используется в приведенной выше конфигурации для точного сопоставления, что повышает скорость обработки.

На этом этапе содержимое нашего блока политики очень простое. itternal в конфигурации означает, что клиенты не могут делать запросы к нему напрямую.$api_nameПеременная была переопределена, чтобы соответствовать имени API, чтобы оно правильно отображалось в файле журнала. Окончательный запрос передается в вышестоящую группу, указанную в разделе определения API, с помощью переменной $request_uri (содержащей неизмененный исходный URI запроса).

Свободное или точное определение API

Существует два способа определения API — свободный или точный. Наиболее подходящий метод для каждого API зависит от требований безопасности API и от того, должна ли серверная служба обрабатывать недопустимые URI.

существуетwarehouse_api.simple.confВ документации мы используем свободный способ определения Warehouse API. Это означает, что любой URI с префиксом, отвечающим требованиям, будет проксироваться соответствующей серверной службе, т. е. запросы API для следующих URI будут обрабатываться как действительные URI:

  • /api/warehouse/inventory
  • /api/warehouse/inventory/
  • /api/warehouse/inventory/foo
  • /api/warehouse/inventoryfoo
  • /api/warehouse/inventoryfoo/bar/

Если нам нужно только рассмотреть возможность передачи каждого запроса правильной серверной службе, то свободное определение может обеспечить самую быструю обработку и наиболее компактную конфигурацию. И наоборот, используя метод точного определения, можно понять полное пространство URI API, явно определяя путь URI для каждого доступного ресурса API. Следующая конфигурация Warehouse API реализует точное соответствие для каждого URI, используя комбинацию точных совпадений (=) и регулярных выражений (~).

location = /api/warehouse/inventory { # Complete inventory
    set $upstream inventory_service;
    rewrite ^ /_warehouse last;
}
location ~ ^/api/warehouse/inventory/shelf/[^/]*$ { # Shelf inventory
    set $upstream inventory_service;
    rewrite ^ /_warehouse last;
}
location ~ ^/api/warehouse/inventory/shelf/[^/]*/box/[^/]*$ { # Box on shelf
    set $upstream inventory_service;
    rewrite ^ /_warehouse last;
}
location ~ ^/api/warehouse/pricing/[^/]*$ { # Price for specific item
    set $upstream pricing_service;
    rewrite ^ /_warehouse last;
}

Приведенная выше конфигурация немного многословна, но она более точно описывает ресурсы, реализуемые серверной службой. Это защищает серверные службы от злонамеренных пользовательских запросов, но добавляет дополнительную нагрузку на обработку сопоставления регулярных выражений. В этой конфигурации NGINX Plus примет некоторые URI, а остальные будут отклонены как недействительные:

img
пример совпадения

Использование точных определений API может использовать существующие форматы документов API для управления конфигурацией шлюза API, автоматизируя определения API NGINX Plus в соответствии со спецификацией OpenAPI (ранее известной как Swagger). В этой статье приведены соответствующиепример сценария.

Переписать запрос клиента

По мере развития API иногда возникают непредвиденные ситуации или изменения, требующие обновления клиентских запросов. Типичным примером является переименование или удаление исходного ресурса API. В отличие от веб-браузеров, шлюзы API не могут отправлять перенаправления клиентам с новыми именами для API. К счастью, мы можем исправить это, переписав запрос клиента.

В приведенном ниже коде мы видим, где в третьей строкеpricingслужил раньше какinventoryЧасть услуги реализована. Итак, теперь мы используемrewriteинструкция по конвертации старогоpricingЗапрос ресурса для переключения на новыйpricingзапрос ресурсов.

# 重写规则
#
rewrite ^/api/warehouse/inventory/item/price/(.*)  /api/warehouse/pricing/$1;
# API 定义
#
location /api/warehouse/inventory {
    set $upstream inventory_service;
    rewrite ^(.*)$ /_warehouse$1 last;
}
location /api/warehouse/pricing {
    set $upstream pricing_service;
    rewrite ^(.*) /_warehouse$1 last;
}
# 处理策略
#
location /_warehouse {
    internal;
    set $api_name "Warehouse";
    # 在这里配置相应的策略 (认证, 限速, 日志记录, ...)
    rewrite ^/_warehouse/(.*)$ /$1 break; # 移除 /_warehouse 前缀
    proxy_pass http://$upstream;          # 代理重写后的URI
}

Но использование переписанного URI также означает, что мы больше не можем использовать предпоследнюю строку приведенного выше кода при обработке прокси-запроса.$request_uriпеременная (какwarehouse_api_simple.confстрока 21 делает то же самое). Поэтому нам нужно использовать разные позиции в строке 9 и строке 14 приведенного выше кода.rewriteПосле команды URI передается в блок кода раздела политики.

img

ответ об ошибке

Одной из ключевых развесений между потоком HTTP API и клиентом на основе браузера передается неправильно. Когда мы настраиваем NGINX PLUS в качестве шлюза API, мы настроим это возвращает сообщение об ошибке в порядке, который наилучшим образом подходит для API клиента.

  # 错误响应
    error_page 404 = @400;         # 处理非法URI路径的请求
    proxy_intercept_errors on;     # 不将后端的错误消息发送给客户端
    include api_json_errors.conf;  # 定义返回给客户端的JSON响应数据
    default_type application/json; # 如果不指定 content-type 则默认为 JSON

В приведенном выше коде показана наша конфигурация для ответов об ошибках в шлюзе API верхнего уровня.

Из-за конфигурации во второй строке выше, когда запрос не может соответствовать какому-либо определению API, мы вернем ошибку, указанную в этой строке, вместо стандартного ответа NGINX Plus для клиента. Это необязательное поведение требует, чтобы клиенты выполняли запросы в соответствии со спецификацией документации API, что предотвращает обнаружение неавторизованными пользователями структуры URI API через шлюз API.

proxy_interceprt_errorsОтносится к сообщению об ошибке, сгенерированному серверной службой. Исходное сообщение об ошибке может содержать неверную информацию о стеке или другую и другую конфиденциальную информацию, которую мы не хотим, чтобы клиент видел. После включения этой конфигурации мы нормализуем сообщения об ошибках перед их отправкой клиентам, тем самым дополнительно повышая уровень безопасности сообщений.

На следующей строке мы проходимincludeДиректива вводит полный список ответов об ошибках, первые несколько строк которого показаны ниже. Если вы хотите использовать какой-либо другой формат ошибки, кроме JSON, вы можете изменить последнюю строкуdefault_typeуказанное содержание. Вы также можете использовать в каждом блоке политики APIincludeДиректива по импорту списка для переопределения ошибки по умолчанию.

error_page 400 = @400;
location @400 { return 400 '{"status":400,"message":"Bad request"}\n'; }
error_page 401 = @401;
location @401 { return 401 '{"status":401,"message":"Unauthorized"}\n'; }
error_page 403 = @403;
location @403 { return 403 '{"status":403,"message":"Forbidden"}\n'; }
error_page 404 = @404;
location @404 { return 404 '{"status":404,"message":"Resource not found"}\n'; }

После завершения настройки клиент получит следующий ответ при отправке недопустимого запроса URI:

$ curl -i https://api.example.com/foo
HTTP/1.1 400 Bad Request
Server: nginx/1.13.10
Content-Type: application/json
Content-Length: 39
Connection: keep-alive
{"status":400,"message":"Bad request"}

Интеграция аутентификации

При публикации API мы обычно защищаем их аутентификацией. NGINX Plus предоставляет несколько способов защиты API и аутентификации клиентов API. Для получения конкретной информации обратитесь к официальной документации NGINX.Списки контроля доступа на основе IP-адресов,digital certificate authenticationа такжеHTTP Basic authenticationчасть.在本文中,我们将专注于适用于API的认证方法。

Аутентификация по ключу API

Ключ API — это общий ключ, доступ к содержимому которого есть как у клиента, так и у шлюза API. Суть его заключается в длинном и сложном пароле, который обычно предоставляется клиентам API в качестве долгосрочных учетных данных. Создать ключ API очень просто, вам просто нужно закодировать случайное число, как показано ниже.

$ openssl rand -base64 18 7B5zIqmRGXmrJTFmKa99vcit

Теперь вернемся к файлу конфигурации шлюза API верхнего уровня.api_gateway.conf, вы можете видеть, что в строке 6 мыincludeимяapi_key.confфайл, который содержит информацию о ключе API для каждого клиента API и соответствующее имя клиента или соответствующее описание.

map $http_apikey $api_client_name {
    default "";
    "7B5zIqmRGXmrJTFmKa99vcit" "client_one";
    "QzVV6y1EmQFbbxOfRCwyJs35" "client_two";
    "mGcjH8Fv6U9y3BVF9H3Ypb9T" "client_six";
}

Вы можете видеть, что ключ API определен в блоке кода, показанном выше. один из нихmapДиректива принимает два аргумента. Первый параметр определяет, где искать ключ API, здесь мы получаем заголовок HTTP-запроса клиента поapikeyкак переменная$http_api_keyперенимать. Второй параметр создает новую переменную$api_client_nameИ сопоставьте его с первым параметром, API-ключом пира.

На этом этапе, если клиент предоставляет ключ API7B5zIqmRGXmrJTFmKa99vcitда, переменная$api_client_nameбудет установлено наclient_one. Эту переменную можно использовать для проверки аутентифицированных клиентов и для дальнейшего аудита журналов.

можно увидетьmapФормат блока очень прост, что позволяет нам легко конвертироватьapi_keys.confГенерация интегрирована в автоматизированные рабочие процессы. После этого логика проверки ключа API может быть завершена в блоке политик API.

# 策略块
#
location = /_warehouse {
    internal;
    set $api_name "Warehouse";  
    if ($http_apikey = "") {
        return 401; # Unauthorized (please authenticate)
    }
    if ($api_client_name = "") {
        return 403; # Forbidden (invalid API key)
    }
    proxy_pass http://$upstream$request_uri;
}

Мы хотим, чтобы клиенты отправляли запросы на все указанные в их HTTP-заголовкахapikeyКонтент — это ключ API, которым владеет клиент. Если информация о заголовке HTTP отсутствует или отсутствуетapikeyМы вернемся к клиенту401Код состояния требует завершения аутентификации. Если ключ API, отправленный клиентом, не существует вapi_keys.confсреди,$api_client_nameбудет установлено значение по умолчанию для пустой строки, после чего мы вернемся403Код состояния, чтобы сообщить клиенту, что его аутентификация недействительна.

После завершения вышеуказанной конфигурации API склада теперь может поддерживать проверку ключа API.

$ curl https://api.example.com/api/warehouse/pricing/item001
{"status":401,"message":"Unauthorized"}
$ curl -H "apikey: thisIsInvalid" https://api.example.com/api/warehouse/pricing/item001
{"status":403,"message":"Forbidden"}
$ curl -H "apikey: 7B5zIqmRGXmrJTFmKa99vcit" https://api.example.com/api/warehouse/pricing/item001
{"sku":"item001","price":179.99}

JWT аутентификация

Теперь JSON Web Token (JWT) широко используется для аутентификации API. Обратите внимание, однако, что встроенная поддержка JWT — это функция NGINX Plus. Узнайте, как включить поддержку JWTAuthenticating API Clients with JWT and NGINX Plus.

Суммировать

Эта статья является первой статьей о развертывании Nignx Plus в качестве сетевого отношения API. Все файлы, использованные в этой статье, могут находиться в нашемGitHub Gist repoЗагрузить или просмотреть. В этой серии мы рассмотрим более продвинутые случаи использования для защиты от Backend Services от вредоносных или незаконно управляемых пользователей.


вопросы и ответы

Как написать URL Rewrite с nginx?

Связанное Чтение

Как быстро построить безопасную микросервисную архитектуру с помощью Nginx

Принципиальный анализ Nginx и краткое описание конфигурации

Создание микросервисов с помощью шлюза API


Эта статья была разрешена автором для публикации сообщества Tencent Cloud +, исходная ссылка: https://cloud.tencent.com/developer/article/1149103?fromSource=waitui.

Приветствую всех вОблако Tencent + сообществоИли обратите внимание на общедоступную учетную запись WeChat сообщества Yunjia (QcloudCommunity) и как можно скорее получите больше массовой технической практики галантерейных товаров ~