TL;DR

  • Используйте Terratest для интеграционного тестирования модулей Terraform в GCP—разворачивайте реальные ресурсы, проверяйте через API GCP, затем удаляйте
  • Комбинируйте gcloud terraform vet с constraints Policy Library для валидации политик перед деплоем
  • Нативное тестирование Terraform (v1.6+) обрабатывает юнит-тесты; Terratest обрабатывает интеграционные тесты, требующие реальных ресурсов GCP

Идеально для: Команд, управляющих инфраструктурой GCP с Terraform, которым нужна валидация compliance и уверенность в деплоях Не подходит если: Вы используете исключительно GCP Console или имеете менее 5 модулей Terraform Время чтения: 12 минут

Тестирование инфраструктуры GCP отличается от тестирования AWS или Azure из-за уникальной модели ресурсов Google, структуры IAM и экосистемы Config Validator. Это руководство охватывает практические подходы к тестированию, которые используют нативные инструменты GCP наряду с универсальными паттернами тестирования инфраструктуры.

Понимание пирамиды тестирования Terraform даёт контекст о том, где специфичное для GCP тестирование вписывается в вашу общую стратегию валидации IaC.

Подходы с Использованием ИИ

Инструменты ИИ ускоряют разработку тестов инфраструктуры GCP, особенно для генерации constraint templates и assertions Terratest.

Генерация assertions Terratest для ресурсов GCP:

У меня есть модуль Terraform, который создаёт:

- GCP Compute Instance с кастомными метаданными
- VPC сеть с правилами firewall
- Cloud Storage bucket с политиками lifecycle

Сгенерируй assertions Terratest на Go, которые проверяют:

1. Инстанс запущен и имеет правильный machine type
2. Правила firewall разрешают только порты 443 и 22
3. Bucket имеет включённый versioning и правило lifecycle для удаления через 30 дней
4. Все ресурсы имеют требуемые labels: environment, team, cost-center

Включи правильную обработку ошибок и используй пакеты google.golang.org/api.

Создание constraints Config Validator:

Напиши constraint template на Rego для Config Validator, который применяет:

1. Все GCS buckets должны иметь включённый uniform bucket-level access
2. Все Compute instances не должны иметь внешних IP-адресов
3. Все VPC должны иметь включённые flow logs

Используй формат GCP Policy Library с правильными типами asset CAI
и включи примеры YAML файлов constraint.

Отладка неуспешных валидаций terraform vet:

Моя команда terraform vet завершается с ошибкой с этим выводом:
[вставить вывод vet]

Constraint такой:
[вставить YAML constraint]

Мой план Terraform включает:
[вставить релевантный вывод terraform plan]

Объясни, почему валидация не проходит и как исправить
код Terraform или constraint.

Когда Использовать Каждый Подход к Тестированию

Фреймворк Принятия Решений по Стратегии Тестирования

Тип ТестаИнструментКогда ИспользоватьТребуемые Ресурсы GCP
Валидация синтаксисаterraform validateКаждый коммитНет
Статический анализtflint-ruleset-googleКаждый коммитНет
Pre-check политикgcloud terraform vetПеред plan/applyТолько доступ к API
Юнит-тестированиеTerraform test (нативный)Логика модуляНет (мокируется)
Интеграционное тестированиеTerratestВалидация реальных ресурсовПолный доступ к проекту
End-to-endTerratest + тесты приложенияСреды типа productionВыделенный тестовый проект

Используйте Terratest Когда

  • Нужно проверить реальные свойства ресурсов GCP: Метаданные, labels, конфигурации сети
  • Тестирование взаимодействий между ресурсами: IAM bindings, правила firewall, влияющие на инстансы
  • Валидация специфичных для GCP поведений: Масштабирование managed instance groups, failover Cloud SQL
  • CI/CD требует верификации деплоя: Ресурсы должны существовать перед продолжением

Используйте Config Validator/terraform vet Когда

  • Применение организационных политик: Без публичных IP, требуется шифрование, определённые регионы
  • Проверки compliance перед деплоем: Блокировать несоответствующие планы перед apply
  • Стандартизация между командами: Консистентный tagging, конвенции именования, конфигурации ресурсов

Terratest для Модулей GCP

Базовая Структура Теста Модуля

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)

    // Проверяем свойства инстанса
    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")
}

