MODULE 08 Compute / IaC 20–25% đề thi

ARM Templates & Bicep

Infrastructure as Code trên Azure — viết ARM template JSON chuẩn schema, truyền parameters linh hoạt, và dùng Bicep để định nghĩa hạ tầng gọn gàng hơn JSON thuần.

Lý Thuyết Cốt Lõi

1. Lợi Ích của ARM Templates

ARM Template là file JSON mô tả hạ tầng Azure theo dạng khai báo (declarative). Thay vì click Portal hay gõ từng lệnh CLI, bạn định nghĩa trạng thái mong muốn — ARM sẽ tự xác định cách đạt được trạng thái đó.

Idempotent & Declarative
  • • Deploy cùng template nhiều lần → kết quả luôn nhất quán
  • • Nếu resource đã tồn tại và khớp → ARM bỏ qua, không tạo lại
  • • Nếu resource khác spec → ARM cập nhật về đúng trạng thái
Orchestration tự động
  • • ARM tự phát hiện dependency (VNet phải tạo trước NIC, NIC trước VM)
  • • Resource độc lập được deploy song song → nhanh hơn
  • • Dùng dependsOn khi ARM không tự detect được
Version control & CI/CD
  • • Template là file → lưu Git, review PR, rollback dễ dàng
  • • Tích hợp Azure DevOps pipeline hoặc GitHub Actions để auto deploy
  • • Lịch sử deployment lưu trong Azure (800 lần gần nhất)
Reuse & Modular
  • • Parameters + Variables → 1 template dùng cho nhiều môi trường
  • • Linked templates: gọi template con từ template cha
  • • Template Specs: đóng gói và chia sẻ template trong tổ chức
Deployment modes: --mode Incremental (mặc định) — chỉ thêm/cập nhật resource trong template, không xóa resource thừa. --mode Complete — xóa mọi resource trong RG không có trong template. Thận trọng với Complete mode!

2. Schema ARM Template — Cấu Trúc JSON

Mọi ARM template đều có cấu trúc JSON chuẩn với các section bắt buộc và tùy chọn. Hiểu schema giúp bạn đọc, viết và debug template chính xác.

azuredeploy.json— Cấu trúc ARM Template đầy đủ
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",

  "parameters": {
    "storageAccountName": {
      "type": "string",
      "minLength": 3,
      "maxLength": 24,
      "metadata": { "description": "Tên Storage Account (unique toàn cầu)" }
    },
    "storageSku": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": ["Standard_LRS","Standard_GRS","Standard_ZRS","Premium_LRS"],
      "metadata": { "description": "SKU replication cho storage" }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": { "description": "Region deploy (mặc định theo RG)" }
    }
  },

  "variables": {
    "storageKind": "StorageV2",
    "tagEnvironment": "Lab"
  },

  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": { "name": "[parameters('storageSku')]" },
      "kind": "[variables('storageKind')]",
      "properties": {
        "accessTier": "Hot",
        "supportsHttpsTrafficOnly": true,
        "minimumTlsVersion": "TLS1_2"
      },
      "tags": {
        "Environment": "[variables('tagEnvironment')]",
        "Module": "08"
      }
    }
  ],

  "outputs": {
    "storageAccountId": {
      "type": "string",
      "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
    },
    "primaryEndpoint": {
      "type": "string",
      "value": "[reference(parameters('storageAccountName')).primaryEndpoints.blob]"
    }
  }
}
Section Bắt buộc Mục đích
$schema URL JSON Schema — giúp IDE validate và auto-complete
contentVersion Version của template (bạn tự quản lý, VD: "1.0.0.0")
parameters Không Giá trị đầu vào khi deploy — tái sử dụng template cho nhiều môi trường
variables Không Giá trị trung gian, tính từ parameters — tránh lặp lại logic
resources Danh sách resource cần deploy — phần chính của template
outputs Không Giá trị trả về sau deploy — dùng cho linked template hoặc pipeline

3. Parameters Template — Tái Sử Dụng Linh Hoạt

Parameters file (azuredeploy.parameters.json) tách biệt giá trị môi trường khỏi template logic, cho phép dùng cùng 1 template cho Dev/Staging/Production chỉ bằng cách đổi file parameters.

