Como criar um cluster de Raspberry Pi 4 com Docker Swarm
O Raspberry Pi 4 é um microcontrolador com uma boa capacidade de processamento. Possui uma CPU ARM v8 64 bits (Quad core Cortex-A72), uma interface Gigabit Ethernet, uma porta USB 3.0 e duas portas HDMI. Ele pode ser configurado com até 8 GB de memória. Com armazenamento microSD é possível instalar sistemas operacionais Linux, tal como Ubuntu 18 Core ou Ubuntu 20 e softwares de servidores.
Podemos instalar o Docker neste hardware e criar um cluster com 2 ou mais Raspberry Pi para executar cargas de trabalho de análise de dados em larga escala. A vantagem desta configuração é o baixo custo do hardware. Um cartão microSD pode fornecer desempenho de leitura de 80 MB/s e com a interface gigabit ligada num switch adequado podemos obter 100 MB/s de taxa de transferência. Considerando um custo de 100 dólares por kit Raspberry Pi 4 de 8GB e 30 dólares pelo arranjo mostrado na figura abaixo, temos um custo aproximado de 780 dólares por um cluster de 6 computadores totalizando uma memória de 48 GB e 192 GB de armazenamento de dados.
Isto é suficiente para várias aplicações e provas de conceito de IoT, AI, WEB, micro-serviços rodando de forma distribuída sobre Docker numa solução escalável horizontalmente.
Vale ressaltar que o próprio Raspberry Pi possui interface GPIO e portas para câmera, audio stéreo e vídeo composto, permitindo aquisição e processamentos diversos, incluindo processamento de imagens usando aprendizado de máquina.
Instalação do Sistema Operacional
Na página de Download https://www.raspberrypi.org/downloads/ você pode baixar o Raspberry Pi Imager que está disponível para Ubuntu, MacOS e Windows. Este programa, após instalado no seu computador, oferecerá um diálogo onde você pode escolher [1] a versão do Sistema Operacional que quer copiar para o cartão microSD. Escolha [2] o microSD onde será gravada a imagem do Sistema Operacional. Com estas duas informações preenchidas o botão [3] ficará habilitado para iniciar a gravação.
É importante escolher uma versão de 64 bits tal como Ubuntu Core 18.
As instruções para instalar o Ubuntu Core 18 podem ser vistas neste link:
https://ubuntu.com/download/raspberry-pi-core. Veja abaixo um resumo
Antes de fazer o boot no seu Raspberry Pi com o microSD gerado faça o seguinte no seu computador usado para gerar a imagem:
1. Crie um par de chaves RSA usando o comando ssh-keygen. No meu caso usei opção -C para adicionar um comentário para facilitar o gerenciamento de chaves SSH na minha conta do ubuntu.com. Veja abaixo o comando executado:
ssh-keygen -t rsa -b 4096 -C "id_rsa_r-pi-01 AT admin@automacao.info"
Quando solicitado informe o nome do arquivo, como por exemplo: ~/.ssh/id_rsa_r-pi-01. É importante escolher um nome adequado para não sobrescrever chaves já existentes em ~/.ssh.
2. Copie a chave pública gerada para a área de transferência do computador. No caso do MacOS você pode usar pbcopy como mostrado abaixo:
pbcopy < ~/.ssh/id_rsa_r-pi-01.pub
3. Crie uma conta em https://ubuntu.com e depois adicione a chave pública gerada na etapa 1 acima, na sua lista de chaves. Use a URL abaixo e faça a cópia à partir da área de transferência (Copy/Paste) :
OBS: Esta chave pública será copiada para o seu Raspberry Pi para a área de usuário no arquivo ~/.ssh/authorized_keys
onde ~/
no meu caso é /home/r-pi-01/
, ou seja, o mecanismo que o programa de setup do Ubuntu Core 18 64 bits para permitir o acesso remoto ao Raspberry Pi é criar o arquivo ~/.ssh/authorized_keys
com a chave pública associada a uma chave privada que você tem acesso pois criou o par anteriormente.
Agora já é possível fazer o boot do Raspberry Pi à partir do microSD gerado. Vamos então ao passo a passo.
1. Conecte os cabos: i) teclado na USB; ii) rede ethernet no RJ45; iii) monitor na micro HDMI; iv) alimentação na fonte adequada.
2. Quando for solicitado use as credenciais de sua conta citada na etapa 3, anteriormente, e informe usuário e senha.
3. O Raspberry responde que copiou a chave SSH da sua conta Ubuntu (Public SSH keys were added to the device for remote access) e abre uma tela informando:
- O endereço IP associado a sua interface ethernet
- As impressões digitais (fingerprints) de sua chave RSA
- Instruções de login remoto usando o comando ssh com IPV4
Veja exemplo de saída (no meu caso) abaixo:
Ubuntu Core 18 on 192.168.1.120 (tty1) The host key fingerprints are: RSA SHA256:PaSE+kKJLKSDREKJeiKLKJeJ3JKLKJEEKJ ECDSA SHA256:wIKSDFLKsdlkfsjDFSdfujsdf83kljSDdD ED25519 SHA256:7JSDFSDkdsS3KJKSDsdflkfSDFLJKDSs39 To login: ssh r-pi-01@192.168.1.120 Personalize your account at https://login.ubuntu.com. |
Volte ao seu computador e execute os 3 comandos abaixo:
eval `ssh-agent -s` && ssh-add -D && ssh-add ~/.ssh/id_rsa_r-pi-01
Estes comandos registram sua chave privada criada para efeitos de autenticação automática (sem pedido de senha) no seu Raspberry Pi.
Use as instruções de login remoto fornecidas pelo se Raspberry Pi para logar-se remotamente. No meu caso use o comando abaixo:
ssh r-pi-01@192.168.1.120
Em seguida você pode definir uma senha para seu usuário no Raspberry Pi para usar também sem configuração de chaves SSH.
sudo passwd $USER
Informe a nova senha e anote para uso posterior.
Para ver detalhes da versão instalada execute:
cat /etc/os-release ; uname -a
Esta versão do Ubuntu não vem com apt instalado e devemos usar o snap. Podemos instalar o htop assim:
sudo snap install htop
E depois usá-lo. Num Raspberry Pi com 4GB vemos algo assim:
Veja agora onde o snap instala os aplicativos:
which htop
/snap/bin/htop
Ou seja, todos os aplicativos instalados via snap
ficam em área protegida /snap
.
Podemos instalar o Firewall do Ubuntu Core 18.04 com o snap:
snap install ufw ; sudo ufw allow 22 # Muito importante liberar a porta 22 do SSH !
Instalação do Docker
Podemos pesquisar a documentação oficial em https://docs.ubuntu.com/core/en/platforms. No caso de instalar o Docker podemos pesquisar o repositório de aplicativos assim:
snap find docker | grep canonical
que exibirá:
docker 19.03.11 canonical* - Docker container runtime
etcd 3.4.5 canonical* - Resilient key-value store by CoreOS
Assim podemos instalar o Docker executando:
sudo snap install docker && sudo usermod -aG docker $USER && groups $USER
Com o Docker instalado podemos obter, por exemplo, o OpenJDK 11 64 bits para processadores ARM executando o comando:
docker pull adoptopenjdk/openjdk11:jdk-11.0.8_10-slim
A tag jdk-11.0.8_10-slim ocupa apenas 150 MB (comprimida) e é adequada ao RaspberryPi. Para testar a imagem podemos usar:
docker run -it --rm --name java11 adoptopenjdk/openjdk11:jdk-11.0.8_10-slim bash
Dentro do contêiner é possível executar o jshell
que é um REPL Java e testar o ambiente.
System.out.println("Olá mundo. Timestamp:"
+ new java.sql.Timestamp(System.currentTimeMillis()))
Criando o Cluster com Swarm
Podemos em seguida criar um cluster de hosts gerenciado pelo Swarm que é nativo no Docker. Para tal precisamos saber o endereço externo da interface de rede usada pelo Docker. Já sabemos o endereço mas podemos executar um script awk para filtrar a lista de interfaces de rede executando:
EXTERNAL_IP=`ip addr show | grep 'inet ' | grep -v ' lo$\| docker0$' \
| awk '{ split($2,t,"/"); print t[1] }'`
echo ${EXTERNAL_IP}
que exibe no meu caso:
192.168.1.120
Este é o endereço já conhecido e o script awk foi colocado aí para efeitos de automação, caso seja necessário. Para criar um cluster executamos o comando abaixo no primeiro node (Raspberry Pi corrente):
sudo docker swarm init --advertise-addr ${EXTERNAL_IP}
que exibirá algo assim:
Swarm initialized: current node (e02ra6e1n0coj5rt6j3tqwnad) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token \
SWMTKN-1-5e3lcfszfbvwxpzqbdf6l1yged6pzovctmlze0070bsfv41r98-f59jvnvcesyb5rxd4dm7irlov \
192.168.1.120:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and
follow the instructions.
Observe que o Swarm é inicializado e configura o Raspberry Pi atual como master e fornece o comando para agregar novos hosts ao Cluster.
O próximo passo agora é repetir a instalação e configuração do segundo Raspberry Pi que envolve todas as etapas descritas acima nesta seção. Quando estivermos com o sistema rodando no Ubuntu Core 18 com o Docker instalado podemos executar neste novo host, o comando sugerido pelo swarm (em azul, acima).
docker swarm join --token \
SWMTKN-1-5e3lcfszfbvwxpzqbdf6l1yged6pzovctmlze0070bsfv41r98-f59jvnvcesyb5rxd4dm7irlov \
192.168.1.120:237
O docker responde com o seguinte:
This node joined a swarm as a worker.
Pronto, temos agora um Cluster com 2 Nodes. Este processo pode ser repetido para quantos Raspberry Pi forem necessários no Cluster.
O comando docker info
pode ser executado em todos os hosts onde instalamos o Docker e fizemos Join ao Cluster. Todos eles mostrarão uma seção Swarm com status active. e também mostrará o número de nodes existentes no Cluster, no momento aparece a linha Nodes: 2
sob a seção Swarm.
É importante mencionar que o comando mostrado acima poderá ser executado também em outras máquinas da rede que tenham o Docker instalado. Basta que seja uma versão atual com o Swarm integrado. É bastante saudável, entretanto, que as versões do Docker sejam as mesmas, mesmo que em sistemas operacionais distintos (Ubuntu, CentOS, MacOS, etc.).
Para ter uma visão geral das informações com destaque para o número de nodes podemos executar:
INFO=`sudo docker info 2> /dev/null` ; echo $INFO | grep "Nodes: \|: "
que retorna algo parecido com isto abaixo:
Client: Debug Mode: false Server: Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 19.03.11 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Native Overlay Diff: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog Swarm: active NodeID: e02ra6e1n0coj5rt6j3tqwnad Is Manager: true ClusterID: l7341897urhhwv1d0hyuq2rjh Managers: 1 Nodes: 2 Default Address Pool: 10.0.0.0/8 SubnetSize: 24 Data Path Port: 4789 Orchestratio: Task History Retention Limit: 5 Raft: Snapshot Interval: 10000 Number of Old Snapshots to Retain: 0 Heartbeat Tick: 1 Election Tick: 10 Dispatcher: Heartbeat Period: 5 seconds CA Configuration: Expiry Duration: 3 months Force Rotate: 0 Autolock Managers: false Root Rotation In Progress: false Node Address: 192.168.1.120 Manager Addresses: 192.168.1.120:2377 Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc version: init version: fec3683 Security Options: apparmor seccomp Profile: default Kernel Version: 5.3.0-1028-raspi2 Operating System:Ubuntu Core 16 OSType: linux Architecture: aarch64 CPUs: 4 Total Memory: 3.703GiB Name: localhost ID: A5IA:FQN7:N675:6F5V:E34I:UIHO:K4YY:UJIZ:35CU:XZBE:U4GR:I7SU Docker Root Dir: /var/snap/docker/common/var-lib-docker Debug Mode: false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false
O comando sudo docker node ls
mostra os Nodes existentes no Cluster, seus Hostnames, seus estados e versões do Docker instaladas. Já o comando docker node inspec
mostra mais detalhes.
Exemplo: sudo docker node inspect --pretty localhost
O comando docker service create
pode ser usado no Cluster para iniciar contêineres Docker que irão se replicar pelos nodes existentes. Considere um exemplo com o HTTP Server do Python3 do Ubuntu rodando como serviço em um Cluster de 4 Raspberry Pi. Um deles funcionará como master e os outros 3 como workers.

