TL;DR
- Usa Terratest para testing de integración de módulos Terraform en GCP—despliega recursos reales, verifica con APIs de GCP, luego destruye
- Combina
gcloud terraform vetcon constraints de Policy Library para validación de políticas pre-deployment- Testing nativo de Terraform (v1.6+) maneja tests unitarios; Terratest maneja tests de integración que necesitan recursos GCP reales
Ideal para: Equipos gestionando infraestructura GCP con Terraform que necesitan validación de compliance y confianza en deployments No recomendado si: Usas exclusivamente GCP Console o tienes menos de 5 módulos Terraform Tiempo de lectura: 12 minutos
El testing de infraestructura GCP difiere del testing de AWS o Azure debido al modelo único de recursos de Google, estructura IAM y el ecosistema de Config Validator. Esta guía cubre enfoques prácticos de testing que aprovechan herramientas nativas de GCP junto con patrones universales de testing de infraestructura.
Entender la pirámide de testing de Terraform proporciona contexto sobre dónde encaja el testing específico de GCP dentro de tu estrategia general de validación de IaC.
Enfoques Asistidos por IA
Las herramientas de IA aceleran el desarrollo de tests de infraestructura GCP, especialmente para generar constraint templates y assertions de Terratest.
Generando assertions de Terratest para recursos GCP:
Tengo un módulo Terraform que crea:
- GCP Compute Instance con metadata personalizada
- Red VPC con reglas de firewall
- Bucket de Cloud Storage con políticas de lifecycle
Genera assertions de Terratest en Go que verifiquen:
1. La instancia está corriendo y tiene el machine type correcto
2. Las reglas de firewall solo permiten puertos 443 y 22
3. El bucket tiene versioning habilitado y regla de lifecycle para eliminación a 30 días
4. Todos los recursos tienen labels requeridas: environment, team, cost-center
Incluye manejo de errores apropiado y usa paquetes google.golang.org/api.
Creando constraints de Config Validator:
Escribe un constraint template de Rego para Config Validator que aplique:
1. Todos los buckets GCS deben tener uniform bucket-level access habilitado
2. Todas las instancias Compute no deben tener direcciones IP externas
3. Todas las VPCs deben tener flow logs habilitados
Usa el formato de GCP Policy Library con tipos de asset CAI correctos
e incluye archivos YAML de constraint de ejemplo.
Depurando validaciones fallidas de terraform vet:
Mi comando terraform vet falla con esta salida:
[pegar salida de vet]
El constraint es:
[pegar YAML del constraint]
Mi plan de Terraform incluye:
[pegar salida relevante del terraform plan]
Explica por qué falla la validación y cómo arreglar
el código Terraform o el constraint.
Cuándo Usar Cada Enfoque de Testing
Framework de Decisión de Estrategia de Testing
| Tipo de Test | Herramienta | Cuándo Usar | Recursos GCP Requeridos |
|---|---|---|---|
| Validación de sintaxis | terraform validate | Cada commit | Ninguno |
| Análisis estático | tflint-ruleset-google | Cada commit | Ninguno |
| Pre-check de políticas | gcloud terraform vet | Antes de plan/apply | Solo acceso a API |
| Testing unitario | Terraform test (nativo) | Lógica de módulo | Ninguno (mockeado) |
| Testing de integración | Terratest | Validación de recursos reales | Acceso completo al proyecto |
| End-to-end | Terratest + tests de aplicación | Entornos tipo producción | Proyecto de test dedicado |
Usa Terratest Cuando
- Necesitas verificar propiedades reales de recursos GCP: Metadata, labels, configs de red
- Testing de interacciones entre recursos: IAM bindings, reglas de firewall afectando instancias
- Validando comportamientos específicos de GCP: Scaling de managed instance groups, failover de Cloud SQL
- CI/CD requiere verificación de deployment: Los recursos deben existir antes de proceder
Usa Config Validator/terraform vet Cuando
- Aplicando políticas organizacionales: Sin IPs públicas, encriptación requerida, regiones específicas
- Checks de compliance pre-deployment: Bloquear planes no conformes antes del apply
- Estandarizando entre equipos: Tagging consistente, convenciones de nombres, configuraciones de recursos
Terratest para Módulos GCP
Estructura Básica de Test de Módulo
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/gcp"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestGCPComputeInstance(t *testing.T) {
t.Parallel()
projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
region := "us-central1"
zone := "us-central1-a"
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/compute",
Vars: map[string]interface{}{
"project_id": projectID,
"region": region,
"zone": zone,
"instance_name": "test-instance-" + random.UniqueId(),
"machine_type": "e2-micro",
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Verificar propiedades de la instancia
instanceName := terraform.Output(t, terraformOptions, "instance_name")
instance := gcp.FetchInstance(t, projectID, zone, instanceName)
assert.Equal(t, "RUNNING", instance.Status)
assert.Contains(t, instance.MachineType, "e2-micro")
}
Testing VPC y Reglas de Firewall
func TestGCPNetworkWithFirewall(t *testing.T) {
t.Parallel()
projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/network",
Vars: map[string]interface{}{
"project_id": projectID,
"network_name": "test-vpc-" + random.UniqueId(),
"allowed_ports": []string{"443", "22"},
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
networkName := terraform.Output(t, terraformOptions, "network_name")
// Verificar reglas de firewall
firewalls := gcp.GetFirewallRulesForNetwork(t, projectID, networkName)
for _, fw := range firewalls {
// Asegurar que ninguna regla permite 0.0.0.0/0 en puertos sensibles
for _, allowed := range fw.Allowed {
if contains(fw.SourceRanges, "0.0.0.0/0") {
assert.NotContains(t, allowed.Ports, "3389",
"RDP no debe estar abierto a internet")
assert.NotContains(t, allowed.Ports, "3306",
"MySQL no debe estar abierto a internet")
}
}
}
}
Testing Cloud Storage con Reglas de Lifecycle
func TestGCSBucketCompliance(t *testing.T) {
t.Parallel()
projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
bucketName := "test-bucket-" + strings.ToLower(random.UniqueId())
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/storage",
Vars: map[string]interface{}{
"project_id": projectID,
"bucket_name": bucketName,
"location": "US",
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Usar biblioteca cliente de GCP para inspección detallada del bucket
ctx := context.Background()
client, err := storage.NewClient(ctx)
require.NoError(t, err)
defer client.Close()
bucket := client.Bucket(bucketName)
attrs, err := bucket.Attrs(ctx)
require.NoError(t, err)
// Verificar requisitos de compliance
assert.True(t, attrs.UniformBucketLevelAccess.Enabled,
"Uniform bucket-level access debe estar habilitado")
assert.True(t, attrs.VersioningEnabled,
"Versioning debe estar habilitado")
assert.NotEmpty(t, attrs.Lifecycle.Rules,
"Reglas de lifecycle deben estar configuradas")
}
Config Validator y Policy Library
Configurando Policy Library
# Clonar la policy library de GCP
git clone https://github.com/GoogleCloudPlatform/policy-library.git
cd policy-library
# Estructura
# policies/
# constraints/ # Tus definiciones de constraints
# templates/ # Constraint templates (reglas Rego)
Creando Constraints Personalizados
# policies/constraints/require_bucket_versioning.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPStorageBucketVersioningConstraintV1
metadata:
name: require-bucket-versioning
spec:
severity: high
match:
ancestries:
- "organizations/123456789"
excludedAncestries: []
parameters:
versioning_enabled: true
Ejemplo de Constraint Template
# policies/templates/gcp_storage_bucket_versioning.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: gcpstoragebucketversioningconstraintv1
spec:
crd:
spec:
names:
kind: GCPStorageBucketVersioningConstraintV1
validation:
openAPIV3Schema:
properties:
versioning_enabled:
type: boolean
targets:
- target: validation.gcp.forsetisecurity.org
rego: |
package templates.gcp.GCPStorageBucketVersioningConstraintV1
violation[{
"msg": message,
"details": metadata,
}] {
asset := input.asset
asset.asset_type == "storage.googleapis.com/Bucket"
versioning := asset.resource.data.versioning
not versioning.enabled
message := sprintf("Bucket %v no tiene versioning habilitado", [asset.name])
metadata := {"resource": asset.name}
}
Usando gcloud terraform vet
# Generar plan de Terraform en formato JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
# Validar contra policy library
gcloud beta terraform vet tfplan.json \
--policy-library=./policy-library \
--project=my-project
# En pipeline CI/CD
gcloud beta terraform vet tfplan.json \
--policy-library=./policy-library \
--format=json > violations.json
# Verificar violaciones
if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
echo "Se encontraron violaciones de política:"
jq '.violations[] | .message' violations.json
exit 1
fi
Integración CI/CD
Workflow de GitHub Actions
name: GCP Infrastructure Tests
on:
pull_request:
paths:
- 'terraform/**'
- 'policies/**'
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
- name: Terraform Validate
run: terraform validate
working-directory: terraform/
- name: TFLint con ruleset GCP
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: latest
- run: |
tflint --init
tflint --format=compact
working-directory: terraform/
policy-validation:
runs-on: ubuntu-latest
needs: static-analysis
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Autenticar en GCP
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@v2
- name: Generar plan
run: |
terraform init
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
working-directory: terraform/
- name: Validación de políticas
run: |
gcloud beta terraform vet tfplan.json \
--policy-library=../policies \
--format=json > violations.json
if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
echo "::error::Violaciones de política detectadas"
jq -r '.violations[] | "- \(.constraint): \(.message)"' violations.json
exit 1
fi
working-directory: terraform/
integration-tests:
runs-on: ubuntu-latest
needs: policy-validation
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Autenticar en GCP
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Ejecutar Terratest
run: |
cd test
go test -v -timeout 30m -parallel 4
env:
GOOGLE_PROJECT: ${{ secrets.GCP_TEST_PROJECT }}
Patrones Comunes de Testing GCP
Testing de IAM Bindings
func TestIAMBindings(t *testing.T) {
projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
// Después de terraform apply...
// Verificar que service account tiene roles esperados
saEmail := terraform.Output(t, terraformOptions, "service_account_email")
policy, err := getProjectIAMPolicy(projectID)
require.NoError(t, err)
expectedRole := "roles/storage.objectViewer"
member := "serviceAccount:" + saEmail
found := false
for _, binding := range policy.Bindings {
if binding.Role == expectedRole {
for _, m := range binding.Members {
if m == member {
found = true
break
}
}
}
}
assert.True(t, found, "Service account debe tener rol %s", expectedRole)
}
Testing Private Google Access
func TestPrivateGoogleAccess(t *testing.T) {
projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
// Después de crear subnet...
subnetName := terraform.Output(t, terraformOptions, "subnet_name")
region := "us-central1"
subnet := gcp.FetchSubnet(t, projectID, region, subnetName)
assert.True(t, subnet.PrivateIpGoogleAccess,
"Private Google Access debe estar habilitado para subnets privadas")
}
Midiendo el Éxito
| Métrica | Antes del Testing | Después del Testing | Cómo Rastrear |
|---|---|---|---|
| Violaciones de política en prod | Desconocido | 0 | Reportes de Config Validator |
| Deployments fallidos | 15%/mes | <2%/mes | Métricas de pipeline CI/CD |
| Hallazgos de auditoría de compliance | 5-10/trimestre | 0-1/trimestre | Reportes de auditoría |
| Tiempo medio para detectar issues | Días | Minutos | Timestamps de alertas |
Señales de advertencia de que tu testing no funciona:
- Violaciones de política aún llegando a producción
- Terratest pasando pero issues reales en recursos desplegados
- Tests tomando >30 minutos (probable fuga de recursos)
- Tests flaky debido a consistencia eventual
Conclusión
El testing de infraestructura GCP combina prácticas universales de testing IaC con herramientas específicas de Google:
- Usa Terratest para tests de integración que requieren recursos GCP reales
- Implementa constraints de Config Validator para políticas organizacionales
- Ejecuta gcloud terraform vet en CI/CD para detectar violaciones temprano
- Aprovecha la Policy Library para patrones comunes de compliance
La clave es estratificar estas herramientas—el análisis estático detecta issues de sintaxis, la validación de políticas detecta issues de compliance, y los tests de integración detectan issues funcionales.
Ver También
- Estrategias de Testing y Validación de Terraform - Pirámide completa de testing Terraform
- Testing de Infraestructura AWS con LocalStack - Comparar enfoques de testing AWS
- Estrategias de Testing de Kubernetes - Testing de GKE y contenedores
- Testing de Policy as Code con OPA y Sentinel - Profundización en lenguajes de políticas
- Testing de Infrastructure as Code - Conceptos fundamentales de testing IaC
