亚马逊AWS官方博客

利用 CloudWatch AIOps 实现智能化根因分析与故障排查

主题概述

在当今复杂的云环境中,运维团队面临着前所未有的挑战。随着微服务架构、容器化应用和无服务器计算的普及,系统组件之间的依赖关系变得越来越复杂,故障排查的难度也随之增加。当生产环境出现问题时,运维人员通常需要在海量的日志、指标和事件中寻找故障的根本原因,这不仅耗时耗力,还可能导致业务中断时间延长,造成严重的经济损失。

传统的运维方法已经难以应对这种复杂性。运维人员需要同时监控多个系统、分析各种指标、查看不同的日志,并尝试将这些信息关联起来以找出问题的根源。这个过程不仅繁琐,而且容易受到人为因素的影响,导致分析结果不准确或不完整。

AWS CloudWatch AIOps 应运而生,它利用人工智能和机器学习技术,自动化根因分析过程,帮助运维团队快速定位和解决问题。本文将深入探讨 CloudWatch AIOps 的功能和优势,并通过实际案例展示其如何改变传统的运维方式,提高故障排查效率。

背景

CloudWatch AIOps 的产品定位

CloudWatch AIOps 是 Amazon CloudWatch 的一项高级功能,专为解决云环境中的复杂故障排查问题而设计。它将人工智能和机器学习技术与 AWS 的可观测性服务相结合,提供自动化的根因分析能力。CloudWatch AIOps 的核心目标是减少平均故障排除时间(MTTR),提高系统可靠性,并降低运维团队的工作负担。

CloudWatch AIOps 不仅仅是一个监控工具,它是一个智能分析平台,能够理解应用程序和基础设施之间的复杂关系,自动识别异常模式,并提供有针对性的解决方案建议。

主要功能

1. 智能根因分析(Root Cause Analysis, RCA):

自动分析告警和异常事件,识别潜在的根本原因
利用机器学习算法检测异常模式和关联性
提供可视化的问题影响路径,帮助理解故障传播

2. 多种入口点:

CloudWatch 告警:直接从触发的告警进入分析流程
Amazon Q:通过自然语言交互方式启动分析
CloudWatch 控制台:从监控面板直接进入分析

3. 广泛的服务集成:

支持多种 AWS 服务,包括 EC2、Lambda、RDS、DynamoDB、API Gateway 等
完整支持列表参见:[CloudWatch AIOps 支持的服务](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Investigations-Services.html)

4.上下文感知分析:

自动收集相关的日志、指标和事件
考虑资源之间的依赖关系和最近的配置变更
分析时间序列数据,识别异常趋势和模式

5. 可操作的建议:

提供具体的修复建议和最佳实践
生成详细的调查报告,便于团队协作和知识共享
支持自定义修复工作流程

方案:可观测性与 CloudOps 的协同作用

可观测性的三大支柱

有效的故障排查需要建立在强大的可观测性基础上。可观测性通常包括三个关键支柱:

1.指标(Metrics):

数值化的系统性能和健康状态指标
CloudWatch 提供了丰富的内置指标和自定义指标功能
支持高精度监控和异常检测

2. 日志(Logs):

详细记录系统和应用程序的行为和事件
CloudWatch Logs 集中存储和分析各种日志数据
支持实时处理和结构化查询

3. 追踪(Traces):

跟踪请求在分布式系统中的完整路径
AWS X-Ray 提供端到端的请求追踪能力
可视化服务依赖关系和性能瓶颈

CloudWatch AIOps 将这三个支柱的数据整合在一起,提供全面的系统视图,使故障排查变得更加高效和准确。

CloudOps 关联产品的协同作用

CloudWatch AIOps 不是孤立运行的,它与多个 AWS CloudOps 产品协同工作,形成完整的运维解决方案:

1.AWS CloudTrail:

记录 AWS 账户中的所有 API 调用和资源变更
提供审计跟踪,帮助识别配置变更导致的问题
CloudWatch AIOps 自动分析 CloudTrail 日志,检测可能导致故障的配置变更

2.AWS Config:

持续监控和记录 AWS 资源配置
评估资源配置是否符合最佳实践
CloudWatch AIOps 利用 Config 数据了解资源的历史状态和变更

3.Amazon EventBridge:

处理来自各种 AWS 服务和应用程序的事件
实现事件驱动的自动化响应
CloudWatch AIOps 分析 EventBridge 事件,识别系统变更和异常行为

4.AWS Systems Manager:

