Post

Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform Modular Versão 2

Eleve sua automação de infraestrutura a um novo patamar. Aprenda a estruturar seu projeto Terraform com módulos reutilizáveis para provisionar ambientes KVM/LIBVIRT de forma escalável, manutenível e eficiente.

Tutorial: Automatizando Infraestrutura KVM/LIBVIRT com Terraform Modular Versão 2

Introdução

Este tutorial detalha como provisionar máquinas virtuais (VMs) utilizando uma combinação poderosa de Terraform, Cloud-init e Libvirt. O objetivo é fornecer um guia passo a passo para entender e replicar uma infraestrutura de VMs definida por código, focando na automação da configuração de rede e usuários.

O Terraform será utilizado para orquestrar a criação e gerenciamento das VMs e suas redes no ambiente Libvirt (KVM/QEMU). O Cloud-init, por sua vez, será responsável pela personalização inicial das VMs, como configuração de interfaces de rede, adição de usuários e definição de hostnames, garantindo que as máquinas estejam prontas para uso assim que forem provisionadas.

Ao final deste tutorial, você terá uma compreensão clara de como esses componentes se integram para criar um fluxo de trabalho eficiente e repetível para o provisionamento de infraestrutura virtual.

Pré-requisitos

Para seguir este tutorial, você precisará ter os seguintes componentes instalados e configurados em seu sistema:

Com esses pré-requisitos atendidos, você estará pronto para começar a construir sua infraestrutura virtualizada com Terraform.

Download de Imagens Cloud-Init

Para facilitar o download das imagens base compatíveis com Cloud-Init para o seu pool de templates, você pode usar os seguintes comandos. Certifique-se de que o pool templates esteja configurado e acessível.

Exemplo para Debian 12 (Bookworm):

1
2
3
4
5
# Navegue até o diretório do seu pool de templates (ex: /var/lib/libvirt/templates)
cd /var/lib/libvirt/templates

# Baixe a imagem mais recente do Debian 12 Cloud (amd64)
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2 -O debian-12-amd64.qcow2

Exemplo para Oracle Linux 9:

1
2
3
4
5
# Navegue até o diretório do seu pool de templates (ex: /var/lib/libvirt/templates)
cd /var/lib/libvirt/templates

# Baixe a imagem mais recente do Oracle Linux 9 Cloud (qcow2)
wget https://yum.oracle.com/templates/OracleLinux/OL9/u5/x86_64/OL9U5_x86_64-kvm-b259.qcow2 -O ol9-amd64.qcow2

Nota: Verifique sempre os links oficiais para as versões mais recentes das imagens, pois os URLs podem mudar com o tempo. Após o download, as imagens estarão prontas para serem utilizadas pelo Terraform em seu pool de templates.

Estrutura do Projeto

O projeto está organizado em uma estrutura de diretórios modular, facilitando a reutilização e a organização do código. A seguir, a representação da estrutura e uma breve descrição de cada diretório:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.
├── cloud-init
│   ├── network_config_gateway.yml
│   ├── network_config.yml
│   └── user_data.yml
├── environments
│   └── production
│       ├── main.tf
│       ├── networks.auto.tfvars
│       ├── outputs.tf
│       ├── providers.tf
│       ├── servers.auto.tfvars
│       ├── terraform.tfvars
│       └── variables.tf
└── modules
    ├── compute
    │   ├── cloudinit.tf
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── variables.tf
    │   └── versions.tf
    ├── network
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── variables.tf
    │   └── versions.tf
    └── storage
        ├── main.tf
        ├── outputs.tf
        ├── variables.tf
        └── versions.tf

Descrição dos Diretórios:

  • cloud-init/: Contém os templates de configuração do Cloud-init que serão injetados nas VMs no momento do provisionamento. Estes arquivos são responsáveis por configurar a rede, usuários, hostname e outras configurações iniciais.
    • network_config.yml: Template para configuração de interfaces de rede (IPv4 e IPv6, DHCP ou estático, DNS e rotas).
    • user_data.yml: Template para configuração de usuários, chaves SSH, desabilitação de login por senha e definição de hostname.
  • environments/: Este diretório agrupa as configurações específicas para diferentes ambientes (ex: production, development, staging). Cada subdiretório representa um ambiente distinto.
    • environments/production/: Contém os arquivos Terraform que definem a infraestrutura para o ambiente de produção.
      • main.tf: O arquivo principal que orquestra a criação dos recursos, chamando os módulos de rede, armazenamento e computação.
      • networks.auto.tfvars: Define as configurações das redes a serem criadas (nomes, modos, CIDRs IPv4/IPv6).
      • outputs.tf: Define os valores de saída que podem ser consultados após a aplicação do Terraform (ex: IP do gateway).
      • providers.tf: Configura os provedores Terraform necessários, neste caso, o provedor libvirt.
      • servers.auto.tfvars: Define as especificações de cada servidor/VM a ser provisionado (vCPUs, memória, OS, configurações de rede).
      • terraform.tfvars: Contém variáveis sensíveis ou específicas do ambiente, como a chave SSH pública e pools de armazenamento.
      • variables.tf: Declara as variáveis que serão utilizadas nos arquivos Terraform do ambiente.
  • modules/: Contém módulos Terraform reutilizáveis. Cada módulo encapsula um conjunto de recursos relacionados, promovendo a modularidade e a reutilização do código.
    • modules/compute/: Módulo responsável pela criação das máquinas virtuais (domínios Libvirt) e pela injeção das configurações do Cloud-init.
      • cloudinit.tf: Define o recurso libvirt_cloudinit_disk que gera os ISOs de Cloud-init para cada VM.
      • main.tf: Define o recurso libvirt_domain que cria as VMs, configurando CPU, memória, discos e interfaces de rede.
      • outputs.tf: Define os valores de saída do módulo de computação (ex: informações dos domínios criados).
      • variables.tf: Declara as variáveis de entrada para o módulo de computação.
      • versions.tf: Define as versões mínimas dos provedores Terraform exigidos pelo módulo.
    • modules/network/: Módulo responsável pela criação das redes virtuais no Libvirt.
      • main.tf: Define o recurso libvirt_network que cria as redes com suas configurações de nome, modo e endereçamento.
      • outputs.tf: Define os valores de saída do módulo de rede (ex: informações das redes criadas).
      • variables.tf: Declara as variáveis de entrada para o módulo de rede.
      • versions.tf: Define as versões mínimas dos provedores Terraform exigidos pelo módulo.
    • modules/storage/: Módulo responsável pela criação dos volumes de disco para as VMs, baseados em imagens de template.
      • main.tf: Define o recurso libvirt_volume que cria os volumes de disco para cada VM, utilizando imagens base.
      • outputs.tf: Define os valores de saída do módulo de armazenamento (ex: informações dos volumes criados).
      • variables.tf: Declara as variáveis de entrada para o módulo de armazenamento.
      • versions.tf: Define as versões mínimas dos provedores Terraform exigidos pelo módulo.

Esta estrutura permite uma clara separação de responsabilidades e facilita a manutenção e escalabilidade da infraestrutura.

Explicação dos Arquivos de Configuração

Nesta seção, detalharemos o conteúdo de cada arquivo de configuração, explicando seu propósito e como eles contribuem para o provisionamento automatizado das VMs.

Cloud-init

Os arquivos do Cloud-init são templates que permitem a personalização das VMs no primeiro boot. Eles são processados pelo Terraform e injetados nas VMs como um disco cloudinit.

cloud-init/network_config.yml

Este arquivo é um template Cloud-init para configurar as interfaces de rede das VMs. Ele é dinâmico e utiliza variáveis do Terraform para adaptar a configuração a cada VM e interface de rede. O template suporta configurações DHCP e estáticas, além de DNS e rotas padrão.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#cloud-config
network:
  version: 2
  ethernets:
