Quando se trata dos sites mais acessados da Internet, o NGINX e o NGINX Plus dominam o mercado. Na verdade, o NGINX é usado por mais sites entre o 1 milhão de sites mais acessados do mundo do que qualquer outro servidor web. Sua capacidade de lidar com mais de 1 milhão de conexões simultâneas em um único servidor impulsionou sua adoção por sites e aplicativos de “hiperescala”, como Airbnb, Netflix e Uber.
Embora o NGINX Plus seja mais conhecido como servidor web, proxy reverso HTTP e balanceador de carga, ele também é um controlador de entrega de aplicações (ADC) completo, com suporte a aplicações TCP e UDP. Sua arquitetura orientada a eventos e todos os outros atributos que contribuíram para seu sucesso em casos de uso de HTTP são igualmente aplicáveis à Internet das Coisas (IoT).
Neste artigo, mostramos como o NGINX Plus pode ser usado para balancear o tráfego MQTT. O MQTT foi publicado originalmente em 1999 para comunicação com campos de petróleo remotos. Ele foi atualizado em 2013 para casos de uso de IoT e, desde então, tornou-se o protocolo preferido para muitas implantações de IoT. Implantações de IoT em produção com milhões de dispositivos exigem alto desempenho e funcionalidades avançadas de um balanceador de carga. Nesta série de duas partes de posts no blog, abordaremos os seguintes casos de uso avançados.
- Balanceamento de carga do tráfego MQTT (este artigo)
- Criptografia e autenticação do tráfego MQTT
O ambiente de teste
Para explorar os recursos do NGINX Plus, usaremos um ambiente de teste simples que representa os principais componentes de um ambiente de IoT com um cluster de brokers MQTT. Os brokers MQTT nesse ambiente são instâncias do HiveMQ executadas em contêineres Docker.
Ambiente de teste para balanceamento de carga MQTT e persistência de sessão
O NGINX Plus atua como proxy reverso e balanceador de carga para o broker MQTT, escutando na porta MQTT padrão 1883. Isso fornece uma interface simples e consistente ao cliente, enquanto os nós MQTT de backend podem ser ampliados (e até mesmo colocados offline) sem afetar o cliente de forma alguma. Usamos a Mosquitto ferramenta de linha de comando como cliente, que representa os dispositivos de IoT no ambiente de teste.
Todos os casos de uso desta publicação e da Parte 2 utilizam este ambiente de teste, e todas as configurações se aplicam diretamente à arquitetura mostrada na figura. Para obter instruções completas sobre como criar o ambiente de teste, consulte o Apêndice 1.
Balanceamento de carga MQTT com verificações ativas de integridade para alta disponibilidade
Uma das principais funções de um balanceador de carga é proporcionar alta disponibilidade à aplicação, permitindo que servidores de backend sejam adicionados, removidos ou fiquem offline sem afetar o cliente. Para garantir isso de forma confiável, são essenciais verificações de integridade que sondam proativamente a disponibilidade de cada servidor de backend. Com verificações de integridade ativas, o NGINX Plus pode remover servidores com falha do grupo de balanceamento de carga antes que solicitações reais de clientes cheguem até eles.
A utilidade de uma verificação de integridade depende da precisão com que ela simula o tráfego real da aplicação e analisa a resposta. Verificações simples de disponibilidade do servidor, como um ping, não garantem que o serviço de backend esteja em execução. Verificações de portas TCP abertas não garantem que a própria aplicação esteja íntegra. Aqui, configuramos o balanceamento de carga básico para o ambiente de teste com uma verificação de integridade que garante que cada servidor de backend seja capaz de aceitar novas conexões MQTT.
Faremos alterações em dois arquivos de configuração.
No arquivo principal nginx.conf, incluímos o seguinte bloco stream e a diretiva include para que o NGINX Plus leia a configuração de balanceamento de carga TCP de um ou mais arquivos no subdiretório stream_conf.d, que fica no mesmo diretório que o arquivo nginx.conf. Fazemos isso em vez de incluir a configuração propriamente dita no arquivo nginx.conf.
Carregando exemplo de código…
Em seguida, no mesmo diretório que o nginx.conf, criamos o diretório stream_conf.d para armazenar nossos arquivos de configuração TCP e UDP. Observe que não usamos o diretório preexistente conf.d, pois, por padrão, ele é reservado para o contexto de configuração http, portanto, adicionar a configuração stream nesse diretório causará uma falha.
Carregando exemplo de código…
Em stream_mqtt_healthcheck.conf, primeiro definimos o formato do log de acesso para o tráfego MQTT (linhas 1–2). Ele é intencionalmente semelhante ao formato de log comum do HTTP, para que os logs resultantes possam ser importados para ferramentas de análise de logs.
Em seguida, definimos o grupo upstream chamado hive_mq (linhas 4–9), que contém três servidores MQTT. Em nosso ambiente de teste, cada um deles pode ser acessado no localhost por meio de uma porta exclusiva. A diretiva zone define uma quantidade de memória compartilhada entre todos os processos de trabalho do NGINX Plus para manter o estado do balanceamento de carga e as informações de integridade.
O bloco match (linhas 11–15) define a verificação de integridade usada para testar a disponibilidade dos servidores MQTT. A diretiva send é uma representação hexadecimal de um pacote MQTT CONNECT completo com um identificador de cliente (ClientId) igual a nginx health check. Ele é enviado a cada um dos servidores definidos no grupo upstream sempre que a verificação de integridade é acionada. A diretiva expect correspondente descreve a resposta que o servidor deve retornar para que o NGINX Plus o considere íntegro. Aqui, a string hexadecimal de 4 bytes 20 02 00 00 é um pacote MQTT CONNACK completo. O recebimento desse pacote demonstra que o servidor MQTT é capaz de aceitar novas conexões de clientes.
O bloco server (linhas 17–25) configura como o NGINX Plus lida com os clientes. O NGINX Plus escuta na porta MQTT padrão, 1883, e encaminha todo o tráfego para o grupo upstream hive_mq (linha 19). A diretiva health_check especifica que as verificações de integridade são realizadas no grupo upstream (na frequência padrão de cinco segundos) e que é usada a verificação definida pelo bloco mqtt_conn match.
Verificando a configuração
Para verificar se essa configuração básica está funcionando, podemos usar o cliente Mosquitto para publicar alguns dados de teste em nosso ambiente de testes.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001"Client thing001 sending CONNECT
Client thing001 received CONNACK
Client thing001 sending PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes))
Client thing001 sending DISCONNECT
$ tail --lines=1 /var/log/nginx/mqtt_access.log
192.168.91.1 [23/Mar/2017:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831A linha do log de acesso mostra que o NGINX Plus recebeu um total de 23 bytes e que 4 bytes foram enviados ao cliente (o pacote CONNACK). Também podemos ver que o nó MQTT node1 foi escolhido (porta 18831). Como mostram as linhas a seguir do log de acesso, quando repetimos o teste, o algoritmo padrão de balanceamento de carga Round Robin seleciona node1, node2 e node3 alternadamente.
$ tail --lines=4 /var/log/nginx/mqtt_access.log192.168.91.1 [23/Mar/2017:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831
192.168.91.1 [23/Mar/2017:11:42:26 +0000] TCP 200 23 4 127.0.0.1:18832
192.168.91.1 [23/Mar/2017:11:42:27 +0000] TCP 200 23 4 127.0.0.1:18833
192.168.91.1 [23/Mar/2017:11:42:28 +0000] TCP 200 23 4 127.0.0.1:18831Balanceamento de carga MQTT para persistência de sessão com o módulo JavaScript do NGINX
[Editor – O caso de uso a seguir é apenas um dos muitos disponíveis para o módulo JavaScript do NGINX. Para ver a lista completa, consulte Casos de uso do módulo JavaScript do NGINX.]
O código nesta seção foi atualizado da seguinte forma para refletir as alterações na implementação do NGINX JavaScript desde a publicação original do blog:
- Para usar o objeto de sessão refatorado (
s) para o módulo Stream, que foi introduzido no NGINX JavaScript 0.2.4. - Para usar a diretiva
js_import, que substitui a diretivajs_includeno NGINX Plus R23 e em versões posteriores. Para obter mais informações, consulte a documentação de referência do módulo NGINX JavaScript – a seção Exemplo de configuração mostra a sintaxe correta para os arquivos de configuração do NGINX e os arquivos JavaScript.
O balanceamento de carga Round Robin é um mecanismo eficaz para distribuir conexões de clientes entre um grupo de servidores. No entanto, há vários motivos pelos quais ele não é ideal para conexões MQTT.
Os servidores MQTT geralmente esperam uma conexão de longa duração entre o cliente e o servidor, e uma grande quantidade de informações de estado da sessão pode ser acumulada no servidor. Infelizmente, devido à natureza dos dispositivos de IoT e das redes IP que eles utilizam, as conexões podem ser interrompidas, obrigando alguns clientes a se reconectarem com frequência. O NGINX Plus pode usar seu algoritmo de balanceamento de carga Hash para selecionar um servidor MQTT com base no endereço IP do cliente. Basta adicionar hash $remote_addr; ao bloco upstream para habilitar a persistência de sessão, de modo que, sempre que uma nova conexão for recebida de um determinado endereço IP de cliente, o mesmo servidor MQTT seja selecionado.
Mas não podemos contar com o fato de que os dispositivos IoT se reconectarão usando o mesmo endereço IP, especialmente se estiverem usando redes celulares (por exemplo, GSM ou LTE). Para garantir que o mesmo cliente se reconecte ao mesmo servidor MQTT, devemos usar o identificador do cliente MQTT como chave do algoritmo de hash.
Persistência de sessão baseada no ClientId do MQTT
O ClientId do MQTT é um elemento obrigatório do pacote inicial CONNECT, o que significa que ele fica disponível para o NGINX Plus antes de o pacote ser encaminhado por proxy ao servidor upstream. Podemos usar o NGINX JavaScript para analisar o pacote CONNECT e extrair o ClientId como uma variável que pode então ser usada pela diretiva hash para implementar a persistência de sessão específica do MQTT.
O NGINX JavaScript é a linguagem de configuração programática “nativa do NGINX”. Trata-se de uma implementação exclusiva de JavaScript para NGINX e NGINX Plus, desenvolvida especificamente para casos de uso no lado do servidor e processamento por solicitação. Ela tem três características principais que a tornam adequada para implementar a persistência de sessão para MQTT:
- O NGINX JavaScript é totalmente integrado às fases de processamento do NGINX Plus, permitindo inspecionar os pacotes dos clientes antes que eles sejam distribuídos entre os servidores do grupo upstream.
- O NGINX JavaScript usa métodos JavaScript integrados para o processamento de strings e números, o que permite a análise eficiente de protocolos da Camada 4. A análise propriamente dita do pacote MQTT
CONNECTrequer menos de 20 linhas de código. - O NGINX JavaScript pode criar variáveis que ficam disponíveis para a configuração do NGINX Plus.
Para obter instruções sobre como habilitar o NGINX JavaScript, consulte o Apêndice 2.
Configuração do NGINX Plus para persistência de sessão
A configuração do NGINX Plus para este caso de uso continua relativamente simples. A configuração a seguir é uma versão modificada do exemplo apresentado em Balanceamento de carga com verificações ativas de integridade, com as verificações de integridade removidas para fins de concisão.
Carregando exemplo de código…
Começamos especificando o local do código JavaScript do NGINX com a diretiva js_import. A diretiva js_set instrui o NGINX Plus a chamar a função setClientId quando precisar avaliar a variável $mqtt_client_id. Adicionamos mais detalhes ao log de acesso acrescentando essa variável ao mqtt formato de log na linha 5.
Habilitamos a persistência de sessão na linha 12 com a diretiva hash, especificando $mqtt_client_id como a chave. Observe que usamos o parâmetro consistent para que, se um servidor upstream falhar, sua parcela do tráfego seja distribuída uniformemente entre os servidores restantes, sem afetar as sessões já estabelecidas nesses servidores. O hashing consistente é abordado em mais detalhes em nossa publicação no blog sobre particionamento de um cache da Web – os princípios e benefícios também se aplicam a este caso.
A diretiva js_preread (linha 18) especifica a função JavaScript do NGINX que é executada na fase de pré-leitura do processamento da solicitação. A fase de pré-leitura é acionada para cada pacote (em ambas as direções) e ocorre antes do proxy, para que o valor de $mqtt_client_id esteja disponível quando necessário no bloco upstream.
Código JavaScript do NGINX para persistência de sessão
Definimos o JavaScript para extrair o ClientId do MQTT no arquivo mqtt.js, que é carregado pela diretiva js_import no arquivo de configuração do NGINX Plus (stream_mqtt_session_persistence.conf).
Carregando exemplo de código…
A função principal, getClientId(), é declarada na linha 4. Ela recebe o objeto chamado s, que representa a sessão TCP atual. O objeto de sessão tem diversas propriedades, várias das quais são usadas nessa função.
As linhas 5 a 9 garantem que o pacote atual seja o primeiro recebido do cliente. As mensagens subsequentes do cliente e as respostas do servidor são ignoradas para que, após o estabelecimento da conexão, não haja sobrecarga adicional no fluxo de tráfego.
Carregando exemplo de código…
As linhas 10–24 examinam o cabeçalho MQTT para garantir que o pacote seja do tipo CONNECT e determinar onde começa a carga útil do MQTT.
Carregando exemplo de código…
As linhas 27–32 extraem o ClientId da carga útil, armazenando o valor na variável global JavaScript client_id_str. Em seguida, essa variável é exportada para a configuração do NGINX com a função setClientId (linhas 43–45).
Verificando a persistência da sessão
Agora podemos usar o cliente Mosquitto novamente para testar a persistência da sessão, enviando uma série de solicitações de publicação MQTT com três valores diferentes de ClientId (a opção -i).
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "bar"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "baz"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "bar"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "bar"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "baz"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "baz"A análise do log de acesso mostra que o ClientId foo sempre se conecta ao node1 (porta 18831), o ClientId bar sempre se conecta ao node2 (porta 18832) e o ClientId baz sempre se conecta ao node3 (porta 18833).
$ tail /var/log/nginx/mqtt_access.log192.168.91.1 [23/Mar/2017:12:24:24 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:28 +0000] TCP 200 23 4 127.0.0.1:18832 bar
192.168.91.1 [23/Mar/2017:12:24:32 +0000] TCP 200 23 4 127.0.0.1:18833 baz
192.168.91.1 [23/Mar/2017:12:24:35 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:37 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:38 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:42 +0000] TCP 200 23 4 127.0.0.1:18832 bar
192.168.91.1 [23/Mar/2017:12:24:44 +0000] TCP 200 23 4 127.0.0.1:18832 bar
192.168.91.1 [23/Mar/2017:12:24:47 +0000] TCP 200 23 4 127.0.0.1:18833 baz
192.168.91.1 [23/Mar/2017:12:24:48 +0000] TCP 200 23 4 127.0.0.1:18833 bazObserve que também temos a vantagem de o ClientId do MQTT aparecer nas linhas do log de acesso, independentemente de usarmos persistência de sessão ou qualquer outro algoritmo de balanceamento de carga.
Conclusão
Neste primeiro post de uma série de duas partes, demonstramos como o NGINX Plus usa verificações ativas de integridade para melhorar a disponibilidade e a confiabilidade de aplicações de IoT e como o NGINX JavaScript pode ampliar o NGINX Plus, oferecendo um recurso de balanceamento de carga na Camada 7, como a persistência de sessão para tráfego TCP. Na segunda parte, exploramos como o NGINX Plus pode tornar suas aplicações de IoT mais seguras ao realizar o descarregamento da terminação TLS e autenticar clientes.
Em conjunto com o NGINX JavaScript ou por conta própria, o alto desempenho e a eficiência inerentes do NGINX Plus fazem dele a solução ideal de balanceamento de carga por software para sua infraestrutura de IoT.
Para experimentar o NGINX JavaScript com o NGINX Plus, inicie sua avaliação gratuita de 30 dias ou entre em contato conosco para conversar sobre seus casos de uso.
Apêndices
Criando o ambiente de teste
Instalamos o ambiente de teste em uma máquina virtual para que ele fique isolado e possa ser reproduzido. No entanto, nada impede que você o instale em um servidor físico, do tipo “bare metal”.
Instalando o NGINX Plus
Consulte as instruções no Guia de Administração do NGINX Plus.
Instalando o HiveMQ
Qualquer servidor MQTT pode ser usado, mas este ambiente de teste é baseado no HiveMQ (baixe aqui). Neste exemplo, instalamos o HiveMQ em um único host, usando contêineres Docker para cada nó. As instruções a seguir foram adaptadas de Implantação do HiveMQ com Docker.
- Crie um Dockerfile para o HiveMQ no mesmo diretório que o arquivo hivemq.zip.
Carregando exemplo de código…
- No diretório que contém o arquivo hivemq.zip e o Dockerfile, crie a imagem do Docker.
$ docker build -t hivemq:latest .- Crie três nós do HiveMQ, cada um exposto em uma porta diferente.
$ docker run -p 18831:1883 -d --name node1 hivemq:latest
ff2c012c595a
$ docker run -p 18832:1883 -d --name node2 hivemq:latest
47992b1f4910
$ docker run -p 18833:1883 -d --name node3 hivemq:latest
17303b900b64- Verifique se todos os três nós do HiveMQ estão em execução. (No exemplo de saída a seguir, as colunas
COMMAND,CREATEDeSTATUSforam omitidas para facilitar a leitura.)
$ docker psCONTAINER ID IMAGE ... PORTS NAMES
17303b900b64 hivemq:latest ... 0.0.0.0:18833->1883/tcp node3
47992b1f4910 hivemq:latest ... 0.0.0.0:18832->1883/tcp node2
ff2c012c595a hivemq:latest ... 0.0.0.0:18831->1883/tcp node1Instalando o Mosquitto
O cliente de linha de comando do Mosquitto pode ser baixado no site do projeto. Usuários de Mac com o Homebrew instalado podem executar o seguinte comando.
$ brew install mosquittoTeste o cliente Mosquitto e a instalação do HiveMQ enviando uma mensagem simples de publicação para uma das imagens do Docker.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 18831
Client thing001 sending CONNECT
Client thing001 received CONNACK
Client thing001 sending PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes))
Client thing001 sending DISCONNECT[ngx_snippet name=’njs-enable-instructions’]
Sobre o autor

Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.
Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.
Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.