azuredeploy.parameters.dev.json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "value": "stm08devlab001"
    },
    "storageSku": {
      "value": "Standard_LRS"
    },
    "location": {
      "value": "southeastasia"
    }
  }
}
azuredeploy.parameters.prod.json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "value": "stm08prodhtlab001"
    },
    "storageSku": {
      "value": "Standard_GRS"
    },
    "location": {
      "value": "southeastasia"
    }
  }
}
Các kiểu parameter
  • string — văn bản, có thể dùng minLength/maxLength
  • int — số nguyên, có thể dùng minValue/maxValue
  • bool — true/false
  • array — mảng giá trị
  • object — object JSON lồng nhau
  • securestring — password/key, không log trong output
ARM Template Functions
  • [parameters('name')] — lấy giá trị parameter
  • [variables('name')] — lấy giá trị variable
  • [resourceGroup().location] — region của RG hiện tại
  • [concat('st', uniqueString(resourceGroup().id))] — tạo tên unique
  • [reference(resName).property] — lấy thuộc tính resource sau deploy

4. Bicep — DSL Thay Thế ARM JSON

Bicep là domain-specific language (DSL) do Microsoft phát triển như lớp abstraction trên ARM template JSON. Bicep biên dịch thành ARM JSON khi deploy — không có runtime riêng, không có overhead.

ARM JSON (storage account)
azuredeploy.json — 28 dòng
{
  "$schema": "...",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "type": "string"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "sku": { "name": "Standard_LRS" },
      "kind": "StorageV2",
      "properties": {
        "supportsHttpsTrafficOnly": true,
        "minimumTlsVersion": "TLS1_2"
      }
    }
  ]
}
Bicep (tương đương)
main.bicep — 13 dòng
param storageAccountName string
param location string = resourceGroup().location

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    supportsHttpsTrafficOnly: true
    minimumTlsVersion: 'TLS1_2'
  }
}
Gọn hơn

Ít boilerplate, không cần $schema, contentVersion. Cú pháp sạch, dễ đọc hơn JSON.

Type-safe

VS Code extension Bicep validate syntax, auto-complete apiVersion và properties ngay khi gõ.

Modules

Bicep modules tương tự linked template — gọi file .bicep khác, tái sử dụng tốt hơn.

Decompile ARM → Bicep: Có thể chuyển đổi ARM JSON sang Bicep bằng lệnh az bicep decompile --file azuredeploy.json. Ngược lại Bicep → ARM JSON: az bicep build --file main.bicep. AZ-104 yêu cầu hiểu khái niệm Bicep nhưng không cần thuộc cú pháp chi tiết.

Bài Tập Thực Hành (Lab)

Lab 08-A: Deploy ARM Template JSON Lab 08-B: Parameters File Lab 08-C: Deploy Bicep File Lab 08-D: Xem Deployment History
1

Tạo RG và viết ARM Template JSON deploy Storage Account + VNet

Bash— Linux/macOS/Cloud Shell, KHÔNG chạy CMD
# Tạo Resource Group
az group create --name rg-az104-m08 --location southeastasia

# Tạo file ARM template JSON — deploy Storage Account + VNet
cat > /tmp/azuredeploy.json << 'ARMEOF'
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "type": "string",
      "minLength": 3,
      "maxLength": 24,
      "metadata": { "description": "Tên Storage Account duy nhất toàn cầu" }
    },
    "vnetName": {
      "type": "string",
      "defaultValue": "vnet-m08-lab",
      "metadata": { "description": "Tên Virtual Network" }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "environment": {
      "type": "string",
      "defaultValue": "Lab",
      "allowedValues": ["Lab","Dev","Prod"]
    }
  },
  "variables": {
    "subnetName": "subnet-default",
    "vnetAddressPrefix": "10.8.0.0/16",
    "subnetAddressPrefix": "10.8.0.0/24"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": { "name": "Standard_LRS" },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot",
        "supportsHttpsTrafficOnly": true,
        "minimumTlsVersion": "TLS1_2"
      },
      "tags": {
        "Environment": "[parameters('environment')]",
        "Module": "08",
        "DeployedBy": "ARM-Template"
      }
    },
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2023-09-01",
      "name": "[parameters('vnetName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": { "addressPrefixes": ["[variables('vnetAddressPrefix')]"] },
        "subnets": [
          {
            "name": "[variables('subnetName')]",
            "properties": { "addressPrefix": "[variables('subnetAddressPrefix')]" }
          }
        ]
      },
      "tags": {
        "Environment": "[parameters('environment')]",
        "Module": "08"
      }
    }
  ],
  "outputs": {
    "storageAccountId": {
      "type": "string",
      "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
    },
    "vnetId": {
      "type": "string",
      "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"
    }
  }
}
ARMEOF