%{ for iface in interfaces ~}
    ${iface.if_name}:
      %{ if iface.ipv4 == "dhcp" }
      dhcp4: true
      dhcp6: true
      %{ else }
      dhcp4: no
      dhcp6: no
      accept-ra: false
      addresses:
        - ${iface.ipv4}/${iface.ipv4_prefix}
        %{ if iface.ipv6 != "dhcp" }
        - ${iface.ipv6}/${iface.ipv6_prefix}
        %{ endif }
        
      # Configuração de DNS (global para todas as interfaces estáticas)
      %{ if !has_dhcp_interface }
        %{ if hostname == "ns1" || hostname == "ns2" }
      nameservers:
        addresses:
          - 127.0.0.1
          - "::1"
        %{ else }
      nameservers:
        addresses:
          - ${global_dns.ns1_v4}
          - ${global_dns.ns2_v4}
          - ${global_dns.ns1_v6}
          - ${global_dns.ns2_v6}
        %{ endif }
      %{ endif }
      
      # Aplica rota APENAS se for o gateway padrão
      %{ if contains(keys(network_gateways), iface.name) && try(iface.is_default_gateway, false) }
      routes:
        - to: 0.0.0.0/0
          via: ${network_gateways[iface.name].gateway_v4}
        %{ if iface.ipv6 != "dhcp" }
        - to: "::/0"
          via: ${network_gateways[iface.name].gateway_v6}
        %{ endif }
      %{ endif }
      %{ endif }
%{ endfor ~}

Explicação Detalhada:

  • network: version: 2: Define a versão do formato de configuração de rede do Cloud-init.
  • ethernets:: Bloco que contém as configurações para cada interface de rede.
  • %{ for iface in interfaces ~}: Loop Terraform que itera sobre a lista de interfaces de rede (interfaces) definida para cada VM no servers.auto.tfvars.
    • ${iface.if_name}: Nome da interface de rede (ex: ens3, ens4).
    • %{ if iface.ipv4 == "dhcp" }: Condicional que verifica se a interface deve obter um endereço IPv4 via DHCP.
      • dhcp4: true e dhcp6: true: Habilita DHCP para IPv4 e IPv6.
    • %{ else }: Bloco executado se a interface não for DHCP (configuração estática).
      • dhcp4: no, dhcp6: no, accept-ra: false: Desabilita DHCP e aceitação de Router Advertisements.
      • addresses:: Define os endereços IP estáticos para a interface.
        • - ${iface.ipv4}/${iface.ipv4_prefix}: Endereço IPv4 e prefixo de rede.
        • %{ if iface.ipv6 != "dhcp" }: Condicional para configurar IPv6 estático se não for DHCP.
          • - ${iface.ipv6}/${iface.ipv6_prefix}: Endereço IPv6 e prefixo de rede.
    • # Configuração de DNS (global para todas as interfaces estáticas): Comentário indicando a seção de DNS.
    • %{ if !has_dhcp_interface }: Condicional que aplica a configuração de DNS apenas se nenhuma interface na VM estiver configurada com DHCP. Isso evita conflitos com DNSs fornecidos via DHCP.
      • %{ if hostname == "ns1" || hostname == "ns2" }: Condicional específica para os servidores DNS (ns1, ns2), que usarão 127.0.0.1 e ::1 como nameservers (loopback), indicando que eles mesmos serão os resolvedores de DNS.
      • %{ else }: Para outras VMs, utiliza os DNSs globais definidos em global_dns (vindos de terraform.tfvars).
    • # Aplica rota APENAS se for o gateway padrão: Comentário indicando a seção de rotas.
    • %{ if contains(keys(network_gateways), iface.name) && try(iface.is_default_gateway, false) }: Condicional complexa que verifica se a interface atual (iface.name) está listada como um gateway de rede e se is_default_gateway está definido como true para essa interface. Isso garante que a rota padrão (0.0.0.0/0 e ::/0) seja aplicada apenas à interface designada como gateway principal.
      • routes:: Bloco para definir rotas estáticas.
        • - to: 0.0.0.0/0 via: ${network_gateways[iface.name].gateway_v4}: Rota padrão IPv4.
        • %{ if iface.ipv6 != "dhcp" }: Condicional para rota padrão IPv6 se a interface não for DHCP.
          • - to: "::/0" via: ${network_gateways[iface.name].gateway_v6}: Rota padrão IPv6.

Este template é um exemplo robusto de como o Cloud-init pode ser parametrizado com Terraform para lidar com diversas configurações de rede de forma automatizada e flexível.

cloud-init/user_data.yml

Este arquivo Cloud-init é responsável pela configuração de usuários, chaves SSH, desabilitação de login por senha e definição do hostname da VM. Ele garante que as VMs estejam seguras e acessíveis via SSH com a chave pública fornecida.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#cloud-config

manage_etc_hosts: true

# Template para dados de usuário
users:
  - name: ${user_name}
    gecos: ${gecos}
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: ${jsonencode(groups)}
    shell: /bin/bash
    lock_passwd: true 
    ssh_authorized_keys:
      - "${ssh_key}"

# Desabilita login por senha e do usuário root
disable_root: true
ssh_pwauth: false

# Define o hostname da máquina
runcmd:
  - hostnamectl set-hostname ${hostname}
  - echo '127.0.1.1 ${hostname}' >> /etc/hosts

Explicação Detalhada:

  • manage_etc_hosts: true: Garante que o Cloud-init gerencie o arquivo /etc/hosts, adicionando uma entrada para o hostname da máquina.
  • users:: Bloco para definir os usuários que serão criados na VM.
    • - name: ${user_name}: Define o nome do usuário, que é parametrizado via Terraform (vindo de default_vm_user ou sobrescrito por servidor).
    • gecos: ${gecos}: Informações GECOS (General Electric Comprehensive Operating System), geralmente o nome completo do usuário.
    • sudo: ALL=(ALL) NOPASSWD:ALL: Concede privilégios de sudo ao usuário sem a necessidade de senha.
    • groups: ${jsonencode(groups)}: Define os grupos aos quais o usuário pertencerá. Os grupos são passados como uma lista JSON, vindo dos perfis de OS definidos em terraform.tfvars.
    • shell: /bin/bash: Define o shell padrão para o usuário como Bash.
    • lock_passwd: true: Bloqueia a senha do usuário, forçando o acesso via chave SSH.
    • ssh_authorized_keys:: Lista de chaves SSH públicas autorizadas para o usuário.
      • - "${ssh_key}": A chave SSH pública é injetada aqui, vinda da variável ssh_public_key em terraform.tfvars.
  • disable_root: true: Desabilita o login direto para o usuário root.
  • ssh_pwauth: false: Desabilita a autenticação por senha via SSH, aumentando a segurança.
  • runcmd:: Lista de comandos que serão executados na VM após a inicialização.
    • - hostnamectl set-hostname ${hostname}: Define o hostname da máquina, utilizando o nome da VM definido no Terraform.
    • - echo '127.0.1.1 ${hostname}' >> /etc/hosts: Adiciona uma entrada para o hostname no arquivo /etc/hosts, mapeando-o para o endereço de loopback. Isso ajuda na resolução local do hostname.

Este arquivo é crucial para a segurança e acessibilidade das VMs, garantindo que apenas usuários autorizados com chaves SSH válidas possam acessá-las e que o hostname esteja corretamente configurado para identificação na rede.

Ambientes (Environments)

O diretório environments/production contém a configuração específica para o ambiente de produção, orquestrando a criação de redes, volumes e máquinas virtuais.

environments/production/main.tf

