Post

Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform Modular

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.

Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform Modular

Introdução

Este tutorial aprofunda a automação de infraestruturas de virtualização KVM/LIBVIRT, apresentando uma abordagem modular com Terraform. Ao invés de um único conjunto de arquivos de configuração, exploraremos como estruturar seu projeto Terraform em módulos reutilizáveis para computação, rede e armazenamento. Essa modularização não apenas melhora a organização e a manutenibilidade do seu código, mas também permite a criação de múltiplos ambientes (desenvolvimento, homologação, produção) de forma consistente e eficiente.

Compreenderemos a estrutura de um projeto Terraform modular, o papel de cada arquivo e como eles interagem para provisionar e configurar máquinas virtuais de forma automatizada, utilizando o poder do Cloud-Init para a personalização do sistema operacional no primeiro boot. Ao final deste guia, você terá uma base sólida para construir e gerenciar infraestruturas virtualizadas complexas com Terraform, aplicando as melhores práticas de Infraestrutura como Código (IaC).

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.

Visão Geral da Infraestrutura Modular

A arquitetura modular proposta para este projeto Terraform visa otimizar a gestão de infraestruturas KVM/LIBVIRT, promovendo a reusabilidade e a escalabilidade. A ideia central é dividir a infraestrutura em componentes lógicos e independentes, cada um encapsulado em seu próprio módulo Terraform. Isso permite que equipes diferentes trabalhem em partes distintas da infraestrutura sem interferir umas nas outras, além de facilitar a replicação de ambientes e a evolução do código.

Cada módulo é responsável por um aspecto específico da infraestrutura:

  • Módulo de Computação (compute): Gerencia a criação e configuração das máquinas virtuais (domínios Libvirt), incluindo CPU, memória, discos e interfaces de rede.
  • Módulo de Rede (network): Lida com a definição e provisionamento das redes virtuais Libvirt, configurando seus modos de operação (NAT, isolada) e blocos de endereçamento IP.
  • Módulo de Armazenamento (storage): Responsável pela criação e gerenciamento dos volumes de disco para as VMs, utilizando o eficiente mecanismo de copy-on-write a partir de templates base.

Além dos módulos, o projeto inclui uma camada de environments, onde cada diretório representa um ambiente específico (ex: production, development). Dentro de cada ambiente, os módulos são orquestrados e configurados com variáveis específicas para aquele contexto, garantindo que cada ambiente seja uma instância única e isolada da infraestrutura.

Estrutura do Projeto

A organização do projeto segue uma convenção que facilita a navegação e a compreensão da infraestrutura. A estrutura de diretórios é a seguinte:

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
.
├── 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
├── environments/                  # Configurações específicas para cada ambiente
│   └── production/                # Exemplo de ambiente de produção
│       ├── main.tf                # Orquestração dos módulos para o ambiente
│       ├── networks.auto.tfvars   # Definição de redes para o ambiente
│       ├── outputs.tf             # Saídas de dados do ambiente
│       ├── providers.tf           # Provedores específicos do ambiente
│       ├── servers.auto.tfvars    # Definição de servidores para o ambiente
│       ├── terraform.tfvars       # Variáveis globais do ambiente
│       └── variables.tf           # Declaração de variáveis do ambiente
└── modules/                       # Módulos reutilizáveis da infraestrutura
    ├── compute/                   # Módulo para criação de VMs (domínios libvirt)
    │   ├── cloudinit.tf           # Criação de discos Cloud-Init
    │   ├── main.tf                # Definição principal das VMs
    │   ├── outputs.tf             # Saídas de dados do módulo compute
    │   ├── variables.tf           # Variáveis de entrada do módulo compute
    │   └── versions.tf            # Versões de provedores para o módulo compute
    ├── network/                   # Módulo para criação de redes libvirt
    │   ├── main.tf                # Definição principal das redes
    │   ├── outputs.tf             # Saídas de dados do módulo network
    │   ├── variables.tf           # Variáveis de entrada do módulo network
    │   └── versions.tf            # Versões de provedores para o módulo network
    └── storage/                   # Módulo para criação de volumes de armazenamento
        ├── main.tf                # Definição principal dos volumes
        ├── outputs.tf             # Saídas de dados do módulo storage
        ├── variables.tf           # Variáveis de entrada do módulo storage
        └── versions.tf            # Versões de provedores para o módulo storage

Esta estrutura promove uma clara separação de responsabilidades:

  • cloud-init/: Contém os templates de configuração que serão injetados nas VMs no primeiro boot. Estes templates são genéricos e reutilizáveis por qualquer ambiente ou módulo.
  • environments/: Cada subdiretório representa um ambiente de implantação. Dentro de cada ambiente, os arquivos .tf orquestram os módulos e definem as variáveis específicas para aquele ambiente (ex: IPs, nomes de VMs, etc.).
  • modules/: Contém os blocos de construção reutilizáveis da infraestrutura. Cada módulo é autocontido e focado em uma única responsabilidade (computação, rede, armazenamento).

Essa abordagem modular é fundamental para gerenciar infraestruturas complexas, pois permite que você escreva o código uma vez e o reutilize em diferentes contextos, garantindo consistência e reduzindo a duplicação de código. Além disso, facilita a testabilidade e a manutenção, pois as alterações em um módulo não afetam diretamente outros módulos, apenas suas interfaces.

Entendendo os Arquivos Terraform

Nesta seção, detalharemos o propósito e o conteúdo de cada arquivo chave no projeto modular, começando pelos templates do Cloud-Init, passando pelos arquivos de configuração de ambiente e, por fim, explorando os módulos reutilizáveis.

cloud-init/network_config.yml

Este arquivo é um template de configuração de rede para o Cloud-Init, que será utilizado para configurar as interfaces de rede dentro das máquinas virtuais. Ele é projetado para ser flexível, suportando tanto configurações DHCP quanto estáticas, e inclui lógica para roteamento e configuração de 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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#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.
#
# Variáveis de entrada:
# - interfaces: Lista de objetos de interface de rede para a VM.
# - dmz_config: Objeto contendo configurações de gateway e DNS para a rede DMZ.
# - has_dhcp_interface: Booleano indicando se a VM possui alguma interface DHCP.

network:
  version: 2  # Especifica a versão 2 do formato netplan/cloud-init
  ethernets:
%{ for iface in interfaces ~}
    # -------------------------------------------------------------------------
    # INTERFACE: ${iface.if_name} (Rede: ${iface.name})
    # -------------------------------------------------------------------------
    # Configuração para a interface de rede ${iface.if_name}, conectada à rede ${iface.name}.
    ${iface.if_name}:
      %{ if iface.ipv4 == "dhcp" }
      # Configuração DHCP: A interface obterá seu endereço IP, gateway e DNS
      # automaticamente de um servidor DHCP na rede.
      dhcp4: true   # Habilita DHCP para IPv4
      dhcp6: true   # Habilita DHCP para IPv6
      %{ else }
      # Configuração estática: O endereço IP, gateway e DNS são definidos
      # manualmente neste template.
      dhcp4: no           # Desabilita DHCP para IPv4
      dhcp6: no           # Desabilita DHCP para IPv6
      accept-ra: false    # Desabilita Router Advertisement IPv6 para evitar conflitos com configuração estática
      
      # Endereços IP estáticos da interface
      addresses:
        - ${iface.ipv4}/${iface.ipv4_prefix}    # Endereço IPv4 com prefixo de rede (ex: 192.168.1.10/24)
        %{ if iface.ipv6 != "dhcp" }
        - ${iface.ipv6}/${iface.ipv6_prefix}    # Endereço IPv6 com prefixo de rede (ex: fd00::10/64)
        %{ endif }
      
      %{ if iface.ipv4 != "dhcp" && !has_dhcp_interface }
      # Configuração de DNS e roteamento para interfaces estáticas.
      # Esta seção é aplicada apenas se a VM não possuir nenhuma interface
      # configurada para DHCP, garantindo que as configurações estáticas
      # não sejam sobrescritas por DHCP.
        %{ if hostname == "ns1" || hostname == "ns2" }
      # Configuração especial de nameservers para servidores DNS (ns1, ns2).
      # Eles usam o localhost como DNS para evitar dependências circulares
      # e garantir que a resolução de nomes funcione mesmo se os servidores
      # DNS externos ainda não estiverem totalmente operacionais.
      nameservers:
        addresses:
          - 127.0.0.1     # Localhost IPv4
          - "::1"         # Localhost IPv6
        %{ else }
      # Configuração padrão de nameservers para outras VMs.
      # As VMs usarão os servidores DNS definidos para a rede DMZ.
      nameservers:
        addresses:
          - ${dmz_config.ns1_v4}    # DNS primário IPv4 da DMZ
          - ${dmz_config.ns2_v4}    # DNS secundário IPv4 da DMZ
          - ${dmz_config.ns1_v6}    # DNS primário IPv6 da DMZ
          - ${dmz_config.ns2_v6}    # DNS secundário IPv6 da DMZ
        %{ endif }
      
      # Configuração de rotas padrão para interfaces estáticas.
      # Define o gateway padrão para o tráfego de saída da VM.
      routes:
        - to: 0.0.0.0/0                    # Rota padrão para todo o tráfego IPv4
          via: ${dmz_config.gateway_v4}    # Gateway IPv4 da rede DMZ
        %{ if iface.ipv6 != "dhcp" }
        - to: "::/0"                       # Rota padrão para todo o tráfego IPv6
          via: ${dmz_config.gateway_v6}    # Gateway IPv6 da rede DMZ
        %{ endif }
      %{ endif }
      %{ endif }
%{ endfor ~}

# =============================================================================
# NOTAS SOBRE CONFIGURAÇÃO DE REDE
# =============================================================================
#
# LÓGICA DE CONFIGURAÇÃO:
# - Interfaces DHCP: Obtêm toda a configuração de rede (IP, DNS, rotas) automaticamente do servidor DHCP.
# - Interfaces estáticas: Requerem configuração manual de IP, DNS e rotas, conforme definido neste template.
# - Servidores DNS (ns1, ns2): São configurados para usar o localhost como servidor DNS para evitar dependências circulares durante a inicialização.
# - VMs com interfaces mistas (DHCP e estáticas): Se uma VM possui pelo menos uma interface configurada para DHCP, as configurações de DNS e rotas padrão são obtidas via DHCP. As interfaces estáticas terão apenas seus IPs configurados.
#
# 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 usando as informações da rede DMZ.
# 3. Servidores DNS (ns1, ns2) sempre usam localhost como DNS, independentemente de outras interfaces.
#
# ROTEAMENTO:
# - A rota padrão IPv4 (0.0.0.0/0) é configurada para apontar para o gateway IPv4 da rede DMZ.
# - A rota padrão IPv6 (::/0) é configurada para apontar para o gateway IPv6 da rede DMZ.
# - Rotas específicas podem ser adicionadas manualmente no template, se necessário, para cenários de roteamento mais complexos.
#
# DNS:
# - Para VMs normais, os servidores DNS primário e secundário da rede DMZ (ns1 e ns2) são utilizados.
# - Para os próprios servidores DNS (ns1 e ns2), o localhost é configurado como DNS para garantir que eles possam resolver nomes localmente sem depender de si mesmos ou de outros servidores externos durante a inicialização.
# - A ordem dos servidores DNS é primário primeiro, depois secundário.
#
# TROUBLESHOOTING:
# - Verifique a conectividade: Use `ping` para testar a comunicação com o gateway e os servidores DNS.
# - Teste a resolução de nomes: Use `nslookup` ou `dig` para verificar se a VM consegue resolver nomes de domínio conhecidos.
# - Verifique as rotas de rede: Use `ip route show` para IPv4 e `ip -6 route show` para IPv6 para inspecionar as tabelas de roteamento.
# - Consulte os logs do Cloud-Init: Os logs detalhados do processo de inicialização do Cloud-Init podem ser encontrados em `/var/log/cloud-init.log` e `/var/log/cloud-init-output.log` dentro da VM. Estes logs são cruciais para diagnosticar problemas de configuração.

Explicação:

Este template utiliza a sintaxe do netplan (versão 2) para configurar as interfaces de rede dentro das VMs. Sua principal característica é a adaptabilidade, baseada nas propriedades passadas pelo Terraform:

  • Loop for iface in interfaces: O template itera sobre cada interface de rede definida para a VM, configurando-a individualmente. Isso permite que uma única VM tenha múltiplas interfaces, cada uma conectada a uma rede virtual diferente.
  • Configuração DHCP vs. Estática: Uma condicional (%{ if iface.ipv4 == "dhcp" }) é usada para determinar se a interface deve obter um endereço IP via DHCP ou se deve usar um IP estático. Para IPs estáticos, o template define os endereços IPv4 e IPv6 com seus respectivos prefixos de rede.
  • Configuração de DNS e Rotas: Para interfaces configuradas estaticamente (e se a VM não tiver nenhuma interface DHCP), o template configura os servidores DNS e as rotas padrão. Uma lógica especial é aplicada para os servidores DNS (ns1, ns2), que são configurados para usar 127.0.0.1 e ::1 como servidores DNS. Isso evita dependências circulares durante a inicialização, garantindo que os próprios servidores DNS possam resolver nomes localmente. Para as demais VMs, os servidores DNS definidos na configuração da rede DMZ (dmz_config) são utilizados.

Este template é fundamental para garantir 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 servidores DNS apropriados para a topologia da rede definida. Isso minimiza a necessidade de intervenção manual e garante a consistência da configuração de rede em toda a infraestrutura.

cloud-init/user_data.yml

Este template é responsável pela configuração de usuários, injeção de 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, além de permitir a execução de scripts de pós-instalaçã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
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
#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 a criação de um usuário administrativo,
# configuração de acesso SSH e definição do hostname da máquina.
#
# Variáveis de entrada:
# - user_name: Nome do usuário a ser criado.
# - gecos: Nome completo ou descrição do usuário.
# - groups: Lista de grupos aos quais o usuário pertencerá.
# - ssh_key: Chave pública SSH a ser autorizada para o usuário.
# - hostname: Nome do host a ser definido para a VM.

manage_etc_hosts: true  # Permite que o Cloud-Init gerencie automaticamente o arquivo /etc/hosts

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE USUÁRIOS
# -----------------------------------------------------------------------------
# Cria o usuário administrativo principal com privilégios sudo e acesso SSH.
# Este usuário será o ponto de entrada para a administração da VM.
users:
  - name: ${user_name}                    # Nome de login do usuário (ex: 'suporte')
    gecos: ${gecos}                       # Nome completo ou descrição do usuário (ex: 'Usuário de Suporte')
    sudo: ALL=(ALL) NOPASSWD:ALL          # Concede privilégios sudo sem a necessidade de senha para todos os comandos
    groups: ${jsonencode(groups)}          # Lista de grupos aos quais o usuário será adicionado (ex: ['sudo', 'users'])
    shell: /bin/bash                      # Define o shell padrão para o usuário
    lock_passwd: true                     # Bloqueia o login por senha para este usuário, forçando o uso de chave SSH
    ssh_authorized_keys:                  # Lista de chaves SSH públicas autorizadas para este usuário
      - "${ssh_key}"                      # A chave pública SSH fornecida será adicionada ao authorized_keys do usuário

