Quando se trata dos sites mais movimentados na Internet, o NGINX e o NGINX Plus dominam o mercado. Na verdade, o NGINX alimenta mais dos 1 milhão de sites mais movimentados 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 um servidor web, proxy reverso HTTP e balanceador de carga, ele também é um controlador de entrega de aplicativos (ADC) completo com suporte para aplicativos TCP e UDP. Sua arquitetura orientada a eventos e todos os outros atributos que a tornaram bem-sucedida 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 a carga do tráfego MQTT . O MQTT foi publicado originalmente em 1999 para comunicação com campos de petróleo remotos. Ele foi atualizado para casos de uso de IoT em 2013 e desde então se tornou o protocolo escolhido para muitas implantações de IoT. Implantações de IoT de produção com milhões de dispositivos exigem alto desempenho e funcionalidade avançada de um balanceador de carga e, nesta série de postagens de blog em duas partes, discutiremos os seguintes casos de uso avançados.
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 corretores MQTT. Os brokers MQTT neste ambiente são instâncias do HiveMQ executadas dentro de contêineres Docker.
O NGINX Plus atua como um proxy reverso e balanceador de carga para o broker MQTT, escutando na porta MQTT padrão 1883. Isso fornece uma interface simples e consistente para o cliente, enquanto os nós MQTT de backend podem ser dimensionados (e até mesmo colocados offline) sem afetar o cliente de forma alguma. Usamos a ferramenta de linha de comando Mosquitto como cliente, que representa os dispositivos IoT no ambiente de teste.
Todos os casos de uso neste post e na Parte 2 usam este ambiente de teste, e todas as configurações se aplicam diretamente à arquitetura mostrada na figura. Para obter instruções completas sobre como construir o ambiente de teste, consulte o Apêndice 1 .
Uma função principal de um balanceador de carga é fornecer alta disponibilidade para o aplicativo, para que servidores de back-end possam ser adicionados, removidos ou ficar offline sem afetar o cliente. Inerente a fazer isso de forma confiável estão as verificações de integridade que sondam proativamente cada um dos servidores de backend para verificar sua disponibilidade. Com verificações de integridade ativas, o NGINX Plus pode remover servidores com falha do grupo de balanceamento de carga antes que as solicitações reais do cliente os alcancem.
A utilidade de uma verificação de integridade depende da precisão com que ela simula o tráfego real do aplicativo e analisa a resposta. Verificações simples de atividade do servidor, como um ping, não garantem que o serviço de backend esteja em execução. As verificações de porta TCP aberta não garantem que o aplicativo em si esteja íntegro. 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.
Estamos fazendo alterações em dois arquivos de configuração.
No arquivo principal nginx.conf , incluímos o seguinte bloco de fluxo
e a diretiva include
para que o NGINX Plus leia a configuração para balanceamento de carga TCP de um ou mais arquivos no subdiretório stream_conf.d , que está no mesmo diretório que nginx.conf . Fazemos isso em vez de incluir a configuração real em nginx.conf .
Então, no mesmo diretório que nginx.conf, criamos o diretório stream_conf.d para conter nossos arquivos de configuração TCP e UDP. Observe que não usamos o diretório conf.d pré-existente porque, por padrão, ele é reservado para o contexto de configuração http
e, portanto, adicionar a configuração de fluxo
lá falhará.
Em stream_mqtt_healthcheck.conf, primeiro definimos o formato do log de acesso para o tráfego MQTT (linhas 1–2). Isso é deliberadamente semelhante ao formato de log comum HTTP para que os logs resultantes possam ser importados para ferramentas de análise de log.
Em seguida, definimos o grupo upstream
chamado hive_mq (linhas 4 a 9), que contém três servidores MQTT. Em nosso ambiente de teste, cada um deles é acessível no host local com um número de porta exclusivo. A diretiva de zona
define uma quantidade de memória que é compartilhada entre todos os processos de trabalho do NGINX Plus para manter o estado de balanceamento de carga e as informações de integridade.
O bloco de correspondência
(linhas 11 a 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) de verificação
de integridade
nginx
. Isso é enviado para cada um dos servidores definidos no grupo upstream sempre que a verificação de integridade é disparada. A diretiva expect
correspondente descreve a resposta que o servidor deve retornar para que o NGINX Plus o considere íntegro. Aqui, a sequência hexadecimal de 4 bytes20
02
00
00
é um pacote MQTT CONNACK
completo. O recebimento deste pacote demonstra que o servidor MQTT é capaz de receber novas conexões de clientes.
O bloco do servidor
(linhas 17 a 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 executadas no grupo upstream (na frequência padrão de cinco segundos) e que a verificação definida pelo bloco de correspondência
mqtt_conn é usada.
Para testar se essa configuração básica está funcionando, podemos usar o cliente Mosquitto para publicar alguns dados de teste em nosso ambiente de teste.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" Cliente thing001 enviando CONNECT Cliente thing001 recebeu CONNACK Cliente thing001 enviando PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes)) Cliente thing001 enviando 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:18831
A linha do log de acesso mostra que o NGINX Plus recebeu um total de 23 bytes e 4 bytes foram enviados ao cliente (o pacote CONNACK
). Também podemos ver que o nó MQTT1 foi escolhido (porta 18831). Conforme mostrado nas linhas a seguir do log de acesso, quando repetimos o teste, o algoritmo de balanceamento de carga Round Robin padrão seleciona node1 , node2 e node3 por vez.
$ tail --lines=4 /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: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:18831
[ Editor – O caso de uso a seguir é apenas um entre muitos para o módulo NGINX JavaScript. Para uma lista completa, consulte Casos de uso para o módulo JavaScript NGINX .]
O código nesta seção é atualizado da seguinte forma para refletir as alterações na implementação do NGINX JavaScript desde que o blog foi publicado originalmente:
s
) para o módulo Stream, que foi introduzido no NGINX JavaScript 0.2.4 .js_import
, que substitui a diretiva js_include
no NGINX Plus R23 e 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 a 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 estado de sessão pode ser acumulada no servidor. Infelizmente, a natureza dos dispositivos IoT e as redes IP que eles usam fazem com que as conexões sejam interrompidas, forçando 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 o hash
$remote_addr;
ao bloco upstream para habilitar a persistência da sessão , de modo que cada vez que uma nova conexão chega de um determinado endereço IP do cliente, o mesmo servidor MQTT é selecionado.
Mas não podemos confiar que dispositivos IoT se reconectem a partir do 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 a chave para o algoritmo de hash.
O MQTT ClientId é um elemento obrigatório do pacote CONNECT
inicial, o que significa que ele está disponível para o NGINX Plus antes que o pacote seja enviado por proxy para o servidor upstream. Podemos usar o JavaScript NGINX 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.
NGINX JavaScript é a linguagem de configuração programática “NGINX‑nativa”. É uma implementação JavaScript exclusiva para NGINX e NGINX Plus, projetada especificamente para casos de uso do lado do servidor e processamento por solicitação. Ele tem três características principais que o tornam adequado para uma implementação de persistência de sessão para MQTT:
CONNECT
requer menos de 20 linhas de código.Para obter instruções sobre como habilitar o JavaScript do NGINX, consulte o Apêndice 2 .
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 em Balanceamento de carga com verificações de integridade ativas , com verificações de integridade removidas por questões de brevidade.
Começamos especificando a localização do código JavaScript do NGINX com a diretiva js_import
. A diretiva js_set
informa ao NGINX Plus para chamar a função setClientId
quando precisar avaliar a variável $mqtt_client_id
. Adicionamos mais detalhes ao log de acesso anexando esta variável ao formato de log mqtt 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 consistente
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 hash consistente é discutido mais detalhadamente em nossa postagem do blog sobre como fragmentar um cache da web – os princípios e benefícios se aplicam igualmente aqui.
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
.
Definimos o JavaScript para extrair o MQTT ClientId no arquivo mqtt.js , que é carregado pela diretiva js_import
no arquivo de configuração do NGINX Plus ( stream_mqtt_session_persistence.conf ).
A função primária, getClientId()
, é declarada na linha 4. É passado o objeto chamado s
, que representa a sessão TCP atual. O objeto de sessão tem inúmeras propriedades , várias das quais são usadas nesta função.
As linhas 5 a 9 garantem que o pacote atual seja o primeiro a ser recebido do cliente. Mensagens subsequentes do cliente e respostas do servidor são ignoradas para que, uma vez estabelecida uma conexão, não haja sobrecarga adicional no fluxo de tráfego.
As linhas 10 a 24 examinam o cabeçalho MQTT para garantir que o pacote seja do tipo CONNECT
e para determinar onde a carga útil MQTT começa.
As linhas 27 a 32 extraem o ClientId da carga útil, armazenando o valor na variável global JavaScript client_id_str
. Essa variável é então exportada para a configuração do NGINX com a função setClientId
(linhas 43–45).
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 ClientId diferentes (a opção -i
).
$ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "bar" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "baz" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "tópico/ teste" -m "teste123" -i "bar" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "bar" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "baz" $ mosquitto_pub -h mqtt.example.com -t "tópico/teste" -m "teste123" -i "baz"
A análise do log de acesso mostra que ClientId foo sempre se conecta ao node1 (porta 18831), ClientId bar sempre se conecta ao node2 (porta 18832) e ClientId baz sempre se conecta ao node3 (porta 18833).
$ tail /var/log/nginx/mqtt_access.log 192.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 barra 192.168.91.1 [23/mar/2017:12:24:44 +0000] TCP 200 23 4 127.0.0.1:18832 barra 192.168.91.1 [23/mar/2017:12:24:47 +0000] TCP 200 23 4 127.0.0.1:18833 base 192.168.91.1 [23/mar/2017:12:24:48 +0000] TCP 200 23 4 127.0.0.1:18833 base
Observe que também temos o benefício do MQTT ClientId aparecer nas linhas de log de acesso, independentemente de usarmos persistência de sessão ou qualquer outro algoritmo de balanceamento de carga.
Nesta primeira postagem de uma série de duas partes, demonstramos como o NGINX Plus usa verificações de integridade ativas para melhorar a disponibilidade e a confiabilidade de aplicativos de IoT e como o NGINX JavaScript pode estender o NGINX Plus fornecendo um recurso de balanceamento de carga da Camada 7, como persistência de sessão para tráfego TCP. Na segunda parte , exploramos como o NGINX Plus pode tornar seus aplicativos de IoT mais seguros, descarregando a terminação TLS e autenticando clientes.
Em combinação com o NGINX JavaScript ou sozinho, o alto desempenho e a eficiência inerentes do NGINX Plus o tornam ideal como um balanceador de carga de software para sua infraestrutura de IoT.
Para experimentar o NGINX JavaScript com o NGINX Plus, inicie seu teste gratuito de 30 dias ou entre em contato conosco para discutir seus casos de uso .
Apêndices
Instalamos o ambiente de teste em uma máquina virtual para que ele fique isolado e repetível. Entretanto, não há razão para que você não possa instalá-lo em um servidor físico, “bare metal”.
Veja as instruções no Guia de administração do NGINX Plus .
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 Implantando o HiveMQ com o Docker .
Crie um Dockerfile para o HiveMQ no mesmo diretório que hivemq.zip .
Trabalhando no diretório que contém hivemq.zip e o Dockerfile, crie a imagem do Docker.
$ docker build -t hivemq:latest .
Crie três nós HiveMQ, cada um exposto em uma porta diferente.
$ docker run -p 18831:1883 -d --nome node1 hivemq:mais recente ff2c012c595a $ docker run -p 18832:1883 -d --nome node2 hivemq:mais recente 47992b1f4910 $ docker run -p 18833:1883 -d --nome node3 hivemq:mais recente 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
, CREATED
e STATUS
foram omitidas para facilitar a leitura.)
$ docker ps IMAGEM DE ID DO CONTAINER ... NOMES DE PORTAS 17303b900b64 hivemq:latest ... 0.0.0.0:18833->1883/tcp node3 47992b1f4910 hivemq:mais recente ... 0.0.0.0:18832->1883/tcp node2 ff2c012c595a hivemq:mais recente ... 0.0.0.0:18831->1883/nó tcp1
O cliente de linha de comando Mosquitto pode ser baixado do site do projeto . Usuários de Mac com o Homebrew instalado podem executar o seguinte comando.
$ brew instalar mosquito
Teste o cliente Mosquitto e a instalação do HiveMQ enviando uma mensagem de publicação simples para uma das imagens do Docker.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 18831 Cliente thing001 enviando CONNECT Cliente thing001 recebeu CONNACK Cliente thing001 enviando PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes)) Cliente thing001 enviando DISCONNECT
[nome do snippet ngx=’njs-enable-instructions’]
"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."