- 背景:有 3 台自建的 K8s 服务器,跑着核心业务。之前一直用阿里云 SLS 采集日志,但最近业务调整,希望使用AWS。
- 日志采集看起来是件小事,但真做起来才发现:采集器用什么?日志怎么传?告警怎么配?钉钉怎么通知?
- 经过一系列的踩坑、试错、解决各种报错,最终跑通了一条完整的链路:K8s → Fluent Bit → CloudWatch → SNS→ Lambda →钉钉告警。
AWS CloudWatch 日志接入
K8s 集群日志采集 + 钉钉告警 完整部署指南
1. 文档概述
1.1 目的
将自建 K8s 集群(3台服务器,containerd 运行时,rocky linux 9.7)的容器日志接入 AWS CloudWatch,并配置钉钉告警通知。
1.2 架构图
┌─────────────────────────────────────────────────────────────────────────────┐ │ K8s 集群 (3台服务器) │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Fluent Bit (DaemonSet) │ │ │ │ - 采集 /var/log/containers/*.log │ │ │ │ - 解析 Kubernetes 元数据 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ Amazon CloudWatch │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 日志组: /aws/containerinsights/{cluster-name}/application │ │ │ │ - 日志流按 Pod 自动命名 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 指标筛选器: ErrorCount │ │ │ │ - 筛选条件: ERROR │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 告警规则: K8s-Error-DingTalk │ │ │ │ - 条件: 5分钟内 ERROR > 5次 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ SNS 主题: K8s-DingTalk-Alarm │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ Lambda: DingTalkNotifier │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 钉钉群机器人 │ └─────────────────────────────────────────────────────────────────────────────┘2. 前置条件
2.1 环境信息
| 项目 | 值 |
|---|---|
| AWS 账号 ID | 登录AWS获取 |
| AWS 区域 | 按需选择 |
| K8s 版本 | v1.34.6 |
| 节点数量 | 3(1 control-plane + 2 worker) |
| 操作系统 | Rocky Linux 9.7 |
| 容器运行时 | containerd 2.2.3 |
| 日志路径 | /var/log/pods///*.log |
| 采集器 | Fluent Bit (aws-for-fluent-bit) |
2.2 所需权限
- AWS IAM 用户需具备以下权限:
CloudWatchAgentServerPolicyAmazonSNSFullAccessCloudWatchLogsFullAccessCloudWatchFullAccessAWSLambda_FullAccessIAMFullAccess(创建角色和策略)
2.3 前置准备
- AWS CLI 已安装并配置
- kubectl 已安装并可连接集群
- Helm 已安装(可选)
- 钉钉群机器人 Webhook 地址已准备
3. 第一步:AWS IAM 角色配置
3.1 创建 IAM 角色(在 AWS 控制台操作)
- 进入 IAM → 角色 → 创建角色
- 选择"AWS服务" → “EC2”
- 附加策略:
CloudWatchAgentServerPolicy - 角色名称:
CloudWatchAgent-Role - 点击"创建角色"
3.2 绑定 IAM 角色到 EC2 实例
- 进入 EC2 控制台 → 实例
- 选中需要绑定的实例(你的 3 台 K8s 节点)
- 操作 → 安全 → 修改 IAM 角色
- 选择
CloudWatchAgent-Role - 点击"更新 IAM 角色"
3.3 验证 IAM 角色绑定成功
curl-shttp://169.254.169.254/latest/meta-data/iam/security-credentials/CloudWatchAgent-Role|head-20期望输出:返回包含AccessKeyId和SecretAccessKey的 JSON
3.4 为 sms-sender 用户添加权限
# 附加 SNS 权限aws iam attach-user-policy\--user-name sms-sender\--policy-arn arn:aws:iam::aws:policy/AmazonSNSFullAccess# 附加 CloudWatch 日志权限aws iam attach-user-policy\--user-name sms-sender\--policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess# 附加 CloudWatch 告警权限aws iam attach-user-policy\--user-name sms-sender\--policy-arn arn:aws:iam::aws:policy/CloudWatchFullAccess# 附加 Lambda 权限aws iam attach-user-policy\--user-name sms-sender\--policy-arn arn:aws:iam::aws:policy/AWSLambda_FullAccess4. 第二步:部署 Fluent Bit 采集器
4.1 创建部署脚本
创建deploy-fluentbit.sh:
#!/bin/bash# deploy-fluentbit.sh - 部署 Fluent Bit 日志采集器# 在 K8s Master 节点执行set-eecho"=========================================="echo"部署 Fluent Bit 日志采集器"echo"=========================================="# 设置变量AWS_ACCOUNT_ID="997836553899"REGION="ap-east-1"ROLE_NAME="CloudWatchAgent-Role"CLUSTER_NAME="k8s"# 1. 创建命名空间echo">>> 创建命名空间 amazon-cloudwatch"kubectl create namespace amazon-cloudwatch --dry-run=client-oyaml|kubectl apply-f-# 2. 创建 ServiceAccountecho">>> 创建 ServiceAccount"cat<<EOF|kubectl apply-f-apiVersion: v1 kind: ServiceAccount metadata: name: fluent-bit-sa namespace: amazon-cloudwatch annotations: eks.amazonaws.com/role-arn: arn:aws:iam::$AWS_ACCOUNT_ID:role/$ROLE_NAMEEOF# 3. 创建 ConfigMapecho">>> 创建 Fluent Bit 配置"cat<<EOF|kubectl apply-f-apiVersion: v1 kind: ConfigMap metadata: name: fluent-bit-config namespace: amazon-cloudwatch labels: k8s-app: fluent-bit data: fluent-bit.conf: | [SERVICE] Flush 5 Log_Level info Daemon off Parsers_File parsers.conf HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020 [INPUT] Name tail Path /var/log/containers/*.log Parser cri Tag kube.* Refresh_Interval 10 Mem_Buf_Limit 50MB Skip_Long_Lines On [FILTER] Name kubernetes Match kube.* Kube_URL https://kubernetes.default.svc:443 Kube_Tag_Prefix kube.var.log.containers. Merge_Log On Merge_Log_Key log_processed Keep_Log On K8S-Logging.Parser On K8S-Logging.Exclude Off Annotations Off Labels Off [OUTPUT] Name cloudwatch_logs Match kube.* region ap-east-1 log_group_name /aws/containerinsights/${CLUSTER_NAME}/application log_stream_prefix${CLUSTER_NAME}- auto_create_group true log_retention_days 30 parsers.conf: | [PARSER] Name cri Format regex Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$ Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L%z EOF# 4. 部署 DaemonSetecho">>> 部署 Fluent Bit DaemonSet"cat<<EOF|kubectl apply-f-apiVersion: apps/v1 kind: DaemonSet metadata: name: fluent-bit namespace: amazon-cloudwatch labels: k8s-app: fluent-bit spec: selector: matchLabels: k8s-app: fluent-bit template: metadata: labels: k8s-app: fluent-bit spec: serviceAccountName: fluent-bit-sa hostNetwork: true dnsPolicy: ClusterFirstWithHostNet containers: - name: fluent-bit image: public.ecr.aws/aws-observability/aws-for-fluent-bit:3 imagePullPolicy: Always env: - name: REGION value: "${REGION}" - name: CLUSTER_NAME value: "${CLUSTER_NAME}" resources: requests: memory: "128Mi" cpu: "50m" limits: memory: "256Mi" cpu: "100m" volumeMounts: - name: varlog mountPath: /var/log - name: dockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: fluent-bit-config mountPath: /fluent-bit/etc/ - name: runlog mountPath: /var/run volumes: - name: varlog hostPath: path: /var/log - name: dockercontainers hostPath: path: /var/lib/docker/containers - name: fluent-bit-config configMap: name: fluent-bit-config - name: runlog hostPath: path: /var/run tolerations: - operator: Exists EOFecho"=========================================="echo"部署完成!"echo"=========================================="echo""echo"查看状态:"echo"kubectl get pods -n amazon-cloudwatch"echo""echo"查看日志:"echo"kubectl logs -n amazon-cloudwatch -l k8s-app=fluent-bit --tail=30"4.2 执行部署
chmod+x deploy-fluentbit.sh ./deploy-fluentbit.sh4.3 验证部署
# 查看 Pod 状态(3 个节点都应该 Running)kubectl get pods-namazon-cloudwatch# 查看 Fluent Bit 日志kubectl logs-namazon-cloudwatch-lk8s-app=fluent-bit--tail=30期望输出:
- 3 个 Pod 状态为
Running - 日志显示
[ info] [output:cloudwatch_logs]和[ info] [input:tail]
5. 第三步:创建 SNS 主题
5.1 创建 SNS 主题
aws sns create-topic\--name"K8s-DingTalk-Alarm"\--regionap-east-1输出示例:
{"TopicArn":"arn:aws:sns:ap-east-1:997836553899:K8s-DingTalk-Alarm"}5.2 记录 Topic ARN
保存输出中的TopicArn,后续步骤需要用到。
6. 第四步:创建 Lambda 函数(钉钉通知)
6.1 在 AWS 控制台创建 Lambda
- 进入 Lambda → 创建函数
- 选择"从头开始创作"
- 函数名称:
DingTalkNotifier - 运行时:
Python 3.9 - 点击"创建函数"
6.2 粘贴 Lambda 代码
importjsonimporturllib3importos http=urllib3.PoolManager()# 从环境变量读取 Webhook 地址WEBHOOK_URL=os.environ.get('WEBHOOK_URL')deflambda_handler(event,context):# 解析 SNS 消息try:sns_message=event['Records'][0]['Sns']['Message']alarm_data=json.loads(sns_message)exceptExceptionase:print(f"解析消息失败:{e}")return{"statusCode":400,"body":"Invalid message"}# 提取告警信息alarm_name=alarm_data.get('AlarmName','未知告警')alarm_state=alarm_data.get('NewStateValue','未知')alarm_reason=alarm_data.get('NewStateReason','无详细信息')timestamp=alarm_data.get('StateChangeTime','未知时间')# 构建钉钉消息(包含 ERROR 关键字,满足关键词安全策略)dingtalk_msg={"msgtype":"markdown","markdown":{"title":"🚨 K8s 日志告警","text":f""" ### 🚨 K8s 日志告警 - **告警名称**:{alarm_name}- **状态**: **{alarm_state}** - **时间**:{timestamp}- **详情**:{alarm_reason}> 关键字: ERROR """},"at":{"isAtAll":False}}try:response=http.request('POST',WEBHOOK_URL,body=json.dumps(dingtalk_msg).encode('utf-8'),headers={'Content-Type':'application/json'})print(f"钉钉响应状态:{response.status}")print(f"钉钉响应内容:{response.data}")return{"statusCode":response.status,"body":json.dumps({"message":"Message sent to DingTalk"})}exceptExceptionase:print(f"发送失败:{e}")return{"statusCode":500,"body":str(e)}重要:粘贴后必须点击“Deploy”按钮保存。
6.3 配置环境变量
- Lambda → 配置 → 环境变量
- 添加环境变量:
- 键:
WEBHOOK_URL - 值:你的钉钉机器人 Webhook 地址
- 键:
6.4 添加 SNS 触发器
- 点击"添加触发器"
- 选择 “SNS”
- 选择现有主题
K8s-DingTalk-Alarm - 点击"添加"
7. 第五步:创建告警规则
7.1 创建告警脚本
创建create-alarm.sh:
#!/bin/bash# create-alarm.sh - 创建 ERROR 日志告警LOG_GROUP="/aws/containerinsights/k8s/application"REGION="ap-east-1"SNS_TOPIC_ARN="arn:aws:sns:ap-east-1:997836553899:K8s-DingTalk-Alarm"echo"=========================================="echo"创建 CloudWatch 日志告警"echo"=========================================="# 1. 创建指标筛选器echo">>> 创建 ERROR 指标筛选器..."aws logs put-metric-filter\--log-group-name"$LOG_GROUP"\--filter-name"ErrorCount"\--filter-pattern"ERROR"\--metric-transformationsmetricName=ErrorCount,metricNamespace=K8sLogs,metricValue=1\--region$REGIONif[$?-eq0];thenecho"✅ 指标筛选器创建成功"elseecho"❌ 指标筛选器创建失败"fi# 2. 创建告警echo">>> 创建告警规则..."aws cloudwatch put-metric-alarm\--alarm-name"K8s-Error-DingTalk"\--alarm-description"当 5 分钟内 ERROR 日志超过 5 条时触发钉钉告警"\--metric-name"ErrorCount"\--namespace"K8sLogs"\--statistic"Sum"\--period300\--evaluation-periods1\--threshold5\--comparison-operator"GreaterThanThreshold"\--alarm-actions"$SNS_TOPIC_ARN"\--region$REGIONif[$?-eq0];thenecho"✅ 告警规则创建成功"elseecho"❌ 告警规则创建失败"fiecho"=========================================="echo"告警配置完成!"echo"=========================================="7.2 执行告警创建
chmod+x create-alarm.sh ./create-alarm.sh7.3 验证告警创建
# 验证指标筛选器aws logs describe-metric-filters\--log-group-name"/aws/containerinsights/k8s/application"\--filter-name-prefix"ErrorCount"\--regionap-east-1# 验证告警aws cloudwatch describe-alarms\--alarm-names"K8s-Error-DingTalk"\--regionap-east-18. 第六步:测试告警
8.1 生成 ERROR 日志
# 生成 6 条 ERROR 日志(超过阈值 5)foriin{1..6};dokubectl run test-error-$i--image=busybox--restart=Never--rm-it--sh-c"echo 'ERROR: test alarm message #$i'"2>/dev/null||truesleep2done8.2 验证指标数据
aws cloudwatch get-metric-statistics\--namespaceK8sLogs\--metric-name ErrorCount\--start-time"$(date-u-d'10 minutes ago'+%Y-%m-%dT%H:%M:%SZ)"\--end-time"$(date-u+%Y-%m-%dT%H:%M:%SZ)"\--period300\--statisticsSum\--regionap-east-1期望输出:返回Sum值大于 0
8.3 验证钉钉收到告警
等待 3-5 分钟,钉钉群应收到告警消息:
🚨 K8s 日志告警 - 告警名称: K8s-Error-DingTalk - 状态: ALARM - 时间: 2026-07-01T03:12:10.404+0000 - 详情: Threshold Crossed: 1 datapoint [26.0 ...] was greater than the threshold (5.0). 关键字: ERROR9. Logs Insights 查询指南
9.1 查看所有日志流(了解有哪些服务)
SOURCE"/aws/containerinsights/k8s/application"|statscount()by@logStream|sort countdesc|limit509.2 查看特定服务日志
SOURCE"/aws/containerinsights/k8s/application"|filter@logStreamlike/gateway/|fields@timestamp,@message|sort@timestampdesc|limit1009.3 查看 ERROR 日志
SOURCE"/aws/containerinsights/k8s/application"|filter@messagelike/ERROR/|fields@timestamp,@message,@logStream|sort@timestampdesc|limit1009.4 查看特定命名空间日志
SOURCE"/aws/containerinsights/k8s/application"|filter@logStreamlike/k8s/|fields@timestamp,@message,@logStream|sort@timestampdesc|limit10010. 常见问题与解决方案
10.1 IAM 权限问题
| 错误信息 | 解决方案 |
|---|---|
User is not authorized to perform: SNS:CreateTopic | 添加AmazonSNSFullAccess策略到用户 |
User is not authorized to perform: logs:PutMetricFilter | 添加CloudWatchLogsFullAccess策略 |
User is not authorized to perform: cloudwatch:PutMetricAlarm | 添加CloudWatchFullAccess策略 |
User is not authorized to perform: cloudwatch:DescribeAlarms | 添加CloudWatchFullAccess或cloudwatch:DescribeAlarms权限 |
10.2 Fluent Bit 部署问题
| 问题 | 解决方案 |
|---|---|
ErrImagePull/ImagePullBackOff | 使用public.ecr.aws/aws-observability/aws-for-fluent-bit:3替代amazon/cloudwatch-agent:1.300042.0 |
could not allocate key value pair | Fluent Bit 5.x 配置语法更严格,使用简化配置(不带@INCLUDE的子配置文件) |
could not get meta for POD | 这是控制平面节点的警告,不影响业务 Pod,可以忽略 |
10.3 CloudWatch 查询问题
| 错误信息 | 解决方案 |
|---|---|
MalformedQueryException | 检查 Logs Insights 语法,使用filter @logStream like /xxx/而非filter @logStream = "xxx" |
A log group must be selected | 在 Logs Insights 中选择正确的日志组 |
kubernetes.container_name字段为空 | 使用@logStream替代,日志流名称已包含 Pod 和容器信息 |
10.4 钉钉告警问题
| 问题 | 解决方案 |
|---|---|
| 钉钉收不到消息 | 1. 检查 Lambda 是否被触发(查看调用次数) 2. 检查 Lambda 环境变量 WEBHOOK_URL是否正确3. 检查钉钉机器人关键词是否包含 ERROR |
| Lambda 触发但钉钉无响应 | 查看 Lambda CloudWatch 日志,检查urllib3错误 |
| 告警不触发 | 1. 检查指标筛选器是否创建成功 2. 等待 3-5 分钟让数据聚合 3. 查看指标数据 get-metric-statistics |
11. 附录
11.1 环境变量汇总
| 变量 | 值 |
|---|---|
| AWS_ACCOUNT_ID | 997836553899 |
| AWS_REGION | ap-east-1 |
| CLUSTER_NAME | k8s |
| LOG_GROUP | /aws/containerinsights/k8s/application |
| SNS_TOPIC_ARN | arn:aws:sns:ap-east-1:997836553899:K8s-DingTalk-Alarm |
| IAM_ROLE | CloudWatchAgent-Role |
11.2 关键命令速查
# 查看 Fluent Bit Podkubectl get pods-namazon-cloudwatch# 查看 Fluent Bit 日志kubectl logs-namazon-cloudwatch-lk8s-app=fluent-bit--tail=30# 重启 Fluent Bitkubectl rollout restart daemonset fluent-bit-namazon-cloudwatch# 查看告警状态aws cloudwatch describe-alarms --alarm-names"K8s-Error-DingTalk"--regionap-east-1# 查看指标数据aws cloudwatch get-metric-statistics--namespaceK8sLogs --metric-name ErrorCount --start-time"$(date-u-d'10 minutes ago'+%Y-%m-%dT%H:%M:%SZ)"--end-time"$(date-u+%Y-%m-%dT%H:%M:%SZ)"--period300--statisticsSum--regionap-east-1# 查看日志流aws logs describe-log-streams --log-group-name"/aws/containerinsights/k8s/application"--regionap-east-1 --max-items1011.3 相关文档链接
- CloudWatch Logs 文档
- Fluent Bit Kubernetes 过滤器
- CloudWatch Logs Insights 查询语法
- 钉钉自定义机器人文档
12. 版本历史
| 版本 | 日期 | 作者 | 变更说明 |
|---|---|---|---|
| v1.0 | 2026-07-01 | - | 初始版本,基于实际部署经验整理 |
文档结束
如果有任何问题或需要补充的内容,请随时提出!📝