# -----------------------------------------------------------------------------
# CONFIGURAÇÕES DE SEGURANÇA SSH
# -----------------------------------------------------------------------------
# Desabilita métodos de autenticação menos seguros para aumentar a segurança da VM.
disable_root: true      # Desabilita o login direto como usuário 'root' via SSH
ssh_pwauth: false       # Desabilita a autenticação por senha via SSH, permitindo apenas autenticação por chave

# -----------------------------------------------------------------------------
# COMANDOS DE INICIALIZAÇÃO
# -----------------------------------------------------------------------------
# Comandos que serão executados uma única vez após a configuração inicial do sistema
# pelo Cloud-Init. São ideais para tarefas de pós-instalação e configuração básica.
runcmd:
  # Define o hostname da máquina virtual.
  # O comando `hostnamectl set-hostname` é utilizado para definir o nome do host de forma persistente.
  - hostnamectl set-hostname ${hostname}
  
  # Adiciona uma entrada no arquivo /etc/hosts para resolução local do hostname.
  # Isso garante que a VM possa resolver seu próprio nome sem depender de DNS externo.
  - echo '127.0.1.1 ${hostname}' >> /etc/hosts
  
  # Comandos adicionais podem ser adicionados aqui para personalizar ainda mais a VM:
  # - Instalação de pacotes de software específicos (ex: `apt install nginx -y`)
  # - Configuração de serviços (ex: `systemctl enable --now apache2`)
  # - Aplicação de patches de segurança ou atualizações do sistema
  # - Configuração de ferramentas de monitoramento ou agentes de segurança

# =============================================================================
# CONFIGURAÇÕES ADICIONAIS DISPONÍVEIS NO CLOUD-INIT
# =============================================================================
# O Cloud-Init oferece uma vasta gama de opções para automação da configuração.
# Abaixo estão alguns exemplos de seções que podem ser adicionadas a este template:
#
# INSTALAÇÃO DE PACOTES:
# Define uma lista de pacotes a serem instalados no sistema operacional.
# packages:
#   - htop    # Ferramenta de monitoramento de processos
#   - vim     # Editor de texto
#   - curl    # Ferramenta para transferir dados com URLs
#   - wget    # Utilitário para download de arquivos
#
# ATUALIZAÇÃO DO SISTEMA:
# Controla se o sistema deve ser atualizado durante a inicialização.
# package_update: true  # Habilita a atualização da lista de pacotes
# package_upgrade: true # Habilita a atualização de todos os pacotes instalados
#
# CONFIGURAÇÃO DE TIMEZONE:
# Define o fuso horário do sistema.
# timezone: America/Sao_Paulo
#
# CONFIGURAÇÃO DE LOCALE:
# Define as configurações de localidade do sistema.
# locale: pt_BR.UTF-8
#
# ESCRITA DE ARQUIVOS:
# Permite criar ou modificar arquivos no sistema de arquivos da VM.
# write_files:
#   - path: /etc/motd  # Caminho completo do arquivo a ser criado/modificado
#     content: |
#       Bem-vindo ao servidor ${hostname}
#       Acesso restrito - Use responsavelmente
#     permissions: '0644' # Permissões do arquivo (opcional)
#     owner: root:root    # Proprietário e grupo do arquivo (opcional)
#
# CONFIGURAÇÃO DE SERVIÇOS:
# Permite habilitar, desabilitar ou iniciar serviços do sistema.
# runcmd:
#   - systemctl enable --now nginx   # Habilita e inicia o serviço Nginx
#   - systemctl disable --now apache2 # Desabilita e para o serviço Apache2

Explicação:

Este template define as configurações de usuário e sistema para as VMs, garantindo que elas sejam provisionadas de forma segura e pronta para uso:

  • manage_etc_hosts: true: Garante que o Cloud-Init gerencie o arquivo /etc/hosts da VM, adicionando uma entrada para o hostname da máquina. Isso permite que a VM resolva seu próprio nome localmente.
  • users: Este bloco é responsável pela criação do usuário administrativo principal. As propriedades como name, gecos, sudo, groups, shell, lock_passwd e ssh_authorized_keys são preenchidas dinamicamente com base nas variáveis passadas pelo Terraform. É importante notar que 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 fornecida, permitindo acesso seguro e sem senha à VM.
  • disable_root: true e ssh_pwauth: false: Estas configurações aumentam a segurança da VM, desabilitando o login direto como usuário root via SSH e a autenticação por senha via SSH, respectivamente. Isso força o uso exclusivo de chaves SSH para acesso, que é a prática recomendada para ambientes de produção.
  • runcmd: Uma lista de comandos que serão executados uma única vez após a configuração inicial do sistema pelo Cloud-Init. Neste exemplo, o hostname da máquina é definido e uma entrada para o hostname é adicionada ao /etc/hosts. Esta seção é ideal para tarefas de pós-instalação, como a instalação de pacotes adicionais, configuração de serviços ou aplicação de patches de segurança.

Este template automatiza a criação de usuários, a configuração inicial de segurança e a execução de comandos essenciais, tornando as VMs prontas para uso e acessíveis via SSH imediatamente após o provisionamento, com um alto nível de segurança e consistência.

Arquivos de Ambiente (environments/production/)

Os arquivos localizados no diretório environments/production/ são responsáveis por orquestrar os módulos Terraform e definir as configurações específicas para o ambiente de produção. Esta abordagem permite que você tenha configurações distintas para diferentes ambientes (ex: desenvolvimento, homologação, produção) sem duplicar o código dos módulos.

environments/production/main.tf

Este arquivo atua como o ponto central para a composição da infraestrutura do ambiente de produção. Ele invoca os módulos de rede, armazenamento e computação, passando as variáveis necessárias para cada um e estabelecendo as dependências entre eles.

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
# =============================================================================
# ORQUESTRAÇÃO DO AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo atua como o ponto central para a composição da infraestrutura
# do ambiente de produção, orquestrando a criação de recursos através da
# invocação de módulos Terraform. Ele define as dependências e passa as
# variáveis necessárias para cada módulo.

# -----------------------------------------------------------------------------
# MÓDULO DE REDE
# -----------------------------------------------------------------------------
# Responsável pela criação e gerenciamento das redes virtuais libvirt
# para o ambiente de produção, conforme definido em `networks.auto.tfvars`.
module "network" {
  source   = "../../modules/network"  # Caminho relativo para o módulo de rede
  networks = var.networks             # Passa as definições de rede para o módulo
}

# -----------------------------------------------------------------------------
# MÓDULO DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Responsável pela criação e gerenciamento dos volumes de disco para as VMs
# no ambiente de produção. Utiliza templates de SO e pools de armazenamento.
module "storage" {
  source           = "../../modules/storage"  # Caminho relativo para o módulo de armazenamento
  servers          = var.servers              # Passa as definições de servidores para o módulo
  os_profiles      = var.os_profiles          # Passa os perfis de SO para o módulo
  volume_pool      = var.volume_pool          # Pool de armazenamento para volumes de VMs
  base_volume_pool = var.base_volume_pool     # Pool de armazenamento para templates base
}

# -----------------------------------------------------------------------------
# MÓDULO DE COMPUTAÇÃO (MÁQUINAS VIRTUAIS)
# -----------------------------------------------------------------------------
# Responsável pela criação e gerenciamento das máquinas virtuais (domínios libvirt)
# no ambiente de produção. Depende dos módulos de rede e armazenamento para
# obter os recursos necessários (volumes e redes).
module "compute" {
  source            = "../../modules/compute"  # Caminho relativo para o módulo de computação
  servers           = var.servers              # Passa as definições de servidores para o módulo
  default_vm_user   = var.default_vm_user      # Usuário padrão para as VMs
  os_profiles       = var.os_profiles          # Perfis de SO para as VMs
  ssh_public_key    = var.ssh_public_key       # Chave SSH pública para acesso às VMs
  network_dmz       = var.network_dmz          # Configurações de rede DMZ para Cloud-Init
  volumes           = module.storage.volumes   # Saída do módulo de armazenamento (volumes criados)
  network_resources = module.network.networks  # Saída do módulo de rede (recursos de rede criados)
}

Explicação:

Este arquivo é o coração da orquestração do ambiente de produção. Ele define como os diferentes módulos da infraestrutura se encaixam:

  • **`module

network**: Invoca o módulo de rede, passando as definições de rede específicas do ambiente de produção. A variável var.networks é carregada do networks.auto.tfvars`.

  • module "storage": Invoca o módulo de armazenamento, passando as definições de servidores, perfis de SO e pools de armazenamento. O módulo de armazenamento é responsável por criar os volumes de disco para as VMs.
  • module "compute": Invoca o módulo de computação, que é responsável por criar as máquinas virtuais. Este módulo recebe como entrada as definições de servidores, usuário padrão, perfis de SO, chave SSH pública, configurações de rede DMZ, e, crucialmente, as saídas dos módulos storage (volumes criados) e network (recursos de rede criados). Isso demonstra a interconexão entre os módulos e como as saídas de um módulo podem ser usadas como entradas para outro.

Este main.tf centraliza a lógica de implantação do ambiente de produção, tornando-o fácil de entender e gerenciar. Ao alterar as variáveis de entrada ou a forma como os módulos são invocados, é possível adaptar a infraestrutura sem modificar o código interno dos módulos.

environments/production/networks.auto.tfvars

Este arquivo é um .tfvars especial que o Terraform carrega automaticamente devido à sua convenção de nomenclatura (*.auto.tfvars). Ele é usado para definir as redes virtuais específicas que serão criadas no ambiente de produção, atribuindo valores à variável networks declarada no módulo 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
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 REDES VIRTUAIS PARA O AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo define as configurações específicas das redes virtuais que
# serão criadas no ambiente de produção. Ele é automaticamente carregado
# pelo Terraform devido à convenção de nomenclatura `*.auto.tfvars`.

networks = {
  # ---------------------------------------------------------------------------
  # REDE EXTERNA - Conexão com Internet (Modo NAT)
  # ---------------------------------------------------------------------------
  # Rede que fornece conectividade com a internet para as VMs através de NAT.
  # O libvirt gerencia o DHCP e o roteamento para esta rede.
  external = {
    net_name  = "external"              # Nome da rede no libvirt
    net_mode  = "nat"                   # Modo NAT: VMs acessam a internet via NAT do host
    ipv4_cidr = "192.168.100.0/24"      # Bloco IPv4 da rede (gerenciado pelo libvirt)
    ipv6_cidr = "fd12:ee::/64"          # Bloco IPv6 da rede (gerenciado pelo libvirt)
  },

  # ---------------------------------------------------------------------------
  # REDE DMZ - Zona Desmilitarizada (Modo Isolado)
  # ---------------------------------------------------------------------------
  # 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"                  # Modo isolado: sem DHCP ou roteamento automático do libvirt
    ipv4_cidr = "10.32.16.0/24"         # Bloco IPv4 da DMZ (para documentação e Cloud-Init)
    ipv6_cidr = "fd00:32:16::/64"       # Bloco IPv6 da DMZ (para documentação e Cloud-Init)
  },

  # ---------------------------------------------------------------------------
  # 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"                  # Modo isolado
    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 (Modo Isolado)
  # ---------------------------------------------------------------------------
  # Rede para dispositivos que obtêm configuração automaticamente via DHCP.
  # O servidor DHCP para esta rede deve ser configurado manualmente em uma VM
  # (ex: no servidor gateway).
  dhcp = {
    net_name  = "dhcp"                  # Nome da rede no libvirt
    net_mode  = "none"                  # Modo isolado
    ipv4_cidr = "10.128.112.0/20"       # Bloco IPv4 maior para suportar 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 e para
#   serem usados pelo Cloud-Init em VMs com IPs estáticos.
# - Para redes com `net_mode = "none"`, o endereçamento é configurado manualmente
#   dentro das VMs via Cloud-Init ou outro método.
# - Para redes com `net_mode = "nat"`, o libvirt gerencia o endereçamento e o DHCP
#   automaticamente.
# - Certifique-se de que não há sobreposição de blocos IP entre as diferentes redes
#   para evitar conflitos de roteamento.
#
# MODOS DE REDE DISPONÍVEIS:
# - "nat": Rede com NAT para acesso à internet. O libvirt atua como um roteador e servidor DHCP.
# - "none": Rede isolada sem serviços automáticos (DHCP, roteamento) do libvirt. A configuração de IP e roteamento deve ser feita manualmente nas VMs ou via Cloud-Init.
# - "bridge": Rede em bridge com uma interface física do host. Permite que as VMs se comportem como hosts na rede física. Requer configuração adicional no host.
# - "route": Rede roteada sem NAT. O tráfego é roteado diretamente para a rede física, mas sem NAT. Requer configuração de roteamento no host.
#
# PLANEJAMENTO DE ENDEREÇOS:
# - Use blocos RFC 1918 para redes privadas (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) para evitar conflitos com IPs públicos.
# - Para IPv6, é recomendado usar endereços ULA (Unique Local Address) com prefixo `fd00::/8` para redes privadas.
# - Mantenha uma consistência na numeração entre IPv4 e IPv6 para facilitar a administração.
# - Reserve espaço suficiente em cada bloco de rede para o crescimento futuro da infraestrutura.
#
# SEGURANÇA E ISOLAMENTO:
# - Redes com `net_mode = "none"` são completamente isoladas por padrão, o que é ideal para DMZs e redes internas.
# - O roteamento entre redes isoladas deve ser configurado explicitamente em uma VM (ex: servidor gateway).
# - Implemente regras de firewall apropriadas no servidor gateway e nas VMs para controlar o fluxo de tráfego.
# - Considere o uso de VLANs adicionais para maior segmentação e isolamento de tráfego, se necessário.
#
# DHCP E DNS:
# - Para redes isoladas (`net_mode = "none"`), o servidor DHCP e DNS deve ser configurado em uma VM dedicada (ex: servidor gateway ou ns1/ns2).
# - Configure o encaminhamento DNS apropriado para garantir a resolução de nomes entre as redes e para a internet.
# - Mantenha os registros DNS atualizados para VMs com IPs estáticos.

Explicação:

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

  • Nome da Rede (ex: external, dmz, cgr, dhcp): A chave do mapa networks é o nome lógico da rede virtual.
  • net_name: O nome da rede no Libvirt, que será usado para referenciá-la em outros recursos.
  • net_mode: O modo de operação da rede. Pode ser:
    • nat: Rede com NAT para acesso à internet. O Libvirt gerencia o DHCP e o roteamento automaticamente.
    • none: Rede isolada, sem DHCP automático ou roteamento gerenciado pelo Libvirt. Os IPs e rotas devem ser configurados manualmente dentro das VMs via Cloud-Init ou outro método.
    • bridge: Rede em bridge com uma interface física do host, permitindo que as VMs se comportem como hosts na rede física. Requer configuração adicional no host.
    • route: Rede roteada sem NAT. O tráfego é roteado diretamente para a rede física, mas sem NAT. Requer configuração de roteamento no host.
  • 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 e para serem usados pelo Cloud-Init na configuração de IPs estáticos. Para redes nat, o Libvirt gerencia o endereçamento automaticamente.

Este arquivo é crucial para definir a topologia de rede do ambiente de produção, permitindo a criação de redes com diferentes propósitos e modos de operação para isolamento e conectividade. As notas importantes fornecem diretrizes para o planejamento de endereçamento IP, segurança, isolamento e configuração de DHCP/DNS, garantindo uma infraestrutura de rede robusta e bem organizada.

environments/production/outputs.tf

Este arquivo define os valores de saída (outputs) que serão expostos pelo módulo raiz do ambiente de produção. Os outputs são uma forma de extrair informações sobre os recursos criados pelo Terraform, tornando-as acessíveis para outros módulos, scripts ou para o usuário final. Eles são particularmente úteis para obter endereços IP, IDs de recursos ou outras propriedades importantes após a aplicação do 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
# =============================================================================
# SAÍDAS DE DADOS DO AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo define os valores de saída que serão expostos pelo módulo raiz
# do ambiente de produção. Estas saídas são úteis para acessar informações
# importantes da infraestrutura após a aplicação do Terraform, como IPs de VMs.

# -----------------------------------------------------------------------------
# ENDEREÇO IP DO GATEWAY
# -----------------------------------------------------------------------------
# Exporta o endereço IP da primeira interface de rede do servidor 'gateway'.
# Este output é útil para conectar-se ao gateway ou para configurar outros
# sistemas que dependam do seu endereço IP.
output "gateway_ip" {
  description = "O endereço IP da primeira interface de rede do servidor gateway."
  value       = module.compute.domains["gateway"].network_interface[0].addresses[0]
}

# -----------------------------------------------------------------------------
# NOTAS SOBRE OUTPUTS
# -----------------------------------------------------------------------------
# - Adicione outputs para quaisquer informações que você precise acessar
#   facilmente após a aplicação do Terraform (ex: IPs de outros servidores,
#   IDs de volumes, nomes de redes).
# - Use `terraform output <nome_do_output>` para visualizar os valores.
# - Use `terraform output -json` para obter todos os outputs em formato JSON.

Explicação:

Neste exemplo, o arquivo outputs.tf define um único output chamado gateway_ip. Este output extrai o endereço IP da primeira interface de rede do servidor gateway. A sintaxe module.compute.domains["gateway"].network_interface[0].addresses[0] demonstra como acessar as propriedades de um recurso criado por um módulo (neste caso, o módulo compute) e extrair informações específicas (o primeiro endereço IP da primeira interface de rede do domínio gateway).

Os outputs são essenciais para a automação e integração contínua, pois permitem que ferramentas externas ou outros pipelines de CI/CD consumam informações sobre a infraestrutura provisionada. É uma boa prática definir outputs para todas as informações que serão necessárias para interações futuras com a infraestrutura.

environments/production/providers.tf

Este arquivo define os requisitos de versão do Terraform e configura os provedores específicos para o ambiente de produção. Embora os módulos também possam ter seus próprios arquivos versions.tf para definir as versões dos provedores que eles utilizam, este arquivo no diretório do ambiente garante que o módulo raiz (o ambiente de produção, neste caso) utilize as versões corretas e estabeleça a conexão com o 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
# =============================================================================
# CONFIGURAÇÃO DE PROVEDORES PARA O AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo define os requisitos de versão do Terraform e configura os
# provedores específicos para o ambiente de produção. Garante que as versões
# corretas dos provedores sejam utilizadas e que a conexão com o libvirt
# seja estabelecida.

terraform {
  # Versão mínima do Terraform CLI necessária para executar este projeto.
  # Garante compatibilidade com as funcionalidades e sintaxe utilizadas.
  required_version = ">= 1.5"
  
  # Definição dos provedores necessários e suas versões específicas.
  # O Terraform fará o download e a instalação automática desses provedores.
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"  # Provedor oficial para gerenciar recursos libvirt
      version = "0.8.3"              # Versão exata do provedor libvirt a ser utilizada
    }
  }
}