Este é o arquivo principal do ambiente de produção. Ele atua como um orquestrador, chamando os módulos Terraform definidos no diretório modules para construir a infraestrutura completa. Ele define as dependências entre os módulos, garantindo que os recursos sejam criados na ordem correta (por exemplo, redes antes das máquinas virtuais).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module "network" {
  source   = "../../modules/network"
  networks = var.networks
}

module "storage" {
  source           = "../../modules/storage"
  servers          = var.servers
  os_profiles      = var.os_profiles
  volume_pool      = var.volume_pool
  base_volume_pool = var.base_volume_pool
}

module "compute" {
  source          = "../../modules/compute"
  servers         = var.servers
  default_vm_user = var.default_vm_user
  os_profiles     = var.os_profiles
  ssh_public_key  = var.ssh_public_key
  network_dmz     = var.network_dmz
  network_cgr     = var.network_cgr
  defaults_dns    = var.defaults_dns
  volume_pool     = var.volume_pool
  volumes         = module.storage.volumes
  # Passa as redes como dependência
  network_resources = module.network.networks
}

Explicação Detalhada:

  • module "network": Este bloco invoca o módulo network localizado em ../../modules/network. Ele é responsável por criar as redes virtuais no Libvirt. A variável networks é passada para o módulo, que contém as definições de cada rede (nome, modo, CIDRs IPv4/IPv6).
    • source = "../../modules/network": Caminho relativo para o diretório do módulo de rede.
    • networks = var.networks: Mapeia a variável networks (definida em networks.auto.tfvars) para a entrada correspondente no módulo de rede.
  • module "storage": Este bloco invoca o módulo storage localizado em ../../modules/storage. Ele é responsável por criar os volumes de disco para as VMs, utilizando imagens base. As variáveis servers, os_profiles, volume_pool e base_volume_pool são passadas para o módulo.
    • source = "../../modules/storage": Caminho relativo para o diretório do módulo de armazenamento.
    • servers = var.servers: Mapeia a variável servers (definida em servers.auto.tfvars) para a entrada correspondente no módulo de armazenamento.
    • os_profiles = var.os_profiles: Mapeia a variável os_profiles (definida em terraform.tfvars) para a entrada correspondente no módulo de armazenamento.
    • volume_pool = var.volume_pool: Mapeia a variável volume_pool (definida em terraform.tfvars) para a entrada correspondente no módulo de armazenamento.
    • base_volume_pool = var.base_volume_pool: Mapeia a variável base_volume_pool (definida em terraform.tfvars) para a entrada correspondente no módulo de armazenamento.
  • module "compute": Este bloco invoca o módulo compute localizado em ../../modules/compute. Ele é responsável por criar as máquinas virtuais e injetar as configurações do Cloud-init. Este módulo recebe uma série de variáveis, incluindo as definições dos servidores, informações de usuário padrão, perfis de OS, chave SSH pública, gateways de rede e DNSs padrão. É importante notar que ele também recebe os volumes (saída do módulo storage) e os network_resources (saída do módulo network) como dependências, garantindo que os discos e as redes existam antes das VMs serem criadas.
    • source = "../../modules/compute": Caminho relativo para o diretório do módulo de computação.
    • servers = var.servers: Mapeia a variável servers (definida em servers.auto.tfvars) para a entrada correspondente no módulo de computação.
    • default_vm_user = var.default_vm_user: Mapeia a variável default_vm_user (definida em terraform.tfvars) para a entrada correspondente no módulo de computação.
    • os_profiles = var.os_profiles: Mapeia a variável os_profiles (definida em terraform.tfvars) para a entrada correspondente no módulo de computação.
    • ssh_public_key = var.ssh_public_key: Mapeia a variável ssh_public_key (definida em terraform.tfvars) para a entrada correspondente no módulo de computação.
    • network_dmz, network_cgr, defaults_dns: Mapeiam as variáveis de configuração de rede e DNS (definidas em terraform.tfvars) para as entradas correspondentes no módulo de computação.
    • volumes = module.storage.volumes: Passa a saída volumes do módulo storage como entrada para o módulo compute. Isso estabelece uma dependência implícita, garantindo que os volumes sejam criados antes de serem anexados às VMs.
    • network_resources = module.network.networks: Passa a saída networks do módulo network como entrada para o módulo compute. Isso também estabelece uma dependência implícita, garantindo que as redes estejam disponíveis antes de serem configuradas nas interfaces das VMs.

Este arquivo main.tf demonstra a capacidade do Terraform de compor uma infraestrutura complexa a partir de módulos menores e reutilizáveis, gerenciando as dependências entre os recursos de forma eficiente.

environments/production/networks.auto.tfvars

Este arquivo define as configurações das redes virtuais que serão criadas no ambiente de produção. O Terraform carrega automaticamente arquivos com a extensão .auto.tfvars.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
networks = {
  external = {
    net_name  = "external"
    net_mode  = "nat"
    ipv4_cidr = "192.168.100.0/24"
    ipv6_cidr = "fd12:ee::/64"
  },
  dmz = {
    net_name  = "dmz"
    net_mode  = "none"
    ipv4_cidr = "10.32.16.0/24"
    ipv6_cidr = "fd00:32:16::/64"
  },
  cgr = {
    net_name  = "cgr"
    net_mode  = "none"
    ipv4_cidr = "10.48.32.0/24"
    ipv6_cidr = "fd00:48:32::/64"
  },
  dhcp = {
    net_name  = "dhcp"
    net_mode  = "none"
    ipv4_cidr = "10.128.112.0/20"
    ipv6_cidr = "fd00:128:112::/64"
  }
  public = {
    net_name  = "public"
    net_mode  = "none"
    ipv4_cidr = "203.0.113.0/24"
    ipv6_cidr = "2001:db8:feed::/64"
  }
}

Explicação Detalhada:

  • networks = { ... }: Define um mapa de objetos, onde cada chave representa o nome lógico de uma rede (ex: external, dmz, cgr, dhcp, public).
  • Para cada rede, são definidos os seguintes atributos:
    • net_name: O nome real da rede que será criada no Libvirt. É uma boa prática que seja o mesmo nome lógico para clareza.
    • net_mode: O modo de operação da rede. Pode ser nat (para redes com NAT, permitindo acesso externo) ou none (para redes isoladas, sem NAT).
    • ipv4_cidr: O bloco CIDR IPv4 para a rede (ex: 192.168.100.0/24).
    • ipv6_cidr: O bloco CIDR IPv6 para a rede (ex: fd12:ee::/64).

Este arquivo permite que as configurações de rede sejam facilmente modificadas e estendidas sem alterar o código principal do Terraform, seguindo o princípio de separação de variáveis de configuração.

environments/production/outputs.tf

Este arquivo define os valores de saída (outputs) que o Terraform pode expor após a aplicação da configuração. Outputs são úteis para extrair informações importantes da infraestrutura provisionada, que podem ser usadas por outros módulos, scripts ou para consulta manual.

1
2
3
output "gateway_ip" {
  value = module.compute.domains["gateway"].network_interface[0].addresses[0]
}

Explicação Detalhada:

  • output "gateway_ip" { ... }: Declara um output chamado gateway_ip.
  • value = module.compute.domains["gateway"].network_interface[0].addresses[0]: Define o valor do output. Neste caso, ele está extraindo o primeiro endereço IP (IPv4) da primeira interface de rede da VM chamada gateway que foi provisionada pelo módulo compute. Isso é útil para saber o endereço IP do gateway principal após o provisionamento.

Outputs são uma forma eficaz de tornar informações da infraestrutura acessíveis e reutilizáveis.

environments/production/providers.tf

