Post

Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform

Aprenda a provisionar e gerenciar uma infraestrutura de virtualização completa com KVM/LIBVIRT e Terraform. Este guia passo a passo cobre a criação de VMs, redes, volumes e configuração automatizada com Cloud-Init para um ambiente robusto e escalável.

Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform

Introdução

Este tutorial detalha como utilizar o Terraform para provisionar e gerenciar uma infraestrutura de virtualização completa baseada em KVM/LIBVIRT. Abordaremos a criação de máquinas virtuais, redes, volumes de armazenamento e a configuração automatizada via Cloud-Init, proporcionando um ambiente robusto e escalável para seus projetos.

O objetivo é transformar os scripts Terraform existentes em um guia passo a passo, explicando cada componente e sua função, desde a configuração inicial até a implantação e verificação.

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 como virsh. Para instruções detalhadas de instalação e configuração do servidor KVM/LIBVIRT, consulte: https://geanmartins.com.br/posts/kvm-oracle-linux-7/

  • Terraform: Baixe e instale a versão mais recente do Terraform (versão 1.5 ou superior é recomendada).

  • Provedor Terraform para Libvirt: O provedor dmacvicar/libvirt será utilizado para interagir com o Libvirt. Ele será baixado automaticamente pelo Terraform, mas é importante garantir que seu ambiente tenha as dependências necessárias para ele funcionar. Isso inclui o próprio Libvirt e suas bibliotecas de desenvolvimento:
    • Debian/Ubuntu: sudo apt install libvirt-dev
    • RHEL/CentOS/Oracle Linux: sudo yum install libvirt-devel ou sudo dnf install libvirt-devel
  • 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/

  • Cloud-Init: As imagens base das máquinas virtuais devem ser compatíveis com Cloud-Init para que a configuração automatizada funcione. Certifique-se de que suas imagens (por exemplo, debian-12-amd64.qcow2, ol9-amd64.qcow2) possuam o cloud-init instalado.

  • Templates de Imagens: Você precisará de imagens base dos sistemas operacionais. Você pode:
  • Pools de Armazenamento Libvirt: Você precisará ter dois pools de armazenamento configurados no Libvirt:
    • default: Para armazenar os volumes das VMs em execução.
    • templates: Para armazenar as imagens base dos sistemas operacionais (templates).

    Você pode criá-los usando comandos como:

    1
    2
    3
    4
    5
    6
    7
    
    sudo virsh pool-define-as default dir --target /var/lib/libvirt/images
    sudo virsh pool-start default
    sudo virsh pool-autostart default
    
    sudo virsh pool-define-as templates dir --target /var/lib/libvirt/templates
    sudo virsh pool-start templates
    sudo virsh pool-autostart templates
    

    Certifique-se de que os diretórios /var/lib/libvirt/images e /var/lib/libvirt/templates existam e tenham as permissões corretas.

  • Chave SSH: Uma chave SSH pública será necessária para acesso às VMs. Você pode gerá-la conforme as instruções fornecidas neste tutorial.

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 Terraform

O projeto Terraform é organizado em uma estrutura modular para facilitar a gestão e a escalabilidade. Cada arquivo .tf ou diretório tem uma responsabilidade específica, conforme detalhado abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── cloud-init/                    # Templates de configuração Cloud-Init
│   ├── network_config.yml         # Template para configuração de rede das VMs
│   └── user_data.yml              # Template para configuração de usuários e sistema
├── providers.tf                   # Configuração do provedor Terraform
├── variables.tf                   # Declaração de todas as variáveis do projeto
├── network.tf                     # Definição das redes virtuais LIBVIRT
├── volumes.tf                     # Criação de volumes de armazenamento para VMs
├── cloudinit.tf                   # Geração de discos Cloud-Init para cada VM
├── domain.tf                      # Definição das máquinas virtuais (domínios)
├── terraform.tfvars               # Valores das variáveis principais
├── servers.auto.tfvars            # Definição específica dos servidores
└── networks.auto.tfvars           # Definição específica das redes

Esta estrutura permite uma clara separação de responsabilidades, onde cada arquivo lida com um aspecto específico da infraestrutura, desde a definição de redes até a criação de máquinas virtuais e suas configurações iniciais.

Entendendo os Arquivos Terraform

providers.tf

Este arquivo é fundamental para qualquer projeto Terraform, pois ele define os provedores que o Terraform utilizará para interagir com a infraestrutura. No nosso caso, ele configura o provedor libvirt.

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
# =============================================================================
# CONFIGURAÇÃO DO PROVEDOR TERRAFORM
# =============================================================================
# Este arquivo define os requisitos de versão do Terraform e configura o
# provedor libvirt para gerenciar recursos de virtualização KVM/QEMU.

terraform {
  # Versão mínima do Terraform necessária para executar este projeto
  # Nota: A versão 1.5 é estável, mas sempre verifique a documentação oficial
  # da HashiCorp para a versão mais recente e estável. Considere usar
  # uma restrição de versão mais flexível (ex: `~> 1.5`) para permitir
  # atualizações de patch sem quebrar a compatibilidade, ou uma versão
  # específica para garantir reprodutibilidade.
  required_version = ">= 1.5"

  # Definição dos provedores necessários e suas versões
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"  # Provedor oficial para libvirt
      version = ">= 0.8.3"           # Versão mais recente (atualizada em 21/07/2025)
    }
  }
}

# Configuração do provedor libvirt
provider "libvirt" {
  # URI de conexão com o daemon libvirt
  # Opções disponíveis:
  # - "qemu:///system": Conexão local como root (padrão)
  # - "qemu+ssh://user@host/system": Conexão remota via SSH
  #uri = "qemu+ssh://gean@192.168.0.254/system"  # Exemplo para conexão remota
  uri = "qemu:///system"  # Conexão local padrão
}

Explicação:

  • terraform { ... }: Este bloco especifica a versão mínima do Terraform necessária e declara os provedores que serão utilizados. Aqui, definimos que precisamos do provedor libvirt na versão 0.8.3 ou superior.
  • provider "libvirt" { ... }: Este bloco configura o provedor libvirt. A propriedade uri define como o Terraform se conectará ao daemon libvirtd. No exemplo, qemu:///system indica uma conexão local. Você pode alterá-lo para uma conexão remota via SSH, se necessário.

variables.tf

