Separar dev de prod na GCP tem duas abordagens principais: projetos GCP distintos ou workspaces do Terraform. A escolha muda radicalmente o isolamento real de recursos, IAM e cobrança.
Projetos separados (recomendado para dados)
Cada ambiente tem seu próprio projeto GCP. Billing, IAM, quotas e limites de API são isolados por padrão. Um bug em dev não pode consumir as quotas de prod nem afetar recursos do ambiente de produção.
organização/
├── meu-projeto-dev (projeto GCP isolado)
└── meu-projeto-prod (projeto GCP isolado)
Estrutura de arquivos para essa abordagem:
infra/
├── modules/
│ ├── storage/
│ │ └── main.tf
│ ├── bigquery/
│ │ └── main.tf
│ └── looker/
│ └── main.tf
├── envs/
│ ├── dev/
│ │ ├── main.tf ← chama os módulos
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── backend.tf
└── schemas/
└── pedidos.json ← schemas BigQuery compartilhados
O backend.tf de cada ambiente aponta para um bucket de state distinto:
# envs/dev/backend.tf
terraform {
backend "gcs" {
bucket = "meu-projeto-tfstate-dev"
prefix = "infra/terraform.tfstate"
}
}
# envs/prod/backend.tf
terraform {
backend "gcs" {
bucket = "meu-projeto-tfstate-prod"
prefix = "infra/terraform.tfstate"
}
}Workspaces (adequado para ambientes simples)
Workspaces compartilham o mesmo projeto GCP. Adequado quando isolamento de IAM e billing não é crítico (times pequenos, workloads não-regulados).
locals {
ambiente = terraform.workspace # "dev" ou "prod"
prefixo = "meu-projeto-${local.ambiente}"
config = {
dev = {
bigquery_edition = "STANDARD"
bq_delete_on_destroy = true
bq_table_expiration = 2592000000 # 30d em ms
slot_count = 0
}
prod = {
bigquery_edition = "ENTERPRISE"
bq_delete_on_destroy = false
bq_table_expiration = null
slot_count = 100
}
}
cfg = local.config[local.ambiente]
}terraform workspace new dev
terraform workspace select dev
terraform apply -var-file="dev.tfvars"
terraform workspace new prod
terraform workspace select prod
terraform apply -var-file="prod.tfvars"Variáveis por ambiente
# variables.tf (compartilhado entre envs)
variable "projeto_id" { type = string }
variable "regiao" { type = string }
variable "ambiente" { type = string }
variable "prefixo" { type = string }
variable "bigquery_edition" {
type = string
description = "Edição do BigQuery Reservations. Valores: STANDARD, ENTERPRISE, ENTERPRISE_PLUS"
default = "STANDARD"
}# dev.tfvars
projeto_id = "meu-projeto-dev"
regiao = "southamerica-east1"
ambiente = "dev"
prefixo = "meu-projeto-dev"
bigquery_edition = "STANDARD"
# prod.tfvars
projeto_id = "meu-projeto-prod"
regiao = "southamerica-east1"
ambiente = "prod"
prefixo = "meu-projeto-prod"
bigquery_edition = "ENTERPRISE"Labels obrigatórias
Labels são a base para filtros de custo no Billing e auditoria de recursos. Todo recurso criado deve receber o bloco:
locals {
labels = {
ambiente = var.ambiente # "dev" | "prod"
projeto = var.prefixo
managedby = "terraform"
time = "dados" # identifica o time responsável
}
}GCS, BigQuery, Compute e a maioria dos recursos GCP usam o campo labels (não tags). O nome é consistente no provider Google.
Habilitando APIs por ambiente
APIs precisam ser habilitadas uma vez por projeto. Use google_project_service para garantir isso via código:
resource "google_project_service" "apis" {
for_each = toset([
"bigquery.googleapis.com",
"storage.googleapis.com",
"looker.googleapis.com",
"bigqueryreservation.googleapis.com",
"bigqueryconnection.googleapis.com",
])
service = each.value
disable_on_destroy = false
}disable_on_destroy = false é importante: se outro time usa a mesma API no projeto, desabilitá-la ao destruir o state quebraria os recursos deles.
Service Accounts por ambiente
Crie SAs com escopo mínimo para cada serviço. Nunca use a SA padrão do projeto em workloads de dados:
resource "google_service_account" "pipeline" {
account_id = "${local.prefixo}-pipeline"
display_name = "Pipeline de Dados - ${var.ambiente}"
}
# Permissão mínima: BigQuery Job User (para executar queries)
resource "google_project_iam_member" "pipeline_bq_job" {
project = var.projeto_id
role = "roles/bigquery.jobUser"
member = "serviceAccount:${google_service_account.pipeline.email}"
}Promoção dev → prod
# 1. Apply em dev
cd envs/dev
terraform plan -out=dev.plan
terraform apply dev.plan
# 2. Validação: smoke tests, dados de exemplo, checks de schema...
# 3. Apply em prod (mesmo código, tfvars diferente)
cd ../prod
terraform plan -out=prod.plan
terraform apply prod.planO gap entre os dois apply é o gate de qualidade. Nenhuma mudança de infraestrutura vai para prod sem ter sido aplicada e validada em dev primeiro.
Comparação das abordagens
| Critério | Projetos separados | Workspaces |
|---|---|---|
| Isolamento de billing | Total | Nenhum |
| Isolamento de IAM | Total | Parcial (mesmas SAs) |
| Isolamento de quotas | Total | Nenhum |
| Complexidade de setup | Maior | Menor |
| Risco de blast radius | Baixo | Médio |
| Adequado para | Dados regulados, times maiores | Projetos simples, prototipação |
Conexões
Ver também: terraform-cloud-gcp | terraform-variaveis | terraform-state | terraform-gcp-buckets | terraform-gcp-bigquery | terraform-gcp-looker