提供集中化的系统管理功能
自动执行运维任务和修复操作
CloudWatch AIOps 可以触发 Systems Manager 自动化工作流进行修复

5. Amazon DevOps Guru:

提供基于机器学习的运维洞察
预测潜在的运维问题
与 CloudWatch AIOps 协同工作,提供更全面的分析

这些产品的协同作用创建了一个闭环的运维流程:监控 → 检测 → 分析 → 修复 → 验证,大大提高了运维效率和系统可靠性。

理论基础:可观测性与 CloudOps 的协同效应

从监控到可观测性的演进

传统的监控方法主要关注预定义的指标和阈值,这种方法在简单系统中可能有效,但在复杂的云环境中显得力不从心。可观测性则采取了更全面的方法,不仅关注”系统是否正常运行”,还关注”为什么系统会出现这种行为”。

可观测性的核心理念是:系统的内部状态可以通过其外部输出来推断。通过收集和分析指标、日志和追踪数据,运维团队可以构建系统行为的完整视图,即使面对未预见的问题也能快速定位根因。

CloudWatch AIOps 的工作原理

CloudWatch AIOps 基于以下理论基础运作:

1. 因果推理:

建立系统组件之间的因果关系模型
分析事件序列,识别潜在的触发因素
区分症状和根本原因

2. 异常检测:

学习系统的正常行为模式
识别偏离正常模式的异常情况
考虑时间相关性和季节性变化

3.关联分析:

发现不同指标和事件之间的相关性
构建资源依赖关系图
追踪问题的传播路径

4.知识图谱:

整合 AWS 服务的专业知识和最佳实践
应用领域特定的故障排查逻辑
持续学习和改进分析能力

根因分析的自动化流程

当系统触发告警或用户启动调查时,CloudWatch AIOps 执行以下步骤:

1. 数据收集:

自动收集相关的指标、日志和追踪数据
获取资源配置和最近的变更记录
确定受影响资源的范围

2.上下文构建:

分析资源之间的依赖关系
考虑时间相关性和事件顺序
构建问题的完整上下文

3. 模式识别:

应用机器学习算法识别异常模式
比较当前情况与历史案例
检测潜在的根本原因

4. 影响分析:

评估问题对系统各部分的影响
确定关键路径和瓶颈
预测问题的潜在发展

5. 解决方案生成:

提供针对根本原因的具体修复建议
推荐预防类似问题的最佳实践
生成详细的调查报告

这种自动化的根因分析流程大大减少了手动排查的时间和精力,使运维团队能够更快地解决问题,提高系统可靠性。

操作演示:使用 CloudWatch AIOps 排查 S3 权限问题

为了展示 CloudWatch AIOps 的实际应用,我们将使用一个简单的演示应用程序,该应用程序由 API Gateway、Lambda 函数和 S3 存储桶组成。我们将模拟一个常见的运维问题:S3 存储桶策略变更导致的应用程序故障,并使用 CloudWatch AIOps 进行根因分析。

演示应用架构

我们的演示应用是一个简单的图片服务,其架构如下:

1. **API Gateway**:提供 REST API 接口,接收客户端请求
2. **Lambda 函数**:处理请求,从 S3 存储桶中随机获取图片
3. **S3 存储桶**:存储图片文件
4. **Bedrock Lambda**:使用 Amazon Bedrock 生成图片并存储到 S3 存储桶

应用正常工作时,用户可以通过 API 获取随机图片。我们将通过修改 S3 存储桶策略,模拟一个权限问题,然后使用 CloudWatch AIOps 进行故障排查。

### 步骤 1:部署演示应用

首先,我们使用 CloudFormation 模板部署演示应用:

您需要将下面的 Cloudformation Template 存储到本地。

