Ir para o conteúdo
Ferramenta para Plataforma1470 palavras

Terraform para GCP: Guia Completo de Deploy

Publicado no blog Vivo de Código

Introdução

Gerenciar infraestrutura manualmente no console do Google Cloud é algo que funciona quando você tem dois ou três recursos. Quando o projeto cresce, quando a equipe cresce, quando você precisa replicar ambientes, essa abordagem se torna um pesadelo. É nesse cenário que o Terraform entra como peça fundamental.

O Terraform é uma ferramenta de Infrastructure as Code (IaC) criada pela HashiCorp que permite declarar, versionar e provisionar recursos de infraestrutura de forma previsível e repetível. Quando combinado com o Google Cloud Platform (GCP), ele oferece uma experiência poderosa para times que precisam de controle total sobre seus ambientes.

Neste guia, vou compartilhar o que aprendi em anos trabalhando com essa combinação em projetos de produção. Vamos desde o setup inicial até boas práticas avançadas, passando por exemplos reais que você pode adaptar ao seu contexto.

Por que usar Terraform com GCP

Vantagens da infraestrutura declarativa no Google Cloud

O GCP já oferece o Deployment Manager como ferramenta nativa de IaC. Então por que escolher o Terraform? Existem razões sólidas para isso.

Primeiro: o Terraform é agnóstico de provedor. Se sua empresa usa GCP para workloads de Machine Learning e AWS para o restante, você gerencia tudo com a mesma linguagem (HCL) e o mesmo fluxo de trabalho. Isso reduz a carga cognitiva do time de forma significativa.

Segundo: o ecossistema do Terraform é imenso. O provider oficial do Google Cloud é mantido em parceria direta entre HashiCorp e Google, o que garante suporte rápido a novos serviços. Além disso, a comunidade produz módulos prontos para cenários comuns como criação de clusters GKE, redes VPC e configurações de IAM.

Terceiro: o modelo de estado (state) do Terraform permite que você saiba exatamente o que está provisionado, o que mudou e o que será destruído antes de qualquer ação. Esse ciclo de plan e apply é essencial para ambientes de produção.

Casos de uso reais onde essa combinação brilha

Na prática, os cenários onde Terraform com GCP se destaca incluem: provisionamento de clusters GKE para times de desenvolvimento, criação de ambientes efêmeros para testes de integração, gerenciamento de políticas de IAM em organizações com múltiplos projetos, e configuração de pipelines de dados com BigQuery, Pub/Sub e Cloud Functions.

Um caso que vivenciei pessoalmente: uma fintech precisava criar ambientes completos (VPC, Cloud SQL, GKE, Cloud Run) para cada nova feature branch. Com Terraform e workspaces, automatizamos esse processo e reduzimos o tempo de provisionamento de dois dias para quinze minutos.

Setup inicial do Terraform com GCP

Instalando e configurando o ambiente

Antes de escrever qualquer código, você precisa do Terraform CLI e do Google Cloud SDK instalados. Vamos ao passo a passo.

Instale o Terraform (usando o gerenciador oficial da HashiCorp):

Bash
# No Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
 
# Verificando a instalação
terraform version

Instale e configure o Google Cloud SDK:

Bash
# Autenticação para uso local
gcloud auth application-default login
 
# Definindo o projeto padrão
gcloud config set project meu-projeto-id

Criando a Service Account para o Terraform

Em ambientes de CI/CD, você nunca deve usar credenciais pessoais. Crie uma Service Account dedicada:

Bash
# Criando a service account
gcloud iam service-accounts create terraform-sa \
  --display-name="Terraform Service Account" \
  --project=meu-projeto-id
 
# Atribuindo permissões (ajuste conforme necessidade)
gcloud projects add-iam-policy-binding meu-projeto-id \
  --member="serviceAccount:[email protected]" \
  --role="roles/editor"
 
# Gerando a chave (para CI/CD)
gcloud iam service-accounts keys create terraform-key.json \
  --iam-account=terraform-sa@meu-projeto-id.iam.gserviceaccount.com

Configuração do backend remoto com GCS

Armazenar o arquivo de estado localmente é um risco. Use o Google Cloud Storage como backend remoto:

