Provisionamento de Infraestrutura KVM com Terraform e Módulos Reutilizáveis
Introdução
No cenário atual de infraestrutura como código (IaC), a automação é fundamental para garantir agilidade, consistência e escalabilidade. Este tutorial foi desenvolvido para administradores de sistemas, engenheiros de DevOps e entusiastas de virtualização que desejam automatizar o provisionamento de máquinas virtuais KVM utilizando o Terraform. Abordaremos desde a configuração inicial do ambiente até a implantação e gerenciamento de VMs, com ênfase na criação de módulos Terraform reutilizáveis para otimizar seu fluxo de trabalho.
Ao final deste guia, você será capaz de:
- Configurar seu ambiente com KVM e Terraform.
- Compreender a estrutura de um projeto Terraform modular para KVM.
- Automatizar a criação de redes e máquinas virtuais.
- Utilizar o Cloud-Init para personalização inicial das VMs.
Estrutura do Projeto
O projeto Terraform para provisionamento de VMs KVM é organizado de forma modular para promover a reusabilidade e a clareza. Abaixo, detalhamos a estrutura de diretórios e a finalidade de cada arquivo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── .gitignore # Regras para ignorar arquivos sensíveis e temporários, garantindo que credenciais e estados do Terraform não sejam versionados.
├── main.tf # O arquivo de configuração principal do Terraform. Ele orquestra a criação dos recursos, chamando módulos e definindo a lógica de alto nível da infraestrutura.
├── modules # Diretório que contém os módulos Terraform reutilizáveis. Cada subdiretório aqui representa um módulo independente.
│ └── vm # **Módulo VM**: Este módulo é responsável por encapsular toda a lógica e os recursos necessários para provisionar uma única máquina virtual KVM, incluindo discos, cloud-init e a definição do domínio KVM.
│ ├── cloud-init # Contém os templates do Cloud-Init (`.tpl`) que serão usados para personalizar as VMs no primeiro boot, configurando usuários, SSH e rede.
│ │ ├── network_config.yml.tpl # Template Cloud-Init para configuração de rede da VM.
│ │ └── user_data.yml.tpl # Template Cloud-Init para configuração de usuário, chaves SSH e comandos de inicialização da VM.
│ ├── main.tf # Define os recursos Terraform específicos para o módulo VM (ex: `libvirt_cloudinit_disk`, `libvirt_volume`, `libvirt_domain`).
│ ├── outputs.tf # Declara os valores de saída do módulo VM (ex: IPs das VMs, nomes de domínio) que podem ser referenciados pelo módulo pai ou pelo usuário.
│ ├── variables.tf # Define as variáveis de entrada para o módulo VM, permitindo que ele seja configurado de forma flexível.
│ └── versions.tf # Especifica as versões mínimas e máximas dos provedores Terraform que este módulo requer, garantindo compatibilidade.
├── network.tf # Define os recursos de rede Libvirt, como a rede virtual que as VMs utilizarão para comunicação.
├── provider.tf # Configura os provedores Terraform necessários para o projeto (neste caso, o provedor `libvirt`).
├── terraform.tfvars # Arquivo para armazenar valores de variáveis sensíveis ou específicos do ambiente (ex: chaves SSH, configurações de VM). **Este arquivo não deve ser versionado!**
├── terraform.tfvars.example # Um exemplo do arquivo `terraform.tfvars`, mostrando a estrutura e os tipos de valores esperados, sem conter dados sensíveis.
└── variables.tf # Declara as variáveis globais do projeto, que podem ser usadas em `main.tf`, `network.tf` e passadas para os módulos.
Entendendo os Módulos Terraform:
Os módulos Terraform são contêineres para múltiplas configurações de recursos que são usadas em conjunto. Eles permitem que você organize seu código, encapsule a complexidade e crie componentes reutilizáveis. No nosso caso, o módulo vm
abstrai a criação de uma máquina virtual KVM, permitindo que o main.tf
simplesmente “chame” esse módulo várias vezes para provisionar múltiplas VMs com configurações diferentes, sem duplicar código.
1. Configuração dos Arquivos Terraform
Nesta seção, exploraremos os principais arquivos de configuração do Terraform que compõem nosso projeto. Cada arquivo desempenha um papel crucial na definição e orquestração da nossa infraestrutura KVM.
1.1. provider.tf
O arquivo provider.tf
é onde configuramos os provedores Terraform que serão utilizados em nosso projeto. Provedores são plugins que o Terraform usa para interagir com APIs de diferentes serviços de infraestrutura (neste caso, o libvirt
para KVM). É crucial definir o provedor corretamente para que o Terraform possa se comunicar com o ambiente KVM.
1
2
3
4
5
6
7
8
9
10
11
12
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt" # Define a origem do provedor, indicando onde o Terraform deve baixá-lo.
version = "0.8.3" # Especifica a versão exata do provedor a ser utilizada, garantindo compatibilidade e reprodutibilidade.
}
}
}
provider "libvirt" {
uri = var.libvirt_uri # Define o URI de conexão para o daemon libvirtd. Este valor é uma variável para flexibilidade.
}
Explicação Detalhada:
terraform { required_providers { ... } }
: Este bloco é fundamental para o Terraform. Ele declara quais provedores são necessários para o projeto e de onde eles devem ser obtidos. Ao especificar asource
e aversion
, garantimos que o Terraform baixe e utilize a versão correta do provedorlibvirt
, evitando problemas de compatibilidade.provider "libvirt" { ... }
: Este bloco configura o provedorlibvirt
em si. A propriedadeuri
é essencial, pois informa ao provedor como se conectar ao seu ambiente KVM. Utilizamosvar.libvirt_uri
para tornar este valor configurável através de variáveis, permitindo que você altere o URI de conexão sem modificar o código do provedor. O valor padrão paralibvirt_uri
será definido emvariables.tf
.
1.2. variables.tf
O arquivo variables.tf
é onde declaramos as variáveis de entrada para o nosso projeto Terraform. Variáveis permitem que você personalize o comportamento do seu código Terraform sem precisar modificar o código diretamente, tornando-o mais flexível e reutilizável. É uma prática recomendada para desacoplar a configuração do código, facilitando a gestão de diferentes ambientes (desenvolvimento, produção, etc.).
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
variable "libvirt_uri" {
type = string
default = "qemu:///system" # Valor padrão para a URI de conexão, útil para ambientes de desenvolvimento local.
description = "URI de conexão para o daemon libvirtd. O padrão 'qemu:///system' conecta-se ao sistema KVM local."
}
variable "network_name" {
type = string
default = "tfnet" # Nome padrão para a rede virtual, pode ser sobrescrito.
description = "Nome da rede virtual Libvirt a ser criada."
}
variable "network_mode" {
type = string
default = "nat" # Modo NAT é comum para VMs que precisam de acesso à internet mas não de IPs públicos.
description = "Modo de operação da rede Libvirt (ex: 'nat' para NAT, 'bridge' para bridge)."
}
variable "network_addresses" {
type = string
default = "10.64.0.0/24" # Define o bloco de IPs para a rede virtual.
description = "Endereço IP e máscara de sub-rede para a rede virtual Libvirt."
}
variable "ssh_public_key" {
description = "Conteúdo da chave SSH pública para acesso às VMs. É recomendado que este valor seja fornecido via terraform.tfvars ou variáveis de ambiente por ser sensível." # Importante para segurança, não deve ser hardcoded.
type = string
sensitive = true # Marca a variável como sensível, ocultando seu valor em logs e saídas do Terraform.
}
variable "vms" {
type = map(object({
username = string
gecos = string
groups = list(string)
network_ip = string
nameserver_ip = string
route_ip = string
memory = number
vcpu = number
os_image_url = string
}))
sensitive = false # Este mapa contém configurações, mas não dados sensíveis diretamente.
description = "Um mapa de configurações para cada máquina virtual a ser provisionada. A chave do mapa é o hostname da VM." # Permite a criação de múltiplas VMs com configurações distintas de forma dinâmica.
validation {
condition = length(var.vms) == length(distinct(keys(var.vms))) # Garante que todos os hostnames sejam únicos para evitar conflitos.
error_message = "Todos os hostnames devem ser únicos."
}
validation {
condition = alltrue([for k, v in var.vms : can(regex("^[a-z0-9-]{1,63}$", k))]) # Valida o formato do hostname de acordo com padrões de DNS.
error_message = "Hostname deve conter apenas letras minúsculas, números e hifens, com até 63 caracteres."
}
validation {
condition = alltrue([
for k, v in var.vms :
can(regex("^\\d+\\.\\d+\\.\\d+\\.\\d+$", v.network_ip))
])
error_message = "Endereços IP devem estar no formato IPv4 válido." # Assegura que os IPs fornecidos são válidos.
}
validation {
condition = alltrue([
for k, v in var.vms :
v.memory >= 512 && v.memory <= 65536
])
error_message = "Memória deve estar entre 512MB e 64GB." # Define um range aceitável para a memória da VM.
}
}
Explicação Detalhada:
Cada bloco variable
define uma variável de entrada. As propriedades comuns incluem:
description
: Uma descrição clara e concisa do propósito da variável, essencial para a documentação do seu código Terraform.type
: O tipo de dado esperado para a variável (ex:string
,number
,list
,map
,object
). Definir o tipo ajuda o Terraform a validar a entrada e evita erros inesperados.default
: Um valor padrão para a variável, caso não seja fornecido explicitamente. Isso torna as variáveis opcionais e simplifica o uso para configurações comuns.sensitive
: Setrue
, o valor da variável será ocultado na saída do Terraform para proteger informações sensíveis (como chaves SSH, senhas, etc.). Isso é uma medida de segurança crucial.validation
: Blocos opcionais para definir regras de validação para os valores das variáveis. As validações garantem que os dados fornecidos estejam no formato ou intervalo esperado, prevenindo erros antes mesmo do provisionamento da infraestrutura. Por exemplo, validamos o formato de IPs e o range de memória para as VMs.
1.3. main.tf
O arquivo main.tf
é o ponto de entrada principal do nosso projeto Terraform. Ele é responsável por orquestrar a criação dos recursos, chamando os módulos definidos e conectando as diferentes partes da nossa infraestrutura. É aqui que a modularidade do nosso projeto se torna evidente, pois em vez de definir cada VM individualmente, nós simplesmente invocamos o módulo vm
e passamos as configurações necessárias.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module "vm_cluster" {
source = "./modules/vm"
# Passa o mapa de configurações de VMs (definido em variables.tf) para o módulo 'vm'.
# O módulo irá iterar sobre este mapa para criar múltiplas VMs.
vm_configs = var.vms
# Conecta as VMs à rede virtual Libvirt criada em network.tf.
# O 'id' é uma saída do recurso 'libvirt_network.tf_network'.
network_id = libvirt_network.tf_network.id
# Passa a chave SSH pública para o módulo 'vm'.
# Esta chave será usada pelo Cloud-Init para configurar o acesso SSH às VMs.
ssh_public_key = var.ssh_public_key
}
Explicação Detalhada:
module "vm_cluster" { ... }
: Este bloco declara uma instância do módulo Terraform. O nomevm_cluster
é um identificador lógico que você atribui a esta invocação específica do módulo. Isso é útil quando você precisa referenciar as saídas ou o estado deste conjunto de VMs.source = "./modules/vm"
: Este argumento crucial informa ao Terraform onde encontrar o código do módulo. No nosso caso,"./modules/vm"
aponta para um diretório local chamadovm
dentro da pastamodules
. Isso significa que estamos usando um módulo local, o que é ideal para encapsular a lógica específica do nosso projeto.vm_configs = var.vms
: Esta linha demonstra como as variáveis são passadas para um módulo.var.vms
refere-se à variávelvms
declarada no arquivovariables.tf
do projeto raiz. O Terraform pega o valor dessa variável (que é um mapa de configurações para cada VM) e o atribui à variável de entradavm_configs
dentro do módulovm
. O módulovm
então usa este mapa para criar dinamicamente várias máquinas virtuais.network_id = libvirt_network.tf_network.id
: Aqui, estamos passando o ID da rede virtual Libvirt que foi criada no arquivonetwork.tf
.libvirt_network.tf_network.id
é uma referência a um atributo de saída do recursolibvirt_network
nomeadotf_network
. Isso garante que todas as VMs criadas por este módulo sejam anexadas à rede correta.ssh_public_key = var.ssh_public_key
: Similar avm_configs
, esta linha passa o valor da variávelssh_public_key
(dovariables.tf
raiz) para o módulovm
. O módulovm
utilizará esta chave para configurar o acesso SSH às VMs através do Cloud-Init, permitindo que você se conecte a elas após o provisionamento.
1.4. network.tf
O arquivo network.tf
é dedicado à definição da rede virtual Libvirt que será utilizada pelas máquinas virtuais. Uma rede virtual é essencial para permitir a comunicação entre as VMs e, dependendo da configuração, com a rede externa. A configuração da rede é um passo crucial para garantir que suas VMs possam se comunicar entre si e com o mundo exterior, se necessário.
1
2
3
4
5
resource "libvirt_network" "tf_network" {
name = var.network_name # Define o nome da rede virtual, obtido da variável global `network_name`.
mode = var.network_mode # Especifica o modo de operação da rede (e.g., 'nat', 'bridge'), também configurável via variável.
addresses = [var.network_addresses] # Define o bloco de endereços IP para a rede, permitindo a personalização da sub-rede.
}
Explicação Detalhada:
resource "libvirt_network" "tf_network" { ... }
: Este bloco declara um recurso do tipolibvirt_network
, que representa uma rede virtual gerenciada pelo Libvirt. O nometf_network
é um identificador lógico que o Terraform usa para referenciar esta rede dentro do seu código.name = var.network_name
: Atribui um nome à rede virtual. Este nome é importante para identificar a rede no Libvirt e pode ser configurado através da variávelnetwork_name
(definida emvariables.tf
), que por padrão étfnet
.mode = var.network_mode
: Define o modo de operação da rede. O modonat
(Network Address Translation) é o mais comum para ambientes de teste e desenvolvimento, pois permite que as VMs acessem a internet usando o IP do host, enquanto as isola da rede física principal. Outras opções incluembridge
para integração direta com a rede física.addresses = [var.network_addresses]
: Especifica o bloco de endereços IP que será usado pela rede virtual. O valor é obtido da variávelnetwork_addresses
(por padrão10.64.0.0/24
), que define a sub-rede e a máscara. O Terraform usará essa informação para configurar o servidor DHCP e o roteamento para as VMs conectadas a esta rede.### 1.5.modules/vm/main.tf
O arquivo main.tf
dentro do módulo vm
é o coração da definição da máquina virtual. Ele contém os recursos Terraform que efetivamente criam e configuram a VM no ambiente KVM. Este arquivo é crucial para entender como cada componente da VM é provisionado e interconectado.
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
resource "libvirt_cloudinit_disk" "cloudinit" {
for_each = var.vm_configs # Cria um disco Cloud-Init para cada VM definida no mapa `vm_configs`.
name = "cloudinit-${each.key}.iso" # Nome do arquivo ISO do Cloud-Init, usando o hostname da VM para unicidade.
pool = "default" # Define o pool de armazenamento onde o disco Cloud-Init será criado.
user_data = templatefile("${path.module}/cloud-init/user_data.yml.tpl", { # Renderiza o template user_data.yml.tpl com variáveis específicas da VM.
hostname = each.key
user_name = each.value.username
gecos = each.value.gecos
groups = each.value.groups
ssh_key = var.ssh_public_key
})
network_config = templatefile("${path.module}/cloud-init/network_config.yml.tpl", { # Renderiza o template network_config.yml.tpl com variáveis de rede da VM.
network_ip = each.value.network_ip
nameserver_ip = each.value.nameserver_ip
route_ip = each.value.route_ip
})
}
resource "libvirt_volume" "os_image" {
for_each = var.vm_configs # Cria um volume de disco para cada VM.
name = "base-${each.key}.qcow2" # Nome do volume de disco, usando o hostname da VM.
pool = "default" # Pool de armazenamento para a imagem do sistema operacional.
source = each.value.os_image_url # URL da imagem QCOW2 base a ser copiada para o volume.
format = "qcow2" # Formato da imagem do disco.
}
resource "libvirt_domain" "domain" {
for_each = var.vm_configs # Cria um domínio Libvirt (VM) para cada configuração de VM.
name = each.key # Nome da VM (hostname).
memory = each.value.memory # Memória RAM alocada para a VM.
vcpu = each.value.vcpu # Número de vCPUs alocadas para a VM.
cpu {
mode = "host-passthrough" # Configura a CPU da VM para usar as capacidades do host, otimizando o desempenho.
}
cloudinit = libvirt_cloudinit_disk.cloudinit[each.key].id # Associa o disco Cloud-Init à VM.
network_interface {
network_id = var.network_id # Conecta a VM à rede virtual definida no módulo pai.
}
disk {
volume_id = libvirt_volume.os_image[each.key].id # Anexa o volume de disco da imagem do SO à VM.
}
console {
type = "pty" # Tipo de console (pseudo-terminal).
target_port = "0" # Porta alvo para o console.
target_type = "virtio" # Tipo de dispositivo para o console.
}
}
Explicação Detalhada:
resource "libvirt_cloudinit_disk" "cloudinit" { ... }
: Este recurso é responsável por criar um disco ISO que contém as configurações iniciais para a VM, utilizando o Cloud-Init. Ofor_each
permite que um disco seja gerado para cada VM definida. Os blocosuser_data
enetwork_config
utilizam a funçãotemplatefile
para preencher os templates YML do Cloud-Init com dados dinâmicos, como hostname, usuário, chaves SSH e configurações de rede.resource "libvirt_volume" "os_image" { ... }
: Este recurso provisiona um volume de disco no pool de armazenamento KVM. Ele é usado para copiar a imagem base do sistema operacional (QCOW2) de uma URL externa para o ambiente KVM, servindo como o disco principal da VM. Ofor_each
garante que um volume seja criado para cada VM.resource "libvirt_domain" "domain" { ... }
: Este é o recurso central que define a máquina virtual KVM (o domínio Libvirt). Ele configura atributos como nome, memória, vCPUs e associa o disco Cloud-Init e o volume da imagem do SO à VM. A configuraçãocpu { mode = "host-passthrough" }
é uma otimização importante para o desempenho da VM, permitindo que ela utilize as extensões de virtualização do processador do host diretamente. As seçõesnetwork_interface
edisk
conectam a VM à rede virtual e ao disco de inicialização, respectivamente. O blococonsole
configura um console serial, útil para depuração e acesso inicial à VM.
1.6. modules/vm/outputs.tf
O arquivo outputs.tf
dentro de um módulo define os valores que o módulo irá expor para o módulo pai ou para o usuário final. Isso é útil para obter informações importantes sobre os recursos criados, como endereços IP ou nomes de domínio, que podem ser necessários para interações futuras ou para validação do provisionamento.
1
2
3
4
output "vm_ips" {
description = "Endereços IP das VMs provisionadas" # Descrição clara do que a saída representa.
value = { for k, v in libvirt_domain.domain : k => v.network_interface[0].addresses[0] } # Mapeia o hostname da VM para o seu primeiro endereço IP.
}
Explicação Detalhada:
output "vm_ips" { ... }
: Este bloco declara uma saída chamadavm_ips
. O nome da saída deve ser descritivo e indicar o tipo de informação que ela fornecerá.description
: Uma descrição clara do propósito da saída. Isso é fundamental para a documentação do módulo, ajudando outros usuários (ou você mesmo no futuro) a entender o que essa saída representa e como ela pode ser utilizada.value
: A expressão Terraform que calcula o valor da saída. Neste caso, utilizamos umafor
expression para iterar sobre todos os domínios Libvirt (libvirt_domain.domain
) criados pelo módulo. Para cada domínio, ele extrai okey
(que é o hostname da VM) e o primeiro endereço IP (v.network_interface[0].addresses[0]
) da interface de rede. O resultado é um mapa onde as chaves são os hostnames das VMs e os valores são seus respectivos endereços IP. Isso permite que o módulo pai acesse facilmente os IPs de todas as VMs provisionadas. da primeira interface de rede de cada VM. Isso permite que você obtenha facilmente os IPs de todas as VMs após a implantação.
1.7. modules/vm/variables.tf
Assim como o variables.tf
global, este arquivo define as variáveis de entrada específicas para o módulo vm
. Essas variáveis permitem que o módulo seja reutilizável e configurável a partir do main.tf
do projeto principal.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
variable "vm_configs" {
type = map(object({
username = string
gecos = string
groups = list(string)
network_ip = string
nameserver_ip = string
route_ip = string
memory = number
vcpu = number
os_image_url = string
}))
description = "Um mapa de configurações para cada máquina virtual a ser provisionada por este módulo."
}
variable "network_id" {
type = string
description = "O ID da rede Libvirt à qual as VMs serão conectadas."
}
variable "ssh_public_key" {
type = string
sensitive = true
description = "A chave SSH pública a ser injetada nas VMs para acesso."
}
Explicação:
vm_configs
: Recebe o mapa de configurações de VMs domain.tf
principal. É a principal entrada para o módulo, definindo as características de cada VM a ser criada.network_id
: Recebe o ID da rede Libvirt criada no projeto principal, garantindo que as VMs sejam conectadas à rede correta.ssh_public_key
: Recebe a chave SSH pública do projeto principal, que será usada para configurar o acesso SSH nas VMs via Cloud-Init.
1.8. modules/vm/versions.tf
O arquivo versions.tf
dentro de um módulo especifica as versões mínimas e máximas dos provedores Terraform que este módulo requer. Isso ajuda a garantir a compatibilidade e a evitar problemas quando o módulo é usado em diferentes projetos ou por diferentes colaboradores.
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 raiz
}
}
}
Explicação:
- Este bloco é similar ao
versions.tf
do projeto raiz, mas se aplica especificamente ao módulo. Ele garante que o provedorlibvirt
na versão0.8.3
(ou compatível) esteja disponível para o módulo. A nota “Use a mesma versão da raiz” é um lembrete importante para manter a consistência entre o módulo e o projeto principal.
2. Configuração do Cloud-Init
O Cloud-Init é um pacote padrão da indústria para personalização de máquinas virtuais na primeira inicialização. Ele permite que você injete configurações como usuários, chaves SSH, configurações de rede e scripts de execução inicial nas VMs de forma automatizada. No nosso projeto Terraform, utilizamos templates do Cloud-Init para configurar as VMs provisionadas pelo provedor libvirt
.
Os templates do Cloud-Init são arquivos de texto com variáveis (indicadas por ${...}
) que o Terraform preenche dinamicamente usando a função templatefile
. Temos dois templates principais:
2.1. Configuração de Usuário e SSH (modules/vm/cloud-init/user_data.yml.tpl
)
Este template (user_data.yml.tpl
) é usado para configurar usuários, chaves SSH e comandos a serem executados na primeira inicialização da VM. Ele é passado para o recurso libvirt_cloudinit_disk
no módulo VM.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#cloud-config
users:
- name: ${user_name} # Define o nome do usuário a ser criado na VM.
gecos: ${gecos} # Informações gerais sobre o usuário (Nome Completo, etc.).
sudo: ALL=(ALL) NOPASSWD:ALL # Concede permissões sudo sem a necessidade de senha.
groups: ${jsonencode(groups)} # Adiciona o usuário aos grupos especificados. A função `jsonencode` é usada para formatar a lista de grupos corretamente para o Cloud-Init.
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:
- "${ssh_key}" # Adiciona a chave SSH pública fornecida para permitir acesso via SSH sem senha.
disable_root: true # Desabilita o usuário root.
ssh_pwauth: false # Desabilita a autenticação por senha para SSH em todo o sistema.
runcmd:
- hostnamectl set-hostname ${hostname} # Define o hostname da VM após a inicialização.
Explicação:
- O arquivo começa com
#cloud-config
, que é um cabeçalho obrigatório para arquivos Cloud-Init. - A seção
users
configura um novo usuário com as propriedades especificadas pelas variáveis do template (user_name
,gecos
,groups
,ssh_key
). disable_root
essh_pwauth
aumentam a segurança desabilitando o login root direto e a autenticação por senha via SSH.runcmd
lista comandos que serão executados uma única vez durante a primeira inicialização da VM. Aqui, usamoshostnamectl
para definir o nome da máquina com base na variável${hostname}
.
2.2. Configuração de Rede (modules/vm/cloud-init/network_config.yml.tpl
)
Este template (network_config.yml.tpl
) é usado para configurar a interface de rede da VM, definindo endereço IP, gateway e servidores DNS. Ele também é passado para o recurso libvirt_cloudinit_disk
.
1
2
3
4
5
6
7
8
9
10
11
12
13
#cloud-config
network:
version: 2
ethernets:
ens3: # Nome da interface de rede. Pode variar dependendo do sistema operacional da imagem base.
addresses:
- ${network_ip}/24 # Define o endereço IP e a máscara de sub-rede para a interface.
nameservers:
addresses:
- ${nameserver_ip} # Define o endereço IP do servidor DNS.
routes:
- to: 0.0.0.0/0
via: ${route_ip} # Define a rota padrão (gateway) para a internet.
Explicação:
- A seção
network
configura as interfaces de rede da VM. version: 2
indica a versão do formato de configuração de rede do Cloud-Init.ethernets
lista as interfaces Ethernet a serem configuradas.ens3
é um nome comum para a primeira interface de rede em muitas distribuições Linux modernas.addresses
,nameservers
eroutes
configuram o endereço IP estático, o servidor DNS e o gateway padrão, respectivamente, utilizando as variáveis fornecidas pelo Terraform (network_ip
,nameserver_ip
,route_ip
).
Validando e Testando Cloud-Init
O tutorial menciona dois comandos que podem ser úteis durante o desenvolvimento e depuração:
cloud-init schema --config-file user_data.yml
: Este comando (parte das ferramentas do Cloud-Init, que podem precisar ser instaladas na máquina onde você está executando o Terraform ou em uma VM de teste) valida a sintaxe de um arquivo de configuração do Cloud-Init contra seu esquema. É útil para verificar se seus arquivos.yml
estão formatados corretamente.terraform console
: Este comando inicia um console interativo do Terraform onde você pode avaliar expressões. O exemplo> var.groups
mostra como você pode inspecionar o valor de uma variável Terraform, o que é útil para verificar se os dados estão sendo carregados e formatados como esperado antes de serem passados para os templates ou recursos.
3. Configurações Sensíveis (terraform.tfvars
)
O arquivo terraform.tfvars
é crucial para o seu projeto Terraform, pois é onde você define os valores para as variáveis de entrada que não possuem um valor padrão ou que contêm informações sensíveis. É de extrema importância que este arquivo NUNCA seja versionado em sistemas de controle de versão como o Git, pois ele pode conter chaves SSH, senhas ou outros dados confidenciais.
3.1. Crie terraform.tfvars
a partir do Exemplo
Para começar, você deve criar seu próprio arquivo terraform.tfvars
na raiz do projeto. O arquivo terraform.tfvars.example
serve como um modelo para guiá-lo sobre quais variáveis precisam ser definidas e o formato esperado. Copie o conteúdo do terraform.tfvars.example
para um novo arquivo chamado terraform.tfvars
:
1
cp terraform.tfvars.example terraform.tfvars
Agora, edite o arquivo terraform.tfvars
que você acabou de criar, preenchendo os valores conforme suas necessidades. Abaixo, um exemplo de como seu terraform.tfvars
pode se parecer:
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
ssh_public_key = "ssh-ed25519 AAAAC... user@host" # Substitua pela sua chave SSH pública real
vms = {
"debian-vm" = { # Nome da VM (hostname)
username = "suporte" # Nome de usuário para acesso SSH
gecos = "Suporte User" # Informações gerais do usuário
groups = ["users", "sudo"] # Grupos aos quais o usuário pertencerá
network_ip = "10.64.0.10" # Endereço IP estático para a VM
nameserver_ip = "10.64.0.1" # Endereço IP do servidor DNS
route_ip = "10.64.0.1" # Endereço IP do gateway padrão
memory = 4096 # Memória RAM em MB
vcpu = 4 # Número de vCPUs
os_image_url = "/home/gean/kvm/templates/debian-12-amd64.qcow2" # Caminho completo para a imagem base do SO
},
"oracle-vm" = {
username = "suporte"
gecos = "Suporte User"
groups = ["users", "wheel"]
network_ip = "10.64.0.11"
nameserver_ip = "10.64.0.1"
route_ip = "10.64.0.1"
memory = 4096
vcpu = 4
os_image_url = "/home/gean/kvm/templates/ol9-amd64.qcow2"
}
}
# Configurações opcionais (podem ser sobrescritas aqui, se necessário)
libvirt_uri = "qemu:///system"
network_name = "tfnet"
network_mode = "nat"
network_addresses = "10.64.0.0/24"
Entendendo o Bloco vms
:
O bloco vms
é um mapa de objetos, onde cada chave representa o hostname de uma máquina virtual que você deseja provisionar. Para cada VM, você define um conjunto de atributos como username
, network_ip
, memory
, vcpu
, e o caminho para a os_image_url
. Certifique-se de que o os_image_url
aponte para uma imagem QCOW2 válida em seu sistema KVM.
Nota sobre Imagens Base: Você pode baixar imagens template prontas (ex:
wget -O ~/kvm/templates/debian-12-amd64.qcow2 https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
) ou criar suas próprias imagens personalizadas. Para a criação de templates personalizados, você pode consultar os seguintes guias:
Gerenciamento de Segredos em Produção:
Para ambientes de produção, o uso de terraform.tfvars
para dados sensíveis não é a prática mais recomendada. Considere integrar soluções de gerenciamento de segredos como:
- HashiCorp Vault: Uma ferramenta para armazenar e gerenciar segredos de forma centralizada.
- Variáveis de Ambiente em Pipelines de CI/CD: Injetar segredos como variáveis de ambiente seguras durante a execução do Terraform em pipelines de integração contínua/entrega contínua.
4. Versionamento com .gitignore
O uso correto do .gitignore
é fundamental para qualquer projeto versionado com Git, especialmente em projetos Terraform que lidam com arquivos de estado (.tfstate
) e variáveis sensíveis (terraform.tfvars
). Estes arquivos não devem ser incluídos no controle de versão por questões de segurança e para evitar conflitos.
Crie ou edite o arquivo .gitignore
na raiz do## 4. Versionamento com .gitignore
O uso correto do .gitignore
é fundamental para qualquer projeto versionado com Git, especialmente em projetos Terraform que lidam com arquivos de estado (.tfstate
) e variáveis sensíveis (terraform.tfvars
). Estes arquivos não devem ser incluídos no controle de versão por questões de segurança e para evitar conflitos.
Crie ou edite o arquivo .gitignore
na raiz do seu projeto com o seguinte conteúdo:
# Terraform
.terraform/
*.tfstate
*.tfstate.backup
crash.log
*.tfvars
*.tfvars.json
# Providers
.terraform.lock.hcl
# Local Configuration
.DS_Store
*.log
*.out
Explicação:
.terraform/
: Ignora o diretório.terraform
, que contém os plugins dos provedores e outros dados internos do Terraform. Este diretório é gerado automaticamente peloterraform init
.*.tfstate
: Ignora o arquivo de estado do Terraform. O arquivo.tfstate
contém o mapeamento entre os recursos reais da sua infraestrutura e a configuração do Terraform. Ele pode conter informações sensíveis e deve ser gerenciado por um backend remoto (como Terraform Cloud, S3, etc.) em ambientes de equipe.*.tfstate.backup
: Ignora os backups automáticos do arquivo de estado que o Terraform cria.crash.log
: Ignora logs de crash do Terraform.*.tfvars
e*.tfvars.json
: Ignora os arquivos de variáveis, comoterraform.tfvars
, que contêm valores específicos do ambiente e, frequentemente, informações sensíveis (como chaves SSH, senhas). É crucial que esses arquivos nunca sejam versionados!.terraform.lock.hcl
: Ignora o arquivo de bloqueio de provedores, que garante que a mesma versão dos provedores seja usada por todos os colaboradores..DS_Store
,*.log
,*.out
: Ignora arquivos de sistema macOS, logs genéricos e arquivos de saída que não devem ser versionados.
Explicação:
.terraform/
: Ignora o diretório.terraform
, que contém os plugins dos provedores e outros dados internos do Terraform. Este diretório é gerado automaticamente peloterraform init
.*.tfstate
: Ignora o arquivo de estado do Terraform. O arquivo.tfstate
contém o mapeamento entre os recursos reais da sua infraestrutura e a configuração do Terraform. Ele pode conter informações sensíveis e deve ser gerenciado por um backend remoto (como Terraform Cloud, S3, etc.) em ambientes de equipe.*.tfstate.backup
: Ignora os backups automáticos do arquivo de estado que o Terraform cria.crash.log
: Ignora logs de crash do Terraform.*.tfvars
e*.tfvars.json
: Ignora os arquivos de variáveis, comoterraform.tfvars
, que contêm valores específicos do ambiente e, frequentemente, informações sensíveis (como chaves SSH, senhas). É crucial que esses arquivos nunca sejam versionados!.terraform.lock.hcl
: Ignora o arquivo de bloqueio de provedores, que garante que a mesma versão dos provedores seja usada por todos os colaboradores..DS_Store
,*.log
,*.out
: Ignora arquivos de sistema macOS, logs genéricos e arquivos de saída que não devem ser versionados.
5.2. Planeje a Infraestrutura
1
terraform plan
5.3. Aplique as Mudanças
1
terraform apply
6. Acessando a VM
6.1. Acesso via SSH
1
2
3
ssh -i ~/.ssh/tfvms <user_name>@<vm_ip>
# Exemplo:
ssh -i ~/.ssh/tfvms suporte@10.64.0.10
7. Destruindo a infraestrutura
7.1. Destroi a infra
1
terraform destroy
Conclusão
Este tutorial detalhou o processo de provisionamento de infraestrutura KVM utilizando Terraform e módulos reutilizáveis. Através da modularização, demonstramos como é possível criar um ambiente de virtualização robusto, escalável e de fácil manutenção. A automação de tarefas repetitivas, como a criação de máquinas virtuais e a configuração de redes, não só otimiza o tempo dos administradores e engenheiros de DevOps, mas também minimiza erros e garante a consistência do ambiente.
Ao longo deste guia, você aprendeu a estruturar seu projeto Terraform, a configurar provedores e variáveis, e a utilizar módulos para encapsular a lógica de provisionamento de VMs. A integração com o Cloud-Init para personalização inicial das máquinas virtuais é um diferencial que permite um alto grau de automação desde o primeiro boot. A capacidade de definir redes virtuais e gerenciar o ciclo de vida completo das VMs diretamente do código-fonte é um testemunho do poder da Infraestrutura como Código.
Esperamos que este tutorial sirva como um ponto de partida sólido para suas próprias implementações de KVM com Terraform. Encorajamos você a explorar ainda mais as capacidades do Terraform, como a gestão de estado remoto, a integração com sistemas de CI/CD e a criação de módulos mais complexos para atender às suas necessidades específicas. A jornada para uma infraestrutura totalmente automatizada é contínua, e o Terraform é uma ferramenta poderosa para guiá-lo nesse caminho. Continue experimentando, aprendendo e otimizando seus fluxos de trabalho para construir ambientes cada vez mais eficientes e resilientes.