```cloudformation
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AIOps Integration Test - Image Service'

Resources:
  # S3 Bucket
  S3BucketAIOpsIntegration:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Delete
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Join 
        - ''
        - - 'aiops-image-bucket-'
          - !Select [0, !Split ["-", !Select [2, !Split ["/", !Ref "AWS::StackId"]]]]

  CleanupLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
        - PolicyName: S3CleanupPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:ListBucket'
                  - 's3:DeleteObject'
                  - 's3:DeleteObjectVersion'
                Resource:
                  - !Sub 'arn:aws:s3:::${S3BucketAIOpsIntegration}'
                  - !Sub 'arn:aws:s3:::${S3BucketAIOpsIntegration}/*'

  # Cleanup Lambda used to empty contents of S3 Bucket
  CleanupLambda:
    Type: 'AWS::Lambda::Function'
    DependsOn:
      - CleanupLambdaRole
      - S3BucketAIOpsIntegration
    Properties:
      FunctionName: !Sub '${AWS::StackName}-cleanup-lambda'
      Handler: 'index.lambda_handler'
      Role: !GetAtt CleanupLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse

          def lambda_handler(event, context):
              try:
                  if event['RequestType'] == 'Delete':
                      s3 = boto3.resource('s3')
                      bucket = s3.Bucket(event['ResourceProperties']['BucketName'])
                      bucket.objects.all().delete()
                      print(f"Cleaned up bucket {event['ResourceProperties']['BucketName']}")
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              except Exception as e:
                  print(f"Error: {str(e)}")
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})
      Runtime: python3.13
      Timeout: 300
      MemorySize: 128

  S3BucketCleanup:
    Type: 'Custom::S3BucketCleanup'
    DependsOn: 
      - CleanupLambda
      - BedrockLambda
      - TriggerCustomResource
    Properties:
      ServiceToken: !GetAtt CleanupLambda.Arn
      BucketName: !Ref S3BucketAIOpsIntegration

  # SSM Parameter for Bucket Name
  BucketNameParameter:
    Type: 'AWS::SSM::Parameter'
    Properties:
      Name: !Sub '/${AWS::StackName}/bucket-name'
      Type: 'String'
      Value: !Ref S3BucketAIOpsIntegration
      Description: 'S3 Bucket name for AIOps Integration Test'

  # Lambda Role for Bucket Policy Update
  BucketPolicyLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${AWS::StackName}-bucket-policy-lambda-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
        - PolicyName: !Sub '${AWS::StackName}-bucket-policy-lambda-policy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 
                  - s3:PutBucketPolicy
                Resource: !GetAtt S3BucketAIOpsIntegration.Arn

  # Lambda Function for Bucket Policy Update
  BucketPolicyLambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Sub '${AWS::StackName}-bucket-policy-lambda'
      Handler: 'index.lambda_handler'
      Role: !GetAtt BucketPolicyLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import json
          import os

          def lambda_handler(event, context):
              try:
                  s3 = boto3.client('s3')
                  bucket_name = os.environ['BUCKET_NAME']
                  role_arn = os.environ['LAMBDA_ROLE_ARN']
                  
                  bucket_policy = {
                      "Version": "2012-10-17",
                      "Id": "Policy1736953436011",
                      "Statement": [{
                          "Sid": "DenyBucketAccess",
                          "Effect": "Deny",
                          "Principal": {
                              "AWS": role_arn
                          },
                          "Action": "s3:ListBucket",
                          "Resource": f"arn:aws:s3:::{bucket_name}"
                      }, {
                          "Sid": "DenyObjectAccess",
                          "Effect": "Deny",
                          "Principal": {
                              "AWS": role_arn
                          },
                          "Action": "s3:GetObject",
                          "Resource": f"arn:aws:s3:::{bucket_name}/*"
                      }]
                  }
                  
                  s3.put_bucket_policy(
                      Bucket=bucket_name,
                      Policy=json.dumps(bucket_policy)
                  )
                  
                  return {
                      'statusCode': 200,
                      'body': 'Bucket policy updated successfully'
                  }
                  
              except Exception as e:
                  print(f"Error: {str(e)}")
                  return {
                      'statusCode': 500,
                      'body': f"Error updating bucket policy: {str(e)}"
                  }
      Runtime: python3.13
      Timeout: 30
      MemorySize: 128
      Environment:
        Variables:
          BUCKET_NAME: !Ref S3BucketAIOpsIntegration
          LAMBDA_ROLE_ARN: !GetAtt LambdaRoleAIOpsIntegration.Arn

  LambdaTriggerBucketPolicyAlarm:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: !Sub '${AWS::StackName}-lambda-trigger-alarm'
      AlarmDescription: 'Alarm when Lambda is triggered 3 or more times'
      MetricName: 'Invocations'
      Namespace: 'AWS/Lambda'
      Dimensions:
        - Name: FunctionName
          Value: !Ref LambdaAIOpsIntegration
      Statistic: 'Sum'
      Period: 60
      EvaluationPeriods: 1
      Threshold: 3
      ComparisonOperator: 'GreaterThanOrEqualToThreshold'
      TreatMissingData: 'notBreaching'
      AlarmActions:
        - !GetAtt BucketPolicyLambda.Arn

  # Lambda Permission for CloudWatch Alarm
  BucketPolicyLambdaPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName: !Ref BucketPolicyLambda
      Action: 'lambda:InvokeFunction'
      Principal: 'lambda.alarms.cloudwatch.amazonaws.com'
      SourceAccount: !Ref 'AWS::AccountId'
      SourceArn: !Sub 'arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:${AWS::StackName}-lambda-trigger-alarm'

  BedrockLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${AWS::StackName}-bedrock-lambda-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/CloudWatchLambdaApplicationSignalsExecutionRolePolicy'
      Policies:
        - PolicyName: !Sub '${AWS::StackName}-bedrock-lambda-policy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: logs:CreateLogGroup
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*'
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-bedrock-lambda:*'
              - Effect: Allow
                Action:
                  - s3:PutObject
                Resource: !Sub '${S3BucketAIOpsIntegration.Arn}/*'
              - Effect: Allow
                Action:
                  - bedrock:InvokeModel
                Resource: '*'
              - Effect: Allow
                Action:
                  - xray:PutTraceSegments
                  - xray:PutTelemetryRecords
                Resource: '*'
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${AWS::StackName}/bucket-name'

  # Bedrock Lambda Function
  BedrockLambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Sub '${AWS::StackName}-bedrock-lambda'
      Handler: 'index.lambda_handler'
      Role: !GetAtt BedrockLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import base64
          import json
          import os

          def get_bucket_name():
              ssm = boto3.client('ssm')
              parameter_name = os.environ['BUCKET_NAME_PARAMETER']
              response = ssm.get_parameter(Name=parameter_name)
              return response['Parameter']['Value']

          def lambda_handler(event, context):
              try:
                  # Initialize S3 and Bedrock clients
                  s3 = boto3.client('s3')
                  bedrock = boto3.client('bedrock-runtime')
                  
                  # Get bucket name from Parameter Store
                  bucket_name = get_bucket_name()
                  print(f"Using bucket: {bucket_name}")
                  
                  print("Generating images using Bedrock...")
                  
                  # Prepare request body for Bedrock
                  request_body = {
                      "taskType": "TEXT_IMAGE",
                      "textToImageParams": {
                          "text": "Provide me images of different breeds of Dogs with Sunglasses",
                          "negativeText": "Dont include cats"
                      },
                      "imageGenerationConfig": {
                          "numberOfImages": 5,
                          "height": 1024,
                          "width": 1024,
                          "cfgScale": 8.0,
                          "seed": 0
                      }
                  }
                  
                  # Call Bedrock to generate image
                  response = bedrock.invoke_model(
                      modelId="amazon.titan-image-generator-v1",
                      contentType="application/json",
                      accept="application/json",
                      body=json.dumps(request_body)
                  )
                  
                  # Parse response and save image to S3
                  response_body = json.loads(response['body'].read())
                  for idx, image in enumerate(response_body['images']):
                      # Decode base64 image
                      image_data = base64.b64decode(image)
                      
                      # Upload to S3
                      file_name = f"generated_image_{idx}.jpg"
                      s3.put_object(
                          Bucket=bucket_name,
                          Key=file_name,
                          Body=image_data,
                          ContentType='image/jpeg'
                      )
                  
                  print("Images generated and stored in S3")
                  return {
                      'statusCode': 200,
                      'body': json.dumps({'message': 'Images generated successfully'})
                  }
                  
              except Exception as e:
                  print(f"Error: {str(e)}")
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'message': 'Internal Server Error - HTTP 500'})
                  }
      Runtime: python3.13
      Timeout: 300
      MemorySize: 1024
      Environment:
        Variables:
          BUCKET_NAME_PARAMETER: !Sub '/${AWS::StackName}/bucket-name'
      TracingConfig:
        Mode: Active

  # Custom Resource Lambda Role
  TriggerLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
        - PolicyName: InvokeBedrockLambda
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 'lambda:InvokeFunction'
                Resource: !GetAtt BedrockLambda.Arn

  # Custom Resource Lambda
  TriggerLambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Sub '${AWS::StackName}-trigger-lambda'
      Handler: 'index.lambda_handler'
      Role: !GetAtt TriggerLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse

          def lambda_handler(event, context):
              try:
                  if event['RequestType'] == 'Create':
                      lambda_client = boto3.client('lambda')
                      response = lambda_client.invoke(
                          FunctionName=f"{event['ResourceProperties']['StackName']}-bedrock-lambda",
                          InvocationType='Event'
                      )
                      print("Successfully triggered Bedrock Lambda")
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                  else:
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              except Exception as e:
                  print(f"Error: {str(e)}")
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})
      Runtime: python3.13
      Timeout: 30
      MemorySize: 128

  TriggerCustomResource:
    Type: 'Custom::TriggerBedrock'
    DependsOn:
      - S3BucketAIOpsIntegration
      - BedrockLambda
    Properties:
      ServiceToken: !GetAtt TriggerLambda.Arn
      StackName: !Ref 'AWS::StackName'
  # Updated Lambda Role with SSM permissions
  LambdaRoleAIOpsIntegration:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${AWS::StackName}-lambda-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/CloudWatchLambdaApplicationSignalsExecutionRolePolicy'
      Policies:
        - PolicyName: !Sub '${AWS::StackName}-lambda-policy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: logs:CreateLogGroup
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*'
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: '*'
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:ListBucket
                Resource:
                  - !GetAtt S3BucketAIOpsIntegration.Arn
                  - !Sub '${S3BucketAIOpsIntegration.Arn}/*'
              - Effect: Allow
                Action:
                  - xray:PutTraceSegments
                  - xray:PutTelemetryRecords
                Resource: '*'
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${AWS::StackName}/bucket-name'

  # Updated Lambda Function with SSM parameter for S3 Bucket
  LambdaAIOpsIntegration:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Sub '${AWS::StackName}-aiops-lambda'
      Handler: 'index.lambda_handler'
      Role: !GetAtt LambdaRoleAIOpsIntegration.Arn
      Code:
        ZipFile: |
          import boto3
          import random
          import base64
          import os
          import json

          def get_bucket_name():
              ssm = boto3.client('ssm')
              parameter_name = os.environ['BUCKET_NAME_PARAMETER']
              response = ssm.get_parameter(Name=parameter_name)
              return response['Parameter']['Value']

          def lambda_handler(event, context):
              try:
                  # Initialize S3 client
                  s3 = boto3.client('s3')
                  
                  # Get bucket name from Parameter Store
                  bucket_name = get_bucket_name()
                  print(f"Using bucket: {bucket_name}")
                  
                  # List all objects
                  response = s3.list_objects_v2(Bucket=bucket_name)
                  
                  if 'Contents' not in response:
                      return {
                          'statusCode': 404,
                          'headers': {
                              'Content-Type': 'application/json',
                              'Access-Control-Allow-Origin': '*'
                          },
                          'body': '{"message": "No images found"}'
                      }
                  
                  # Filter for both .jpg and .jpeg files
                  image_files = [obj for obj in response['Contents'] 
                              if obj['Key'].lower().endswith(('.jpg', '.jpeg'))]
                  
                  if not image_files:
                      return {
                          'statusCode': 404,
                          'headers': {
                              'Content-Type': 'application/json',
                              'Access-Control-Allow-Origin': '*'
                          },
                          'body': '{"message": "No JPG/JPEG images found"}'
                      }
                  
                  # Select random image
                  random_image = random.choice(image_files)
                  print(f"Selected image: {random_image['Key']}")
                  
                  # Get the image
                  image = s3.get_object(
                      Bucket=bucket_name,
                      Key=random_image['Key']
                  )
                  
                  # Get image data
                  image_data = image['Body'].read()
                  
                  # Return binary response for API Gateway
                  return {
                      'statusCode': 200,
                      'headers': {
                          'Content-Type': 'image/jpeg'
                      },
                      'body': base64.b64encode(image_data).decode('utf-8'),
                      'isBase64Encoded': True
                  }
                  
              except Exception as e:
                  print(f"Error: {str(e)}")
                  return {
                      'statusCode': 500,
                      'headers': {
                          'Content-Type': 'application/json',
                          'Access-Control-Allow-Origin': '*'
                      },
                      'body': json.dumps({
                          "message": "Internal Server Error"
                      })
                  }
      Runtime: python3.13
      Timeout: 60
      MemorySize: 128
      Environment:
        Variables:
          BUCKET_NAME_PARAMETER: !Sub '/${AWS::StackName}/bucket-name'
      TracingConfig:
        Mode: Active

  # API Gateway Role
  ApiGatewayRoleAIOpsIntegration:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${AWS::StackName}-apigw-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: apigateway.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs'

  # API Gateway Account Settings
  ApiGatewayAccountConfig:
    Type: 'AWS::ApiGateway::Account'
    Properties:
      CloudWatchRoleArn: !GetAtt ApiGatewayRoleAIOpsIntegration.Arn

  # REST API Gateway
  ApiGatewayAIOpsIntegration:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: !Sub '${AWS::StackName}-api'
      Description: 'API for random image service'
      EndpointConfiguration:
        Types:
          - REGIONAL
      BinaryMediaTypes:
        - 'image/jpeg'
        - '*/*'

  # API Resource
  ApiResourceRandom:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref ApiGatewayAIOpsIntegration
      ParentId: !GetAtt ApiGatewayAIOpsIntegration.RootResourceId
      PathPart: 'random'

  # API Method
  ApiMethodGet:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref ApiGatewayAIOpsIntegration
      ResourceId: !Ref ApiResourceRandom
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaAIOpsIntegration.Arn}/invocations'

  # Enable CORS
  ApiMethodOptions:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref ApiGatewayAIOpsIntegration
      ResourceId: !Ref ApiResourceRandom
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      Integration:
        Type: MOCK
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates:
              application/json: ''
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true

  # API Deployment
  ApiDeployment:
    Type: 'AWS::ApiGateway::Deployment'
    DependsOn:
      - ApiMethodGet
      - ApiMethodOptions
    Properties:
      RestApiId: !Ref ApiGatewayAIOpsIntegration

  # API Stage
  ApiStage:
    Type: 'AWS::ApiGateway::Stage'
    DependsOn: ApiGatewayAccountConfig
    Properties:
      DeploymentId: !Ref ApiDeployment
      RestApiId: !Ref ApiGatewayAIOpsIntegration
      StageName: 'prod'
      Description: 'Production Stage'
      TracingEnabled: true
      MethodSettings:
        - ResourcePath: '/*'
          HttpMethod: '*'
          MetricsEnabled: true
          DataTraceEnabled: true
          LoggingLevel: INFO

  # Lambda Permission
  LambdaPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !Ref LambdaAIOpsIntegration
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayAIOpsIntegration}/*'
  
  # API Gateway 5XX Error Alarm
  ApiGateway5XXAlarm:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: !Sub '${AWS::StackName}-api-5xx-error-alarm'
      AlarmDescription: 'Alarm when API Gateway returns 5XX errors'
      MetricName: '5XXError'
      Namespace: 'AWS/ApiGateway'
      Dimensions:
        - Name: ApiName
          Value: !Sub '${AWS::StackName}-api'
        - Name: Stage
          Value: 'prod'
      Statistic: 'Sum'
      Period: 300
      EvaluationPeriods: 1
      Threshold: 1
      ComparisonOperator: 'GreaterThanOrEqualToThreshold'
      TreatMissingData: 'notBreaching'

Outputs:
  S3BucketName:
    Description: 'Name of the created S3 bucket'
    Value: !Ref S3BucketAIOpsIntegration

  LambdaFunctionName:
    Description: 'Name of the created Lambda function'
    Value: !Ref LambdaAIOpsIntegration

  BedrockLambdaName:
    Description: 'Name of the Bedrock Lambda function'
    Value: !Ref BedrockLambda

  TriggerLambdaName:
    Description: 'Name of the Trigger Lambda function'
    Value: !Ref TriggerLambda

  BucketPolicyLambdaName:
    Description: 'Name of the Bucket Policy Lambda function'
    Value: !Ref BucketPolicyLambda

  CloudWatchAlarmName:
    Description: 'Name of the CloudWatch Alarm'
    Value: !Ref LambdaTriggerBucketPolicyAlarm

  ApiGateway5XXAlarmName:
    Description: 'Name of the API Gateway 5XX Error Alarm'
    Value: !Ref ApiGateway5XXAlarm

  ApiEndpoint:
    Description: 'API Gateway Endpoint URL'
    Value: !Sub 'https://${ApiGatewayAIOpsIntegration}.execute-api.${AWS::Region}.amazonaws.com/prod/random'

```

