Trong phần này, chúng ta sẽ dựng Foundation layer cho dự án bằng Infrastructure as Code (IaC) sử dụng Terraform và Bicep. Đây là nền tảng cần thiết để các Task 8-19 có thể chạy được, bao gồm Resource Group, Key Vault, Azure Container Registry, Storage Account, Azure ML Workspace, và Compute clusters. Việc sử dụng IaC đảm bảo reproducibility, version control, và consistency across environments.
✅ Dựng Foundation layer bằng IaC (Terraform/Bicep)
✅ Tạo tất cả tài nguyên cần thiết cho MLOps pipeline
✅ Cấu hình RBAC và Managed Identity
✅ Chuẩn bị nền tảng cho các Task 8-19
1. Resource Group:
rg-mlops-retail-{env}2. Key Vault:
kv-retail-{env}3. Azure Container Registry:
acrretail{env}4. Storage Account:
stretail{env}5. Azure ML Workspace:
amlws-retail-{env}6. AML Compute:
cpu-cluster1. Managed Identity:
2. RBAC Configuration:
3. Encryption:
4. Network Security:
Resource Naming Pattern:
rg-mlops-retail-{env}kv-retail-{env}acrretail{env} (no hyphens)stretail{env} (no hyphens)amlws-retail-{env}cpu-clusterEnvironment Suffixes:
devstagingprodStandard Tags:
MLOpsRetailForecast{dev/staging/prod}mlDataTeamML-PlatformRequired Components:
azure/infra/
├── main.tf # Main infrastructure resources
├── variables.tf # Variable definitions
├── outputs.tf # Output values
├── providers.tf # Provider configuration
├── backend.tf # Backend configuration
├── dev.tfvars # Development environment variables
├── prod.tfvars # Production environment variables
├── online_endpoint.tf # ML endpoint configuration (optional)
├── alerts.tf # Monitoring alerts (Task 15)
├── main.bicep # Bicep alternative
└── parameters/ # Bicep parameter files
├── main.dev.json
└── main.prod.json
# ---------- Resource Group ----------
resource "azurerm_resource_group" "rg" {
name = "${var.name_prefix}-rg"
location = var.location
tags = var.tags
}
# ---------- Log Analytics & App Insights ----------
resource "azurerm_log_analytics_workspace" "law" {
name = "${var.name_prefix}-log"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
sku = "PerGB2018"
retention_in_days = 30
tags = var.tags
}
resource "azurerm_application_insights" "appi" {
name = "${var.name_prefix}-appi"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
application_type = "web"
workspace_id = azurerm_log_analytics_workspace.law.id
tags = var.tags
}
# ---------- Storage (Blob/ADLS) ----------
resource "azurerm_storage_account" "st" {
name = replace("${var.name_prefix}st", "-", "")
resource_group_name = azurerm_resource_group.rg.name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
# Enable ADLS Gen2
is_hns_enabled = true
# Enable versioning and soft delete
blob_properties {
delete_retention_policy {
days = 7
}
versioning_enabled = true
}
tags = var.tags
}
resource "azurerm_storage_container" "data" {
name = "data"
storage_account_name = azurerm_storage_account.st.name
container_access_type = "private"
}
# ---------- Key Vault (RBAC) ----------
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "kv" {
name = replace("${var.name_prefix}-kv", "_", "-")
location = var.location
resource_group_name = azurerm_resource_group.rg.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
purge_protection_enabled = true
soft_delete_retention_days = 7
enable_rbac_authorization = true
tags = var.tags
}
# ---------- Azure Container Registry ----------
resource "azurerm_container_registry" "acr" {
name = replace("${var.name_prefix}acr", "-", "")
resource_group_name = azurerm_resource_group.rg.name
location = var.location
sku = var.acr_sku
admin_enabled = false
# Enable vulnerability scanning
security_policy {
trust_policy {
enabled = true
}
}
tags = var.tags
}
# ---------- Azure ML Workspace ----------
resource "azurerm_machine_learning_workspace" "aml" {
name = "${var.name_prefix}-amlws"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
application_insights_id = azurerm_application_insights.appi.id
key_vault_id = azurerm_key_vault.kv.id
storage_account_id = azurerm_storage_account.st.id
container_registry_id = azurerm_container_registry.acr.id
identity { type = "SystemAssigned" }
tags = var.tags
}
# ---------- AML Compute Cluster ----------
resource "azurerm_machine_learning_compute_cluster" "cpu" {
name = "cpu-cluster"
location = var.location
machine_learning_workspace_id = azurerm_machine_learning_workspace.aml.id
vm_size = "Standard_DS3_v2"
scale_settings {
min_node_count = 0
max_node_count = 4
scale_down_nodes_after_idle_duration = "PT15M"
}
tags = var.tags
}
# ---------- RBAC Assignments ----------
# AML Workspace MI → ACR Pull
resource "azurerm_role_assignment" "acr_pull_to_aml" {
scope = azurerm_container_registry.acr.id
role_definition_name = "AcrPull"
principal_id = azurerm_machine_learning_workspace.aml.identity[0].principal_id
}
# AML Workspace MI → Storage Blob Data Reader
resource "azurerm_role_assignment" "storage_reader_to_aml" {
scope = azurerm_storage_account.st.id
role_definition_name = "Storage Blob Data Reader"
principal_id = azurerm_machine_learning_workspace.aml.identity[0].principal_id
}
# AML Workspace MI → Key Vault Secrets User
resource "azurerm_role_assignment" "kv_secrets_to_aml" {
scope = azurerm_key_vault.kv.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_machine_learning_workspace.aml.identity[0].principal_id
}
# variables.tf
variable "location" {
type = string
default = "southeastasia"
description = "Azure region for resources"
}
variable "name_prefix" {
type = string
default = "retail-dev"
description = "Prefix for resource naming"
}
variable "acr_sku" {
type = string
default = "Standard"
description = "ACR SKU (Basic, Standard, Premium)"
}
variable "tags" {
type = map(string)
default = {
Project = "MLOpsRetailForecast"
Environment = "dev"
Component = "ml"
Owner = "DataTeam"
CostCenter = "ML-Platform"
}
description = "Standard tags for all resources"
}
# Online endpoint/deployment variables
variable "endpoint_name" {
type = string
default = "retail-forecast-endpoint"
description = "Name for ML online endpoint"
}
variable "model_name" {
type = string
default = "retail-forecast"
description = "Model name for deployment"
}
variable "model_version" {
type = string
default = "1"
description = "Model version for deployment"
}
variable "infer_image_tag" {
type = string
default = "latest"
description = "Inference container image tag"
}
# Alert configuration
variable "action_group_id" {
type = string
default = ""
description = "Resource ID of Action Group for alerts"
}
# dev.tfvars
location = "southeastasia"
name_prefix = "retail-dev"
acr_sku = "Standard"
endpoint_name = "retail-forecast-endpoint"
model_name = "retail-forecast"
model_version = "1"
infer_image_tag = "latest"
tags = {
Project = "MLOpsRetailForecast"
Environment = "dev"
Component = "ml"
Owner = "DataTeam"
CostCenter = "ML-Platform"
}
action_group_id = ""
# prod.tfvars
location = "southeastasia"
name_prefix = "retail-prod"
acr_sku = "Premium"
endpoint_name = "retail-forecast-endpoint"
model_name = "retail-forecast"
model_version = "1"
infer_image_tag = "latest"
tags = {
Project = "MLOpsRetailForecast"
Environment = "prod"
Component = "ml"
Owner = "DataTeam"
CostCenter = "ML-Platform"
}
action_group_id = "/subscriptions/{sub-id}/resourceGroups/rg-alerts/providers/microsoft.insights/actiongroups/prod-alerts"
Step 1: Initialize Terraform
cd azure/infra
terraform init
Step 2: Plan Deployment
terraform plan -var-file=dev.tfvars
Step 3: Apply Infrastructure
terraform apply -var-file=dev.tfvars
Step 4: Verify Deployment
terraform output
#!/bin/bash
# deploy-infrastructure.sh
# Configuration
ENVIRONMENT=${1:-dev}
SUBSCRIPTION_ID="your-subscription-id"
RESOURCE_GROUP="rg-mlops-retail-${ENVIRONMENT}"
echo "🚀 Deploying MLOps Infrastructure for ${ENVIRONMENT} environment..."
# Login to Azure
az login
# Set subscription
az account set --subscription $SUBSCRIPTION_ID
# Navigate to infrastructure directory
cd azure/infra
# Initialize Terraform
echo "📋 Initializing Terraform..."
terraform init
# Validate configuration
echo "✅ Validating Terraform configuration..."
terraform validate
# Plan deployment
echo "📊 Planning infrastructure deployment..."
terraform plan -var-file="${ENVIRONMENT}.tfvars" -out="${ENVIRONMENT}.tfplan"
# Apply infrastructure
echo "🏗️ Applying infrastructure..."
terraform apply "${ENVIRONMENT}.tfplan"
# Display outputs
echo "📋 Infrastructure deployment completed!"
echo "📊 Resource outputs:"
terraform output
# Verify resources
echo "🔍 Verifying deployed resources..."
az resource list --resource-group $RESOURCE_GROUP --output table
echo "✅ Infrastructure deployment completed successfully!"
# backend.tf
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate"
container_name = "tfstate"
key = "mlops-retail-dev.tfstate"
}
}
// main.bicep
param location string = resourceGroup().location
param environment string = 'dev'
param workspaceName string = 'amlws-retail-${environment}'
param storageName string = 'stretail${environment}'
param acrName string = 'acrretail${environment}'
param keyVaultName string = 'kv-retail-${environment}'
// Storage Account
resource storage 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
isHnsEnabled: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
deleteRetentionPolicy: {
enabled: true
days: 7
}
}
tags: {
Project: 'MLOpsRetailForecast'
Environment: environment
Component: 'ml'
}
}
// Container Registry
resource acr 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = {
name: acrName
location: location
sku: {
name: 'Standard'
}
properties: {
adminUserEnabled: false
policies: {
trustPolicy: {
enabled: true
}
}
}
tags: {
Project: 'MLOpsRetailForecast'
Environment: environment
Component: 'ml'
}
}
// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = {
name: keyVaultName
location: location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
enableRbacAuthorization: true
enablePurgeProtection: true
softDeleteRetentionInDays: 7
}
tags: {
Project: 'MLOpsRetailForecast'
Environment: environment
Component: 'ml'
}
}
// Application Insights
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: 'appi-retail-${environment}'
location: location
kind: 'web'
properties: {
Application_Type: 'web'
}
tags: {
Project: 'MLOpsRetailForecast'
Environment: environment
Component: 'ml'
}
}
// Azure ML Workspace
resource amlWorkspace 'Microsoft.MachineLearningServices/workspaces@2023-04-01' = {
name: workspaceName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
description: 'Azure ML workspace for retail forecasting'
friendlyName: 'RetailML-${environment}'
storageAccount: storage.id
containerRegistry: acr.id
keyVault: keyVault.id
applicationInsights: appInsights.id
}
tags: {
Project: 'MLOpsRetailForecast'
Environment: environment
Component: 'ml'
}
}
// AML Compute Cluster
resource computeCluster 'Microsoft.MachineLearningServices/workspaces/computes@2023-04-01' = {
parent: amlWorkspace
name: 'cpu-cluster'
location: location
properties: {
computeType: 'AmlCompute'
properties: {
vmSize: 'Standard_DS3_v2'
vmPriority: 'Dedicated'
scaleSettings: {
minNodeCount: 0
maxNodeCount: 4
nodeIdleTimeBeforeScaleDown: 'PT15M'
}
}
}
}
// RBAC Assignments
resource acrPullAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(amlWorkspace.id, 'AcrPull')
scope: acr
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
principalId: amlWorkspace.identity.principalId
principalType: 'ServicePrincipal'
}
}
// Outputs
output workspaceId string = amlWorkspace.id
output workspaceName string = amlWorkspace.name
output storageAccountName string = storage.name
output acrName string = acr.name
output keyVaultName string = keyVault.name
// parameters/main.dev.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environment": {
"value": "dev"
},
"workspaceName": {
"value": "amlws-retail-dev"
},
"storageName": {
"value": "stretaildev"
},
"acrName": {
"value": "acrretaildev"
},
"keyVaultName": {
"value": "kv-retail-dev"
}
}
}
#!/bin/bash
# deploy-bicep.sh
ENVIRONMENT=${1:-dev}
RESOURCE_GROUP="rg-mlops-retail-${ENVIRONMENT}"
echo "🚀 Deploying infrastructure with Bicep..."
# Create resource group if not exists
az group create --name $RESOURCE_GROUP --location southeastasia
# Deploy infrastructure
az deployment group create \
--resource-group $RESOURCE_GROUP \
--template-file main.bicep \
--parameters @parameters/main.${ENVIRONMENT}.json
echo "✅ Bicep deployment completed!"
terraform plan -var-file=dev.tfvars outputterraform apply completed successfullyrg-mlops-retail-devamlws-retail-devcpu-clusterrg-mlops-retail-{env} with proper naming#!/bin/bash
# deploy-complete-infrastructure.sh
set -e
# Configuration
ENVIRONMENT=${1:-dev}
SUBSCRIPTION_ID=${2:-"your-subscription-id"}
RESOURCE_GROUP="rg-mlops-retail-${ENVIRONMENT}"
echo "🚀 Starting complete MLOps infrastructure deployment..."
echo "Environment: $ENVIRONMENT"
echo "Subscription: $SUBSCRIPTION_ID"
# Validate environment
if [[ ! "$ENVIRONMENT" =~ ^(dev|staging|prod)$ ]]; then
echo "❌ Invalid environment. Use: dev, staging, or prod"
exit 1
fi
# Login and set subscription
echo "🔐 Authenticating with Azure..."
az login
az account set --subscription $SUBSCRIPTION_ID
# Create resource group
echo "📁 Creating resource group..."
az group create \
--name $RESOURCE_GROUP \
--location southeastasia \
--tags \
Project=MLOpsRetailForecast \
Environment=$ENVIRONMENT \
Component=ml \
Owner=DataTeam
# Navigate to infrastructure directory
cd azure/infra
# Initialize Terraform
echo "📋 Initializing Terraform..."
terraform init
# Validate configuration
echo "✅ Validating Terraform configuration..."
terraform validate
# Plan deployment
echo "📊 Planning infrastructure deployment..."
terraform plan \
-var-file="${ENVIRONMENT}.tfvars" \
-out="${ENVIRONMENT}.tfplan" \
-detailed-exitcode
PLAN_EXIT_CODE=$?
if [ $PLAN_EXIT_CODE -eq 0 ]; then
echo "✅ No changes needed"
elif [ $PLAN_EXIT_CODE -eq 2 ]; then
echo "🔄 Changes detected, proceeding with deployment..."
# Apply infrastructure
echo "🏗️ Applying infrastructure..."
terraform apply "${ENVIRONMENT}.tfplan"
# Display outputs
echo "📋 Infrastructure deployment completed!"
echo "📊 Resource outputs:"
terraform output
else
echo "❌ Terraform plan failed"
exit 1
fi
# Verify deployment
echo "🔍 Verifying deployed resources..."
az resource list \
--resource-group $RESOURCE_GROUP \
--output table
# Test AML workspace access
echo "🤖 Testing Azure ML Workspace access..."
WORKSPACE_NAME=$(terraform output -raw workspace_name)
az ml workspace show \
--name $WORKSPACE_NAME \
--resource-group $RESOURCE_GROUP
echo "✅ Infrastructure deployment completed successfully!"
echo "🎉 Foundation layer ready for MLOps pipeline!"
#!/bin/bash
# validate-infrastructure.sh
ENVIRONMENT=${1:-dev}
RESOURCE_GROUP="rg-mlops-retail-${ENVIRONMENT}"
echo "🔍 Validating infrastructure deployment..."
# Check resource group
echo "📁 Checking resource group..."
az group show --name $RESOURCE_GROUP --query "name" -o tsv
# Check all resources
echo "🏗️ Checking deployed resources..."
RESOURCES=(
"azurerm_storage_account"
"azurerm_key_vault"
"azurerm_container_registry"
"azurerm_machine_learning_workspace"
"azurerm_application_insights"
)
for resource_type in "${RESOURCES[@]}"; do
echo "Checking $resource_type..."
az resource list \
--resource-group $RESOURCE_GROUP \
--resource-type $resource_type \
--query "[].name" -o tsv
done
# Check RBAC assignments
echo "🔐 Checking RBAC assignments..."
WORKSPACE_ID=$(az ml workspace show \
--name "amlws-retail-${ENVIRONMENT}" \
--resource-group $RESOURCE_GROUP \
--query identity.principalId -o tsv)
echo "AML Workspace Principal ID: $WORKSPACE_ID"
# Check compute cluster
echo "⚡ Checking compute cluster..."
az ml compute show \
--name cpu-cluster \
--workspace-name "amlws-retail-${ENVIRONMENT}" \
--resource-group $RESOURCE_GROUP
echo "✅ Infrastructure validation completed!"
Issue 1: “Resource name conflicts”
# Check naming convention
# Ensure unique names across Azure
# Use proper naming patterns
Issue 2: “RBAC permission errors”
# Verify user has Contributor role on subscription
# Check Key Vault access policies
# Validate Managed Identity assignments
Issue 3: “Terraform state conflicts”
# Use remote backend for state
# Lock state during operations
# Backup state before major changes
Best Practice: Always validate infrastructure with terraform plan before applying changes. Use environment-specific variable files to maintain consistency across deployments.
Security Note: Never commit sensitive information like connection strings or keys to version control. Use Azure Key Vault for secrets management.
Foundation infrastructure hoàn tất! 🎉 Nền tảng đã sẵn sàng cho Task 8: Azure ML Workspace Configuration.