从阿里云 SLS 到 AWS CloudWatch:K8s 日志采集 + 钉钉告警完整迁移实战

  • 背景:有 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 用户需具备以下权限:
    • CloudWatchAgentServerPolicy
    • AmazonSNSFullAccess
    • CloudWatchLogsFullAccess
    • CloudWatchFullAccess
    • AWSLambda_FullAccess
    • IAMFullAccess(创建角色和策略)

2.3 前置准备

  • AWS CLI 已安装并配置
  • kubectl 已安装并可连接集群
  • Helm 已安装(可选)
  • 钉钉群机器人 Webhook 地址已准备

3. 第一步:AWS IAM 角色配置

3.1 创建 IAM 角色(在 AWS 控制台操作)

  1. 进入 IAM → 角色 → 创建角色
  2. 选择"AWS服务 " → "EC2"
  3. 附加策略:CloudWatchAgentServerPolicy
  4. 角色名称:CloudWatchAgent-Role
  5. 点击"创建角色"

3.2 绑定 IAM 角色到 EC2 实例

  1. 进入 EC2 控制台 → 实例
  2. 选中需要绑定的实例(你的 3 台 K8s 节点)
  3. 操作 → 安全 → 修改 IAM 角色
  4. 选择 CloudWatchAgent-Role
  5. 点击"更新 IAM 角色"

3.3 验证 IAM 角色绑定成功

bash 复制代码
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/CloudWatchAgent-Role | head -20

期望输出 :返回包含 AccessKeyIdSecretAccessKey 的 JSON

3.4 为 sms-sender 用户添加权限

bash 复制代码
# 附加 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_FullAccess

4. 第二步:部署 Fluent Bit 采集器

4.1 创建部署脚本

创建 deploy-fluentbit.sh

bash 复制代码
#!/bin/bash
# deploy-fluentbit.sh - 部署 Fluent Bit 日志采集器
# 在 K8s Master 节点执行

set -e

echo "=========================================="
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 -o yaml | kubectl apply -f -

# 2. 创建 ServiceAccount
echo ">>> 创建 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_NAME
EOF

# 3. 创建 ConfigMap
echo ">>> 创建 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. 部署 DaemonSet
echo ">>> 部署 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
EOF

echo "=========================================="
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 执行部署

bash 复制代码
chmod +x deploy-fluentbit.sh
./deploy-fluentbit.sh

4.3 验证部署

bash 复制代码
# 查看 Pod 状态(3 个节点都应该 Running)
kubectl get pods -n amazon-cloudwatch

# 查看 Fluent Bit 日志
kubectl logs -n amazon-cloudwatch -l k8s-app=fluent-bit --tail=30

期望输出

  • 3 个 Pod 状态为 Running
  • 日志显示 [ info] [output:cloudwatch_logs][ info] [input:tail]

5. 第三步:创建 SNS 主题

5.1 创建 SNS 主题

bash 复制代码
aws sns create-topic \
  --name "K8s-DingTalk-Alarm" \
  --region ap-east-1

输出示例

json 复制代码
{
    "TopicArn": "arn:aws:sns:ap-east-1:997836553899:K8s-DingTalk-Alarm"
}

5.2 记录 Topic ARN

保存输出中的 TopicArn,后续步骤需要用到。

6. 第四步:创建 Lambda 函数(钉钉通知)

6.1 在 AWS 控制台创建 Lambda

  1. 进入 Lambda → 创建函数
  2. 选择"从头开始创作"
  3. 函数名称:DingTalkNotifier
  4. 运行时:Python 3.9
  5. 点击"创建函数"

6.2 粘贴 Lambda 代码

python 复制代码
import json
import urllib3
import os

http = urllib3.PoolManager()

# 从环境变量读取 Webhook 地址
WEBHOOK_URL = os.environ.get('WEBHOOK_URL')

def lambda_handler(event, context):
    # 解析 SNS 消息
    try:
        sns_message = event['Records'][0]['Sns']['Message']
        alarm_data = json.loads(sns_message)
    except Exception as e:
        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"})
        }

    except Exception as e:
        print(f"发送失败: {e}")
        return {"statusCode": 500, "body": str(e)}

重要 :粘贴后必须点击 "Deploy" 按钮保存。