创建堆栈的时候请注意将下面命令行的 参数 `--template-body` 中的文件路径和文件名匹配到您当前的文件存储位置和文件名。

```bash
# 创建 CloudFormation 堆栈
aws cloudformation create-stack \
  --stack-name aiops-demo \
  --template-body file:///【您的文件路径】/【您的文件名】.yaml \
  --capabilities CAPABILITY_NAMED_IAM
```

部署完成后,我们可以测试 API 是否正常工作:

```bash
# 获取 API 端点
API_ENDPOINT=$(aws cloudformation describe-stacks \
  --stack-name aiops-demo \
  --query "Stacks[0].Outputs[?OutputKey=='ApiEndpoint'].OutputValue" \
  --output text)

# 测试 API
curl -v $API_ENDPOINT
```

此时,API 应该能够正常返回一张随机图片。

我們可以透過 x-ray 查看部署的 service map
![trace_map](image/trace_map.png)

步骤 2:触发故障场景

为了模拟故障场景,我们将触发 CloudWatch 告警,该告警会自动调用 Lambda 函数修改 S3 存储桶策略,阻止 Lambda 函数访问 S3 存储桶:

```bash
# 获取 Lambda 函数名称
LAMBDA_FUNCTION=$(aws cloudformation describe-stacks \
  --stack-name aiops-demo \
  --query "Stacks[0].Outputs[?OutputKey=='LambdaFunctionName'].OutputValue" \
  --output text)

# 多次调用 Lambda 函数触发告警
for i in {1..5}; do
  aws lambda invoke \
    --function-name $LAMBDA_FUNCTION \
    --payload '{}' \
    /dev/null
  echo "Invocation $i completed"
  sleep 2
done
```