# Tạo file parameters
STORAGE_UNIQUE="stm08arm$(date +%s | tail -c 6)"
cat > /tmp/azuredeploy.parameters.json << EOF
{
  "\$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": { "value": "$STORAGE_UNIQUE" },
    "vnetName":           { "value": "vnet-m08-lab" },
    "environment":        { "value": "Lab" }
  }
}
EOF

echo "Storage name sẽ deploy: $STORAGE_UNIQUE"

# Validate template trước khi deploy
az deployment group validate \
  --resource-group rg-az104-m08 \
  --template-file /tmp/azuredeploy.json \
  --parameters @/tmp/azuredeploy.parameters.json

echo "Validation passed. Tiến hành deploy..."

# Deploy ARM template
az deployment group create \
  --resource-group rg-az104-m08 \
  --name "Deploy-ARM-M08-$(date +%Y%m%d%H%M)" \
  --template-file /tmp/azuredeploy.json \
  --parameters @/tmp/azuredeploy.parameters.json \
  --mode Incremental

echo "Deploy completed!"
Verify Portal: Portal → rg-az104-m08 → Resources → xác nhận có Storage Account và VNet. Mở mỗi resource → Tags → thấy Environment=Lab, Module=08, DeployedBy=ARM-Template. Portal → rg-az104-m08 → Deployments → thấy deployment vừa chạy với status Succeeded.
2

Deploy lại với parameters file khác — kiểm tra Incremental mode

Bash— Linux/macOS/Cloud Shell, KHÔNG chạy CMD
# Deploy lần 2: đổi environment thành Dev — kiểm tra Incremental idempotent
STORAGE_UNIQUE=$(az storage account list -g rg-az104-m08 --query "[0].name" -o tsv)

cat > /tmp/azuredeploy.parameters.dev.json << EOF
{
  "\$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": { "value": "$STORAGE_UNIQUE" },
    "vnetName":           { "value": "vnet-m08-lab" },
    "environment":        { "value": "Dev" }
  }
}
EOF

# Deploy lại cùng template, parameters khác — chỉ update tag, không tạo lại resource
az deployment group create \
  --resource-group rg-az104-m08 \
  --name "Deploy-ARM-M08-Dev-$(date +%Y%m%d%H%M)" \
  --template-file /tmp/azuredeploy.json \
  --parameters @/tmp/azuredeploy.parameters.dev.json \
  --mode Incremental

# Kiểm tra tag đã đổi chưa
az storage account show \
  --name $STORAGE_UNIQUE \
  --resource-group rg-az104-m08 \
  --query "tags" \
  --output table

# Xem deployment history
az deployment group list \
  --resource-group rg-az104-m08 \
  --query "[].{Name:name,State:properties.provisioningState,Time:properties.timestamp}" \
  --output table
Verify Portal: Portal → rg-az104-m08 → Deployments → thấy 2 deployment. Storage Account → Tags → Environment đã chuyển từ "Lab" thành "Dev". VNet vẫn tồn tại (Incremental mode không xóa resource không có trong template thay đổi).
3

Viết và deploy Bicep file — tạo Storage Account thứ hai

Bash— Linux/macOS/Cloud Shell, KHÔNG chạy CMD
# Cài Bicep CLI (nếu chưa có — trong Cloud Shell đã có sẵn)
az bicep install

# Tạo file Bicep — deploy Storage Account với lifecycle management policy
BICEP_STORAGE="stm08bicep$(date +%s | tail -c 6)"

cat > /tmp/main.bicep << 'BICEPEOF'
@description('Tên Storage Account — unique toàn cầu')
@minLength(3)
@maxLength(24)
param storageAccountName string

@description('Region deploy')
param location string = resourceGroup().location

@description('SKU replication')
@allowed(['Standard_LRS', 'Standard_GRS', 'Standard_ZRS'])
param storageSku string = 'Standard_LRS'

