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 đó.
- • 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
- • 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
dependsOnkhi ARM không tự detect được
- • 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)
- • 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
--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.
{
"$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 | Có | URL JSON Schema — giúp IDE validate và auto-complete |
| contentVersion | Có | 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 | Có | 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.
{
"$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"
}
}
}
{
"$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"
}
}
}
- •
string— văn bản, có thể dùngminLength/maxLength - •
int— số nguyên, có thể dùngminValue/maxValue - •
bool— true/false - •
array— mảng giá trị - •
object— object JSON lồng nhau - •
securestring— password/key, không log trong output
- •
[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.
{
"$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"
}
}
]
}
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'
}
}
Ít boilerplate, không cần $schema, contentVersion. Cú pháp sạch, dễ đọc hơn JSON.
VS Code extension Bicep validate syntax, auto-complete apiVersion và properties ngay khi gõ.
Bicep modules tương tự linked template — gọi file .bicep khác, tái sử dụng tốt hơn.
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)
Tạo RG và viết ARM Template JSON deploy Storage Account + VNet
# 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!"
Deploy lại với parameters file khác — kiểm tra Incremental mode
# 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
Viết và deploy Bicep file — tạo Storage Account thứ hai
# 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
Xem Deployment History và Export Template từ resource có sẵn
# 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')}\")"
Cleanup — Xóa toàn bộ tài nguyên Lab
# 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."
azuredeploy.json và main.bicep vào git repository trước khi cleanup.
Kết Quả Đầu Ra
Nắm 6 section ($schema, contentVersion, parameters, variables, resources, outputs) và biết section nào bắt buộc
Tạo template deploy Storage Account + VNet với parameters, variables, functions và outputs hoạt động
Dùng file parameters khác nhau cho Dev/Prod với cùng 1 template — Incremental vs Complete mode
Viết Bicep với param, resource, output; deploy trực tiếp qua CLI; build sang ARM JSON để kiểm tra
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
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.
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.
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.
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.
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).
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.
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 đủ.
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).
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.
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.