这将触发 CloudWatch 告警,进而调用修改 S3 存储桶策略的 Lambda 函数。策略修改后,主 Lambda 函数将无法访问 S3 存储桶,导致 API 返回错误。

步骤 3:验证故障

几分钟后,我们再次测试 API:

```bash
# 测试 API
curl -v $API_ENDPOINT
```

此时,API 应该返回 500 错误,表明应用程序出现了故障。
![500_error](image/500_error.png)

步骤 4:使用 CloudWatch AIOps 进行根因分析

现在,我们将使用 CloudWatch AIOps 来分析和解决这个问题。

方法 1:通过 CloudWatch 告警进行分析

1. 登录 AWS 管理控制台,导航到 CloudWatch 服务
2. 在左侧导航栏中,选择”告警”
3. 找到触发的 API Gateway 5XX 错误告警
![step1.1](image/step1.1.png)

4. 点击”调查”按钮启动 CloudWatch AIOps 分析
![step1.4](image/step1.4.png)

输入一个 investigation title, 並且点击”开始调查”
![investigation-details](image/investigation-details.png)

CloudWatch AIOps 提供一个引导式的问题排查流程,在右侧会显示排查结果,我们可以通过 Accept 或 Decline 按钮,决定是否采纳该结果作为问题的根本原因分析依据。