@description('Environment tag')
param environment string = 'Lab'

// Storage Account resource
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: storageSku
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
    supportsHttpsTrafficOnly: true
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
  }
  tags: {
    Environment: environment
    Module: '08'
    DeployedBy: 'Bicep'
  }
}

// Blob service — bật versioning
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
  parent: storageAccount
  name: 'default'
  properties: {
    isVersioningEnabled: true
    deleteRetentionPolicy: {
      enabled: true
      days: 7
    }
  }
}

// Output
output storageAccountId string = storageAccount.id
output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob
BICEPEOF

echo "Bicep file created. Preview ARM JSON output:"

# Build Bicep → ARM JSON để xem trước (không deploy)
az bicep build --file /tmp/main.bicep --outfile /tmp/main.generated.json
echo "Generated ARM JSON lines: $(wc -l < /tmp/main.generated.json)"

# Deploy Bicep trực tiếp (Azure CLI tự biên dịch sang ARM JSON)
az deployment group create \
  --resource-group rg-az104-m08 \
  --name "Deploy-Bicep-M08-$(date +%Y%m%d%H%M)" \
  --template-file /tmp/main.bicep \
  --parameters storageAccountName="$BICEP_STORAGE" environment="Lab" \
  --mode Incremental

echo "Bicep deploy completed: $BICEP_STORAGE"

# Xác nhận resource
az storage account show \
  --name $BICEP_STORAGE \
  --resource-group rg-az104-m08 \
  --query "{name:name,sku:sku.name,isVersioningEnabled:properties.isVersioningEnabled}" \
  --output table 2>/dev/null || \
az storage account show \
  --name $BICEP_STORAGE \
  --resource-group rg-az104-m08 \
  --query "{name:name,sku:sku.name}" \
  --output table
Verify Portal: Portal → rg-az104-m08 → Resources → thấy storage account thứ hai (tên bắt đầu bằng stm08bicep). Mở storage → Tags → DeployedBy=Bicep. Portal → rg-az104-m08 → Deployments → thấy 3 deployment, deployment mới nhất tên bắt đầu Deploy-Bicep-M08-...
4

Xem Deployment History và Export Template từ resource có sẵn

Azure CLI— Chạy được trên PowerShell, CMD, Bash hoặc Azure Cloud Shell
# Xem deployment history của RG
az deployment group list \
  --resource-group rg-az104-m08 \
  --query "[].{Name:name,State:properties.provisioningState,Timestamp:properties.timestamp,Mode:properties.mode}" \
  --output table

# Xem chi tiết một deployment cụ thể (thay tên deployment thực tế)
DEPLOY_NAME=$(az deployment group list -g rg-az104-m08 --query "[0].name" -o tsv)
az deployment group show \
  --resource-group rg-az104-m08 \
  --name $DEPLOY_NAME \
  --query "{name:name,state:properties.provisioningState,outputs:properties.outputs}" \
  --output json

