Saudações.
Esse tutorial é um guia completo de como construir um ambiente de execução de máquinas virtuais KVM (Kernel Virtual Machine) no Linux começando do zero absoluto.
Ao dominar todos os recursos desse artigo você se tornará apto a criar seu próprio Proxmox (não precisa de muito pra criar algo melhor que ele).
Requisitos:
- Processador com suporte a VT-x (Intel) ou AMD-v (AMD);
- Instalação do Linux (Debian);
- Internet no servidor (sua VPS ou host);
1 – Preparativos do Linux como Hypervisor
Precisamos garantir que nosso servidor tenha todas as ferramentas e tecnologias necessárias.
1.1 – Suporte a virtualização de CPU
Precisamos que o hardware da CPU tenha esse suporte e também é necessários que a BIOS do servidor físico (baremetal) tenha esse recurso ativo.
Se você estiver montando esse laboratório em máquina virtual (Nested = criar uma VM dentro de uma VM), você precisa:
- VMware: Ativar nas propriedades da CPU a opção “Expose hardware assisted virtualisation“, link: https://techdocs.broadcom.com/us/en/vmware-cis/vsphere/vsphere/8-0/expose-hardware-assisted-virtualization.html
- Proxmox: Procedimentos “Enable Nested Hardware-assisted Virtualization” https://pve.proxmox.com/wiki/Nested_Virtualization
Com o Linux de base para o hypervisor pronto, confira se o suporte está realmente ativo:
# Verificar suporte a virtualizacao na CPU:
sup=0;
egrep -q '^flags.*(vmx|svm)' /proc/cpuinfo >/dev/null && sup=1
# Testar se tem suporte
if [ "$sup" = "0" ]; then
echo 'Virtualizacao NAO suportada, precisa arrumar isso.';
else
echo 'Virtualizacao suportada!!!!';
fi;
Precisa retornar “Virtualização suportada!!!!“.
1.2 – Instalar ferramentas gerais
Ferramentas necessárias para manobrar o sistema:
# Ferramenta:
# - Nativas (costumam ja estarem instaladas)
apt-get -y install kmod;
apt-get -y install procps;
apt-get -y install dosfstools;
# - Parar criar e gerenciar interfaces de rede
apt-get -y install iproute2;
apt-get -y install bridge-utils;
# - Usar wget oficial (evitar o wget do busybox)
apt-get -y install wget;
apt-get -y install curl;
# - Analise de quadros e pacotes
apt-get -y install tcpdump;
# - Comandos de ping
apt-get -y install iputils-ping;
apt-get -y install fping;
# - Firewall para NAT
apt-get -y install nftables;
# - Servidor DHCP para atender as VMs:
apt-get -y install kea-dhcp4-server;
apt-get -y install kea-dhcp6-server;
apt-get -y install radvd;
# - Ativar servicos (explicitamente, costuma ativar sozinho)
systemctl enable kea-dhcp4-server;
systemctl enable radvd;
Correções nas UNITs dos serviços para que eles sejam reiniciados em caso de falhas:
# Funcao para correcao de unit do systemd
# para implementacao de restart automatico
_systemd_enable_restart(){
unit="$1";
# Se restart presente como on-failure, mudar para always
sed -i 's#Restart=on-failure#Restart=always#' $unit;
# Adicionar restart em caso de omissao
egrep -q 'Restart=' $unit || {
# Faltou politica de restart
sed -i '/^\[Service\]/a Restart=always' $unit;
};
# Adicionar pause entre restart de 3s em caso de omissao
egrep -q 'RestartSec=' $unit || {
# Faltou politica de restart
sed -i '/^\[Service\]/a RestartSec=3' $unit;
};
};
# Aplicar politica de restart nos servicos vitais
_systemd_enable_restart "/usr/lib/systemd/system/kea-dhcp4-server.service";
_systemd_enable_restart "/usr/lib/systemd/system/kea-dhcp6-server.service";
_systemd_enable_restart "/usr/lib/systemd/system/radvd.service";
# Recarregar units:
systemctl daemon-reload;
1.3 – Instalar Q-EMU, VirtIO e KVM
O Q-EMU é um gestor de recursos virtuais no Linux com capacidade de emular alguns periféricos da VM.
Ele é o processo que provisiona todos os pedaços do computador virtual (VM) e liga ela na “tomada”, provê a BIOS, boot, EFI, barramento PCI virtual, relógio virtual, tela, teclado, mouse, USB, etc.
# Instalar ferramentas de virtualizacao
apt-get -y install qemu-system-common;
apt-get -y install qemu-system-x86; # CPUs virtuais AMD64 (x86_64)
apt-get -y install qemu-system-arm; # CPUs virtuais ARM64, opcional
apt-get -y install qemu-utils;
apt-get -y install qemu-slof;
apt-get -y install qemu-kvm;
apt-get -y install socat;
apt-get -y install ovmf;
1.4 – Módulos do kernel KVM e VirtIO
O KVM é a máquina virtual de baixo nível direto no bloco CPU-RAM, provê acesso acelerado aos núcleos da CPU criando vCPUs e também faz a gestão da memória RAM da VM.
O VirtIO é uma tecnologia de para-virtualização do kernel Linux que provê camadas aceleradoras de dispositivos virtuais para as VMs, como discos virtuais, interfaces de rede e demais periféricos. Cada dispositivo virtual da VM que for provisionado usando Virtio em vez de Q-EMU acelera em até 3.000 vezes a velocidade de processamento e transferência (zero-copy).
Ativar módulos do kernel do KVM e VirtIO para paravirtualização máxima:
# Módulos do kernel para maquinas virtuais
# - Suporte a KVM
echo "kvm" > /etc/modules-load.d/kvm.conf;
egrep -qi intel /proc/cpuinfo && echo "kvm-intel" > /etc/modules-load.d/kvmi.conf;
egrep -qi amd /proc/cpuinfo && echo "kvm-amd" > /etc/modules-load.d/kvma.conf;
# - Suporte a interfaces de rede TAP (ethernet outside da VM)
echo "tap" > /etc/modules-load.d/tap.conf;
# - Perifericos virtuais da VM no virtio:
(
echo virtio;
echo virtio_balloon;
echo virtio_pci;
echo virtio_rng;
echo virtio_net;
echo virtio_blk;
) > /etc/modules-load.d/virtio.conf;
# Carregar todos imediatamente:
systemctl restart systemd-modules-load;
1.5 – Ajustes de sistema
Para execução de máquinas virtuais é recomendado ajustes de HugePages.
Deixe seu Linux afinado com esses tutoriais:
- Debian Ajustes finos: https://blog.patrickbrandao.com/debian-ajustes-finos/
- Ajuste fino e tuning no kernel Linux – https://blog.patrickbrandao.com/ajuste-fino-e-tuning-no-kernel-linux/
Tutoriais recomendados:
- FRR – https://blog.patrickbrandao.com/frr-frrouting-debian/
- Servidor NTP local – https://blog.patrickbrandao.com/ntp-servidor/
- Servidor DNS local – https://blog.patrickbrandao.com/servidor-dns-unbound-com-anablock/
- Servidor SSH – https://blog.patrickbrandao.com/ssh-guia-rapido/
- nftables: Estrutura de Firewall Linux – https://blog.patrickbrandao.com/nftables-debian-linux/
2 – Transformando o Linux em Hypervisor KVM
No capítulo 1 instalamos todos os softwares necessários para que nosso Linux seja um hypervisor KVM, agora movas configurá-los adequadamente.
2.1 – Hypervisor como roteador e NAT
Normalmente os hypervisors colocam as máquinas virtuais na rede externa por meio de uma bridge (vSwitch) principal que serve tanto para administrar o hypervisor (Proxmox e VMware ESXi fazem assim) como para dar passagem à rede externa para as VMs.
Não vou fazer isso!
Meu hypervisor pessoal será o roteador das máquinas virtuais. Diagrama:

