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-end | Terratest + тесты приложения | Среды типа 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 |
| Находки аудита compliance | 5-10/квартал | 0-1/квартал | Отчёты аудита |
| Среднее время обнаружения проблем | Дни | Минуты | Timestamps алертов |
Сигналы, что ваше тестирование не работает:
- Нарушения политик всё ещё достигают production
- Terratest проходит, но реальные проблемы в развёрнутых ресурсах
- Тесты занимают >30 минут (вероятна утечка ресурсов)
- Flaky тесты из-за eventual consistency
Заключение
Тестирование инфраструктуры GCP комбинирует универсальные практики тестирования IaC с инструментами, специфичными для Google:
- Используйте Terratest для интеграционных тестов, требующих реальных ресурсов GCP
- Реализуйте constraints Config Validator для организационных политик
- Запускайте gcloud terraform vet в CI/CD для раннего обнаружения нарушений
- Используйте Policy Library для общих паттернов compliance
Ключ в слоистости этих инструментов—статический анализ ловит проблемы синтаксиса, валидация политик ловит проблемы compliance, а интеграционные тесты ловят функциональные проблемы.
Смотрите также
- Стратегии Тестирования и Валидации Terraform - Полная пирамида тестирования Terraform
- Тестирование Инфраструктуры AWS с LocalStack - Сравнение подходов к тестированию AWS
- Стратегии Тестирования Kubernetes - Тестирование GKE и контейнеров
- Тестирование Policy as Code с OPA и Sentinel - Глубокое погружение в языки политик
- Тестирование Infrastructure as Code - Фундаментальные концепции тестирования IaC