Antes liberamos a porta 80 no Raspberry Pi usando os comandos:
sudo ufw allow 80 ; sudo ufw enable ; sudo ufw status
É preciso também atualizar a disponibilidade do Node master. No primeiro Raspberry Pi criado, que funcionará como node master, execute o seguinte:
NODE_ID=`docker info | grep "NodeID: " | awk '{ print $2 }'`
echo $NODE_ID # Para verificar a execução correta do AWK
sudo docker node update --availability drain $NODE_ID
Agora criamos o serviço HTTP Server do Python3 disponível, por exemplo no Ubuntu 18.
sudo docker service create --name http-server -d -p 80:8000 \
ubuntu:18.04 python3 -m http.server 8000
Agora podemos listar os serviços.
sudo docker service ls
E verificar o estado deles.
sudo docker service ps http-server
Ao finalizar a utilização dos serviços no Cluster podemos removê-los
sudo docker service rm http-server
O docker swarm é apenas um dentre os diversos orquestradores multi-host para contêineres Docker. O padrão de fato para computação em nuvem é o Kubernetes. Então podemos verificar o suporte no Ubuntu Core 18 64 bits para este componente. Executando o comando snap find kubernetes verificamos que não existe um snap pronto com Kubernetes no Ubuntu Core 18. Na verdade esta versão do sistema é focada em IoT e sistemas embarcados e por isso deve ser muito enxuta. Para a maioria das aplicações, mesmo em Cluster, o Docker Swarm atende as necessidades de infraestrutura neste tipo de hardware. Sendo assim Kubernetes no Raspberry Pi fica pra próxima.
Conclusão
Esta foi uma breve introdução ao orquestrador multi host nativo do Docker que portanto pode rodar normalmente mesmo em sistemas Linux embarcados como é o caso do Ubuntu Core 18. Não foi a intenção esgotar as funcionalidades do Docker Swarm neste documento mas ele poderá servir de ponto de partida para desenvolvimento de soluções criativas, escaláveis e com bom desempenho. Também foi abordado o processo de instalação do Ubuntu Core 18.04 64 bits no Raspberry Pi 4 de forma que fica muito fácil criar um cluster on-premise com este tipo de hardware de baixo custo. Tal solução permite desenvolver soluções de IoT com computação distribuída usando micro serviços. Uma dessas soluções é o SOMA – Sistema Orientado à Gestão de Ativos de engenharia. Este sistema foi instalado pelo CEPEL (http://cepel.br) na Usina de Itaipú, usando servidores Windows Server, para monitorar toda a planta de geração de energia, mas poderá ser instalado em pequenas plantas industriais, em um cluster como este descrito, pois é uma solução Java EE e portanto compatível com a arquitetura ARM 64.