Este arquivo configura os provedores Terraform necessários para o ambiente de produção. Ele especifica qual provedor será usado e qual versão é exigida, garantindo a compatibilidade e a consistência do ambiente.

1
2
3
4
5
6
7
8
9
10
11
12
13
terraform {
  required_version = ">= 1.5"
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "0.8.3"
    }
  }
}

provider "libvirt" {
  uri = "qemu:///system"
}

Explicação Detalhada:

  • terraform { ... }: Bloco de configuração global do Terraform.
    • required_version = ">= 1.5": Define a versão mínima do Terraform CLI necessária para executar esta configuração. Isso ajuda a evitar problemas de compatibilidade.
    • required_providers { ... }: Declara os provedores Terraform que esta configuração requer.
      • libvirt = { ... }: Define o provedor libvirt.
        • source = "dmacvicar/libvirt": Especifica a origem do provedor no Terraform Registry. Isso garante que o Terraform baixe o provedor correto.
        • version = "0.8.3": Fixa a versão do provedor libvirt a ser utilizada. É uma boa prática fixar as versões para garantir que as operações do Terraform sejam consistentes ao longo do tempo.
  • provider "libvirt" { ... }: Bloco de configuração específica para o provedor libvirt.
    • uri = "qemu:///system": Define o URI de conexão para o daemon Libvirt. qemu:///system é o URI padrão para conectar ao Libvirt como root, gerenciando VMs em todo o sistema. Certifique-se de que o usuário que executa o Terraform tenha as permissões adequadas para se conectar a este URI.

Este arquivo é fundamental para garantir que o Terraform possa interagir corretamente com o ambiente Libvirt e provisionar os recursos conforme o esperado.

environments/production/servers.auto.tfvars

Este arquivo define as especificações detalhadas de cada máquina virtual (VM) que será provisionada. Assim como networks.auto.tfvars, ele é carregado automaticamente pelo Terraform.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
servers = {
  gateway = {
    vcpus  = 2,
    memory = "2048",
    os     = "debian12",
    networks = [
      { name = "external", ipv4 = "dhcp", ipv6 = "dhcp", if_name = "ens3", is_default_gateway = true },
      { name = "dmz", ipv4 = "10.32.16.1", ipv4_prefix = 24, ipv6 = "fd00:32:16::1", ipv6_prefix = 64, if_name = "ens4", is_default_gateway = false },
      { name = "cgr", ipv4 = "10.48.32.1", ipv4_prefix = 24, ipv6 = "fd00:48:32::1", ipv6_prefix = 64, if_name = "ens5", is_default_gateway = false },
      { name = "dhcp", ipv4 = "10.128.112.1", ipv4_prefix = 24, ipv6 = "fd00:128:112::1", ipv6_prefix = 64, if_name = "ens6", is_default_gateway = false },
      { name = "public", ipv4 = "203.0.113.1", ipv4_prefix = 24, ipv6 = "2001:db8:feed::1", ipv6_prefix = 64, if_name = "ens7", is_default_gateway = false }
    ]
  },

  # Hosts da rede DMZ
  ns1 = {
    vcpus  = 2,
    memory = "2048",
    os     = "oracle9",
    networks = [
      { name = "dmz", ipv4 = "10.32.16.3", ipv4_prefix = 24, ipv6 = "fd00:32:16::3", ipv6_prefix = 64, if_name = "ens3", is_default_gateway = true },
      { name = "public", ipv4 = "203.0.113.3", ipv4_prefix = 24, ipv6 = "2001:db8:feed::3", ipv6_prefix = 64, if_name = "ens4", is_default_gateway = false }
    ]
  },

  ns2 = {
    vcpus  = 2,
    memory = "2048",
    os     = "oracle9",
    networks = [
      { name = "dmz", ipv4 = "10.32.16.4", ipv4_prefix = 24, ipv6 = "fd00:32:16::4", ipv6_prefix = 64, if_name = "ens3", is_default_gateway = true },
      { name = "public", ipv4 = "203.0.113.4", ipv4_prefix = 24, ipv6 = "2001:db8:feed::4", ipv6_prefix = 64, if_name = "ens4", is_default_gateway = true }
    ]
  },

  # Hosts da rede CGR
  cgr-linux = {
    vcpus    = 2,
    memory   = "2048",
    os       = "ubuntu24",
    networks = [{ name = "cgr", ipv4 = "10.48.32.2", ipv4_prefix = 24, ipv6 = "fd00:48:32::2", ipv6_prefix = 64, if_name = "ens3", is_default_gateway = true }]
  }
}

Explicação Detalhada:

  • servers = { ... }: Define um mapa de objetos, onde cada chave representa o nome lógico de uma VM (ex: gateway, ns1, ns2, cgr-linux).
  • Para cada VM, são definidos os seguintes atributos:
    • vcpus: Número de vCPUs alocadas para a VM.
    • memory: Quantidade de memória RAM alocada para a VM (em MB).
    • os: O nome do perfil do sistema operacional a ser usado, que corresponde a uma entrada em os_profiles definida em terraform.tfvars (ex: debian12, oracle9, ubuntu24). Este perfil determina a imagem base e os grupos padrão do usuário.
    • networks: Uma lista de objetos, onde cada objeto representa uma interface de rede para a VM. Para cada interface:
      • name: O nome da rede Libvirt à qual a interface será conectada (deve corresponder a uma rede definida em networks.auto.tfvars).
      • ipv4: Endereço IPv4 da interface. Pode ser dhcp para obter um endereço dinamicamente ou um endereço IP estático.
      • ipv4_prefix: (Opcional) Prefixo de rede IPv4 para endereços estáticos (ex: 24).
      • ipv6: Endereço IPv6 da interface. Pode ser dhcp ou um endereço IP estático.
      • ipv6_prefix: (Opcional) Prefixo de rede IPv6 para endereços estáticos (ex: 64).
      • if_name: O nome da interface de rede dentro da VM (ex: ens3, ens4).
      • is_default_gateway: (Opcional) Um booleano que indica se esta interface deve ser configurada como o gateway padrão da VM. Usado no template network_config.yml do Cloud-init.

Este arquivo é o coração da definição da infraestrutura de computação, permitindo a especificação detalhada de cada VM e suas conexões de rede.

environments/production/terraform.tfvars

Este arquivo contém valores para as variáveis de entrada do Terraform que são específicas para o ambiente de produção. Ele é o local ideal para armazenar configurações sensíveis (como chaves SSH) ou valores que variam entre ambientes (como pools de armazenamento e perfis de OS). O Terraform carrega automaticamente variáveis definidas neste arquivo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# Chave SSH usada para acessar as VMs
#terraform plan -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"
#terraform apply -var="ssh_public_key=$(cat ~/.ssh/kvm.pub)"
ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzaHM66H9EFpX/6aYcvFH85eHjyAsMVRk8DbfQhxxmI gean@inspiron"

# Pools
volume_pool      = "default"
base_volume_pool = "templates"

# Perfis de SO
os_profiles = {
  "debian12" = {
    template_name  = "debian-12-amd64.qcow2",
    default_groups = ["users", "sudo"]
  },
  "oracle9" = {
    template_name  = "ol9-amd64.qcow2",
    default_groups = ["users", "wheel"]
  }
  "ubuntu24" = {
    template_name  = "ubuntu-24-amd64.qcow2",
    default_groups = ["users", "sudo"]
  }
}

# Usuário padrão para todas as VMs
default_vm_user = {
  name  = "suporte"
  gecos = "Suporte User"
}

# Gateway das redes
network_dmz = {
  gateway_v4 = "10.32.16.1",
  gateway_v6 = "fd00:32:16::1"
}

network_cgr = {
  gateway_v4 = "10.48.32.1",
  gateway_v6 = "fd00:48:32::1"
}

