AWS: bản đồ dịch vụ cho một backend Java
Tiếp đi: AWS. Mở console ra thấy hơn 200 dịch vụ, mình không biết shop-app thực sự cần những cái nào.
Một backend Java điển hình chỉ cần khoảng 10–12 dịch vụ. Bài này vẽ bản đồ đó cho shop-app, cái nào thay thế cái gì bạn đã tự chạy trước đây.
1. Bài toán
shop-app cần lên AWS: 4 service Spring Boot, PostgreSQL, Redis, Kafka (hoặc queue), lưu ảnh sản phẩm và hóa đơn PDF, log và metric. Yêu cầu: database không ra Internet, không có access key trong code.
2. Vấn đề khi "cứ thế mà làm"
- Tạo EC2 ở public subnet, mở port 5432 cho
0.0.0.0/0để "dev cho tiện". - Copy
AWS_ACCESS_KEY_IDvàoapplication.yml, lỡ push lên GitHub, bị dùng đào coin trong vài giờ. - Tự cài Postgres lên EC2, tự lo backup, failover, patch.
3. Giải pháp: bản đồ dịch vụ
| Nhu cầu | Dịch vụ AWS | Thay cho |
|---|---|---|
| Mạng riêng | VPC, subnet public/private, Security Group, NAT Gateway | Mạng LAN + firewall |
| Cửa vào | Route 53, ALB (L7), WAF, CloudFront | DNS, Nginx |
| Chạy app | EC2 / ECS Fargate / EKS / Lambda | Server, Docker host |
| Database | RDS PostgreSQL hoặc Aurora | Postgres tự cài |
| Cache | ElastiCache (Redis/Valkey) | Redis tự cài |
| File | S3 | Ổ đĩa, NFS |
| Messaging | SQS (queue), SNS (pub/sub), MSK (Kafka), EventBridge | RabbitMQ, Kafka tự chạy |
| Image | ECR | Docker registry |
| Quyền | IAM role, policy | User/password dịch vụ |
| Secret | Secrets Manager, Parameter Store, KMS | File .env |
| Quan sát | CloudWatch Logs/Metrics/Alarms, X-Ray | ELK, Prometheus |
Chọn nơi chạy app
| EC2 | ECS Fargate | EKS | Lambda | |
|---|---|---|---|---|
| Bạn quản lý | OS, JVM, deploy | Task definition | Cluster K8s, manifest | Chỉ code |
| Phù hợp | App legacy, cần kiểm soát máy | Vài service container, team nhỏ | Nhiều service, đã dùng K8s, cần portable | Job sự kiện, traffic thưa |
| Lưu ý Java | — | — | Phải vận hành K8s | Cold start: dùng SnapStart / GraalVM; timeout 15 phút |
shop-app đã có Helm chart và Argo CD (bài 18.3) nên chọn EKS. Team nhỏ chưa biết K8s thì ECS Fargate đơn giản hơn nhiều.
4. Code từng bước
Bước 1: mạng (Terraform, rút gọn)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "shop-prod"
cidr = "10.10.0.0/16"
azs = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"]
public_subnets = ["10.10.0.0/24", "10.10.1.0/24", "10.10.2.0/24"] # ALB, NAT
private_subnets = ["10.10.10.0/24", "10.10.11.0/24", "10.10.12.0/24"] # EKS node
database_subnets= ["10.10.20.0/24", "10.10.21.0/24", "10.10.22.0/24"] # RDS, Redis
enable_nat_gateway = true
}
Security Group chain: ALB-sg nhận 443 từ Internet → node-sg chỉ nhận từ ALB-sg → db-sg chỉ nhận 5432 từ node-sg. Khái niệm VPC/NAT xem thêm bài 9.7.
Bước 2: IAM role cho pod (IRSA / EKS Pod Identity), không có access key
# ServiceAccount của order-service gắn với role chỉ được ghi vào bucket hóa đơn
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-service
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/shop-order-invoices
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject"],
"Resource": "arn:aws:s3:::shop-prod-invoices/*"
}
Bước 3: Java dùng default credentials chain
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency> <!-- version qua BOM software.amazon.awssdk:bom -->
@Configuration
class AwsConfig {
@Bean
S3Client s3Client(@Value("${aws.region}") String region) {
// Không truyền key: SDK tự lấy từ env → web identity token (IRSA) → instance profile
return S3Client.builder().region(Region.of(region)).build();
}
}
@Service
@RequiredArgsConstructor
class InvoiceStorage {
private final S3Client s3;
@Value("${shop.invoice-bucket}") private String bucket;
public String save(long orderId, byte[] pdf) {
String key = "invoices/%d.pdf".formatted(orderId);
s3.putObject(b -> b.bucket(bucket).key(key).contentType("application/pdf"),
RequestBody.fromBytes(pdf));
return key;
}
}
# application.yml
aws.region: ${AWS_REGION}
shop.invoice-bucket: ${INVOICE_BUCKET}
spring.datasource:
url: jdbc:postgresql://${DB_HOST}:5432/shop
username: ${DB_USER}
password: ${DB_PASSWORD} # bơm từ Secrets Manager qua External Secrets Operator
Bước 4: kiểm tra
kubectl exec deploy/order-service -- env | grep AWS_ # thấy AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE
curl -X POST https://api.shop.example/orders/1001/invoice
aws s3 ls s3://shop-prod-invoices/invoices/ # có 1001.pdf
aws logs tail /aws/containerinsights/shop-prod/application --follow
5. Kiến trúc tham chiếu
Internet ─► Route 53 ─► CloudFront + WAF ─► ALB (public subnet, 3 AZ)
│
┌────────────── VPC 10.10.0.0/16 ┼─────────────────────────┐
│ private subnet (3 AZ) ▼ │
│ EKS nodes: gateway ─► order / payment / inventory │
│ │ IRSA role │ │ │
│ ▼ ▼ ▼ │
│ database subnet: RDS Postgres Multi-AZ, ElastiCache │
│ NAT GW ─► Internet (gọi payment provider, pull ECR) │
└─────────────────────────────────────────────────────────┘
Ngoài VPC (qua VPC endpoint): S3, SQS/SNS hoặc MSK, ECR, Secrets Manager, CloudWatch
6. So sánh messaging trên AWS
| SQS | SNS | MSK | |
|---|---|---|---|
| Mô hình | Queue, 1 message → 1 consumer | Pub/sub fan-out | Kafka log, replay được |
| Vận hành | Serverless, gần như 0 | Serverless | Quản lý broker (có bản Serverless) |
| Dùng khi | Job nền, gửi email, hóa đơn | Một event nhiều nơi nhận (SNS → nhiều SQS) | Event streaming, outbox/CDC, cần thứ tự theo key |
Lý thuyết queue vs log đã có ở bài 8.10.
7. Khi nào nên / không nên
- Nên dùng dịch vụ managed (RDS, ElastiCache, SQS) thay vì tự chạy trong K8s, trừ khi có lý do rõ.
- Nên: 3 AZ, Multi-AZ cho RDS; VPC endpoint cho S3/ECR để giảm phí NAT.
- Không nên: access key dài hạn trong code/env; một IAM role cho mọi service; database ở public subnet.
- Chú ý chi phí: NAT Gateway và data transfer giữa AZ hay là khoản bất ngờ trên hóa đơn.
Ở local mình vẫn cần gọi S3, không có IRSA thì chẳng lẽ lại phải dùng access key?
Đúng là local không có IRSA, nhưng vẫn không cần key dài hạn trong code. Vì SDK dùng default credentials chain, code không đổi, chỉ nguồn credential đổi:
- Dev:
aws sso login --profile shop-devrồiAWS_PROFILE=shop-dev. SDK đọc token SSO ngắn hạn trong~/.aws. - Test tự động: dùng LocalStack qua Testcontainers, override endpoint, không cần AWS thật (cách dùng Testcontainers ở bài 5.5).
- Prod: IRSA/Pod Identity.
Cùng một dòng S3Client.builder().build() chạy được cả ba nơi. Đó là lý do không bao giờ gọi StaticCredentialsProvider với key thật.
Security Group và Network ACL khác nhau thế nào? Mình thấy cả hai đều chặn port.
Bạn hiểu đúng là cả hai lọc traffic, chỉ khác tầng và cách nhớ trạng thái:
| Security Group | Network ACL | |
|---|---|---|
| Gắn vào | ENI (instance, pod, RDS) | Subnet |
| Stateful? | Có: cho vào thì chiều về tự cho | Không: phải mở cả chiều về (ephemeral port) |
| Rule | Chỉ allow | Allow và deny, theo thứ tự số |
| Tham chiếu | Được tham chiếu SG khác (db-sg cho phép node-sg) | Chỉ CIDR |
Thực tế: dùng Security Group là chính, để NACL mặc định. NACL chỉ thêm khi cần chặn cứng một dải IP ở cả subnet.
Phỏng vấn hỏi về AWS thì trả lời sao?
Q1. Mô tả kiến trúc AWS cho một hệ thống Spring Boot microservices.
🇻🇳 Tiếng Việt:
VPC trải 3 AZ: public subnet cho ALB và NAT, private subnet cho EKS node, database subnet cho RDS PostgreSQL Multi-AZ và ElastiCache. Traffic vào qua Route 53, CloudFront, WAF, ALB. Image ở ECR, file ở S3, messaging bằng SQS/SNS hoặc MSK. Pod dùng IAM role qua IRSA, secret trong Secrets Manager, log và metric vào CloudWatch. Hạ tầng dựng bằng Terraform.
🇬🇧 English:
A VPC across three AZs: public subnets for the ALB and NAT, private subnets for EKS nodes, database subnets for RDS PostgreSQL Multi-AZ and ElastiCache. Traffic enters through Route 53, CloudFront, WAF and the ALB. Images live in ECR, files in S3, messaging uses SQS/SNS or MSK. Pods get IAM roles through IRSA, secrets come from Secrets Manager, and logs and metrics go to CloudWatch. Everything is provisioned with Terraform.
Q2. Ứng dụng lấy quyền gọi S3 như thế nào mà không có access key?
🇻🇳 Tiếng Việt:
Gắn IAM role cho workload: instance profile cho EC2, task role cho ECS, IRSA hoặc Pod Identity cho EKS. SDK dùng default credentials chain để lấy credential tạm thời, tự refresh. Role theo least privilege, mỗi service một role, chỉ đúng bucket và action cần.
🇬🇧 English:
We attach an IAM role to the workload: an instance profile on EC2, a task role on ECS, IRSA or Pod Identity on EKS. The SDK's default credentials chain fetches temporary credentials and refreshes them automatically. Roles follow least privilege: one role per service, scoped to the exact bucket and actions.
Q3. Khi nào chọn ECS, EKS hay Lambda?
🇻🇳 Tiếng Việt:
Lambda cho xử lý theo sự kiện, traffic thưa, chạy ngắn; với Java phải xử lý cold start. ECS Fargate cho vài service container khi team không muốn vận hành Kubernetes. EKS khi có nhiều service, đã có hệ sinh thái K8s như Helm, Argo CD, hoặc cần chạy được trên nhiều cloud.
🇬🇧 English:
Lambda for event-driven, spiky, short-running work; with Java we must handle cold starts. ECS Fargate for a handful of container services when the team doesn't want to operate Kubernetes. EKS when we have many services, already use the Kubernetes ecosystem like Helm and Argo CD, or need portability across clouds.
Cách nhớ ngắn gọn
- Mạng: public (ALB, NAT) / private (app) / database; SG chain theo tầng.
- Compute: EC2 < ECS < EKS về độ linh hoạt; Lambda cho sự kiện.
- Data: RDS, ElastiCache, S3; Messaging: SQS, SNS, MSK.
- Quyền: IAM role cho workload, không access key trong code.
- Quan sát: CloudWatch; hạ tầng: Terraform.
Tự kiểm tra
Pod trong private subnet ra Internet bằng gì?
NAT Gateway nằm ở public subnet. Với dịch vụ AWS như S3/ECR nên dùng VPC endpoint để khỏi đi qua NAT.
Security Group stateful nghĩa là gì?
Đã cho phép kết nối vào thì response đi ra tự động được phép, không cần rule chiều ngược lại.
IRSA hoạt động dựa trên gì?
ServiceAccount có annotation role ARN; pod nhận web identity token (OIDC), SDK đổi token đó lấy credential tạm qua STS.
SQS và SNS khác nhau chỗ nào?
SQS là queue, mỗi message một consumer xử lý. SNS là pub/sub, một message đẩy tới nhiều subscriber (thường là nhiều SQS).
Vì sao nên chọn RDS thay vì Postgres chạy trong EKS?
RDS lo backup, Multi-AZ failover, patch, point-in-time recovery; giảm việc vận hành stateful trong K8s.