CloudWatch, CloudTrail, AWS Config, X-Ray: nhìn thấy shop-app đang chạy thế nào

5 câu hỏi~45 phútTừ khóa: CloudWatch metrics/logs/alarms, Logs Insights, metric filter, CloudWatch agent, CloudTrail, AWS Config, X-Ray, ADOT, SAA: Domain 2 Resilient / Domain 3 Secure

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 đề

3. Giải pháp: 4 dịch vụ, 4 câu hỏi

Câu hỏiDị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 Insightsmetric, 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?CloudTrailmanagement 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 Configconfiguration 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íCloudWatchCloudTrailAWS ConfigX-Ray
Trả lời câuHệ 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àoRequest chậm/lỗi ở service nào
Nguồn dữ liệuMetric dịch vụ, agent, SDK, logAPI call tới AWS (Console, CLI, SDK, service khác)Snapshot cấu hình resourceSegment từ SDK/agent/daemon
Độ trễGiây → 1 phút~15 phút tới S3; gần real-time nếu sang CW LogsPhútGiây
Dùng khiAlarm, dashboard, autoscaling, tìm logSecurity incident, audit, compliance, "ai xóa bucket"Compliance liên tục, auto-remediation, timeline cấu hìnhDebug latency microservice / Lambda
Chi phí chínhCustom metric, log ingest (GB) + lưu trữ, alarm, Insights query (GB scan)1 trail management event miễn phí; data event, Insights tốnTheo configuration item và rule evaluationTheo trace ghi/lấy; sampling giảm chi phí

So sánh nội bộ CloudWatch hay ra đề:

CặpKhá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 InsightsFilter: 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 alarmComposite = 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 taskSubscription: real-time đẩy sang Kinesis/Firehose/Lambda/OpenSearch. Export: batch một lần sang S3
CloudWatch Synthetics vs RUMSynthetics: 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 InsightsTì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 metadataAnnotation: 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

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

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

MetricNên chọnVì
5xx count, ErrorCountnotBreachingKhông có lỗi = tốt
Heartbeat job batch ("JobCompleted" mỗi giờ)breachingKhông có datapoint = job chết
CPU của instance (ASG hay scale về 0 ban đêm)ignoreGiữ 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

Chi phí & thời gian

Chuẩn bị

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

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/app log 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_percent và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_percent into 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 header X-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 the X-Amzn-Trace-Id header. 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

Từ khóa trong đềDịch vụ / tính năng
who made the API call, audit, compliance evidence, tamper-proof logCloudTrail (+ log file validation, organization trail)
S3 object-level / Lambda invoke activityCloudTrail data events
unusual API activity volumeCloudTrail Insights
resource configuration history, compliance rule, auto-remediateAWS Config (+ SSM Automation)
memory / disk metric of EC2CloudWatch agent (custom metric)
1-minute EC2 metricsDetailed monitoring
count occurrences in logs → alarmMetric filter
ad-hoc query logsLogs Insights
stream logs real-time to another serviceSubscription filter (Kinesis / Firehose / Lambda / OpenSearch)
reduce alarm noise, alarm on combinationComposite alarm; M-of-N datapoints
latency per request across services, service mapX-Ray (ADOT)
simulate user checks of endpointCloudWatch Synthetics canary
IP traffic accepted/rejected at ENIVPC 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