AWS App Mesh: Triển Khai Service Mesh Toàn Diện Cho Microservices
Nếu bạn đang vận hành một hệ thống microservices trên AWS mà vẫn đang dùng thủ công để kiểm soát lưu lượng giữa các service — thì cần đọc bài này ngay. AWS App Mesh giải quyết đúng cái đau đó: visibility mù, retry ad-hoc, circuit breaker viết tay. Mình đã mất một tuần debug production vì không có service mesh — và đó là lần cuối mình bỏ qua nó.
AWS App Mesh là gì và tại sao cần nó?
App Mesh là service mesh được quản lý hoàn toàn bởi AWS — nó hoạt động bằng cách inject Envoy proxy như một sidecar container vào mỗi task/pod của bạn. Envoy chịu trách nhiệm xử lý toàn bộ traffic in/out, còn logic nghiệp vụ của bạn không cần biết điều gì xảy ra ở tầng network.
Khổ nỗi là nhiều team nghĩ rằng "service discovery dùng ECS Service Connect là đủ". Đúng — đến khi bạn cần canary deployment 5%/95%, retry policy theo HTTP status code, hoặc trace từng request xuyên qua 6 service khác nhau. Lúc đó mới thấy thiếu gì.
App Mesh phù hợp khi hệ thống của bạn có từ 3–5 service trở lên, và bạn muốn:
— Kiểm soát traffic routing mà không cần deploy lại code
— Có circuit breaker và retry tự động
— Observability toàn diện qua AWS X-Ray và CloudWatch
— mTLS giữa các service mà không cần tự cấu hình certificate
Kiến trúc thực tế của App Mesh
Trước khi code, cần hiểu 4 khái niệm cốt lõi:
Mesh: Container logic chứa toàn bộ cấu hình. Tạo 1 mesh là điểm khởi đầu.
Virtual Node: Đại diện cho 1 service thực tế (ECS Task, EKS Pod, EC2). Mỗi virtual node có DNS name riêng và Envoy sẽ route traffic đến đó.
Virtual Service: Là abstraction layer — service khác gọi vào đây, App Mesh sẽ quyết định route đến virtual node nào.
Virtual Router + Routes: Quy tắc routing — ví dụ "10% traffic đến v2, 90% đến v1", hoặc "prefix /api/v2 → route đến node backend-v2".
Nói thật thì ban đầu mình cũng rối với 4 khái niệm này. Cách nhớ dễ nhất: Virtual Service = domain ảo, Virtual Node = server thật, Router = nginx config.
Hướng dẫn triển khai từng bước
Mình sẽ demo với ECS Fargate, nhưng pattern tương tự với EKS.
Bước 1: Tạo Mesh
aws appmesh create-mesh \
--mesh-name production-mesh \
--spec '{"egressFilter":{"type":"ALLOW_ALL"}}'egressFilter: ALLOW_ALL cho phép các service trong mesh vẫn gọi được ra ngoài. Nếu set DROP_ALL, bạn phải khai báo explicit từng external endpoint.
Bước 2: Tạo Virtual Node cho mỗi service
aws appmesh create-virtual-node \
--mesh-name production-mesh \
--virtual-node-name backend-v1 \
--spec '{
"serviceDiscovery": {
"dns": {"hostname": "backend-v1.production.local"}
},
"listeners": [{"portMapping": {"port": 8080, "protocol": "http"}}],
"backends": [{"virtualService": {"virtualServiceName": "database.production.local"}}],
"logging": {"accessLog": {"file": {"path": "/dev/stdout"}}}
}'Chú ý phần backends — đây là khai báo service nào mà node này được phép gọi. App Mesh dùng thông tin này để setup Envoy listener đúng cách.
Bước 3: Tạo Virtual Router và Route
aws appmesh create-virtual-router \
--mesh-name production-mesh \
--virtual-router-name backend-router \
--spec '{"listeners": [{"portMapping": {"port": 8080, "protocol": "http"}}]}'
aws appmesh create-route \
--mesh-name production-mesh \
--virtual-router-name backend-router \
--route-name weighted-route \
--spec '{
"httpRoute": {
"match": {"prefix": "/"},
"action": {
"weightedTargets": [
{"virtualNode": "backend-v1", "weight": 90},
{"virtualNode": "backend-v2", "weight": 10}
]
},
"retryPolicy": {
"httpRetryEvents": ["server-error", "gateway-error"],
"maxRetries": 3,
"perRetryTimeout": {"unit": "ms", "value": 2000}
}
}
}'Bước 4: Inject Envoy vào ECS Task Definition
{
"containerDefinitions": [
{
"name": "envoy",
"image": "840364872350.dkr.ecr.ap-northeast-1.amazonaws.com/aws-appmesh-envoy:v1.27.2.0-prod",
"essential": true,
"environment": [
{"name": "APPMESH_RESOURCE_ARN", "value": "arn:aws:appmesh:ap-northeast-1:123456789:mesh/production-mesh/virtualNode/backend-v1"},
{"name": "ENABLE_ENVOY_XRAY_TRACING", "value": "1"},
{"name": "ENABLE_ENVOY_STATS_TAGS", "value": "1"}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE"],
"interval": 5, "timeout": 10, "retries": 10
}
}
],
"proxyConfiguration": {
"type": "APPMESH",
"containerName": "envoy",
"properties": [
{"name": "IgnoredUID", "value": "1337"},
{"name": "AppPorts", "value": "8080"},
{"name": "EgressIgnoredIPs", "value": "169.254.170.2,169.254.169.254"}
]
}
}Hai IP trong EgressIgnoredIPs là ECS metadata endpoint và IMDSv2 — bắt buộc phải exclude, không Envoy sẽ chặn và task không start được.
Mẹo thực chiến: Luôn bật
ENABLE_ENVOY_XRAY_TRACING=1ngay từ đầu, dù chưa cần trace ngay. Sau này muốn debug mà không có trace history thì hối hận không kịp.
Observability với X-Ray và CloudWatch
Đây là phần mình thấy App Mesh thực sự tỏa sáng. Với ENABLE_ENVOY_XRAY_TRACING=1, mỗi request tự động được trace end-to-end — bạn thấy latency từng hop, error rate theo service, và dependency map thực tế.
Để có metrics đầy đủ trên CloudWatch, thêm X-Ray daemon sidecar:
{
"name": "xray-daemon",
"image": "amazon/aws-xray-daemon",
"portMappings": [{"containerPort": 2000, "protocol": "udp"}],
"logConfiguration": {
"logDriver": "awslogs",
"options": {"awslogs-group": "/ecs/xray-daemon", "awslogs-region": "ap-northeast-1"}
}
}Sau đó vào AWS X-Ray Service Map — bạn sẽ thấy dependency graph realtime. Request nào fail, latency outlier ở đâu — rõ tức thì mà không cần thêm code logging nào.
Ổ gà thường gặp khi triển khai
Lỗi 1: Task không start vì Envoy chưa healthy. ECS chờ Envoy healthy trước khi start app container. Nếu APPMESH_RESOURCE_ARN sai — Envoy sẽ không connect được control plane → task stuck. Luôn verify ARN chính xác trước.
Lỗi 2: Service gọi nhau qua IP thay vì DNS. App Mesh chỉ intercept traffic đến DNS name đã khai báo. Nếu code hardcode IP → Envoy bỏ qua → không có trace, không có retry policy. Bắt buộc phải dùng DNS.
Lỗi 3: Quên IAM permission cho Envoy. Envoy cần quyền appmesh:StreamAggregatedResources và xray:PutTraceSegments. Thiếu là task chạy nhưng không có observability.
Lỗi 4: Route weight tổng không bằng 100. API không báo lỗi ngay — traffic sẽ bị drop silent. Luôn verify tổng weight = 100 sau khi update route.
FAQ thường gặp
Q: App Mesh với EKS có dùng được không, hay chỉ cho ECS?A: Dùng được cả ECS Fargate, ECS EC2, EKS, và cả EC2 thường. Với EKS thì install App Mesh Controller qua Helm — nó sẽ tự inject Envoy sidecar dựa trên annotation trên Pod.
Q: App Mesh có thay thế được API Gateway không?A: Không. App Mesh xử lý East-West traffic (service-to-service nội bộ). API Gateway xử lý North-South (external → internal). Hai thứ complement nhau, không thay thế.
Q: Có cần service mesh nếu đang dùng ECS Service Connect?A: Service Connect đủ cho service discovery và load balancing cơ bản. Khi cần weighted routing, circuit breaker, retry policy phức tạp, hoặc mTLS — thì App Mesh mới là lựa chọn.
Khi nào triển khai — và khi nào không cần
App Mesh không phải silver bullet. Nếu hệ thống của bạn chỉ có 2–3 service đơn giản, chi phí vận hành (IAM, Envoy sidecar overhead, cấu hình) sẽ không xứng với lợi ích. Nhưng khi scale lên 5+ service với yêu cầu reliability cao — đây là thứ mình không muốn thiếu nữa.
Điều mình đánh giá cao nhất ở App Mesh: thay đổi routing policy mà không cần deploy lại code. Canary từ 10% lên 50% lên 100% — chỉ cần update weight trong route config, Envoy tự nhận sau vài giây. Cái tốc độ rollout đó cứu mình nhiều lần rồi.