Bash
# Criando o bucket para o state
gsutil mb -p meu-projeto-id -l us-central1 gs://meu-projeto-terraform-state
gsutil versioning set on gs://meu-projeto-terraform-state

Exemplo prático: infraestrutura completa para uma aplicação web

Vamos criar uma infraestrutura real: uma aplicação Node.js rodando no Cloud Run, com um banco Cloud SQL PostgreSQL e uma VPC dedicada.

Estrutura de arquivos do projeto Terraform

Text
infra/
  main.tf
  variables.tf
  outputs.tf
  modules/
    network/
      main.tf
      variables.tf
      outputs.tf
    database/
      main.tf
      variables.tf
      outputs.tf
    cloudrun/
      main.tf
      variables.tf
      outputs.tf

Arquivo principal de configuração

HCL
# main.tf
terraform {
  required_version = ">= 1.5.0"
 
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
 
  backend "gcs" {
    bucket = "meu-projeto-terraform-state"
    prefix = "terraform/state"
  }
}
 
provider "google" {
  project = var.project_id
  region  = var.region
}
 
module "network" {
  source     = "./modules/network"
  project_id = var.project_id
  region     = var.region
}
 
module "database" {
  source            = "./modules/database"
  project_id        = var.project_id
  region            = var.region
  vpc_id            = module.network.vpc_id
  private_ip_range  = module.network.private_ip_range
  db_password       = var.db_password
}
 
module "cloudrun" {
  source              = "./modules/cloudrun"
  project_id          = var.project_id
  region              = var.region
  image               = var.container_image
  db_connection_name  = module.database.connection_name
  db_password         = var.db_password
  vpc_connector_id    = module.network.vpc_connector_id
}
HCL
# variables.tf
variable "project_id" {
  description = "ID do projeto GCP"
  type        = string
}
 
variable "region" {
  description = "Região padrão para os recursos"
  type        = string
  default     = "us-central1"
}
 
variable "db_password" {
  description = "Senha do banco de dados PostgreSQL"
  type        = string
  sensitive   = true
}
 
variable "container_image" {
  description = "Imagem Docker para o Cloud Run"
  type        = string
}

Módulo de rede VPC

HCL
# modules/network/main.tf
resource "google_compute_network" "main" {
  name                    = "app-vpc"
  project                 = var.project_id
  auto_create_subnetworks = false
}
 
resource "google_compute_subnetwork" "main" {
  name          = "app-subnet"
  project       = var.project_id
  region        = var.region
  network       = google_compute_network.main.id
  ip_cidr_range = "10.0.0.0/24"
}
 
resource "google_compute_global_address" "private_ip" {
  name          = "private-ip-range"
  project       = var.project_id
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 16
  network       = google_compute_network.main.id
}
 
resource "google_service_networking_connection" "private_vpc" {
  network                 = google_compute_network.main.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip.name]
}
 
resource "google_vpc_access_connector" "connector" {
  name          = "vpc-connector"
  project       = var.project_id
  region        = var.region
  network       = google_compute_network.main.name
  ip_cidr_range = "10.8.0.0/28"
}

Módulo do Cloud SQL

HCL
# modules/database/main.tf
resource "google_sql_database_instance" "postgres" {
  name             = "app-db-instance"
  project          = var.project_id
  region           = var.region
  database_version = "POSTGRES_15"
 
  depends_on = [var.private_ip_range]
 
  settings {
    tier              = "db-custom-2-4096"
    availability_type = "REGIONAL"
 
    ip_configuration {
      ipv4_enabled    = false
      private_network = var.vpc_id
    }
 
    backup_configuration {
      enabled                        = true
      point_in_time_recovery_enabled = true
      start_time                     = "03:00"
    }
  }
 
  deletion_protection = true
}
 
resource "google_sql_database" "app" {
  name     = "appdb"
  instance = google_sql_database_instance.postgres.name
  project  = var.project_id
}
 
resource "google_sql_user" "app" {
  name     = "appuser"
  instance = google_sql_database_instance.postgres.name
  project  = var.project_id
  password = var.db_password
}

Executando o provisionamento

Bash
# Inicializando o Terraform
terraform init
 
