TL;DR
- Azure предоставляет deployment what-if для предварительной валидации — используйте в CI перед каждым apply
- Azurite эмулирует Storage, Queues и Tables локально — быстрее реального Azure для storage-тестов
- Ошибка #1: пропуск тестирования Azure Policy до тех пор, пока деплой не упадёт в продакшене
Подходит для: Команд, деплоящих в Azure с Terraform, Bicep или ARM templates Пропустить если: Вы только на AWS/GCP или используете Azure PaaS без инфраструктурного кода Время чтения: 10 минут
Ваш Terraform plan выглядит чистым. Деплой в Azure начинается. Двадцать минут спустя падает: “Azure Policy evaluation failed.” Вы тратите час выясняя, какая policy заблокировала деплой, затем ещё час на рефакторинг для соответствия. Тем временем команда заблокирована.
Тестирование Azure инфраструктуры имеет уникальные вызовы. Применение Azure Policy происходит во время деплоя. Соглашения об именовании ресурсов различаются по регионам. Eventual consistency при распространении Azure AD вызывает периодические сбои. Понимание этих паттернов — разница между гладким CI и постоянным тушением пожаров.
Реальная проблема
Azure вводит вызовы тестирования, отличные от AWS:
Azure Policy: Корпоративные Azure-подписки имеют policies, блокирующие несоответствующие деплои. Вы не узнаете о нарушениях пока terraform apply или az deployment не упадёт.
Регистрация Resource Provider: Первое использование сервиса в подписке требует регистрации provider. Тесты неожиданно падают в чистых подписках.
Задержки распространения Azure AD: Service principals, managed identities и role assignments требуют времени для распространения. Тесты, работающие локально, падают в CI.
Ограничения имён: Именование ресурсов Azure имеет сложные правила — storage accounts должны быть глобально уникальными, 3-24 строчных буквенно-цифровых символа. Key Vaults имеют другие правила. VMs имеют другие правила опять.
Deployment What-If
Операция what-if Azure валидирует деплои перед выполнением:
# ARM/Bicep what-if
az deployment group what-if \
--resource-group myResourceGroup \
--template-file main.bicep \
--parameters @params.json
# Деплой на уровне подписки
az deployment sub what-if \
--location eastus \
--template-file main.bicep
Для Terraform комбинируйте plan с Azure-специфичной валидацией:
# Генерировать plan
terraform plan -out=tfplan
# Конвертировать в JSON для анализа
terraform show -json tfplan > tfplan.json
# Проверить соответствие Azure Policy (требует Azure CLI)
az policy state trigger-scan --resource-group myResourceGroup
# Или использовать Checkov с Azure правилами
checkov -f tfplan.json --framework terraform_plan
Terratest для Azure
Terratest имеет Azure-специфичные модули:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/azure"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestAzureStorageAccount(t *testing.T) {
t.Parallel()
subscriptionID := azure.GetSubscriptionID()
uniqueID := random.UniqueId()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/storage-account",
Vars: map[string]interface{}{
"resource_group_name": "rg-test-" + uniqueID,
"storage_account_name": "sttest" + uniqueID,
"location": "eastus",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Получить outputs
resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name")
storageAccountName := terraform.Output(t, terraformOptions, "storage_account_name")
// Проверить что storage account существует и имеет правильные свойства
exists := azure.StorageAccountExists(t, storageAccountName, resourceGroupName, subscriptionID)
assert.True(t, exists)
// Проверить свойства storage account
storageAccount := azure.GetStorageAccount(t, storageAccountName, resourceGroupName, subscriptionID)
assert.Equal(t, "Standard_LRS", string(storageAccount.Sku.Name))
assert.True(t, *storageAccount.EnableHTTPSTrafficOnly)
}
func TestAzureVirtualNetwork(t *testing.T) {
t.Parallel()
subscriptionID := azure.GetSubscriptionID()
uniqueID := random.UniqueId()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/virtual-network",
Vars: map[string]interface{}{
"resource_group_name": "rg-test-" + uniqueID,
"vnet_name": "vnet-test-" + uniqueID,
"address_space": []string{"10.0.0.0/16"},
"location": "eastus",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vnetName := terraform.Output(t, terraformOptions, "vnet_name")
resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name")
// Проверить что VNet существует
exists := azure.VirtualNetworkExists(t, vnetName, resourceGroupName, subscriptionID)
assert.True(t, exists)
// Проверить subnets
subnets := azure.GetVirtualNetworkSubnets(t, vnetName, resourceGroupName, subscriptionID)
assert.GreaterOrEqual(t, len(subnets), 1)
}
Azurite для локального тестирования Storage
Azurite эмулирует сервисы Azure Storage локально:
# Установить через npm
npm install -g azurite
# Запустить все сервисы
azurite --silent --location ./azurite-data --debug ./azurite-debug.log
# Или через Docker
docker run -d \
-p 10000:10000 \
-p 10001:10001 \
-p 10002:10002 \
-v azurite-data:/data \
mcr.microsoft.com/azure-storage/azurite
Настроить Terraform для использования Azurite:
provider "azurerm" {
features {}
# Для Azurite переопределить storage endpoints
# Примечание: Полный AzureRM не поддерживает Azurite напрямую
# Используйте этот паттерн для тестирования кода приложения, не полного Terraform
}
# Для тестирования приложения со storage
resource "null_resource" "test_storage" {
provisioner "local-exec" {
command = <<-EOT
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1"
python test_storage_operations.py
EOT
}
}
Тесты на Python с Azurite:
import os
from azure.storage.blob import BlobServiceClient
def test_blob_operations():
# Строка подключения Azurite
connection_string = os.environ.get(
"AZURE_STORAGE_CONNECTION_STRING",
"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1"
)
blob_service = BlobServiceClient.from_connection_string(connection_string)
# Создать container
container_client = blob_service.create_container("test-container")
# Загрузить blob
blob_client = container_client.get_blob_client("test-blob.txt")
blob_client.upload_blob("Hello, Azure!", overwrite=True)
# Скачать и проверить
downloaded = blob_client.download_blob().readall()
assert downloaded == b"Hello, Azure!"
# Очистка
container_client.delete_container()
Тестирование Bicep с What-If
Для Bicep деплоев интегрируйте what-if в CI:
# azure-pipelines.yml
trigger:
paths:
include:
- infra/**
stages:
- stage: Validate
jobs:
- job: BicepValidation
pool:
vmImage: ubuntu-latest
steps:
- task: AzureCLI@2
displayName: 'Bicep Lint'
inputs:
azureSubscription: 'MyServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az bicep build --file infra/main.bicep --stdout > /dev/null
- task: AzureCLI@2
displayName: 'What-If Analysis'
inputs:
azureSubscription: 'MyServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group what-if \
--resource-group $(ResourceGroup) \
--template-file infra/main.bicep \
--parameters infra/params.$(Environment).json
- stage: Deploy
dependsOn: Validate
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployInfra
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'MyServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group create \
--resource-group $(ResourceGroup) \
--template-file infra/main.bicep \
--parameters infra/params.$(Environment).json
Тестирование Azure Policy
Тестируйте соответствие Azure Policy перед деплоем:
func TestAzurePolicyCompliance(t *testing.T) {
t.Parallel()
subscriptionID := azure.GetSubscriptionID()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/storage-account",
Vars: map[string]interface{}{
"resource_group_name": "rg-policy-test",
"storage_account_name": "stpolicytest" + random.UniqueId(),
// Намеренно несоответствующий для тестирования
"enable_https_only": false,
},
}
// Не авто-удалять - хотим проверить состояние policy
terraform.Init(t, terraformOptions)
// Plan должен успешно выполниться
terraform.Plan(t, terraformOptions)
// Но apply должен упасть из-за policy
_, err := terraform.ApplyE(t, terraformOptions)
// Утверждаем что ошибка связана с policy
assert.Error(t, err)
assert.Contains(t, err.Error(), "PolicyViolation")
// Очистить неудачный деплой
terraform.Destroy(t, terraformOptions)
}
Запросить соответствие policy программно:
# Запустить оценку policy
az policy state trigger-scan --resource-group myResourceGroup
# Проверить состояние соответствия
az policy state list \
--resource-group myResourceGroup \
--filter "complianceState eq 'NonCompliant'" \
--query "[].{Resource:resourceId, Policy:policyDefinitionName}"
Интеграция CI/CD
GitHub Actions для Azure инфраструктуры:
name: Azure Infrastructure
on:
pull_request:
paths:
- 'terraform/**'
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform
- name: Terraform Validate
run: terraform validate
working-directory: terraform
- name: Terraform Plan
id: plan
run: terraform plan -out=tfplan -no-color
working-directory: terraform
continue-on-error: true
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const output = `#### Terraform Plan 📖
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\`
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
terratest:
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run Terratest
run: go test -v -timeout 30m ./tests/...
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_USE_OIDC: true
Подходы с помощью ИИ
Azure имеет сложные правила именования и взаимодействия policy. ИИ-инструменты помогают навигировать.
Что ИИ делает хорошо:
- Генерация соответствующих имён ресурсов для Azure naming conventions
- Перевод определений Azure Policy в assertions тестов
- Создание кода Terratest из спецификаций Azure ресурсов
- Объяснение Azure-специфичных сообщений об ошибках и решений
Что всё ещё требует людей:
- Понимание организационных требований Azure Policy
- Проектирование архитектуры тестов для сложных Azure Landing Zones
- Решение какие тесты нужны с реальным Azure vs локальная эмуляция
- Отладка проблем timing распространения Azure AD
Полезный промпт:
У меня есть Azure Terraform модуль который создаёт:
- Resource Group
- Storage Account с blob containers
- Key Vault с access policies
- Azure Functions с managed identity
Сгенерируй:
1. Код Terratest для валидации всех ресурсов
2. Проверки Azure Policy которые нужно включить
3. Типичные Azure-специфичные подводные камни для тестирования
4. Тесты Azurite для операций со storage
Когда это не работает
Тестирование Azure инфраструктуры имеет ограничения:
Timing Azure AD: Role assignments и распространение managed identity могут занимать минуты. Тестам нужна логика retry и задержки.
Региональные различия: Некоторые сервисы недоступны во всех регионах. Тесты работающие в eastus падают в других регионах.
Ресурсы уровня подписки: Management groups, подписки и некоторые policies требуют повышенных прав которых CI service principals могут не иметь.
Стоимость очистки: Неудачные Terraform destroys оставляют осиротевшие ресурсы. У Azure нет таких же инструментов очистки как у AWS.
Рассмотрите дополнительные подходы:
- Мульти-облачное тестирование для переносимых паттернов
- Azure DevTest Labs для изолированных тестовых окружений
- Policy as Code для предварительной валидации
Framework принятия решений
Используйте Azurite когда:
- Тестируете код приложения использующий Azure Storage
- Скорость критична (Azurite мгновенный)
- Требуется сетевая изоляция
Используйте what-if когда:
- Валидация Bicep/ARM деплоев
- Проверка соответствия Azure Policy
- Предварительный просмотр изменений
Используйте Terratest с реальным Azure когда:
- Тестируете полные модули инфраструктуры
- Валидация cross-resource интеграций
- Финальная валидация перед продакшеном
Измерение успеха
| Метрика | До | После | Как отслеживать |
|---|---|---|---|
| Сбои Azure Policy в CI | Частые | 0 | Логи деплоя |
| Время выполнения тестов | 20+ мин | <10 мин | Метрики CI |
| Осиротевшие тестовые ресурсы | Неизвестно | 0 | Azure Cost Management |
| Успешность первого деплоя | 60% | 95%+ | История деплоев |
Предупреждающие знаки что не работает:
- what-if проходит но deploy падает
- Нестабильные тесты из-за timing Azure AD
- Растущий список задач ручной очистки
- Команды обходят CI для “быстрых” деплоев
Что дальше
Начните с валидации, затем расширяйтесь до интеграции:
- Добавьте
az deployment what-ifв каждый PR - Внедрите Checkov для сканирования Azure policy
- Настройте Azurite для локального тестирования storage
- Добавьте Terratest для критичных модулей инфраструктуры
- Настройте автоматизацию очистки для упавших тестов
- Отслеживайте Azure-специфичные метрики тестирования
Цель — ловить Azure-специфичные проблемы до деплоя, а не после.
Связанные статьи:
- Тестирование AWS инфраструктуры с LocalStack
- Тестирование мульти-облачной инфраструктуры
- Policy as Code Testing: OPA vs Sentinel
- Стратегии тестирования и валидации Terraform
Внешние ресурсы:
Официальные ресурсы
See Also
- Тестирование Сетевой Конфигурации: Batfish, Terraform и Валидация VPC для Облачной Инфраструктуры - Освойте тестирование сетевой конфигурации с Batfish для…
- Тестирование Масштабируемости Инфраструктуры: Валидация Auto-Scaling с K6, Locust и Terraform - Освойте тестирование масштабируемости инфраструктуры с K6, Locust…
- Тестирование Security Groups: Валидация AWS Security Groups, Azure NSGs и Правил Firewall GCP - Освойте тестирование security groups в AWS, Azure и GCP с Checkov,…
- Тестирование оценки затрат для Infrastructure as Code: Полное руководство - Освойте тестирование оценки затрат для IaC с Infracost, анализом…