Este arquivo centraliza a declaração de todas as variáveis utilizadas no projeto. Definir variáveis é uma boa prática no Terraform, pois permite reutilizar o código e adaptar a infraestrutura a diferentes ambientes sem modificar o código principal.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# =============================================================================
# DECLARAÇÃO DE VARIÁVEIS DO PROJETO
# =============================================================================
# Este arquivo centraliza todas as declarações de variáveis utilizadas no
# projeto, definindo tipos, descrições e valores padrão quando aplicável.

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE SERVIDORES
# -----------------------------------------------------------------------------
# Variável principal que define todas as máquinas virtuais a serem criadas.
# Cada servidor é definido como um objeto com especificações de hardware,
# sistema operacional e configuração de rede.
variable "servers" {
  description = <<-EOT
    Mapa de objetos que definem as máquinas virtuais a serem criadas.
    Cada chave representa o nome da VM e o valor contém suas especificações:
    - username: Nome do usuário personalizado (opcional, usa default_vm_user se não especificado)
    - gecos: Nome completo do usuário (opcional, usa default_vm_user se não especificado)
    - groups: Lista de grupos do usuário (opcional, usa os grupos padrão do SO)
    - vcpus: Número de CPUs virtuais
    - memory: Quantidade de memória RAM em MB (como string)
    - os: Tipo de sistema operacional (deve existir em os_profiles)
    - networks: Lista de interfaces de rede da VM
  EOT
  
  type = map(object({
    username = optional(string)      # Nome de usuário personalizado
    gecos    = optional(string)      # Nome completo do usuário
    groups   = optional(list(string)) # Grupos adicionais do usuário
    vcpus    = number                # Número de CPUs virtuais
    memory   = string                # Memória RAM em MB
    os       = string                # Perfil do sistema operacional
    networks = list(object({
      name        = string
      ipv4        = string
      ipv4_prefix = optional(number)
      ipv6        = string
      ipv6_prefix = optional(number)
      if_name     = string
    }))
  }))
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE USUÁRIO PADRÃO
# -----------------------------------------------------------------------------
# Define as configurações padrão para o usuário principal das VMs quando
# não especificado individualmente em cada servidor.
variable "default_vm_user" {
  description = <<-EOT
    Configurações do usuário padrão aplicadas a todas as VMs que não
    especificarem um usuário personalizado. Este usuário terá privilégios
    administrativos (sudo/wheel) e acesso via chave SSH.
  EOT
  
  type = object({
    name  = string
    gecos = string
  })
}

# -----------------------------------------------------------------------------
# PERFIS DE SISTEMAS OPERACIONAIS
# -----------------------------------------------------------------------------
# Define templates e configurações específicas para cada tipo de SO suportado.
# Permite padronizar configurações como grupos de usuários e imagens base.
variable "os_profiles" {
  description = <<-EOT
    Mapa que define perfis completos para cada tipo de sistema operacional.
    Cada perfil inclui:
    - template_name: Nome do arquivo de template/imagem base no pool de templates
    - default_groups: Lista de grupos padrão para usuários neste SO
    
    Exemplos de grupos por SO:
    - Debian/Ubuntu: ["users", "sudo"]
    - RHEL/CentOS/Oracle: ["users", "wheel"]
  EOT
  
  type = map(object({
    template_name  = string
    default_groups = list(string)
  }))
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE POOLS DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Define os pools de armazenamento utilizados para volumes das VMs e templates.
variable "volume_pool" {
  description = <<-EOT
    Nome do pool de armazenamento onde serão criados os volumes das máquinas
    virtuais. Este pool deve existir no libvirt e ter espaço suficiente para
    todos os volumes que serão criados.
  EOT
  type        = string
  default     = "default"
}

variable "base_volume_pool" {
  description = <<-EOT
    Nome do pool de armazenamento onde estão localizados os templates/imagens
    base dos sistemas operacionais. Estes templates servem como base para
    criar os volumes das VMs através de copy-on-write.
  EOT
  type        = string
  default     = "templates"
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE REDES VIRTUAIS
# -----------------------------------------------------------------------------
# Define todas as redes virtuais que serão criadas no ambiente.
variable "networks" {
  description = <<-EOT
    Mapa de objetos que definem as redes virtuais a serem criadas no libvirt.
    Cada rede pode ter diferentes modos de operação:
    - "nat": Rede com NAT para acesso à internet
    - "none": Rede isolada sem DHCP (configuração manual via Cloud-Init)
    - "bridge": Rede em bridge com interface física (requer configuração adicional)
  EOT
  
  type = map(object({
    net_name  = string
    net_mode  = string
    ipv4_cidr = string
    ipv6_cidr = string
  }))
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE ACESSO SSH
# -----------------------------------------------------------------------------
# Chave pública SSH que será injetada em todas as VMs para acesso sem senha.
variable "ssh_public_key" {
  description = <<-EOT
    Chave pública SSH que será injetada em todas as máquinas virtuais para
    permitir acesso sem senha. Esta chave deve corresponder à chave privada
    que será usada para conectar às VMs.
    
    Exemplo de geração: ssh-keygen -t ed25519 -C "email@example.com"
  EOT
  type        = string
  # Nota: Descomente a linha abaixo para marcar como sensível nos logs
  #sensitive   = true
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE REDE DMZ
# -----------------------------------------------------------------------------
# Define configurações de gateway e DNS para VMs na rede DMZ (zona desmilitarizada).
# Estas configurações são aplicadas via Cloud-Init nas VMs que não usam DHCP.
variable "network_dmz" {
  description = <<-EOT
    Configurações de rede para a zona DMZ, incluindo gateways e servidores DNS.
    Estas configurações são aplicadas automaticamente via Cloud-Init em VMs
    que possuem interfaces estáticas (não DHCP) na rede DMZ.
    
    Componentes:
    - gateway_v4/v6: Endereços do gateway padrão para IPv4 e IPv6
    - ns1_v4/v6, ns2_v4/v6: Endereços dos servidores DNS primário e secundário
  EOT
  
  type = object({
    gateway_v4 = string
    gateway_v6 = string
    ns1_v4     = string
    ns1_v6     = string
    ns2_v4     = string
    ns2_v6     = string
  })
}

Explicação:

Este arquivo define diversas variáveis que controlam a criação e configuração das VMs e redes. As principais são:

  • servers: Um mapa complexo que define cada máquina virtual a ser criada, incluindo CPU, memória, sistema operacional e interfaces de rede. Permite personalizar cada VM individualmente.
  • default_vm_user: Define um usuário padrão para as VMs, caso não seja especificado um usuário personalizado para cada uma.
  • os_profiles: Um mapa que associa nomes de sistemas operacionais a templates de imagem e grupos de usuários padrão, facilitando a padronização.
  • volume_pool e base_volume_pool: Nomes dos pools de armazenamento do Libvirt onde os volumes das VMs e os templates base serão armazenados, respectivamente.
  • networks: Um mapa que define as redes virtuais a serem criadas, incluindo nome, modo de operação (NAT, isolada, etc.) e blocos CIDR IPv4/IPv6.
  • ssh_public_key: A chave pública SSH que será injetada em todas as VMs para acesso sem senha.
  • network_dmz: Configurações específicas para a rede DMZ (zona desmilitarizada), como gateways e servidores DNS, que serão aplicadas via Cloud-Init para VMs com IPs estáticos nesta rede.

network.tf

Este arquivo é responsável por criar as redes virtuais no Libvirt, conforme definido na variável networks em variables.tf e seus valores em networks.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
33
34
35
36
37
38
39
40
41
# =============================================================================
# DEFINIÇÃO DE REDES VIRTUAIS LIBVIRT
# =============================================================================
# Este arquivo cria todas as redes virtuais definidas na variável 'networks'.
# Cada rede pode ter diferentes configurações de modo, DHCP e endereçamento
# dependendo de sua finalidade na infraestrutura.

# Criação das redes virtuais baseada no mapa de configurações
resource "libvirt_network" "network" {
  # Itera sobre cada rede definida em var.networks
  for_each  = var.networks

  # Configurações básicas da rede
  name      = each.value.net_name  # Nome da rede no libvirt
  mode      = each.value.net_mode  # Modo de operação (nat, none, bridge, etc.)
  autostart = true                 # Inicia automaticamente com o libvirt

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DINÂMICA DE DHCP
  # -----------------------------------------------------------------------------
  # O DHCP é habilitado apenas para redes que não estão no modo "none".
  # Redes no modo "none" são tipicamente redes isoladas onde a configuração
  # de IP é feita manualmente via Cloud-Init ou outros métodos.
  dynamic "dhcp" {
    # Cria o bloco DHCP apenas se o modo não for "none"
    for_each = each.value.net_mode != "none" ? [1] : []
    content {
      enabled = true  # Habilita o servidor DHCP da rede
    }
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE ENDEREÇAMENTO
  # -----------------------------------------------------------------------------
  # Define os blocos CIDR IPv4 e IPv6 para redes que não estão no modo "none".
  # Para redes no modo "none", o endereçamento é gerenciado externamente.
  addresses = each.value.net_mode != "none" ? [
    each.value.ipv4_cidr,  # Bloco CIDR IPv4 (ex: 192.168.100.0/24)
    each.value.ipv6_cidr   # Bloco CIDR IPv6 (ex: fd12:ee::/64)
  ] : null  # Sem endereçamento automático para redes isoladas
}

Explicação:

  • resource "libvirt_network" "network" { ... }: Este bloco define um recurso de rede Libvirt. O for_each = var.networks permite que o Terraform crie múltiplas redes, uma para cada entrada no mapa networks definido em suas variáveis.
  • name, mode, autostart: Definem o nome da rede, seu modo de operação (ex: nat para redes com NAT, none para redes isoladas, bridge para bridge com interface física) e se ela deve iniciar automaticamente com o Libvirt.
  • dynamic "dhcp" { ... }: Este bloco configura o servidor DHCP para a rede. Ele é criado dinamicamente (ou seja, só é incluído se a condição for verdadeira) apenas se o net_mode da rede não for none. Redes no modo none são isoladas e não possuem DHCP gerenciado pelo Libvirt, exigindo configuração manual de IP nas VMs via Cloud-Init.
  • addresses: Define os blocos CIDR IPv4 e IPv6 para a rede. Assim como o DHCP, o endereçamento é configurado apenas para redes que não estão no modo none.

volumes.tf

Este arquivo é responsável pela criação dos volumes de disco para cada máquina virtual. Ele utiliza o conceito de copy-on-write (qcow2) a partir de um template base, o que otimiza o uso de espaço em disco e agiliza a criação de VMs.

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
# =============================================================================
# CRIAÇÃO DE VOLUMES DE ARMAZENAMENTO PARA VMs
# =============================================================================
# Este arquivo cria volumes de disco para cada máquina virtual definida,
# utilizando templates base como origem através do mecanismo copy-on-write
# do formato qcow2, otimizando o uso de espaço em disco.

# Criação de volumes individuais para cada VM
resource "libvirt_volume" "os_image" {
  # Itera sobre cada servidor definido em var.servers
  for_each = { for vm_name, config in var.servers : vm_name => config }
  
  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DO VOLUME
  # -----------------------------------------------------------------------------
  name             = "${each.key}.qcow2"                              # Nome do arquivo do volume
  pool             = var.volume_pool                                  # Pool onde o volume será criado
  base_volume_name = var.os_profiles[each.value.os].template_name     # Template base do SO
  base_volume_pool = var.base_volume_pool                             # Pool onde está o template
  format           = "qcow2"                                          # Formato do volume (copy-on-write)
  
  # -----------------------------------------------------------------------------
  # FUNCIONAMENTO DO COPY-ON-WRITE
  # -----------------------------------------------------------------------------
  # O volume criado é um "snapshot" do template base, ocupando inicialmente
  # pouco espaço em disco. Conforme a VM escreve dados, apenas as diferenças
  # são armazenadas no novo volume, otimizando o uso de armazenamento.
  #
  # Vantagens:
  # - Criação rápida de VMs (não copia todo o template)
  # - Economia de espaço (compartilha dados comuns)
  # - Facilita atualizações de templates base
}

Explicação:

  • resource "libvirt_volume" "os_image" { ... }: Este bloco cria um volume Libvirt para cada VM definida na variável servers.
  • name: Define o nome do arquivo do volume, usando o nome da VM como base.
  • pool: Especifica o pool de armazenamento (volume_pool) onde o novo volume será criado.
  • base_volume_name e base_volume_pool: Indicam o nome do template base (definido em os_profiles) e o pool onde este template está localizado. O Terraform usará este template para criar o novo volume.
  • format = "qcow2": Define o formato do volume como qcow2, que permite o mecanismo de copy-on-write. Isso significa que o volume da VM não é uma cópia completa do template, mas sim uma camada que armazena apenas as diferenças em relação ao template base. Isso economiza espaço e acelera a criação de VMs.

cloudinit.tf

Este arquivo é crucial para a automação da configuração das máquinas virtuais. Ele gera discos Cloud-Init que são anexados às VMs e contêm instruções para configurar usuários, rede e outros aspectos do sistema durante o primeiro boot.

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
# =============================================================================
# GERAÇÃO DE DISCOS CLOUD-INIT PARA CONFIGURAÇÃO AUTOMÁTICA
# =============================================================================
# Este arquivo cria discos Cloud-Init para cada VM, contendo configurações
# de usuário, rede e sistema que são aplicadas automaticamente durante
# o primeiro boot da máquina virtual.

# Criação de discos Cloud-Init individuais para cada VM
resource "libvirt_cloudinit_disk" "cloudinit" {
  # Itera sobre cada servidor definido em var.servers
  for_each = var.servers

  # Configurações básicas do disco Cloud-Init
  name = "cloudinit-${each.key}.iso"  # Nome do arquivo ISO do Cloud-Init
  pool = var.volume_pool              # Pool onde o disco será armazenado

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE DADOS DE USUÁRIO (USER DATA)
  # -----------------------------------------------------------------------------
  # O user_data contém configurações de sistema, usuários, pacotes e comandos
  # que serão executados durante a inicialização da VM.
  user_data = templatefile("${path.module}/cloud-init/user_data.yml", {
    hostname  = each.key                                                    # Nome da VM
    user_name = coalesce(each.value.username, var.default_vm_user.name)     # Nome do usuário (personalizado ou padrão)
    gecos     = coalesce(each.value.gecos, var.default_vm_user.gecos)       # Nome completo do usuário
    groups    = coalesce(each.value.groups, var.os_profiles[each.value.os].default_groups)  # Grupos do usuário
    ssh_key   = var.ssh_public_key                                          # Chave SSH pública
  })

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE REDE (NETWORK CONFIG)
  # -----------------------------------------------------------------------------
  # O network_config define a configuração de rede da VM, incluindo interfaces,
  # endereços IP, rotas e servidores DNS. A configuração varia dependendo se
  # a VM usa DHCP ou configuração estática.
  network_config = templatefile(
    "${path.module}/cloud-init/network_config.yml",
    {
      hostname           = each.key                                          # Nome da VM para referência
      interfaces         = each.value.networks                              # Lista de interfaces de rede
      dmz_config         = var.network_dmz                                   # Configurações da rede DMZ
      has_dhcp_interface = length([for iface in each.value.networks : iface if iface.ipv4 == "dhcp"]) > 0  # Verifica se há interface DHCP
    }
  )
  
  # -----------------------------------------------------------------------------
  # FUNCIONAMENTO DO CLOUD-INIT
  # -----------------------------------------------------------------------------
  # O Cloud-Init é executado automaticamente no primeiro boot da VM e:
  # 1. Configura usuários e chaves SSH
  # 2. Define configurações de rede
  # 3. Executa comandos de inicialização
  # 4. Instala pacotes se especificado
  # 5. Configura hostname e outros parâmetros do sistema
}

Explicação:

  • resource "libvirt_cloudinit_disk" "cloudinit" { ... }: Este bloco cria um disco Cloud-Init para cada VM definida em var.servers.
  • name e pool: Definem o nome do arquivo ISO do Cloud-Init e o pool de armazenamento onde ele será salvo.
  • user_data: Este é o coração da configuração de usuário. Ele usa a função templatefile para preencher o template cloud-init/user_data.yml com informações específicas de cada VM, como hostname, nome de usuário, grupos e chave SSH pública. A função coalesce é usada para fornecer valores padrão caso não sejam especificados para uma VM individual.
  • network_config: Similar ao user_data, este bloco usa templatefile para preencher o template cloud-init/network_config.yml com as configurações de rede da VM, incluindo interfaces, endereços IP (DHCP ou estático) e configurações de DMZ. A lógica has_dhcp_interface é usada para adaptar a configuração de rede com base na presença de interfaces DHCP.

domain.tf

Este é o arquivo que, de fato, cria as máquinas virtuais (domínios) no Libvirt, unindo todas as configurações definidas nos arquivos anteriores.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# =============================================================================
# DEFINIÇÃO DE MÁQUINAS VIRTUAIS (DOMÍNIOS LIBVIRT)
# =============================================================================
# Este arquivo cria as máquinas virtuais propriamente ditas, definindo
# especificações de hardware, discos, interfaces de rede e dispositivos
# de console para cada VM especificada na configuração.

# Criação das máquinas virtuais
resource "libvirt_domain" "domain" {
  # Itera sobre cada servidor definido em var.servers
  for_each = var.servers

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÕES BÁSICAS DA VM
  # -----------------------------------------------------------------------------
  name   = each.key           # Nome da VM no libvirt
  memory = each.value.memory  # Quantidade de RAM em MB
  vcpu   = each.value.vcpus   # Número de CPUs virtuais

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE CPU
  # -----------------------------------------------------------------------------
  # Utiliza passthrough do host para melhor performance, permitindo que a VM
  # acesse diretamente as instruções e recursos da CPU física.
  cpu {
    mode = "host-passthrough"  # Expõe todas as funcionalidades da CPU host
  }

  # -----------------------------------------------------------------------------
  # DEPENDÊNCIAS DE RECURSOS
  # -----------------------------------------------------------------------------
  # Garante que redes e volumes sejam criados antes da VM
  depends_on = [
    libvirt_network.network,  # Redes virtuais devem existir
    libvirt_volume.os_image   # Volumes de disco devem estar prontos
  ]

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DO CLOUD-INIT
  # -----------------------------------------------------------------------------
  # Anexa o disco Cloud-Init que contém as configurações de inicialização
  cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE DISCO PRINCIPAL
  # -----------------------------------------------------------------------------
  # Define o disco principal da VM usando o volume criado anteriormente
  disk {
    volume_id = libvirt_volume.os_image[each.key].id  # Volume do sistema operacional
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DINÂMICA DE INTERFACES DE REDE
  # -----------------------------------------------------------------------------
  # Cria uma interface de rede para cada rede especificada na configuração da VM
  dynamic "network_interface" {
    for_each = each.value.networks  # Itera sobre as redes da VM
    content {
      network_name   = network_interface.value.name                                    # Nome da rede virtual
      wait_for_lease = network_interface.value.ipv4 == "dhcp" ? true : false         # Aguarda lease DHCP se aplicável
      
      # Nota: Para interfaces com IP estático, o endereçamento é configurado
      # via Cloud-Init, não através do libvirt
    }
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE CONSOLE SERIAL
  # -----------------------------------------------------------------------------
  # Permite acesso à VM via console serial para administração e troubleshooting
  console {
    type        = "pty"     # Tipo de console (pseudo-terminal)
    target_type = "serial"  # Console serial
    target_port = "0"       # Porta serial 0 (ttyS0)
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE INTERFACE GRÁFICA
  # -----------------------------------------------------------------------------
  # Configura acesso gráfico via protocolo SPICE para administração visual
  graphics {
    type        = "spice"    # Protocolo SPICE para melhor performance
    listen_type = "address"  # Escuta em endereço específico
    autoport    = true       # Seleciona porta automaticamente
  }
  
  # -----------------------------------------------------------------------------
  # RECURSOS ADICIONAIS DISPONÍVEIS
  # -----------------------------------------------------------------------------
  # Outros recursos que podem ser configurados conforme necessário:
  # - Discos adicionais (múltiplos blocos "disk")
  # - Dispositivos USB passthrough
  # - Configurações de NUMA
  # - Dispositivos de áudio
  # - Redirecionamento de dispositivos
}

Explicação:

  • resource "libvirt_domain" "domain" { ... }: Este bloco cria uma máquina virtual (domínio) no Libvirt para cada servidor definido em var.servers.
  • name, memory, vcpu: Definem o nome da VM, a quantidade de RAM e o número de vCPUs, respectivamente.
  • cpu { mode = "host-passthrough" }: Configura a CPU da VM para usar o modo host-passthrough, o que permite que a VM acesse diretamente as instruções e recursos da CPU física do host, resultando em melhor performance.
  • depends_on: Garante que as redes e os volumes de disco sejam criados e estejam prontos antes que a VM seja provisionada.
  • cloudinit: Anexa o disco Cloud-Init gerado em cloudinit.tf à VM, permitindo que as configurações de usuário e rede sejam aplicadas automaticamente no primeiro boot.
  • disk { volume_id = ... }: Associa o volume de disco principal (criado em volumes.tf) à VM.
  • dynamic "network_interface" { ... }: Cria dinamicamente as interfaces de rede para a VM, com base nas configurações de rede definidas para cada servidor. O wait_for_lease garante que a VM aguarde um lease DHCP se a interface estiver configurada para DHCP.
  • console e graphics: Configuram o acesso ao console serial (para administração via terminal) e à interface gráfica via protocolo SPICE (para acesso visual à VM).

terraform.tfvars

Este arquivo é usado para atribuir valores às variáveis declaradas em variables.tf. Ele contém as configurações principais e globais do projeto.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# =============================================================================
# CONFIGURAÇÕES PRINCIPAIS DO PROJETO TERRAFORM
# =============================================================================
# Este arquivo contém os valores das variáveis principais que definem o
# comportamento global da infraestrutura. Estas configurações são aplicadas
# a todos os recursos criados no projeto.

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE ACESSO SSH
# -----------------------------------------------------------------------------
# Chave pública SSH que será injetada em todas as VMs para acesso administrativo.
# 
# Métodos alternativos para definir a chave:
# 1. Via linha de comando: terraform plan -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"
# 2. Via apply direto: terraform apply -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"
# 3. Definindo diretamente aqui (método atual)
ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzaHM66H9EFpX/6aYcvFH85eHjyAsMVRk8DbfQhxxmI gean@inspiron"

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE POOLS DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Define onde os volumes das VMs e templates base são armazenados no libvirt.
volume_pool      = "default"    # Pool para volumes das VMs em execução
base_volume_pool = "templates"  # Pool para templates/imagens base dos SOs

# -----------------------------------------------------------------------------
# PERFIS DE SISTEMAS OPERACIONAIS SUPORTADOS
# -----------------------------------------------------------------------------
# Define as configurações específicas para cada tipo de SO que pode ser usado
# nas VMs, incluindo o nome do template e grupos padrão de usuários.
os_profiles = {
  # Perfil para Debian 12
  "debian12" = {
    template_name  = "debian-12-amd64.qcow2",  # Arquivo template no pool base
    default_groups = ["users", "sudo"]         # Grupos padrão (sudo para admin)
  },
  
  # Perfil para Oracle Linux 9
  "oracle9" = {
    template_name  = "ol9-amd64.qcow2",       # Arquivo template no pool base
    default_groups = ["users", "wheel"]       # Grupos padrão (wheel para admin)
  }
  
  # Adicione novos perfis conforme necessário:
  # "ubuntu22" = {
  #   template_name  = "ubuntu-22.04-amd64.qcow2",
  #   default_groups = ["users", "sudo"]
  # }
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE USUÁRIO PADRÃO
# -----------------------------------------------------------------------------
# Define o usuário administrativo padrão que será criado em todas as VMs
# quando não especificado individualmente na configuração do servidor.
default_vm_user = {
  name  = "suporte"      # Nome de login do usuário
  gecos = "Suporte User" # Nome completo/descrição do usuário
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÕES DE REDE DMZ
# -----------------------------------------------------------------------------
# Define os parâmetros de rede para a zona DMZ (zona desmilitarizada),
# incluindo gateways e servidores DNS que serão configurados automaticamente
# nas VMs através do Cloud-Init.
network_dmz = {
  # Gateways padrão para roteamento
  gateway_v4 = "10.32.16.1",      # Gateway IPv4 da rede DMZ
  gateway_v6 = "fd00:32:16::1",   # Gateway IPv6 da rede DMZ
  
  # Servidores DNS primário e secundário
  ns1_v4     = "10.32.16.3",      # DNS primário IPv4 (ns1)
  ns1_v6     = "fd00:32:16::3",   # DNS primário IPv6 (ns1)
  ns2_v4     = "10.32.16.4",      # DNS secundário IPv4 (ns2)
  ns2_v6     = "fd00:32:16::4"    # DNS secundário IPv6 (ns2)
}

# -----------------------------------------------------------------------------
# NOTAS IMPORTANTES
# -----------------------------------------------------------------------------
# 1. Templates: Certifique-se de que os arquivos de template especificados
#    em os_profiles existem no pool base_volume_pool antes de executar.
#
# 2. Pools: Os pools 'default' e 'templates' devem existir no libvirt.
#    Crie-os com: virsh pool-define-as <nome> dir - - - - <caminho>
#
# 3. SSH: A chave privada correspondente à chave pública deve estar disponível
#    para conectar às VMs após a criação.
#
# 4. Rede: As configurações de rede DMZ devem ser consistentes com a
#    topologia real da infraestrutura.

Explicação:

Este arquivo define os valores para as variáveis globais do projeto, como:

  • ssh_public_key: A chave pública SSH que será usada para acessar as VMs. É crucial que esta chave seja a sua chave pública para que você possa se conectar às máquinas virtuais após o provisionamento.
  • volume_pool e base_volume_pool: Os nomes dos pools de armazenamento do Libvirt. Certifique-se de que esses pools existam e estejam configurados corretamente em seu ambiente Libvirt.
  • os_profiles: Define os perfis de sistemas operacionais suportados, mapeando um nome amigável (ex: debian12) para o nome do arquivo de template (debian-12-amd64.qcow2) e os grupos padrão de usuários para aquele SO.
  • default_vm_user: O nome de usuário e o nome completo do usuário padrão que será criado em todas as VMs.
  • network_dmz: Contém as configurações de rede para a zona DMZ, incluindo gateways e servidores DNS. Essas informações são usadas pelo Cloud-Init para configurar as interfaces de rede estáticas das VMs na DMZ.

servers.auto.tfvars

Este arquivo é um .tfvars especial que o Terraform carrega automaticamente. Ele é usado para definir as máquinas virtuais específicas que serão criadas, atribuindo valores à variável servers declarada em variables.tf.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# =============================================================================
# DEFINIÇÃO DE SERVIDORES DA INFRAESTRUTURA
# =============================================================================
# Este arquivo define todas as máquinas virtuais que serão criadas, incluindo
# suas especificações de hardware, sistema operacional e configuração de rede.
# Cada servidor tem um papel específico na arquitetura da infraestrutura.

servers = {
  # ---------------------------------------------------------------------------
  # SERVIDOR GATEWAY - Roteador e Firewall Principal
  # ---------------------------------------------------------------------------
  # Servidor que atua como gateway principal da infraestrutura, conectando
  # múltiplas redes e fornecendo roteamento entre elas. Possui interfaces
  # em todas as redes para permitir comunicação inter-redes.
  gateway = {
    vcpus  = 2,          # CPUs suficientes para roteamento e firewall
    memory = "2048",     # 2GB RAM para operações de rede
    os     = "debian12", # Debian para estabilidade e facilidade de configuração
    
    # Configuração de múltiplas interfaces de rede
    networks = [
      # Interface externa - Conexão com internet via DHCP
      { 
        name = "external", 
        ipv4 = "dhcp", ipv6 = "dhcp",  # Obtém IP automaticamente do provedor
        if_name = "ens3"               # Nome da interface dentro da VM
      },
      
      # Interface DMZ - Rede de serviços públicos
      { 
        name = "dmz", 
        ipv4 = "10.32.16.1", ipv4_prefix = 24,     # Gateway da rede DMZ
        ipv6 = "fd00:32:16::1", ipv6_prefix = 64,  # Gateway IPv6 da DMZ
        if_name = "ens4" 
      },
      
      # Interface CGR - Rede corporativa/gestão
      { 
        name = "cgr", 
        ipv4 = "10.48.32.1", ipv4_prefix = 24,     # Gateway da rede CGR
        ipv6 = "fd00:48:32::1", ipv6_prefix = 64,  # Gateway IPv6 da CGR
        if_name = "ens5" 
      },
      
      # Interface DHCP - Rede para clientes DHCP
      { 
        name = "dhcp", 
        ipv4 = "10.128.112.1", ipv4_prefix = 24,   # Gateway da rede DHCP
        ipv6 = "fd00:128:112::1", ipv6_prefix = 64, # Gateway IPv6 da DHCP
        if_name = "ens6" 
      }
    ]
  },

  # ---------------------------------------------------------------------------
  # SERVIDOR DNS PRIMÁRIO (NS1)
  # ---------------------------------------------------------------------------
  # Servidor DNS primário responsável pela resolução de nomes na infraestrutura.
  # Localizado na rede DMZ para ser acessível por todas as outras redes.
  ns1 = {
    vcpus    = 2,          # CPUs adequadas para serviço DNS
    memory   = "2048",     # 2GB RAM para cache DNS e operações
    os       = "oracle9",  # Oracle Linux para ambiente empresarial
    
    # Interface única na rede DMZ
    networks = [
      { 
        name = "dmz", 
        ipv4 = "10.32.16.3", ipv4_prefix = 24,     # IP fixo do DNS primário
        ipv6 = "fd00:32:16::3", ipv6_prefix = 64,  # IPv6 do DNS primário
        if_name = "ens3" 
      }
    ]
  },

  # ---------------------------------------------------------------------------
  # SERVIDOR DNS SECUNDÁRIO (NS2)
  # ---------------------------------------------------------------------------
  # Servidor DNS secundário para redundância e balanceamento de carga.
  # Sincroniza com o DNS primário e fornece resolução backup.
  ns2 = {
    vcpus    = 2,          # Especificações idênticas ao NS1
    memory   = "2048",     # 2GB RAM para operações DNS
    os       = "oracle9",  # Mesmo SO do NS1 para consistência
    
    # Interface única na rede DMZ
    networks = [
      { 
        name = "dmz", 
        ipv4 = "10.32.16.4", ipv4_prefix = 24,     # IP fixo do DNS secundário
        ipv6 = "fd00:32:16::4", ipv6_prefix = 64,  # IPv6 do DNS secundário
        if_name = "ens3" 
      }
    ]
  }
  
  # ---------------------------------------------------------------------------
  # SERVIDORES ADICIONAIS
  # ---------------------------------------------------------------------------
  # Outros servidores podem ser definidos aqui seguindo o mesmo padrão.
  # Exemplos de servidores comuns em infraestruturas:
  #
  # web1 = {
  #   vcpus  = 4,
  #   memory = "4096",
  #   os     = "debian12",
  #   networks = [
  #     { name = "dmz", ipv4 = "10.32.16.10", ipv4_prefix = 24, 
  #       ipv6 = "fd00:32:16::10", ipv6_prefix = 64, if_name = "ens3" }
  #   ]
  # },
  #
  # db1 = {
  #   vcpus  = 8,
  #   memory = "8192",
  #   os     = "oracle9",
  #   networks = [
  #     { name = "cgr", ipv4 = "10.48.32.10", ipv4_prefix = 24,
  #       ipv6 = "fd00:48:32::10", ipv6_prefix = 64, if_name = "ens3" }
  #   ]
  # }
  
  # Placeholder para indicar que outros servidores foram omitidos
  # [...]
}

# =============================================================================
# NOTAS SOBRE CONFIGURAÇÃO DE SERVIDORES
# =============================================================================
#
# ESPECIFICAÇÕES DE HARDWARE:
# - vcpus: Ajuste conforme a carga esperada do serviço
# - memory: Especificado como string em MB (ex: "2048" = 2GB)
# - Considere reservar recursos para o host físico
#
# CONFIGURAÇÃO DE REDE:
# - Cada servidor pode ter múltiplas interfaces
# - IPs estáticos são configurados via Cloud-Init
# - DHCP pode ser usado em redes que suportam
# - Prefixos de rede devem ser consistentes com a topologia
#
# SISTEMAS OPERACIONAIS:
# - Use perfis definidos em terraform.tfvars (os_profiles)
# - Certifique-se de que os templates existem no pool base
# - Considere padronizar SOs por função (ex: Debian para web, Oracle para DB)
#
# NOMENCLATURA:
# - Use nomes descritivos que indiquem a função do servidor
# - Mantenha consistência na convenção de nomes
# - Considere incluir ambiente (dev, prod) se aplicável

Explicação:

Este arquivo define as especificações de cada máquina virtual a ser criada. Cada bloco dentro de servers representa uma VM, com as seguintes propriedades:

  • Nome da VM (ex: gateway, ns1, ns2): A chave do mapa servers é o nome da máquina virtual.
  • vcpus: Número de CPUs virtuais alocadas para a VM.
  • memory: Quantidade de memória RAM em MB (especificada como string).
  • os: O perfil do sistema operacional a ser usado, que deve corresponder a um dos perfis definidos em os_profiles no terraform.tfvars.
  • networks: Uma lista de objetos, onde cada objeto representa uma interface de rede para a VM. Para cada interface, você define:
    • name: O nome da rede virtual Libvirt à qual a interface será conectada.
    • ipv4 e ipv6: O endereço IP (ou dhcp para obter um IP via DHCP).
    • ipv4_prefix e ipv6_prefix: O prefixo da rede (ex: 24 para /24).
    • if_name: O nome da interface de rede dentro da VM (ex: ens3).

Este arquivo permite uma flexibilidade enorme para definir a topologia da sua infraestrutura, criando diferentes tipos de servidores com configurações de hardware e rede personalizadas.

networks.auto.tfvars

Similar ao servers.auto.tfvars, este arquivo é carregado automaticamente pelo Terraform e é usado para definir as redes virtuais específicas que serão criadas, atribuindo valores à variável networks declarada em variables.tf.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# =============================================================================
# DEFINIÇÃO DE REDES VIRTUAIS DA INFRAESTRUTURA
# =============================================================================
# Este arquivo define todas as redes virtuais que serão criadas no ambiente
# libvirt. Cada rede tem uma finalidade específica na arquitetura e pode
# operar em diferentes modos dependendo dos requisitos de conectividade.

networks = {
  # ---------------------------------------------------------------------------
  # REDE EXTERNA - Conexão com Internet
  # ---------------------------------------------------------------------------
  # Rede que fornece conectividade com a internet através de NAT.
  # Utilizada pelo gateway para obter conectividade externa.
  external = {
    net_name  = "external"              # Nome da rede no libvirt
    net_mode  = "nat"                   # Modo NAT para acesso à internet
    ipv4_cidr = "192.168.100.0/24"      # Bloco IPv4 gerenciado pelo libvirt
    ipv6_cidr = "fd12:ee::/64"          # Bloco IPv6 gerenciado pelo libvirt
  },

  # ---------------------------------------------------------------------------
  # REDE DMZ - Zona Desmilitarizada
  # ---------------------------------------------------------------------------
  # Rede isolada para serviços que precisam ser acessíveis externamente
  # mas devem ser segregados da rede interna. Inclui servidores DNS,
  # web servers e outros serviços públicos.
  dmz = {
    net_name  = "dmz"                   # Nome da rede no libvirt
    net_mode  = "none"                  # Rede isolada sem DHCP automático
    ipv4_cidr = "10.32.16.0/24"         # Bloco IPv4 da DMZ (configuração manual)
    ipv6_cidr = "fd00:32:16::/64"       # Bloco IPv6 da DMZ (configuração manual)
  },

  # ---------------------------------------------------------------------------
  # REDE CGR - Centro de Gerência de Redes
  # ---------------------------------------------------------------------------
  # Rede que terá acesso administrativo a toda a infraestrutura.
  # Acesso SSH a rede DMZ, por exemplo, só será permitido a partir desta rede
  # As demais serão bloqueadas no firewall.
  cgr = {
    net_name  = "cgr"                   # Nome da rede no libvirt
    net_mode  = "none"                  # Rede isolada sem DHCP automático
    ipv4_cidr = "10.48.32.0/24"         # Bloco IPv4 da rede corporativa
    ipv6_cidr = "fd00:48:32::/64"       # Bloco IPv6 da rede corporativa
  },

  # ---------------------------------------------------------------------------
  # REDE DHCP - Clientes Dinâmicos
  # ---------------------------------------------------------------------------
  # Rede para dispositivos que obtêm configuração automaticamente via DHCP.
  # Utilizada para estações de trabalho, dispositivos temporários e
  # sistemas que não requerem IPs fixos.
  dhcp = {
    net_name  = "dhcp"                  # Nome da rede no libvirt
    net_mode  = "none"                  # Rede isolada (DHCP gerenciado externamente)
    ipv4_cidr = "10.128.112.0/20"       # Bloco IPv4 maior para muitos clientes
    ipv6_cidr = "fd00:128:112::/64"     # Bloco IPv6 para clientes DHCP
  }
}

# =============================================================================
# NOTAS IMPORTANTES SOBRE CONFIGURAÇÃO DE REDES
# =============================================================================
#
# ENDEREÇAMENTO IP:
# - Os blocos CIDR definidos aqui são principalmente para documentação
# - Para redes com net_mode = "none", o endereçamento é configurado manualmente
# - Para redes com net_mode = "nat", o libvirt gerencia o endereçamento automaticamente
# - Certifique-se de que não há sobreposição entre blocos de diferentes redes
#
# MODOS DE REDE DISPONÍVEIS:
# - "nat": Rede com NAT para acesso à internet (DHCP automático)
# - "none": Rede isolada sem serviços automáticos (configuração manual)
# - "bridge": Rede em bridge com interface física (requer configuração adicional)
# - "route": Rede roteada sem NAT (requer configuração de roteamento)
#
# PLANEJAMENTO DE ENDEREÇOS:
# - Use blocos RFC 1918 para redes privadas (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
# - Use endereços IPv6 ULA (Unique Local Address) com prefixo fd00::/8
# - Mantenha consistência na numeração entre IPv4 e IPv6
# - Reserve espaço para crescimento futuro da rede
#
# SEGURANÇA E ISOLAMENTO:
# - Redes com mode "none" são completamente isoladas por padrão
# - O roteamento entre redes deve ser configurado no gateway
# - Implemente regras de firewall apropriadas no gateway
# - Considere VLANs adicionais para maior segmentação se necessário
#
# DHCP E DNS:
# - Para redes isoladas, configure DHCP no gateway se necessário
# - Configure encaminhamento DNS apropriado para resolução de nomes
# - Mantenha registros DNS atualizados para IPs estáticos

Explicação:

Este arquivo define as redes virtuais que serão criadas no ambiente Libvirt. Cada bloco dentro de networks representa uma rede, com as seguintes propriedades:

  • Nome da Rede (ex: external, dmz, cgr, dhcp): A chave do mapa networks é o nome da rede virtual.
  • net_name: O nome da rede no Libvirt.
  • net_mode: O modo de operação da rede. Pode ser nat (com NAT para acesso à internet), none (rede isolada sem DHCP automático, exigindo configuração manual de IP nas VMs), ou bridge (rede em bridge com interface física).
  • ipv4_cidr e ipv6_cidr: Os blocos CIDR IPv4 e IPv6 da rede. Para redes com `net_mode =

none, esses CIDRs são principalmente para documentação, pois o endereçamento é configurado manualmente via Cloud-Init. Para redes nat`, o Libvirt gerencia o endereçamento automaticamente.

Este arquivo permite definir a topologia de rede da sua infraestrutura, criando redes com diferentes propósitos e modos de operação para isolamento e conectividade.

cloud-init/network_config.yml

Este é um template de configuração de rede para o Cloud-Init, que será usado para configurar as interfaces de rede dentro das máquinas virtuais. Ele suporta tanto configurações DHCP quanto estáticas, com lógica para roteamento e DNS.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#cloud-config
# =============================================================================
# TEMPLATE DE CONFIGURAÇÃO DE REDE CLOUD-INIT
# =============================================================================
# Este template gera a configuração de rede para cada VM baseado em suas
# interfaces definidas. Suporta tanto configuração DHCP quanto estática,
# com roteamento e DNS configurados automaticamente conforme necessário.

network:
  version: 2  # Versão 2 do formato netplan/cloud-init
  ethernets:
%{ for iface in interfaces ~}
    # -------------------------------------------------------------------------
    # INTERFACE: ${iface.if_name} (Rede: ${iface.name})
    # -------------------------------------------------------------------------
    ${iface.if_name}:
      %{ if iface.ipv4 == "dhcp" }
      # Configuração DHCP - Obtém IP automaticamente
      dhcp4: true   # Habilita DHCP para IPv4
      dhcp6: true   # Habilita DHCP para IPv6
      %{ else }
      # Configuração estática - IPs definidos manualmente
      dhcp4: no           # Desabilita DHCP para IPv4
      dhcp6: no           # Desabilita DHCP para IPv6
      accept-ra: false    # Desabilita Router Advertisement IPv6
      
      # Endereços IP estáticos da interface
      addresses:
        - ${iface.ipv4}/${iface.ipv4_prefix}    # Endereço IPv4 com prefixo
        %{ if iface.ipv6 != "dhcp" }
        - ${iface.ipv6}/${iface.ipv6_prefix}    # Endereço IPv6 com prefixo
        %{ endif }
      
      %{ if iface.ipv4 != "dhcp" && !has_dhcp_interface }
      # Configuração de DNS e roteamento para interfaces estáticas
      # (Aplicado apenas se não houver interfaces DHCP na VM)
        %{ if hostname == "ns1" || hostname == "ns2" }
      # Configuração especial para servidores DNS
      nameservers:
        addresses:
          - 127.0.0.1     # Localhost para servidores DNS
          - "::1"         # Localhost IPv6 para servidores DNS
        %{ else }
      # Configuração padrão de DNS para outras VMs
      nameservers:
        addresses:
          - ${dmz_config.ns1_v4}    # DNS primário IPv4
          - ${dmz_config.ns2_v4}    # DNS secundário IPv4
          - ${dmz_config.ns1_v6}    # DNS primário IPv6
          - ${dmz_config.ns2_v6}    # DNS secundário IPv6
        %{ endif }
      
      # Configuração de rotas padrão
      routes:
        - to: 0.0.0.0/0                    # Rota padrão IPv4
          via: ${dmz_config.gateway_v4}    # Gateway IPv4 da DMZ
        %{ if iface.ipv6 != "dhcp" }
        - to: "::/0"                       # Rota padrão IPv6
          via: ${dmz_config.gateway_v6}    # Gateway IPv6 da DMZ
        %{ endif }
      %{ endif }
      %{ endif }
%{ endfor ~}

# =============================================================================
# NOTAS SOBRE CONFIGURAÇÃO DE REDE
# =============================================================================
#
# LÓGICA DE CONFIGURAÇÃO:
# - Interfaces DHCP: Obtêm configuração automaticamente (IP, DNS, rotas)
# - Interfaces estáticas: Requerem configuração manual de IP, DNS e rotas
# - Servidores DNS (ns1, ns2): Usam localhost como DNS para evitar loops
# - VMs mistas: Se têm interface DHCP, não configuram DNS/rotas nas estáticas
#
# PRECEDÊNCIA DE CONFIGURAÇÃO:
# 1. Se a VM tem pelo menos uma interface DHCP, ela obtém DNS/rotas via DHCP
# 2. Se todas as interfaces são estáticas, configura DNS/rotas manualmente
# 3. Servidores DNS sempre usam localhost independente de outras interfaces
#
# ROTEAMENTO:
# - Rota padrão IPv4: 0.0.0.0/0 via gateway DMZ
# - Rota padrão IPv6: ::/0 via gateway DMZ
# - Rotas específicas podem ser adicionadas conforme necessário
#
# DNS:
# - VMs normais: Usam servidores DNS da DMZ (ns1 e ns2)
# - Servidores DNS: Usam localhost para evitar dependência circular
# - Ordem dos servidores: Primário primeiro, depois secundário
#
# TROUBLESHOOTING:
# - Verifique conectividade: ping para gateway e DNS
# - Teste resolução: nslookup ou dig para domínios conhecidos
# - Verifique rotas: ip route show (IPv4) e ip -6 route show (IPv6)
# - Logs do Cloud-Init: /var/log/cloud-init.log e /var/log/cloud-init-output.log

Explicação:

Este template utiliza a sintaxe do netplan (versão 2) para configurar as interfaces de rede. Ele é dinâmico e se adapta com base nas propriedades passadas pelo Terraform:

  • Loop for iface in interfaces: Itera sobre cada interface de rede definida para a VM, configurando-a individualmente.
  • Configuração DHCP vs. Estática: Usa uma condicional (%{ if iface.ipv4 == "dhcp" }) para determinar se a interface deve obter um IP via DHCP ou se deve usar um IP estático. Para IPs estáticos, ele define os endereços IPv4 e IPv6 com seus respectivos prefixos.
  • Configuração de DNS e Rotas: Para interfaces estáticas (e se a VM não tiver nenhuma interface DHCP), o template configura os servidores DNS e as rotas padrão. Há uma lógica especial para os servidores DNS (ns1, ns2) que os faz usar 127.0.0.1 e ::1 como DNS, evitando dependências circulares. Para as demais VMs, ele usa os servidores DNS definidos em network_dmz.

Este template garante que cada VM tenha sua configuração de rede correta aplicada automaticamente no momento da inicialização, seja ela DHCP ou estática, com as rotas e DNS apropriados para a topologia da rede.

cloud-init/user_data.yml

Este template é responsável pela configuração de usuários, chaves SSH e execução de comandos iniciais dentro da máquina virtual. Ele garante que a VM esteja pronta para uso com um usuário administrativo e acesso seguro via SSH.

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
77
78
79
80
81
#cloud-config
# =============================================================================
# TEMPLATE DE CONFIGURAÇÃO DE USUÁRIO CLOUD-INIT
# =============================================================================
# Este template configura usuários, sistema e serviços básicos durante
# a inicialização da VM. Inclui criação de usuário administrativo,
# configuração de SSH e definição de hostname.

# Gerenciamento automático do arquivo /etc/hosts
manage_etc_hosts: true

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE USUÁRIOS
# -----------------------------------------------------------------------------
# Cria o usuário administrativo principal com privilégios sudo e acesso SSH
users:
  - name: ${user_name}                    # Nome do usuário (ex: suporte)
    gecos: ${gecos}                       # Nome completo do usuário
    sudo: ALL=(ALL) NOPASSWD:ALL          # Privilégios sudo sem senha
    groups: ${jsonencode(groups)}          # Grupos do usuário (sudo, wheel, etc.)
    shell: /bin/bash                      # Shell padrão
    lock_passwd: true                     # Bloqueia login por senha
    ssh_authorized_keys:                  # Chaves SSH autorizadas
      - "${ssh_key}"                      # Chave pública SSH para acesso

# -----------------------------------------------------------------------------
# CONFIGURAÇÕES DE SEGURANÇA SSH
# -----------------------------------------------------------------------------
# Desabilita métodos de autenticação menos seguros
disable_root: true      # Desabilita login direto como root
ssh_pwauth: false       # Desabilita autenticação por senha via SSH

# -----------------------------------------------------------------------------
# COMANDOS DE INICIALIZAÇÃO
# -----------------------------------------------------------------------------
# Comandos executados após a configuração inicial do sistema
runcmd:
  # Define o hostname da máquina
  - hostnamectl set-hostname ${hostname}
  
  # Adiciona entrada no /etc/hosts para resolução local
  - echo '127.0.1.1 ${hostname}' >> /etc/hosts
  
  # Comandos adicionais podem ser adicionados aqui:
  # - Instalação de pacotes específicos
  # - Configuração de serviços
  # - Aplicação de patches de segurança
  # - Configuração de monitoramento

# =============================================================================
# CONFIGURAÇÕES ADICIONAIS DISPONÍVEIS
# =============================================================================
#
# INSTALAÇÃO DE PACOTES:
# packages:
#   - htop
#   - vim
#   - curl
#   - wget
#
# ATUALIZAÇÃO DO SISTEMA:
# package_update: true
# package_upgrade: true
#
# CONFIGURAÇÃO DE TIMEZONE:
# timezone: America/Sao_Paulo
#
# CONFIGURAÇÃO DE LOCALE:
# locale: pt_BR.UTF-8
#
# ESCRITA DE ARQUIVOS:
# write_files:
#   - path: /etc/motd
#     content: |
#       Bem-vindo ao servidor ${hostname}
#       Acesso restrito - Use responsavelmente
#
# CONFIGURAÇÃO DE SERVIÇOS:
# runcmd:
#   - systemctl enable --now nginx
#   - systemctl disable --now apache2

Explicação:

Este template define as configurações de usuário e sistema para as VMs:

  • manage_etc_hosts: true: Garante que o Cloud-Init gerencie o arquivo /etc/hosts da VM.
  • users: Define o usuário administrativo principal. As propriedades name, gecos, sudo, groups, shell, lock_passwd e ssh_authorized_keys são preenchidas dinamicamente com base nas variáveis passadas pelo Terraform. O sudo: ALL=(ALL) NOPASSWD:ALL concede privilégios de superusuário sem a necessidade de senha, e ssh_authorized_keys injeta a chave pública SSH para acesso seguro.
  • disable_root: true e ssh_pwauth: false: Aumentam a segurança desabilitando o login direto como root e a autenticação por senha via SSH, forçando o uso de chaves SSH.
  • runcmd: Uma lista de comandos que serão executados após a configuração inicial do sistema. Aqui, o hostname da máquina é definido e uma entrada para o hostname é adicionada ao /etc/hosts.

Este template automatiza a criação de usuários e a configuração inicial de segurança, tornando as VMs prontas para uso e acessíveis via SSH imediatamente após o provisionamento.

Configuração Alternativa: Autenticação por Senha

Embora a autenticação por chave SSH seja a mais recomendada para ambientes de produção devido à sua segurança, pode haver cenários (como ambientes de desenvolvimento ou testes) onde o acesso por senha é desejado. O Cloud-Init permite configurar isso com algumas modificações no user_data.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
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
#cloud-config
# =============================================================================
# TEMPLATE ALTERNATIVO COM AUTENTICAÇÃO POR SENHA
# =============================================================================
# Esta configuração permite login tanto por chave SSH quanto por senha,
# útil para ambientes de desenvolvimento ou quando o acesso por chave
# não está disponível.

manage_etc_hosts: true

# Habilita autenticação por senha via SSH
ssh_pwauth: true

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE USUÁRIO COM SENHA
# -----------------------------------------------------------------------------
users:
  - name: ${user_name}                    # Nome do usuário
    gecos: ${gecos}                       # Nome completo do usuário
    sudo: ALL=(ALL) NOPASSWD:ALL          # Privilégios sudo sem senha
    groups: ${jsonencode(groups)}          # Grupos do usuário
    shell: /bin/bash                      # Shell padrão
    lock_passwd: false                    # Permite login por senha
    passwd: ${user_password}              # Hash da senha (gerado com mkpasswd)
    ssh_authorized_keys:                  # Chaves SSH (ainda funcionam)
      - "${ssh_key}"

# -----------------------------------------------------------------------------
# COMANDOS DE INICIALIZAÇÃO
# -----------------------------------------------------------------------------
runcmd:
  - hostnamectl set-hostname ${hostname}
  - echo '127.0.1.1 ${hostname}' >> /etc/hosts

# =============================================================================
# NOTAS DE SEGURANÇA PARA CONFIGURAÇÃO COM SENHA
# =============================================================================
#
# GERAÇÃO DE SENHA SEGURA:
# - Use: mkpasswd --method=SHA-512
# - Exemplo: mkpasswd --method=SHA-512 "MinhaSenh@123"
# - O resultado é um hash que deve ser usado em ${user_password}
#
# CONSIDERAÇÕES DE SEGURANÇA:
# - Senhas devem ser complexas (maiúsculas, minúsculas, números, símbolos)
# - Considere usar apenas em ambientes de desenvolvimento
# - Para produção, prefira autenticação apenas por chave SSH
# - Monitore tentativas de login por senha nos logs
#
# CONFIGURAÇÕES ADICIONAIS DE SSH:
# - Considere alterar a porta padrão do SSH (22)
# - Configure fail2ban para proteção contra ataques de força bruta
# - Use autenticação de dois fatores quando possível

Explicação das Modificações:

  • ssh_pwauth: true: Habilita a autenticação por senha para o SSH.
  • lock_passwd: false: Permite o login por senha para o usuário.
  • passwd: ${user_password}: Adiciona o hash da senha do usuário. É crucial que este hash seja gerado de forma segura (ex: usando mkpasswd --method=SHA-512) e não a senha em texto claro. Você precisaria adicionar uma nova variável user_password em variables.tf e fornecer seu valor em terraform.tfvars se for usar esta abordagem.

Recomendação de Segurança:

Para ambientes de produção, sempre prefira a autenticação por chave SSH. A autenticação por senha, mesmo com senhas fortes, é mais suscetível a ataques de força bruta. Se for usar senhas, certifique-se de que sejam complexas e considere implementar medidas adicionais de segurança, como fail2ban e autenticação de dois fatores.

Instruções para Geração de Credenciais

Para que o Cloud-Init possa configurar o acesso às suas VMs de forma segura, você precisará gerar uma chave SSH e, opcionalmente, uma senha criptografada.

Geração de Senha Criptografada (Opcional)

Se você optar por usar a configuração alternativa com senha, precisará gerar um hash da senha. Nunca use a senha em texto claro no seu código Terraform!

1
2
3
4
5
6
7
8
# Instale o pacote 'whois' se 'mkpasswd' não estiver disponível
sudo apt update && sudo apt install -y whois

# Gera hash de senha compatível com Cloud-Init
mkpasswd --method=SHA-512 "SuaSenhaSegura123"

# Exemplo de saída:
# $6$rounds=656000$YourSaltHere$HashedPasswordHere...

Copie o hash gerado e use-o no terraform.tfvars (se você adicionar a variável user_password).

Geração de Chave SSH

É altamente recomendado usar chaves SSH para acesso seguro às suas VMs. Se você ainda não tem um par de chaves, pode gerá-lo da seguinte forma:

1
2
3
4
5
6
7
8
# Gera par de chaves SSH Ed25519 (recomendado por ser mais seguro e moderno)
ssh-keygen -t ed25519 -C "seu_email@exemplo.com" -f ~/.ssh/tfvms

# Gera par de chaves SSH RSA (alternativa, menos moderna)
ssh-keygen -t rsa -b 4096 -C "seu_email@exemplo.com" -f ~/.ssh/tfvms

# Exibe a chave pública para uso no terraform.tfvars
cat ~/.ssh/tfvms.pub

Copie o conteúdo da chave pública (o que for exibido pelo comando cat ~/.ssh/tfvms.pub) e cole-o no terraform.tfvars na variável ssh_public_key.

Implantação da Infraestrutura com Terraform

Com todos os arquivos de configuração prontos, você pode agora implantar sua infraestrutura KVM/LIBVIRT usando os comandos do Terraform.

  1. Inicialize o Diretório Terraform: Navegue até o diretório raiz do seu projeto Terraform (onde estão os arquivos .tf) e execute o comando de inicialização. Isso fará o download dos provedores necessários (como o libvirt).
    1
    
    terraform init
    
  2. Planeje a Implantação: O comando terraform plan permite que você visualize as alterações que o Terraform fará em sua infraestrutura antes de aplicá-las. É uma boa prática revisar este plano para garantir que tudo esteja conforme o esperado.
    1
    
    terraform plan
    
  3. Aplique as Alterações: Uma vez satisfeito com o plano, execute o comando terraform apply para provisionar os recursos definidos. O Terraform solicitará uma confirmação antes de prosseguir.
    1
    
    terraform apply
    

    Se você quiser pular a confirmação (não recomendado para produção), pode usar:

    1
    
    terraform apply -auto-approve
    

Verificação e Acesso às VMs

Após a conclusão do terraform apply, suas máquinas virtuais estarão provisionadas e configuradas. Você pode verificar o status e acessá-las da seguinte forma:

  1. Listar VMs no Libvirt: Use o comando virsh list --all para ver todas as máquinas virtuais gerenciadas pelo Libvirt.
    1
    
    virsh list --all
    
  2. Obter Endereços IP das VMs: Os endereços IP das VMs podem ser obtidos de várias maneiras. Uma forma é inspecionar os logs do Cloud-Init ou, se a VM já estiver em execução, usar virsh domifaddr <nome_da_vm> (pode levar um tempo para o DHCP atribuir o IP).
    1
    2
    
    # Exemplo para obter o IP do gateway
    virsh domifaddr gateway
    

    Alternativamente, você pode verificar os outputs do Terraform se tiver configurado:

    1
    2
    3
    
    output "vm_ips" {
      value = { for k, v in libvirt_domain.domain : k => v.network_interface[0].addresses[0] }
    }
    

    E então executar terraform output vm_ips.

  3. Acessar as VMs via SSH: Com a chave SSH configurada e o IP da VM, você pode acessá-la via SSH. Lembre-se que o usuário padrão criado pelo Cloud-Init é suporte (ou o que você definiu em default_vm_user).
    1
    
    ssh -i ~/.ssh/tfvms suporte@IP_DA_VM
    

    Para facilitar o acesso, você pode configurar seu arquivo ~/.ssh/config:

    1
    2
    3
    4
    
    echo "Host vm-*
    User suporte
    IdentityFile ~/.ssh/tfvms
    StrictHostKeyChecking no" >> ~/.ssh/config
    

    Após isso, você pode se conectar usando um nome mais amigável, por exemplo:

    1
    
    ssh vm-gateway
    
  4. Destruir a Infraestrutura (Opcional): Quando você não precisar mais da infraestrutura, pode destruí-la completamente com um único comando Terraform. ATENÇÃO: Este comando removerá IRREVERSIVELMENTE todas as VMs, volumes e redes criadas. Use com extrema cautela e certifique-se de que não há dados importantes ou dependências nesta infraestrutura antes de executar!
    1
    
    terraform destroy
    

    Assim como o apply, você pode usar -auto-approve para pular a confirmação.

Com estas instruções, você pode implantar, verificar e gerenciar sua infraestrutura KVM/LIBVIRT de forma eficiente usando Terraform.

Diagrama de Rede

Para uma melhor compreensão da topologia de rede proposta, um diagrama visual seria extremamente útil. Abaixo, uma representação simplificada da arquitetura de rede, que pode ser expandida para incluir todos os componentes e fluxos de tráfego.

graph TD
    subgraph Internet
        A[External Network]
    end

    subgraph KVM Host
        B(Libvirt External Network) -- NAT --> A
        B -- eth0 --> C[Gateway VM]

        subgraph DMZ
            D(Libvirt DMZ Network) -- eth1 --> C
            D -- eth0 --> E[NS1 VM]
            D -- eth0 --> F[NS2 VM]
        end

        subgraph CGR
            G(Libvirt CGR Network) -- eth2 --> C
            G -- eth0 --> H[DB1 VM (Exemplo)]
        end

        subgraph DHCP
            I(Libvirt DHCP Network) -- eth3 --> C
            I -- eth0 --> J[Client VM (Exemplo)]
        end

        C -- eth1 --> D
        C -- eth2 --> G
        C -- eth3 --> I
    end

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#ccf,stroke:#333,stroke-width:2px
    style D fill:#fcf,stroke:#333,stroke-width:2px
    style E fill:#ccf,stroke:#333,stroke-width:2px
    style F fill:#ccf,stroke:#333,stroke-width:2px
    style G fill:#ccf,stroke:#333,stroke-width:2px
    style H fill:#ccf,stroke:#333,stroke-width:2px
    style I fill:#bbf,stroke:#333,stroke-width:2px
    style J fill:#ccf,stroke:#333,stroke-width:2px

Explicação do Diagrama:

  • External Network: Representa a conexão com a internet.
  • Libvirt External Network: Rede virtual do Libvirt com NAT para acesso à internet.
  • Gateway VM: Máquina virtual que atua como roteador e firewall, conectando todas as redes internas à rede externa.
  • Libvirt DMZ Network: Rede isolada para serviços públicos (como NS1 e NS2).
  • NS1 VM / NS2 VM: Máquinas virtuais de servidores DNS primário e secundário.
  • Libvirt CGR Network: Rede isolada para gerenciamento e acesso administrativo (ex: DB1 VM).
  • DB1 VM (Exemplo): Máquina virtual de banco de dados, acessível apenas pela rede CGR.
  • Libvirt DHCP Network: Rede para clientes que obtêm IP via DHCP.
  • Client VM (Exemplo): Máquina virtual cliente que obtém IP via DHCP.

Este diagrama ajuda a visualizar o isolamento e a comunicação entre as diferentes zonas da infraestrutura, reforçando a importância do Gateway VM como ponto central de controle de tráfego.

Outputs do Terraform

Para facilitar a obtenção de informações sobre os recursos criados, como os endereços IP das VMs, você pode definir outputs no seu código Terraform. Crie um arquivo outputs.tf com o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
12
# outputs.tf

output "vm_ips" {
  description = "Endereços IP das máquinas virtuais criadas"
  value = {
    for vm_name, vm_config in libvirt_domain.domain :
    vm_name => {
      for iface in vm_config.network_interface :
      iface.hostname => iface.addresses
    }
  }
}

Após aplicar as alterações com terraform apply, você pode executar terraform output vm_ips para ver uma lista dos IPs de todas as VMs.

Gerenciamento de Estado (State Management)

O Terraform mantém um arquivo de estado (terraform.tfstate) que mapeia os recursos reais da sua infraestrutura para a sua configuração. Este arquivo é crucial para o funcionamento do Terraform, pois ele usa o estado para determinar quais alterações precisam ser feitas para alcançar o estado desejado.

Considerações Importantes:

  • Estado Local: Por padrão, o Terraform armazena o arquivo de estado localmente no diretório do projeto. Para projetos pequenos e individuais, isso pode ser suficiente.
  • Estado Remoto: Para equipes e ambientes de produção, é altamente recomendado configurar um backend remoto para o estado (ex: S3, Azure Blob Storage, Terraform Cloud). Isso oferece:
    • Colaboração: Permite que múltiplos usuários trabalhem no mesmo projeto sem conflitos.
    • Bloqueio de Estado: Previne que múltiplos terraform apply sejam executados simultaneamente, evitando corrupção do estado.
    • Histórico de Versões: Mantém um histórico de todas as alterações no estado, facilitando a recuperação em caso de erros.
    • Segurança: Armazena o estado em um local seguro e criptografado.

Exemplo de Configuração de Backend S3 (em providers.tf):

1
2
3
4
5
6
7
8
9
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "kvm-libvirt/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-terraform-state-lock"
  }
}

Nota: A configuração de um backend remoto deve ser feita antes do primeiro terraform apply. Se você já tem um estado local, precisará migrá-lo para o backend remoto.

Considerações de Performance para KVM/Libvirt

Na seção domain.tf, o uso de cpu { mode = "host-passthrough" } já é um excelente passo para otimizar a performance da CPU. No entanto, outras otimizações podem ser aplicadas para melhorar o desempenho geral das máquinas virtuais:

  • Drivers VirtIO: Para discos e interfaces de rede, é altamente recomendado usar os drivers VirtIO. Eles são drivers paravirtualizados que oferecem melhor desempenho em comparação com a emulação de hardware tradicional. Certifique-se de que suas imagens de Cloud-Init incluam os módulos virtio necessários.
    • Exemplo de configuração de disco com VirtIO (em domain.tf):
      1
      2
      3
      4
      
      disk {
        volume_id = libvirt_volume.os_image[each.key].id
        target_bus = "virtio"  # Define o barramento como VirtIO
      }
      
    • Exemplo de configuração de interface de rede com VirtIO (em domain.tf):
      1
      2
      3
      4
      5
      
      network_interface {
        network_name = network_interface.value.name
        model = "virtio"  # Define o modelo da interface como VirtIO
        # ... outras configurações
      }
      
  • IO Thread: Para cargas de trabalho intensivas em I/O, configurar um IO thread dedicado para os volumes de disco pode melhorar significativamente o desempenho. Isso permite que as operações de I/O sejam processadas em um thread separado, reduzindo a latência.
    • Exemplo de configuração de IO Thread (em domain.tf):
      1
      2
      3
      4
      5
      
      disk {
        volume_id = libvirt_volume.os_image[each.key].id
        target_bus = "virtio"
        io_thread = "1"  # Associa o disco ao IO thread 1
      }
      
    • Nota: Você precisaria definir o io_thread no recurso libvirt_domain também, se estiver usando múltiplos threads de I/O.
  • Cache de Disco: A configuração do cache de disco pode ter um grande impacto no desempenho. As opções comuns são writeback (melhor desempenho, maior risco de perda de dados em caso de falha), writethrough (equilíbrio entre desempenho e segurança) e none (mais seguro, menor desempenho).
    • Exemplo de configuração de cache (em domain.tf):
      1
      2
      3
      4
      5
      
      disk {
        volume_id = libvirt_volume.os_image[each.key].id
        target_bus = "virtio"
        cache = "writeback"  # Ou "writethrough", "none"
      }
      

Ao aplicar essas otimizações, você pode garantir que suas máquinas virtuais KVM/Libvirt operem com a máxima eficiência, especialmente em ambientes de produção com altas demandas de recursos.

Configuração de Firewall no Gateway

O tutorial menciona que o servidor gateway atuará como roteador e firewall. Para garantir o isolamento e a segurança das redes internas, é crucial configurar regras de firewall adequadas dentro da VM gateway. Isso complementa o isolamento de rede fornecido pelo Libvirt e controla o tráfego entre as diferentes zonas (DMZ, CGR, DHCP) e a rede externa.

Ferramentas Comuns de Firewall no Linux:

  • iptables / nftables: Ferramentas de linha de comando para configurar o firewall do kernel Linux. nftables é a sucessora moderna do iptables.
  • firewalld: Um daemon que fornece uma interface dinâmica para gerenciar o firewall, facilitando a configuração de zonas e serviços.

Exemplo Simplificado de Regras (usando iptables para ilustrar o conceito):

Este é um exemplo conceitual. As regras exatas dependerão da sua política de segurança.

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
# Exemplo de script de firewall (a ser executado dentro da VM gateway)

# Limpa regras existentes
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Permite tráfego de loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Permite tráfego já estabelecido ou relacionado
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Habilita NAT para a rede externa (se aplicável)
iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE

# Permite SSH de redes específicas (ex: CGR)
iptables -A INPUT -i ens5 -p tcp --dport 22 -j ACCEPT
iptables -A FORWARD -i ens5 -o ens4 -p tcp --dport 22 -j ACCEPT # CGR para DMZ (SSH)

# Permite tráfego DNS da DMZ para a rede externa
iptables -A FORWARD -i ens4 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i ens4 -p tcp --dport 53 -j ACCEPT

# Bloqueia todo o resto
iptables -A INPUT -j LOG --log-prefix "IPTABLES_DROP: "
iptables -A INPUT -j DROP
iptables -A FORWARD -j LOG --log-prefix "IPTABLES_FORWARD_DROP: "
iptables -A FORWARD -j DROP

# Salvar regras (depende da distribuição, ex: netfilter-persistent para Debian/Ubuntu)
# sudo apt install -y netfilter-persistent
# sudo netfilter-persistent save

Recomendações:

  • Política de Segurança: Defina uma política de segurança clara antes de implementar as regras de firewall. O que deve ser permitido? O que deve ser negado?
  • Menos Privilégio: Adote o princípio do menor privilégio, permitindo apenas o tráfego essencial e bloqueando todo o resto.
  • Logging: Configure o logging para o tráfego bloqueado para monitorar tentativas de acesso não autorizado.
  • Automatização: Integre a configuração do firewall ao seu processo de automação (ex: via Cloud-Init runcmd ou uma ferramenta de gerenciamento de configuração como Ansible) para garantir consistência e repetibilidade.
  • Testes: Teste exaustivamente suas regras de firewall para garantir que elas funcionem conforme o esperado e não bloqueiem tráfego legítimo.

Conclusão

Este tutorial demonstrou como utilizar o Terraform para automatizar a criação e gerenciamento de uma infraestrutura de virtualização KVM/LIBVIRT. Através da modularização dos arquivos de configuração e do uso inteligente do Cloud-Init, é possível provisionar ambientes complexos de forma rápida, consistente e repetível.

Compreender a função de cada arquivo (providers.tf, variables.tf, network.tf, volumes.tf, cloudinit.tf, domain.tf, terraform.tfvars, servers.auto.tfvars, networks.auto.tfvars) e a interação entre eles é fundamental para adaptar e expandir esta infraestrutura às suas necessidades específicas. A automação com Terraform não só economiza tempo, mas também reduz erros manuais, garantindo que seus ambientes virtuais sejam sempre provisionados de acordo com as melhores práticas e suas definições.

Esperamos que este guia detalhado sirva como um ponto de partida sólido para suas implementações de infraestrutura como código com Terraform e KVM/LIBVIRT.

This post is licensed under CC BY 4.0 by the author.