Do código ao cluster: IaC avançado com Terraform e GitOps

Do código ao cluster: IaC avançado com Terraform e GitOps
“A maior vantagem da infraestrutura como código é a capacidade de tratar ambientes de produção como software: versionamento, revisão e automação.” – Adaptado de práticas modernas de DevOps.
Introdução
Nos últimos anos, a entrega de software evoluiu de um processo manual e propenso a erros para um fluxo totalmente automatizado, onde infraestrutura e aplicação são tratadas como um único artefato. Essa mudança é impulsionada por três pilares:
Neste artigo, vamos montar um cenário completo: provisionar recursos na nuvem com Terraform, criar um cluster Kubernetes, e usar GitHub Actions (ou outra ferramenta de CI) para aplicar mudanças via GitOps. Tudo isso sem mencionar ferramentas que já foram abordadas em outros posts.
Objetivo: ao final, você será capaz de criar um pipeline que, ao receber um pull request, valida o código IaC, executa testes de segurança e, se aprovado, sincroniza o estado desejado com o cluster.
1. Preparando o ambiente de IaC com Terraform
1.1 Estrutura de diretórios
infra/
├─ modules/
│ └─ k8s-cluster/
│ ├─ main.tf
│ ├─ variables.tf
│ └─ outputs.tf
├─ environments/
│ ├─ dev/
│ │ ├─ main.tf
│ │ └─ terraform.tfvars
│ └─ prod/
│ ├─ main.tf
│ └─ terraform.tfvars
└─ provider.tf
- modules/ – código reutilizável (ex.: criação de VPC, sub‑redes, nós do cluster).
- environments/ – configurações específicas de cada estágio (dev, prod).
- provider.tf – definição do provedor de nuvem (ex.: AWS, Azure, GCP).
1.2 Exemplo de módulo k8s-cluster
# modules/k8s-cluster/main.tf
resource "aws_eks_cluster" "this" {
name = var.cluster_name
role_arn = var.cluster_role_arn
vpc_config {
subnet_ids = var.subnet_ids
}
tags = {
Environment = var.environment
}
}
# modules/k8s-cluster/variables.tf
variable "cluster_name" { type = string }
variable "cluster_role_arn" { type = string }
variable "subnet_ids" { type = list(string) }
variable "environment" { type = string }
# modules/k8s-cluster/outputs.tf
output "cluster_endpoint" {
value = aws_eks_cluster.this.endpoint
}
output "cluster_certificate_authority_data" {
value = aws_eks_cluster.this.certificate_authority[0].data
}
1.3 Consumo do módulo em dev
# environments/dev/main.tf
module "k8s_cluster" {
source = "../../modules/k8s-cluster"
cluster_name = "myapp-dev"
cluster_role_arn = aws_iam_role.eks_role.arn
subnet_ids = data.aws_subnets.private.ids
environment = "dev"
}
Dica: Use o comando
terraform fmtpara garantir formatação padrão eterraform validatepara detectar erros antes de aplicar.
2. GitOps: declarando o estado desejado no repositório
2.1 Estrutura de diretórios para o cluster
gitops/
├─ base/
│ ├─ namespace.yaml
│ ├─ ingress-controller.yaml
│ └─ storage-class.yaml
└─ overlays/
├─ dev/
│ ├─ kustomization.yaml
│ └─ app-config.yaml
└─ prod/
├─ kustomization.yaml
└─ app-config.yaml
- base/ – recursos comuns a todos os ambientes.
- overlays/ – variações específicas (ex.: réplicas, limites de recursos).
2.2 Exemplo de kustomization.yaml para dev
# gitops/overlays/dev/kustomization.yaml
resources:
- ../../base
patchesStrategicMerge:
- app-config.yaml
commonLabels:
env: dev
2.3 Manifesto de aplicação (ex.: API em Node.js)
# gitops/base/app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
labels:
app: api
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: ghcr.io/empresa/api:{{IMAGE_TAG}}
ports:
- containerPort: 8080
env:
- name: NODE_ENV
value: production
Observação: O placeholder
{{IMAGE_TAG}}será substituído pelo pipeline de CI/CD com a tag da imagem recém‑construída.
3. CI/CD – Automatizando build, teste e sincronização
3.1 Workflow GitHub Actions (arquivo .github/workflows/ci-cd.yml)
```yaml name: CI/CD – IaC & GitOps
on: push: branches: [ main ] pull_request: types: [ opened, synchronize, reopened ]
jobs: lint-terraform: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: "1.6.0" - name: Terraform fmt run: terraform fmt -check -recursive - name: Terraform validate run: terraform validate
test-k8s-manifests: runs-on: ubuntu-latest needs: lint-terraform steps: - uses: actions/checkout@v3 - name: Install kustomize run: | curl -sLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.0.0/kustomize_v5.0.0_linux_amd64.tar.gz tar -xzf kustomize_v5.0.0_linux_amd64.tar.gz kustomize sudo mv kustomize /usr/local/bin/ - name: Render manifests (dev) run: | kustomize build gitops/overlays/dev > rendered-dev.yaml - name: Validate YAML uses: actionshub/kubeval@v2 with: files: rendered-dev.yaml
build-and-push-image: runs-on: ubuntu-latest needs: test-k8s-manifests steps: - uses: actions/checkout@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Buildx uses: docker/setup-buildx-action@v2 - name: Login to GHCR uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build & push id: image uses: docker/build-push-action@v4 with: context: . push: true tags: ghcr.io/empresa/api:${{ github.sha }}
sync-gitops: runs-on: ubuntu-latest needs: build-and-push-image if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - name: Update image tag run: | sed -i "s/{{IMAGE_TAG}}/${{ github.sha }}/g" gitops/overlays/prod/app-deployment.yaml git config user.name "github-actions" git config user.email "actions@github.com" git add gitops/overlays/prod/app-deployment.yaml git commit -m "


