
服務掛了,你知道原因嗎?如果你的回答是「看 log」,那你只用到了可觀測性的三分之一。更常見的是另一種:告警設了三十條,半夜響了沒人理,因為前二十九次都是假的。
結論:問題不是「要不要設告警」,是「設了沒人理」
Metrics、Logs、Traces 三支柱大家都知道,AWS 對應 CloudWatch、CloudWatch Logs、X-Ray。這篇不重講工具,講四條原則和三個預設坑:
- 每條告警都要對應一個動作——收到了不知道該幹嘛的告警,刪掉。這是過濾噪音唯一有效的標準。
- 告症狀,不告原因——告「結帳失敗率超過 2%」,不要告「CPU 超過 80%」。CPU 高不一定有事,結帳掛了一定有事。
- 分級對應通道——會吵醒人的、上班看的、只進儀表板的,三條路分開。
- 每條附處理手冊連結——半夜被叫起來的人需要的是步驟,不是一個數字。
三個預設坑:log driver 預設會卡死你的程式、log 預設永不過期、CloudWatch 的抑制只做得到 Alertmanager 的一部分。「抑制」的意思是上游掛了,就別再噴下游的告警;Alertmanager 是 Prometheus 那套裡負責分派告警的元件。
一、八條告警,每條對應一個斷裂點
常見的指標表長這樣:「EC2 CPU > 80%、RDS 記憶體 < 500 MB」——這些是原因不是症狀,而且門檻是憑感覺寫的。這張表每條都是使用者會感覺到、或錢會流走的事。
| # | 告警 | 指標 | 為什麼是這條 |
|---|---|---|---|
| 1 | ALB 的 5xx 比率 | HTTPCode_Target_5XX_Count ÷ RequestCount | 使用者實際感受,最該有的一條 |
| 2 | ALB 回應時間 p99 | TargetResponseTime 的 p99 | 不要用平均值,平均會把尾巴藏掉 |
| 3 | NAT 配置不到 port | ErrorPortAllocation > 0 | 官方定義:「大於零代表同時連線太多」——這是唯一看得到 NAT 撞上限的地方(見 第 2 篇) |
| 4 | 資料庫連線數逼近上限 | RDS DatabaseConnections;有 Proxy 再看 DatabaseConnectionsCurrentlySessionPinned | 見 第 6 篇,釘住率高 Proxy 等於沒裝 |
| 5 | 佇列積壓、死信佇列非空 | SQS ApproximateNumberOfMessagesVisible | 死信佇列本來就不該有東西,有就是壞訊息 |
| 6 | ECS 跑不到預期數量 | RunningTaskCount < desired 持續 N 分鐘 | 擴容失敗、部署壞掉(部署失敗另有事件可接,見表下) |
| 7 | 對外部依賴的錯誤率 | 自訂指標,分依賴 | 金流變慢不該把會員登入一起拖死,要分開看 |
| 8 | 預算超標 | AWS Budgets 的預測值 | 異常費用常常是被入侵或忘記關的第一個訊號 |
第 6 條的「部署壞掉」還有一個更直接的訊號。ECS 部署失敗時,會送出一個 EventBridge 事件 SERVICE_DEPLOYMENT_FAILED(EventBridge 是 AWS 傳遞「某件事發生了」這類事件的服務)。把它接到同一條通知,細節見 第 10 篇。
第 3 條設的時候,統計方式和維度要選對。同一頁官方文件寫,ErrorPortAllocation 的單位是次數(Count),建議用 Sum,也就是把這段時間內配置失敗的次數加總。
維度是「這個數字要按什麼分開看」的篩選條件。NAT gateway 分 zonal(以單一可用區為單位)和 regional(以整個區域為單位)兩種。zonal 的只用 NatGatewayId 一個維度。regional 的要 NatGatewayId 加上 AvailabilityZone,按可用區分開看。
同一頁還有 ActiveConnectionCount,是目前開著的連線數,建議看 Max,適合看趨勢。但真的撞到上限時,先跳的是 ErrorPortAllocation。
加碼一條:ACM 憑證續期失敗。 ACM(AWS Certificate Manager)是 AWS 管 HTTPS 憑證的服務。DNS 驗證是在你的 DNS 加一筆 CNAME 記錄,證明這個網域是你的。用 DNS 驗證的憑證會自動續期,但官方文件列了兩個前提:
- 憑證正被某個 AWS 服務使用。
- 驗證用的 CNAME 記錄都還在公開 DNS 上查得到。
少一個就續不了。驗不過的時候,ACM 會在到期前 30、15、7、3、1 天發 AWS Health 和 EventBridge 事件。這些事件沒接到 SNS(AWS 的通知服務,負責把訊息推到 Email、Slack 這些地方),就沒有人會收到。等憑證真的過期,整站 HTTPS 一起掛。
一條 alarm 長這樣——死信佇列那條最簡單也最重要:
aws cloudwatch put-metric-alarm --alarm-name "dlq-not-empty" \
--namespace AWS/SQS --metric-name ApproximateNumberOfMessagesVisible \
--dimensions Name=QueueName,Value=orders-dlq \
--statistic Maximum --period 60 --threshold 0 \
--comparison-operator GreaterThanThreshold --evaluation-periods 1 \
--alarm-actions arn:aws:sns:ap-northeast-1:123456789012:oncall注意 CPU 沒有在表上。 CPU 進儀表板、不進告警。它是你收到第 1 條告警之後去查的東西,不是把你叫醒的東西。
二、CloudWatch 比 Alertmanager 弱在哪
從 K8s 過來的人最容易在這裡失望。Alertmanager 原生有分組、抑制、靜音、路由四樣,CloudWatch 對得上的只有一部分:
- 分組——同類告警合併成一則。CloudWatch 沒有直接對應,十個 task 同時不健康就是十則。
- 抑制——上游掛了就別噴下游的告警。CloudWatch 沒有通用的 inhibition rule;最接近的是 composite alarm 的
ActionsSuppressor。composite alarm 是把好幾條 alarm 用AND/OR/NOT組成一條的組合告警;ActionsSuppressor只壓得住 composite alarm 自己的動作(下面講)。 - 靜音——維護時段把通知關掉。CloudWatch 用 Alarm Mute Rules 做這件事。它只壓通知、照樣判斷:靜音期間 alarm 照樣評估、狀態照樣變,只是動作不執行。時段結束時,CloudWatch 會照 alarm 當下的狀態,把被壓掉的動作補發。規格是這樣:時段用 cron(排程語法)或
at(...)排,持續 1 分鐘到 15 天;一條規則最多指定 100 個 alarm;metric alarm(盯單一指標的一般 alarm)和 composite alarm 都能掛。 - 路由——Alertmanager 依標籤樹狀分派;CloudWatch 只能靠 SNS topic 分流,粗得多。
後果:同一個根因噴一整串,大家開始無視告警,告警疲勞就從這裡開始。
能做的是 composite alarm:官方文件說它把多個 alarm 用 AND / OR / NOT 組合,只在組合條件成立時發。用法是把根因和衍生綁在一起,只讓根因那則發:
aws cloudwatch put-composite-alarm --alarm-name "db-down-root-cause" \
--alarm-rule 'ALARM("rds-connections-maxed") AND ALARM("alb-5xx-rate")' \
--alarm-actions arn:aws:sns:ap-northeast-1:123456789012:oncall然後把 alb-5xx-rate 自己的通知拿掉,它只當 composite 的輸入。這不是抑制,是「把衍生告警降級成條件」——效果接近,但要自己一條一條綁。
部署時段也能用同一招擋掉:在 composite alarm 的規則裡加上 NOT ALARM(DeploymentInProgress),意思是「部署中」那條 alarm 沒在響的時候才算數。這是官方 API 文件自己舉的例子。
真正叫「抑制」的是 composite alarm 的 ActionsSuppressor,用途是上游已經在響,這條就先別發。官方文件寫,指定的 suppressor alarm(例如「資料庫掛了」)處於 ALARM 時,這個 composite alarm 不執行動作;suppressor 回到 OK 才恢復。限制在名字裡:只有 composite alarm 能被壓,一般的 metric alarm 要先包進 composite 才享受得到。
兩條 alarm 變狀態的時間不一定對得上,所以另有兩個參數補時間差。ActionsSuppressorWaitPeriod:composite 比 suppressor 先進 ALARM 時,最多等幾秒讓 suppressor 跟上,等不到才發。ActionsSuppressorExtensionPeriod:suppressor 回 OK 之後,再多壓幾秒,讓 composite 自己也有時間回 OK。文件建議兩個都設 60 秒,因為 metric alarm 每分鐘評估一次。
通知的管線就一條:
CloudWatch Alarm → SNS topic → Slack / PagerDuty / Email分級就分成不同 SNS topic,接不同通道——原則第 3 條的「會吵醒人的」和「上班看的」各一個 topic,「只進儀表板的」不接 topic。
三、Log driver 預設會卡死你的程式
這條很少有人提,我整理架構筆記時才對到。
ECS 預設用 awslogs driver 把容器的 stdout / stderr 送到 CloudWatch。Docker 的 log driver 有兩種投遞模式,Docker 官方文件寫得很直白:預設是 blocking——driver 送不出去的時候,容器寫 stdout 的那一行會卡住;原文是「當 STDERR 或 STDOUT 卡住時,應用程式很可能以意想不到的方式失敗」。
翻成白話:CloudWatch 那邊限流、或網路抖一下,你的程式在 print 一行 log 的地方停住,然後 health check 失敗、task 被踢、滾動重啟——根因是 log 送不出去,但你看到的是「服務不健康」。
兩條路:
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/web-app",
"awslogs-region": "ap-northeast-1",
"mode": "non-blocking",
"max-buffer-size": "25m"
}
}non-blocking 用一個記憶體緩衝區接 log,滿了就丟——文件說「丟訊息通常比卡住應用程式好」。預設緩衝只有 1 MB,正式環境要調大。代價是尖峰時可能掉 log,這是明確的取捨:寧可掉幾行 log,不要服務停。
另一條路是 FireLens(Fluent Bit 當 sidecar):不會阻塞、還能把 log 路由到 S3 或 OpenSearch,省 CloudWatch 的錢。量大的正式環境走這條。
四、Log 預設永不過期,而且每筆要帶關聯 ID
官方文件原話:「預設情況下,log 資料無限期存放在 CloudWatch Logs」。建 log group 的時候不設保留期,它就一直存、一直算錢(成本面見 第 11 篇)。
aws logs create-log-group --log-group-name /ecs/web-app
aws logs put-retention-policy --log-group-name /ecs/web-app --retention-in-days 30關聯 ID:每筆 log 帶一個 request ID,跨服務用同一個。沒有它,Logs Insights 再快也只能看到「某個服務某一秒出錯」,串不出「這個使用者的這次請求經過哪五個服務、在第三個掛掉」。ALB 會給每個請求一個 X-Amzn-Trace-Id,直接沿用它就好——順便把 ALB 的 access log 開起來(預設是關的)。一個 request 從外面進來、經過一層層元件再回去,這一路上能留紀錄的地方裡,ALB 的 access log 最重要。
Logs Insights 留一個最常用的查法:
fields @timestamp, @message
| filter @message like /ERROR/
| stats count() as errors by bin(5m)
| sort @timestamp desc五、追蹤:ECS 沒有 DaemonSet,所以是 sidecar
X-Ray 要在每個 task 旁邊放一個 daemon 容器收 trace——K8s 用 DaemonSet 一個 node 放一個,Fargate 沒有 node,只能每個 task 都帶一個 sidecar。
{
"name": "xray-daemon",
"image": "amazon/aws-xray-daemon",
"cpu": 32, "memoryReservation": 256,
"portMappings": [{"containerPort": 2000, "protocol": "udp"}]
}要可攜的話 sidecar 換成 OpenTelemetry Collector,程式用 OTel SDK,後端接 X-Ray 或自架的 Tempo 都行——換平台不用改程式碼。
Container Insights 一行開,看 task 層級的 CPU / 記憶體 / 網路:
aws ecs update-cluster-settings --cluster prod-cluster \
--settings name=containerInsights,value=enabled這篇的驗證標準
| 主張 | ❌ 不算 | ✅ 算 |
|---|---|---|
| 告警會響 | alarm 是 OK 狀態 | 人為觸發一次,通知真的到指定通道——SNS 訂閱沒確認、Slack webhook 過期,設定是綠的但永遠不會響 |
| log 串得起來 | log 有進 CloudWatch | 拿一個關聯 ID,跨服務查得到完整鏈路 |
| log driver 不會卡 | 設了 non-blocking | 壓測時故意讓 CloudWatch 限流,服務不掉 |
實戰 Checklist
- 八條告警設了,CPU 不在裡面;每條有動作、有 runbook 連結
- 死信佇列 > 0 就告警
-
awslogs改non-blocking並調大緩衝,或改 FireLens - 每個 log group 有保留期;每筆 log 帶關聯 ID;ALB access log 開了
- 每季人為觸發一次告警,確認通知真的到
從 K8s 過來的話
Prometheus + Alertmanager + Loki + Tempo 那套,AWS 對應 CloudWatch Metrics + Alarms + Logs + X-Ray——但 Alertmanager 那一段是退步的,心理準備要有。DaemonSet 收 log 和 trace 的做法在 Fargate 上不存在,全部變 sidecar。
你在 K8s 上用慣的 OTel 可以原封不動搬過來:sidecar 換成 OTel Collector,後端指到 X-Ray。這是少數「自架經驗直接可用」的地方。細節在 K8s 監控。
相關文章
- 上一篇 DynamoDB,下一篇 SSM
- 八條告警對應的斷裂點:VPC 網路(NAT)、負載平衡(5xx、p99)
- 概念層:Metrics & Monitoring、Log Management
監控的目的不是收集資料,是在出事之前就看到苗頭——或至少在出事的時候知道去哪裡找答案。