6.3 配置环境变量

  1. Lambda → 配置 → 环境变量
  2. 添加环境变量:
    • WEBHOOK_URL
    • :你的钉钉机器人 Webhook 地址

6.4 添加 SNS 触发器

  1. 点击"添加触发器"
  2. 选择 "SNS"
  3. 选择现有主题 K8s-DingTalk-Alarm
  4. 点击"添加"

7. 第五步:创建告警规则

7.1 创建告警脚本

创建 create-alarm.sh

bash 复制代码
#!/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-transformations metricName=ErrorCount,metricNamespace=K8sLogs,metricValue=1 \
  --region $REGION

if [ $? -eq 0 ]; then
  echo "✅ 指标筛选器创建成功"
else
  echo "❌ 指标筛选器创建失败"
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" \
  --period 300 \
  --evaluation-periods 1 \
  --threshold 5 \
  --comparison-operator "GreaterThanThreshold" \
  --alarm-actions "$SNS_TOPIC_ARN" \
  --region $REGION

if [ $? -eq 0 ]; then
  echo "✅ 告警规则创建成功"
else
  echo "❌ 告警规则创建失败"
fi

echo "=========================================="
echo "告警配置完成!"
echo "=========================================="

7.2 执行告警创建

bash 复制代码
chmod +x create-alarm.sh
./create-alarm.sh

7.3 验证告警创建

bash 复制代码
# 验证指标筛选器
aws logs describe-metric-filters \
  --log-group-name "/aws/containerinsights/k8s/application" \
  --filter-name-prefix "ErrorCount" \
  --region ap-east-1

# 验证告警
aws cloudwatch describe-alarms \
  --alarm-names "K8s-Error-DingTalk" \
  --region ap-east-1

8. 第六步:测试告警

8.1 生成 ERROR 日志

bash 复制代码
# 生成 6 条 ERROR 日志(超过阈值 5)
for i in {1..6}; do
  kubectl run test-error-$i --image=busybox --restart=Never --rm -it -- sh -c "echo 'ERROR: test alarm message #$i'" 2>/dev/null || true
  sleep 2
done

8.2 验证指标数据

bash 复制代码
aws cloudwatch get-metric-statistics \
  --namespace K8sLogs \
  --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)" \
  --period 300 \
  --statistics Sum \
  --region ap-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).
关键字: ERROR

9. Logs Insights 查询指南

9.1 查看所有日志流(了解有哪些服务)

sql 复制代码
SOURCE "/aws/containerinsights/k8s/application" 
| stats count() by @logStream
| sort count desc
| limit 50

9.2 查看特定服务日志

sql 复制代码
SOURCE "/aws/containerinsights/k8s/application" 
| filter @logStream like /gateway/
| fields @timestamp, @message
| sort @timestamp desc
| limit 100

9.3 查看 ERROR 日志

sql 复制代码
SOURCE "/aws/containerinsights/k8s/application" 
| filter @message like /ERROR/
| fields @timestamp, @message, @logStream
| sort @timestamp desc
| limit 100

9.4 查看特定命名空间日志

sql 复制代码
SOURCE "/aws/containerinsights/k8s/application" 
| filter @logStream like /k8s/
| fields @timestamp, @message, @logStream
| sort @timestamp desc
| limit 100

10. 常见问题与解决方案

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 添加 CloudWatchFullAccesscloudwatch: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 关键命令速查

bash 复制代码
# 查看 Fluent Bit Pod
kubectl get pods -n amazon-cloudwatch

# 查看 Fluent Bit 日志
kubectl logs -n amazon-cloudwatch -l k8s-app=fluent-bit --tail=30

# 重启 Fluent Bit
kubectl rollout restart daemonset fluent-bit -n amazon-cloudwatch

# 查看告警状态
aws cloudwatch describe-alarms --alarm-names "K8s-Error-DingTalk" --region ap-east-1

# 查看指标数据
aws cloudwatch get-metric-statistics --namespace K8sLogs --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)" --period 300 --statistics Sum --region ap-east-1

# 查看日志流
aws logs describe-log-streams --log-group-name "/aws/containerinsights/k8s/application" --region ap-east-1 --max-items 10

11.3 相关文档链接

12. 版本历史

版本 日期 作者 变更说明
v1.0 2026-07-01 - 初始版本,基于实际部署经验整理

文档结束

如果有任何问题或需要补充的内容,请随时提出!📝