Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform Modular Versão 2
Eleve sua automação de infraestrutura a um novo patamar. Aprenda a estruturar seu projeto Terraform com módulos reutilizáveis para provisionar ambientes KVM/LIBVIRT de forma escalável, manutenível e eficiente.
Introdução
Este tutorial detalha como provisionar máquinas virtuais (VMs) utilizando uma combinação poderosa de Terraform, Cloud-init e Libvirt. O objetivo é fornecer um guia passo a passo para entender e replicar uma infraestrutura de VMs definida por código, focando na automação da configuração de rede e usuários.
O Terraform será utilizado para orquestrar a criação e gerenciamento das VMs e suas redes no ambiente Libvirt (KVM/QEMU). O Cloud-init, por sua vez, será responsável pela personalização inicial das VMs, como configuração de interfaces de rede, adição de usuários e definição de hostnames, garantindo que as máquinas estejam prontas para uso assim que forem provisionadas.
Ao final deste tutorial, você terá uma compreensão clara de como esses componentes se integram para criar um fluxo de trabalho eficiente e repetível para o provisionamento de infraestrutura virtual.
Pré-requisitos
Para seguir este tutorial, você precisará ter os seguintes componentes instalados e configurados em seu sistema:
Libvirt/KVM: Certifique-se de que o ambiente de virtualização KVM/LIBVIRT esteja instalado e funcionando corretamente em seu host Linux. Isso inclui o
libvirtd
e as ferramentas de linha de comando comovirsh
. Para instruções detalhadas de instalação e configuração do servidor KVM/LIBVIRT, consulte: https://geanmartins.com.br/posts/kvm-oracle-linux-7/Estação de Trabalho: Para configurar sua estação de trabalho com todas as ferramentas necessárias (Terraform, ferramentas de desenvolvimento, etc.), consulte: https://geanmartins.com.br/posts/workspace-ubuntu-2404/
Templates de Imagens: Você precisará de imagens base dos sistemas operacionais. Você pode:
- Baixar templates prontos:
- Debian: https://cloud.debian.org/images/cloud
- Oracle Linux: https://yum.oracle.com/oracle-linux-templates.html
- Criar seus próprios templates usando Packer: Para instruções sobre como criar templates personalizados, consulte: https://geanmartins.com.br/posts/tpl-deb12-qemu-packer/
- Baixar templates prontos:
Com esses pré-requisitos atendidos, você estará pronto para começar a construir sua infraestrutura virtualizada com Terraform.
Download de Imagens Cloud-Init
Para facilitar o download das imagens base compatíveis com Cloud-Init para o seu pool de templates
, você pode usar os seguintes comandos. Certifique-se de que o pool templates
esteja configurado e acessível.
Exemplo para Debian 12 (Bookworm):
1
2
3
4
5
# Navegue até o diretório do seu pool de templates (ex: /var/lib/libvirt/templates)
cd /var/lib/libvirt/templates
# Baixe a imagem mais recente do Debian 12 Cloud (amd64)
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2 -O debian-12-amd64.qcow2
Exemplo para Oracle Linux 9:
1
2
3
4
5
# Navegue até o diretório do seu pool de templates (ex: /var/lib/libvirt/templates)
cd /var/lib/libvirt/templates
# Baixe a imagem mais recente do Oracle Linux 9 Cloud (qcow2)
wget https://yum.oracle.com/templates/OracleLinux/OL9/u5/x86_64/OL9U5_x86_64-kvm-b259.qcow2 -O ol9-amd64.qcow2
Nota: Verifique sempre os links oficiais para as versões mais recentes das imagens, pois os URLs podem mudar com o tempo. Após o download, as imagens estarão prontas para serem utilizadas pelo Terraform em seu pool de templates
.
Estrutura do Projeto
O projeto está organizado em uma estrutura de diretórios modular, facilitando a reutilização e a organização do código. A seguir, a representação da estrutura e uma breve descrição de cada diretório:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.
├── cloud-init
│ ├── network_config_gateway.yml
│ ├── network_config.yml
│ └── user_data.yml
├── environments
│ └── production
│ ├── main.tf
│ ├── networks.auto.tfvars
│ ├── outputs.tf
│ ├── providers.tf
│ ├── servers.auto.tfvars
│ ├── terraform.tfvars
│ └── variables.tf
└── modules
├── compute
│ ├── cloudinit.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── network
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
└── storage
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
Descrição dos Diretórios:
cloud-init/
: Contém os templates de configuração do Cloud-init que serão injetados nas VMs no momento do provisionamento. Estes arquivos são responsáveis por configurar a rede, usuários, hostname e outras configurações iniciais.network_config.yml
: Template para configuração de interfaces de rede (IPv4 e IPv6, DHCP ou estático, DNS e rotas).user_data.yml
: Template para configuração de usuários, chaves SSH, desabilitação de login por senha e definição de hostname.
environments/
: Este diretório agrupa as configurações específicas para diferentes ambientes (ex:production
,development
,staging
). Cada subdiretório representa um ambiente distinto.environments/production/
: Contém os arquivos Terraform que definem a infraestrutura para o ambiente de produção.main.tf
: O arquivo principal que orquestra a criação dos recursos, chamando os módulos de rede, armazenamento e computação.networks.auto.tfvars
: Define as configurações das redes a serem criadas (nomes, modos, CIDRs IPv4/IPv6).outputs.tf
: Define os valores de saída que podem ser consultados após a aplicação do Terraform (ex: IP do gateway).providers.tf
: Configura os provedores Terraform necessários, neste caso, o provedorlibvirt
.servers.auto.tfvars
: Define as especificações de cada servidor/VM a ser provisionado (vCPUs, memória, OS, configurações de rede).terraform.tfvars
: Contém variáveis sensíveis ou específicas do ambiente, como a chave SSH pública e pools de armazenamento.variables.tf
: Declara as variáveis que serão utilizadas nos arquivos Terraform do ambiente.
modules/
: Contém módulos Terraform reutilizáveis. Cada módulo encapsula um conjunto de recursos relacionados, promovendo a modularidade e a reutilização do código.modules/compute/
: Módulo responsável pela criação das máquinas virtuais (domínios Libvirt) e pela injeção das configurações do Cloud-init.cloudinit.tf
: Define o recursolibvirt_cloudinit_disk
que gera os ISOs de Cloud-init para cada VM.main.tf
: Define o recursolibvirt_domain
que cria as VMs, configurando CPU, memória, discos e interfaces de rede.outputs.tf
: Define os valores de saída do módulo de computação (ex: informações dos domínios criados).variables.tf
: Declara as variáveis de entrada para o módulo de computação.versions.tf
: Define as versões mínimas dos provedores Terraform exigidos pelo módulo.
modules/network/
: Módulo responsável pela criação das redes virtuais no Libvirt.main.tf
: Define o recursolibvirt_network
que cria as redes com suas configurações de nome, modo e endereçamento.outputs.tf
: Define os valores de saída do módulo de rede (ex: informações das redes criadas).variables.tf
: Declara as variáveis de entrada para o módulo de rede.versions.tf
: Define as versões mínimas dos provedores Terraform exigidos pelo módulo.
modules/storage/
: Módulo responsável pela criação dos volumes de disco para as VMs, baseados em imagens de template.main.tf
: Define o recursolibvirt_volume
que cria os volumes de disco para cada VM, utilizando imagens base.outputs.tf
: Define os valores de saída do módulo de armazenamento (ex: informações dos volumes criados).variables.tf
: Declara as variáveis de entrada para o módulo de armazenamento.versions.tf
: Define as versões mínimas dos provedores Terraform exigidos pelo módulo.
Esta estrutura permite uma clara separação de responsabilidades e facilita a manutenção e escalabilidade da infraestrutura.
Explicação dos Arquivos de Configuração
Nesta seção, detalharemos o conteúdo de cada arquivo de configuração, explicando seu propósito e como eles contribuem para o provisionamento automatizado das VMs.
Cloud-init
Os arquivos do Cloud-init são templates que permitem a personalização das VMs no primeiro boot. Eles são processados pelo Terraform e injetados nas VMs como um disco cloudinit
.
cloud-init/network_config.yml
Este arquivo é um template Cloud-init para configurar as interfaces de rede das VMs. Ele é dinâmico e utiliza variáveis do Terraform para adaptar a configuração a cada VM e interface de rede. O template suporta configurações DHCP e estáticas, além de DNS e rotas padrão.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#cloud-config
network:
version: 2
ethernets:
%{ for iface in interfaces ~}
${iface.if_name}:
%{ if iface.ipv4 == "dhcp" }
dhcp4: true
dhcp6: true
%{ else }
dhcp4: no
dhcp6: no
accept-ra: false
addresses:
- ${iface.ipv4}/${iface.ipv4_prefix}
%{ if iface.ipv6 != "dhcp" }
- ${iface.ipv6}/${iface.ipv6_prefix}
%{ endif }
# Configuração de DNS (global para todas as interfaces estáticas)
%{ if !has_dhcp_interface }
%{ if hostname == "ns1" || hostname == "ns2" }
nameservers:
addresses:
- 127.0.0.1
- "::1"
%{ else }
nameservers:
addresses:
- ${global_dns.ns1_v4}
- ${global_dns.ns2_v4}
- ${global_dns.ns1_v6}
- ${global_dns.ns2_v6}
%{ endif }
%{ endif }
# Aplica rota APENAS se for o gateway padrão
%{ if contains(keys(network_gateways), iface.name) && try(iface.is_default_gateway, false) }
routes:
- to: 0.0.0.0/0
via: ${network_gateways[iface.name].gateway_v4}
%{ if iface.ipv6 != "dhcp" }
- to: "::/0"
via: ${network_gateways[iface.name].gateway_v6}
%{ endif }
%{ endif }
%{ endif }
%{ endfor ~}
Explicação Detalhada:
network: version: 2
: Define a versão do formato de configuração de rede do Cloud-init.ethernets:
: Bloco que contém as configurações para cada interface de rede.%{ for iface in interfaces ~}
: Loop Terraform que itera sobre a lista de interfaces de rede (interfaces
) definida para cada VM noservers.auto.tfvars
.${iface.if_name}
: Nome da interface de rede (ex:ens3
,ens4
).%{ if iface.ipv4 == "dhcp" }
: Condicional que verifica se a interface deve obter um endereço IPv4 via DHCP.dhcp4: true
edhcp6: true
: Habilita DHCP para IPv4 e IPv6.
%{ else }
: Bloco executado se a interface não for DHCP (configuração estática).dhcp4: no
,dhcp6: no
,accept-ra: false
: Desabilita DHCP e aceitação de Router Advertisements.addresses:
: Define os endereços IP estáticos para a interface.- ${iface.ipv4}/${iface.ipv4_prefix}
: Endereço IPv4 e prefixo de rede.%{ if iface.ipv6 != "dhcp" }
: Condicional para configurar IPv6 estático se não for DHCP.- ${iface.ipv6}/${iface.ipv6_prefix}
: Endereço IPv6 e prefixo de rede.
# Configuração de DNS (global para todas as interfaces estáticas)
: Comentário indicando a seção de DNS.%{ if !has_dhcp_interface }
: Condicional que aplica a configuração de DNS apenas se nenhuma interface na VM estiver configurada com DHCP. Isso evita conflitos com DNSs fornecidos via DHCP.%{ if hostname == "ns1" || hostname == "ns2" }
: Condicional específica para os servidores DNS (ns1
,ns2
), que usarão127.0.0.1
e::1
como nameservers (loopback), indicando que eles mesmos serão os resolvedores de DNS.%{ else }
: Para outras VMs, utiliza os DNSs globais definidos emglobal_dns
(vindos deterraform.tfvars
).
# Aplica rota APENAS se for o gateway padrão
: Comentário indicando a seção de rotas.%{ if contains(keys(network_gateways), iface.name) && try(iface.is_default_gateway, false) }
: Condicional complexa que verifica se a interface atual (iface.name
) está listada como um gateway de rede e seis_default_gateway
está definido comotrue
para essa interface. Isso garante que a rota padrão (0.0.0.0/0 e ::/0) seja aplicada apenas à interface designada como gateway principal.routes:
: Bloco para definir rotas estáticas.- to: 0.0.0.0/0 via: ${network_gateways[iface.name].gateway_v4}
: Rota padrão IPv4.%{ if iface.ipv6 != "dhcp" }
: Condicional para rota padrão IPv6 se a interface não for DHCP.- to: "::/0" via: ${network_gateways[iface.name].gateway_v6}
: Rota padrão IPv6.
Este template é um exemplo robusto de como o Cloud-init pode ser parametrizado com Terraform para lidar com diversas configurações de rede de forma automatizada e flexível.
cloud-init/user_data.yml
Este arquivo Cloud-init é responsável pela configuração de usuários, chaves SSH, desabilitação de login por senha e definição do hostname da VM. Ele garante que as VMs estejam seguras e acessíveis via SSH com a chave pública fornecida.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#cloud-config
manage_etc_hosts: true
# Template para dados de usuário
users:
- name: ${user_name}
gecos: ${gecos}
sudo: ALL=(ALL) NOPASSWD:ALL
groups: ${jsonencode(groups)}
shell: /bin/bash
lock_passwd: true
ssh_authorized_keys:
- "${ssh_key}"
# Desabilita login por senha e do usuário root
disable_root: true
ssh_pwauth: false
# Define o hostname da máquina
runcmd:
- hostnamectl set-hostname ${hostname}
- echo '127.0.1.1 ${hostname}' >> /etc/hosts
Explicação Detalhada:
manage_etc_hosts: true
: Garante que o Cloud-init gerencie o arquivo/etc/hosts
, adicionando uma entrada para o hostname da máquina.users:
: Bloco para definir os usuários que serão criados na VM.- name: ${user_name}
: Define o nome do usuário, que é parametrizado via Terraform (vindo dedefault_vm_user
ou sobrescrito por servidor).gecos: ${gecos}
: Informações GECOS (General Electric Comprehensive Operating System), geralmente o nome completo do usuário.sudo: ALL=(ALL) NOPASSWD:ALL
: Concede privilégios de sudo ao usuário sem a necessidade de senha.groups: ${jsonencode(groups)}
: Define os grupos aos quais o usuário pertencerá. Os grupos são passados como uma lista JSON, vindo dos perfis de OS definidos emterraform.tfvars
.shell: /bin/bash
: Define o shell padrão para o usuário como Bash.lock_passwd: true
: Bloqueia a senha do usuário, forçando o acesso via chave SSH.ssh_authorized_keys:
: Lista de chaves SSH públicas autorizadas para o usuário.- "${ssh_key}"
: A chave SSH pública é injetada aqui, vinda da variávelssh_public_key
emterraform.tfvars
.
disable_root: true
: Desabilita o login direto para o usuárioroot
.ssh_pwauth: false
: Desabilita a autenticação por senha via SSH, aumentando a segurança.runcmd:
: Lista de comandos que serão executados na VM após a inicialização.- hostnamectl set-hostname ${hostname}
: Define o hostname da máquina, utilizando o nome da VM definido no Terraform.- echo '127.0.1.1 ${hostname}' >> /etc/hosts
: Adiciona uma entrada para o hostname no arquivo/etc/hosts
, mapeando-o para o endereço de loopback. Isso ajuda na resolução local do hostname.
Este arquivo é crucial para a segurança e acessibilidade das VMs, garantindo que apenas usuários autorizados com chaves SSH válidas possam acessá-las e que o hostname esteja corretamente configurado para identificação na rede.
Ambientes (Environments)
O diretório environments/production
contém a configuração específica para o ambiente de produção, orquestrando a criação de redes, volumes e máquinas virtuais.
environments/production/main.tf
Este é o arquivo principal do ambiente de produção. Ele atua como um orquestrador, chamando os módulos Terraform definidos no diretório modules
para construir a infraestrutura completa. Ele define as dependências entre os módulos, garantindo que os recursos sejam criados na ordem correta (por exemplo, redes antes das máquinas virtuais).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module "network" {
source = "../../modules/network"
networks = var.networks
}
module "storage" {
source = "../../modules/storage"
servers = var.servers
os_profiles = var.os_profiles
volume_pool = var.volume_pool
base_volume_pool = var.base_volume_pool
}
module "compute" {
source = "../../modules/compute"
servers = var.servers
default_vm_user = var.default_vm_user
os_profiles = var.os_profiles
ssh_public_key = var.ssh_public_key
network_dmz = var.network_dmz
network_cgr = var.network_cgr
defaults_dns = var.defaults_dns
volume_pool = var.volume_pool
volumes = module.storage.volumes
# Passa as redes como dependência
network_resources = module.network.networks
}
Explicação Detalhada:
module "network"
: Este bloco invoca o módulonetwork
localizado em../../modules/network
. Ele é responsável por criar as redes virtuais no Libvirt. A variávelnetworks
é passada para o módulo, que contém as definições de cada rede (nome, modo, CIDRs IPv4/IPv6).source = "../../modules/network"
: Caminho relativo para o diretório do módulo de rede.networks = var.networks
: Mapeia a variávelnetworks
(definida emnetworks.auto.tfvars
) para a entrada correspondente no módulo de rede.
module "storage"
: Este bloco invoca o módulostorage
localizado em../../modules/storage
. Ele é responsável por criar os volumes de disco para as VMs, utilizando imagens base. As variáveisservers
,os_profiles
,volume_pool
ebase_volume_pool
são passadas para o módulo.source = "../../modules/storage"
: Caminho relativo para o diretório do módulo de armazenamento.servers = var.servers
: Mapeia a variávelservers
(definida emservers.auto.tfvars
) para a entrada correspondente no módulo de armazenamento.os_profiles = var.os_profiles
: Mapeia a variávelos_profiles
(definida emterraform.tfvars
) para a entrada correspondente no módulo de armazenamento.volume_pool = var.volume_pool
: Mapeia a variávelvolume_pool
(definida emterraform.tfvars
) para a entrada correspondente no módulo de armazenamento.base_volume_pool = var.base_volume_pool
: Mapeia a variávelbase_volume_pool
(definida emterraform.tfvars
) para a entrada correspondente no módulo de armazenamento.
module "compute"
: Este bloco invoca o módulocompute
localizado em../../modules/compute
. Ele é responsável por criar as máquinas virtuais e injetar as configurações do Cloud-init. Este módulo recebe uma série de variáveis, incluindo as definições dos servidores, informações de usuário padrão, perfis de OS, chave SSH pública, gateways de rede e DNSs padrão. É importante notar que ele também recebe osvolumes
(saída do módulostorage
) e osnetwork_resources
(saída do módulonetwork
) como dependências, garantindo que os discos e as redes existam antes das VMs serem criadas.source = "../../modules/compute"
: Caminho relativo para o diretório do módulo de computação.servers = var.servers
: Mapeia a variávelservers
(definida emservers.auto.tfvars
) para a entrada correspondente no módulo de computação.default_vm_user = var.default_vm_user
: Mapeia a variáveldefault_vm_user
(definida emterraform.tfvars
) para a entrada correspondente no módulo de computação.os_profiles = var.os_profiles
: Mapeia a variávelos_profiles
(definida emterraform.tfvars
) para a entrada correspondente no módulo de computação.ssh_public_key = var.ssh_public_key
: Mapeia a variávelssh_public_key
(definida emterraform.tfvars
) para a entrada correspondente no módulo de computação.network_dmz
,network_cgr
,defaults_dns
: Mapeiam as variáveis de configuração de rede e DNS (definidas emterraform.tfvars
) para as entradas correspondentes no módulo de computação.volumes = module.storage.volumes
: Passa a saídavolumes
do módulostorage
como entrada para o módulocompute
. Isso estabelece uma dependência implícita, garantindo que os volumes sejam criados antes de serem anexados às VMs.network_resources = module.network.networks
: Passa a saídanetworks
do módulonetwork
como entrada para o módulocompute
. Isso também estabelece uma dependência implícita, garantindo que as redes estejam disponíveis antes de serem configuradas nas interfaces das VMs.
Este arquivo main.tf
demonstra a capacidade do Terraform de compor uma infraestrutura complexa a partir de módulos menores e reutilizáveis, gerenciando as dependências entre os recursos de forma eficiente.
environments/production/networks.auto.tfvars
Este arquivo define as configurações das redes virtuais que serão criadas no ambiente de produção. O Terraform carrega automaticamente arquivos com a extensão .auto.tfvars
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
networks = {
external = {
net_name = "external"
net_mode = "nat"
ipv4_cidr = "192.168.100.0/24"
ipv6_cidr = "fd12:ee::/64"
},
dmz = {
net_name = "dmz"
net_mode = "none"
ipv4_cidr = "10.32.16.0/24"
ipv6_cidr = "fd00:32:16::/64"
},
cgr = {
net_name = "cgr"
net_mode = "none"
ipv4_cidr = "10.48.32.0/24"
ipv6_cidr = "fd00:48:32::/64"
},
dhcp = {
net_name = "dhcp"
net_mode = "none"
ipv4_cidr = "10.128.112.0/20"
ipv6_cidr = "fd00:128:112::/64"
}
public = {
net_name = "public"
net_mode = "none"
ipv4_cidr = "203.0.113.0/24"
ipv6_cidr = "2001:db8:feed::/64"
}
}
Explicação Detalhada:
networks = { ... }
: Define um mapa de objetos, onde cada chave representa o nome lógico de uma rede (ex:external
,dmz
,cgr
,dhcp
,public
).- Para cada rede, são definidos os seguintes atributos:
net_name
: O nome real da rede que será criada no Libvirt. É uma boa prática que seja o mesmo nome lógico para clareza.net_mode
: O modo de operação da rede. Pode sernat
(para redes com NAT, permitindo acesso externo) ounone
(para redes isoladas, sem NAT).ipv4_cidr
: O bloco CIDR IPv4 para a rede (ex:192.168.100.0/24
).ipv6_cidr
: O bloco CIDR IPv6 para a rede (ex:fd12:ee::/64
).
Este arquivo permite que as configurações de rede sejam facilmente modificadas e estendidas sem alterar o código principal do Terraform, seguindo o princípio de separação de variáveis de configuração.
environments/production/outputs.tf
Este arquivo define os valores de saída (outputs) que o Terraform pode expor após a aplicação da configuração. Outputs são úteis para extrair informações importantes da infraestrutura provisionada, que podem ser usadas por outros módulos, scripts ou para consulta manual.
1
2
3
output "gateway_ip" {
value = module.compute.domains["gateway"].network_interface[0].addresses[0]
}
Explicação Detalhada:
output "gateway_ip" { ... }
: Declara um output chamadogateway_ip
.value = module.compute.domains["gateway"].network_interface[0].addresses[0]
: Define o valor do output. Neste caso, ele está extraindo o primeiro endereço IP (IPv4) da primeira interface de rede da VM chamadagateway
que foi provisionada pelo módulocompute
. Isso é útil para saber o endereço IP do gateway principal após o provisionamento.
Outputs são uma forma eficaz de tornar informações da infraestrutura acessíveis e reutilizáveis.
environments/production/providers.tf
Este arquivo configura os provedores Terraform necessários para o ambiente de produção. Ele especifica qual provedor será usado e qual versão é exigida, garantindo a compatibilidade e a consistência do ambiente.
1
2
3
4
5
6
7
8
9
10
11
12
13
terraform {
required_version = ">= 1.5"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
Explicação Detalhada:
terraform { ... }
: Bloco de configuração global do Terraform.required_version = ">= 1.5"
: Define a versão mínima do Terraform CLI necessária para executar esta configuração. Isso ajuda a evitar problemas de compatibilidade.required_providers { ... }
: Declara os provedores Terraform que esta configuração requer.libvirt = { ... }
: Define o provedorlibvirt
.source = "dmacvicar/libvirt"
: Especifica a origem do provedor no Terraform Registry. Isso garante que o Terraform baixe o provedor correto.version = "0.8.3"
: Fixa a versão do provedorlibvirt
a ser utilizada. É uma boa prática fixar as versões para garantir que as operações do Terraform sejam consistentes ao longo do tempo.
provider "libvirt" { ... }
: Bloco de configuração específica para o provedorlibvirt
.uri = "qemu:///system"
: Define o URI de conexão para o daemon Libvirt.qemu:///system
é o URI padrão para conectar ao Libvirt como root, gerenciando VMs em todo o sistema. Certifique-se de que o usuário que executa o Terraform tenha as permissões adequadas para se conectar a este URI.
Este arquivo é fundamental para garantir que o Terraform possa interagir corretamente com o ambiente Libvirt e provisionar os recursos conforme o esperado.
environments/production/servers.auto.tfvars
Este arquivo define as especificações detalhadas de cada máquina virtual (VM) que será provisionada. Assim como networks.auto.tfvars
, ele é carregado automaticamente pelo Terraform.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
servers = {
gateway = {
vcpus = 2,
memory = "2048",
os = "debian12",
networks = [
{ name = "external", ipv4 = "dhcp", ipv6 = "dhcp", if_name = "ens3", is_default_gateway = true },
{ name = "dmz", ipv4 = "10.32.16.1", ipv4_prefix = 24, ipv6 = "fd00:32:16::1", ipv6_prefix = 64, if_name = "ens4", is_default_gateway = false },
{ name = "cgr", ipv4 = "10.48.32.1", ipv4_prefix = 24, ipv6 = "fd00:48:32::1", ipv6_prefix = 64, if_name = "ens5", is_default_gateway = false },
{ name = "dhcp", ipv4 = "10.128.112.1", ipv4_prefix = 24, ipv6 = "fd00:128:112::1", ipv6_prefix = 64, if_name = "ens6", is_default_gateway = false },
{ name = "public", ipv4 = "203.0.113.1", ipv4_prefix = 24, ipv6 = "2001:db8:feed::1", ipv6_prefix = 64, if_name = "ens7", is_default_gateway = false }
]
},
# Hosts da rede DMZ
ns1 = {
vcpus = 2,
memory = "2048",
os = "oracle9",
networks = [
{ name = "dmz", ipv4 = "10.32.16.3", ipv4_prefix = 24, ipv6 = "fd00:32:16::3", ipv6_prefix = 64, if_name = "ens3", is_default_gateway = true },
{ name = "public", ipv4 = "203.0.113.3", ipv4_prefix = 24, ipv6 = "2001:db8:feed::3", ipv6_prefix = 64, if_name = "ens4", is_default_gateway = false }
]
},
ns2 = {
vcpus = 2,
memory = "2048",
os = "oracle9",
networks = [
{ name = "dmz", ipv4 = "10.32.16.4", ipv4_prefix = 24, ipv6 = "fd00:32:16::4", ipv6_prefix = 64, if_name = "ens3", is_default_gateway = true },
{ name = "public", ipv4 = "203.0.113.4", ipv4_prefix = 24, ipv6 = "2001:db8:feed::4", ipv6_prefix = 64, if_name = "ens4", is_default_gateway = true }
]
},
# Hosts da rede CGR
cgr-linux = {
vcpus = 2,
memory = "2048",
os = "ubuntu24",
networks = [{ name = "cgr", ipv4 = "10.48.32.2", ipv4_prefix = 24, ipv6 = "fd00:48:32::2", ipv6_prefix = 64, if_name = "ens3", is_default_gateway = true }]
}
}
Explicação Detalhada:
servers = { ... }
: Define um mapa de objetos, onde cada chave representa o nome lógico de uma VM (ex:gateway
,ns1
,ns2
,cgr-linux
).- Para cada VM, são definidos os seguintes atributos:
vcpus
: Número de vCPUs alocadas para a VM.memory
: Quantidade de memória RAM alocada para a VM (em MB).os
: O nome do perfil do sistema operacional a ser usado, que corresponde a uma entrada emos_profiles
definida emterraform.tfvars
(ex:debian12
,oracle9
,ubuntu24
). Este perfil determina a imagem base e os grupos padrão do usuário.networks
: Uma lista de objetos, onde cada objeto representa uma interface de rede para a VM. Para cada interface:name
: O nome da rede Libvirt à qual a interface será conectada (deve corresponder a uma rede definida emnetworks.auto.tfvars
).ipv4
: Endereço IPv4 da interface. Pode serdhcp
para obter um endereço dinamicamente ou um endereço IP estático.ipv4_prefix
: (Opcional) Prefixo de rede IPv4 para endereços estáticos (ex:24
).ipv6
: Endereço IPv6 da interface. Pode serdhcp
ou um endereço IP estático.ipv6_prefix
: (Opcional) Prefixo de rede IPv6 para endereços estáticos (ex:64
).if_name
: O nome da interface de rede dentro da VM (ex:ens3
,ens4
).is_default_gateway
: (Opcional) Um booleano que indica se esta interface deve ser configurada como o gateway padrão da VM. Usado no templatenetwork_config.yml
do Cloud-init.
Este arquivo é o coração da definição da infraestrutura de computação, permitindo a especificação detalhada de cada VM e suas conexões de rede.
environments/production/terraform.tfvars
Este arquivo contém valores para as variáveis de entrada do Terraform que são específicas para o ambiente de produção. Ele é o local ideal para armazenar configurações sensíveis (como chaves SSH) ou valores que variam entre ambientes (como pools de armazenamento e perfis de OS). O Terraform carrega automaticamente variáveis definidas neste arquivo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# Chave SSH usada para acessar as VMs
#terraform plan -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"
#terraform apply -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"
ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzaHM66H9EFpX/6aYcvFH85eHjyAsMVRk8DbfQhxxmI gean@inspiron"
# Pools
volume_pool = "default"
base_volume_pool = "templates"
# Perfis de SO
os_profiles = {
"debian12" = {
template_name = "debian-12-amd64.qcow2",
default_groups = ["users", "sudo"]
},
"oracle9" = {
template_name = "ol9-amd64.qcow2",
default_groups = ["users", "wheel"]
}
"ubuntu24" = {
template_name = "ubuntu-24-amd64.qcow2",
default_groups = ["users", "sudo"]
}
}
# Usuário padrão para todas as VMs
default_vm_user = {
name = "suporte"
gecos = "Suporte User"
}
# Gateway das redes
network_dmz = {
gateway_v4 = "10.32.16.1",
gateway_v6 = "fd00:32:16::1"
}
network_cgr = {
gateway_v4 = "10.48.32.1",
gateway_v6 = "fd00:48:32::1"
}
# Defaults DNSs para as redes
defaults_dns = {
ns1_v4 = "10.32.16.3",
ns1_v6 = "fd00:32:16::3",
ns2_v4 = "10.32.16.4",
ns2_v6 = "fd00:32:16::4"
}
Explicação Detalhada:
ssh_public_key
: Contém a chave SSH pública que será injetada nas VMs via Cloud-init. É crucial para o acesso seguro às máquinas. As linhas comentadas mostram como passar a chave diretamente via linha de comando, mas defini-la aqui é mais conveniente para o ambiente.volume_pool
: Define o pool de armazenamento Libvirt onde os volumes de disco das VMs serão criados (padrão:default
).base_volume_pool
: Define o pool de armazenamento Libvirt onde as imagens base dos sistemas operacionais (templates) estão localizadas (padrão:templates
).os_profiles
: Um mapa que define perfis para diferentes sistemas operacionais. Cada perfil especifica:template_name
: O nome do arquivo da imagem base do sistema operacional no poolbase_volume_pool
(ex:debian-12-amd64.qcow2
).default_groups
: Uma lista de grupos padrão aos quais o usuário criado via Cloud-init será adicionado (ex:users
,sudo
,wheel
).
default_vm_user
: Define o usuário padrão que será criado em todas as VMs, a menos que seja sobrescrito por uma VM específica emservers.auto.tfvars
.name
: Nome de usuário (ex:suporte
).gecos
: Nome completo ou descrição do usuário (ex:Suporte User
).
network_dmz
,network_cgr
: Mapas que definem os endereços IPv4 e IPv6 dos gateways para as redes DMZ e CGR, respectivamente. Usados na configuração de rotas via Cloud-init.defaults_dns
: Define os endereços IPv4 e IPv6 dos servidores DNS padrão para as redes. Usados na configuração de DNS via Cloud-init.
Este arquivo centraliza as configurações que podem mudar entre diferentes implantações ou ambientes, tornando o projeto mais flexível e fácil de gerenciar.
environments/production/variables.tf
Este arquivo declara as variáveis de entrada que são esperadas pela configuração Terraform no ambiente de produção. Ele define o nome, tipo e, opcionalmente, uma descrição e um valor padrão para cada variável. As variáveis declaradas aqui são preenchidas pelos arquivos .tfvars
(como terraform.tfvars
, networks.auto.tfvars
, servers.auto.tfvars
) ou passadas via linha de comando.
1
2
3
4
5
6
7
8
9
10
variable "servers" { type = map(any) }
variable "networks" { type = map(any) }
variable "ssh_public_key" { type = string }
variable "os_profiles" { type = map(any) }
variable "default_vm_user" { type = map(any) }
variable "network_dmz" { type = object(any) }
variable "network_cgr" { type = object(any) }
variable "defaults_dns" { type = object(any) }
variable "volume_pool" { type = string }
variable "base_volume_pool" { type = string }
Explicação Detalhada:
Cada bloco variable
declara uma variável:
variable "servers" { type = map(any) }
: Declara a variávelservers
, que é um mapa de qualquer tipo. Esta variável é preenchida pelo conteúdo deservers.auto.tfvars
e contém as definições de cada VM.variable "networks" { type = map(any) }
: Declara a variávelnetworks
, um mapa de qualquer tipo. Preenchida pornetworks.auto.tfvars
, contém as definições das redes.variable "ssh_public_key" { type = string }
: Declara a variávelssh_public_key
, uma string que representa a chave SSH pública. Preenchida porterraform.tfvars
.variable "os_profiles" { type = map(any) }
: Declara a variávelos_profiles
, um mapa de qualquer tipo. Preenchida porterraform.tfvars
, contém os perfis dos sistemas operacionais.variable "default_vm_user" { type = map(any) }
: Declara a variáveldefault_vm_user
, um mapa de qualquer tipo. Preenchida porterraform.tfvars
, contém as informações do usuário padrão para as VMs.variable "network_dmz" { type = object(any) }
: Declara a variávelnetwork_dmz
, um objeto de qualquer tipo. Preenchida porterraform.tfvars
, contém as informações do gateway da rede DMZ.variable "network_cgr" { type = object(any) }
: Declara a variávelnetwork_cgr
, um objeto de qualquer tipo. Preenchida porterraform.tfvars
, contém as informações do gateway da rede CGR.variable "defaults_dns" { type = object(any) }
: Declara a variáveldefaults_dns
, um objeto de qualquer tipo. Preenchida porterraform.tfvars
, contém as informações dos servidores DNS padrão.variable "volume_pool" { type = string }
: Declara a variávelvolume_pool
, uma string. Preenchida porterraform.tfvars
, define o pool de armazenamento para os volumes.variable "base_volume_pool" { type = string }
: Declara a variávelbase_volume_pool
, uma string. Preenchida porterraform.tfvars
, define o pool de armazenamento para as imagens base.
Este arquivo é essencial para documentar as entradas esperadas pela configuração e para permitir que o Terraform valide os tipos de dados fornecidos.
Módulos (Modules)
Os módulos Terraform são blocos de construção reutilizáveis que encapsulam um conjunto de recursos. Eles promovem a modularidade, a organização e a reutilização do código, permitindo que a infraestrutura seja definida de forma mais limpa e escalável.
Módulo compute
O módulo compute
é responsável por provisionar as máquinas virtuais (domínios Libvirt) e integrar as configurações do Cloud-init para personalização inicial.
modules/compute/cloudinit.tf
Este arquivo define o recurso libvirt_cloudinit_disk
, que é responsável por criar os discos de Cloud-init (arquivos ISO) que serão anexados às VMs. Esses discos contêm as configurações de rede e usuário geradas a partir dos templates user_data.yml
e network_config.yml
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
resource "libvirt_cloudinit_disk" "cloudinit" {
for_each = var.servers
name = "cloudinit-${each.key}.iso"
pool = var.volume_pool
user_data = templatefile("${path.module}/../../cloud-init/user_data.yml", {
hostname = each.key
user_name = coalesce(each.value.username, var.default_vm_user.name)
gecos = coalesce(each.value.gecos, var.default_vm_user.gecos)
groups = coalesce(each.value.groups, var.os_profiles[each.value.os].default_groups)
ssh_key = var.ssh_public_key
})
network_config = templatefile(
"${path.module}/../../cloud-init/network_config.yml",
{
hostname = each.key
interfaces = each.value.networks
network_gateways = {
dmz = var.network_dmz,
cgr = var.network_cgr
}
global_dns = var.defaults_dns
has_dhcp_interface = length([for iface in each.value.networks : iface if iface.ipv4 == "dhcp"]) > 0
}
)
}
Explicação Detalhada:
resource "libvirt_cloudinit_disk" "cloudinit" { ... }
: Declara um recurso do tipolibvirt_cloudinit_disk
com o nome localcloudinit
.for_each = var.servers
: Este meta-argumento faz com que o Terraform crie uma instância deste recurso para cada entrada no mapavar.servers
(que vem deservers.auto.tfvars
). Isso significa que um disco Cloud-init será gerado para cada VM definida.name = "cloudinit-${each.key}.iso"
: Define o nome do arquivo ISO do Cloud-init.each.key
refere-se ao nome da VM (ex:gateway
,ns1
).pool = var.volume_pool
: Especifica o pool de armazenamento Libvirt onde o disco Cloud-init será criado.user_data = templatefile(...)
: Gera o conteúdo douser_data
a partir do templateuser_data.yml
localizado em../../cloud-init/user_data.yml
. As variáveis passadas para o template são:hostname
: O nome da VM (each.key
).user_name
,gecos
,groups
: Utiliza a funçãocoalesce
para usar o valor específico da VM (each.value.username
,each.value.gecos
,each.value.groups
) se estiver definido, caso contrário, usa o valor padrão devar.default_vm_user
ouvar.os_profiles
.ssh_key
: A chave SSH pública fornecida emvar.ssh_public_key
.
network_config = templatefile(...)
: Gera o conteúdo donetwork_config
a partir do templatenetwork_config.yml
localizado em../../cloud-init/network_config.yml
. As variáveis passadas para o template são:hostname
: O nome da VM (each.key
).interfaces
: A lista de interfaces de rede definida para a VM (each.value.networks
).network_gateways
: Um mapa contendo os gateways para as redes DMZ e CGR, vindos devar.network_dmz
evar.network_cgr
.global_dns
: Os servidores DNS globais definidos emvar.defaults_dns
.has_dhcp_interface
: Uma expressão booleana que verifica se alguma interface da VM está configurada para usar DHCP. Isso é usado no templatenetwork_config.yml
para controlar a aplicação de configurações de DNS.
Este arquivo é a ponte entre as definições de infraestrutura do Terraform e a personalização interna das VMs via Cloud-init, garantindo que cada VM seja configurada de acordo com suas especificações.
modules/compute/main.tf
Este arquivo define o recurso principal do módulo compute
: a máquina virtual Libvirt (libvirt_domain
). Ele configura os parâmetros de hardware da VM e anexa os discos de sistema operacional e Cloud-init, além de configurar as interfaces de rede.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
resource "libvirt_domain" "domain" {
for_each = var.servers
name = each.key
memory = each.value.memory
vcpu = each.value.vcpus
cpu { mode = "host-passthrough" }
depends_on = [
var.network_resources
]
cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id
disk { volume_id = var.volumes[each.key].id }
dynamic "network_interface" {
for_each = each.value.networks
content {
network_name = network_interface.value.name
wait_for_lease = network_interface.value.ipv4 == "dhcp" ? true : false
}
}
console {
type = "pty"
target_type = "serial"
target_port = "0"
}
graphics {
type = "spice"
listen_type = "address"
autoport = true
}
}
Explicação Detalhada:
resource "libvirt_domain" "domain" { ... }
: Declara um recurso do tipolibvirt_domain
(máquina virtual) com o nome localdomain
.for_each = var.servers
: Assim como nocloudinit.tf
, este meta-argumento garante que uma VM seja criada para cada servidor definido emvar.servers
.name = each.key
: Define o nome da VM no Libvirt, usando o nome lógico do servidor (ex:gateway
,ns1
).memory = each.value.memory
: Define a quantidade de memória RAM da VM, vinda da definição do servidor.vcpu = each.value.vcpus
: Define o número de vCPUs da VM, vindo da definição do servidor.cpu { mode = "host-passthrough" }
: Configura a CPU da VM para usar o modohost-passthrough
, o que permite que a VM utilize as mesmas capacidades de CPU do host físico, otimizando o desempenho.depends_on = [ var.network_resources ]
: Esta é uma dependência explícita. Garante que as redes definidas no módulonetwork
sejam criadas e estejam prontas antes que as VMs tentem se conectar a elas. Embora o Terraform geralmente infira dependências, em alguns casos, como este, uma dependência explícita pode ser útil para garantir a ordem correta.cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id
: Anexa o disco Cloud-init gerado pelo recursolibvirt_cloudinit_disk
(definido emcloudinit.tf
) à VM. Oid
refere-se ao identificador único do disco Cloud-init.disk { volume_id = var.volumes[each.key].id }
: Anexa o volume de disco do sistema operacional à VM.var.volumes
é a saída do módulostorage
, que contém os IDs dos volumes criados para cada servidor.dynamic "network_interface" { ... }
: Um blocodynamic
que itera sobre a lista de redes definida para cada servidor (each.value.networks
). Isso permite criar múltiplas interfaces de rede para uma única VM.network_name = network_interface.value.name
: Define o nome da rede Libvirt à qual esta interface será conectada.wait_for_lease = network_interface.value.ipv4 == "dhcp" ? true : false
: Se a interface estiver configurada para DHCP, o Terraform aguardará a obtenção de um lease de IP antes de considerar a interface pronta. Isso é útil para garantir que a VM tenha conectividade de rede logo após o boot.
console { ... }
: Configura um console serial para a VM, útil para depuração e acesso direto ao sistema operacional da VM.type = "pty"
,target_type = "serial"
,target_port = "0"
: Configura um console serial via PTY (pseudo-terminal).
graphics { ... }
: Configura a interface gráfica da VM, permitindo acesso via SPICE (Simple Protocol for Independent Computing Environments).type = "spice"
,listen_type = "address"
,autoport = true
: Habilita o acesso SPICE, permitindo que o Libvirt atribua uma porta automaticamente.
Este arquivo é o cerne do provisionamento das VMs, definindo suas características de hardware e conectividade, e integrando as configurações de inicialização via Cloud-init.
modules/compute/outputs.tf
Este arquivo define os valores de saída do módulo compute
. Os outputs permitem que informações sobre as VMs provisionadas sejam acessadas por outros módulos ou pelo ambiente raiz do Terraform.
1
2
3
output "domains" {
value = libvirt_domain.domain
}
Explicação Detalhada:
output "domains" { ... }
: Declara um output chamadodomains
.value = libvirt_domain.domain
: O valor deste output é o mapa completo dos recursoslibvirt_domain
criados por este módulo. Isso significa que todas as propriedades das VMs (como IPs, nomes, interfaces de rede, etc.) estarão disponíveis para serem referenciadas em outras partes da configuração Terraform (como nooutputs.tf
do ambiente de produção, que extrai o IP do gateway).
Este output é fundamental para permitir a interconexão entre os módulos e a extração de informações importantes da infraestrutura de computação.
modules/compute/variables.tf
Este arquivo declara as variáveis de entrada esperadas pelo módulo compute
. Ele define a estrutura e os tipos de dados das informações que o módulo precisa para criar as máquinas virtuais e configurar o Cloud-init.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
variable "servers" {
type = map(object({
username = optional(string)
gecos = optional(string)
groups = optional(list(string))
os = string
vcpus = number
memory = string
networks = list(object({
name = string
ipv4 = string
ipv4_prefix = optional(number)
ipv6 = string
ipv6_prefix = optional(number)
if_name = string
is_default_gateway = optional(bool)
}))
}))
}
variable "default_vm_user" {
type = object({
name = string
gecos = string
})
}
variable "os_profiles" {
type = map(object({
default_groups = list(string)
}))
}
variable "ssh_public_key" {
type = string
sensitive = true
}
# Declaração das variáveis de gateway
variable "network_dmz" {
type = object({
gateway_v4 = string
gateway_v6 = string
})
}
variable "network_cgr" {
type = object({
gateway_v4 = string
gateway_v6 = string
})
}
variable "defaults_dns" {
description = "Servidores DNS padrão para todas as redes (IPv4/IPv6)."
type = object({ ns1_v4 = string, ns1_v6 = string, ns2_v4 = string, ns2_v6 = string })
}
# Adicione esta nova variável
variable "volumes" {
description = "Mapa de volumes criados pelo módulo de storage"
type = map(object({
id = string
}))
}
variable "volume_pool" {
description = "Pool de armazenamento para volumes criados"
type = string
default = "default"
}
variable "network_resources" {
description = "Mapa de recursos de rede para dependências"
type = any
}
Explicação Detalhada:
variable "servers"
: Define a estrutura esperada para a variávelservers
, que é um mapa de objetos. Cada objeto representa uma VM e inclui detalhes comousername
,gecos
,groups
,os
,vcpus
,memory
e uma lista denetworks
. A estrutura aninhada paranetworks
detalha os atributos de cada interface de rede (nome, IPs, prefixos, nome da interface e se é gateway padrão).optional(string)
: Indica que o atributo é opcional.
variable "default_vm_user"
: Define a estrutura para o objeto de usuário padrão, comname
egecos
.variable "os_profiles"
: Define a estrutura para os perfis de sistema operacional, incluindodefault_groups
.variable "ssh_public_key"
: Uma string para a chave SSH pública, marcada comosensitive = true
para evitar que seu valor seja exibido em logs do Terraform.variable "network_dmz"
evariable "network_cgr"
: Objetos que definem os gateways IPv4 e IPv6 para as redes DMZ e CGR.variable "defaults_dns"
: Objeto que define os servidores DNS IPv4 e IPv6 padrão.variable "volumes"
: Um mapa de objetos que espera o ID dos volumes criados pelo módulostorage
.variable "volume_pool"
: Uma string para o pool de armazenamento dos volumes, com um valor padrão dedefault
.variable "network_resources"
: Uma variável de tipoany
para receber o mapa de recursos de rede do módulonetwork
, usada para dependências explícitas.
Este arquivo é crucial para a validação de entrada do módulo compute
, garantindo que os dados fornecidos estejam no formato correto e contenham todas as informações necessárias para o provisionamento das VMs.
modules/compute/versions.tf
Este arquivo especifica as versões dos provedores Terraform exigidas pelo módulo compute
. É uma boa prática incluir este arquivo em cada módulo para garantir que o módulo seja executado com as versões corretas dos provedores, evitando problemas de compatibilidade.
1
2
3
4
5
6
7
8
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3" # Use a mesma versão da environments
}
}
}
Explicação Detalhada:
terraform { ... }
: Bloco de configuração global do Terraform para este módulo.required_providers { ... }
: Declara os provedores necessários.libvirt = { ... }
: Especifica o provedorlibvirt
.source = "dmacvicar/libvirt"
: Origem do provedor no Terraform Registry.version = "0.8.3"
: Versão exata do provedorlibvirt
que este módulo requer. A nota# Use a mesma versão da environments
é um lembrete importante para manter a consistência entre o ambiente raiz e os módulos.
Manter as versões dos provedores fixas e consistentes em todo o projeto é fundamental para garantir a reprodutibilidade e evitar comportamentos inesperados devido a atualizações de provedores.
Módulo network
O módulo network
é responsável por provisionar as redes virtuais no ambiente Libvirt. Ele cria e configura as redes que serão utilizadas pelas máquinas virtuais.
modules/network/main.tf
Este arquivo define o recurso libvirt_network
, que cria as redes virtuais no Libvirt com base nas configurações fornecidas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource "libvirt_network" "network" {
for_each = var.networks
name = each.value.net_name
mode = each.value.net_mode
autostart = true
dynamic "dhcp" {
for_each = each.value.net_mode != "none" ? [1] : []
content { enabled = true }
}
addresses = each.value.net_mode != "none" ? [
each.value.ipv4_cidr,
each.value.ipv6_cidr
] : null
}
Explicação Detalhada:
resource "libvirt_network" "network" { ... }
: Declara um recurso do tipolibvirt_network
com o nome localnetwork
.for_each = var.networks
: Este meta-argumento faz com que o Terraform crie uma instância deste recurso para cada entrada no mapavar.networks
(que vem denetworks.auto.tfvars
). Isso significa que uma rede Libvirt será criada para cada definição de rede.name = each.value.net_name
: Define o nome da rede no Libvirt, usando onet_name
especificado na definição da rede (ex:external
,dmz
).mode = each.value.net_mode
: Define o modo de operação da rede (ex:nat
,none
).autostart = true
: Configura a rede para iniciar automaticamente quando o host Libvirt for inicializado.dynamic "dhcp" { ... }
: Um blocodynamic
que condicionalmente cria um blocodhcp
dentro da definição da rede. Este bloco é criado apenas se onet_mode
da rede não fornone
(ou seja, se for uma rede que precisa de DHCP, comonat
).for_each = each.value.net_mode != "none" ? [1] : []
: A expressãoeach.value.net_mode != "none" ? [1] : []
retorna uma lista contendo um único elemento[1]
se o modo da rede não fornone
, e uma lista vazia[]
caso contrário. Isso efetivamente cria o blocodhcp
apenas quando necessário.content { enabled = true }
: Habilita o servidor DHCP para a rede.
addresses = each.value.net_mode != "none" ? [ ... ] : null
: Define os blocos de endereços IPv4 e IPv6 para a rede. Os endereços são definidos apenas se onet_mode
não fornone
. Caso contrário,null
é atribuído, indicando que a rede não terá blocos de endereços gerenciados pelo Libvirt (útil para redes isoladas sem DHCP).
Este arquivo é a base para a criação e configuração das redes virtuais, permitindo a definição flexível de diferentes tipos de redes (com ou sem NAT, com ou sem DHCP) de forma automatizada.
modules/network/outputs.tf
Este arquivo define os valores de saída do módulo network
. O output networks
expõe as informações das redes virtuais criadas, permitindo que outros módulos (como o módulo compute
) referenciem essas redes.
1
2
3
output "networks" {
value = libvirt_network.network
}
Explicação Detalhada:
output "networks" { ... }
: Declara um output chamadonetworks
.value = libvirt_network.network
: O valor deste output é o mapa completo dos recursoslibvirt_network
criados por este módulo. Isso inclui todas as propriedades das redes (nome, modo, CIDRs, etc.), que podem ser usadas como dependências ou para extrair informações em outras partes da configuração Terraform.
Este output é essencial para a interconexão entre os módulos, garantindo que o módulo compute
possa se referir às redes criadas pelo módulo network
ao configurar as interfaces das VMs.
modules/network/variables.tf
Este arquivo declara as variáveis de entrada esperadas pelo módulo network
. Ele define a estrutura e os tipos de dados das informações que o módulo precisa para criar as redes virtuais.
1
2
3
4
5
6
7
8
variable "networks" {
type = map(object({
net_name = string
net_mode = string
ipv4_cidr = string
ipv6_cidr = string
}))
}
Explicação Detalhada:
variable "networks"
: Declara a variávelnetworks
, que é um mapa de objetos. Cada objeto representa uma rede e inclui os seguintes atributos:net_name
: O nome da rede (string).net_mode
: O modo de operação da rede (string, ex:nat
,none
).ipv4_cidr
: O bloco CIDR IPv4 da rede (string).ipv6_cidr
: O bloco CIDR IPv6 da rede (string).
Este arquivo garante que os dados de entrada para o módulo network
estejam no formato correto, permitindo que o módulo crie as redes de forma consistente.
modules/network/versions.tf
Similar ao módulo compute
, este arquivo especifica as versões dos provedores Terraform exigidas pelo módulo network
, garantindo a consistência e compatibilidade.
1
2
3
4
5
6
7
8
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3" # Use a mesma versão da environments
}
}
}
Explicação Detalhada:
- Este arquivo é idêntico ao
modules/compute/versions.tf
, garantindo que ambos os módulos utilizem a mesma versão do provedorlibvirt
que o ambiente raiz. Isso é crucial para evitar problemas de compatibilidade e garantir que o comportamento do Terraform seja previsível em todo o projeto.
Módulo storage
O módulo storage
é responsável por provisionar os volumes de disco para as máquinas virtuais no ambiente Libvirt. Ele utiliza imagens base (templates) para criar os discos das VMs.
modules/storage/main.tf
Este arquivo define o recurso libvirt_volume
, que cria os volumes de disco para cada VM, baseando-se em uma imagem de template.
1
2
3
4
5
6
7
8
resource "libvirt_volume" "os_image" {
for_each = var.servers
name = "${each.key}.qcow2"
pool = var.volume_pool
base_volume_name = var.os_profiles[each.value.os].template_name
base_volume_pool = var.base_volume_pool
format = "qcow2"
}
Explicação Detalhada:
resource "libvirt_volume" "os_image" { ... }
: Declara um recurso do tipolibvirt_volume
com o nome localos_image
.for_each = var.servers
: Cria um volume para cada servidor definido emvar.servers
.name = "${each.key}.qcow2"
: Define o nome do volume de disco, utilizando o nome da VM (ex:gateway.qcow2
).pool = var.volume_pool
: Especifica o pool de armazenamento Libvirt onde o novo volume será criado (ex:default
).base_volume_name = var.os_profiles[each.value.os].template_name
: Define o nome da imagem base (template) a ser utilizada para criar o volume. O nome do template é obtido do perfil do sistema operacional (os_profiles
) correspondente ao OS da VM (each.value.os
).base_volume_pool = var.base_volume_pool
: Especifica o pool de armazenamento onde a imagem base (template) está localizada (ex:templates
).format = "qcow2"
: Define o formato do volume de disco como QCOW2.
Este arquivo automatiza a criação de discos para as VMs, garantindo que cada máquina tenha seu próprio volume de sistema operacional baseado em um template pré-existente.
modules/storage/outputs.tf
Este arquivo define os valores de saída do módulo storage
. O output volumes
expõe as informações dos volumes de disco criados, permitindo que outros módulos (como o módulo compute
) referenciem esses volumes.
1
2
3
output "volumes" {
value = libvirt_volume.os_image
}
Explicação Detalhada:
output "volumes" { ... }
: Declara um output chamadovolumes
.value = libvirt_volume.os_image
: O valor deste output é o mapa completo dos recursoslibvirt_volume
criados por este módulo. Isso inclui todas as propriedades dos volumes (nome, ID, pool, etc.), que podem ser usadas como dependências ou para extrair informações em outras partes da configuração Terraform.
Este output é essencial para a interconexão entre os módulos, garantindo que o módulo compute
possa se referir aos volumes criados pelo módulo storage
ao configurar os discos das VMs.
modules/storage/variables.tf
Este arquivo declara as variáveis de entrada esperadas pelo módulo storage
. Ele define a estrutura e os tipos de dados das informações que o módulo precisa para criar os volumes de disco.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
variable "servers" {
type = map(object({
os = string
}))
}
variable "os_profiles" {
type = map(object({
template_name = string
}))
}
variable "volume_pool" {
description = "Pool de armazenamento para volumes criados"
type = string
default = "default"
}
variable "base_volume_pool" {
description = "Pool de armazenamento para templates base"
type = string
default = "templates"
}
Explicação Detalhada:
variable "servers"
: Declara a variávelservers
, que é um mapa de objetos. Para o módulostorage
, apenas o atributoos
de cada servidor é relevante para determinar qual template de OS usar.variable "os_profiles"
: Declara a variávelos_profiles
, um mapa de objetos. Cada objeto de perfil de OS deve conter otemplate_name
da imagem base.variable "volume_pool"
: Uma string para o pool de armazenamento onde os novos volumes serão criados, com um valor padrão dedefault
.variable "base_volume_pool"
: Uma string para o pool de armazenamento onde as imagens base (templates) estão localizadas, com um valor padrão detemplates
.
Este arquivo garante que os dados de entrada para o módulo storage
estejam no formato correto, permitindo que o módulo crie os volumes de forma consistente.
modules/storage/versions.tf
Similar aos outros módulos, este arquivo especifica as versões dos provedores Terraform exigidas pelo módulo storage
, garantindo a consistência e compatibilidade.
1
2
3
4
5
6
7
8
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3" # Use a mesma versão da environments
}
}
}
Explicação Detalhada:
- Este arquivo é idêntico aos
versions.tf
dos móduloscompute
enetwork
, garantindo que todos os módulos utilizem a mesma versão do provedorlibvirt
que o ambiente raiz. Isso é crucial para evitar problemas de compatibilidade e garantir que o comportamento do Terraform seja previsível em todo o projeto.
Como Usar o Projeto
Para utilizar este projeto e provisionar suas próprias máquinas virtuais, siga os passos abaixo:
1. Pré-requisitos
Certifique-se de ter todos os pré-requisitos instalados e configurados conforme detalhado na seção Pré-requisitos. Isso inclui Terraform, Libvirt, e as imagens QCOW2 necessárias nos pools de armazenamento corretos.
2. Clonar o Repositório (ou Estruturar os Arquivos)
Se este projeto estivesse em um repositório Git, o primeiro passo seria cloná-lo. Como você recebeu os arquivos, certifique-se de que a estrutura de diretórios esteja organizada exatamente como mostrado na seção Estrutura do Projeto.
1
2
3
4
5
6
7
mkdir -p cloud-init environments/production modules/compute modules/network modules/storage
# Criar os arquivos dentro de cada diretório com o conteúdo fornecido
# Exemplo:
# touch cloud-init/network_config.yml
# touch cloud-init/user_data.yml
# ... e preencher com o conteúdo correspondente
3. Configurar a Chave SSH Pública
Edite o arquivo environments/production/terraform.tfvars
e substitua o valor da variável ssh_public_key
pela sua própria chave SSH pública. Esta chave será usada para acessar as VMs após o provisionamento.
1
2
# environments/production/terraform.tfvars
ssh_public_key = "<SUA_CHAVE_SSH_PUBLICA_AQUI>"
4. Inicializar o Terraform
Navegue até o diretório do ambiente de produção (environments/production
) e inicialize o Terraform. Este comando baixará os provedores necessários (neste caso, o provedor libvirt
).
1
2
cd environments/production
terraform init
5. Validar a Configuração
Após a inicialização, é uma boa prática validar a configuração para verificar se há erros de sintaxe ou lógica.
1
terraform validate
6. Planejar a Execução
Execute o comando terraform plan
para ver um resumo das ações que o Terraform realizará (quais recursos serão criados, modificados ou destruídos) sem realmente aplicá-las. Isso é crucial para revisar as mudanças antes de aplicá-las.
1
terraform plan
7. Aplicar a Configuração
Se o plano estiver de acordo com o esperado, aplique a configuração. O Terraform começará a provisionar as redes, volumes e máquinas virtuais no seu ambiente Libvirt.
1
terraform apply
O Terraform solicitará uma confirmação antes de prosseguir. Digite yes
e pressione Enter.
8. Acessar as VMs
Após a conclusão do terraform apply
, as VMs estarão em execução. Você pode obter o endereço IP do gateway (e de outras VMs, se configurado nos outputs) usando o comando terraform output
.
1
terraform output gateway_ip
Com o IP, você pode acessar a VM via SSH usando o usuário configurado no Cloud-init (ex: suporte
) e a chave SSH que você forneceu.
1
ssh suporte@<IP_DO_GATEWAY>
9. Destruir a Infraestrutura (Opcional)
Quando não precisar mais da infraestrutura, você pode destruí-la completamente usando o comando terraform destroy
. Isso removerá todas as VMs, volumes e redes criadas pelo Terraform.
1
terraform destroy
O Terraform solicitará uma confirmação antes de prosseguir. Digite yes
e pressione Enter.
Este guia passo a passo deve permitir que você utilize o projeto para provisionar e gerenciar suas VMs de forma eficiente e automatizada.