So sánh IaC Tools
| Tiêu chí | Bicep | Terraform | ARM Templates |
|---|---|---|---|
| Provider | Microsoft | HashiCorp | Microsoft |
| Syntax | DSL đơn giản | HCL | JSON verbose |
| State management | Không cần (Azure native) | Remote state (S3, Blob) | Không cần |
| Multi-cloud | Chỉ Azure | AWS, Azure, GCP, ... | Chỉ Azure |
| Learning curve | Thấp | Trung bình | Cao (JSON phức tạp) |
| Module ecosystem | Azure Verified Modules | Terraform Registry rất lớn | Linked templates |
| Preview changes | what-if | terraform plan | what-if |
| IDE support | VS Code extension tốt | VS Code + IntelliJ | VS Code |
| Rollback | Không tự động | terraform destroy + apply | Không tự động |
Trade-off: Bicep là lựa chọn tốt nhất nếu chỉ dùng Azure (zero state management, native support). Terraform phù hợp multi-cloud hoặc team đã có kinh nghiệm HCL.
Bicep Fundamentals
Cấu trúc project
infra/
main.bicep # Entry point
modules/
appService.bicep # App Service module
sqlServer.bicep # SQL Server module
keyVault.bicep # Key Vault module
parameters/
dev.bicepparam # Dev parameters
staging.bicepparam # Staging parameters
prod.bicepparam # Production parameters
Main Bicep - Deploy .NET Stack
bicep// main.bicep targetScope = 'resourceGroup' @description('Tên môi trường: dev, staging, prod') @allowed(['dev', 'staging', 'prod']) param environment string @description('Vị trí Azure region') param location string = resourceGroup().location @description('Tên ứng dụng') param appName string = 'myapp' @secure() @description('SQL admin password') param sqlAdminPassword string var prefix = '${appName}-${environment}' var tags = { Environment: environment ManagedBy: 'Bicep' Application: appName } // Key Vault - lưu secrets module keyVault 'modules/keyVault.bicep' = { name: 'keyVault' params: { name: '${prefix}-kv' location: location tags: tags } } // SQL Server + Database module sql 'modules/sqlServer.bicep' = { name: 'sqlServer' params: { name: '${prefix}-sql' location: location tags: tags adminPassword: sqlAdminPassword keyVaultName: keyVault.outputs.name } } // App Service + Plan module app 'modules/appService.bicep' = { name: 'appService' params: { name: '${prefix}-app' location: location tags: tags keyVaultName: keyVault.outputs.name sqlConnectionString: sql.outputs.connectionString environment: environment } } output appUrl string = app.outputs.url output keyVaultName string = keyVault.outputs.name
App Service Module
bicep// modules/appService.bicep param name string param location string param tags object param keyVaultName string param sqlConnectionString string param environment string var skuMap = { dev: { name: 'B1', tier: 'Basic' } staging: { name: 'S1', tier: 'Standard' } prod: { name: 'P1v3', tier: 'PremiumV3' } } resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = { name: '${name}-plan' location: location tags: tags sku: skuMap[environment] properties: { reserved: true // Linux } } resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { name: keyVaultName } resource appService 'Microsoft.Web/sites@2023-12-01' = { name: name location: location tags: tags identity: { type: 'SystemAssigned' } properties: { serverFarmId: appServicePlan.id httpsOnly: true siteConfig: { linuxFxVersion: 'DOTNETCORE|8.0' alwaysOn: environment != 'dev' minTlsVersion: '1.2' healthCheckPath: '/health' appSettings: [ { name: 'ASPNETCORE_ENVIRONMENT' value: environment == 'prod' ? 'Production' : 'Staging' } { name: 'KeyVault__VaultUri' value: keyVault.properties.vaultUri } ] connectionStrings: [ { name: 'DefaultConnection' connectionString: sqlConnectionString type: 'SQLAzure' } ] } } } // Grant Key Vault access cho App Service resource kvAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2023-07-01' = { parent: keyVault name: 'add' properties: { accessPolicies: [ { tenantId: appService.identity.tenantId objectId: appService.identity.principalId permissions: { secrets: ['get', 'list'] } } ] } } output url string = 'https://${appService.properties.defaultHostName}'
SQL Server Module
bicep// modules/sqlServer.bicep param name string param location string param tags object @secure() param adminPassword string param keyVaultName string resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { name: name location: location tags: tags properties: { administratorLogin: 'sqladmin' administratorLoginPassword: adminPassword minimalTlsVersion: '1.2' } } resource sqlDb 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { parent: sqlServer name: 'appdb' location: location tags: tags sku: { name: 'Basic' tier: 'Basic' } } // Cho phép Azure services truy cập resource firewallRule 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { parent: sqlServer name: 'AllowAzureServices' properties: { startIpAddress: '0.0.0.0' endIpAddress: '0.0.0.0' } } // Lưu connection string vào Key Vault resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { name: keyVaultName } resource secret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { parent: keyVault name: 'SqlConnectionString' properties: { value: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=appdb;User ID=sqladmin;Password=${adminPassword};Encrypt=true;' } } output connectionString string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=appdb;Authentication=Active Directory Managed Identity;Encrypt=true;'
Parameter Files
bicep// parameters/dev.bicepparam using '../main.bicep' param environment = 'dev' param appName = 'myapp' param sqlAdminPassword = readEnvironmentVariable('SQL_ADMIN_PASSWORD')
Playbook: Deploy .NET Stack
bash# 1. Tạo Resource Group
az group create --name rg-myapp-dev --location southeastasia
# 2. Preview changes (what-if)
az deployment group what-if \
--resource-group rg-myapp-dev \
--template-file infra/main.bicep \
--parameters infra/parameters/dev.bicepparam
# 3. Deploy
az deployment group create \
--resource-group rg-myapp-dev \
--template-file infra/main.bicep \
--parameters infra/parameters/dev.bicepparam
# 4. Kiểm tra output
az deployment group show \
--resource-group rg-myapp-dev \
--name main \
--query properties.outputs
Terraform State Management
Nếu chọn Terraform, cần cấu hình remote state:
hcl# backend.tf terraform { backend "azurerm" { resource_group_name = "rg-terraform-state" storage_account_name = "tfstatemyapp" container_name = "tfstate" key = "dev.terraform.tfstate" } }
bash# Tạo storage cho Terraform state
az storage account create \
--name tfstatemyapp \
--resource-group rg-terraform-state \
--sku Standard_LRS \
--encryption-services blob
az storage container create \
--name tfstate \
--account-name tfstatemyapp
Best practice: Luôn dùng state locking (Azure Blob có native lease lock) để tránh concurrent modifications.