# Defaults DNSs para as redes
defaults_dns = {
  ns1_v4 = "10.32.16.3",
  ns1_v6 = "fd00:32:16::3",
  ns2_v4 = "10.32.16.4",
  ns2_v6 = "fd00:32:16::4"
}

Explicação Detalhada:

  • ssh_public_key: Contém a chave SSH pública que será injetada nas VMs via Cloud-init. É crucial para o acesso seguro às máquinas. As linhas comentadas mostram como passar a chave diretamente via linha de comando, mas defini-la aqui é mais conveniente para o ambiente.
  • volume_pool: Define o pool de armazenamento Libvirt onde os volumes de disco das VMs serão criados (padrão: default).
  • base_volume_pool: Define o pool de armazenamento Libvirt onde as imagens base dos sistemas operacionais (templates) estão localizadas (padrão: templates).
  • os_profiles: Um mapa que define perfis para diferentes sistemas operacionais. Cada perfil especifica:
    • template_name: O nome do arquivo da imagem base do sistema operacional no pool base_volume_pool (ex: debian-12-amd64.qcow2).
    • default_groups: Uma lista de grupos padrão aos quais o usuário criado via Cloud-init será adicionado (ex: users, sudo, wheel).
  • default_vm_user: Define o usuário padrão que será criado em todas as VMs, a menos que seja sobrescrito por uma VM específica em servers.auto.tfvars.
    • name: Nome de usuário (ex: suporte).
    • gecos: Nome completo ou descrição do usuário (ex: Suporte User).
  • network_dmz, network_cgr: Mapas que definem os endereços IPv4 e IPv6 dos gateways para as redes DMZ e CGR, respectivamente. Usados na configuração de rotas via Cloud-init.
  • defaults_dns: Define os endereços IPv4 e IPv6 dos servidores DNS padrão para as redes. Usados na configuração de DNS via Cloud-init.

Este arquivo centraliza as configurações que podem mudar entre diferentes implantações ou ambientes, tornando o projeto mais flexível e fácil de gerenciar.

environments/production/variables.tf

Este arquivo declara as variáveis de entrada que são esperadas pela configuração Terraform no ambiente de produção. Ele define o nome, tipo e, opcionalmente, uma descrição e um valor padrão para cada variável. As variáveis declaradas aqui são preenchidas pelos arquivos .tfvars (como terraform.tfvars, networks.auto.tfvars, servers.auto.tfvars) ou passadas via linha de comando.

1
2
3
4
5
6
7
8
9
10
variable "servers" { type = map(any) }
variable "networks" { type = map(any) }
variable "ssh_public_key" { type = string }
variable "os_profiles" { type = map(any) }
variable "default_vm_user" { type = map(any) }
variable "network_dmz" { type = object(any) }
variable "network_cgr" { type = object(any) }
variable "defaults_dns" { type = object(any) }
variable "volume_pool" { type = string }
variable "base_volume_pool" { type = string }

Explicação Detalhada:

Cada bloco variable declara uma variável:

  • variable "servers" { type = map(any) }: Declara a variável servers, que é um mapa de qualquer tipo. Esta variável é preenchida pelo conteúdo de servers.auto.tfvars e contém as definições de cada VM.
  • variable "networks" { type = map(any) }: Declara a variável networks, um mapa de qualquer tipo. Preenchida por networks.auto.tfvars, contém as definições das redes.
  • variable "ssh_public_key" { type = string }: Declara a variável ssh_public_key, uma string que representa a chave SSH pública. Preenchida por terraform.tfvars.
  • variable "os_profiles" { type = map(any) }: Declara a variável os_profiles, um mapa de qualquer tipo. Preenchida por terraform.tfvars, contém os perfis dos sistemas operacionais.
  • variable "default_vm_user" { type = map(any) }: Declara a variável default_vm_user, um mapa de qualquer tipo. Preenchida por terraform.tfvars, contém as informações do usuário padrão para as VMs.
  • variable "network_dmz" { type = object(any) }: Declara a variável network_dmz, um objeto de qualquer tipo. Preenchida por terraform.tfvars, contém as informações do gateway da rede DMZ.
  • variable "network_cgr" { type = object(any) }: Declara a variável network_cgr, um objeto de qualquer tipo. Preenchida por terraform.tfvars, contém as informações do gateway da rede CGR.
  • variable "defaults_dns" { type = object(any) }: Declara a variável defaults_dns, um objeto de qualquer tipo. Preenchida por terraform.tfvars, contém as informações dos servidores DNS padrão.
  • variable "volume_pool" { type = string }: Declara a variável volume_pool, uma string. Preenchida por terraform.tfvars, define o pool de armazenamento para os volumes.
  • variable "base_volume_pool" { type = string }: Declara a variável base_volume_pool, uma string. Preenchida por terraform.tfvars, define o pool de armazenamento para as imagens base.

Este arquivo é essencial para documentar as entradas esperadas pela configuração e para permitir que o Terraform valide os tipos de dados fornecidos.

Módulos (Modules)

Os módulos Terraform são blocos de construção reutilizáveis que encapsulam um conjunto de recursos. Eles promovem a modularidade, a organização e a reutilização do código, permitindo que a infraestrutura seja definida de forma mais limpa e escalável.

Módulo compute

O módulo compute é responsável por provisionar as máquinas virtuais (domínios Libvirt) e integrar as configurações do Cloud-init para personalização inicial.

modules/compute/cloudinit.tf

Este arquivo define o recurso libvirt_cloudinit_disk, que é responsável por criar os discos de Cloud-init (arquivos ISO) que serão anexados às VMs. Esses discos contêm as configurações de rede e usuário geradas a partir dos templates user_data.yml e network_config.yml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
resource "libvirt_cloudinit_disk" "cloudinit" {
  for_each = var.servers

  name = "cloudinit-${each.key}.iso"
  pool = var.volume_pool 

  user_data = templatefile("${path.module}/../../cloud-init/user_data.yml", {
    hostname  = each.key
    user_name = coalesce(each.value.username, var.default_vm_user.name)
    gecos     = coalesce(each.value.gecos, var.default_vm_user.gecos)
    groups    = coalesce(each.value.groups, var.os_profiles[each.value.os].default_groups)
    ssh_key   = var.ssh_public_key
  })

  network_config = templatefile(
    "${path.module}/../../cloud-init/network_config.yml", 
    {
      hostname    = each.key
      interfaces  = each.value.networks
      network_gateways = {
        dmz = var.network_dmz,
        cgr = var.network_cgr
      }
      global_dns         = var.defaults_dns
      has_dhcp_interface = length([for iface in each.value.networks : iface if iface.ipv4 == "dhcp"]) > 0
    }
  )
}

Explicação Detalhada:

  • resource "libvirt_cloudinit_disk" "cloudinit" { ... }: Declara um recurso do tipo libvirt_cloudinit_disk com o nome local cloudinit.
  • for_each = var.servers: Este meta-argumento faz com que o Terraform crie uma instância deste recurso para cada entrada no mapa var.servers (que vem de servers.auto.tfvars). Isso significa que um disco Cloud-init será gerado para cada VM definida.
  • name = "cloudinit-${each.key}.iso": Define o nome do arquivo ISO do Cloud-init. each.key refere-se ao nome da VM (ex: gateway, ns1).
  • pool = var.volume_pool: Especifica o pool de armazenamento Libvirt onde o disco Cloud-init será criado.
  • user_data = templatefile(...): Gera o conteúdo do user_data a partir do template user_data.yml localizado em ../../cloud-init/user_data.yml. As variáveis passadas para o template são:
    • hostname: O nome da VM (each.key).
    • user_name, gecos, groups: Utiliza a função coalesce para usar o valor específico da VM (each.value.username, each.value.gecos, each.value.groups) se estiver definido, caso contrário, usa o valor padrão de var.default_vm_user ou var.os_profiles.
    • ssh_key: A chave SSH pública fornecida em var.ssh_public_key.
  • network_config = templatefile(...): Gera o conteúdo do network_config a partir do template network_config.yml localizado em ../../cloud-init/network_config.yml. As variáveis passadas para o template são:
    • hostname: O nome da VM (each.key).
    • interfaces: A lista de interfaces de rede definida para a VM (each.value.networks).
    • network_gateways: Um mapa contendo os gateways para as redes DMZ e CGR, vindos de var.network_dmz e var.network_cgr.
    • global_dns: Os servidores DNS globais definidos em var.defaults_dns.
    • has_dhcp_interface: Uma expressão booleana que verifica se alguma interface da VM está configurada para usar DHCP. Isso é usado no template network_config.yml para controlar a aplicação de configurações de DNS.

