NGINX.COM
Web Server Load Balancing with NGINX Plus

在宣布推出 NGINX Plus R29 版本时,我们简要介绍了其对 MQTT 消息解析的全新原生支持。本文将在此基础上探讨如何配置 NGINX Plus,以优化企业环境中的 MQTT 部署。

 

什么是 MQTT?

MQTT 是指“消息队列遥测传输”,是一种常用的轻量级“发布-订阅”消息协议,非常适合通过互联网连接物联网(IoT)或机器对机器(M2M)设备和应用。MQTT 可在低带宽或低功耗环境中高效运行,因此是有着众多远程客户端的应用的理想之选,适用于多个行业,包括消费类电子产品、汽车、运输、制造及医疗行业。

 

NGINX Plus MQTT 消息处理

NGINX Plus R29 支持 MQTT 3.1.1MQTT 5.0。它充当了客户端和 Broker 之间的代理,可从核心系统卸载数据加密任务,简化可扩展性并降低计算成本。具体来说,NGINX Plus 能够解析并重写 MQTT CONNECT 消息的部分内容,从而实现以下功能:

  • MQTT Broker 负载均衡
  • 会话保持(将客户端重新连接到同一 Broker)
  • SSL/TLS 卸载
  • 客户端证书身份验证

MQTT 消息处理指令必须在 NGINX 配置文件的 stream 上下文中进行定义,并由 ngx_stream_mqtt_preread_module
ngx_stream_mqtt_filter_module 提供。

preread 模块先于 NGINX 的内部代理处理 MQTT 数据,允许根据解析的消息数据做出负载均衡和上游路由决策。

filter 模块支持重写已接收 CONNECT 消息中的 clientidusernamepassword 字段,并允许将这些字段设置为变量和复值,这可大幅扩展配置选项,支持 NGINX Plus 屏蔽敏感设备信息或插入 TLS 证书专有名称等数据。

MQTT 指令和变量

现有多个新指令和嵌入式变量可用于调整 NGINX 配置,以优化 MQTT 部署并满足您的特定需求。

preread 模块指令和嵌入式变量

  • mqtt_preread – 启用 MQTT 解析,以便从客户端设备发送的 CONNECT 消息中提取 clientidusername 字段。这些值通过嵌入式变量提供,可帮助将会话散列到负载均衡的上游服务器上(示例请见下文)。
  • $mqtt_preread_clientid – 表示设备发送的 MQTT 客户端标识符。
  • $mqtt_preread_username – 表示客户端发送的用于身份验证的用户名。

filter 模块指令

  • mqtt – 定义是否启用 MQTT 重写。
  • mqtt_buffers – 覆盖每个连接可分配的最大 MQTT 处理缓冲区数以及每个缓冲区的大小。默认情况下,NGINX 将限制每个连接 100 个缓冲区,每个缓冲区的长度为 1k。通常,这对于性能来说是最佳的,但在特殊情况下可能需要进行调整。例如,较长的 MQTT 消息需要较大的缓冲区。在短时间内为给定连接处理大量 MQTT 消息的系统可能会受益于缓冲区数量的增加。在大多数情况下,由于 NGINX 是从内部内存池构建缓冲区的,调整缓冲区参数对底层系统性能影响不大。
  • mqtt_rewrite_buffer_size – 指定用于构建已修改 MQTT 消息的缓冲区的大小。该指令已被弃用,并且自 NGINX Plus R30 起已过时。
  • mqtt_set_connect – 重写从客户端发送的 CONNECT 消息的参数。支持的参数包括:clientidusernamepassword

 

MQTT 示例

下面我们来更详细地了解一下使用 NGINX Plus 处理 MQTT 消息的优势以及相关的最佳实践。请注意,在下面的示例中,我们使用的是端口 1883 和端口 8883。端口 1883 是默认的不安全 MQTT 端口,而端口 8883 是默认的 SSL/TLS 加密端口。

MQTT Broker 负载均衡

MQTT 设备的短暂连接行为可能导致客户端 IP 意外更改,不利于将设备连接路由到正确的上游 Broker。在将设备连接从一个上游 Broker 移动到另一个上游 Broker 时,会带来高开销的 Broker间同步操作,进而增加延迟和成本。

通过解析 MQTT CONNECT 消息中的 clientid 字段,NGINX 可与上游服务 Broker 建立会话保持。其实现方法是,使用 clientid 作为哈希键来维护与后端 Broker 服务的连接。

在本例中,我们使用 clientid 作为令牌来代理 MQTT 设备数据,从而与三个上游 Broker 建立会话保持。我们使用一致的参数,以便在上游服务器发生故障时,将其流量平均分发给其余服务器,而不影响这些服务器上已建立的会话。