CloudWatch AIOps 将自动收集相关数据并开始分析。分析完成后,它会显示一个调查报告,包括:

问题概述:API Gateway 返回 5XX 错误
修复建议:恢复 S3 存储桶策略的正确配置
根本原因:S3 存储桶策略变更阻止了 Lambda 函数访问 S3 存储桶

影响范围:分析受影响的资源和服务

时间线:问题发生的时间和相关事件

相关证据:CloudTrail 日志显示的 S3 策略变更事件

查看S3 存储桶的 event record

方法 2:通过 Amazon Q 进行分析

1. 在 AWS 管理控制台中,打开 Amazon Q
2. 输入自然语言查询,例如:”我的 API Gateway 返回 500 错误,请帮我分析原因”
3. Amazon Q 将启动 CloudWatch AIOps 分析,并提供类似的调查报告

步骤 5:解决问题

根据 CloudWatch AIOps 的建议,我们可以修复 S3 存储桶策略:

```bash
# 获取 S3 存储桶名称
S3_BUCKET=$(aws cloudformation describe-stacks \
  --stack-name aiops-demo \
  --query "Stacks[0].Outputs[?OutputKey=='S3BucketName'].OutputValue" \
  --output text)

# 删除错误的存储桶策略
aws s3api delete-bucket-policy --bucket $S3_BUCKET

修复后,我们再次测试 API:

```bash
# 测试 API
curl -v $API_ENDPOINT
```

此时,API 应该恢复正常,能够返回随机图片。

分析结果解读

CloudWatch AIOps 的分析报告提供了以下关键信息:

1. 问题识别:

API Gateway 5XX 错误率突然增加
Lambda 函数出现 AccessDenied 错误

2. 根因确定:

CloudTrail 日志显示 S3 存储桶策略在问题发生前被修改
新策略明确拒绝了 Lambda 执行角色访问 S3 存储桶

3. 影响路径:

S3 策略变更 → Lambda 无法访问 S3 → API Gateway 返回 500 错误

4. 时间相关性:

问题开始时间与 S3 策略变更时间高度一致
没有其他相关的配置变更或系统事件

5. 修复建议:

恢复 S3 存储桶策略
实施更严格的权限控制和变更管理
添加更详细的监控和告警

这个演示展示了 CloudWatch AIOps 如何快速识别配置变更导致的问题,并提供明确的修复建议,大大减少了故障排查时间。

未来展望:全程自动化的智能运维

随着云计算和人工智能技术的不断发展,我们可以展望一个更加自动化和智能化的运维未来。CloudWatch AIOps 只是这一发展方向的开始,未来的智能运维系统将实现更高级别的自动化和预测能力。

资源的自动创建与管理

未来的智能运维系统将能够:

1. 自动化基础设施部署:

基于应用需求自动设计和部署最佳架构
持续优化资源配置,平衡性能和成本
自动扩展和收缩资源,适应负载变化

2. 智能配置管理:

自动生成符合最佳实践的配置
预测配置变更的潜在影响
防止有风险的配置变更

3. 自我修复能力:

检测到问题后自动启动修复流程
学习和改进修复策略
在问题影响用户之前解决潜在问题

告警的自动生成与优化

传统的告警配置通常是静态的,需要人工设置和维护。未来的智能运维系统将实现:

1. 自适应告警阈值:

基于历史数据和季节性模式自动调整阈值
考虑业务上下文和重要性
减少误报和漏报

2. 异常检测告警:

自动识别异常模式,无需预定义阈值
学习系统的正常行为
检测复杂的多维异常

3. 预测性告警:

预测潜在问题,在问题发生前发出警告
估计问题的严重性和影响范围
提供预防措施建议

自动化问题解决流程

未来的智能运维系统将不仅能够发现问题,还能自动解决问题:

1. 自动化修复工作流:

根据根因分析结果自动执行修复操作
验证修复效果,必要时回滚
记录和学习修复经验

2. 智能事件管理:

自动分类和优先级排序
智能路由到合适的团队或系统
协调复杂问题的解决流程

3. 持续学习和改进:

从每次事件中学习
改进分析和修复策略
预防类似问题再次发生

运维角色的转变

随着智能运维系统的发展,运维人员的角色将发生重大转变:

1. 从执行者到设计者:

设计自动化策略和工作流
定义运维最佳实践和策略
创建和优化自动化模板

2. 从被动响应到主动预防:

分析趋势和模式,预测潜在问题
实施预防措施,提高系统弹性
持续优化系统架构和配置

3. 从技术专家到业务伙伴:

将技术洞察转化为业务价值
参与战略决策和规划
推动技术创新和业务转型

总结

CloudWatch AIOps 代表了运维领域的一次重大变革,它将人工智能和机器学习技术应用于复杂系统的故障排查,大大提高了运维效率和系统可靠性。通过自动化根因分析,CloudWatch AIOps 帮助运维团队快速定位和解决问题,减少平均故障排除时间,提高用户满意度。

本文探讨了 CloudWatch AIOps 的核心功能和工作原理,并通过一个实际案例展示了它如何帮助排查 S3 权限问题。我们还展望了智能运维的未来发展方向,包括资源的自动创建与管理、告警的自动生成与优化,以及自动化问题解决流程。

随着云计算和人工智能技术的不断发展,智能运维将成为企业数字化转型的关键推动力。CloudWatch AIOps 只是这一发展方向的开始,未来的智能运维系统将实现更高级别的自动化和预测能力,彻底改变传统的运维方式。

对于企业来说,现在是时候开始探索和采用这些智能运维技术,为未来的数字化转型做好准备。通过结合 CloudWatch AIOps 和其他 AWS CloudOps 产品,企业可以构建一个全面的智能运维平台,提高系统可靠性,降低运维成本,加速业务创新。

参考资源

*前述特定亚马逊云科技生成式人工智能相关的服务目前在亚马逊云科技海外区域可用。亚马逊云科技中国区域相关云服务由西云数据和光环新网运营,具体信息以中国区域官网为准。

本篇作者

谭欣

亚马逊云科技解决方案架构师,负责帮助客户设计和优化符合自身业务场景的云架构,并提供技术支持。拥有 7 年创业经历,在直播音视频架构设计、生成式 AI 应用落地方面有着丰富的实战经验。

徐为

亚马逊云科技资深解决方案架构师。近20年的IT从业经验,主导开发和迁移过多个大型的IT项目,在ERP,游戏,汽车等多个行业有深刻理解。目前的专注于可观测性及AIOps领域,协助多家大型企业完成可观测性及AIOps的规划及建设。

张庭纶

亚马逊云科技解决方案架构师,主要专注于云端可观测性解决方案,目前负责媒体与娱乐行业,协助客户导入可观测性方案,提升系统稳定性与运维效率。