🎯 Mục Tiêu Lab
Tạo Azure Container Registry (ACR) với admin user disabled ngay từ đầu
Push image lên ACR dùng Entra ID credentials (az acr login)
Gán AcrPull role cho AKS kubelet managed identity
Deploy pod trên AKS pull image từ ACR không cần imagePullSecrets
Cấu hình private endpoint cho ACR và tắt public network access
Kiểm tra ACR firewall và xem audit log
📋 Chuẩn Bị
- Docker Desktop đã cài hoặc dùng Azure Cloud Shell
- Azure CLI 2.50+ với extension aks-preview
- AKS cluster từ Lab 35 (hoặc tạo mới đơn giản)
- kubectl đã kết nối cluster
- RG: rg-az500-lab36
- ACR: acraz500lab36 (chỉ lowercase+số)
- AKS: aks-az500-lab36
- VNet: vnet-az500-lab36
🏗️ Kịch Bản
ACR admin user cung cấp username/password tĩnh — nếu credential bị lộ, attacker có thể pull/push image bất kỳ lúc nào. Thực hành tốt 2026: (1) tắt admin user; (2) dùng managed identity để AKS tự lấy token ngắn hạn từ Entra ID khi pull image; (3) nếu cần cô lập network hoàn toàn, cấu hình private endpoint và tắt public access.
Admin user + imagePullSecrets chứa password tĩnh
Service principal với secret rotation định kỳ
Managed identity + AcrPull RBAC + private endpoint
🧪 Các Bước Thực Hiện
Tạo ACR Với Admin User Disabled
- 1.1Portal → Container registries → + Create → RG:
rg-az500-lab36→ Registry name:acraz500lab36→ SKU: Standard - 1.2Tab Review + create → Create (Admin user mặc định là disabled)
- 1.3Sau khi tạo: ACR → Access keys → xác nhận Admin user: Disabled
# Tạo resource group
az group create --name rg-az500-lab36 --location southeastasia
# Tạo ACR SKU Standard (admin user disabled mặc định)
az acr create \
--resource-group rg-az500-lab36 \
--name acraz500lab36 \
--sku Standard \
--location southeastasia \
--admin-enabled false
# Xác nhận admin user đã tắt
az acr show \
--resource-group rg-az500-lab36 \
--name acraz500lab36 \
--query "{name:name, adminEnabled:adminUserEnabled, loginServer:loginServer}" \
-o table
# Đăng nhập ACR bằng Entra ID (không cần password)
az acr login --name acraz500lab36
# Pull image từ Docker Hub và push lên ACR
docker pull nginx:alpine
docker tag nginx:alpine acraz500lab36.azurecr.io/nginx:lab36
docker push acraz500lab36.azurecr.io/nginx:lab36
# Xác nhận image đã có trong ACR
az acr repository list --name acraz500lab36 -o table
az acr repository show-tags \
--name acraz500lab36 \
--repository nginx \
-o table
az acr import để copy image từ Docker Hub vào ACR mà không cần Docker local: az acr import --name acraz500lab36 --source docker.io/library/nginx:alpine --image nginx:lab36
Tạo AKS và Gán AcrPull Qua Managed Identity
- 2.1Tạo AKS cluster → Tab Integrations → Container registry → chọn
acraz500lab36→ Portal tự động gán AcrPull cho kubelet identity - 2.2Hoặc với AKS đã tạo: AKS → Settings → Properties → phần Container Registry → Attach → chọn ACR
# Phương án A: Tạo AKS mới tích hợp ACR ngay khi tạo
az aks create \
--resource-group rg-az500-lab36 \
--name aks-az500-lab36 \
--location southeastasia \
--node-count 1 \
--node-vm-size Standard_B2s \
--enable-managed-identity \
--attach-acr acraz500lab36 \
--generate-ssh-keys
# Phương án B: Gán AcrPull cho AKS đã tồn tại
# (dùng nếu đã có AKS từ lab 35 hoặc lab khác)
ACR_ID=$(az acr show --name acraz500lab36 --resource-group rg-az500-lab36 --query id -o tsv)
# Lấy kubelet managed identity của AKS
KUBELET_MI=$(az aks show \
--resource-group rg-az500-lab36 \
--name aks-az500-lab36 \
--query identityProfile.kubeletidentity.objectId -o tsv)
# Gán AcrPull cho kubelet managed identity
az role assignment create \
--role "AcrPull" \
--assignee-object-id $KUBELET_MI \
--assignee-principal-type ServicePrincipal \
--scope $ACR_ID
# Xác nhận role assignment
az role assignment list \
--scope $ACR_ID \
--query "[?roleDefinitionName=='AcrPull'].{principal:principalName, principalType:principalType}" \
-o table
Deploy Pod Pull Image Từ ACR — Không Cần imagePullSecrets
Portal → AKS → Connect → mở Cloud Shell → chạy các lệnh kubectl bên dưới.
# Lấy kubeconfig cho AKS
az aks get-credentials \
--resource-group rg-az500-lab36 \
--name aks-az500-lab36 \
--overwrite-existing
# ACR login server
ACR_SERVER=$(az acr show --name acraz500lab36 --query loginServer -o tsv)
echo "ACR login server: $ACR_SERVER"
# Deploy pod dùng image từ ACR — KHÔNG có imagePullSecrets
cat <
Cấu Hình Network Firewall và Kiểm Tra ACR Security
- 4.1ACR → Networking → Firewalls and virtual networks → Selected networks → Add IP của bạn → Save
- 4.2ACR → Access keys → xác nhận Admin user vẫn Disabled
- 4.3ACR → Activity log → xem các thao tác push/pull được audit
# Giới hạn ACR chỉ nhận kết nối từ AKS subnet
AKS_SUBNET_ID=$(az aks show \
--resource-group rg-az500-lab36 \
--name aks-az500-lab36 \
--query "agentPoolProfiles[0].vnetSubnetId" -o tsv)
# Nếu AKS dùng Azure CNI và có subnet ID, thêm vào ACR network rules
# Nếu AKS dùng kubenet, thêm IP range của node pool
# Thêm IP công cộng hiện tại vào whitelist (để test az acr login)
MY_IP=$(curl -s https://api.ipify.org)
az acr network-rule add \
--name acraz500lab36 \
--resource-group rg-az500-lab36 \
--ip-address $MY_IP
# Bật network rules (chặn tất cả ngoài whitelist)
az acr update \
--name acraz500lab36 \
--resource-group rg-az500-lab36 \
--default-action Deny
# Xem trạng thái network rule
az acr show \
--name acraz500lab36 \
--resource-group rg-az500-lab36 \
--query "networkRuleSet" -o json
# Xem tất cả role assignments trên ACR
ACR_ID=$(az acr show --name acraz500lab36 -g rg-az500-lab36 --query id -o tsv)
az role assignment list --scope $ACR_ID \
--query "[].{role:roleDefinitionName, principal:principalName, type:principalType}" \
-o table
# Security checklist: xác nhận admin user tắt
az acr show --name acraz500lab36 -g rg-az500-lab36 \
--query "{adminEnabled:adminUserEnabled, sku:sku.name, networkRules:networkRuleSet.defaultAction}" \
-o table
# Xem activity log - audit các thao tác ACR
az monitor activity-log list \
--resource-group rg-az500-lab36 \
--start-time $(date -u -d "1 hour ago" '+%Y-%m-%dT%H:%M:%SZ') \
--query "[?resourceType.value=='Microsoft.ContainerRegistry/registries'].{time:eventTimestamp, op:operationName.localizedValue, caller:caller}" \
-o table
Bước Nâng Cao: Private Endpoint cho ACR Tùy chọn
Yêu cầu ACR SKU Premium. Bước này tạo private endpoint để AKS pull image qua private IP thay vì Internet.
# Nâng SKU lên Premium (nếu cần private endpoint)
az acr update --name acraz500lab36 -g rg-az500-lab36 --sku Premium
# Tạo VNet và subnet cho private endpoint
az network vnet create \
--resource-group rg-az500-lab36 \
--name vnet-az500-lab36 \
--address-prefix 10.20.0.0/16 \
--subnet-name snet-private \
--subnet-prefix 10.20.1.0/24
# Tắt private endpoint policies trên subnet
az network vnet subnet update \
--resource-group rg-az500-lab36 \
--vnet-name vnet-az500-lab36 \
--name snet-private \
--disable-private-endpoint-network-policies true
ACR_ID=$(az acr show --name acraz500lab36 -g rg-az500-lab36 --query id -o tsv)
# Tạo private endpoint
az network private-endpoint create \
--resource-group rg-az500-lab36 \
--name pe-acr-lab36 \
--vnet-name vnet-az500-lab36 \
--subnet snet-private \
--private-connection-resource-id $ACR_ID \
--group-id registry \
--connection-name pec-acr-lab36 \
--location southeastasia
# Tạo private DNS zone cho ACR
az network private-dns zone create \
--resource-group rg-az500-lab36 \
--name "privatelink.azurecr.io"
az network private-dns link vnet create \
--resource-group rg-az500-lab36 \
--zone-name "privatelink.azurecr.io" \
--name dns-link-acr \
--virtual-network vnet-az500-lab36 \
--registration-enabled false
# Tắt public network access (chỉ dùng private endpoint)
az acr update \
--name acraz500lab36 \
--resource-group rg-az500-lab36 \
--public-network-enabled false
📊 Kết Quả Đầu Ra
az acr show → adminUserEnabled: false. Không có password tĩnh nào tồn tại.
az role assignment list → AcrPull cho ServicePrincipal (kubelet managed identity)
kubectl describe pod → Successfully pulled image từ acraz500lab36.azurecr.io, không có imagePullSecrets
defaultAction: Deny + whitelist IP/subnet — truy cập bị chặn từ nguồn không xác định
🧹 Dọn Dẹp
# Xóa toàn bộ resource group lab 36
az group delete --name rg-az500-lab36 --yes --no-wait
# Xác nhận sau vài phút
az group list --query "[?contains(name,'lab36')].name" -o table
❓ Câu Hỏi Ôn Tập
1. Tại sao phải tắt admin user ACR? Admin user hoạt động theo cơ chế nào?
Gợi ý: Admin user cung cấp username (tên registry) và password tĩnh không expire — nếu lộ là compromise vĩnh viễn đến khi regenerate. Không có audit log chi tiết per-user. Dùng RBAC + managed identity thay thế.
2. Lệnh az aks create --attach-acr thực hiện chính xác điều gì phía dưới?
Gợi ý: Gán role AcrPull cho kubelet managed identity (service principal của agent node pool) trên scope của ACR. AKS nodes dùng identity này để lấy token ngắn hạn từ Entra ID khi pull image.
3. ACR SKU nào hỗ trợ Private Endpoint? Tại sao cần Private Endpoint cho production?
Gợi ý: Chỉ SKU Premium. Private endpoint ngăn image traffic đi qua Internet, bảo vệ khỏi data exfiltration, đáp ứng yêu cầu compliance "no public endpoint" trong nhiều framework.
4. Sự khác nhau giữa AcrPull, AcrPush và AcrDelete trong ACR RBAC?
Gợi ý: AcrPull = chỉ pull (đọc) image. AcrPush = pull + push (ghi) image và artifacts. AcrDelete = xóa image và tags. Tuân thủ least privilege: AKS nodes chỉ cần AcrPull, CI/CD pipeline cần AcrPush.
5. az acr import dùng để làm gì? Tại sao lệnh này hữu ích trong môi trường không có Docker?
Gợi ý: Copy image từ registry nguồn (Docker Hub, MCR, ACR khác) vào ACR trực tiếp server-side — không cần Docker daemon local. Hữu ích trong Cloud Shell, CI/CD pipeline không có Docker, hoặc môi trường air-gapped.