Módulos no Terraform

1. Introdução aos Módulos no Terraform

Módulos no Terraform são contêineres lógicos para recursos relacionados, que permitem agrupar e reutilizar configurações de infraestrutura como código. Em pipelines DevOps, módulos são essenciais para criar ambientes consistentes, reduzir duplicação e facilitar a manutenção de infraestrutura complexa.

Os principais benefícios dos módulos incluem:
- Reuso: Um módulo bem projetado pode ser utilizado em múltiplos ambientes (dev, staging, production)
- Encapsulamento: Detalhes internos ficam ocultos, expondo apenas interfaces claras via variáveis e outputs
- Versionamento: Módulos podem ser versionados e distribuídos, garantindo rastreabilidade das mudanças

No Terraform, existem dois tipos principais de módulos:
- Módulo root: O diretório raiz onde terraform apply é executado
- Módulos filhos: Módulos chamados pelo módulo root ou por outros módulos

2. Estrutura de um Módulo Terraform

Um módulo Terraform segue uma estrutura de diretórios padronizada:

modules/
  k8s-cluster/
    main.tf          # Recursos principais do módulo
    variables.tf     # Declaração de variáveis de entrada
    outputs.tf       # Valores expostos para outros módulos
    README.md        # Documentação do módulo

Exemplo de módulo simples para cluster Kubernetes

modules/k8s-cluster/variables.tf:

variable "cluster_name" {
  description = "Nome do cluster Kubernetes"
  type        = string
}

variable "node_count" {
  description = "Número de nós no cluster"
  type        = number
  default     = 3
}

variable "node_instance_type" {
  description = "Tipo de instância para os nós"
  type        = string
  default     = "t3.medium"
}

modules/k8s-cluster/main.tf:

resource "aws_eks_cluster" "main" {
  name     = var.cluster_name
  role_arn = aws_iam_role.cluster.arn

  vpc_config {
    subnet_ids = var.subnet_ids
  }
}

resource "aws_eks_node_group" "main" {
  cluster_name    = aws_eks_cluster.main.name
  node_group_name = "${var.cluster_name}-nodes"
  instance_types  = [var.node_instance_type]
  node_role_arn   = aws_iam_role.node.arn

  scaling_config {
    desired_size = var.node_count
    min_size     = 1
    max_size     = 5
  }

  subnet_ids = var.subnet_ids
}

modules/k8s-cluster/outputs.tf:

output "cluster_endpoint" {
  description = "Endpoint do cluster Kubernetes"
  value       = aws_eks_cluster.main.endpoint
}

output "cluster_certificate_authority" {
  description = "Certificado CA do cluster"
  value       = aws_eks_cluster.main.certificate_authority[0].data
}

output "node_group_arn" {
  description = "ARN do grupo de nós"
  value       = aws_eks_node_group.main.arn
}

3. Entrada e Saída de Dados em Módulos

Variáveis de entrada com validações

variable "environment" {
  description = "Ambiente de deployment"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "O ambiente deve ser dev, staging ou prod."
  }
}

variable "tags" {
  description = "Tags para todos os recursos"
  type        = map(string)
  default = {
    ManagedBy = "Terraform"
  }
}

Outputs para comunicação entre módulos

output "vpc_id" {
  description = "ID da VPC criada"
  value       = aws_vpc.main.id
}

output "private_subnet_ids" {
  description = "IDs das subnets privadas"
  value       = aws_subnet.private[*].id
}

Uso de locals para lógica interna

locals {
  name_prefix = "${var.project}-${var.environment}"
  common_tags = merge(var.tags, {
    Environment = var.environment
    Project     = var.project
  })
}

4. Chamando Módulos no Código Principal

Sintaxe do bloco module

module "vpc" {
  source = "./modules/vpc"

  vpc_cidr       = "10.0.0.0/16"
  environment    = var.environment
  project        = var.project
  tags           = var.tags
}

module "eks_cluster" {
  source = "./modules/k8s-cluster"

  cluster_name       = "${var.project}-${var.environment}"
  node_count         = var.environment == "prod" ? 5 : 3
  node_instance_type = var.environment == "prod" ? "t3.large" : "t3.medium"
  subnet_ids         = module.vpc.private_subnet_ids
}

Caminhos remotos (Terraform Registry)

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "${var.project}-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

5. Versionamento e Registro de Módulos