# Configuração do provedor libvirt.
# Define como o Terraform se conectará ao daemon libvirt para gerenciar VMs,
# redes e volumes.
provider "libvirt" {
  # URI de conexão com o daemon libvirt.
  # "qemu:///system" indica uma conexão local com o daemon do sistema (requer permissões).
  # Para conexões remotas, use "qemu+ssh://user@host/system".
  uri = "qemu:///system"  # Conexão local padrão com o libvirt do sistema
}

Explicação:

  • terraform { ... }: Este bloco especifica a versão mínima do Terraform CLI necessária para executar o projeto e declara os provedores que serão utilizados, juntamente com suas versões específicas. Neste caso, o provedor libvirt na versão 0.8.3 é definido.
  • provider "libvirt" { ... }: Este bloco configura o provedor libvirt, definindo como o Terraform se conectará ao daemon libvirtd. O uri = "qemu:///system" indica uma conexão local com o daemon do sistema. Para ambientes de produção ou cenários de equipe, é comum configurar uma conexão remota via SSH (qemu+ssh://user@host/system) para gerenciar um host KVM/Libvirt centralizado.

É importante manter as versões dos provedores consistentes em todo o projeto para evitar problemas de compatibilidade. O uso de versões fixas ("0.8.3") garante a reprodutibilidade, enquanto o uso de restrições de versão mais flexíveis ("~> 0.8.0") permite atualizações de patch sem quebrar a compatibilidade.

environments/production/servers.auto.tfvars

Este arquivo, como o networks.auto.tfvars, é carregado automaticamente pelo Terraform e é usado para definir as máquinas virtuais específicas que serão criadas no ambiente de produção. Ele atribui valores à variável servers que é consumida pelo módulo compute.

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 PARA O AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo define as configurações específicas de cada máquina virtual
# (servidor) que será criada no ambiente de produção. Ele é automaticamente
# carregado pelo Terraform devido à convenção de nomenclatura `*.auto.tfvars`.
# Cada servidor é um objeto com suas especificações de hardware, sistema
# operacional e interfaces de rede.

servers = {
  # ---------------------------------------------------------------------------
  # SERVIDOR GATEWAY - Roteador e Firewall Principal
  # ---------------------------------------------------------------------------
  # Este servidor atua como o ponto de entrada e saída da infraestrutura,
  # fornecendo roteamento entre as redes internas e acesso à internet via NAT.
  # Possui múltiplas interfaces de rede para conectar-se a todas as zonas.
  gateway = {
    vcpus  = 2,          # Número de CPUs virtuais (adequado para roteamento e firewall)
    memory = "2048",     # Quantidade de memória RAM em MB (2GB)
    os     = "debian12", # Perfil do sistema operacional (definido em `terraform.tfvars`)
    
    # Configuração de múltiplas interfaces de rede para o gateway.
    # Cada interface conecta o gateway a uma rede virtual diferente.
    networks = [
      # Interface 'external': Conexão com a internet (DHCP)
      { 
        name = "external",             # Nome da rede libvirt à qual esta interface se conecta
        ipv4 = "dhcp", ipv6 = "dhcp",  # Obtém endereço IPv4 e IPv6 via DHCP
        if_name = "ens3"               # Nome da interface de rede dentro da VM (ex: eth0, ens3)
      },
      
      # Interface 'dmz': Conexão com a Zona Desmilitarizada (IP estático)
      { 
        name = "dmz", 
        ipv4 = "10.32.16.1", ipv4_prefix = 24,     # Endereço IPv4 estático e prefixo
        ipv6 = "fd00:32:16::1", ipv6_prefix = 64,  # Endereço IPv6 estático e prefixo
        if_name = "ens4" 
      },
      
      # Interface 'cgr': Conexão com a Rede Corporativa/Gestão (IP estático)
      { 
        name = "cgr", 
        ipv4 = "10.48.32.1", ipv4_prefix = 24,     # Endereço IPv4 estático e prefixo
        ipv6 = "fd00:48:32::1", ipv6_prefix = 64,  # Endereço IPv6 estático e prefixo
        if_name = "ens5" 
      },
      
      # Interface 'dhcp': Conexão com a Rede de Clientes Dinâmicos (IP estático)
      { 
        name = "dhcp", 
        ipv4 = "10.128.112.1", ipv4_prefix = 24,   # Endereço IPv4 estático e prefixo
        ipv6 = "fd00:128:112::1", ipv6_prefix = 64, # Endereço IPv6 estático e prefixo
        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 um servidor DNS
    memory   = "2048",     # 2GB RAM para cache DNS e operações
    os       = "oracle9",  # Perfil do sistema operacional (Oracle Linux)
    
    # Interface única na rede DMZ com IP estático.
    networks = [
      { 
        name = "dmz", 
        ipv4 = "10.32.16.3", ipv4_prefix = 24,     # IP fixo do DNS primário na DMZ
        ipv6 = "fd00:32:16::3", ipv6_prefix = 64,  # IPv6 do DNS primário na DMZ
        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 de nomes de backup.
  ns2 = {
    vcpus    = 2,          # Especificações de hardware idênticas ao NS1
    memory   = "2048",     # 2GB RAM
    os       = "oracle9",  # Mesmo SO do NS1 para consistência
    
    # Interface única na rede DMZ com IP estático.
    networks = [
      { 
        name = "dmz", 
        ipv4 = "10.32.16.4", ipv4_prefix = 24,     # IP fixo do DNS secundário na DMZ
        ipv6 = "fd00:32:16::4", ipv6_prefix = 64,  # IPv6 do DNS secundário na DMZ
        if_name = "ens3" 
      }
    ]
  }
  
  # ---------------------------------------------------------------------------
  # EXEMPLOS DE SERVIDORES ADICIONAIS
  # ---------------------------------------------------------------------------
  # Outros servidores podem ser definidos aqui seguindo o mesmo padrão.
  # Descomente e ajuste os blocos abaixo para adicionar mais VMs à sua infraestrutura.
  #
  # 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" }
  #   ]
  # }
}

# =============================================================================
# NOTAS SOBRE CONFIGURAÇÃO DE SERVIDORES
# =============================================================================
#
# ESPECIFICAÇÕES DE HARDWARE:
# - `vcpus`: Ajuste o número de CPUs virtuais conforme a carga de trabalho esperada para o serviço da VM.
# - `memory`: A quantidade de memória RAM é especificada como uma string em MB (ex: "2048" para 2GB).
# - Lembre-se de reservar recursos suficientes para o host físico que executará as VMs.
#
# CONFIGURAÇÃO DE REDE:
# - Cada servidor pode ter múltiplas interfaces de rede, conectando-o a diferentes redes virtuais.
# - Endereços IP estáticos são configurados via Cloud-Init dentro da VM, não diretamente no libvirt.
# - O DHCP pode ser usado em redes que suportam um servidor DHCP (ex: redes no modo `nat`).
# - Os prefixos de rede (ex: `/24`, `/64`) devem ser consistentes com a topologia de rede definida.
#
# SISTEMAS OPERACIONAIS:
# - Utilize os perfis de sistema operacional definidos em `terraform.tfvars` (variável `os_profiles`).
# - Certifique-se de que os arquivos de template de imagem (ex: `debian-12-amd64.qcow2`) existem no pool de armazenamento base (`base_volume_pool`).
# - Considere padronizar os sistemas operacionais por função (ex: Debian para servidores web, Oracle Linux para bancos de dados) para facilitar a manutenção.
#
# NOMENCLATURA:
# - Use nomes descritivos para os servidores que indiquem claramente sua função na infraestrutura (ex: `gateway`, `ns1`, `web1`).
# - Mantenha uma convenção de nomes consistente em todo o projeto.
# - Considere incluir o ambiente (ex: `prod-web1`, `dev-db1`) no nome do servidor se você tiver múltiplos ambientes.

Explicação:

Este arquivo é fundamental para definir a composição do ambiente de produção em termos de máquinas virtuais. Ele atribui valores à variável servers que é consumida pelo módulo compute. Cada bloco dentro do mapa servers representa uma VM, com as seguintes propriedades:

  • Nome da VM (ex: gateway, ns1, ns2): A chave do mapa servers é o nome lógico da máquina virtual, que será usado para identificá-la em todo o projeto.
  • vcpus: O número de CPUs virtuais alocadas para a VM. Este valor deve ser ajustado com base na carga de trabalho esperada para o serviço que a VM irá executar.
  • memory: A quantidade de memória RAM em MB, especificada como uma string (ex: "2048" para 2GB). É importante considerar a memória disponível no host físico para evitar sobrecarga.
  • os: O perfil do sistema operacional a ser usado, que deve corresponder a um dos perfis definidos na variável os_profiles em terraform.tfvars. Isso permite a padronização e reutilização de configurações de SO.
  • 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 (ex: external, dmz).
    • ipv4 e ipv6: O endereço IP (ou dhcp para obter um IP via DHCP). Para IPs estáticos, o Cloud-Init configurará o endereço dentro da VM.
    • 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 oferece uma flexibilidade enorme para definir a topologia da sua infraestrutura, permitindo a criação de diferentes tipos de servidores com configurações de hardware e rede personalizadas para atender às necessidades específicas do ambiente de produção. As notas importantes fornecem diretrizes para o planejamento de hardware, configuração de rede, seleção de sistemas operacionais e nomenclatura, garantindo uma infraestrutura de servidor bem planejada e organizada.

environments/production/terraform.tfvars

Este arquivo contém os valores das variáveis principais que definem o comportamento global da infraestrutura no ambiente de produção. Ele é automaticamente carregado pelo Terraform devido à convenção de nomenclatura terraform.tfvars. Este arquivo é o local ideal para armazenar configurações que são globais para o ambiente, mas que podem variar entre diferentes ambientes (ex: chaves SSH, nomes de pools de armazenamento, perfis de SO).

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
# =============================================================================
# CONFIGURAÇÕES PRINCIPAIS PARA O AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo contém os valores das variáveis principais que definem o
# comportamento global da infraestrutura no ambiente de produção. Estas
# configurações são aplicadas a todos os recursos criados neste ambiente.
# Ele é automaticamente carregado pelo Terraform devido à convenção de
# nomenclatura `terraform.tfvars`.

# -----------------------------------------------------------------------------
# CHAVE PÚBLICA SSH PARA ACESSO ÀS VMs
# -----------------------------------------------------------------------------
# A chave pública SSH será injetada em todas as máquinas virtuais para
# permitir acesso administrativo sem a necessidade de senha. É altamente
# recomendado usar chaves SSH para acesso seguro.
#
# Métodos alternativos para definir a chave (descomente e use um deles):
# 1. Via linha de comando durante o `terraform plan`:
#    `terraform plan -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"`
# 2. Via linha de comando durante o `terraform apply`:
#    `terraform apply -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"`
# 3. Definindo diretamente aqui (método atual, menos seguro para chaves sensíveis em repositórios públicos)
# ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzaHM66H9EFpX/6aYcvFH85eHjyAsMVRk8DbfQhxxmI gean@inspiron"

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE POOLS DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Define os pools de armazenamento (storage pools) no libvirt onde os volumes
# das VMs e os templates base das imagens de SO serão armazenados.
volume_pool      = "default"    # Nome do pool de armazenamento para os volumes das VMs em execução
base_volume_pool = "templates"  # Nome do pool de armazenamento para os templates/imagens base dos sistemas operacionais

# -----------------------------------------------------------------------------
# PERFIS DE SISTEMAS OPERACIONAIS SUPORTADOS
# -----------------------------------------------------------------------------
# Define as configurações específicas para cada tipo de sistema operacional
# que pode ser utilizado nas VMs. Isso inclui o nome do arquivo de template
# e os grupos padrão de usuários para cada SO.
os_profiles = {
  # Perfil para Debian 12:
  "debian12" = {
    template_name  = "debian-12-amd64.qcow2",  # Nome do arquivo de imagem base no `base_volume_pool`
    default_groups = ["users", "sudo"]         # Grupos padrão para usuários neste SO (ex: 'sudo' para privilégios administrativos)
  },
  
  # Perfil para Oracle Linux 9:
  "oracle9" = {
    template_name  = "ol9-amd64.qcow2",       # Nome do arquivo de imagem base no `base_volume_pool`
    default_groups = ["users", "wheel"]       # Grupos padrão para usuários neste SO (ex: 'wheel' para privilégios administrativos)
  }
  
  # Exemplo de como adicionar novos perfis de SO:
  # "ubuntu22" = {
  #   template_name  = "ubuntu-22.04-amd64.qcow2",
  #   default_groups = ["users", "sudo"]
  # }
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÃO DE USUÁRIO PADRÃO PARA VMs
# -----------------------------------------------------------------------------
# Define o usuário administrativo padrão que será criado em todas as VMs
# quando um usuário específico não for definido individualmente na configuração
# de cada servidor em `servers.auto.tfvars`.
default_vm_user = {
  name  = "suporte"      # Nome de login do usuário padrão
  gecos = "Suporte User" # Nome completo ou descrição do usuário padrão
}

# -----------------------------------------------------------------------------
# CONFIGURAÇÕES DE REDE DMZ
# -----------------------------------------------------------------------------
# Define os parâmetros de rede para a Zona Desmilitarizada (DMZ), incluindo
# endereços de gateway e servidores DNS. Estas configurações serão utilizadas
# pelo Cloud-Init para configurar as VMs que pertencem à rede DMZ.
network_dmz = {
  # Endereços de gateway padrão para roteamento na DMZ
  gateway_v4 = "10.32.16.1",      # Gateway IPv4 da rede DMZ
  gateway_v6 = "fd00:32:16::1",   # Gateway IPv6 da rede DMZ
  
  # Endereços dos servidores DNS primário e secundário para a DMZ
  ns1_v4     = "10.32.16.3",      # DNS primário IPv4 (servidor ns1)
  ns1_v6     = "fd00:32:16::3",   # DNS primário IPv6 (servidor ns1)
  ns2_v4     = "10.32.16.4",      # DNS secundário IPv4 (servidor ns2)
  ns2_v6     = "fd00:32:16::4"    # DNS secundário IPv6 (servidor ns2)
}

# =============================================================================
# NOTAS IMPORTANTES PARA O AMBIENTE DE PRODUÇÃO
# =============================================================================
# 1. **Templates de Imagem:** Certifique-se de que os arquivos de template
#    especificados em `os_profiles` (ex: `debian-12-amd64.qcow2`) existem no
#    pool de armazenamento definido por `base_volume_pool` antes de executar
#    o Terraform.
#
# 2. **Pools de Armazenamento:** Os pools de armazenamento (`default` e `templates`)
#    devem estar configurados e acessíveis no seu seu ambiente libvirt. Caso não
#    existam, crie-os utilizando comandos como:
#    `virsh pool-define-as <nome_do_pool> dir --target <caminho_do_diretorio>`
#    `virsh pool-start <nome_do_pool>`
#    `virsh pool-autostart <nome_do_pool>`
#
# 3. **Chave SSH:** A chave privada correspondente à `ssh_public_key` deve estar
#    disponível na máquina que você usará para conectar às VMs após a criação.
#    Mantenha sua chave privada segura e protegida.
#
# 4. **Configurações de Rede:** As configurações de rede (CIDRs, gateways, DNS)
#    definidas aqui devem ser consistentes com a topologia de rede real da sua
#    infraestrutura para garantir a conectividade correta das VMs.

Explicação:

Este arquivo é o ponto central para a configuração de variáveis globais do ambiente de produção. Ele atribui valores às variáveis declaradas em environments/production/variables.tf e que são consumidas pelos módulos. As principais configurações definidas aqui incluem:

  • ssh_public_key: A chave pública SSH que será injetada em todas as VMs para acesso seguro. É crucial que esta chave seja a sua chave pública para que você possa se conectar às máquinas virtuais após o provisionamento. O tutorial fornece métodos alternativos para definir esta chave, incluindo a leitura de um arquivo local, o que é uma prática mais segura do que embutir a chave diretamente no código.
  • volume_pool e base_volume_pool: Os nomes dos pools de armazenamento do Libvirt onde os volumes das VMs e os templates base serão armazenados, respectivamente. É fundamental que esses pools existam e estejam configurados corretamente em seu ambiente Libvirt antes de executar o Terraform.
  • 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. Isso permite a padronização e reutilização de configurações de SO em todo o ambiente.
  • default_vm_user: O nome de usuário e o nome completo do usuário padrão que será criado em todas as VMs, caso um usuário específico não seja definido individualmente para cada VM.
  • network_dmz: Contém as configurações de rede para a Zona Desmilitarizada (DMZ), incluindo endereços de gateway e servidores DNS. Essas informações são usadas pelo Cloud-Init para configurar as interfaces de rede estáticas das VMs na DMZ.

As notas importantes no final do arquivo fornecem diretrizes cruciais para garantir o sucesso da implantação, como a verificação da existência dos templates de imagem e dos pools de armazenamento, a segurança da chave SSH e a consistência das configurações de rede. Este arquivo é a principal interface para personalizar o ambiente de produção sem modificar o código dos módulos subjacentes.

environments/production/variables.tf

Este arquivo declara todas as variáveis de entrada esperadas para o módulo raiz do ambiente de produção. Ele serve como um contrato para as configurações que o ambiente espera receber. As variáveis são tipadas e podem ter descrições detalhadas para clareza, facilitando a compreensão e o uso do ambiente. Os valores para estas variáveis são geralmente fornecidos via terraform.tfvars, *.auto.tfvars ou linha de comando.

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
# =============================================================================
# DECLARAÇÃO DE VARIÁVEIS PARA O AMBIENTE DE PRODUÇÃO
# =============================================================================
# Este arquivo declara todas as variáveis de entrada esperadas para o módulo
# raiz do ambiente de produção. As variáveis são tipadas e podem ter descrições
# para clareza. Os valores para estas variáveis são geralmente fornecidos via
# `terraform.tfvars`, `*.auto.tfvars` ou linha de comando.

# -----------------------------------------------------------------------------
# VARIÁVEIS DE CONFIGURAÇÃO DE SERVIDORES
# -----------------------------------------------------------------------------
# Define a estrutura e as especificações de cada máquina virtual a ser criada.
# O tipo `map(any)` é usado aqui para flexibilidade, mas um tipo `object` mais
# detalhado pode ser definido para validação rigorosa.
variable "servers" {
  description = "Um mapa de objetos que definem as máquinas virtuais a serem criadas no ambiente de produção."
  type        = map(any) # Contém vcpus, memory, os, networks, etc.
}

# -----------------------------------------------------------------------------
# VARIÁVEIS DE CONFIGURAÇÃO DE REDES
# -----------------------------------------------------------------------------
# Define as redes virtuais que serão criadas e configuradas no ambiente.
variable "networks" {
  description = "Um mapa de objetos que definem as redes virtuais a serem criadas no ambiente de produção."
  type        = map(any) # Contém net_name, net_mode, ipv4_cidr, ipv6_cidr, etc.
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE CHAVE PÚBLICA SSH
# -----------------------------------------------------------------------------
# A chave pública SSH a ser injetada nas VMs para acesso seguro.
variable "ssh_public_key" {
  description = "Chave pública SSH a ser injetada nas máquinas virtuais para acesso sem senha."
  type        = string
  # sensitive = true # Descomente para ocultar o valor nos logs do Terraform
}

# -----------------------------------------------------------------------------
# VARIÁVEIS DE PERFIS DE SISTEMAS OPERACIONAIS
# -----------------------------------------------------------------------------
# Define os perfis de SO disponíveis, incluindo o nome do template e grupos padrão.
variable "os_profiles" {
  description = "Define perfis completos (template, grupos, etc.) para cada tipo de SO suportado."
  type        = map(any) # Contém template_name, default_groups, etc.
}

# -----------------------------------------------------------------------------
# VARIÁVEIS DE USUÁRIO PADRÃO DA VM
# -----------------------------------------------------------------------------
# Configurações para o usuário padrão que será criado em todas as VMs.
variable "default_vm_user" {
  description = "Configurações do usuário padrão para as VMs, usado quando não especificado individualmente."
  type        = map(any) # Contém name, gecos, etc.
}

# -----------------------------------------------------------------------------
# VARIÁVEIS DE CONFIGURAÇÃO DE REDE DMZ
# -----------------------------------------------------------------------------
# Parâmetros de rede específicos para a Zona Desmilitarizada (DMZ).
variable "network_dmz" {
  description = "Configurações de gateway e DNS para a rede DMZ, usadas pelo Cloud-Init."
  type        = map(any) # Contém gateway_v4/v6, ns1_v4/v6, ns2_v4/v6, etc.
}

# -----------------------------------------------------------------------------
# VARIÁVEIS DE POOLS DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Nomes dos pools de armazenamento libvirt para volumes de VMs e templates.
variable "volume_pool" {
  description = "Pool de armazenamento para volumes de disco criados para as VMs."
  type        = string
}

variable "base_volume_pool" {
  description = "Pool de armazenamento onde os templates base das imagens de SO estão localizados."
  type        = string
}

Explicação:

Este arquivo serve como a interface de entrada para o ambiente de produção. Ele declara todas as variáveis que o ambiente espera receber para configurar a infraestrutura. As variáveis são definidas com um description claro e um type para garantir a validação dos dados. Embora map(any) seja usado para flexibilidade, em projetos maiores, é recomendável usar tipos de objeto mais específicos para uma validação de esquema mais rigorosa.

As variáveis declaradas aqui incluem:

  • servers: Um mapa que define as máquinas virtuais a serem criadas, incluindo suas especificações de hardware e rede. Os valores para esta variável são geralmente fornecidos em servers.auto.tfvars.
  • networks: Um mapa que define as redes virtuais a serem criadas, incluindo seus nomes, modos de operação e blocos CIDR. Os valores para esta variável são geralmente fornecidos em networks.auto.tfvars.
  • ssh_public_key: A chave pública SSH a ser injetada nas VMs para acesso seguro. A opção sensitive = true (comentada no exemplo) pode ser usada para ocultar o valor nos logs do Terraform, o que é uma boa prática de segurança para dados sensíveis.
  • os_profiles: Define os perfis de sistema operacional disponíveis, incluindo o nome do template de imagem e os grupos padrão de usuários para cada SO.
  • default_vm_user: As configurações para o usuário padrão que será criado em todas as VMs.
  • network_dmz: Parâmetros de rede específicos para a Zona Desmilitarizada (DMZ), como gateways e servidores DNS.
  • volume_pool e base_volume_pool: Os nomes dos pools de armazenamento Libvirt para volumes de VMs e templates, respectivamente.

Ao centralizar a declaração de variáveis neste arquivo, o ambiente de produção se torna mais legível e fácil de usar, pois todas as entradas esperadas são claramente documentadas. Isso também facilita a criação de novos ambientes, pois eles podem reutilizar a mesma estrutura de variáveis, apenas fornecendo valores diferentes.

Módulos Reutilizáveis (modules/)

Os módulos são a espinha dorsal de uma arquitetura Terraform modular. Eles encapsulam um conjunto de recursos relacionados e os expõem como um bloco reutilizável. Isso permite que você defina a lógica de provisionamento uma vez e a reutilize em diferentes ambientes ou partes da sua infraestrutura, promovendo a consistência, a manutenibilidade e a escalabilidade.

modules/compute/

O módulo compute é responsável por provisionar as máquinas virtuais (domínios Libvirt). Ele depende dos módulos network e storage para obter os recursos de rede e os volumes de disco necessários para as VMs. Dentro deste módulo, você encontrará os seguintes arquivos:

modules/compute/cloudinit.tf

Este arquivo é responsável por gerar os discos de configuração Cloud-Init para cada máquina virtual. Estes discos são anexados às VMs e contêm as instruções para configuração inicial do sistema operacional, como usuários, chaves SSH e configurações de rede. Ele utiliza os templates user_data.yml e network_config.yml localizados no diretório cloud-init/ raiz 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
# =============================================================================
# CRIAÇÃO DE DISCOS CLOUD-INIT NO MÓDULO COMPUTE
# =============================================================================
# Este arquivo é responsável por gerar os discos de configuração Cloud-Init
# para cada máquina virtual. Estes discos são anexados às VMs e contêm as
# instruções para configuração inicial do sistema operacional, como usuários,
# chaves SSH e configurações de rede.

# Criação de discos Cloud-Init individuais para cada VM
resource "libvirt_cloudinit_disk" "cloudinit" {
  # Itera sobre cada servidor definido na variável `servers` do módulo.
  for_each = var.servers

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÕES BÁSICAS DO DISCO CLOUD-INIT
  # -----------------------------------------------------------------------------
  name = "cloudinit-${each.key}.iso"  # Nome do arquivo ISO do Cloud-Init (ex: cloudinit-gateway.iso)
  pool = var.volume_pool              # Pool de armazenamento libvirt onde o disco ISO será criado

  # -----------------------------------------------------------------------------
  # GERAÇÃO DO USER DATA (DADOS DO USUÁRIO)
  # -----------------------------------------------------------------------------
  # O `user_data` contém scripts e configurações que são executados durante
  # o primeiro boot da VM. Inclui a criação de usuários, configuração de sudo,
  # injeção de chaves SSH e comandos de inicialização.
  user_data = templatefile(
    "${path.module}/../../cloud-init/user_data.yml", # Caminho para o template user_data.yml
    {
      hostname  = each.key                                                    # Nome da VM (usado para definir o hostname)
      user_name = coalesce(each.value.username, var.default_vm_user.name)     # Nome do usuário (usa o do servidor ou o 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 (usa os do servidor ou os padrão do SO)
      ssh_key   = var.ssh_public_key                                          # Chave pública SSH a ser injetada
    }
  )

  # -----------------------------------------------------------------------------
  # GERAÇÃO DO NETWORK CONFIG (CONFIGURAÇÃO DE REDE)
  # -----------------------------------------------------------------------------
  # O `network_config` define a configuração de rede da VM, incluindo interfaces,
  # endereços IP (DHCP ou estático), rotas e servidores DNS. É crucial para a
  # conectividade da VM após a inicialização.
  network_config = templatefile(
    "${path.module}/../../cloud-init/network_config.yml", # Caminho para o template network_config.yml
    {
      hostname           = each.key                                          # Nome da VM
      interfaces         = each.value.networks                              # Lista de interfaces de rede da VM
      dmz_config         = var.network_dmz                                   # Configurações da rede DMZ (gateways, DNS)
      has_dhcp_interface = length([for iface in each.value.networks : iface if iface.ipv4 == "dhcp"]) > 0  # Verifica se alguma interface usa DHCP
    }
  )
  
  # -----------------------------------------------------------------------------
  # FUNCIONAMENTO DO CLOUD-INIT
  # -----------------------------------------------------------------------------
  # O Cloud-Init é uma ferramenta de inicialização de máquinas virtuais que
  # automatiza a configuração de sistemas operacionais. Ele lê os dados
  # fornecidos nos discos `user_data` e `network_config` e aplica as
  # configurações correspondentes no primeiro boot da VM.
  #
  # Etapas comuns do Cloud-Init:
  # 1. Configuração de rede (IP, gateway, DNS)
  # 2. Criação de usuários e injeção de chaves SSH
  # 3. Definição do hostname
  # 4. Execução de comandos personalizados
  # 5. Instalação de pacotes de software
}

Explicação:

Este arquivo é o responsável por criar os discos Cloud-Init para cada VM. Ele utiliza o recurso libvirt_cloudinit_disk para gerar um arquivo ISO contendo as configurações de user_data e network_config. As principais características são:

  • for_each = var.servers: Permite que o Terraform crie um disco Cloud-Init para cada servidor definido na variável servers do módulo compute.
  • name e pool: Definem o nome do arquivo ISO do Cloud-Init (ex: cloudinit-gateway.iso) e o pool de armazenamento Libvirt onde ele será criado.
  • user_data: Este bloco utiliza a função templatefile para preencher o template cloud-init/user_data.yml (localizado no diretório raiz do projeto) com informações específicas de cada VM. As variáveis passadas para o template incluem hostname, user_name, gecos, groups e ssh_key. A função coalesce é usada para fornecer valores padrão caso não sejam especificados individualmente para uma VM.
  • network_config: Similar ao user_data, este bloco utiliza templatefile para preencher o template cloud-init/network_config.yml com as configurações de rede da VM. As variáveis passadas incluem hostname, interfaces, dmz_config e has_dhcp_interface, que é uma lógica para verificar se a VM possui alguma interface configurada para DHCP.

O Cloud-Init é uma ferramenta poderosa que automatiza a configuração do sistema operacional no primeiro boot da VM, garantindo que as máquinas virtuais sejam provisionadas com as configurações desejadas de forma consistente e sem intervenção manual. Este arquivo no módulo compute é o elo entre as definições do Terraform e a configuração interna das VMs.

modules/compute/main.tf

Este arquivo é o coração do módulo compute, responsável por criar as máquinas virtuais (domínios) no ambiente Libvirt. Ele une todas as configurações definidas nos arquivos anteriores, como os volumes de disco (do módulo storage), as interfaces de rede (do módulo network) e os discos Cloud-Init (criados neste próprio módulo).

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
# =============================================================================
# DEFINIÇÃO DE MÁQUINAS VIRTUAIS (DOMÍNIOS LIBVIRT) NO MÓDULO COMPUTE
# =============================================================================
# Este arquivo é o coração do módulo `compute`, responsável por criar as
# máquinas virtuais (domínios) no ambiente libvirt. Ele define as
# especificações de hardware, anexa os volumes de disco e as interfaces de
# rede, e configura os dispositivos de console para cada VM.

# Criação das máquinas virtuais
resource "libvirt_domain" "domain" {
  # Itera sobre cada servidor definido na variável `servers` do módulo.
  for_each = var.servers

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÕES BÁSICAS DA VM
  # -----------------------------------------------------------------------------
  name   = each.key           # Nome da VM no libvirt (ex: 'gateway', 'ns1')
  memory = each.value.memory  # Quantidade de memória RAM em MB (ex: "2048")
  vcpu   = each.value.vcpu    # Número de CPUs virtuais

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

  # -----------------------------------------------------------------------------
  # DEPENDÊNCIAS DE RECURSOS
  # -----------------------------------------------------------------------------
  # Garante que os recursos de rede e os volumes de disco sejam criados e
  # estejam disponíveis antes que a máquina virtual seja provisionada.
  depends_on = [
    var.network_resources, # Garante que as redes virtuais estejam prontas
    # libvirt_volume.os_image # Dependência implícita via `var.volumes`
  ]

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DO CLOUD-INIT
  # -----------------------------------------------------------------------------
  # Anexa o disco Cloud-Init gerado anteriormente à VM. Este disco contém as
  # configurações de inicialização do sistema operacional (usuários, rede, etc.).
  cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE DISCO PRINCIPAL
  # -----------------------------------------------------------------------------
  # Define o disco principal da VM, que é o volume do sistema operacional.
  # O `volume_id` é obtido a partir dos volumes criados pelo módulo `storage`.
  disk {
    volume_id = var.volumes[each.key].id  # ID do volume do sistema operacional para esta VM
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DINÂMICA DE INTERFACES DE REDE
  # -----------------------------------------------------------------------------
  # Cria uma interface de rede para cada rede especificada na configuração da VM.
  # Isso permite que as VMs tenham múltiplas interfaces conectadas a diferentes
  # redes virtuais.
  dynamic "network_interface" {
    for_each = each.value.networks  # Itera sobre a lista de redes definidas para a VM
    content {
      network_name   = network_interface.value.name                                    # Nome da rede virtual libvirt a ser conectada
      wait_for_lease = network_interface.value.ipv4 == "dhcp" ? true : false         # Aguarda a obtenção de um lease DHCP se a interface estiver configurada para DHCP
      
      # Nota: Para interfaces com IP estático, o endereçamento é configurado
      # via Cloud-Init (no `network_config.yml`), não diretamente aqui no libvirt.
    }
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE CONSOLE SERIAL
  # -----------------------------------------------------------------------------
  # Configura um console serial para a VM, que é útil para depuração e acesso
  # de baixo nível ao sistema operacional, mesmo que a rede não esteja funcionando.
  console {
    type        = "pty"     # Tipo de console: pseudo-terminal (acessível via `virsh console`)
    target_type = "serial"  # O dispositivo emulado é um console serial
    target_port = "0"       # Porta serial 0 (geralmente mapeada para /dev/ttyS0 dentro da VM)
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE INTERFACE GRÁFICA
  # -----------------------------------------------------------------------------
  # Configura uma interface gráfica para a VM, permitindo acesso visual através
  # de um cliente VNC ou SPICE. O protocolo SPICE é recomendado para melhor
  # performance e recursos (ex: copiar/colar, redimensionamento).
  graphics {
    type        = "spice"    # Protocolo SPICE para acesso gráfico
    listen_type = "address"  # O libvirt escutará em um endereço IP específico
    autoport    = true       # O libvirt selecionará uma porta disponível automaticamente
  }
  
  # -----------------------------------------------------------------------------
  # RECURSOS ADICIONAIS DISPONÍVEIS PARA DOMÍNIOS LIBVIRT
  # -----------------------------------------------------------------------------
  # O recurso `libvirt_domain` oferece muitas outras opções de configuração.
  # Alguns exemplos que podem ser adicionados conforme a necessidade:
  # - Discos adicionais: Múltiplos blocos `disk` para volumes de dados.
  # - Dispositivos USB passthrough: Para passar dispositivos USB físicos diretamente para a VM.
  # - Configurações de NUMA: Para otimizar o acesso à memória em sistemas com arquitetura NUMA.
  # - Dispositivos de áudio: Para habilitar saída de áudio na VM.
  # - Redirecionamento de dispositivos: Para dispositivos PCI, etc.
}

Explicação:

Este arquivo é o componente central do módulo compute, responsável por criar as máquinas virtuais (domínios) no Libvirt. Ele utiliza o recurso libvirt_domain para definir as VMs com base nas entradas fornecidas ao módulo. As principais configurações e conceitos abordados são:

  • for_each = var.servers: Permite que o Terraform crie múltiplas VMs, uma para cada servidor definido na variável servers do módulo.
  • Configurações Básicas (name, memory, vcpu): Definem o nome da VM, a quantidade de memória RAM e o número de vCPUs, respectivamente. Esses valores são extraídos das definições de cada servidor.
  • Configuração de CPU (cpu { mode = "host-passthrough" }): Configura a CPU da VM para usar o modo host-passthrough. Isso significa que a VM acessará diretamente as instruções e recursos da CPU física do host, resultando em melhor performance e compatibilidade com aplicações que exigem recursos específicos da CPU.
  • Dependências de Recursos (depends_on): Garante que os recursos de rede (var.network_resources do módulo network) e os volumes de disco (var.volumes do módulo storage) sejam criados e estejam disponíveis antes que a máquina virtual seja provisionada. Isso evita erros de provisionamento devido a recursos ausentes.
  • Configuração do Cloud-Init (cloudinit): Anexa o disco Cloud-Init (gerado pelo cloudinit.tf dentro deste mesmo módulo) à VM. Este disco contém as configurações de inicialização do sistema operacional, como usuários, chaves SSH e configurações de rede, que são aplicadas no primeiro boot da VM.
  • Configuração de Disco Principal (disk): Define o disco principal da VM, que é o volume do sistema operacional. O volume_id é obtido a partir dos volumes criados pelo módulo storage, demonstrando a integração entre os módulos.
  • Configuração Dinâmica de Interfaces de Rede (dynamic "network_interface"): Cria dinamicamente as interfaces de rede para a VM, com base nas configurações de rede definidas para cada servidor. A lógica wait_for_lease garante que a VM aguarde a obtenção de um lease DHCP se a interface estiver configurada para DHCP. Para IPs estáticos, o endereçamento é configurado via Cloud-Init.
  • Console Serial (console): Configura um console serial para a VM, útil para depuração e acesso de baixo nível ao sistema operacional, mesmo que a rede não esteja funcionando.
  • Interface Gráfica (graphics): Configura uma interface gráfica para a VM, permitindo acesso visual através de um cliente VNC ou SPICE. O protocolo SPICE é recomendado para melhor performance e recursos.

Este arquivo é a peça central que orquestra a criação das VMs, integrando as definições de hardware, armazenamento, rede e configuração inicial via Cloud-Init. Ele demonstra como o Terraform pode ser usado para provisionar máquinas virtuais de forma declarativa e automatizada, garantindo que cada VM seja criada com as especificações desejadas.

modules/compute/outputs.tf

Este arquivo define os valores de saída (outputs) que serão expostos pelo módulo compute. Os outputs são a forma como um módulo pode retornar informações sobre os recursos que ele criou para o módulo pai (neste caso, o ambiente de produção) ou para outros módulos. Isso permite que as informações sobre as VMs provisionadas sejam acessíveis e reutilizáveis em outras partes da infraestrutura.

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
# =============================================================================
# SAÍDAS DE DADOS DO MÓDULO COMPUTE
# =============================================================================
# Este arquivo define os valores de saída que serão expostos pelo módulo
# `compute`. Estas saídas são úteis para que o módulo raiz (ou outros módulos)
# possam acessar informações sobre as máquinas virtuais criadas, como seus IDs
# ou detalhes de interface de rede.

# -----------------------------------------------------------------------------
# DOMÍNIOS (MÁQUINAS VIRTUAIS) CRIADOS
# -----------------------------------------------------------------------------
# Exporta um mapa de todos os recursos `libvirt_domain` criados por este módulo.
# A chave do mapa é o nome da VM (ex: 'gateway', 'ns1') e o valor é o objeto
# completo do domínio libvirt, contendo todas as suas propriedades.
output "domains" {
  description = "Um mapa de todos os domínios (máquinas virtuais) libvirt criados por este módulo."
  value       = libvirt_domain.domain
}

# -----------------------------------------------------------------------------
# NOTAS SOBRE OUTPUTS
# -----------------------------------------------------------------------------
# - Adicione outputs para quaisquer informações que outros módulos ou o módulo
#   raiz precisem consumir sobre os recursos de computação.
# - Evite expor informações sensíveis diretamente nos outputs.

Explicação:

Neste exemplo, o arquivo outputs.tf do módulo compute define um único output chamado domains. Este output exporta um mapa de todos os recursos libvirt_domain criados por este módulo. A chave do mapa é o nome da VM (ex: gateway, ns1) e o valor é o objeto completo do domínio Libvirt, contendo todas as suas propriedades, como IDs, IPs, configurações de rede, etc.

Ao expor os domínios criados como um output, o módulo pai (o ambiente de produção) pode facilmente acessar informações sobre as VMs provisionadas. Por exemplo, o environments/production/outputs.tf utiliza este output para extrair o endereço IP do servidor gateway. É uma boa prática definir outputs para todas as informações que serão necessárias para interações futuras com os recursos criados pelo módulo.

modules/compute/variables.tf

Este arquivo declara todas as variáveis de entrada esperadas pelo módulo compute. Ele serve como um contrato para as configurações que o módulo espera receber para provisionar as máquinas virtuais. As variáveis são tipadas e podem ter descrições detalhadas para clareza, facilitando a compreensão e o uso do módulo.

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
# =============================================================================
# DECLARAÇÃO DE VARIÁVEIS PARA O MÓDULO COMPUTE
# =============================================================================
# Este arquivo declara todas as variáveis de entrada esperadas pelo módulo
# `compute`. Estas variáveis são essenciais para configurar as máquinas
# virtuais que serão provisionadas por este módulo.

# -----------------------------------------------------------------------------
# VARIÁVEL DE SERVIDORES
# -----------------------------------------------------------------------------
# Define a estrutura e as especificações de cada máquina virtual a ser criada.
# É um mapa onde cada chave é o nome da VM e o valor é um objeto com seus
# atributos detalhados.
variable "servers" {
  description = "Um mapa de objetos que definem as máquinas virtuais a serem criadas."
  type = map(object({
    username = optional(string)   # Nome de usuário personalizado para a VM (opcional)
    gecos    = optional(string)   # Nome completo do usuário (opcional)
    groups   = optional(list(string)) # Lista de grupos adicionais para o usuário (opcional)
    os       = string               # Perfil do sistema operacional (deve corresponder a um os_profiles)
    vcpus    = number               # Número de CPUs virtuais para a VM
    memory   = string               # Quantidade de memória RAM em MB (ex: "2048")
    networks = list(object({         # Lista de interfaces de rede para a VM
      name        = string           # Nome da rede virtual libvirt
      ipv4        = string           # Endereço IPv4 ou "dhcp"
      ipv4_prefix = optional(number) # Prefixo da rede IPv4 (ex: 24)
      ipv6        = string           # Endereço IPv6 ou "dhcp"
      ipv6_prefix = optional(number) # Prefixo da rede IPv6 (ex: 64)
      if_name     = string           # Nome da interface dentro da VM (ex: ens3)
    }))
  }))
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE USUÁRIO PADRÃO DA VM
# -----------------------------------------------------------------------------
# Configurações para o usuário padrão que será criado em todas as VMs,
# caso um usuário específico não seja definido para uma VM individualmente.
variable "default_vm_user" {
  description = "Configurações do usuário padrão para as VMs."
  type = object({
    name  = string  # Nome de login do usuário padrão
    gecos = string  # Nome completo ou descrição do usuário padrão
  })
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE PERFIS DE SISTEMAS OPERACIONAIS
# -----------------------------------------------------------------------------
# Define os perfis de sistema operacional disponíveis, incluindo os grupos
# padrão de usuários para cada tipo de SO. O nome do template é omitido aqui
# pois é tratado no módulo `storage`.
variable "os_profiles" {
  description = "Define perfis de sistema operacional, incluindo grupos padrão."
  type = map(object({
    default_groups = list(string) # Lista de grupos padrão para usuários neste SO
  }))
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE CHAVE PÚBLICA SSH
# -----------------------------------------------------------------------------
# A chave pública SSH que será injetada nas VMs para permitir acesso seguro.
variable "ssh_public_key" {
  description = "Chave pública SSH a ser injetada nas máquinas virtuais."
  type        = string
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE CONFIGURAÇÃO DE REDE DMZ
# -----------------------------------------------------------------------------
# Parâmetros de rede específicos para a Zona Desmilitarizada (DMZ),
# utilizados pelo Cloud-Init para configurar as VMs.
variable "network_dmz" {
  description = "Configurações de gateway e DNS para a rede DMZ."
  type = object({
    gateway_v4 = string  # Gateway IPv4 da rede DMZ
    gateway_v6 = string  # Gateway IPv6 da rede DMZ
    ns1_v4     = string  # DNS primário IPv4
    ns1_v6     = string  # DNS primário IPv6
    ns2_v4     = string  # DNS secundário IPv4
    ns2_v6     = string  # DNS secundário IPv6
  })
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE VOLUMES DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Mapa de volumes de disco criados pelo módulo `storage`. Este módulo `compute`
# precisa desses IDs de volume para anexá-los às VMs.
variable "volumes" {
  description = "Mapa de volumes criados pelo módulo de storage, contendo seus IDs."
  type = map(object({
    id = string # ID do volume libvirt
  }))
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE POOL DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Nome do pool de armazenamento onde os discos Cloud-Init serão criados.
variable "volume_pool" {
  description = "Pool de armazenamento para volumes criados (incluindo discos Cloud-Init)."
  type        = string
  default     = "default" # Valor padrão: 'default' pool do libvirt
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE RECURSOS DE REDE
# -----------------------------------------------------------------------------
# Mapa de recursos de rede criados pelo módulo `network`. Usado para estabelecer
# dependências explícitas e garantir que as redes existam antes das VMs.
variable "network_resources" {
  description = "Mapa de recursos de rede criados pelo módulo de network, usado para dependências."
  type        = any # Pode ser um mapa de objetos libvirt_network
}

Explicação:

Este arquivo define as variáveis de entrada para o módulo compute. Cada variável é cuidadosamente definida com um description e um type para garantir que o módulo receba os dados corretos e para documentar suas expectativas. As principais variáveis de entrada são:

  • servers: Um mapa complexo que define as máquinas virtuais a serem criadas, incluindo detalhes como nome de usuário, grupos, sistema operacional, vCPUs, memória e configurações de rede. Este é o principal ponto de entrada para definir as VMs que o módulo irá provisionar.
  • default_vm_user: Configurações para o usuário padrão que será criado em todas as VMs, caso um usuário específico não seja definido individualmente.
  • os_profiles: Define os perfis de sistema operacional disponíveis, incluindo os grupos padrão de usuários para cada tipo de SO. O nome do template é omitido aqui, pois é tratado no módulo storage.
  • ssh_public_key: A chave pública SSH que será injetada nas VMs para permitir acesso seguro.
  • network_dmz: Parâmetros de rede específicos para a Zona Desmilitarizada (DMZ), utilizados pelo Cloud-Init para configurar as VMs.
  • volumes: Um mapa de volumes de disco criados pelo módulo storage. Este módulo compute precisa dos IDs desses volumes para anexá-los às VMs. Isso demonstra a dependência entre os módulos.
  • volume_pool: O nome do pool de armazenamento onde os discos Cloud-Init serão criados. Possui um valor padrão de default.
  • network_resources: Um mapa de recursos de rede criados pelo módulo network. Esta variável é usada para estabelecer dependências explícitas, garantindo que as redes existam antes que as VMs sejam provisionadas.

Ao definir claramente as variáveis de entrada, o módulo compute se torna reutilizável e fácil de integrar em diferentes ambientes, pois suas dependências e configurações esperadas são transparentes. Isso é um pilar fundamental da modularização no Terraform.

modules/compute/versions.tf

Este arquivo define os requisitos de provedor específicos para o módulo compute. Embora o módulo raiz (o ambiente de produção) também defina as versões dos provedores, é uma boa prática que cada módulo declare suas próprias dependências de provedor. Isso garante que o módulo funcione corretamente, independentemente do ambiente em que é utilizado, e facilita a gestão de versões.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# =============================================================================
# VERSÕES DE PROVEDORES PARA O MÓDULO COMPUTE
# =============================================================================
# Este arquivo define os requisitos de provedor específicos para o módulo
# `compute`. Garante que a versão correta do provedor `libvirt` seja utilizada
# ao provisionar recursos de computação (máquinas virtuais).

terraform {
  # Definição dos provedores necessários e suas versões específicas para este módulo.
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"  # Provedor oficial para gerenciar recursos libvirt
      version = "0.8.3"              # Versão exata do provedor libvirt a ser utilizada
    }
  }
}

Explicação:

Neste arquivo, o bloco terraform especifica o provedor libvirt e sua versão exata (0.8.3) que o módulo compute requer. Isso garante que, ao usar este módulo, o Terraform fará o download e utilizará a versão correta do provedor libvirt para provisionar as máquinas virtuais. Definir as versões dos provedores em cada módulo ajuda a evitar problemas de compatibilidade e garante que o módulo se comporte de forma previsível, mesmo que as versões dos provedores no módulo raiz sejam diferentes.

modules/network/

O módulo network é responsável por provisionar as redes virtuais no ambiente Libvirt. Ele encapsula a lógica de criação de redes, permitindo que você defina e reutilize configurações de rede em diferentes ambientes ou partes da sua infraestrutura. Dentro deste módulo, você encontrará os seguintes arquivos:

modules/network/main.tf

Este arquivo é o principal do módulo network, responsável por criar as redes virtuais no ambiente Libvirt. Ele itera sobre as definições de rede fornecidas como entrada e configura cada rede com seu nome, modo de operação, DHCP e blocos de endereços IP.

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
# =============================================================================
# CRIAÇÃO DE REDES VIRTUAIS LIBVIRT NO MÓDULO NETWORK
# =============================================================================
# Este arquivo é o principal do módulo `network`, responsável por criar as
# redes virtuais no ambiente libvirt. Ele itera sobre as definições de rede
# fornecidas e configura cada rede com seu nome, modo de operação, DHCP e
# blocos de endereços IP.

# Criação das redes virtuais libvirt
resource "libvirt_network" "network" {
  # Itera sobre cada rede definida na variável `networks` do módulo.
  for_each  = var.networks

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÕES BÁSICAS DA REDE
  # -----------------------------------------------------------------------------
  name      = each.value.net_name  # Nome da rede no libvirt (ex: 'external', 'dmz')
  mode      = each.value.net_mode  # Modo de operação da rede (ex: 'nat', 'none', 'bridge')
  autostart = true                 # Define se a rede deve iniciar automaticamente com o libvirt

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DINÂMICA DE DHCP
  # -----------------------------------------------------------------------------
  # O bloco `dhcp` é criado dinamicamente e habilitado apenas para redes que
  # não estão no modo "none". Redes no modo "none" são tipicamente isoladas
  # e o DHCP é gerenciado externamente (ex: por uma VM gateway).
  dynamic "dhcp" {
    # O bloco `dhcp` é gerado se o `net_mode` não for "none".
    for_each = each.value.net_mode != "none" ? [1] : []
    content {
      enabled = true  # Habilita o servidor DHCP interno do libvirt para esta rede
    }
  }

  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DE ENDEREÇAMENTO IP
  # -----------------------------------------------------------------------------
  # Define os blocos CIDR IPv4 e IPv6 para a rede. Para redes no modo "none",
  # os endereços são definidos como `null` aqui, pois o endereçamento será
  # configurado manualmente dentro das VMs via Cloud-Init.
  addresses = each.value.net_mode != "none" ? [
    each.value.ipv4_cidr,  # Bloco CIDR IPv4 da rede (ex: "192.168.100.0/24")
    each.value.ipv6_cidr   # Bloco CIDR IPv6 da rede (ex: "fd12:ee::/64")
  ] : null  # Não define endereços automáticos para redes no modo "none"
}

Explicação:

Este arquivo é o componente central do módulo network, responsável por criar as redes virtuais no Libvirt. Ele utiliza o recurso libvirt_network para definir as redes com base nas entradas fornecidas ao módulo. As principais configurações e conceitos abordados são:

  • for_each = var.networks: Permite que o Terraform crie múltiplas redes, uma para cada definição de rede fornecida na variável networks do módulo.
  • Configurações Básicas (name, mode, autostart): Definem o nome da rede no Libvirt, seu modo de operação (ex: nat, none, bridge) e se ela deve iniciar automaticamente com o Libvirt. O mode é crucial para determinar o comportamento da rede em termos de roteamento e DHCP.
  • Configuração Dinâmica de DHCP (dynamic "dhcp"): O bloco dhcp é criado dinamicamente e habilitado apenas para redes que não estão no modo none. Redes no modo none são tipicamente isoladas e o DHCP é gerenciado externamente (ex: por uma VM gateway). Isso permite flexibilidade na configuração de DHCP, onde o Libvirt pode gerenciar o DHCP para redes NAT, enquanto redes isoladas podem ter um servidor DHCP dedicado em uma VM.
  • Configuração de Endereçamento IP (addresses): Define os blocos CIDR IPv4 e IPv6 para a rede. Para redes no modo none, os endereços são definidos como null aqui, pois o endereçamento será configurado manualmente dentro das VMs via Cloud-Init. Para redes nat, o Libvirt gerencia o endereçamento automaticamente.

Este arquivo é fundamental para provisionar a infraestrutura de rede de forma declarativa e automatizada, garantindo que as redes virtuais sejam criadas com as especificações desejadas e se integrem corretamente com as VMs. A modularização da criação de redes permite a reutilização dessas configurações em diferentes ambientes, promovendo a consistência e a manutenibilidade.

modules/network/outputs.tf

Este arquivo define os valores de saída (outputs) que serão expostos pelo módulo network. Os outputs são a forma como um módulo pode retornar informações sobre os recursos que ele criou para o módulo pai (neste caso, o ambiente de produção) ou para outros módulos. Isso permite que as informações sobre as redes virtuais provisionadas sejam acessíveis e reutilizáveis em outras partes da infraestrutura.

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
# =============================================================================
# SAÍDAS DE DADOS DO MÓDULO NETWORK
# =============================================================================
# Este arquivo define os valores de saída que serão expostos pelo módulo
# `network`. Estas saídas são úteis para que o módulo raiz (ou outros módulos)
# possam acessar informações sobre as redes virtuais criadas, como seus IDs
# ou detalhes de configuração.

# -----------------------------------------------------------------------------
# REDES VIRTUAIS CRIADAS
# -----------------------------------------------------------------------------
# Exporta um mapa de todos os recursos `libvirt_network` criados por este módulo.
# A chave do mapa é o nome da rede (ex: 'external', 'dmz') e o valor é o objeto
# completo da rede libvirt, contendo todas as suas propriedades.
output "networks" {
  description = "Um mapa de todas as redes virtuais libvirt criadas por este módulo."
  value       = libvirt_network.network
}

# -----------------------------------------------------------------------------
# NOTAS SOBRE OUTPUTS
# -----------------------------------------------------------------------------
# - Adicione outputs para quaisquer informações que outros módulos ou o módulo
#   raiz precisem consumir sobre os recursos de rede.
# - Evite expor informações sensíveis diretamente nos outputs.

Explicação:

Neste exemplo, o arquivo outputs.tf do módulo network define um único output chamado networks. Este output exporta um mapa de todos os recursos libvirt_network criados por este módulo. A chave do mapa é o nome da rede (ex: external, dmz) e o valor é o objeto completo da rede Libvirt, contendo todas as suas propriedades, como IDs, modos de operação e blocos CIDR.

Ao expor as redes criadas como um output, o módulo pai (o ambiente de produção) pode facilmente acessar informações sobre as redes provisionadas. Por exemplo, o módulo compute utiliza este output (network_resources) para estabelecer dependências explícitas, garantindo que as redes existam antes que as VMs sejam provisionadas. É uma boa prática definir outputs para todas as informações que serão necessárias para interações futuras com os recursos criados pelo módulo.

modules/network/variables.tf

Este arquivo declara todas as variáveis de entrada esperadas pelo módulo network. Ele serve como um contrato para as configurações que o módulo espera receber para provisionar as redes virtuais. As variáveis são tipadas e podem ter descrições detalhadas para clareza, facilitando a compreensão e o uso do módulo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# =============================================================================
# DECLARAÇÃO DE VARIÁVEIS PARA O MÓDULO NETWORK
# =============================================================================
# Este arquivo declara todas as variáveis de entrada esperadas pelo módulo
# `network`. Estas variáveis são essenciais para configurar as redes virtuais
# que serão provisionadas por este módulo.

# -----------------------------------------------------------------------------
# VARIÁVEL DE REDES
# -----------------------------------------------------------------------------
# Define a estrutura e as especificações de cada rede virtual a ser criada.
# É um mapa onde cada chave é o nome da rede e o valor é um objeto com seus
# atributos detalhados.
variable "networks" {
  description = "Um mapa de objetos que definem as redes virtuais a serem criadas."
  type = map(object({
    net_name  = string  # Nome da rede no libvirt
    net_mode  = string  # Modo de operação da rede (nat, none, bridge, etc.)
    ipv4_cidr = string  # Bloco CIDR IPv4 da rede
    ipv6_cidr = string  # Bloco CIDR IPv6 da rede
  }))
}

Explicação:

Este arquivo define a única variável de entrada para o módulo network: networks. Esta variável é um mapa de objetos, onde cada objeto representa uma rede virtual a ser criada. As propriedades de cada rede incluem:

  • net_name: O nome da rede no Libvirt.
  • net_mode: O modo de operação da rede (ex: nat, none, bridge).
  • ipv4_cidr e ipv6_cidr: Os blocos CIDR IPv4 e IPv6 da rede.

Ao definir claramente as variáveis de entrada, o módulo network se torna reutilizável e fácil de integrar em diferentes ambientes, pois suas dependências e configurações esperadas são transparentes. Isso é um pilar fundamental da modularização no Terraform.

modules/network/versions.tf

Este arquivo define os requisitos de provedor específicos para o módulo network. Assim como no módulo compute, é uma boa prática que cada módulo declare suas próprias dependências de provedor. Isso garante que o módulo funcione corretamente, independentemente do ambiente em que é utilizado, e facilita a gestão de versões.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# =============================================================================
# VERSÕES DE PROVEDORES PARA O MÓDULO NETWORK
# =============================================================================
# Este arquivo define os requisitos de provedor específicos para o módulo
# `network`. Garante que a versão correta do provedor `libvirt` seja utilizada
# ao provisionar recursos de rede (redes virtuais).

terraform {
  # Definição dos provedores necessários e suas versões específicas para este módulo.
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"  # Provedor oficial para gerenciar recursos libvirt
      version = "0.8.3"              # Versão exata do provedor libvirt a ser utilizada
    }
  }
}

Explicação:

Neste arquivo, o bloco terraform especifica o provedor libvirt e sua versão exata (0.8.3) que o módulo network requer. Isso garante que, ao usar este módulo, o Terraform fará o download e utilizará a versão correta do provedor libvirt para provisionar as redes virtuais. Definir as versões dos provedores em cada módulo ajuda a evitar problemas de compatibilidade e garante que o módulo se comporte de forma previsível, mesmo que as versões dos provedores no módulo raiz sejam diferentes.

modules/storage/

O módulo storage é responsável por provisionar os volumes de armazenamento para as máquinas virtuais no ambiente Libvirt. Ele encapsula a lógica de criação de volumes, permitindo que você defina e reutilize configurações de armazenamento em diferentes ambientes ou partes da sua infraestrutura. Dentro deste módulo, você encontrará os seguintes arquivos:

modules/storage/main.tf

Este arquivo é o principal do módulo storage, responsável por criar os volumes de disco para cada máquina virtual. Ele utiliza o mecanismo de copy-on-write (CoW) do formato qcow2, o que permite a criação rápida de volumes a partir de templates base, otimizando o uso de espaço em disco.

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
# =============================================================================
# CRIAÇÃO DE VOLUMES DE ARMAZENAMENTO NO MÓDULO STORAGE
# =============================================================================
# Este arquivo é o principal do módulo `storage`, responsável por criar os
# volumes de disco para cada máquina virtual. Ele utiliza o mecanismo de
# copy-on-write (CoW) do formato qcow2, o que permite a criação rápida de
# volumes a partir de templates base, 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 na variável `servers` do módulo.
  for_each         = var.servers
  
  # -----------------------------------------------------------------------------
  # CONFIGURAÇÃO DO VOLUME
  # -----------------------------------------------------------------------------
  name             = "${each.key}.qcow2"                              # Nome do arquivo do volume (ex: 'gateway.qcow2')
  pool             = var.volume_pool                                  # Pool de armazenamento libvirt onde o volume será criado
  base_volume_name = var.os_profiles[each.value.os].template_name     # Nome do arquivo de imagem base (template) para o SO da VM
  base_volume_pool = var.base_volume_pool                             # Pool de armazenamento onde o template base está localizado
  format           = "qcow2"                                          # Formato do volume, que permite copy-on-write
  
  # -----------------------------------------------------------------------------
  # FUNCIONAMENTO DO COPY-ON-WRITE (CoW)
  # -----------------------------------------------------------------------------
  # O volume criado (`os_image`) é um disco diferencial que aponta para o
  # `base_volume_name`. Inicialmente, ele ocupa muito pouco espaço em disco.
  # Conforme a VM escreve dados, apenas as alterações (diferenças em relação
  # ao template base) são armazenadas no novo volume. Isso economiza espaço
  # e acelera a criação de VMs.
  #
  # Vantagens do CoW:
  # - **Criação Rápida:** Não é necessário copiar todo o template base para cada VM.
  # - **Economia de Espaço:** Múltiplas VMs podem compartilhar o mesmo template base, armazenando apenas suas diferenças.
  # - **Facilita Atualizações:** Atualizações no template base podem ser propagadas para novas VMs sem afetar as existentes.
}

Explicação:

Este arquivo é o componente central do módulo storage, responsável por criar os volumes de disco para as máquinas virtuais. Ele utiliza o recurso libvirt_volume para definir os volumes com base nas entradas fornecidas ao módulo. As principais configurações e conceitos abordados são:

  • for_each = var.servers: Permite que o Terraform crie múltiplos volumes, um para cada servidor definido na variável servers do módulo.
  • Configuração do Volume (name, pool, base_volume_name, base_volume_pool, format): Definem o nome do arquivo do volume (ex: gateway.qcow2), o pool de armazenamento Libvirt onde o volume será criado, o nome do arquivo de imagem base (template) para o SO da VM, o pool onde o template base está localizado e o formato do volume (qcow2).
  • Funcionamento do Copy-on-Write (CoW): O formato qcow2 permite o mecanismo de copy-on-write. Isso significa que o volume criado para a VM não é uma cópia completa do template base, mas sim um disco diferencial que armazena apenas as alterações (diferenças) em relação ao template. Isso resulta em:
    • Criação Rápida: Não é necessário copiar todo o template base para cada VM, acelerando o provisionamento.
    • Economia de Espaço: Múltiplas VMs podem compartilhar o mesmo template base, ocupando menos espaço em disco, pois apenas as diferenças são armazenadas.
    • Facilita Atualizações: Atualizações no template base podem ser propagadas para novas VMs sem afetar as existentes, simplificando a gestão de imagens.

Este arquivo é fundamental para provisionar a infraestrutura de armazenamento de forma declarativa e automatizada, garantindo que os volumes de disco sejam criados com as especificações desejadas e se integrem corretamente com as VMs. A modularização da criação de volumes permite a reutilização dessas configurações em diferentes ambientes, promovendo a consistência e a manutenibilidade.

modules/storage/outputs.tf

Este arquivo define os valores de saída (outputs) que serão expostos pelo módulo storage. Os outputs são a forma como um módulo pode retornar informações sobre os recursos que ele criou para o módulo pai (neste caso, o ambiente de produção) ou para outros módulos. Isso permite que as informações sobre os volumes de armazenamento provisionados sejam acessíveis e reutilizáveis em outras partes da infraestrutura.

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
# =============================================================================
# SAÍDAS DE DADOS DO MÓDULO STORAGE
# =============================================================================
# Este arquivo define os valores de saída que serão expostos pelo módulo
# `storage`. Estas saídas são úteis para que o módulo raiz (ou outros módulos,
# como o módulo `compute`) possam acessar informações sobre os volumes de
# armazenamento criados, como seus IDs.

# -----------------------------------------------------------------------------
# VOLUMES DE DISCO CRIADOS
# -----------------------------------------------------------------------------
# Exporta um mapa de todos os recursos `libvirt_volume` criados por este módulo.
# A chave do mapa é o nome da VM (ex: 'gateway', 'ns1') e o valor é o objeto
# completo do volume libvirt, contendo todas as suas propriedades, incluindo o ID.
output "volumes" {
  description = "Um mapa de todos os volumes de disco libvirt criados por este módulo."
  value       = libvirt_volume.os_image
}

# -----------------------------------------------------------------------------
# NOTAS SOBRE OUTPUTS
# -----------------------------------------------------------------------------
# - Adicione outputs para quaisquer informações que outros módulos ou o módulo
#   raiz precisem consumir sobre os recursos de armazenamento.
# - Evite expor informações sensíveis diretamente nos outputs.

Explicação:

Neste exemplo, o arquivo outputs.tf do módulo storage define um único output chamado volumes. Este output exporta um mapa de todos os recursos libvirt_volume criados por este módulo. A chave do mapa é o nome da VM (ex: gateway, ns1) e o valor é o objeto completo do volume Libvirt, contendo todas as suas propriedades, incluindo o ID do volume. O ID do volume é crucial, pois será usado pelo módulo compute para anexar o disco à máquina virtual.

Ao expor os volumes criados como um output, o módulo pai (o ambiente de produção) e outros módulos (como o compute) podem facilmente acessar informações sobre os volumes provisionados. É uma boa prática definir outputs para todas as informações que serão necessárias para interações futuras com os recursos criados pelo módulo.

modules/storage/variables.tf

Este arquivo declara todas as variáveis de entrada esperadas pelo módulo storage. Ele serve como um contrato para as configurações que o módulo espera receber para provisionar os volumes de armazenamento. As variáveis são tipadas e podem ter descrições detalhadas para clareza, facilitando a compreensão e o uso do módulo.

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
# =============================================================================
# DECLARAÇÃO DE VARIÁVEIS PARA O MÓDULO STORAGE
# =============================================================================
# Este arquivo declara todas as variáveis de entrada esperadas pelo módulo
# `storage`. Estas variáveis são essenciais para configurar os volumes de
# armazenamento que serão provisionados por este módulo.

# -----------------------------------------------------------------------------
# VARIÁVEL DE SERVIDORES
# -----------------------------------------------------------------------------
# Define a estrutura e as especificações de cada máquina virtual para a qual
# um volume de armazenamento será criado. Apenas o atributo `os` é necessário
# aqui para determinar o template base.
variable "servers" {
  description = "Um mapa de objetos que definem as máquinas virtuais para as quais volumes serão criados."
  type = map(object({
    os = string # Tipo de sistema operacional da VM (usado para lookup em os_profiles)
  }))
}

# -----------------------------------------------------------------------------
# VARIÁVEL DE PERFIS DE SISTEMAS OPERACIONAIS
# -----------------------------------------------------------------------------
# Define os perfis de sistema operacional disponíveis, incluindo o nome do
# template de imagem base para cada tipo de SO.
variable "os_profiles" {
  description = "Define perfis de sistema operacional, incluindo o nome do template de imagem base."
  type = map(object({
    template_name = string # Nome do arquivo de imagem base (template) para o SO
  }))
}

# -----------------------------------------------------------------------------
# VARIÁVEIS DE POOLS DE ARMAZENAMENTO
# -----------------------------------------------------------------------------
# Nomes dos pools de armazenamento libvirt para volumes de VMs e templates.
variable "volume_pool" {
  description = "Pool de armazenamento para volumes de disco criados para as VMs."
  type        = string
  default     = "default" # Valor padrão: 'default' pool do libvirt
}

variable "base_volume_pool" {
  description = "Pool de armazenamento onde os templates base das imagens de SO estão localizados."
  type        = string
  default     = "templates" # Valor padrão: 'templates' pool do libvirt
}

Explicação:

Este arquivo define as variáveis de entrada para o módulo storage. Cada variável é cuidadosamente definida com um description e um type para garantir que o módulo receba os dados corretos e para documentar suas expectativas. As principais variáveis de entrada são:

  • servers: Um mapa de objetos que define as máquinas virtuais para as quais volumes serão criados. Neste módulo, apenas o atributo os é necessário para determinar o template base a ser usado.
  • os_profiles: Define os perfis de sistema operacional disponíveis, incluindo o nome do template de imagem base para cada tipo de SO. Esta variável é crucial para que o módulo storage saiba qual imagem base usar para cada VM.
  • volume_pool e base_volume_pool: Os nomes dos pools de armazenamento Libvirt para volumes de VMs e templates, respectivamente. Possuem valores padrão de default e templates.

Ao definir claramente as variáveis de entrada, o módulo storage se torna reutilizável e fácil de integrar em diferentes ambientes, pois suas dependências e configurações esperadas são transparentes. Isso é um pilar fundamental da modularização no Terraform.

modules/storage/versions.tf

Este arquivo define os requisitos de provedor específicos para o módulo storage. Assim como nos outros módulos, é uma boa prática que cada módulo declare suas próprias dependências de provedor. Isso garante que o módulo funcione corretamente, independentemente do ambiente em que é utilizado, e facilita a gestão de versões.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# =============================================================================
# VERSÕES DE PROVEDORES PARA O MÓDULO STORAGE
# =============================================================================
# Este arquivo define os requisitos de provedor específicos para o módulo
# `storage`. Garante que a versão correta do provedor `libvirt` seja utilizada
# ao provisionar recursos de armazenamento (volumes de disco).

terraform {
  # Definição dos provedores necessários e suas versões específicas para este módulo.
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"  # Provedor oficial para gerenciar recursos libvirt
      version = "0.8.3"              # Versão exata do provedor libvirt a ser utilizada
    }
  }
}

Explicação:

Neste arquivo, o bloco terraform especifica o provedor libvirt e sua versão exata (0.8.3) que o módulo storage requer. Isso garante que, ao usar este módulo, o Terraform fará o download e utilizará a versão correta do provedor libvirt para provisionar os volumes de armazenamento. Definir as versões dos provedores em cada módulo ajuda a evitar problemas de compatibilidade e garante que o módulo se comporte de forma previsível, mesmo que as versões dos provedores no módulo raiz sejam diferentes.

Conclusão

Este tutorial demonstrou como utilizar o Terraform com uma abordagem modular para automatizar a criação e gerenciamento de uma infraestrutura de virtualização KVM/LIBVIRT. Através da divisão da infraestrutura em módulos reutilizáveis para computação, rede e armazenamento, e da orquestração desses módulos em ambientes específicos, é possível provisionar ambientes complexos de forma rápida, consistente e repetível.

A modularização no Terraform não apenas melhora a organização e a manutenibilidade do seu código, mas também promove a reusabilidade, a escalabilidade e a colaboração em equipes. Ao encapsular a lógica de provisionamento em módulos, você pode criar blocos de construção que podem ser combinados e configurados para atender a diversas necessidades de infraestrutura, desde ambientes de desenvolvimento simples até complexas infraestruturas de produção.

Compreender a função de cada arquivo (cloud-init/, environments/, modules/) e a interação entre eles é fundamental para adaptar e expandir esta infraestrutura às suas necessidades específicas. A automação com Terraform, especialmente com uma abordagem modular, não só economiza tempo e reduz erros manuais, mas também garante 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, capacitando você a construir e gerenciar ambientes virtualizados de forma mais eficiente e robusta.

Melhores Práticas de Modularização com Terraform

A modularização é um dos pilares para construir infraestruturas escaláveis e manuteníveis com Terraform. Seguir as melhores práticas garante que seus módulos sejam eficientes, reutilizáveis e fáceis de colaborar. Abaixo, destacamos algumas das principais recomendações:

  1. Nomeie seus provedores de forma consistente: Ao criar provedores personalizados ou ao referenciar provedores, siga a convenção terraform-<PROVIDER>-<NAME>. Isso é especialmente importante se você planeja publicar módulos em registros públicos ou privados [1].

  2. Comece a escrever sua configuração com módulos em mente: Mesmo para configurações Terraform de complexidade moderada gerenciadas por uma única pessoa, os benefícios de usar módulos superam o tempo necessário para implementá-los corretamente. Pensar modularmente desde o início simplifica a manutenção e a atualização da sua infraestrutura à medida que ela cresce [1].

  3. Use módulos locais para organizar e encapsular seu código: Mesmo que você não esteja usando ou publicando módulos remotos, organizar sua configuração em termos de módulos desde o início reduzirá significativamente o ônus de manter e atualizar sua configuração à medida que sua infraestrutura se torna mais complexa. Isso cria uma separação clara de responsabilidades [1, 4].

  4. Use o Terraform Registry público para encontrar módulos úteis: Aproveite o trabalho da comunidade para implementar cenários de infraestrutura comuns. Isso permite que você implemente sua configuração de forma mais rápida e confiante, utilizando módulos já testados e validados [1].

  5. Publique e compartilhe módulos com sua equipe: Módulos são uma forma importante para que as equipes possam trabalhar juntas na criação e manutenção da infraestrutura. Você pode publicar módulos publicamente ou em registros privados, permitindo que os membros da equipe referenciem e reutilizem esses blocos de construção [1].

  6. Mantenha os módulos focados e genéricos: Um módulo deve ser baseado em um recurso base específico e quaisquer recursos relacionados que sejam necessários. Evite criar módulos muito grandes ou que tentem cobrir muitos casos de uso. Um bom módulo deve ser genérico o suficiente para ser reutilizado em diferentes contextos, mas específico o suficiente para ter uma responsabilidade clara [6, 9].

  7. Use outputs para expor informações importantes: Os outputs são cruciais para que os módulos retornem informações sobre os recursos que eles criaram. Isso permite que outros módulos ou o módulo raiz consumam essas informações, facilitando a interconexão e a automação [5].

  8. Gerenciamento de estado remoto: Para ambientes de equipe e produção, é fundamental usar um backend remoto para o estado do Terraform (ex: S3, Azure Blob Storage, HashiCorp Consul). Isso garante que o estado seja compartilhado de forma segura e consistente entre os membros da equipe e evita corrupção do estado local [3, 20].

  9. Controle de versão: Mantenha seu código Terraform, incluindo módulos, em um sistema de controle de versão (ex: Git). Isso permite rastrear alterações, colaborar com a equipe e reverter para versões anteriores, se necessário [15].

Ao incorporar essas práticas em seus projetos Terraform, você construirá infraestruturas mais robustas, escaláveis e fáceis de gerenciar.

Referências

[1] HashiCorp Developer. Modules overview. Disponível em: https://developer.hashicorp.com/terraform/tutorials/modules/module. Acesso em: 21 jul. 2025. [3] Spacelift. 20 Terraform Best Practices to Improve your TF workflow. Disponível em: https://spacelift.io/blog/terraform-best-practices. Acesso em: 21 jul. 2025. [4] Reddit. Terraform Module Best Practices… When to use, when not to use?. Disponível em: https://www.reddit.com/r/Terraform/comments/jswttn/terraform_module_best_practices_when_to_use_when/. Acesso em: 21 jul. 2025. [5] Google Cloud. Best practices for general style and structure. Disponível em: https://cloud.google.com/docs/terraform/best-practices/general-style-structure. Acesso em: 21 jul. 2025. [6] HashiCorp Developer. Module creation - recommended pattern. Disponível em: https://developer.hashicorp.com/terraform/tutorials/modules/pattern-module-creation. Acesso em: 21 jul. 2025. [9] HashiCorp Discuss. Module design best practice. Disponível em: https://discuss.hashicorp.com/t/module-design-best-practice/60168. Acesso em: 21 jul. 2025. [15] Medium. Day 15: Terraform Best Practices. Disponível em: https://medium.com/@vinoji2005/day-15-terraform-best-practices-60c759a78432. Acesso em: 21 jul. 2025. [20] Stack Overflow. Best practices when using Terraform? Is there any official guide to it?. Disponível em: https://stackoverflow.com/questions/33157516/best-practices-when-using-terraform-is-there-any-official-guide-to-it. Acesso em: 21 jul. 2025.

Inicializando e Aplicando o Projeto Modular

Após estruturar seu projeto Terraform de forma modular, o próximo passo é inicializá-lo e aplicá-lo para provisionar a infraestrutura. O Terraform gerencia as dependências entre os módulos automaticamente, garantindo que os recursos sejam criados na ordem correta.

1. Inicialização do Projeto

Antes de executar qualquer comando Terraform, você precisa inicializar o diretório de trabalho. Isso baixa os provedores e módulos necessários, e configura o backend para o estado do Terraform.

Navegue até o diretório do ambiente que você deseja provisionar (por exemplo, environments/production/):

1
cd environments/production/

Em seguida, execute o comando terraform init:

1
terraform init

Saída esperada:

1
2
3
4
5
6
7
8
9
10
11
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of dmacvicar/libvirt from the dependency lock file
- Finding dmacvicar/libvirt versions matching "0.8.3"...
- Installing dmacvicar/libvirt v0.8.3...
- Installed dmacvicar/libvirt v0.8.3 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required to your infrastructure.

Este comando fará o download do provedor libvirt e de quaisquer outros provedores ou módulos remotos que você tenha configurado. Ele também inicializará o backend do estado do Terraform.

2. Planejamento da Infraestrutura

Após a inicialização, é uma boa prática executar terraform plan. Este comando gera um plano de execução, mostrando quais ações o Terraform realizará para atingir o estado desejado da sua infraestrutura, sem realmente fazer nenhuma alteração. Isso permite que você revise as mudanças propostas antes de aplicá-las.

1
terraform plan

Saída esperada (exemplo resumido):

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
Terraform will perform the following actions:

  # libvirt_network.network["cgr"] will be created
  + resource "libvirt_network" "network" {
      + autostart = true
      + bridge    = (known after apply)
      + id        = (known after apply)
      + mode      = "none"
      + name      = "cgr"
      + uuid      = (known after apply)

      + addresses {
          + address = "10.48.32.0/24"
        }
      + addresses {
          + address = "fd00:48:32::/64"
        }
    }

  # libvirt_domain.domain["gateway"] will be created
  + resource "libvirt_domain" "domain" {
      + arch        = (known after apply)
      + cloudinit   = (known after apply)
      + id          = (known after apply)
      + memory      = 2048
      + name        = "gateway"
      + running     = (known after apply)
      + vcpu        = 2
      + uuid        = (known after apply)
      ...
    }

Plan: 10 to add, 0 to change, 0 to destroy.

O plano detalhará todos os recursos que serão criados, modificados ou destruídos. Revise cuidadosamente esta saída para garantir que as ações propostas correspondam às suas expectativas.

3. Aplicação da Infraestrutura

Uma vez que você esteja satisfeito com o plano de execução, você pode aplicar as mudanças para provisionar a infraestrutura. Use o comando terraform apply:

1
terraform apply

O Terraform exibirá novamente o plano de execução e solicitará sua confirmação. Digite yes para prosseguir com a aplicação.

Saída esperada (exemplo resumido):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Plan: 10 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

libvirt_network.network["cgr"]: Creating...
libvirt_network.network["cgr"]: Creation complete after 0s [id=cgr]
...
libvirt_domain.domain["gateway"]: Creating...
libvirt_domain.domain["gateway"]: Still creating... [id=...]
libvirt_domain.domain["gateway"]: Creation complete after 1m30s [id=...]

Apply complete! Resources: 10 added, 0 changed, 0 destroyed.

O Terraform começará a provisionar os recursos em seu ambiente Libvirt. Isso pode levar alguns minutos, dependendo da complexidade da sua infraestrutura e do desempenho do seu host KVM. Ao final, você verá um resumo das ações realizadas.

4. Acessando os Outputs

Após a aplicação bem-sucedida, você pode acessar os valores de saída definidos em outputs.tf usando o comando terraform output:

1
terraform output gateway_ip

Saída esperada:

1
10.32.16.1

Você também pode ver todos os outputs em formato JSON:

1
terraform output -json

5. Destruindo a Infraestrutura

Para destruir todos os recursos provisionados pelo seu projeto Terraform (por exemplo, para limpar o ambiente de teste ou desenvolvimento), use o comando terraform destroy:

1
terraform destroy

O Terraform exibirá um plano de destruição e solicitará sua confirmação. Digite yes para prosseguir.

CUIDADO: Este comando removerá permanentemente todos os recursos gerenciados pelo seu estado Terraform. Use-o com extrema cautela, especialmente em ambientes de produção.

Ao seguir estes passos, você será capaz de gerenciar seu ambiente KVM/LIBVIRT de forma eficiente e automatizada usando a abordagem modular do Terraform.

Diagrama de Rede da Infraestrutura

Para visualizar melhor a topologia de rede da infraestrutura modular, apresentamos o diagrama abaixo que ilustra as conexões entre as diferentes redes virtuais e as máquinas virtuais:

graph TB
    subgraph "Host KVM/LIBVIRT"
        subgraph "Rede Externa (NAT)"
            EXT[external<br/>192.168.100.0/24<br/>fd12:ee::/64]
        end
        
        subgraph "Rede DMZ (Isolada)"
            DMZ[dmz<br/>10.32.16.0/24<br/>fd00:32:16::/64]
            NS1[ns1<br/>10.32.16.3<br/>fd00:32:16::3]
            NS2[ns2<br/>10.32.16.4<br/>fd00:32:16::4]
        end
        
        subgraph "Rede CGR (Isolada)"
            CGR[cgr<br/>10.48.32.0/24<br/>fd00:48:32::/64]
        end
        
        subgraph "Rede DHCP (Isolada)"
            DHCP_NET[dhcp<br/>10.128.112.0/20<br/>fd00:128:112::/64]
        end
        
        subgraph "Gateway VM"
            GW[gateway<br/>Múltiplas Interfaces]
            GW_EXT[ens3: DHCP]
            GW_DMZ[ens4: 10.32.16.1]
            GW_CGR[ens5: 10.48.32.1]
            GW_DHCP[ens6: 10.128.112.1]
        end
    end
    
    subgraph "Internet"
        INET[Internet]
    end
    
    %% Conexões do Gateway
    GW_EXT -.-> EXT
    GW_DMZ -.-> DMZ
    GW_CGR -.-> CGR
    GW_DHCP -.-> DHCP_NET
    
    %% Conexões dos servidores DNS
    NS1 -.-> DMZ
    NS2 -.-> DMZ
    
    %% Conexão com a Internet
    EXT --> INET
    
    %% Roteamento através do Gateway
    DMZ -.-> GW_DMZ
    CGR -.-> GW_CGR
    DHCP_NET -.-> GW_DHCP
    GW_EXT --> EXT
    
    %% Estilos
    classDef networkBox fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    classDef vmBox fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    classDef gatewayBox fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef internetBox fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
    
    class EXT,DMZ,CGR,DHCP_NET networkBox
    class NS1,NS2 vmBox
    class GW,GW_EXT,GW_DMZ,GW_CGR,GW_DHCP gatewayBox
    class INET internetBox

Explicação do Diagrama

O diagrama acima ilustra a arquitetura de rede da infraestrutura modular, destacando os seguintes componentes:

Redes Virtuais:

  • Externa (NAT): Fornece conectividade com a internet através de NAT gerenciado pelo Libvirt
  • DMZ (Isolada): Zona desmilitarizada para serviços públicos como DNS
  • CGR (Isolada): Rede de gerência para acesso administrativo
  • DHCP (Isolada): Rede para clientes que obtêm configuração via DHCP

Máquinas Virtuais:

  • Gateway: Servidor multi-homed que atua como roteador entre todas as redes
  • NS1/NS2: Servidores DNS primário e secundário na rede DMZ

Fluxo de Tráfego: O gateway atua como o ponto central de roteamento, permitindo comunicação controlada entre as redes isoladas e fornecendo acesso à internet através da rede externa NAT.

Avisos Importantes e Notas de Segurança

Ao trabalhar com automação de infraestrutura, especialmente em ambientes de virtualização como KVM/LIBVIRT, é crucial estar ciente de alguns avisos importantes e seguir as melhores práticas de segurança. Ignorar esses pontos pode levar a perda de dados, vulnerabilidades de segurança ou interrupções no serviço.

1. Gerenciamento do Estado do Terraform

O arquivo de estado do Terraform (terraform.tfstate) é um componente crítico que mapeia os recursos reais da sua infraestrutura para a sua configuração. Ele contém informações sensíveis e é fundamental para o Terraform gerenciar seus recursos.

  • Nunca edite o arquivo de estado manualmente: Alterações diretas no terraform.tfstate podem corromper o estado e levar a inconsistências entre a sua configuração e a infraestrutura real, resultando em perda de dados ou recursos órfãos.
  • Use backends remotos: Para ambientes de equipe e produção, é altamente recomendado usar um backend remoto (como S3, Azure Blob Storage, HashiCorp Consul, Terraform Cloud) para armazenar o estado. Isso garante que o estado seja compartilhado de forma segura e consistente entre os membros da equipe, oferece bloqueio de estado para evitar conflitos e mantém o estado seguro contra exclusão acidental ou corrupção local.
  • Backup do estado: Independentemente do backend, sempre tenha uma estratégia de backup para o seu arquivo de estado. Em caso de corrupção, um backup pode ser a única forma de recuperar o controle da sua infraestrutura.

2. Comando terraform destroy

O comando terraform destroy é extremamente poderoso e deve ser usado com a máxima cautela. Ele remove permanentemente todos os recursos gerenciados pelo seu estado Terraform.

  • Confirmação explícita: O Terraform sempre solicitará uma confirmação (yes) antes de executar um destroy. Certifique-se de que você realmente deseja destruir os recursos antes de confirmar.
  • Ambientes de produção: Nunca execute terraform destroy em ambientes de produção sem uma revisão rigorosa e um plano de contingência. Considere implementar proteções como prevent_destroy = true em recursos críticos para evitar exclusões acidentais.
  • Escopo: Verifique sempre o escopo do destroy. Se você estiver em um diretório de ambiente (ex: environments/production/), o destroy afetará apenas os recursos definidos para aquele ambiente. No entanto, um destroy executado no diretório raiz de um projeto pode ter consequências catastróficas.

3. Segurança da Chave SSH

A chave SSH pública injetada nas VMs permite acesso administrativo sem senha. A chave privada correspondente é a sua credencial de acesso.

  • Proteja sua chave privada: Sua chave privada SSH deve ser mantida em um local seguro, com permissões restritivas (ex: chmod 400 ~/.ssh/id_rsa). Nunca a compartilhe ou a exponha em repositórios públicos.
  • Use senhas fortes para chaves privadas: Se sua chave privada for protegida por uma senha (passphrase), use uma senha forte e única.
  • Rotação de chaves: Considere implementar uma política de rotação de chaves SSH, especialmente para ambientes de produção.

4. Configurações de Rede e Firewall

Uma configuração de rede inadequada pode expor sua infraestrutura a ataques ou causar interrupções no serviço.

  • Princípio do menor privilégio: Configure as regras de firewall (no host KVM, no gateway VM e nas próprias VMs) para permitir apenas o tráfego essencial. Bloqueie todas as portas e protocolos por padrão e abra apenas o que for estritamente necessário.
  • Segmentação de rede: Utilize a segmentação de rede (DMZ, redes isoladas) para limitar o impacto de uma possível violação de segurança. Se um segmento for comprometido, o atacante terá dificuldade em se mover lateralmente para outras partes da sua infraestrutura.
  • Monitoramento de tráfego: Implemente ferramentas de monitoramento de rede para detectar atividades suspeitas ou anomalias no tráfego.

5. Imagens Base e Cloud-Init

As imagens base (qcow2) e os templates Cloud-Init são a fundação das suas VMs.

  • Imagens confiáveis: Utilize apenas imagens base de fontes confiáveis. Se você criar suas próprias imagens com Packer, certifique-se de que o processo de construção seja seguro e que as imagens sejam verificadas quanto a vulnerabilidades.
  • Validação de templates Cloud-Init: Revise cuidadosamente os templates user_data.yml e network_config.yml. Erros ou configurações maliciosas nesses templates podem comprometer a segurança ou a funcionalidade das suas VMs.
  • Atualizações de segurança: Mantenha suas imagens base e os pacotes dentro das VMs atualizados com os patches de segurança mais recentes.

Ao seguir estas diretrizes, você pode mitigar muitos dos riscos associados à automação de infraestrutura e garantir que seu ambiente KVM/LIBVIRT seja robusto, seguro e confiável.

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