Тестирование VPC и Правил 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")

    // Проверяем правила firewall
    firewalls := gcp.GetFirewallRulesForNetwork(t, projectID, networkName)

    for _, fw := range firewalls {
        // Убеждаемся, что ни одно правило не разрешает 0.0.0.0/0 на чувствительные порты
        for _, allowed := range fw.Allowed {
            if contains(fw.SourceRanges, "0.0.0.0/0") {
                assert.NotContains(t, allowed.Ports, "3389",
                    "RDP не должен быть открыт в интернет")
                assert.NotContains(t, allowed.Ports, "3306",
                    "MySQL не должен быть открыт в интернет")
            }
        }
    }
}

Тестирование Cloud Storage с Правилами 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)

    // Используем клиентскую библиотеку GCP для детальной инспекции 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)

    // Проверяем требования compliance
    assert.True(t, attrs.UniformBucketLevelAccess.Enabled,
        "Uniform bucket-level access должен быть включён")
    assert.True(t, attrs.VersioningEnabled,
        "Versioning должен быть включён")
    assert.NotEmpty(t, attrs.Lifecycle.Rules,
        "Правила lifecycle должны быть настроены")
}

Config Validator и Policy Library

Настройка Policy Library

# Клонируем policy library GCP
git clone https://github.com/GoogleCloudPlatform/policy-library.git
cd policy-library

# Структура
# policies/
#   constraints/    # Ваши определения constraints
#   templates/      # Constraint templates (правила Rego)

Создание Кастомных Constraints

# 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

Пример 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 не имеет включённого versioning", [asset.name])
            metadata := {"resource": asset.name}
        }

Использование gcloud terraform vet

# Генерируем план Terraform в формате JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Валидируем против policy library
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --project=my-project

# В CI/CD pipeline
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --format=json > violations.json

# Проверяем нарушения
if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
    echo "Найдены нарушения политик:"
    jq '.violations[] | .message' violations.json
    exit 1
fi

Интеграция CI/CD

Workflow 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 с 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: Аутентификация в 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: Генерация плана
        run: |
          terraform init
          terraform plan -out=tfplan.binary
          terraform show -json tfplan.binary > tfplan.json
        working-directory: terraform/

      - name: Валидация политик
        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::Обнаружены нарушения политик"
            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: Аутентификация в GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Запуск Terratest
        run: |
          cd test
          go test -v -timeout 30m -parallel 4
        env:
          GOOGLE_PROJECT: ${{ secrets.GCP_TEST_PROJECT }}

Распространённые Паттерны Тестирования GCP

Тестирование IAM Bindings

func TestIAMBindings(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // После terraform apply...

    // Проверяем, что service account имеет ожидаемые роли
    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 должен иметь роль %s", expectedRole)
}

Тестирование Private Google Access

func TestPrivateGoogleAccess(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // После создания 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 должен быть включён для приватных subnets")
}

Измерение Успеха

МетрикаДо ТестированияПосле ТестированияКак Отслеживать
Нарушения политик в prodНеизвестно0Отчёты Config Validator
Неудачные деплои15%/месяц<2%/месяцМетрики pipeline CI/CD
Находки аудита compliance5-10/квартал0-1/кварталОтчёты аудита
Среднее время обнаружения проблемДниМинутыTimestamps алертов

Сигналы, что ваше тестирование не работает:

  • Нарушения политик всё ещё достигают production
  • Terratest проходит, но реальные проблемы в развёрнутых ресурсах
  • Тесты занимают >30 минут (вероятна утечка ресурсов)
  • Flaky тесты из-за eventual consistency

Заключение

Тестирование инфраструктуры GCP комбинирует универсальные практики тестирования IaC с инструментами, специфичными для Google:

  1. Используйте Terratest для интеграционных тестов, требующих реальных ресурсов GCP
  2. Реализуйте constraints Config Validator для организационных политик
  3. Запускайте gcloud terraform vet в CI/CD для раннего обнаружения нарушений
  4. Используйте Policy Library для общих паттернов compliance

Ключ в слоистости этих инструментов—статический анализ ловит проблемы синтаксиса, валидация политик ловит проблемы compliance, а интеграционные тесты ловят функциональные проблемы.

Смотрите также

Официальные ресурсы