Azure: cùng kiến trúc, tên khác, vài khác biệt quan trọng
Tiếp đi: Azure. Khách hàng mới bắt buộc chạy shop-app trên Azure, mình chỉ quen AWS.
Tin tốt: kiến trúc ở bài 18.5 giữ nguyên, phần lớn chỉ đổi tên. Bài này đi qua các tương đương, rồi tập trung vào những chỗ Azure khác thật sự.
1. Bài toán
Đưa shop-app (gateway, order, payment, inventory) lên Azure: PostgreSQL, Redis, lưu hóa đơn, messaging, không có password/connection string nào trong code.
2. Vấn đề khi bê nguyên tư duy AWS
- Azure có thêm tầng Subscription → Resource Group để gom và phân quyền; quên nó thì resource vương vãi, khó dọn.
- Quyền trên Azure dựa vào Microsoft Entra ID (Azure AD) và RBAC role assignment, không phải policy JSON gắn vào role như IAM.
- Copy connection string Storage/Service Bus có
AccountKey=...vào config là cách dễ nhất, cũng là cách sai nhất.
3. Giải pháp: bản đồ dịch vụ Azure
| Nhu cầu | Azure |
|---|---|
| Mạng | VNet, subnet, NSG (Network Security Group), NAT Gateway, Private Endpoint |
| Cửa vào | Azure DNS, Front Door (global + WAF), Application Gateway (L7 regional + WAF), Load Balancer (L4) |
| Chạy app | VM / App Service / AKS / Container Apps / Functions |
| Database | Azure Database for PostgreSQL – Flexible Server |
| Cache | Azure Cache for Redis (Azure Managed Redis) |
| File | Blob Storage |
| Messaging | Service Bus (queue/topic), Event Hubs (streaming, có endpoint Kafka), Event Grid |
| Image | Azure Container Registry (ACR) |
| Định danh | Managed Identity, Workload Identity (AKS), Entra ID RBAC |
| Secret | Key Vault |
| Quan sát | Azure Monitor, Log Analytics, Application Insights |
Chọn nơi chạy app
| VM | App Service | Container Apps | AKS | |
|---|---|---|---|---|
| Bạn quản lý | OS, JVM | Chỉ JAR/container | Container, scale rule | Cluster K8s, manifest |
| Phù hợp | Legacy, cần kiểm soát | Web app/API đơn lẻ, team nhỏ | Microservices không muốn vận hành K8s, scale về 0 (KEDA) | Nhiều service, hệ sinh thái Helm/Argo CD |
| Tương đương AWS | EC2 | Elastic Beanstalk | ECS Fargate / App Runner | EKS |
shop-app đã dùng Helm + Argo CD nên chọn AKS; chart giữ nguyên, chỉ đổi values (storage class, annotation identity, ingress).
4. Code từng bước
Bước 1: hạ tầng bằng Terraform (azurerm, rút gọn)
resource "azurerm_resource_group" "shop" {
name = "rg-shop-prod"
location = "southeastasia"
}
resource "azurerm_kubernetes_cluster" "aks" {
name = "aks-shop-prod"
resource_group_name = azurerm_resource_group.shop.name
location = azurerm_resource_group.shop.location
dns_prefix = "shop"
oidc_issuer_enabled = true # cho Workload Identity
workload_identity_enabled = true
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
node_count = 3
vnet_subnet_id = azurerm_subnet.aks.id
zones = ["1", "2", "3"]
}
identity { type = "SystemAssigned" }
}
resource "azurerm_user_assigned_identity" "order" {
name = "id-order-service"
resource_group_name = azurerm_resource_group.shop.name
location = azurerm_resource_group.shop.location
}
# Quyền: chỉ ghi blob vào storage hóa đơn
resource "azurerm_role_assignment" "order_blob" {
scope = azurerm_storage_account.invoices.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_user_assigned_identity.order.principal_id
}
resource "azurerm_federated_identity_credential" "order" {
name = "order-service-sa"
resource_group_name = azurerm_resource_group.shop.name
parent_id = azurerm_user_assigned_identity.order.id
issuer = azurerm_kubernetes_cluster.aks.oidc_issuer_url
subject = "system:serviceaccount:shop:order-service"
audience = ["api://AzureADTokenExchange"]
}
State để ở Azure Storage backend (blob lease làm lock), giống bài 18.4.
Bước 2: ServiceAccount dùng Workload Identity
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-service
namespace: shop
annotations:
azure.workload.identity/client-id: ${ORDER_IDENTITY_CLIENT_ID}
---
# trong pod template của Deployment
metadata:
labels:
azure.workload.identity/use: "true"
Bước 3: Java dùng DefaultAzureCredential
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-storage-blob</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
</dependency> <!-- version qua BOM spring-cloud-azure-dependencies -->
# application.yml
spring:
cloud:
azure:
storage.blob.account-name: ${INVOICE_STORAGE_ACCOUNT} # không có account key
keyvault.secret.property-sources[0].endpoint: ${KEYVAULT_URI}
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/shop?sslmode=require
username: ${DB_USER}
password: ${db-password} # secret "db-password" đọc từ Key Vault
@Service
@RequiredArgsConstructor
class InvoiceStorage {
private final BlobServiceClient blobs; // starter tự tạo, dùng DefaultAzureCredential
public String save(long orderId, byte[] pdf) {
String name = "invoices/%d.pdf".formatted(orderId);
blobs.getBlobContainerClient("invoices")
.getBlobClient(name)
.upload(BinaryData.fromBytes(pdf), true);
return name;
}
}
Với PostgreSQL có thể bỏ luôn password: bật Entra authentication và dùng spring.datasource.azure.passwordless-enabled: true, app lấy token từ Managed Identity.
Bước 4: kiểm tra
kubectl -n shop exec deploy/order-service -- env | grep AZURE_ # AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE
curl -X POST https://api.shop.example/orders/1001/invoice
az storage blob list --account-name "$INVOICE_STORAGE_ACCOUNT" -c invoices --auth-mode login -o table
5. Kiến trúc tham chiếu
Internet ─► Front Door + WAF ─► Application Gateway (AGIC) hoặc Ingress NGINX
│
┌─────── VNet 10.30.0.0/16 (rg-shop-prod) ───────────────────┐
│ snet-aks (3 zone): gateway ─► order / payment / inventory │
│ │ Workload Identity │
│ ▼ │
│ Private Endpoint: PostgreSQL Flexible (zone-redundant HA), │
│ Redis, Blob, Service Bus, Key Vault │
└─────────────────────────────────────────────────────────────┘
ACR (pull bằng managed identity của AKS) · Azure Monitor / App Insights · Entra ID
6. Bảng ánh xạ AWS ↔ Azure
| Nhóm | AWS | Azure | Khác biệt đáng nhớ |
|---|---|---|---|
| Tổ chức | Account, Organizations | Subscription, Resource Group, Management Group | Resource Group gom vòng đời và quyền |
| Mạng | VPC, subnet | VNet, subnet | Subnet AWS gắn 1 AZ; subnet Azure trải mọi zone |
| Firewall | Security Group, NACL | NSG, ASG | NSG gắn subnet hoặc NIC, có cả allow/deny theo priority |
| Kết nối riêng | VPC endpoint / PrivateLink | Private Endpoint / Private Link | — |
| LB | ALB / NLB | Application Gateway / Load Balancer | — |
| Edge | CloudFront + WAF | Front Door + WAF | — |
| VM | EC2 | Virtual Machines | — |
| PaaS app | Elastic Beanstalk / App Runner | App Service / Container Apps | Container Apps có KEDA, scale về 0 |
| Kubernetes | EKS | AKS | AKS không tính phí control plane ở tier Free |
| Serverless | Lambda | Functions | — |
| PostgreSQL | RDS / Aurora | Database for PostgreSQL Flexible | — |
| Redis | ElastiCache | Azure Cache for Redis | — |
| Object storage | S3 | Blob Storage | Blob nằm trong Storage Account |
| Queue | SQS | Service Bus queue | Service Bus có session, transaction, DLQ sẵn |
| Pub/sub | SNS | Service Bus topic / Event Grid | — |
| Streaming | MSK / Kinesis | Event Hubs | Event Hubs nói được giao thức Kafka |
| Registry | ECR | ACR | — |
| Định danh workload | IAM role, IRSA | Managed Identity, Workload Identity | Quyền = role assignment ở một scope |
| Secret | Secrets Manager, KMS | Key Vault | Key Vault gộp secret, key, certificate |
| Quan sát | CloudWatch, X-Ray | Azure Monitor, Log Analytics, App Insights | Truy vấn log bằng KQL |
| IaC gốc | CloudFormation, CDK | ARM, Bicep | Terraform dùng được cho cả hai |
7. Khi nào nên / không nên
- Nên: một Resource Group theo ứng dụng + môi trường; Private Endpoint cho mọi PaaS; Managed Identity thay connection string có key.
- Nên: Container Apps khi team nhỏ và chỉ vài service; AKS khi đã có tooling K8s.
- Không nên: dùng storage account key / Service Bus SAS key dùng chung trong app; mở public network access cho PostgreSQL.
- Không nên: viết code phụ thuộc chặt SDK một cloud ở tầng domain; bọc sau interface (
InvoiceStorage) để đổi S3 ↔ Blob dễ.
Managed Identity với Service Principal khác nhau chỗ nào? Mình thấy tài liệu cũ toàn hướng dẫn tạo Service Principal rồi lấy client secret.
Đúng rồi, tài liệu cũ hay dùng Service Principal + client secret. Cả hai đều là danh tính trong Entra ID, khác ở chỗ ai giữ credential:
| Service Principal + secret | Managed Identity | |
|---|---|---|
| Credential | Bạn giữ client secret/cert, phải rotate | Azure tự quản, không ai thấy |
| Vòng đời | Tạo, xóa tay | System-assigned: gắn theo resource; user-assigned: tái dùng được |
| Dùng khi | Ứng dụng chạy ngoài Azure không có federation | Mọi workload chạy trên Azure |
CI (GitHub Actions) ngoài Azure cũng không cần secret: dùng federated credential (OIDC) trên app registration, giống OIDC với AWS ở bài 18.2.
Service Bus và Event Hubs nghe giống nhau, order-service phát event "OrderPlaced" thì nên dùng cái nào?
Hai cái giải hai bài khác nhau, giống RabbitMQ vs Kafka:
- Service Bus: message nghiệp vụ, mỗi message cần xử lý chắc chắn. Có DLQ, duplicate detection, session (giữ thứ tự theo key), scheduled message. Topic + subscription để nhiều service cùng nhận.
- Event Hubs: luồng sự kiện lượng lớn (telemetry, clickstream, CDC), consumer tự giữ offset, replay trong thời gian retention, có endpoint Kafka nên client Kafka/Spring Kafka dùng gần như nguyên.
"OrderPlaced" để payment và inventory xử lý: Service Bus topic với 2 subscription. Nếu shop-app đang dùng Kafka + outbox/CDC và muốn giữ code, chọn Event Hubs (Kafka endpoint).
Phỏng vấn hỏi về Azure thì trả lời sao?
Q1. Bạn deploy Spring Boot microservices lên Azure thế nào?
🇻🇳 Tiếng Việt:
Image build trong CI, push lên ACR. Chạy trên AKS (hoặc Container Apps nếu ít service), deploy bằng Helm qua Argo CD. Traffic vào qua Front Door và Application Gateway có WAF. PostgreSQL Flexible Server, Azure Cache for Redis, Blob, Service Bus, tất cả qua Private Endpoint trong VNet. Hạ tầng bằng Terraform, log và metric vào Azure Monitor và Application Insights.
🇬🇧 English:
CI builds the image and pushes it to ACR. Services run on AKS, or Container Apps for a small number of services, deployed with Helm through Argo CD. Traffic enters through Front Door and Application Gateway with WAF. We use PostgreSQL Flexible Server, Azure Cache for Redis, Blob Storage and Service Bus, all reached through Private Endpoints in the VNet. Infrastructure is Terraform, and telemetry goes to Azure Monitor and Application Insights.
Q2. Làm sao để không có secret trong cấu hình ứng dụng trên Azure?
🇻🇳 Tiếng Việt:
Dùng Managed Identity; trên AKS là Workload Identity liên kết ServiceAccount với user-assigned identity. Cấp quyền bằng role assignment đúng scope, ví dụ Storage Blob Data Contributor trên một storage account. Code dùng DefaultAzureCredential. Secret còn lại để trong Key Vault, PostgreSQL có thể dùng Entra authentication không cần password.
🇬🇧 English:
We use Managed Identity; on AKS, Workload Identity links a ServiceAccount to a user-assigned identity. Permissions are role assignments at the narrowest scope, for example Storage Blob Data Contributor on one storage account. Code uses DefaultAzureCredential. Remaining secrets live in Key Vault, and PostgreSQL can use Entra authentication without a password.
Q3. Bạn đã dùng AWS, chuyển sang Azure thấy khác gì?
🇻🇳 Tiếng Việt:
Kiến trúc gần như ánh xạ một-một: VPC thành VNet, IAM role thành Managed Identity, S3 thành Blob, SQS thành Service Bus, EKS thành AKS. Khác biệt chính là mô hình tổ chức Subscription và Resource Group, quyền dựa trên Entra ID và role assignment, subnet trải nhiều zone, và Event Hubs hỗ trợ giao thức Kafka. Vì code tách storage và messaging sau interface, phần đổi chủ yếu là cấu hình và hạ tầng.
🇬🇧 English:
The architecture maps almost one to one: VPC to VNet, IAM roles to Managed Identity, S3 to Blob, SQS to Service Bus, EKS to AKS. The main differences are the Subscription and Resource Group model, permissions based on Entra ID role assignments, subnets spanning zones, and Event Hubs speaking the Kafka protocol. Because our code hides storage and messaging behind interfaces, the change was mostly configuration and infrastructure.
Cách nhớ ngắn gọn
- Subscription → Resource Group → resource.
- VNet/NSG, AKS/Container Apps, PostgreSQL Flexible, Redis, Blob, Service Bus/Event Hubs.
- Managed Identity + role assignment + Key Vault = không key trong code.
- Private Endpoint cho mọi PaaS.
Tự kiểm tra
Workload Identity trên AKS cần những mảnh nào?
Cluster bật OIDC issuer + workload identity, user-assigned identity, federated credential trỏ tới ServiceAccount, annotation client-id trên SA và label use trên pod.
Tương đương của SQS và SNS trên Azure?
Service Bus queue (SQS); Service Bus topic hoặc Event Grid (SNS).
App Service và Container Apps khác nhau chỗ nào?
App Service chạy web app/API đơn lẻ (JAR hoặc container). Container Apps hướng microservices, có KEDA autoscale, scale về 0, Dapr, revision traffic split.
Private Endpoint giải quyết gì?
Cho PaaS (PostgreSQL, Blob, Key Vault) một IP riêng trong VNet, tắt truy cập public.
Event Hubs hợp với tình huống nào hơn Service Bus?
Streaming lượng lớn, cần replay, consumer tự giữ offset, hoặc muốn dùng client Kafka.
Tổng kết chương 18
| Bài | Chủ đề | Ý chính |
|---|---|---|
| 18.1 | Git | Commit bất biến, branch là con trỏ; rebase cái của mình, merge cái của chung; revert trên main, reflog để cứu; trunk-based + feature flag |
| 18.2 | CI pipeline | build → test → scan → image → push; build once deploy many; tag git SHA; secret qua OIDC |
| 18.3 | CD & zero-downtime | Rolling / blue-green / canary / feature flag; migration expand-contract; GitOps với Argo CD |
| 18.4 | Terraform | Declarative; plan trước apply; state remote + lock; module theo môi trường; xử lý drift |
| 18.5 | AWS | VPC 3 tầng subnet, EKS/ECS/Lambda, RDS, ElastiCache, S3, SQS/SNS/MSK, IAM role/IRSA, CloudWatch |
| 18.6 | Azure | VNet/NSG, AKS/Container Apps, PostgreSQL Flexible, Blob, Service Bus/Event Hubs, Managed Identity, Key Vault, Monitor |