CloudWatch, CloudTrail, AWS Config, X-Ray: nhìn thấy shop-app đang chạy thế nào
Tiếp đi: CloudWatch. Hôm qua shop-app trên ASG trả 502 khoảng 10 phút mà không ai biết, khách báo mới hay. Mình muốn AWS tự báo khi có lỗi, và muốn tìm lại được log của instance đã bị ASG terminate.
Đây đúng là bài học "mất bò mới lo làm chuồng" mà hầu hết team gặp lần đầu lên AWS. Ở bài 22.5 ta đã có ALB + ASG tự thay instance hỏng, nhưng chưa có ai nhìn hệ thống. Bài này làm 4 việc: đẩy log ra khỏi instance, biến log thành metric, đặt alarm gửi email, và ghi lại "ai đã làm gì" trên tài khoản. Lý thuyết observability (log/metric/trace, SLO, OpenTelemetry) đã học ở chương 19; bài này chỉ nói phần AWS làm thay bạn.
1. Bài toán: ASG terminate instance là mất log
Hiện trạng shop-app sau bài 22.5:
Internet → shop-alb → shop-tg → shop-asg (2–4 × t3.micro)
└─ shop-app.jar ghi log ra /var/log/shop/app.log
Sự cố hôm qua:
09:12 RDS failover (Multi-AZ) → connection pool lỗi 40 giây
09:12 app trả 502 qua ALB, health check /actuator/health fail
09:14 ASG terminate 2 instance, tạo 2 instance mới
09:22 app ổn lại
09:50 khách hàng nhắn Zalo "web lỗi"
10:05 dev SSH vào... instance cũ không còn, log mất theo EBS root volume
Ba thứ thiếu: không ai được báo lúc 09:12, log của instance đã chết không còn, và không biết ai/cái gì gây ra (RDS failover do ai bấm reboot? hay AWS bảo trì?).
2. Vấn đề
- Log nằm trên disk của instance. Instance là tài nguyên "dùng rồi bỏ" (ASG, Spot, deploy lại). Log phải rời khỏi máy trong vài giây sau khi ghi.
- Không có metric cho lỗi nghiệp vụ. ALB có metric
HTTPCode_ELB_5XX_Countsẵn, nhưng "số đơn hàng thanh toán fail" chỉ có trong log app. - Không có alarm. Metric có nhưng không ai đặt ngưỡng → không ai được gọi.
- Không có audit trail. Ai gọi API
RebootDBInstance? Console không hiện lịch sử nếu chưa bật CloudTrail (mặc định chỉ giữ 90 ngày Event history, không có trong S3, không query được dễ). - Không biết chậm ở đâu. Request 2 giây: ALB? app? RDS? Redis? Cần tracing.
3. Giải pháp: 4 dịch vụ, 4 câu hỏi
| Câu hỏi | Dịch vụ | Thuật ngữ đề thi |
|---|---|---|
| Hệ thống đang thế nào? (số đo, log) | CloudWatch Metrics, Logs, Alarms, Dashboards, Logs Insights, Synthetics, Contributor Insights | metric, namespace, dimension, period, statistic, metric filter, log group / log stream, retention, alarm state OK/ALARM/INSUFFICIENT_DATA, composite alarm, detailed monitoring, custom metric, high-resolution metric, embedded metric format |
| Ai đã gọi API gì, khi nào, từ IP nào? | CloudTrail | management event, data event (S3 object, Lambda invoke), Insights event, trail, organization trail, log file validation, CloudTrail Lake |
| Tài nguyên đang cấu hình đúng chuẩn không? Lịch sử thay đổi cấu hình? | AWS Config | configuration item, Config rule (managed/custom), compliance, remediation (SSM Automation), conformance pack, aggregator |
| Request chậm ở đoạn nào trong chuỗi service? | X-Ray (và CloudWatch ServiceLens / Application Signals) | trace, segment, subsegment, sampling rule, service map, annotation vs metadata, X-Ray daemon / ADOT collector |
Cách phân biệt nhanh mà đề SAA hay bẫy:
CloudWatch = "performance / health" → số đo, log của app & tài nguyên
CloudTrail = "who did what" → API call, audit, compliance, security investigation
AWS Config = "what does it look like now" → cấu hình tài nguyên, có đúng rule không, lịch sử thay đổi
X-Ray = "where is it slow" → trace xuyên service
Ví dụ: "SG bị mở port 22 ra 0.0.0.0/0, muốn biết AI mở" → CloudTrail
"muốn tự động phát hiện + sửa SG mở port 22" → Config rule + remediation
"muốn alarm khi CPU > 80%" → CloudWatch Alarm
"Lambda gọi DynamoDB chậm, muốn biết do Lambda hay DynamoDB" → X-Ray
Mô hình dữ liệu CloudWatch cần nhớ:
Namespace AWS/EC2 | AWS/ApplicationELB | AWS/RDS | ShopApp (custom)
└─ Metric name CPUUtilization | HTTPCode_Target_5XX_Count | OrderPaymentFailed
└─ Dimension InstanceId=i-0abc | LoadBalancer=app/shop-alb/xyz | Env=lab
└─ Datapoint (timestamp, value) period 60s (basic 5 phút với EC2, detailed 1 phút)
└─ Statistic Sum | Average | p99 | SampleCount
Log group /shop/app (retention: 7 ngày lab, 30–90 ngày prod; mặc định Never expire = tốn tiền)
└─ Log stream i-0abc123/app.log (một stream mỗi instance / container)
└─ Log event {"@timestamp":"...","level":"ERROR","logger":"...","message":"..."}
4. Cấu hình từng bước
Bước 1: Spring Boot ghi log JSON. Spring Boot 3.4+ có sẵn structured logging, không cần thêm dependency:
# application.yml
logging:
structured:
format:
file: ecs # hoặc logstash; ecs = Elastic Common Schema
file:
name: /var/log/shop/app.log
logback:
rollingpolicy:
max-file-size: 50MB
max-history: 3
management:
tracing:
sampling:
probability: 0.1 # 10% request, giảm chi phí X-Ray
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
Một dòng log sẽ thành:
{"@timestamp":"2026-10-21T02:12:03.118Z","log.level":"ERROR","process.thread.name":"http-nio-8080-exec-3",
"log.logger":"com.shop.order.OrderService","message":"Payment failed orderId=1042 reason=TIMEOUT",
"trace.id":"1-6716a3f3-2b7c0e9a4d1f8c3e5a6b7c8d","service.name":"shop-app"}
Log JSON thì CloudWatch Logs Insights tự parse field, không cần regex. trace.id nối log với X-Ray trace.
Bước 2: Cài CloudWatch agent qua user data của shop-lt. Role EC2 shop-app-ec2-role cần thêm managed policy CloudWatchAgentServerPolicy (cho phép logs:PutLogEvents, cloudwatch:PutMetricData, đọc SSM parameter cấu hình agent).
#!/bin/bash
dnf install -y amazon-cloudwatch-agent java-21-amazon-corretto-headless
mkdir -p /var/log/shop
cat > /opt/aws/amazon-cloudwatch-agent/etc/config.json <<'EOF'
{
"agent": { "metrics_collection_interval": 60, "run_as_user": "root" },
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/shop/app.log",
"log_group_name": "/shop/app",
"log_stream_name": "{instance_id}/app.log",
"timestamp_format": "%Y-%m-%dT%H:%M:%S.%f",
"timezone": "UTC",
"retention_in_days": 7
}
]
}
}
},
"metrics": {
"namespace": "ShopApp/EC2",
"append_dimensions": { "InstanceId": "${aws:InstanceId}", "AutoScalingGroupName": "${aws:AutoScalingGroupName}" },
"aggregation_dimensions": [["AutoScalingGroupName"]],
"metrics_collected": {
"mem": { "measurement": ["mem_used_percent"] },
"disk": { "measurement": ["used_percent"], "resources": ["/"] }
}
}
}
EOF
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/config.json -s
# chạy app (jar lấy từ S3, không hardcode secret: DB password đọc Secrets Manager trong app)
aws s3 cp s3://shop-app-assets-$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | grep -oP '"accountId"\s*:\s*"\K[0-9]+')/release/shop-app.jar /opt/shop/shop-app.jar
java -jar /opt/shop/shop-app.jar --spring.profiles.active=aws &
Hai điểm đề thi hay hỏi nằm ngay đây: EC2 không tự gửi memory và disk usage lên CloudWatch (hypervisor chỉ thấy CPU, network, disk I/O); muốn RAM phải cài agent. Và log trong máy muốn lên CloudWatch Logs cũng phải qua agent (hoặc SDK/appender).
Bước 3: Metric filter – biến log thành số. Đếm số dòng log.level = ERROR:
aws logs put-metric-filter --profile shop-lab \
--log-group-name /shop/app \
--filter-name shop-app-error-count \
--filter-pattern '{ $.log.level = "ERROR" }' \
--metric-transformations \
metricName=ErrorCount,metricNamespace=ShopApp,metricValue=1,defaultValue=0,unit=Count
Filter pattern kiểu { $.field = "value" } là cú pháp JSON của CloudWatch. Với log text thường dùng "ERROR" hoặc [level=ERROR, ...]. defaultValue=0 quan trọng: không có thì phút nào không có ERROR sẽ không có datapoint → alarm rơi vào INSUFFICIENT_DATA.
Bước 4: Custom metric từ Java (khi cần số nghiệp vụ). Cách rẻ nhất là Micrometer đã có sẵn trong Spring Boot Actuator, bật registry CloudWatch:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-cloudwatch2</artifactId>
</dependency>
# application.yml
management:
cloudwatch:
metrics:
export:
namespace: ShopApp
step: 1m
batch-size: 20
@Service
public class OrderService {
private final Counter paymentFailed;
public OrderService(MeterRegistry registry) {
this.paymentFailed = Counter.builder("order.payment.failed")
.tag("env", "lab")
.register(registry);
}
public void pay(Order order) {
try {
paymentClient.charge(order);
} catch (PaymentException e) {
paymentFailed.increment();
log.error("Payment failed orderId={} reason={}", order.id(), e.reason());
throw e;
}
}
}
Nếu không dùng Micrometer, gọi SDK trực tiếp:
CloudWatchClient cw = CloudWatchClient.builder().region(Region.AP_SOUTHEAST_1).build();
cw.putMetricData(PutMetricDataRequest.builder()
.namespace("ShopApp")
.metricData(MetricDatum.builder()
.metricName("OrderPaymentFailed")
.value(1.0).unit(StandardUnit.COUNT)
.dimensions(Dimension.builder().name("Env").value("lab").build())
.build())
.build());
Lưu ý chi phí: mỗi custom metric (tính theo cặp metric name + tổ hợp dimension) tốn khoảng vài chục cent/tháng, 10 metric đầu miễn phí. Tag có cardinality cao (orderId, userId) làm dimension là cách đốt tiền nhanh nhất. Thuật ngữ đề thi: high-resolution metric (1 giây, storageResolution=1) đắt hơn và chỉ cần khi alarm phải phản ứng < 1 phút.
Bước 5: Alarm → SNS. Alarm trên ALB 5xx (tạo chi tiết ở Lab):
Alarm shop-alb-5xx-high
metric AWS/ApplicationELB HTTPCode_ELB_5XX_Count + HTTPCode_Target_5XX_Count
statistic Sum, period 60s
threshold > 10
evaluation 3 datapoints trong 5 phút (datapoints-to-alarm 3 / evaluation-periods 5)
missing notBreaching (không có request = không có 5xx = OK)
action SNS shop-alerts → email / Slack (Lambda) / PagerDuty
Ba trạng thái: OK, ALARM, INSUFFICIENT_DATA. Tham số --treat-missing-data (missing, ignore, breaching, notBreaching) là câu hỏi đề thi kinh điển: với metric "đếm lỗi", thiếu dữ liệu nên coi là notBreaching; với metric heartbeat kiểu "job vẫn chạy", thiếu dữ liệu nên coi là breaching.
Bước 6: Tracing với X-Ray qua ADOT. Có hai cách: X-Ray SDK for Java (cũ, phải sửa code) và AWS Distro for OpenTelemetry (ADOT) Java agent (khuyến nghị, không sửa code). Lý thuyết OTel đã có ở 19.4; trên AWS chỉ cần:
# trong user data, sau khi tải jar
curl -sL -o /opt/shop/aws-opentelemetry-agent.jar \
https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest/download/aws-opentelemetry-agent.jar
export OTEL_SERVICE_NAME=shop-app
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=lab
export OTEL_PROPAGATORS=xray,tracecontext,baggage
export OTEL_TRACES_SAMPLER=xray # hỏi sampling rule từ X-Ray
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
java -javaagent:/opt/shop/aws-opentelemetry-agent.jar -jar /opt/shop/shop-app.jar &
Agent tự tạo span cho Spring MVC, JDBC (RDS), Lettuce (Redis), AWS SDK v2 (S3, SQS, DynamoDB), HTTP client. Span đẩy sang ADOT Collector chạy cạnh app (port 4317), collector export sang X-Ray. Role EC2 cần AWSXRayDaemonWriteAccess. Trên ECS, collector là sidecar container; trên Lambda, là layer. Service map trong Console sẽ hiện: Client → shop-alb → shop-app → shop-db (RDS) / shop-cache / S3 với p50/p99 từng cạnh.
Bước 7: CloudTrail và AWS Config (một lần cho cả tài khoản).
# Trail ghi mọi region vào bucket log, bật kiểm tra toàn vẹn file
aws cloudtrail create-trail --profile shop-lab --name shop-trail \
--s3-bucket-name shop-app-logs-$ACCOUNT_ID --s3-key-prefix cloudtrail \
--is-multi-region-trail --enable-log-file-validation \
--tags-list Key=Project,Value=shop-app Key=Env,Value=lab
aws cloudtrail start-logging --profile shop-lab --name shop-trail
# Config: ghi lại cấu hình mọi resource, rule "SG không mở SSH ra Internet"
aws configservice put-config-rule --profile shop-lab --config-rule '{
"ConfigRuleName": "shop-restricted-ssh",
"Source": { "Owner": "AWS", "SourceIdentifier": "INCOMING_SSH_DISABLED" }
}'
CloudTrail cần biết: management event (CreateBucket, RunInstances, AssumeRole…) được ghi mặc định; data event (GetObject trên S3, Invoke Lambda, PutItem DynamoDB) phải bật riêng và tốn tiền theo số event; log đến S3 chậm khoảng 5–15 phút; muốn alarm gần real-time thì gửi thêm sang CloudWatch Logs rồi đặt metric filter (ví dụ đếm ConsoleLogin fail, DeleteTrail, thay đổi SG). Log file validation tạo digest file có chữ ký để chứng minh log không bị sửa, câu hỏi compliance hay ra.
5. Cách hoạt động
EC2 (shop-asg) CloudWatch
┌──────────────────────────────────┐ PutLogEvents ┌────────────────────────┐
│ shop-app.jar ──► /var/log/shop/ │ ─────────────────► │ Log group /shop/app │
│ (JSON log, app.log │ (agent, mỗi 5s) │ ├ stream i-0a/app.log │
│ trace.id) ▲ │ │ └ stream i-0b/app.log │
│ CloudWatch agent ───┘ │ PutMetricData │ │ metric filter │
│ mem/disk ───────────────────── │ ─────────────────► │ ShopApp/ErrorCount │
│ Micrometer ─────────────────────│ ─────────────────► │ ShopApp/order.payment.failed
│ ADOT java agent ─► ADOT collector│ ── PutTraceSegments ─► X-Ray (service map, traces)
└──────────────────────────────────┘ │ │
shop-alb ─ (metric có sẵn) ──────────────────────────► │ AWS/ApplicationELB │
shop-db ─ (metric có sẵn) ──────────────────────────► │ AWS/RDS │
│ │ │
│ Alarm shop-alb-5xx-high
│ OK → ALARM (3/5 datapoints)
└─────────┼──────────────┘
▼
SNS shop-alerts ─► email dev
─► Lambda → Slack
─► ASG scaling policy (nếu là alarm CPU)
Mọi API call (Console/CLI/SDK) ──► CloudTrail ──► S3 shop-app-logs/cloudtrail/ (5–15 phút)
└──► CloudWatch Logs (tùy chọn) ─► metric filter ─► alarm
Mọi thay đổi cấu hình ──────────► AWS Config ─► configuration item ─► rule compliant / NON_COMPLIANT ─► SSM remediation
Vòng đời một sự cố giống hôm qua, sau khi có bài này:
09:12:00 RDS failover → app log 200 dòng ERROR "Connection refused"
09:12:05 agent đẩy log lên /shop/app; metric filter ErrorCount = 200
09:12:30 ALB 5xx đếm 48 → datapoint 1 breaching
09:14:30 3/5 datapoints breaching → alarm ALARM → SNS → email + Slack
09:15 dev mở Logs Insights, query ERROR theo instance, thấy "shop-db failover"
09:15 mở CloudTrail: event RebootDBInstance bởi role ShopAdminRole/thoi lúc 09:11 → nguyên nhân rõ
09:22 alarm về OK → email "OK" thứ hai
6. So sánh kiểu đề thi
| Tiêu chí | CloudWatch | CloudTrail | AWS Config | X-Ray |
|---|---|---|---|---|
| Trả lời câu | Hệ thống khỏe không, log gì | Ai gọi API gì | Cấu hình có đúng chuẩn không, đã đổi thế nào | Request chậm/lỗi ở service nào |
| Nguồn dữ liệu | Metric dịch vụ, agent, SDK, log | API call tới AWS (Console, CLI, SDK, service khác) | Snapshot cấu hình resource | Segment từ SDK/agent/daemon |
| Độ trễ | Giây → 1 phút | ~15 phút tới S3; gần real-time nếu sang CW Logs | Phút | Giây |
| Dùng khi | Alarm, dashboard, autoscaling, tìm log | Security incident, audit, compliance, "ai xóa bucket" | Compliance liên tục, auto-remediation, timeline cấu hình | Debug latency microservice / Lambda |
| Chi phí chính | Custom metric, log ingest (GB) + lưu trữ, alarm, Insights query (GB scan) | 1 trail management event miễn phí; data event, Insights tốn | Theo configuration item và rule evaluation | Theo trace ghi/lấy; sampling giảm chi phí |
So sánh nội bộ CloudWatch hay ra đề:
| Cặp | Khác nhau |
|---|---|
| Basic vs Detailed monitoring (EC2) | Basic 5 phút miễn phí; Detailed 1 phút tốn tiền, ASG scale nhanh hơn |
| Metric filter vs Logs Insights | Filter: liên tục, tạo metric để alarm. Insights: query ad-hoc khi điều tra, tính tiền theo GB scan |
| Alarm vs Composite alarm | Composite = AND/OR nhiều alarm, giảm "bão" thông báo (chỉ báo khi CPU cao VÀ 5xx cao) |
| CloudWatch Logs vs S3 (lưu log lâu) | Logs: tìm kiếm, alarm, đắt theo GB lưu. S3: rẻ, lưu năm, query bằng Athena. Pattern: retention ngắn ở Logs + export/subscription sang S3 |
| Subscription filter vs Export task | Subscription: real-time đẩy sang Kinesis/Firehose/Lambda/OpenSearch. Export: batch một lần sang S3 |
| CloudWatch Synthetics vs RUM | Synthetics: canary giả lập user gọi endpoint định kỳ. RUM: đo trên trình duyệt người dùng thật |
| CloudWatch Logs Anomaly / Contributor Insights | Tìm top-N (IP nào gọi nhiều nhất) từ log, không cần tự viết query |
| X-Ray annotation vs metadata | Annotation: key-value index được, dùng để filter trace (annotation.orderId = 1042). Metadata: không index, chỉ để xem |
7. Khi nào dùng / không dùng
- Dùng CloudWatch Logs khi app chạy trên EC2/ECS/Lambda và team không muốn vận hành ELK/Loki. Lambda và ECS (awslogs driver) đẩy log vào CloudWatch mặc định, không cần agent.
- Không dùng CloudWatch Logs làm kho dài hạn: GB lưu đắt hơn S3 nhiều lần. Đặt retention 30–90 ngày, archive sang S3 (xem 22.6 lifecycle).
- Dùng Prometheus/Grafana (19.3) khi đã chạy EKS và cần PromQL; AWS có Amazon Managed Prometheus + Managed Grafana nếu không muốn tự vận hành. Hai thế giới nối nhau qua CloudWatch data source trong Grafana.
- Bật CloudTrail multi-region ngay ngày đầu dù là tài khoản lab; management event của 1 trail miễn phí, chi phí chủ yếu là S3 lưu trữ.
- AWS Config đắt khi nhiều resource đổi liên tục (ASG scale lên xuống tạo configuration item mỗi lần). Lab chỉ bật khi cần thử rule, tắt sau.
- X-Ray dùng sampling (mặc định 1 req/giây + 5%) đủ cho hầu hết; chỉ tăng khi debug.
Log lên CloudWatch rồi, nhưng mình vẫn quen grep. Logs Insights viết query kiểu gì để tìm 50 lỗi gần nhất của đúng instance đã bị terminate, và đếm lỗi theo từng loại exception? Nghe nói query tính tiền theo GB nữa.
Đúng, Logs Insights tính tiền theo GB dữ liệu bị scan, nên hai thói quen quan trọng: luôn giới hạn khoảng thời gian hẹp, và filter càng sớm càng tốt trong pipeline. Cú pháp là chuỗi lệnh nối bằng |, giống Unix pipe, với log JSON thì field tự được nhận.
Các query dùng hàng ngày trên /shop/app
# 1. 50 lỗi gần nhất của instance đã chết (biết instance id từ ASG activity history)
fields @timestamp, @logStream, log.logger, message
| filter @logStream like /i-0abc123def456/ and log.level = "ERROR"
| sort @timestamp desc
| limit 50
# 2. Đếm lỗi theo loại exception, 1 giờ qua (parse tên exception từ message)
filter log.level = "ERROR"
| parse message /(?<exception>[A-Za-z.]+Exception)/
| stats count(*) as total by exception
| sort total desc
# 3. Lỗi theo phút, theo instance → vẽ chart xem lỗi tập trung ở 1 máy hay cả cụm
filter log.level = "ERROR"
| stats count(*) as errors by bin(1m), @logStream
# 4. Tìm toàn bộ log của một request theo trace id (lấy từ X-Ray hoặc header response)
fields @timestamp, log.level, message
| filter trace.id = "1-6716a3f3-2b7c0e9a4d1f8c3e5a6b7c8d"
| sort @timestamp asc
# 5. p99 thời gian xử lý nếu app log duration (ví dụ message "... took=842ms")
filter message like /took=/
| parse message /took=(?<ms>\d+)ms/
| stats avg(ms), pct(ms, 99), max(ms) by bin(5m)
Chạy bằng CLI (hữu ích để đưa vào script sự cố):
QUERY_ID=$(aws logs start-query --profile shop-lab \
--log-group-name /shop/app \
--start-time $(date -d '1 hour ago' +%s) --end-time $(date +%s) \
--query-string 'filter log.level = "ERROR" | stats count(*) by bin(5m)' \
--query 'queryId' --output text)
sleep 3
aws logs get-query-results --profile shop-lab --query-id $QUERY_ID
Mẹo giảm tiền và giảm thời gian
- Khoảng thời gian là yếu tố số 1. Query 1 giờ trên log group 2 GB/ngày chỉ scan ~85 MB.
- Field JSON được index theo cấu trúc, nhưng
like /regex/trênmessagevẫn phải đọc toàn bộ. Những thứ cần lọc thường xuyên (level, orderId, userId) nên là field riêng, không nhét vào message. - Query hay dùng → lưu vào Saved queries, hoặc biến thành widget trên dashboard
shop-overview(widget Logs Insights chạy lại mỗi lần mở dashboard, cũng tính tiền). - Lưu ý
@logStreamcủa instance bị terminate vẫn còn trong log group đến hết retention. Đây chính là lý do ta đẩy log ra ngoài máy. - Log group dùng nhiều ở prod nên cân nhắc Infrequent Access log class (rẻ hơn ingest, nhưng không có metric filter, subscription, Live Tail).
Mình đặt alarm 5xx với threshold > 0 cho 1 datapoint, tối qua nhận 14 email trong 20 phút vì metric cứ nhảy lên xuống. Chỉnh thế nào để alarm "bình tĩnh" hơn mà vẫn không bỏ lỡ sự cố thật? Composite alarm có giúp gì không?
Bạn gặp đúng hiện tượng flapping: metric dao động quanh ngưỡng, alarm đổi trạng thái liên tục, mỗi lần OK↔ALARM là một email. Cách chữa nằm ở 4 tham số của alarm chứ không phải ở SNS.
1. Threshold > 0 với 1 datapoint là quá nhạy
Một request 5xx lẻ (user bấm refresh đúng lúc deploy) không phải sự cố. Hãy nghĩ theo SLO (19.2): shop-app cho phép 0.5% lỗi; với 2000 request/phút thì 10 lỗi/phút mới đáng báo.
2. "M of N" datapoints
--period 60 --evaluation-periods 5 --datapoints-to-alarm 3
→ trong 5 phút gần nhất, có ≥ 3 phút vượt ngưỡng mới ALARM
→ về OK cũng cần dữ liệu đủ 5 phút, nên không nhảy OK sau 1 phút tốt
Đây là tham số đề thi hay hỏi dưới dạng "reduce false alarms / alarm noise".
3. Dùng metric math để alarm theo tỉ lệ, không theo số tuyệt đối
e1 = HTTPCode_ELB_5XX_Count + HTTPCode_Target_5XX_Count
e2 = RequestCount
rate = IF(e2 > 100, 100 * e1 / e2, 0) ← dưới 100 request/phút thì không đánh giá
alarm khi rate > 2 (%), 3/5 datapoints
Hoặc anomaly detection: CloudWatch học pattern 2 tuần và báo khi lệch khỏi band; hợp với metric có chu kỳ (request đêm thấp, ngày cao).
4. Composite alarm để gom
shop-critical = ALARM(shop-alb-5xx-high) AND (ALARM(shop-rds-cpu-high) OR ALARM(shop-asg-unhealthy-hosts))
→ chỉ composite này gửi SNS tới on-call; alarm con không gắn action
→ 1 sự cố = 1 thông báo thay vì 3
Composite alarm cũng dùng để tắt tiếng khi bảo trì: thêm điều kiện AND NOT ALARM(shop-maintenance-window) với alarm bảo trì bật tay. Hoặc đơn giản hơn: aws cloudwatch disable-alarm-actions trước deploy, enable-alarm-actions sau.
5. Chọn đúng treat-missing-data
| Metric | Nên chọn | Vì |
|---|---|---|
| 5xx count, ErrorCount | notBreaching | Không có lỗi = tốt |
| Heartbeat job batch ("JobCompleted" mỗi giờ) | breaching | Không có datapoint = job chết |
| CPU của instance (ASG hay scale về 0 ban đêm) | ignore | Giữ trạng thái cũ, không nhảy INSUFFICIENT_DATA |
Cuối cùng, hãy gửi alarm ALARM và OK vào 2 topic khác nhau nếu on-call chỉ cần biết lúc hỏng; email "OK" để vào channel Slack ít ồn hơn.
Cho mình bài lab để tự tay làm phần này nhé.
Lab 22.15: Log, metric, alarm, audit và trace cho shop-app
Mục tiêu
- Log JSON của shop-app trên EC2 xuất hiện trong log group
/shop/apptrong vòng 30 giây sau khi ghi. - Metric filter
ShopApp/ErrorCountđếm được ERROR; một Logs Insights query trả về lỗi theo exception. - Alarm
shop-alb-5xx-highchuyển sang ALARM khi ta cố ý gây lỗi và gửi email qua SNS; dashboardshop-overviewhiện 4 widget. - CloudTrail ghi được event
StopInstancesta vừa gọi vào bucketshop-app-logs-$ACCOUNT_ID; X-Ray service map hiệnshop-app → RDS.
Chi phí & thời gian
- Thời gian: khoảng 75–90 phút.
- Ngoài Free Tier: ALB (khoảng 0.025 USD/giờ + LCU) đã có từ lab 22.5; RDS
shop-dbdb.t3.micro nằm trong Free Tier 12 tháng nếu Single-AZ. CloudWatch: 10 custom metric, 10 alarm, 5 GB log ingest, 3 dashboard đầu miễn phí. CloudTrail: 1 trail management event miễn phí. X-Ray: 100k trace/tháng miễn phí. AWS Config tính theo configuration item (khoảng 0.003 USD/item) – lab chỉ bật 1 rule rồi tắt. - Ước tính cả lab nếu dọn đúng: dưới 0.5 USD, phần lớn là ALB.
Chuẩn bị
- Lab 22.5 (ALB + ASG
shop-asg, launch templateshop-lt) và 22.7 (RDSshop-db) đang chạy, hoặc chạy lại phần tạo của chúng. - Role
shop-app-ec2-rolecó sẵn (lab 22.1/22.4). Bucketshop-app-logs-$ACCOUNT_ID(lab 22.6). - Biến môi trường:
export AWS_REGION=ap-southeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --profile shop-lab --query Account --output text)
export ALB_ARN=$(aws elbv2 describe-load-balancers --profile shop-lab --names shop-alb --query 'LoadBalancers[0].LoadBalancerArn' --output text)
export ALB_SUFFIX=${ALB_ARN#*loadbalancer/} # app/shop-alb/xxxx – dùng làm dimension
export ALB_DNS=$(aws elbv2 describe-load-balancers --profile shop-lab --names shop-alb --query 'LoadBalancers[0].DNSName' --output text)
export EMAIL=you@example.com
Các bước
Bước 1: Cho role EC2 quyền ghi log, metric, trace.
aws iam attach-role-policy --profile shop-lab --role-name shop-app-ec2-role \
--policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
aws iam attach-role-policy --profile shop-lab --role-name shop-app-ec2-role \
--policy-arn arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
Console: IAM → Roles → shop-app-ec2-role → Add permissions → Attach policies.
Bước 2: Tạo log group với retention 7 ngày và tag.
aws logs create-log-group --profile shop-lab --log-group-name /shop/app \
--tags Project=shop-app,Env=lab
aws logs put-retention-policy --profile shop-lab --log-group-name /shop/app --retention-in-days 7
Console: CloudWatch → Logs → Log groups → Create log group.
Bước 3: Lưu cấu hình agent vào SSM Parameter Store (để mọi instance của ASG dùng chung, không nhúng JSON vào user data).
cat > cw-agent.json <<'EOF'
{
"agent": { "metrics_collection_interval": 60 },
"logs": { "logs_collected": { "files": { "collect_list": [
{ "file_path": "/var/log/shop/app.log", "log_group_name": "/shop/app",
"log_stream_name": "{instance_id}/app.log", "timezone": "UTC" } ] } } },
"metrics": {
"namespace": "ShopApp/EC2",
"append_dimensions": { "AutoScalingGroupName": "${aws:AutoScalingGroupName}" },
"aggregation_dimensions": [["AutoScalingGroupName"]],
"metrics_collected": { "mem": { "measurement": ["mem_used_percent"] },
"disk": { "measurement": ["used_percent"], "resources": ["/"] } }
}
}
EOF
aws ssm put-parameter --profile shop-lab --name /shop/cw-agent-config --type String \
--value file://cw-agent.json --overwrite \
--tags Key=Project,Value=shop-app Key=Env,Value=lab 2>/dev/null || \
aws ssm put-parameter --profile shop-lab --name /shop/cw-agent-config --type String --value file://cw-agent.json --overwrite
Console: Systems Manager → Parameter Store → Create parameter.
Bước 4: Tạo phiên bản mới của launch template shop-lt với user data cài agent + ADOT.
cat > user-data.sh <<'EOF'
#!/bin/bash
dnf install -y amazon-cloudwatch-agent java-21-amazon-corretto-headless
mkdir -p /var/log/shop /opt/shop
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 \
-c ssm:/shop/cw-agent-config -s
# ADOT collector (docker-less: dùng binary rpm)
dnf install -y https://aws-otel-collector.s3.amazonaws.com/amazon_linux/amd64/latest/aws-otel-collector.rpm
cat > /opt/aws/aws-otel-collector/etc/config.yaml <<'YAML'
receivers:
otlp: { protocols: { grpc: { endpoint: 0.0.0.0:4317 } } }
processors:
batch: {}
exporters:
awsxray: { region: ap-southeast-1 }
service:
pipelines:
traces: { receivers: [otlp], processors: [batch], exporters: [awsxray] }
YAML
/opt/aws/aws-otel-collector/bin/aws-otel-collector-ctl -c /opt/aws/aws-otel-collector/etc/config.yaml -a start
ACCOUNT_ID=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | grep -oP '"accountId"\s*:\s*"\K[0-9]+')
aws s3 cp s3://shop-app-assets-$ACCOUNT_ID/release/shop-app.jar /opt/shop/shop-app.jar --region ap-southeast-1
curl -sL -o /opt/shop/aws-opentelemetry-agent.jar \
https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest/download/aws-opentelemetry-agent.jar
export OTEL_SERVICE_NAME=shop-app OTEL_PROPAGATORS=xray,tracecontext OTEL_TRACES_SAMPLER=xray
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=lab
nohup java -javaagent:/opt/shop/aws-opentelemetry-agent.jar -jar /opt/shop/shop-app.jar \
--spring.profiles.active=aws --logging.structured.format.file=ecs \
--logging.file.name=/var/log/shop/app.log > /var/log/shop/stdout.log 2>&1 &
EOF
aws ec2 create-launch-template-version --profile shop-lab --launch-template-name shop-lt \
--source-version '$Latest' --version-description "cw-agent + adot" \
--launch-template-data "{\"UserData\":\"$(base64 -w0 user-data.sh)\"}"
aws ec2 modify-launch-template --profile shop-lab --launch-template-name shop-lt --default-version '$Latest'
# Thay instance theo đợt để nhận user data mới
aws autoscaling start-instance-refresh --profile shop-lab --auto-scaling-group-name shop-asg \
--preferences '{"MinHealthyPercentage":50,"InstanceWarmup":120}'
Console: EC2 → Launch Templates → shop-lt → Actions → Modify template (Create new version) → Advanced details → User data; sau đó Auto Scaling Groups → shop-asg → Instance refresh → Start.
Nếu shop-app.jar chưa có trong S3: build jar với profile aws và aws s3 cp target/shop-app.jar s3://shop-app-assets-$ACCOUNT_ID/release/.
Bước 5: Metric filter đếm ERROR.
aws logs put-metric-filter --profile shop-lab --log-group-name /shop/app \
--filter-name shop-app-error-count --filter-pattern '{ $.log.level = "ERROR" }' \
--metric-transformations metricName=ErrorCount,metricNamespace=ShopApp,metricValue=1,defaultValue=0
Console: CloudWatch → Log groups → /shop/app → Metric filters → Create metric filter.
Bước 6: SNS topic + đăng ký email.
TOPIC_ARN=$(aws sns create-topic --profile shop-lab --name shop-alerts \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --query TopicArn --output text)
aws sns subscribe --profile shop-lab --topic-arn $TOPIC_ARN --protocol email --notification-endpoint $EMAIL
# Mở hộp thư, bấm "Confirm subscription" trước khi tiếp
Console: SNS → Topics → Create topic (Standard) → Create subscription → Email.
Bước 7: Alarm shop-alb-5xx-high dùng metric math (ELB 5xx + Target 5xx).
cat > alarm-5xx.json <<EOF
{
"AlarmName": "shop-alb-5xx-high",
"AlarmDescription": "shop-alb tra >10 loi 5xx/phut trong 3/5 phut",
"ActionsEnabled": true,
"AlarmActions": ["$TOPIC_ARN"], "OKActions": ["$TOPIC_ARN"],
"EvaluationPeriods": 5, "DatapointsToAlarm": 3,
"Threshold": 10, "ComparisonOperator": "GreaterThanThreshold",
"TreatMissingData": "notBreaching",
"Metrics": [
{ "Id": "elb", "ReturnData": false, "MetricStat": { "Period": 60, "Stat": "Sum",
"Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "HTTPCode_ELB_5XX_Count",
"Dimensions": [{ "Name": "LoadBalancer", "Value": "$ALB_SUFFIX" }] } } },
{ "Id": "tgt", "ReturnData": false, "MetricStat": { "Period": 60, "Stat": "Sum",
"Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "HTTPCode_Target_5XX_Count",
"Dimensions": [{ "Name": "LoadBalancer", "Value": "$ALB_SUFFIX" }] } } },
{ "Id": "total", "Expression": "FILL(elb,0) + FILL(tgt,0)", "Label": "All 5xx", "ReturnData": true }
],
"Tags": [{ "Key": "Project", "Value": "shop-app" }, { "Key": "Env", "Value": "lab" }]
}
EOF
aws cloudwatch put-metric-alarm --profile shop-lab --cli-input-json file://alarm-5xx.json
# Alarm thứ hai trên metric từ log
aws cloudwatch put-metric-alarm --profile shop-lab --alarm-name shop-app-error-log-high \
--namespace ShopApp --metric-name ErrorCount --statistic Sum --period 60 \
--evaluation-periods 5 --datapoints-to-alarm 3 --threshold 20 \
--comparison-operator GreaterThanThreshold --treat-missing-data notBreaching \
--alarm-actions $TOPIC_ARN \
--tags Key=Project,Value=shop-app Key=Env,Value=lab
Console: CloudWatch → Alarms → Create alarm → Select metric → Add math → Expression.
Bước 8: Dashboard shop-overview.
cat > dashboard.json <<EOF
{ "widgets": [
{ "type": "metric", "x": 0, "y": 0, "width": 12, "height": 6, "properties": {
"title": "ALB requests & 5xx", "region": "$AWS_REGION", "stat": "Sum", "period": 60,
"metrics": [
[ "AWS/ApplicationELB", "RequestCount", "LoadBalancer", "$ALB_SUFFIX" ],
[ ".", "HTTPCode_ELB_5XX_Count", ".", ".", { "color": "#d62728" } ],
[ ".", "HTTPCode_Target_5XX_Count", ".", ".", { "color": "#ff7f0e" } ] ] } },
{ "type": "metric", "x": 12, "y": 0, "width": 12, "height": 6, "properties": {
"title": "Target p99 latency", "region": "$AWS_REGION", "stat": "p99", "period": 60,
"metrics": [[ "AWS/ApplicationELB", "TargetResponseTime", "LoadBalancer", "$ALB_SUFFIX" ]] } },
{ "type": "metric", "x": 0, "y": 6, "width": 12, "height": 6, "properties": {
"title": "ASG CPU & mem", "region": "$AWS_REGION", "stat": "Average", "period": 60,
"metrics": [
[ "AWS/EC2", "CPUUtilization", "AutoScalingGroupName", "shop-asg" ],
[ "ShopApp/EC2", "mem_used_percent", "AutoScalingGroupName", "shop-asg" ] ] } },
{ "type": "log", "x": 12, "y": 6, "width": 12, "height": 6, "properties": {
"title": "Errors by exception (1h)", "region": "$AWS_REGION",
"query": "SOURCE '/shop/app' | filter log.level = \"ERROR\" | parse message /(?<exception>[A-Za-z.]+Exception)/ | stats count(*) as total by exception | sort total desc",
"view": "table" } },
{ "type": "alarm", "x": 0, "y": 12, "width": 24, "height": 3, "properties": {
"title": "Alarms", "alarms": [
"arn:aws:cloudwatch:$AWS_REGION:$ACCOUNT_ID:alarm:shop-alb-5xx-high",
"arn:aws:cloudwatch:$AWS_REGION:$ACCOUNT_ID:alarm:shop-app-error-log-high" ] } }
] }
EOF
aws cloudwatch put-dashboard --profile shop-lab --dashboard-name shop-overview --dashboard-body file://dashboard.json
Console: CloudWatch → Dashboards → Create dashboard → Add widget.
Bước 9: CloudTrail trail vào bucket log. Bucket policy dưới đây cấp quyền ghi cho CloudTrail và AWS Config của bước tùy chọn; không ghi đè policy lần nữa khi bật Config:
cat > trail-bucket-policy.json <<EOF
{ "Version": "2012-10-17", "Statement": [
{ "Sid": "AclCheck", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::shop-app-logs-$ACCOUNT_ID",
"Condition": { "StringEquals": { "aws:SourceArn": "arn:aws:cloudtrail:$AWS_REGION:$ACCOUNT_ID:trail/shop-trail" } } },
{ "Sid": "Write", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:PutObject", "Resource": "arn:aws:s3:::shop-app-logs-$ACCOUNT_ID/cloudtrail/AWSLogs/$ACCOUNT_ID/*",
"Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceArn": "arn:aws:cloudtrail:$AWS_REGION:$ACCOUNT_ID:trail/shop-trail" } } },
{ "Sid": "ConfigBucketRead", "Effect": "Allow", "Principal": { "Service": "config.amazonaws.com" },
"Action": ["s3:GetBucketAcl", "s3:ListBucket"], "Resource": "arn:aws:s3:::shop-app-logs-$ACCOUNT_ID",
"Condition": { "StringEquals": { "aws:SourceAccount": "$ACCOUNT_ID" } } },
{ "Sid": "ConfigBucketWrite", "Effect": "Allow", "Principal": { "Service": "config.amazonaws.com" },
"Action": "s3:PutObject", "Resource": "arn:aws:s3:::shop-app-logs-$ACCOUNT_ID/config/AWSLogs/$ACCOUNT_ID/Config/*",
"Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceAccount": "$ACCOUNT_ID" } } } ] }
EOF
aws s3api put-bucket-policy --profile shop-lab --bucket shop-app-logs-$ACCOUNT_ID --policy file://trail-bucket-policy.json
aws cloudtrail create-trail --profile shop-lab --name shop-trail \
--s3-bucket-name shop-app-logs-$ACCOUNT_ID --s3-key-prefix cloudtrail \
--is-multi-region-trail --enable-log-file-validation \
--tags-list Key=Project,Value=shop-app Key=Env,Value=lab
aws cloudtrail start-logging --profile shop-lab --name shop-trail
Console: CloudTrail → Trails → Create trail → chọn bucket có sẵn, bật Log file validation.
Bước 10 (tùy chọn): AWS Config với 1 rule.
aws iam create-service-linked-role --profile shop-lab --aws-service-name config.amazonaws.com 2>/dev/null
cat > config-recorder.json <<EOF
{
"name": "default",
"roleARN": "arn:aws:iam::${ACCOUNT_ID}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig",
"recordingGroup": {
"allSupported": false,
"includeGlobalResourceTypes": false,
"resourceTypes": ["AWS::EC2::SecurityGroup"]
}
}
EOF
aws configservice put-configuration-recorder --profile shop-lab \
--configuration-recorder file://config-recorder.json
aws configservice put-delivery-channel --profile shop-lab --delivery-channel \
"name=default,s3BucketName=shop-app-logs-$ACCOUNT_ID,s3KeyPrefix=config"
aws configservice start-configuration-recorder --profile shop-lab --configuration-recorder-name default
aws configservice put-config-rule --profile shop-lab --config-rule \
'{"ConfigRuleName":"shop-restricted-ssh","Source":{"Owner":"AWS","SourceIdentifier":"INCOMING_SSH_DISABLED"}}'
Policy ở bước 9 đã cấp quyền ghi cho config.amazonaws.com dưới prefix config/. Chỉ ghi SecurityGroup để giữ số configuration item nhỏ.
Kiểm tra kết quả
# 1. Log đã lên (sau khi instance refresh xong ~5 phút)
aws logs describe-log-streams --profile shop-lab --log-group-name /shop/app \
--order-by LastEventTime --descending --query 'logStreams[].logStreamName'
# ["i-0a1b.../app.log", "i-0c2d.../app.log"]
aws logs tail /shop/app --profile shop-lab --since 5m --format short | head
# 2. Gây lỗi: gọi endpoint không tồn tại ở backend 60 lần/phút trong 4 phút
# (shop-app trả 500 cho /api/debug/fail nếu bạn thêm endpoint test; hoặc dừng tạm RDS)
for i in $(seq 1 240); do curl -s -o /dev/null -w "%{http_code}\n" http://$ALB_DNS/api/debug/fail; sleep 1; done | sort | uniq -c
# 3. Metric filter có số
aws cloudwatch get-metric-statistics --profile shop-lab --namespace ShopApp --metric-name ErrorCount \
--statistics Sum --period 60 --start-time $(date -u -d '10 min ago' +%FT%TZ) --end-time $(date -u +%FT%TZ)
# 4. Alarm chuyển trạng thái (sau ≥3 phút vượt ngưỡng) và email đến
aws cloudwatch describe-alarms --profile shop-lab --alarm-names shop-alb-5xx-high \
--query 'MetricAlarms[0].[StateValue,StateReason]'
# ["ALARM", "Threshold Crossed: 3 out of the last 5 datapoints ..."]
aws cloudwatch describe-alarm-history --profile shop-lab --alarm-name shop-alb-5xx-high --max-records 5
# 5. CloudTrail: dừng 1 instance rồi tìm event (Event history có sau ~5 phút, file S3 sau ~15 phút)
INSTANCE=$(aws autoscaling describe-auto-scaling-groups --profile shop-lab --auto-scaling-group-names shop-asg \
--query 'AutoScalingGroups[0].Instances[0].InstanceId' --output text)
aws ec2 stop-instances --profile shop-lab --instance-ids $INSTANCE
aws cloudtrail lookup-events --profile shop-lab --lookup-attributes AttributeKey=EventName,AttributeValue=StopInstances \
--query 'Events[0].[EventTime,Username,CloudTrailEvent]' --output text | head -c 800
aws s3 ls s3://shop-app-logs-$ACCOUNT_ID/cloudtrail/AWSLogs/$ACCOUNT_ID/CloudTrail/$AWS_REGION/ --recursive --profile shop-lab | tail -3
# 6. X-Ray: có trace và service map
aws xray get-service-graph --profile shop-lab --start-time $(date -u -d '15 min ago' +%s) --end-time $(date -u +%s) \
--query 'Services[].[Name,Type]' --output table
# shop-app | AWS::EC2::Instance ; shop-db... | Database::SQL
Console: CloudWatch → Dashboards → shop-overview: thấy 5xx tăng, bảng exception, alarm đỏ. X-Ray → Traces (hoặc CloudWatch → Application Signals → Traces) → Service map.
Thử thêm
- Instance chết, log còn: instance bạn vừa stop sẽ bị ASG terminate và thay mới. Chạy Logs Insights
filter @logStream like /<instance id cũ>/: log vẫn còn. Đây chính là bài học mở đầu. - Subscription filter: đẩy log ERROR real-time sang Lambda
shop-order-notifier(22.12) để post Slack:aws logs put-subscription-filter --log-group-name /shop/app --filter-name errors --filter-pattern '{ $.log.level = "ERROR" }' --destination-arn <lambda arn>. Nhớlambda add-permissioncho principallogs.amazonaws.com. - CloudTrail → alarm: tạo trail gửi thêm sang log group
/shop/cloudtrail, metric filter{ $.eventName = "AuthorizeSecurityGroupIngress" }, alarm báo mỗi khi ai mở SG. So sánh với rule Config ở bước 10: Config nói "đang sai", CloudTrail nói "ai làm sai". - X-Ray annotation: trong code thêm
Span.current().setAttribute("orderId", id)và cấu hình ADOTindexed_attributesđể filter trace theoannotation.orderId.
Dọn tài nguyên
Thứ tự: dừng cái phát sinh dữ liệu trước (Config, CloudTrail), rồi alarm/dashboard/filter, rồi log group, SNS, cuối cùng là ALB/ASG/RDS của lab trước nếu không còn dùng.
# AWS Config (tốn theo item nếu quên)
aws configservice delete-config-rule --profile shop-lab --config-rule-name shop-restricted-ssh
aws configservice stop-configuration-recorder --profile shop-lab --configuration-recorder-name default
aws configservice delete-delivery-channel --profile shop-lab --delivery-channel-name default
aws configservice delete-configuration-recorder --profile shop-lab --configuration-recorder-name default
# CloudTrail (file trong S3 vẫn còn, xóa cùng bucket nếu muốn)
aws cloudtrail stop-logging --profile shop-lab --name shop-trail
aws cloudtrail delete-trail --profile shop-lab --name shop-trail
# Alarm, dashboard, metric filter, log group
aws cloudwatch delete-alarms --profile shop-lab --alarm-names shop-alb-5xx-high shop-app-error-log-high
aws cloudwatch delete-dashboards --profile shop-lab --dashboard-names shop-overview
aws logs delete-metric-filter --profile shop-lab --log-group-name /shop/app --filter-name shop-app-error-count
aws logs delete-log-group --profile shop-lab --log-group-name /shop/app
# SNS
aws sns delete-topic --profile shop-lab --topic-arn $TOPIC_ARN
# SSM parameter
aws ssm delete-parameter --profile shop-lab --name /shop/cw-agent-config
# Nếu không làm lab tiếp: xóa ASG, ALB, RDS theo thứ tự của lab 22.5 / 22.7
aws autoscaling update-auto-scaling-group --profile shop-lab --auto-scaling-group-name shop-asg --min-size 0 --desired-capacity 0
aws s3 rm s3://shop-app-logs-$ACCOUNT_ID/cloudtrail/ --recursive --profile shop-lab
aws s3 rm s3://shop-app-logs-$ACCOUNT_ID/config/ --recursive --profile shop-lab
Nhắc: ALB, RDS ngoài Free Tier, AWS Config recorder và log group không có retention là các thứ hay bị quên và tốn tiền âm thầm.
Phỏng vấn và đề thi SAA hỏi về CloudWatch, CloudTrail, X-Ray thì sao?
Q1. Bạn đưa log của Spring Boot trên EC2/ECS lên CloudWatch và cảnh báo thế nào?
🇻🇳 Tiếng Việt:
Em cho app ghi log JSON (structured logging của Spring Boot 3.4), trên EC2 cài CloudWatch agent đọc file và đẩy vào log group
/shop/app, mỗi instance một stream; trên ECS thì dùng awslogs driver nên không cần agent. Em đặt retention 30 ngày, dùng metric filter đếm dòng ERROR thành metric custom rồi gắn alarm với "3 trên 5 datapoints" để tránh flapping, action gửi SNS. Metric nghiệp vụ như số thanh toán lỗi em đẩy qua Micrometer CloudWatch registry. Khi điều tra thì dùng Logs Insights, lọc theo trace id để nối với X-Ray.
🇬🇧 English:
I make the app write structured JSON logs. On EC2 the CloudWatch agent tails the file and ships it to the
/shop/applog group with one stream per instance; on ECS the awslogs driver does it without an agent. I set a 30-day retention, add a metric filter that counts ERROR lines as a custom metric, and attach an alarm using "3 out of 5 datapoints" to avoid flapping, with an SNS action. Business metrics such as failed payments go through the Micrometer CloudWatch registry. For investigation I use Logs Insights and filter by trace id to correlate with X-Ray.
Q2. CloudWatch, CloudTrail và AWS Config khác nhau thế nào? Khi nào dùng cái nào?
🇻🇳 Tiếng Việt:
CloudWatch trả lời "hệ thống đang chạy thế nào": metric, log, alarm. CloudTrail trả lời "ai đã gọi API gì, lúc nào, từ đâu": dùng cho audit và điều tra bảo mật, ví dụ ai mở Security Group. AWS Config trả lời "tài nguyên đang cấu hình ra sao và có đúng chuẩn không", lưu lịch sử thay đổi và có thể tự sửa bằng SSM Automation. Thực tế ba cái bổ nhau: Config phát hiện SG sai, CloudTrail cho biết ai sửa, CloudWatch alarm báo hậu quả.
🇬🇧 English:
CloudWatch answers "how is the system performing": metrics, logs, alarms. CloudTrail answers "who called which API, when and from where": it is for auditing and security investigation, for example who opened a Security Group. AWS Config answers "how are resources configured and are they compliant", keeping a configuration history and optionally auto-remediating with SSM Automation. In practice they complement each other: Config detects the misconfigured SG, CloudTrail tells you who changed it, and a CloudWatch alarm reports the impact.
Q3. Vì sao CloudWatch không có metric RAM của EC2? Làm sao autoscale theo RAM?
🇻🇳 Tiếng Việt:
Hypervisor chỉ thấy CPU, network, disk I/O, status check; RAM và disk usage là chuyện bên trong OS nên AWS không thấy. Muốn có thì cài CloudWatch agent, nó đẩy
mem_used_percentvào namespace custom với dimension AutoScalingGroupName. Sau đó tạo scaling policy trên metric đó. Nhưng với Java em thường scale theo CPU hay request count per target, vì heap của JVM luôn gần max cấu hình, RAM không phản ánh tải.
🇬🇧 English:
The hypervisor only sees CPU, network, disk I/O and status checks; memory and disk usage live inside the guest OS, so AWS cannot see them. You install the CloudWatch agent, which publishes
mem_used_percentinto a custom namespace with the AutoScalingGroupName dimension, and build a scaling policy on it. For Java, though, I usually scale on CPU or request count per target, because the JVM heap sits near its configured max regardless of load, so memory is a poor signal.
Q4. X-Ray hoạt động thế nào với Spring Boot? Sampling là gì?
🇻🇳 Tiếng Việt:
Em dùng ADOT Java agent gắn bằng
-javaagent, không sửa code: nó tự tạo span cho Spring MVC, JDBC, Lettuce, AWS SDK, gửi OTLP sang ADOT collector chạy cạnh app (sidecar trên ECS, layer trên Lambda), collector export sang X-Ray. Trace id lan truyền qua headerX-Amzn-Trace-Id. Sampling quyết định ghi trace nào: mặc định 1 request/giây cộng 5% phần còn lại, để không ghi hết 100% gây tốn tiền. Annotation là thuộc tính được index để tìm trace; metadata thì không.
🇬🇧 English:
I attach the ADOT Java agent with
-javaagent, no code changes: it auto-instruments Spring MVC, JDBC, Lettuce and the AWS SDK, sends OTLP to an ADOT collector running next to the app (a sidecar on ECS, a layer on Lambda), and the collector exports to X-Ray. The trace id propagates in theX-Amzn-Trace-Idheader. Sampling decides which requests are recorded: by default one request per second plus 5% of the rest, so we do not pay for 100%. Annotations are indexed attributes you can search traces by; metadata is not indexed.
Góc thi SAA-C03
Câu 1: Một công ty cần biết người dùng IAM nào đã thay đổi rule của Security Group trong tuần trước, và cần chứng minh được log không bị chỉnh sửa cho auditor. Giải pháp nào đáp ứng với LEAST operational overhead?
A. Bật VPC Flow Logs và query bằng Athena.
B. Tạo CloudTrail trail ghi vào S3, bật log file validation.
C. Dùng AWS Config rule restricted-ssh và xem compliance timeline.
D. Tạo CloudWatch alarm trên metric NetworkPacketsIn của instance.
Đáp án: B. "Ai đã gọi API" là CloudTrail; "chứng minh log không bị sửa" là log file validation (digest có chữ ký). A: Flow Logs ghi traffic IP, không ghi API call. C: Config biết SG đang sai và lịch sử cấu hình, nhưng không ghi principal nào gọi API (nó chỉ có thể link sang CloudTrail). D: metric network không liên quan.
Câu 2: Ứng dụng Java chạy trên Auto Scaling group. Team muốn scale out khi memory utilization trung bình vượt 75%. Hiện CloudWatch không có metric này. Cần làm gì?
A. Bật detailed monitoring cho EC2.
B. Cài CloudWatch agent trên instance (qua launch template) để đẩy custom metric memory, rồi tạo scaling policy trên metric đó.
C. Dùng CloudWatch Logs Insights để tính memory từ log hệ thống.
D. Bật Enhanced Monitoring.
Đáp án: B. Memory là metric trong OS, phải có agent. A: detailed monitoring chỉ làm metric có sẵn (CPU, network…) chuyển từ 5 phút sang 1 phút, không thêm metric RAM. C: không phải cách tạo metric để scaling. D: Enhanced Monitoring là của RDS (metric OS của DB instance), không phải EC2.
Câu 3: Hệ thống microservices (API Gateway → Lambda → DynamoDB) có một số request chậm bất thường. Solutions architect cần xác định thành phần nào gây ra độ trễ theo từng request. Dịch vụ nào phù hợp nhất?
A. CloudWatch Logs Insights
B. AWS X-Ray
C. CloudTrail Insights
D. VPC Flow Logs
Đáp án: B. "Độ trễ theo từng request xuyên nhiều service" = distributed tracing = X-Ray (service map + trace timeline). A: Insights tìm log, không vẽ chuỗi gọi với thời gian từng đoạn. C: CloudTrail Insights phát hiện lượng API call bất thường (ví dụ ai đó gọi RunInstances ồ ạt), không đo latency ứng dụng. D: Flow Logs là traffic mạng ở tầng IP.
Cách nhớ ngắn gọn
- Watch = số đo & log (how). Trail = dấu vết API (who). Config = cấu hình & compliance (what). X-Ray = soi xuyên service (where slow).
- EC2 không có RAM/disk usage → cài agent. Log trong máy → agent. Lambda/ECS → log tự lên.
- Alarm: M of N datapoints + treat-missing-data đúng loại + composite để gom; metric filter biến log thành số để alarm.
- Log giữ lâu → S3 (rẻ) + Athena; Logs Insights tính tiền theo GB scan → thu hẹp thời gian.
| Từ khóa trong đề | Dịch vụ / tính năng |
|---|---|
| who made the API call, audit, compliance evidence, tamper-proof log | CloudTrail (+ log file validation, organization trail) |
| S3 object-level / Lambda invoke activity | CloudTrail data events |
| unusual API activity volume | CloudTrail Insights |
| resource configuration history, compliance rule, auto-remediate | AWS Config (+ SSM Automation) |
| memory / disk metric of EC2 | CloudWatch agent (custom metric) |
| 1-minute EC2 metrics | Detailed monitoring |
| count occurrences in logs → alarm | Metric filter |
| ad-hoc query logs | Logs Insights |
| stream logs real-time to another service | Subscription filter (Kinesis / Firehose / Lambda / OpenSearch) |
| reduce alarm noise, alarm on combination | Composite alarm; M-of-N datapoints |
| latency per request across services, service map | X-Ray (ADOT) |
| simulate user checks of endpoint | CloudWatch Synthetics canary |
| IP traffic accepted/rejected at ENI | VPC Flow Logs (không phải CloudTrail) |
Tự kiểm tra
Vì sao log phải rời khỏi EC2 ngay, và cách rời là gì?
Instance trong ASG bị terminate bất kỳ lúc nào, EBS root volume mất theo. CloudWatch agent tail file log và PutLogEvents lên log group, mỗi instance một log stream; stream còn lại đến hết retention dù instance đã chết.
Metric filter và Logs Insights khác nhau chỗ nào?
Metric filter chạy liên tục trên log mới, đếm/trích số thành metric để alarm và vẽ chart. Logs Insights là query ad-hoc khi điều tra, tính tiền theo GB scan, không tạo alarm trực tiếp.
treat-missing-data nên là gì cho metric đếm lỗi, và gì cho heartbeat?
Đếm lỗi: notBreaching (không có lỗi là tốt). Heartbeat: breaching (không có tín hiệu nghĩa là job chết).
Management event và data event của CloudTrail khác nhau gì?
Management event: thao tác lên tài nguyên (RunInstances, CreateBucket, AssumeRole), 1 trail ghi miễn phí. Data event: thao tác lên dữ liệu trong tài nguyên (S3 GetObject/PutObject, Lambda Invoke, DynamoDB item), phải bật riêng và tính tiền theo số event.
Annotation và metadata trong X-Ray khác nhau thế nào?
Annotation là key-value được index, dùng trong filter expression để tìm trace (annotation.orderId = 1042), giới hạn 50 mỗi segment. Metadata không index, chỉ để xem chi tiết khi mở trace.
🧪 Thực hành bài này: Intermediate: Ứng dụng HA: ALB + ASG + RDS + S3 + SQS (chưa triển khai) · lab labs/aws