stream {
      mqtt_preread on; 
     
      upstream backend {
          zone tcp_mem 64k;
          hash $mqtt_preread_clientid consistent;
    
          server 10.0.0.7:1883; # upstream mqtt broker 1
          server 10.0.0.8:1883; # upstream mqtt broker 2
          server 10.0.0.9:1883; # upstream mqtt broker 3 
      }
    
      server {
          listen 1883;
          proxy_pass backend;
          proxy_connect_timeout 1s;
      }
  }

NGINX Plus 还能够解析 MQTT CONNECT 消息的 username 字段。更多详情,请参阅 ngx_stream_mqtt_preread_module 规范

SSL/TLS 卸载

加密设备通信是确保数据保密性和抵御中间人攻击的关键。不过,TLS 握手、加密和解密可能会给 MQTT Broker 带来资源负担。为了解决这一问题,NGINX Plus 可从 Broker(或 Broker 集群)卸载数据加密,从而简化安全规则,支持 Broker 全力处理设备消息。

在本例中,我们展示了如何使用 NGINX 将 TLS 加密的 MQTT 流量从设备代理到后端 Broker。ssl_session_cache 指令定义了一个 5 MB 缓存,该容量足以存储约 20,000 个 SSL 会话。根据 proxy_connect_timeout 指令的定义,NGINX 将尝试在五秒钟(超时期限)内访问代理的 Broker 。

stream {
      server {
          listen 8883 ssl;
          ssl_certificate /etc/nginx/certs/tls-cert.crt;
          ssl_certificate_key /etc/nginx/certs/tls-key.key;
          ssl_session_cache shared:SSL:5m;
          proxy_pass 10.0.0.8:1883;
          proxy_connect_timeout 5s;
      }
  } 

客户端 ID 替换

出于安全原因,您可以选择不在 MQTT Broker 的数据库中存储客户端可识别信息。例如,设备发送的 MQTT CONNECT 消息中可能会有序列号或其他敏感数据。只需将设备的标识符替换为从客户端接收的其他已知静态值,便可为尝试访问 NGINX Plus 代理的 Broker 的每台设备建立一个新的唯一标识符。

在本例中,我们从设备的客户端 SSL 证书中提取了一个唯一标识符,并用它来屏蔽其 MQTT 客户端 ID。客户端证书身份验证(双向 TLS)通过 ssl_verify_client 指令进行控制。当参数设置为 on 时,NGINX 可确保客户端证书由受信任证书颁发机构(CA)签发。受信任 CA 证书通过 ssl_client_certificate 指令进行定义。

stream {
      mqtt on; 
    
      server {
          listen 8883 ssl;
          ssl_certificate /etc/nginx/certs/tls-cert.crt;
          ssl_certificate_key /etc/nginx/certs/tls-key.key;
          ssl_client_certificate /etc/nginx/certs/client-ca.crt;
          ssl_session_cache shared:SSL:10m;
          ssl_verify_client on;
          proxy_pass 10.0.0.8:1883;
          proxy_connect_timeout 1s;
          
          mqtt_set_connect clientid $ssl_client_serial;
      }
  }

客户端证书作为身份验证凭证

对 MQTT 客户端进行身份验证的一种常见方法是使用客户端证书中存储的数据作为用户名。NGINX Plus 可以解析客户端证书并重写 MQTT username 字段,从后端 Broker 卸载这项任务。在下面的示例中,我们提取客户端证书的持有者专有名称(持有者 DN),并将其复制到 MQTT CONNECT 消息的 username 部分。

stream {
      mqtt on; 
     
      server {
          listen 8883 ssl;
          ssl_certificate /etc/nginx/certs/tls-cert.crt;
          ssl_certificate_key /etc/nginx/certs/tls-key.key;
          ssl_client_certificate /etc/nginx/certs/client-ca.crt;
          ssl_session_cache shared:SSL:10m;
          ssl_verify_client on;
          proxy_pass 10.0.0.8:1883;
          proxy_connect_timeout 1s;
          
          mqtt_set_connect username $ssl_client_s_dn;
      }
  } 

有关 NGINX Plus MQTT CONNECT 消息重写的完整规范,请参阅 ngx_stream_mqtt_filter_module 规范

 

立即行动

NGINX Plus 中 MQTT 的后续开发可能包括对其他 MQTT 消息类型进行解析,并能够更深入地解析 CONNECT 消息以启用以下功能:

  • 额外的身份验证和访问控制机制
  • 通过对频繁通信的客户端进行速率限制来保护 Broker
  • 消息遥测和连接指标

如果您是 NGINX Plus 新手,请申请 30 天免费试用版,立即开始使用 MQTT。同时欢迎您就所看重的特性给我们反馈,请在评论区告诉我们您的想法。

Hero image
《NGINX 完全指南》2024 年最新完整版


高性能负载均衡的进阶使用指南

关于作者

Michael Vernik

高级产品经理

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。