Controle de versão com constraints

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 19.0"  # Qualquer versão 19.x.x

  # ou
  # version = ">= 19.0, < 20.0"
}

Estratégias de versionamento semântico

Para módulos internos, recomenda-se:
- Major: Mudanças que quebram compatibilidade
- Minor: Novas funcionalidades compatíveis
- Patch: Correções de bugs

# Publicação no Terraform Registry privado
terraform login registry.internal.com
terraform registry publish modules/network:v1.2.0

6. Módulos no Contexto de Docker e Kubernetes

Módulo para criação de imagens Docker

modules/docker-build/variables.tf:

variable "image_name" {
  description = "Nome da imagem Docker"
  type        = string
}

variable "dockerfile_path" {
  description = "Caminho para o Dockerfile"
  type        = string
}

variable "build_args" {
  description = "Argumentos de build"
  type        = map(string)
  default     = {}
}

modules/docker-build/main.tf:

resource "docker_image" "app" {
  name = var.image_name

  build {
    path       = var.dockerfile_path
    build_args = var.build_args
  }
}

resource "docker_registry_image" "app" {
  name          = docker_image.app.name
  keep_remotely = true
}

Módulo para deployment no Kubernetes

modules/k8s-deployment/main.tf:

resource "kubernetes_deployment" "app" {
  metadata {
    name      = var.app_name
    namespace = var.namespace
    labels    = var.labels
  }

  spec {
    replicas = var.replicas

    selector {
      match_labels = var.match_labels
    }

    template {
      metadata {
        labels = var.match_labels
      }

      spec {
        container {
          image             = var.image
          name              = var.app_name
          image_pull_policy = "Always"

          port {
            container_port = var.container_port
          }

          resources {
            limits = {
              cpu    = var.cpu_limit
              memory = var.memory_limit
            }
          }
        }
      }
    }
  }
}

resource "kubernetes_service" "app" {
  metadata {
    name      = var.app_name
    namespace = var.namespace
  }

  spec {
    selector = var.match_labels
    port {
      port        = var.service_port
      target_port = var.container_port
    }
    type = var.service_type
  }
}

Exemplo integrado: Docker build + deploy no K8s

module "docker_build" {
  source = "./modules/docker-build"

  image_name      = "myapp:${var.version}"
  dockerfile_path = abspath("${path.root}/app")
  build_args = {
    VERSION = var.version
  }
}

module "k8s_deploy" {
  source = "./modules/k8s-deployment"

  app_name       = "myapp"
  namespace      = var.namespace
  image          = module.docker_build.image_uri
  replicas       = var.replicas
  container_port = 8080
  service_port   = 80
  service_type   = "ClusterIP"

  match_labels = {
    app = "myapp"
  }

  labels = {
    app     = "myapp"
    version = var.version
  }
}

7. Boas Práticas e Padrões Avançados

Modularização por responsabilidade

Organize módulos por camadas de infraestrutura:

infrastructure/
  modules/
    network/          # VPC, subnets, gateways
    compute/          # EC2, ECS, EKS
    database/         # RDS, DynamoDB
    storage/          # S3, EFS
    monitoring/       # CloudWatch, Prometheus
    ci-cd/            # Jenkins, GitLab Runner

Documentação automática com terraform-docs

Adicione ao seu pipeline CI/CD:

# .github/workflows/terraform-docs.yml
name: Generate Terraform Docs
on:
  pull_request:
    paths:
      - 'modules/**/*.tf'

jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: terraform-docs/gh-actions@v1
        with:
          working-dir: modules
          output-file: README.md
          output-method: replace

Testes de módulos com Terratest

// test/k8s_module_test.go
package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
)

func TestK8sModule(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/k8s-cluster",
        Vars: map[string]interface{}{
            "cluster_name": "test-cluster",
            "node_count":   2,
        },
    }

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    endpoint := terraform.Output(t, terraformOptions, "cluster_endpoint")
    if endpoint == "" {
        t.Errorf("Expected non-empty endpoint")
    }
}

Integração contínua em pipelines CI/CD

# .gitlab-ci.yml
stages:
  - validate
  - plan
  - deploy

validate:
  stage: validate
  script:
    - terraform fmt -check -recursive modules/
    - terraform validate

plan:
  stage: plan
  script:
    - terraform plan -out plan.tfplan
  artifacts:
    paths:
      - plan.tfplan

deploy:
  stage: deploy
  script:
    - terraform apply plan.tfplan
  only:
    - main

Referências