Este arquivo é a ponte entre as definições de infraestrutura do Terraform e a personalização interna das VMs via Cloud-init, garantindo que cada VM seja configurada de acordo com suas especificações.

modules/compute/main.tf

Este arquivo define o recurso principal do módulo compute: a máquina virtual Libvirt (libvirt_domain). Ele configura os parâmetros de hardware da VM e anexa os discos de sistema operacional e Cloud-init, além de configurar as interfaces de rede.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
resource "libvirt_domain" "domain" {
  for_each = var.servers

  name   = each.key
  memory = each.value.memory
  vcpu   = each.value.vcpus
  cpu    { mode = "host-passthrough" }

  depends_on = [
    var.network_resources
  ]

  cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id

  disk { volume_id = var.volumes[each.key].id }


  dynamic "network_interface" {
    for_each = each.value.networks
    content {
      network_name = network_interface.value.name
      wait_for_lease = network_interface.value.ipv4 == "dhcp" ? true : false
    }
  }

  console {
    type        = "pty"
    target_type = "serial"
    target_port = "0"
  }

  graphics {
    type        = "spice"
    listen_type = "address"
    autoport    = true
  }
}

Explicação Detalhada:

  • resource "libvirt_domain" "domain" { ... }: Declara um recurso do tipo libvirt_domain (máquina virtual) com o nome local domain.
  • for_each = var.servers: Assim como no cloudinit.tf, este meta-argumento garante que uma VM seja criada para cada servidor definido em var.servers.
  • name = each.key: Define o nome da VM no Libvirt, usando o nome lógico do servidor (ex: gateway, ns1).
  • memory = each.value.memory: Define a quantidade de memória RAM da VM, vinda da definição do servidor.
  • vcpu = each.value.vcpus: Define o número de vCPUs da VM, vindo da definição do servidor.
  • cpu { mode = "host-passthrough" }: Configura a CPU da VM para usar o modo host-passthrough, o que permite que a VM utilize as mesmas capacidades de CPU do host físico, otimizando o desempenho.
  • depends_on = [ var.network_resources ]: Esta é uma dependência explícita. Garante que as redes definidas no módulo network sejam criadas e estejam prontas antes que as VMs tentem se conectar a elas. Embora o Terraform geralmente infira dependências, em alguns casos, como este, uma dependência explícita pode ser útil para garantir a ordem correta.
  • cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id: Anexa o disco Cloud-init gerado pelo recurso libvirt_cloudinit_disk (definido em cloudinit.tf) à VM. O id refere-se ao identificador único do disco Cloud-init.
  • disk { volume_id = var.volumes[each.key].id }: Anexa o volume de disco do sistema operacional à VM. var.volumes é a saída do módulo storage, que contém os IDs dos volumes criados para cada servidor.
  • dynamic "network_interface" { ... }: Um bloco dynamic que itera sobre a lista de redes definida para cada servidor (each.value.networks). Isso permite criar múltiplas interfaces de rede para uma única VM.
    • network_name = network_interface.value.name: Define o nome da rede Libvirt à qual esta interface será conectada.
    • wait_for_lease = network_interface.value.ipv4 == "dhcp" ? true : false: Se a interface estiver configurada para DHCP, o Terraform aguardará a obtenção de um lease de IP antes de considerar a interface pronta. Isso é útil para garantir que a VM tenha conectividade de rede logo após o boot.
  • console { ... }: Configura um console serial para a VM, útil para depuração e acesso direto ao sistema operacional da VM.
    • type = "pty", target_type = "serial", target_port = "0": Configura um console serial via PTY (pseudo-terminal).
  • graphics { ... }: Configura a interface gráfica da VM, permitindo acesso via SPICE (Simple Protocol for Independent Computing Environments).
    • type = "spice", listen_type = "address", autoport = true: Habilita o acesso SPICE, permitindo que o Libvirt atribua uma porta automaticamente.

Este arquivo é o cerne do provisionamento das VMs, definindo suas características de hardware e conectividade, e integrando as configurações de inicialização via Cloud-init.

modules/compute/outputs.tf

Este arquivo define os valores de saída do módulo compute. Os outputs permitem que informações sobre as VMs provisionadas sejam acessadas por outros módulos ou pelo ambiente raiz do Terraform.

1
2
3
output "domains" {
  value = libvirt_domain.domain
}

Explicação Detalhada:

  • output "domains" { ... }: Declara um output chamado domains.
  • value = libvirt_domain.domain: O valor deste output é o mapa completo dos recursos libvirt_domain criados por este módulo. Isso significa que todas as propriedades das VMs (como IPs, nomes, interfaces de rede, etc.) estarão disponíveis para serem referenciadas em outras partes da configuração Terraform (como no outputs.tf do ambiente de produção, que extrai o IP do gateway).

Este output é fundamental para permitir a interconexão entre os módulos e a extração de informações importantes da infraestrutura de computação.

modules/compute/variables.tf

Este arquivo declara as variáveis de entrada esperadas pelo módulo compute. Ele define a estrutura e os tipos de dados das informações que o módulo precisa para criar as máquinas virtuais e configurar o Cloud-init.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
variable "servers" {
  type = map(object({
    username = optional(string)   
    gecos    = optional(string)  
    groups   = optional(list(string))
    os       = string               
    vcpus    = number
    memory   = string
    networks = list(object({
      name        = string
      ipv4        = string
      ipv4_prefix = optional(number)
      ipv6        = string
      ipv6_prefix = optional(number)
      if_name     = string
      is_default_gateway = optional(bool)
    }))
  }))
}

variable "default_vm_user" {
  type = object({
    name  = string
    gecos = string
  })
}

variable "os_profiles" {
  type = map(object({
    default_groups = list(string)
  }))
}

variable "ssh_public_key" {
  type = string
  sensitive = true
}

# Declaração das variáveis de gateway
variable "network_dmz" {
  type = object({
    gateway_v4 = string
    gateway_v6 = string
  })
}

variable "network_cgr" {
  type = object({
    gateway_v4 = string
    gateway_v6 = string
  })
}

variable "defaults_dns" {
  description = "Servidores DNS padrão para todas as redes (IPv4/IPv6)."
  type        = object({ ns1_v4 = string, ns1_v6 = string, ns2_v4 = string, ns2_v6 = string })
}

# Adicione esta nova variável
variable "volumes" {
  description = "Mapa de volumes criados pelo módulo de storage"
  type = map(object({
    id = string
  }))
}

variable "volume_pool" {
  description = "Pool de armazenamento para volumes criados"
  type        = string
  default     = "default"
}

variable "network_resources" {
  description = "Mapa de recursos de rede para dependências"
  type        = any
}