2.2 – Bridge para as VMs
Vamos preparar a rede de VMs em uma bridge chamada “br-vms” com endereços:
- Interface: br-vms
- IPv4: 172.31.0.1/24
- DHCP, pool: 172.31.0.100 a 172.31.0.199
- IPv6: fc00:172:31::1/64
- DHCPv6, pool: fc00:172:31::/120
- Slaac ND-RA: fc00:172:31::/64
A interface bridge pode ser iniciada com MTU máximo em 65.535 bytes. Esse valor sofre reajuste para ser o menor valor entre todas as interfaces afiliadas à bridge.
Criando bridge para servidor de switch virtual das VMs e para ser o gateway delas:
# Criar config da bridge "br-vms"
(
echo;
echo 'auto br-vms';
echo;
echo 'iface br-vms inet static';
echo ' address 172.31.0.1/24'
echo ' mtu 65535';
echo ' bridge_ports none';
echo ' bridge_stp off';
echo ' bridge_fd 0';
echo;
echo 'iface br-vms inet6 static';
echo ' address fc00:172:31::1/64';
echo ' dad-attempts 0';
echo ' pre-up echo 0 > /proc/sys/net/ipv6/conf/br-vms/addr_gen_mode';
echo;
) > /etc/network/interfaces.d/br-vms
# Ativar:
ifup --force br-vms;
2.3 – DHCP de IPv4 e IPv6
Configurando o servidor DHCP de IPv4:
# Criar arquivo JSON do KEA DHCP de IPv4
(
echo '{';
echo ' "Dhcp4": {';
echo ' "interfaces-config": {';
echo ' "interfaces": ["br-vms"]';
echo ' },';
echo ' "lease-database": {';
echo ' "type": "memfile",';
echo ' "persist": true,';
echo ' "name": "/var/lib/kea/kea-leases4.csv",';
echo ' "lfc-interval": 3600';
echo ' },';
echo ' "valid-lifetime": 3600,';
echo ' "renew-timer": 1800,';
echo ' "rebind-timer": 3150,';
echo ' "subnet4": [';
echo ' {';
echo ' "id": 1,';
echo ' "subnet": "172.31.0.0/24",';
echo ' "pools": [';
echo ' { "pool": "172.31.0.100 - 172.31.0.199" }';
echo ' ],';
echo ' "option-data": [';
echo ' { "name": "routers", "data": "172.31.0.1" },';
echo ' { "name": "domain-name-servers", "data": "8.8.8.8" },';
echo ' { "name": "domain-name", "data": "vms.deb.local" }';
echo ' ]';
echo ' }';
echo ' ],';
echo ' "loggers": [';
echo ' {';
echo ' "name": "kea-dhcp4",';
echo ' "output-options": [';
echo ' { "output": "/var/log/kea/kea-dhcp4.log" }';
echo ' ],';
echo ' "severity": "INFO"';
echo ' }';
echo ' ]';
echo ' }';
echo '}';
) > /etc/kea/kea-dhcp4.conf;
# Reiniciar kea dhcp4
systemctl enable kea-dhcp4-server;
systemctl restart kea-dhcp4-server;
2.4 – DHCP de IPv6
Configurando o servidor DHCP de IPv6:
# Criar arquivo JSON do KEA DHCP de IPv6
(
echo '{';
echo ' "Dhcp6": {';
echo ' "interfaces-config": {';
echo ' "interfaces": ["br-vms"]';
echo ' },';
echo ' "lease-database": {';
echo ' "type": "memfile",';
echo ' "persist": true,';
echo ' "name": "/var/lib/kea/kea-leases6.csv",';
echo ' "lfc-interval": 3600';
echo ' },';
echo ' "preferred-lifetime": 3000,';
echo ' "valid-lifetime": 3600,';
echo ' "renew-timer": 1800,';
echo ' "rebind-timer": 3150,';
echo ' "subnet6": [';
echo ' {';
echo ' "id": 1,';
echo ' "subnet": "fc00:172:31::/64",';
echo ' "pools": [';
echo ' { "pool": "fc00:172:31::100 - fc00:172:31::200" }';
echo ' ],';
echo ' "option-data": [';
echo ' { "name": "dns-servers", "data": "2001:4860:4860::8888" },';
echo ' { "name": "domain-search", "data": "vms.deb.local" }';
echo ' ]';
echo ' }';
echo ' ],';
echo ' "loggers": [';
echo ' {';
echo ' "name": "kea-dhcp6",';
echo ' "output-options": [';
echo ' { "output": "/var/log/kea/kea-dhcp6.log" }';
echo ' ],';
echo ' "severity": "INFO"';
echo ' }';
echo ' ]';
echo ' }';
echo '}';
) > /etc/kea/kea-dhcp6.conf;
# Reiniciar kea dhcp6
systemctl enable kea-dhcp6-server;
systemctl restart kea-dhcp6-server;
2.5 – IPv6 ND-RA para as VMs
Configurar o radvd para enviar gratuitamente a configuração IPv6 via anúncios Neighbor Discovery – Router Advertisement (ND-RA).
# Arquivo do radvd:
(
echo 'interface br-vms {';
echo ' AdvSendAdvert on;';
echo ' MinRtrAdvInterval 30;';
echo ' MaxRtrAdvInterval 100;';
echo ' AdvManagedFlag off;';
echo ' AdvOtherConfigFlag on;';
echo ' prefix fc00:172:31::/64 {';
echo ' AdvOnLink on;';
echo ' AdvAutonomous on;';
echo ' AdvValidLifetime 3600;';
echo ' AdvPreferredLifetime 1800;';
echo ' };';
echo ' RDNSS 2001:4860:4860::8888 {';
echo ' AdvRDNSSLifetime 3600;';
echo ' };';
echo ' DNSSL vms.deb.local {';
echo ' AdvDNSSLLifetime 3600;';
echo ' };';
echo '};';
) > /etc/radvd.conf;
# Reiniciar o radvd
systemctl restart radvd;
2.6 – NFTables para NAT das VMs
Preparar serviço de firewall do nftables:
# Retirar defaults (debian: .conf, alpine: .nfg)
rm -f /etc/nftables.conf 2>/dev/null;
rm -f /etc/nftables.nft 2>/dev/null;
# Pasta com unidades de config:
mkdir -p /etc/nftables.d;
# Criar configuracao baseada nos arquivos ordenados da pasta /etc/nftables.d/
(
echo '#!/usr/sbin/nft -f';
echo;
echo 'flush ruleset';
echo 'include "/etc/nftables.d/*.conf"';
echo 'include "/etc/nftables.d/*.nft"';
echo;
) > /etc/nftables.conf;
# Unificar diferentes distribuicoes no mesmo arquivo, oficial: .conf
ln -sf /etc/nftables.conf /etc/nftables.nft;
Arquivos com estruturas de tabelas:
# Arquivos de boot das tabelas
echo "create table ip nat" > /etc/nftables.d/0004-table-ipv4-nat.conf;
echo "create table ip6 nat" > /etc/nftables.d/0014-table-ipv6-nat.conf;
#------------ NAT IPv4
(
echo "create chain ip nat PREROUTING {";
echo " type nat hook prerouting priority dstnat;";
echo " policy accept;";
echo "}";
) > /etc/nftables.d/0031-chain-ipv4-nat-prerouting.conf;
(
echo "create chain ip nat POSTROUTING {";
echo " type nat hook postrouting priority srcnat;";
echo " policy accept;";
echo "}";
) > /etc/nftables.d/0034-chain-ipv4-nat-postrouting.conf;
#------------ NAT IPv6
(
echo "create chain ip6 nat PREROUTING {";
echo " type nat hook prerouting priority dstnat;";
echo " policy accept;";
echo "}";
) > /etc/nftables.d/0051-chain-ipv6-filter-prerouting.conf;
(
echo "create chain ip6 nat POSTROUTING {";
echo " type nat hook postrouting priority srcnat;";
echo " policy accept;";
echo "}";
) > /etc/nftables.d/0051-chain-ipv6-filter-postrouting.conf;
Regras de NAT da rede br-vms:
# Criar SET (lista) de prefixos privados:
(
echo "set ip nat PRIVNETS_IPV4 {";
echo " type ipv4_addr;";
echo " flags interval;";
echo "}";
) > /etc/nftables.d/1001-ipv4-nat-set-privnets.nft;
(
echo "set ip6 nat PRIVNETS_IPV6 {";
echo " type ipv6_addr;";
echo " flags interval;";
echo "}";
) > /etc/nftables.d/1002-ipv6-nat-set-privnets.nft;
# - Preencher SET com IPs privados IPv4
(
echo "add element ip nat PRIVNETS_IPV4 {";
echo " 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12";
echo "}";
) > /etc/nftables.d/1011-ipv4-nat-add-privnets.nft;
# - Preencher SET com IPs privados IPv6
(
echo "add element ip6 nat PRIVNETS_IPV6 {";
echo " ::/3, 2001:db8::/32, 4000::/2, 8000::/1";
echo "}";
) > /etc/nftables.d/1012-ipv6-nat-add-privnets.nft;
# NAT MASQUERADE na interface de saida
# - IPv4
echo 'add rule ip nat POSTROUTING ip saddr @PRIVNETS_IPV4 counter masquerade' \
> /etc/nftables.d/1901-ipv4-nat-masquerade.nft;
# ou SNAT:
# ... counter snat ip to 45.255.128.2'
# - IPv6
echo 'add rule ip6 nat POSTROUTING ip6 saddr @PRIVNETS_IPV6 counter masquerade' \
> /etc/nftables.d/1902-ipv6-nat-masquerade.nft;
# ou SNAT:
# ... counter snat ip6 to 2804:cafe::2'
Ativar e reiniciar nftables:
# Ativar para subir durante o boot:
systemctl enable nftables;
# Reiniciar para aplicar as regras:
systemctl restart nftables;
2.7 – Ativar roteamento IP
Por padrão o Linux não faz o encaminhamento entre pacotes IPs de uma interface para outra (br-vms para eth0 e vice-versa no nosso caso).
Vamos ativar explicitamente para que as VMs usar o Linux do HOST como roteador:
# Ativar roteamento de pacotes IPv4
(
echo "net.ipv4.conf.default.forwarding=1";
) > "/etc/sysctl.d/065-default-foward-ipv4.conf";
# Ativar roteamento de pacotes IPv6
(
echo "net.ipv6.conf.default.forwarding=1";
) > "/etc/sysctl.d/066-default-foward-ipv6.conf";
# Ativar roteamento em todas as interfaces de rede
echo "net.ipv4.conf.all.forwarding=1" > "/etc/sysctl.d/067-all-foward-ipv4.conf";
echo "net.ipv6.conf.all.forwarding=1" > "/etc/sysctl.d/068-all-foward-ipv6.conf";
echo "net.ipv4.ip_forward=1" > "/etc/sysctl.d/069-ipv4-forward.conf";
# Aplicar imediatamente:
sysctl -q --system 2>/dev/null;
sysctl -q -p 2>/dev/null;
2.8 – Ajustes no Linux para hospedar VMs performáticas
Alguns ajustes devem ser feitas no Kernel Linux para que ele atenda melhor a execução de máquinas virtuais:
# Nao usar SWAP enquanto houver memoria RAM livre
echo "vm.swappiness=0" > /etc/sysctl.d/073-swappiness.conf;
# Flush escrita mais rápido
(
echo "vm.dirty_ratio=5";
echo "vm.dirty_background_ratio=2";
) > /etc/sysctl.d/075-dirty.conf;
# Liberar mais RAM para aceleração de rede
(
echo "net.core.rmem_default=31457280";
echo "net.core.wmem_default=31457280";
echo "net.core.rmem_max=134217728";
echo "net.core.wmem_max=134217728";
echo "net.ipv4.tcp_rmem=4096 87380 134217728";
echo "net.ipv4.tcp_wmem=4096 65536 134217728";
echo "net.core.netdev_max_backlog=250000";
echo "net.core.optmem_max=33554432";
echo "net.core.default_qdisc=fq";
echo "net.core.somaxconn=65535";
) > /etc/sysctl.d/051-net-core.conf;
# Flexibilizar a alocacao de RAM para alem dos limites reais
echo "vm.overcommit_memory=1" > /etc/sysctl.d/076-ram-overcommit.conf;
# KVM Scheduling
(
echo "kernel.sched_min_granularity_ns=10000000";
echo "kernel.sched_wakeup_granularity_ns=15000000";
) > /etc/sysctl.d/098-kernel-sched.conf;
# Reiniciar o kernel apos 10 segundos em caso de pane geral
echo "kernel.panic=10" > /etc/sysctl.d/081-kernel-panic.conf;
# Aplicar imediatamente:
sysctl -q --system 2>/dev/null;
sysctl -q -p 2>/dev/null;
2.9 – Pasta de storage das VMs
Vou criar a pasta /storage para ser a pasta principal de armazenamento dos arquivos das VMs:
# Pasta principal de armazenamento das VMs:
mkdir -p /storage;
# Pasta para ISOs comuns:
mkdir -p /storage/isos;
# Pasta para templates VHD e OVF (sistemas prontos para clonar):
mkdir -p /storage/templates;
2.10 – HugePages
Por padrão o Linux aloca a memória virtual para os programas em pedaços de 4KB, um programa ao alocar 4 MB receberá esse espaço em fragmentos de 4KB – 1.000 páginas. Se esse fragmento for de 2 MB o programa precisaria de apenas 2 páginas.
Isso é bom para programas que alocam pouca memória mas é péssimo para programas que precisam de vários GB como máquinas virtuais.
Ativar o HugePages (HP) no Kernel aumenta a velocidade de acesso à memória.
Usar HP padrão de 2 MB é ideal para casos gerais, para VMs muito grandes (8GB ou mais) é ideal o uso de GigantPages (GP, 1 GB por página, não vou abordar GP aqui).
# No host — reservar hugepages de 2MB (ex: para 100 VMs de 1GB = 51200 páginas)
echo 51200 > /proc/sys/vm/nr_hugepages;
# Persistir no boot
echo "vm.nr_hugepages=51200" >> /etc/sysctl.d/091-hugepages.conf;
Para que a VM faça uso de páginas de 2MB é OBRIGATÓRIO que os argumentos explicitos para HP estejam na VM:
# Adicionar nos argumentos da VM:
...
-object memory-backend-file,id=mem0,size=1G,mem-path=/dev/hugepages,share=on \
-numa node,memdev=mem0 \
...
# "-object" orienta a fonte de alocacao de memoria
# "-numa" faz afinidade no primeiro socket (processador fisico)
2.11 – KSM
O KSM (Kernel Samepage Merging) é um sistema de de-duplicação de RAM, ele identifica blocos de memória RAM que possuem o mesmo conteúdo e de-duplica, ficando as duas páginas apontando para o mesmo endereço (20 páginas de 2MB que possuem o mesmo conteúdo passam a consumir 2MB em vez de 40MB).
Ele só é vantajoso para sistemas que rodam muitas VMs baseadas nos mesmos templates (bases iguais). Ele não é vantagem em sistemas heterogêneos (cada VM é um sistema diferente).
Caso não gere economia, ele apenas consome CPU e banda na RAM e deve ser desativados, procedimentos para remoção do KSM (deixei no começo para não confundir quem copia e cola cegamente).
Desativando KSM do sistema:
# Desativar servicos de KSM do Debian:
systemctl stop ksm;
systemctl stop ksmtuned;
systemctl disable ksm;
systemctl disable ksmtuned;
# Remover config durante o boot:
rm -f /etc/tmpfiles.d/ksm-enable.conf;
# Forcar parada do KSM no kernel:
echo 0 > /sys/kernel/mm/ksm/run;
sleep 4;
# Forcar desagrupamento das paginas:
echo 2 > /sys/kernel/mm/ksm/run;
sleep 16;
# Desativacao final
echo 0 > /sys/kernel/mm/ksm/run;
Ativando KSM nativamente:
# Verificar se o Kernel suporta KSM?
grep CONFIG_KSM /boot/config-$(uname -r);
# Precisa retornar:
# CONFIG_KSM=y
# Ativar KSM durante o boot
# - Criar manifesto de preenchimento de configs durenate o boot:
(
echo 'w /sys/kernel/mm/ksm/run - - - - 1';
echo 'w /sys/kernel/mm/ksm/smart_scan - - - - 1';
echo 'w /sys/kernel/mm/ksm/advisor_min_pages_to_scan - - - - 1250';
echo 'w /sys/kernel/mm/ksm/advisor_max_pages_to_scan - - - - 1250';
echo 'w /sys/kernel/mm/ksm/max_page_sharing - - - - 512';
echo 'w /sys/kernel/mm/ksm/sleep_millisecs - - - - 10';
echo 'w /sys/kernel/mm/ksm/merge_across_nodes - - - - 0';
echo 'w /sys/kernel/mm/ksm/use_zero_pages - - - - 1';
echo 'w /sys/kernel/mm/ksm/advisor_target_scan_time - - - - 200';
echo 'w /sys/kernel/mm/ksm/advisor_mode - - - - scan-time';
echo 'w /sys/kernel/mm/ksm/advisor_max_cpu - - - - 10';
) > /etc/tmpfiles.d/ksm-enable.conf;
# - Aplicando manifestos do tmpfiles imediatamente:
systemd-tmpfiles --create 2>/dev/null;
# - Aplicandos configuracoes no kernel imediatamente:
echo 1 > /sys/kernel/mm/ksm/run;
echo 1 > /sys/kernel/mm/ksm/smart_scan;
echo 1250 > /sys/kernel/mm/ksm/advisor_min_pages_to_scan;
echo 1250 > /sys/kernel/mm/ksm/advisor_max_pages_to_scan;
echo 512 > /sys/kernel/mm/ksm/max_page_sharing;
echo 10 > /sys/kernel/mm/ksm/sleep_millisecs;
echo 0 > /sys/kernel/mm/ksm/merge_across_nodes;
echo 1 > /sys/kernel/mm/ksm/use_zero_pages;
echo 200 > /sys/kernel/mm/ksm/advisor_target_scan_time;
echo scan-time > /sys/kernel/mm/ksm/advisor_mode;
echo 10 > /sys/kernel/mm/ksm/advisor_max_cpu;
Analisando estatísticas do KSM:
# conferir se o kernel está varrendo:
# - deve mostrar 1 em pouco tempo se houver uso de RAM
cat /sys/kernel/mm/ksm/run;
# Script para ver economia de memória:
(
echo "Paginas compartilhadas: $(cat /sys/kernel/mm/ksm/pages_shared)";
echo "Paginas compartilhando: $(cat /sys/kernel/mm/ksm/pages_sharing)";
echo "Paginas nao mescladas: $(cat /sys/kernel/mm/ksm/pages_unshared)"
echo "Memoria economizada (KB): $(($(cat /sys/kernel/mm/ksm/pages_sharing) * 4))";
);
# Metricas principais (páginas de 4 KiB)
cat /sys/kernel/mm/ksm/pages_shared; # paginas atualmente compartilhadas
cat /sys/kernel/mm/ksm/pages_sharing; # "economia" em paginas
cat /sys/kernel/mm/ksm/full_scans; # quantas varreduras completas
3 – Criando uma VM do Alpine Linux
Nesse capítulo vou criar uma máquina virtual do Alpine Linux (alpine-docker) do zero como demonstração de como rodar uma VM manualmente, da construção do vHD até o uso com acesso remoto.
3.1 – Criando o vHD
Nossa VM alpine-docker vai ficar na pasta /storage/alpine-docker
# Diretório dos arquivos da VM:
mkdir -p /storage/alpine-docker;
Criar VHD no formato QCOW2 (q-emu copy on write) com 20 GB.
# Criar VHD QCOW2 de 20GB
qemu-img create -f qcow2 /storage/alpine-docker/vhd.qcow2 20G;
Obs: VHD com zeros preguiçosos (gerados por software), não ocupa espaço a menos que os blocos do VHD sofram escrita.
3.2 – Interface de rede virtual
Quanto entregamos uma interface de rede para uma VM, a interface tem duas faces:
- Interna: Interface PCIe para-virtual dentro da máquina virtual (driver virtio-net ou e1000). Lá dentro a VM enxerga uma placa de rede com MAC, endereço PCI-e, sobe o driver adequado para usá-la. Essa será a “eth0” dentro da VM;
- MTU: Depende da capacidade do driver e do sistema operacional da VM;
- Externa: No host Linux (hypervisor KVM) uma interface TAP contendo os buffers de TX para enviar quadros para a VM e RX para recer os quadros criados pela VM;
- MTU: Limite máximo inicial de 65.535 bytes;
Para que nossa VM navegue ela deve ter a interface TAP adicionada na bridge. Tambem é possível configurar o IP que será o gateway, dhcp, slaac direto nessa interface TAP mas isso é hard-core demais para o momento!
Quando a interface TAP da VM é criada o Q-EMU pode disparar um script de hook para que possamos fazer o que quisermos com ela. Iremos usar esse script para adicionar a TAP na bridge br-vms.
A interface TAP é iniciada com MTU padrão 1500, devemos elevá-la para o maior valor de software possível pois o MTU real é determinado pelo sistema dentro da VM mas não queremos que a interface TAP influencie negativamente o MTU da bridge.
Script de setup da interface TAP:
# Script de evento para agregar a interface TAP da VM na bridge br-vms
# - O script recebe no argumento 1 o nome da interface TAP
# criada pelo q-emu durante a construcao da VM
(
echo '#!/bin/sh';
echo;
echo '# Adicionar a bridge:';
echo 'brctl addif br-vms $1;';
echo;
echo '# Ativar:';
echo 'ip link set up dev $1;';
echo;
echo '# Log de evento:';
echo 'logger "qemu-pnet1-up: start $1 on bridge br-vms";';
echo;
) > /storage/alpine-docker/br-vms-up.sh;
# Tornar executavel:
chmod +x /storage/alpine-docker/br-vms-up.sh;
3.3 – CD/DVD e ISO para instalação da VM
Site do Alpine Linux:
- Homepage: https://www.alpinelinux.org/
- Downloads: https://www.alpinelinux.org/downloads/
Vamos baixar pelo link da relase Virtual arquitetura x86_64 (amd64).
# ISOs:
mkdir -p /storage/isos;
# Entrar na pasta:
cd /storage/isos;
# URL (base do site e caminho do arquivo)
MIRROR="https://dl-cdn.alpinelinux.org";
RELEASE="/alpine/v3.23/releases/x86_64/alpine-virt-3.23.3-x86_64.iso";
ISO_URL="$MIRROR$RELEASE";
ISO_LOCAL="/storage/isos/alpine-virt-3.23.3-x86_64.iso";
# - Baixar Alpine Linux (+- 67M):
wget \
--tries 300 \
--timeout 5 \
--read-timeout 5 \
-c \
-O "$ISO_LOCAL" \
"$ISO_URL";
3.4 – Scripts de controle da VM
Vou optar pelo boot usando DOS/MBR legacy antigo em vez de EFI.
Quero tornar a VM e o vHD mais compatível com todos os sistemas pois normalmente eu a exporto para usar no EVE-NG, VMware, Proxmox, Virtualbox, todos esses sistemas presumem o Linux em boot legacy.
Vou usar a primeira técnica de acesso via TELNET Server do q-emu que dará acesso direto a porta SERIAL da máquina virtual.
A ordem de boot será o HD (“c”) e em seguida CD/ISO (“d”). Dessa forma no primeiro boot com o HD vazio não haverá bootloader e a BIOS virtual irá tentar a segunda opção no CD/ISO. O segundo boot encontrará o bootloader no HD e não consultará mais a ISO.
O ideal é remover a ISO da VM depois de instalada para não criar uma dependência que pode faltar no futuro e comprometer a execução do Q-EMU.
O comando “kvm” é criado como link simbólico para o qemu na arquitetura local, no nosso caso “qemu-system-x86_64“.
Vamos criar os scripts para controlar nossa VM primeiro.
Script para parar graciosamente a VM: /storage/alpine-docker/shutdown.sh
#!/bin/sh
# Script para desligar graciosamente a maquina virtual 'alpine-docker'
# Variaveis
# Socket de comunicacao com a VM
MONITOR_SOCK="/storage/alpine-docker/monitor.sock"
# Enviar sinal de desligamento para a VM:
echo "# Enviando [system_powerdown] para a VM via $MONITOR_SOCK";
echo 'system_powerdown' | socat - UNIX-CONNECT:$MONITOR_SOCK;
Script para matar a força a VM: /storage/alpine-docker/kill.sh
#!/bin/sh
# Script de exterminio da maquina virtual 'alpine-docker'
# Variaveis
# Arquivo com id do processo qemu
PIDFILE="/storage/alpine-docker/qemu.pid";
# Para VM (caso esteja rodando):
kill $(head -1 $PIDFILE 2>/dev/null) 2>/dev/null;
Script para iniciar VM: /storage/alpine-docker/start.sh
#!/bin/sh
# Variaveis
# Socket de comunicacao com a VM
MONITOR_SOCK="/storage/alpine-docker/monitor.sock"
# Arquivo com id do processo qemu
PIDFILE="/storage/alpine-docker/qemu.pid";
# Ordem de boot (c=hd, d=cdrom/iso, n=network)
BOOT_ORDER="cd";
# Caminho da ISO
ISO_LOCAL="/storage/isos/alpine-virt-3.23.3-x86_64.iso";
# Porta telnet para acesso ao console serial
TELNET_BIND="0.0.0.0:23001";
# Numero de nucleos
CPUS=4;
# Disco principal
HD1="/storage/alpine-docker/vhd.qcow2";
# MAC da eth0 dentro da VM
MAC="00:ca:fe:99:00:00";
TAP="tap-cafe990000";
NETID="net0";
NETUP="/storage/alpine-docker/br-vms-up.sh";
# Para VM (caso esteja rodando):
kill $(head -1 $PIDFILE 2>/dev/null) 2>/dev/null;
# Rodar VM Alpine:
qemu-system-x86_64 \
-pidfile $PIDFILE \
-daemonize \
-machine type=pc,accel=kvm \
-monitor unix:$MONITOR_SOCK,server,nowait \
\
-boot order=$BOOT_ORDER,menu=off \
\
-cpu host \
-smp $CPUS,sockets=1,cores=$CPUS,threads=1 \
\
-drive file=$HD1,format=qcow2,if=virtio,cache=none,aio=native,discard=unmap \
\
-cdrom "$ISO_LOCAL" \
\
-m 1G \
-object memory-backend-file,id=mem0,size=1G,mem-path=/dev/hugepages,share=on \
-numa node,memdev=mem0 \
\
-rtc base=utc,clock=host \
\
-watchdog-action reset \
\
-display none \
-serial telnet:$TELNET_BIND,server,nowait \
\
-device virtio-net-pci,netdev=$NETID,mac=$MAC,mq=on,vectors=10 \
-netdev tap,id=$NETID,ifname=$TAP,script=$NETUP,vhost=on,queues=$CPUS;
# Para usar a memoria, troque a declaracao de memoria (-m) por:
#
# - SEM hugepages
# -m 1G \
# -object memory-backend-memfd,id=mem0,size=1G,share=on \
# -numa node,memdev=mem0 \
#
# - COM hugepages, troque a declaracao de memoria (-m) por
# -m 1G \
# -object memory-backend-file,id=mem0,size=1G,mem-path=/dev/hugepages,share=on \
# -numa node,memdev=mem0 \
#
Tornar os scripts executáveis:
# Tornar scripts de controle da VM executaveis no Linux
chmod +x /storage/alpine-docker/*.sh;
3.5 – Executando a VM e primeiro acesso via Serial/Telnet
Vamos rodar nossa máquina virtual!
# Iniciando VM
/storage/alpine-docker/start.sh;
Conferindo a VM rodando no Linux (host):
# Conferindo o processo rodando:
ps ax | grep qemu;
# 9254 ? Sl 0:02 qemu-system-x86_64 -pidfile
# /storage/alpine-docker/qemu.pid -daemonize -boot d
# -cdrom /storage/isos/alpine-virt-3.23.3-x86_64.iso
# -hda /storage/alpine-docker/vhd.qcow2
# -enable-kvm -m 1G -smp 4 -display none
# -serial telnet:0.0.0.0:23001,server,nowait
# -device e1000,netdev=net0,mac=00:ca:fe:99:00:00
# -netdev tap,id=net0,ifname=tap-cafe990000,
# script=/storage/alpine-docker/br-vms-up.sh
# Interfaces na bridge:
brctl show br-vms;
# bridge name bridge id STP enabled interfaces
# br-vms 8000.be963e0debf7 no tap-cafe990000
Acesso telnet (Aperte ENTER para forcar a interação na porta serial):
# Agora conecte-se a porta 23001
telnet localhost 23001;
# ou: telnet ip-publico-servidor 230001;
# Aperte ENTER para forcar a interacao na porta serial
# Trying ::1...
# Connection failed: Connection refused
# Trying 127.0.0.1...
# Connected to localhost.
# Escape character is '^]'.
#
# Welcome to Alpine Linux 3.23
# Kernel 6.18.7-0-virt on x86_64 (/dev/ttyS0)
#
# localhost login:
Login e instalação do Alpine: root , (aperte ENTER, não tem senha):
# Comando para instalar o Alpine no disco:
setup-alpine;
# Enter system hostname..............................: alpine-docker
# Which one do you want to initialize? [eth0]........: (ENTER APENAS)
# Ip address for eth0?................ [dhcp]........: (ENTER APENAS)
# Do you want to do any manual net. config (y/n) [n].: (ENTER APENAS)
#
# Which timezone are you in? [UTC]...................: America/Sao_Paulo
#
# HTTP/FTP proxy URL? [none] ........................: (ENTER APENAS)
#
# Enter mirror number or URL: [1] ...................: (ENTER APENAS)
#
# Setup a user? ............................... [no].: suporte
# Full name for user suporte [suporte] ..............: suporte
# New password.....: tulipa@@
# Bad password.....: too weak < ignore
# Retype password..: tulipa@@
# passwd: password for suporte changed by root
#
# Enter ssh key or URL for suporte (or 'none') [none]: (ENTER APENAS)
#
# Which ssh server? [openssh] .......................: (ENTER APENAS, openssh)
#
# Disk & Install
# ----------------
# Available disks are:
# fd0 (0.0 GB )
# vda (21.5 GB 0x1af4 )
#
# Which disk(s) would you like to use? [none].......: vda
# How would you like to use it? .....................: sys
# WARNING: Erase the above disk(s) and continue?.....: y
#
# Escolha:
# Desligar:
poweroff;
# ou Reiniciar:
reboot;
Parar a VM graciosamente, esperando sistemas se encerrarem:
# Parar a VM normalmente
/storage/alpine-docker/shutdown.sh;
Agora iniciando novamente o sistema final pronto. Você pode remover a linha “-cdrom …iso” do script start.sh se desejar.
# Iniciar a VM com sistema final pronto:
/storage/alpine-docker/start.sh;
3 – Usando a VM Alpine
Você pode entrar na VM por telnet (porta 23001) ou por SSH no IP que ele obteve via DHCP.
Na VM, vamos instalar os programas para que ela seja um Linux completo com Docker e ferramentas para uma nuvem particular de containers.
3.1 – Docker e FRR
Use o tutorial abaixo para fazer as instalações necesárias:
- Alpine Linux Setup: https://blog.patrickbrandao.com/alpine-setup/
Instalando Docker pontualmente:
# Instalar Docker
apk add docker;
# Ativar no boot
rc-update add docker default;
# Reiniciar:
service docker restart;
Instalando FRR pontualmente:
# Instalar FRR
apk add frr frr-rpki frr-snmp frr-pythontools;
# Ativar durante o boot:
rc-update add frr default;
# Ativar servicos de roteamento basicos:
sed -i 's/bgpd=no/bgpd=yes/' /etc/frr/daemons;
sed -i 's/ospfd=no/ospfd=yes/' /etc/frr/daemons;
sed -i 's/ospf6d=no/ospf6d=yes/' /etc/frr/daemons;
sed -i 's/pimd=no/pimd=yes/' /etc/frr/daemons;
sed -i 's/pim6d=no/pim6d=yes/' /etc/frr/daemons;
sed -i 's/babeld=no/babeld=yes/' /etc/frr/daemons;
sed -i 's/pbrd=no/pbrd=yes/' /etc/frr/daemons;
sed -i 's/bfdd=no/bfdd=yes/' /etc/frr/daemons;
# Reiniciar para ligar servicos ativados:
service frr restart;
# Criar comando no sistema: show (repassar para FRR VTYSH)
(
echo '#!/bin/sh';
echo;
echo '[ "x$1" = "x" ] && exit 1';
echo 'cmd="show $@"';
echo 'vtysh -c "$cmd"';
echo;
) > /usr/bin/show;
chmod +x /usr/bin/show;
3.2 – Transformando VM em template vHD
Se você instalar o Alpine 100x, quais arquivos serão idênticos e quais serão diferentes a cada instalação?
Para clonar uma máquina virtual sem que isso gere efeitos colaterais indesejados, precisamos mapear o que torna cada sistema Linux único.
No caso do Alpine Linux, temos os seguintes arquivos singulares:
- Usuários e senhas:
- /etc/passwd
- /etc/shadow
- /etc/group
- Histórico de comandos:
- /root/.ash_history
- /root/.bash_history
- /root/.history_frr
- Chaves do servidor SSH:
- /etc/ssh/ssh_host_ecdsa_key
- /etc/ssh/ssh_host_ecdsa_key.pub
- /etc/ssh/ssh_host_ed25519_key
- /etc/ssh/ssh_host_ed25519_key.pub
- /etc/ssh/ssh_host_rsa_key
- /etc/ssh/ssh_host_rsa_key.pub
- Logs de atividades:
- /var/log/acpid.log
- /var/log/apk.log
- /var/log/docker.log
- /var/log/qemu-ga.log
- /var/log/dmesg
Precisamos criar um mecanismo que:
- Limpar todos os logs;
- Apagar todas as chaves do SSH;
- Apagar o histórico de comandos.
Script de limpeza do ambiente pre-clonagem:
#!/bin/sh
# Apagar chaves para gerar novamente no boot
(
rm -f /etc/ssh/ssh_host_rsa_key;
rm -f /etc/ssh/ssh_host_rsa_key.pub;
rm -f /etc/ssh/ssh_host_ed25519_key.pub;
rm -f /etc/ssh/ssh_host_ed25519_key;
rm -f /etc/ssh/ssh_host_ecdsa_key;
rm -f /etc/ssh/ssh_host_ecdsa_key.pub;
) 2>/dev/null;
# Zerar arquivo existente
_zap(){
[ -f "$1" ] && echo -n > "$1";
};
_zap /var/log/acpid.log;
_zap /var/log/apk.log;
_zap /var/log/dmesg;
_zap /var/log/docker.log;
_zap /var/log/qemu-ga.log;
_zap /var/log/messages;
_zap /root/.ash_history;
_zap /root/.bash_histor;
_zap /root/.history_frr;
Agora desligue a VM (VM guest Alpine):
poweroff;Ou desligue pelo HOST:
# Parar a VM normalmente
/storage/alpine-docker/shutdown.sh;
# Forcar parada
sleep 5; /storage/alpine-docker/kill.sh;
3.3 – Clonando VM por cópia sipmles
Com a VM de origem desligada, vamos clonar o HD do da “alpine-docker” e criar o “alpine-swarm-01“:
# Criar pasta para nova VM:
mkdir -p /storage/alpine-swarm-01;
# Copiar VM do 'alpine-docker' para 'alpine-swarm'
cp -rav /storage/alpine-docker/* /storage/alpine-swarm-01/;
# Substituir strings que identificam a VM
sed -i 's#alpine-docker#alpine-swarm-01#g' /storage/alpine-swarm-01/*.sh;
sed -i 's#00:ca:fe:99:00:00#00:ca:fe:99:01:00#g' /storage/alpine-swarm-01/start.sh;
sed -i 's#cafe990000#cafe990100#g' /storage/alpine-swarm-01/start.sh;
sed -i 's#23001#23002#g' /storage/alpine-swarm-01/start.sh;
Agora você pode usar a VM “alpine-swarm-01” pelo script /storage/alpine-swarm-01/start.sh sem preocupação com qualquer informação ou identificador herdado da VM template de origem!
3.3 – Editando vHD a frio
Uma das formas de personalizar arquivos em um vHD é com a VM ligada (a quente), a outra é montando o vHD e mexendo nele com a VM desligada.
Disco a editar: /storage/alpine-docker/vhd.qcow2
Para isso existe o módulo nbd.
# Informacoes do vHD:
qemu-img info /storage/alpine-docker/vhd.qcow2;
# Ativar módulo nbd no kernel:
modprobe nbd;
# Conectar - Criar dispositivo mapeado no VHD:
qemu-nbd --connect=/dev/nbd0 /storage/alpine-docker/vhd.qcow2;
# Listar:
# fdisk -l /dev/nbd0;
# Disk /dev/nbd0: 20 GiB, 21474836480 bytes, 41943040 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 131072 bytes
# Disklabel type: dos
# Disk identifier: 0xa623429c
#
# Device Boot Start End Sectors Size Id Type
# /dev/nbd0p1 * 2048 616447 614400 300M 83 Linux
# /dev/nbd0p2 616448 4593663 3977216 1.9G 82 Linux swap / Solaris
# /dev/nbd0p3 4593664 41943039 37349376 17.8G 83 Linux
# Montar a terceira particao na pasta /mnt/vhd-p3;
mkdir /mnt/vhd-p3;
mount /dev/nbd0p3 /mnt/vhd-p3;
# Entrar no sistema usando modo chroot:
chroot /mnt/vhd-p3;
# Altere o que quiser aqui!
# Sair:
exit;
# Desmontar terceira particao:
sync;
umount /mnt/vhd-p3;
# Desconectar dispositivo mapeado no VHD:
qemu-nbd --disconnect /dev/nbd0;
3.4 – Convertendo vHD do Q-EMU em formatos universais
A única coisa que realmente importa em uma VM é o disco – vHD.
Disco atual do template:
- Caminho: /storage/alpine-docker/vhd.qcow2
- Formato: QCOW-2
Formatos usados por outros virtualizadores:
- QCOW-2: Disco virtual do Q-EMU/KVM, possui suporte a muitas tecnologias modernas de vHD para software livre como compressão, criptografia, supressão de zeros, snapshots, zero-copy, etc;
- RAW: Disco de bytes espelhados um a um, puros, usados em dispositivos de blocos. O vHD pode ser convertido em RAW (.img) e a imagem ser copiada usando “dd” para pendrive ou disco final;
- VDI (VirtualBox Disk Image): Disco virtual do VirtualBox, muito usado em Windows;
- VMDK (Virtual Machine Disk): Disco virtual do VMware;
Comandos de conversão:
# vHD de template:
VHD_QCOW2_SOURCE="/storage/alpine-docker/vhd.qcow2";
# Pasta para templates em varios formatos:
mkdir -p /storage/templates;
# - Converte QCOW2 para QCOW2 exportavel unificado e
# optimizado para backup completo: (saida: 205M)
qemu-img convert \
-c \
-O qcow2 \
$VHD_QCOW2_SOURCE /storage/templates/alpine-docker-vhd.qcow2;
# - Converter para RAW (bit a bit): (saida: 20G)
qemu-img convert \
-f qcow2 -O raw \
$VHD_QCOW2_SOURCE /storage/templates/alpine-docker-vhd.img;
# - Converter para VDI (VirtualBox): (saida: 593M)
qemu-img convert \
-f qcow2 -O vdi \
$VHD_QCOW2_SOURCE /storage/templates/alpine-docker-vhd.vdi;
# - Converter para VMDK (VMware): (saida 198M)
qemu-img convert \
-f qcow2 -O vmdk -o subformat=streamOptimized \
$VHD_QCOW2_SOURCE /storage/templates/alpine-docker-vhd.vmdk;
Exporte os arquivos de template para o sistema que desejar!
3.5 – Reparando vHD QCOW2
Em algum momento na vida da nossa VM ela sofrerá um desligamento forçado ou problemas de I/O que podem corromper o vHD, procedimentos de manutenção e reparo:
# vHD de template:
VHD_QCOW2_SOURCE="/storage/alpine-docker/vhd.qcow2";
# - Verificar:
qemu-img check $VHD_QCOW2_SOURCE;
# - Reparar:
# - Reparar leaks:
qemu-img check -r leaks $VHD_QCOW2_SOURCE;
# - Reparar tudo:
qemu-img check -r all $VHD_QCOW2_SOURCE;
Se o reparo usando “qemu-img check” não funcionar, siga esses procedimentos:
- 1 – Clonagem do arquivo corrompido para um novo, deixe o corrompido intocado e trabalhe no clone, tente subir o sistema por ele;
- 2 – Montar o nbd do clone e fazer fsck nos sistemas de arquivos das partições;
- 3 – Se nada funcionar, tente retirar os arquivos realmente úteis das montagens do nbd.
3.6 – Usando template vHD no EVE-NG
Eu uso o template “alpine-docker-vhd.qcow2” para montar laboratórios no EVE-NG, lá é mais fácil criar ambientes com várias VMs em topologias de redes hibridas.
Procedimentos para preparar a pasta do template no EVE-NG:
# 1 - Criar a pasta da VM de template
mkdir -p "/opt/unetlab/addons/qemu/linux-alpine-docker-2026-03";
# 2 - Entrar na pasta:
cd "/opt/unetlab/addons/qemu/linux-alpine-docker-2026-03";
Agora copie o vHD de template do seu ambiente Debian+Q-EMU localizado em “/storage/templates/alpine-docker-vhd.qcow2” para o servidor EVE-NG no caminho::
- /opt/unetlab/addons/qemu/linux-alpine-docker-2026-03/hda.qcow2
Pronto, agora basta rodar o Alpine-Docker dentro do EVE-NG.
Terminamos por hoje!
Patrick Brandão, patrickbrandao@gmail.com