# Export ARM template từ RG hiện tại (reverse engineer hạ tầng đang chạy)
az group export --name rg-az104-m08 --output json > /tmp/exported-template.json
echo "Exported template size: $(wc -l < /tmp/exported-template.json) dòng"
echo "Xem resource types trong export:"
cat /tmp/exported-template.json | python3 -c "
import sys, json
t = json.load(sys.stdin)
for r in t.get('resources', []):
    print(f\"  {r.get('type')} / {r.get('name')}\")"
Verify Portal: Portal → rg-az104-m08 → Deployments → thấy danh sách deployments với Timestamp và Status. Portal → rg-az104-m08 → Export template → download template JSON của toàn bộ RG (hữu ích để document hạ tầng hiện có).
5

Cleanup — Xóa toàn bộ tài nguyên Lab

Azure CLI— Chạy được trên PowerShell, CMD, Bash hoặc Azure Cloud Shell
# Xóa Resource Group — kéo theo toàn bộ resource (Storage, VNet, Blob service)
az group delete --name rg-az104-m08 --yes --no-wait

echo "Cleanup initiated. Xác nhận sau vài phút:"
az group list --query "[?name=='rg-az104-m08']" --output table

# Dọn file tạm
rm -f /tmp/azuredeploy.json /tmp/azuredeploy.parameters*.json
rm -f /tmp/main.bicep /tmp/main.generated.json /tmp/exported-template.json
echo "Temp files cleaned."
Lưu ý: Xóa RG xóa cả deployment history lưu trong RG đó. Nếu cần giữ template để tái sử dụng, hãy lưu file azuredeploy.jsonmain.bicep vào git repository trước khi cleanup.

Kết Quả Đầu Ra

Hiểu schema ARM Template

Nắm 6 section ($schema, contentVersion, parameters, variables, resources, outputs) và biết section nào bắt buộc

Viết ARM Template thực tế

Tạo template deploy Storage Account + VNet với parameters, variables, functions và outputs hoạt động

Parameters file tái sử dụng

Dùng file parameters khác nhau cho Dev/Prod với cùng 1 template — Incremental vs Complete mode

Viết và deploy Bicep file

Viết Bicep với param, resource, output; deploy trực tiếp qua CLI; build sang ARM JSON để kiểm tra

Phân biệt ARM JSON vs Bicep

Hiểu Bicep là DSL biên dịch sang ARM JSON — không có runtime riêng, ưu điểm về cú pháp gọn và type safety

Quản lý Deployment History

Xem lịch sử deployment, kiểm tra outputs, export template từ RG đang chạy để document hạ tầng

Ứng Dụng Thực Tế

Tình huống 1: Tập đoàn sản xuất — Triển khai hạ tầng 50 chi nhánh đồng nhất

Mỗi chi nhánh cần cùng bộ hạ tầng Azure (VNet, Storage, Key Vault) nhưng khác tên và region. Làm thủ công qua Portal mất 3 ngày, dễ sai sót.

Giải pháp

1 ARM Template chuẩn hóa toàn bộ hạ tầng chi nhánh. Mỗi chi nhánh có 1 file parameters riêng (branchName, location, costCenter). CI/CD pipeline deploy tự động khi thêm parameters file mới.

Triển khai

Lưu template trong Azure DevOps repo. Pipeline chạy az deployment group create với parameters file tương ứng. Validation step chạy trước deploy thực. Deployment history trong mỗi RG chi nhánh.

Lợi ích

Từ 3 ngày → 30 phút deploy 50 chi nhánh. Zero sai sót cấu hình (mọi branch cùng template). Rollback bằng git revert + redeploy. Audit trail đầy đủ.

Tình huống 2: Startup công nghệ — Chuyển từ ARM JSON sang Bicep

Team DevOps có 200+ ARM template JSON cũ, khó maintain và review. Junior developer mất 30 phút để hiểu 1 template phức tạp.

Giải pháp

Dùng az bicep decompile chuyển đổi hàng loạt ARM JSON sang Bicep. Review từng file, dùng Bicep modules để tái cấu trúc (networking, storage, compute thành module riêng).

Triển khai

VS Code + Bicep extension: auto-complete, type checking, linter. PR review dễ hơn nhờ cú pháp ngắn gọn. Pipeline vẫn chạy az deployment group create --template-file main.bicep — không đổi CI/CD.

Lợi ích

Template giảm 40–60% số dòng. Junior onboard nhanh hơn. Bicep linter phát hiện lỗi sớm. Type-safe parameters giảm bug runtime. Module hóa tăng tái sử dụng.

Tình huống 3: Công ty bảo hiểm — Disaster Recovery theo chuẩn ARM Template

Cần khả năng restore toàn bộ hạ tầng trong 2 giờ khi có sự cố thảm họa. Hạ tầng hiện tại được tạo thủ công, không có documentation đầy đủ.

Giải pháp

Export template từ RG production hiện tại (az group export), làm sạch và chuẩn hóa thành Bicep. Lưu vào Git với parameters file riêng cho DR region (East Asia thay southeastasia).

Triển khai

Test DR drill hàng quý: deploy template vào RG DR, kiểm tra tính đúng đắn, xóa RG DR (tiết kiệm chi phí). Mọi thay đổi hạ tầng prod phải kèm update template trong Git.

Lợi ích

RTO giảm từ 8 giờ → 45 phút. Template là "living documentation" luôn cập nhật. DR drill không tốn tiền vì xóa RG sau test. Đáp ứng yêu cầu audit ISO 22301 Business Continuity.

Zalo