Explicação Detalhada:

  • variable "servers": Define a estrutura esperada para a variável servers, que é um mapa de objetos. Cada objeto representa uma VM e inclui detalhes como username, gecos, groups, os, vcpus, memory e uma lista de networks. A estrutura aninhada para networks detalha os atributos de cada interface de rede (nome, IPs, prefixos, nome da interface e se é gateway padrão).
    • optional(string): Indica que o atributo é opcional.
  • variable "default_vm_user": Define a estrutura para o objeto de usuário padrão, com name e gecos.
  • variable "os_profiles": Define a estrutura para os perfis de sistema operacional, incluindo default_groups.
  • variable "ssh_public_key": Uma string para a chave SSH pública, marcada como sensitive = true para evitar que seu valor seja exibido em logs do Terraform.
  • variable "network_dmz" e variable "network_cgr": Objetos que definem os gateways IPv4 e IPv6 para as redes DMZ e CGR.
  • variable "defaults_dns": Objeto que define os servidores DNS IPv4 e IPv6 padrão.
  • variable "volumes": Um mapa de objetos que espera o ID dos volumes criados pelo módulo storage.
  • variable "volume_pool": Uma string para o pool de armazenamento dos volumes, com um valor padrão de default.
  • variable "network_resources": Uma variável de tipo any para receber o mapa de recursos de rede do módulo network, usada para dependências explícitas.

Este arquivo é crucial para a validação de entrada do módulo compute, garantindo que os dados fornecidos estejam no formato correto e contenham todas as informações necessárias para o provisionamento das VMs.

modules/compute/versions.tf

Este arquivo especifica as versões dos provedores Terraform exigidas pelo módulo compute. É uma boa prática incluir este arquivo em cada módulo para garantir que o módulo seja executado com as versões corretas dos provedores, evitando problemas de compatibilidade.

1
2
3
4
5
6
7
8
terraform {
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "0.8.3" # Use a mesma versão da environments
    }
  }
}

Explicação Detalhada:

  • terraform { ... }: Bloco de configuração global do Terraform para este módulo.
    • required_providers { ... }: Declara os provedores necessários.
      • libvirt = { ... }: Especifica o provedor libvirt.
        • source = "dmacvicar/libvirt": Origem do provedor no Terraform Registry.
        • version = "0.8.3": Versão exata do provedor libvirt que este módulo requer. A nota # Use a mesma versão da environments é um lembrete importante para manter a consistência entre o ambiente raiz e os módulos.

Manter as versões dos provedores fixas e consistentes em todo o projeto é fundamental para garantir a reprodutibilidade e evitar comportamentos inesperados devido a atualizações de provedores.

Módulo network

O módulo network é responsável por provisionar as redes virtuais no ambiente Libvirt. Ele cria e configura as redes que serão utilizadas pelas máquinas virtuais.

modules/network/main.tf

Este arquivo define o recurso libvirt_network, que cria as redes virtuais no Libvirt com base nas configurações fornecidas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource "libvirt_network" "network" {
  for_each  = var.networks

  name      = each.value.net_name
  mode      = each.value.net_mode
  autostart = true

  dynamic "dhcp" {
    for_each = each.value.net_mode != "none" ? [1] : []
    content { enabled = true }
  }

  addresses = each.value.net_mode != "none" ? [
    each.value.ipv4_cidr,
    each.value.ipv6_cidr
  ] : null
}

Explicação Detalhada:

  • resource "libvirt_network" "network" { ... }: Declara um recurso do tipo libvirt_network com o nome local network.
  • for_each = var.networks: Este meta-argumento faz com que o Terraform crie uma instância deste recurso para cada entrada no mapa var.networks (que vem de networks.auto.tfvars). Isso significa que uma rede Libvirt será criada para cada definição de rede.
  • name = each.value.net_name: Define o nome da rede no Libvirt, usando o net_name especificado na definição da rede (ex: external, dmz).
  • mode = each.value.net_mode: Define o modo de operação da rede (ex: nat, none).
  • autostart = true: Configura a rede para iniciar automaticamente quando o host Libvirt for inicializado.
  • dynamic "dhcp" { ... }: Um bloco dynamic que condicionalmente cria um bloco dhcp dentro da definição da rede. Este bloco é criado apenas se o net_mode da rede não for none (ou seja, se for uma rede que precisa de DHCP, como nat).
    • for_each = each.value.net_mode != "none" ? [1] : []: A expressão each.value.net_mode != "none" ? [1] : [] retorna uma lista contendo um único elemento [1] se o modo da rede não for none, e uma lista vazia [] caso contrário. Isso efetivamente cria o bloco dhcp apenas quando necessário.
    • content { enabled = true }: Habilita o servidor DHCP para a rede.
  • addresses = each.value.net_mode != "none" ? [ ... ] : null: Define os blocos de endereços IPv4 e IPv6 para a rede. Os endereços são definidos apenas se o net_mode não for none. Caso contrário, null é atribuído, indicando que a rede não terá blocos de endereços gerenciados pelo Libvirt (útil para redes isoladas sem DHCP).

Este arquivo é a base para a criação e configuração das redes virtuais, permitindo a definição flexível de diferentes tipos de redes (com ou sem NAT, com ou sem DHCP) de forma automatizada.

modules/network/outputs.tf

Este arquivo define os valores de saída do módulo network. O output networks expõe as informações das redes virtuais criadas, permitindo que outros módulos (como o módulo compute) referenciem essas redes.

1
2
3
output "networks" {
  value = libvirt_network.network
}

Explicação Detalhada:

  • output "networks" { ... }: Declara um output chamado networks.
  • value = libvirt_network.network: O valor deste output é o mapa completo dos recursos libvirt_network criados por este módulo. Isso inclui todas as propriedades das redes (nome, modo, CIDRs, etc.), que podem ser usadas como dependências ou para extrair informações em outras partes da configuração Terraform.

Este output é essencial para a interconexão entre os módulos, garantindo que o módulo compute possa se referir às redes criadas pelo módulo network ao configurar as interfaces das VMs.

modules/network/variables.tf

Este arquivo declara as variáveis de entrada esperadas pelo módulo network. Ele define a estrutura e os tipos de dados das informações que o módulo precisa para criar as redes virtuais.

1
2
3
4
5
6
7
8
variable "networks" {
  type = map(object({
    net_name  = string
    net_mode  = string
    ipv4_cidr = string
    ipv6_cidr = string
  }))
}

Explicação Detalhada:

  • variable "networks": Declara a variável networks, que é um mapa de objetos. Cada objeto representa uma rede e inclui os seguintes atributos:
    • net_name: O nome da rede (string).
    • net_mode: O modo de operação da rede (string, ex: nat, none).
    • ipv4_cidr: O bloco CIDR IPv4 da rede (string).
    • ipv6_cidr: O bloco CIDR IPv6 da rede (string).

Este arquivo garante que os dados de entrada para o módulo network estejam no formato correto, permitindo que o módulo crie as redes de forma consistente.

modules/network/versions.tf

Similar ao módulo compute, este arquivo especifica as versões dos provedores Terraform exigidas pelo módulo network, garantindo a consistência e compatibilidade.

1
2
3
4
5
6
7
8
terraform {
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "0.8.3" # Use a mesma versão da environments
    }
  }
}

Explicação Detalhada:

  • Este arquivo é idêntico ao modules/compute/versions.tf, garantindo que ambos os módulos utilizem a mesma versão do provedor libvirt que o ambiente raiz. Isso é crucial para evitar problemas de compatibilidade e garantir que o comportamento do Terraform seja previsível em todo o projeto.

Módulo storage

O módulo storage é responsável por provisionar os volumes de disco para as máquinas virtuais no ambiente Libvirt. Ele utiliza imagens base (templates) para criar os discos das VMs.

