将 NGINX 部署为 API 网关,第 3 部分:发布 gRPC 服务

时间:2022-10-26 16:14:33

原文作者:Liam Crilly of F5

原文链接:将 NGINX 部署为 API 网关,第 3 部分:发布 gRPC 服务

转载来源:NGINX 官方网站


本文是“将 NGINX 开源版和 NGINX Plus 部署为 API 网关”系列博文的第三篇。

  • 第 1 部分详细说明了 NGINX 开源版和 NGINX Plus 作为基于 HTTP 的 RESTful API 的 API 网关的一些用例。
  • 第 2 部分对这些用例进行了扩展,探讨了一系列可用于保护生产环境中后端 API 服务的安全措施。
  • 本文解释了如何将 NGINX 开源版和 NGINX Plus 部署为 gRPC 服务的 API 网关。文章最初发布于 2018 年,随着 NGINX Plus Release 23 中引入了对原生 gRPC 健康检查协议的支持,特此更新,以方便大家充分利用 NGINX 开源版和 NGINX Plus。更新详情请参阅下文“实施健康检查”一节。

注:除非另有说明,否则本文中的所有信息都适用于 NGINX Plus 和 NGINX 开源版。为了便于阅读,当讨论内容同时适用于两个版本时,下文将它们统称为“NGINX”。

近年来,介绍微服务应用架构的概念和优势的文章非常多,其中以 NGINX 博文居首。微服务应用的核心是 HTTP API,本系列博文的前两篇文章使用了一个假设的 REST API 来说明 NGINX 如何处理此类应用。

尽管基于 JSON 消息格式的 REST API 在现代应用中非常流行,但它并不是所有场景或所有企业的理想之选。最常见的挑战是:

  • 文档标准 —— 如果没有良好的开发者制度或强制性的文档要求,最后很容易产生大量缺乏准确定义的 REST API。Open API 规范 已成为 REST API 的通用接口描述语言,但其使用却不是强制性的,需要开发组织内部的有力治理。
  • 事件和长连接—— REST API 以及它们使用 HTTP 传输,几乎决定了所有 API 调用都是请求 – 响应模式。当客户端应用需要服务器反馈消息时,使用 HTTP 长轮询和 WebSocket 等解决方案会有所帮助,但使用此类解决方案最终都需要构建一个单独、相邻的 API。
  • 复杂事务—— REST API 是围绕唯一资源的概念构建的,每个资源都由一个 URI 表示。当应用需要调用多个资源更新时,要么需要多个 API 调用(效率低下),要么必须在后端实现复杂的事务(与 REST 的核心原则相悖)。

近年来,gRPC 已发展成为构建分布式应用,尤其是微服务应用的替代方法。gRPC 最初由 Google 开发,并于 2015 年开源,现已成为云原生计算基金会的一个项目。值得注意的是,gRPC 使用 HTTP/2 作为传输机制,并利用其二进制数据格式和多路复用流功能。

gRPC 的主要优势包括:

  • 紧耦合的接口定义语言(协议缓冲区
  • 对流数据的原生支持(双向)
  • 高效的二进制数据格式
  • 自动生成多语言的代码,支持真正的多语言开发环境,且不会产生互操作性问题

定义 gRPC 网关

本系列博文的前两篇描述了如何通过单个入口点(例如 https://api.example.com)交付多个 API。当 NGINX 部署为 gRPC 网关时,gRPC 流量的默认行为和特征促使 NGINX 也要采用这种方法。虽然 NGINX 可以在同一主机名和端口上共享 HTTP 和 gRPC 流量,但最好还是将它们分开,主要有以下原因有:

  • REST 和 gRPC 应用的 API 客户端需要不同格式的错误响应
  • REST 和 gRPC 访问日志的相关字段有所不同
  • 因为 gRPC 不涉及旧版 Web 浏览器,因此它可以实施更严格的 TLS 策略

为了实现这种分离,我们需要修改gRPC 网关主配置文件 grpc_gateway.conf  server{} 模块,它位于 /etc/nginx/conf.d 目录。

log_format grpc_json escape=json '{"timestamp":"$time_iso8601",'
           '"client":"$remote_addr","uri":"$uri","http-status":$status,'
           '"grpc-status":$grpc_status,"upstream":"$upstream_addr"'
           '"rx-bytes":$request_length,"tx-bytes":$bytes_sent}';

map $upstream_trailer_grpc_status $grpc_status {
    default $upstream_trailer_grpc_status; # grpc-status is usually a trailer
    ''      $sent_http_grpc_status; # Else use the header, whatever its source
}

server {
    listen 50051 http2; # In production, comment out to disable plaintext port
    listen 443   http2 ssl;
    server_name  grpc.example.com;
    access_log   /var/log/nginx/grpc_log.json grpc_json;

    # TLS config
    ssl_certificate      /etc/ssl/certs/grpc.example.com.crt;
    ssl_certificate_key  /etc/ssl/private/grpc.example.com.key;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  5m;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_protocols        TLSv1.2 TLSv1.3;