# Visualizando o plano de execução
terraform plan -var="project_id=meu-projeto-id" \
  -var="db_password=senha-segura-aqui" \
  -var="container_image=gcr.io/meu-projeto-id/app:latest"
 
# Aplicando as mudanças
terraform apply -var="project_id=meu-projeto-id" \
  -var="db_password=senha-segura-aqui" \
  -var="container_image=gcr.io/meu-projeto-id/app:latest"

Automatizando com CDKTF e TypeScript

Se você prefere escrever infraestrutura em TypeScript em vez de HCL, o CDK for Terraform (CDKTF) é a ferramenta ideal. Ele permite usar toda a tipagem e ferramentas do ecossistema TypeScript para definir recursos.

Exemplo 1: provisionando um bucket GCS com CDKTF

TYPESCRIPT
// main.ts
import { Construct } from "constructs";
import { App, TerraformStack, GcsBackend } from "cdktf";
import { GoogleProvider } from "@cdktf/provider-google/lib/provider";
import { StorageBucket } from "@cdktf/provider-google/lib/storage-bucket";
import { StorageBucketIamMember } from "@cdktf/provider-google/lib/storage-bucket-iam-member";
 
class StorageStack extends TerraformStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);
 
    const projectId = "meu-projeto-id";
    const region = "us-central1";
 
    new GoogleProvider(this, "google", {
      project: projectId,
      region: region,
    });
 
    new GcsBackend(this, {
      bucket: "meu-projeto-terraform-state",
      prefix: "cdktf/storage",
    });
 
    const dataBucket = new StorageBucket(this, "data-bucket", {
      name: `${projectId}-data-lake`,
      location: region,
      forceDestroy: false,
      uniformBucketLevelAccess: true,
      versioning: { enabled: true },
      lifecycleRule: [
        {
          condition: { age: 90 },
          action: { type: "SetStorageClass", storageClass: "NEARLINE" },
        },
        {
          condition: { age: 365 },
          action: { type: "SetStorageClass", storageClass: "COLDLINE" },
        },
      ],
    });
 
    new StorageBucketIamMember(this, "data-bucket-reader", {
      bucket: dataBucket.name,
      role: "roles/storage.objectViewer",
      member: `serviceAccount:app-sa@${projectId}.iam.gserviceaccount.com`,
    });
  }
}
 
const app = new App();
new StorageStack(app, "storage");
app.synth();

Exemplo 2: criando um cluster GKE com CDKTF

TYPESCRIPT
// gke-stack.ts
import { Construct } from "constructs";
import { App, TerraformStack, TerraformOutput } from "cdktf";
import { GoogleProvider } from "@cdktf/provider-google/lib/provider";
import { ContainerCluster } from "@cdktf/provider-google/lib/container-cluster";
import { ContainerNodePool } from "@cdktf/provider-google/lib/container-node-pool";
import { ComputeNetwork } from "@cdktf/provider-google/lib/compute-network";
import { ComputeSubnetwork } from "@cdktf/provider-google/lib/compute-subnetwork";
 
interface GkeStackConfig {
  projectId: string;
  region: string;
  environment: string;
  minNodes: number;
  maxNodes: number;
  machineType: string;
}
 
class GkeStack extends TerraformStack {
  constructor(scope: Construct, id: string, config: GkeStackConfig) {
    super(scope, id);
 
    new GoogleProvider(this, "google", {
      project: config.projectId,
      region: config.region,
    });
 
    const network = new ComputeNetwork(this, "gke-network", {
      name: `gke-vpc-${config.environment}`,
      autoCreateSubnetworks: false,
      project: config.projectId,
    });
 
    const subnet = new ComputeSubnetwork(this, "gke-subnet", {
      name: `gke-subnet-${config.environment}`,
      project: config.projectId,
      region: config.region,
      network: network.id,
      ipCidrRange: "10.0.0.0/20",
      secondaryIpRange: [
        { rangeName: "pods", ipCidrRange: "10.4.0.0/14" },
        { rangeName: "services", ipCidrRange: "10.8.0.0/20" },
      ],
    });
 
    const cluster = new ContainerCluster(this, "gke-cluster", {
      name: `app-cluster-${config.environment}`,
      project: config.projectId,
      location: config.region,

Guias Relacionados

Explore mais guias técnicos

Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.

Ver todos os guias