modules/storage/main.tf

Este arquivo define o recurso libvirt_volume, que cria os volumes de disco para cada VM, baseando-se em uma imagem de template.

1
2
3
4
5
6
7
8
resource "libvirt_volume" "os_image" {
  for_each         = var.servers
  name             = "${each.key}.qcow2"
  pool             = var.volume_pool
  base_volume_name = var.os_profiles[each.value.os].template_name
  base_volume_pool = var.base_volume_pool
  format           = "qcow2"
}

Explicação Detalhada:

  • resource "libvirt_volume" "os_image" { ... }: Declara um recurso do tipo libvirt_volume com o nome local os_image.
  • for_each = var.servers: Cria um volume para cada servidor definido em var.servers.
  • name = "${each.key}.qcow2": Define o nome do volume de disco, utilizando o nome da VM (ex: gateway.qcow2).
  • pool = var.volume_pool: Especifica o pool de armazenamento Libvirt onde o novo volume será criado (ex: default).
  • base_volume_name = var.os_profiles[each.value.os].template_name: Define o nome da imagem base (template) a ser utilizada para criar o volume. O nome do template é obtido do perfil do sistema operacional (os_profiles) correspondente ao OS da VM (each.value.os).
  • base_volume_pool = var.base_volume_pool: Especifica o pool de armazenamento onde a imagem base (template) está localizada (ex: templates).
  • format = "qcow2": Define o formato do volume de disco como QCOW2.

Este arquivo automatiza a criação de discos para as VMs, garantindo que cada máquina tenha seu próprio volume de sistema operacional baseado em um template pré-existente.

modules/storage/outputs.tf

Este arquivo define os valores de saída do módulo storage. O output volumes expõe as informações dos volumes de disco criados, permitindo que outros módulos (como o módulo compute) referenciem esses volumes.

1
2
3
output "volumes" {
  value = libvirt_volume.os_image
}

Explicação Detalhada:

  • output "volumes" { ... }: Declara um output chamado volumes.
  • value = libvirt_volume.os_image: O valor deste output é o mapa completo dos recursos libvirt_volume criados por este módulo. Isso inclui todas as propriedades dos volumes (nome, ID, pool, etc.), que podem ser usadas como dependências ou para extrair informações em outras partes da configuração Terraform.

Este output é essencial para a interconexão entre os módulos, garantindo que o módulo compute possa se referir aos volumes criados pelo módulo storage ao configurar os discos das VMs.

modules/storage/variables.tf

Este arquivo declara as variáveis de entrada esperadas pelo módulo storage. Ele define a estrutura e os tipos de dados das informações que o módulo precisa para criar os volumes de disco.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
variable "servers" {
  type = map(object({
    os = string
  }))
}

variable "os_profiles" {
  type = map(object({
    template_name = string
  }))
}

variable "volume_pool" {
  description = "Pool de armazenamento para volumes criados"
  type        = string
  default     = "default"
}

variable "base_volume_pool" {
  description = "Pool de armazenamento para templates base"
  type        = string
  default     = "templates"
}

Explicação Detalhada:

  • variable "servers": Declara a variável servers, que é um mapa de objetos. Para o módulo storage, apenas o atributo os de cada servidor é relevante para determinar qual template de OS usar.
  • variable "os_profiles": Declara a variável os_profiles, um mapa de objetos. Cada objeto de perfil de OS deve conter o template_name da imagem base.
  • variable "volume_pool": Uma string para o pool de armazenamento onde os novos volumes serão criados, com um valor padrão de default.
  • variable "base_volume_pool": Uma string para o pool de armazenamento onde as imagens base (templates) estão localizadas, com um valor padrão de templates.

Este arquivo garante que os dados de entrada para o módulo storage estejam no formato correto, permitindo que o módulo crie os volumes de forma consistente.

modules/storage/versions.tf

Similar aos outros módulos, este arquivo especifica as versões dos provedores Terraform exigidas pelo módulo storage, garantindo a consistência e compatibilidade.

1
2
3
4
5
6
7
8
terraform {
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "0.8.3" # Use a mesma versão da environments
    }
  }
}

Explicação Detalhada:

  • Este arquivo é idêntico aos versions.tf dos módulos compute e network, garantindo que todos os módulos utilizem a mesma versão do provedor libvirt que o ambiente raiz. Isso é crucial para evitar problemas de compatibilidade e garantir que o comportamento do Terraform seja previsível em todo o projeto.

Como Usar o Projeto

Para utilizar este projeto e provisionar suas próprias máquinas virtuais, siga os passos abaixo:

1. Pré-requisitos

Certifique-se de ter todos os pré-requisitos instalados e configurados conforme detalhado na seção Pré-requisitos. Isso inclui Terraform, Libvirt, e as imagens QCOW2 necessárias nos pools de armazenamento corretos.

2. Clonar o Repositório (ou Estruturar os Arquivos)

Se este projeto estivesse em um repositório Git, o primeiro passo seria cloná-lo. Como você recebeu os arquivos, certifique-se de que a estrutura de diretórios esteja organizada exatamente como mostrado na seção Estrutura do Projeto.

1
2
3
4
5
6
7
mkdir -p cloud-init environments/production modules/compute modules/network modules/storage

# Criar os arquivos dentro de cada diretório com o conteúdo fornecido
# Exemplo: 
# touch cloud-init/network_config.yml
# touch cloud-init/user_data.yml
# ... e preencher com o conteúdo correspondente

3. Configurar a Chave SSH Pública

Edite o arquivo environments/production/terraform.tfvars e substitua o valor da variável ssh_public_key pela sua própria chave SSH pública. Esta chave será usada para acessar as VMs após o provisionamento.

1
2
# environments/production/terraform.tfvars
ssh_public_key = "<SUA_CHAVE_SSH_PUBLICA_AQUI>"

4. Inicializar o Terraform

Navegue até o diretório do ambiente de produção (environments/production) e inicialize o Terraform. Este comando baixará os provedores necessários (neste caso, o provedor libvirt).

1
2
cd environments/production
terraform init

5. Validar a Configuração

Após a inicialização, é uma boa prática validar a configuração para verificar se há erros de sintaxe ou lógica.

1
terraform validate

6. Planejar a Execução

Execute o comando terraform plan para ver um resumo das ações que o Terraform realizará (quais recursos serão criados, modificados ou destruídos) sem realmente aplicá-las. Isso é crucial para revisar as mudanças antes de aplicá-las.

1
terraform plan

7. Aplicar a Configuração

Se o plano estiver de acordo com o esperado, aplique a configuração. O Terraform começará a provisionar as redes, volumes e máquinas virtuais no seu ambiente Libvirt.

1
terraform apply

O Terraform solicitará uma confirmação antes de prosseguir. Digite yes e pressione Enter.

8. Acessar as VMs

Após a conclusão do terraform apply, as VMs estarão em execução. Você pode obter o endereço IP do gateway (e de outras VMs, se configurado nos outputs) usando o comando terraform output.

1
terraform output gateway_ip

Com o IP, você pode acessar a VM via SSH usando o usuário configurado no Cloud-init (ex: suporte) e a chave SSH que você forneceu.

1
ssh suporte@<IP_DO_GATEWAY>

9. Destruir a Infraestrutura (Opcional)

Quando não precisar mais da infraestrutura, você pode destruí-la completamente usando o comando terraform destroy. Isso removerá todas as VMs, volumes e redes criadas pelo Terraform.

1
terraform destroy

O Terraform solicitará uma confirmação antes de prosseguir. Digite yes e pressione Enter.

Este guia passo a passo deve permitir que você utilize o projeto para provisionar e gerenciar suas VMs de forma eficiente e automatizada.

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