Em uma postagem anterior, eu mostrei como configurar um broker MQTT no Kubernetes com o Mosquitto. Mas o suporte do Mosquitto para a criação de clusters usando bridges é muito limitado. Felizmente, existe uma solução muito mais simples, que utiliza o Redis para criar o cluster e o Mosca para suporte ao protocolo MQTT.
Essa ideia surgiu quando eu encontrei um artigo no Medium descrevendo basicamente a mesma arquitetura que eu irei mostrar aqui. O artigo original (em inglês) pode ser acessado em How to Build an High Availability MQTT Cluster for the Internet of Things.
Criação do Broker
De forma resumida, pode-se dizer que o Mosca é um framework para criação de um broker MQTT. O Mosca em si não implementa nenhum suporte a pub/sub (que é necessário para o funcionamento do MQTT), mas, em vez disso, permite utilizar outras implementações como backend (incluindo Mosquitto, Redis, MongoDB e outros). O Mosca se preocupa apenas com o protocolo MQTT.
Bom, com o funcionamento do Mosca bem entendido, o próximo passo é criar o broker.
No caso do Mosca, isso significa criar uma aplicação NodeJS e instalar as
dependências pelo NPM. E as única dependências necessárias são o mosca
e um
backend, nesse caso o redis
.
$ npm i -s mosca redis
Esse comando deve instalar as duas dependências e adicioná-las ao package.json
.
Em seguida, é criado o arquivo principal do projeto, que vai iniciar o Mosca e
configurar o backend utilizado. Eu chamei esse arquivo de app.js
.
const url = require('url');
const mosca = require('mosca');
const authorizer = require('./authorizer');
const redis = url.parse(process.env.REDIS_URL);
const settings = {
port: parseInt(process.env.MQTT_PORT) || 1883,
backend: {
type: 'redis',
redis: require('redis'),
host: redis.hostname,
port: redis.port,
db: parseInt(redis.path.slice(1)),
password: redis.password,
},
};
console.log('Broker MQTT iniciando...');
const server = new mosca.Server(settings);
server.on('ready', () => {
// Configura callbacks de autenticação e autorização
server.authenticate = authorizer.authenticate;
server.authorizePublish = authorizer.authorizePublish;
server.authorizeSubscribe = authorizer.authorizeSubscribe;
console.log('Broker MQTT aguardando conexões na porta', settings.port);
});
server.on('published', (packet, client) => {
console.log(
'Message published on topic',
packet.topic,
':',
packet.payload.toString(),
);
});
O módulo authorizer
que foi incluído nesse arquivo possui a lógica de autenticação
e autorização dos clientes. Como autenticação não é o foco desse post, irei deixar
aqui apenas um template de como os métodos podem ser implementados:
module.exports.authenticate = function authenticate(
client,
username,
passwordBuffer,
callback,
) {
if (usuarioESenhaEstaoCorretos) {
// Você pode adicionar informações (ex.: permissões)
// sobre o cliente no objeto client, se quiser:
client.superuser = true;
callback(null, true);
}
else {
callback(null, false);
}
};
module.exports.authorizePublish = function authorizePublish(
client,
topic,
payload,
callback,
) {
// Você pode verificar dados adicionados
// no objeto cliente durante autenticação:
if (client.superuser) {
callback(null, true);
}
else {
callback(null, false);
}
};
module.exports.authorizeSubscribe = function authorizeSubscribe(
client,
topic,
callback,
) {
// Você pode verificar dados adicionados
// no objeto cliente durante autenticação:
if (client.superuser) {
callback(null, true);
}
else {
callback(null, false);
}
};
Agora, para executar o broker, basta rodá-lo como uma aplicação NodeJS comum (sem esquecer de passar a variável de ambiente com a URL do Redis):
$ REDIS_URL=redis://localhost:6379/0 node app.js
Mosca em um container
Um Dockerfile bem simples para adicionar essa aplicação do Mosca para um container Docker seria assim:
FROM node:6-alpine
EXPOSE 1883
WORKDIR /app
ADD package.json /app/package.json
RUN apk add --update --no-cache --virtual .build-deps \
build-base zeromq-dev && \
npm install && \
apk del --purge .build-deps && \
rm -rf /var/cache/apk
ADD . /app
CMD node app.js
Mosca no Kubernetes
Tendo a aplicação com o Mosca rodando dentro de um container, para publicar em um cluster do Kubernetes não é muito diferente do que publicar o Mosquitto. E, para escalonar o cluster, um Horizontal Pod Autoscaler deve resolver.