EngineeringSolution ArchitectPlatform and DevOps

Session 02 - Infrastructure as Code

So sánh IaC Tools

Tiêu chíBicepTerraformARM Templates
ProviderMicrosoftHashiCorpMicrosoft
SyntaxDSL đơn giảnHCLJSON verbose
State managementKhông cần (Azure native)Remote state (S3, Blob)Không cần
Multi-cloudChỉ AzureAWS, Azure, GCP, ...Chỉ Azure
Learning curveThấpTrung bìnhCao (JSON phức tạp)
Module ecosystemAzure Verified ModulesTerraform Registry rất lớnLinked templates
Preview changeswhat-ifterraform planwhat-if
IDE supportVS Code extension tốtVS Code + IntelliJVS Code
RollbackKhông tự độngterraform destroy + applyKhô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.