亚马逊AWS官方博客

使用 Amazon Bedrock Claude3 多模态能力和 Grafana 构建智能巡检平台

前言

现在智能监控平台已经成为确保 IT 基础设施高效运行和优化运维效率的关键工具,很多公司已经使用 Grafana 和 Prometheus 作为可观测性的核心工具。传统的监控系统往往只能被动收集和展示原始数据,缺乏深入分析和智能见解的能力。相比之下,智能监控平台通过融合先进的人工智能、机器学习和大数据分析技术,可以从海量监控数据中主动发现隐藏的模式和趋势,提供预测性维护和自动化优化建议。

借助 Amazon Bedrock 等 AI 技术平台,智能监控平台可以集成业界领先的大语言模型(LLM),如 Claude 3 等。这些 LLM 具备出色的自然语言处理能力和多模态能力,能够对监控日志、警报消息和系统指标图表等非结构化数据进行深度分析和理解,从而发现潜在的异常情况和风险因素。为此我们集成了常用的 Grafana 监控平台,将 CloudWatch 数据通过 Grafana 仪表盘进行展示,结合 Claude 3 的多模态进行智能分析,降低 IT 部门日常监控复杂度,实现高效、智能的自动巡检。

本项目已经发布在 https://github.com/aws-samples/grafana-smartmonitor-sample

架构介绍

本文示例将构建实现以下监控巡检流程:

1. 创建 Grafana datasource,拉取 Amazon CloudWatch 监控数据

2. 关键仪表盘信息提取, 通过chromedp 自动获取仪表盘图片, 由Claude3 多模态能力提取报表数据关键信息

3. Claude 3 根据多模态提取的信息,进行指标判断,返回巡检结果

4. Claude 3 按照项目维度,进行多个指标巡检,并且进行总结

关键组件介绍:

Amazon Cloudwatch:AWS CloudWatch 是亚马逊云科技(AWS)提供的一项监控和可观察性服务。它可以帮助您收集和跟踪来自多个 AWS 资源的指标、日志和事件数据,通过 CloudWatch,您可以全面了解 AWS 环境中的资源和应用程序的运行状况,从而优化资源利用率、应对潜在问题并保持系统正常运行。CloudWatch 与其他 AWS 服务紧密集成,为您提供全面的监控和可观察性解决方案。

Amazon Bedrock:Amazon Bedrock 是一项完全托管的服务,通过单个 API 提供来自 AI21 Labs、Anthropic、Cohere、Meta、Mistral AI、Stability AI 和 Amazon 等领先人工智能公司的高性能基础模型(FM),以及通过安全性、隐私性和负责任的人工智能构建生成式人工智能应用程序所需的一系列广泛功能。

Grafana:Grafana 是一款开源的数据可视化工具,使用 Grafana 可以非常轻松的将数据转成图表的展现形式来做到数据监控以及数据统计。

Chromedp:Chromedp 是一个用 Go 语言编写的高级浏览器驱动库。它可以通过操作 Chrome/Chromium 浏览器来执行各种任务,例如网页抓取、自动化测试、生成屏幕截图或 PDF 等。chromedp 旨在提供一种简单、可靠且高效的方式来与 Chrome 浏览器进行交互。

部署项目

本例中为了简化部署,我们使用 docker compose 的方式来部署服务,咱们可以根据自己的环境特性来选择组件。整体部署流程如下:

1. EC2 配置

EC2 关联 Role 需要具备 Bedrock invokeModel 权限

2. 环境配置

NodeJS:20+,可以参考 https://nodejs.org/en/download/package-manager 进行安装配置

Golang:1.21+,可以参考 https://go.dev 进行安装配置

3. 下载代码

git clone https://github.com/aws-samples/grafana-smartmonitor-sample.git

4. 安装 Docker

sudo yum install docker
sudo systemctl start docker
sudo systemctl enable docker

5. 镜像打包 -backend

cd grafana-smartmonitor-sample/backend
docker build -t backend .

6. 镜像打包 -front

cd grafana-smartmonitor-sample/front
sed -i 's|API_URL="http://localhost:8080/api/v1"|API_URL="http://{Server IP}:8080/api/v1"|g' env.example
docker build -t front .

7. 修改 Docker Compose

version: '4'
 
services: 
  mysql:
    image: mysql:8.0
    container_name: mysql-8
    networks: 
      - grafana
    environment:
      MYSQL_ROOT_PASSWORD: your-secret-pw # Mysql 密码,请修改
    ports:
      - 3306:3306
    volumes:
      - ./backend/sql:/docker-entrypoint-initdb.d/
    healthcheck:
      test: ["CMD-SHELL", "mysql -uroot -p$$MYSQL_ROOT_PASSWORD -e 'SHOW DATABASES LIKE \"bedrock_claude3_grafana_automonitor\";'"]
      interval: 10s
      timeout: 10s
      retries: 5
      start_period: 10s

  grafana: 
    image: grafana/grafana 
    container_name: grafana
    networks: 
      - grafana 
    environment: 
      - GF_PATHS_DATA=/var/lib/grafana 
      - GF_SECURITY_ADMIN_PASSWORD=your-secret-pw # Grafana 密码,请按照自己喜好修改
      - GF_SECURITY_ADMIN_USER=admin
    command:
      - "sh"
      - "-c"
      - "grafana cli admin reset-admin-password ${GF_SECURITY_ADMIN_PASSWORD} && /run.sh"
    ports: 
      - 3100:3000 
  chromedp: 
    image: chromedp/headless-shell
    container_name: chromedp
    networks: 
      - grafana 
    ports: 
      - 9222:9222 
    command:
      - --disable-gpu 
      - --headless 
  backend: 
    image: backend
    container_name: backend
    networks: 
      - grafana
    depends_on:
      mysql:
        condition: service_healthy
    environment: 
      - HEADLESS=true
    command:
      - /app/automonitor 
      - -dbName=bedrock_claude3_grafana_automonitor 
      - -dbUser=root 
      - -dbPassword=your-secret-pw # Mysql 密码,请修改
      - -dbHost=mysql
      - -front=http://front:3000
      - -chromeDP=ws://chromedp:9222
    volumes: 
      - ./backend/static:/app/static
   
    ports: 
      - 8080:8080
  front: 
    image: front
    container_name: front
    networks: 
      - grafana 
    ports: 
      - 3000:3000
networks: 
  grafana: {}

8. 服务启动

docker compose -f docker-compose.yaml up -d

9. 服务验证

docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED        STATUS                  PORTS                               NAMES
d5d09140d837   backend                   "/app/automonitor -d…"   16 hours ago   Up 16 hours             0.0.0.0:8080->8080/tcp              backend
f1e9385aa224   mysql:8.0                 "docker-entrypoint.s…"   17 hours ago   Up 17 hours (healthy)   0.0.0.0:3306->3306/tcp, 33060/tcp   mysql-8
ba553f90dd8b   front                     "docker-entrypoint.s…"   17 hours ago   Up 17 hours             0.0.0.0:3000->3000/tcp              front
b4f3070224ee   grafana/grafana           "/run.sh sh -c 'graf…"   17 hours ago   Up 17 hours             0.0.0.0:3100->3000/tcp              grafana
090a6f359e68   chromedp/headless-shell   "/headless-shell/hea…"   17 hours ago   Up 17 hours             0.0.0.0:9222->9222/tcp              chromedp

至此我们环境就部署完毕了。

巡检测试

下面我们来测试一下整体的服务流程是不是跟我们预期一致。

1. 登录 Grafana(http://localhost:3000),导入 CloudWatch Dashboard

添加 CloudWatch 数据源

配置 AWS Credentials

导入 Dashboard

验证 Dashboard 正常与否

2. 登录 Grafana-SmartMonitor,默认用户名和密码:admin/admin,配置指标

3. 点击 Run 之后,可以看到 SmartMonitor 通过截屏的方式已经分析出响应的指标信息,并给出判断

4. Scheduler 配置,我们可以针对整个项目下的所有指标进行综合评估,并且可以通过周期性调用

5. 过一段时间来查看巡检结果

代码说明

项目分为 front,backend 分别是 SmartMonitor 的前后端。

1. 获取 Grafana 仪表盘

ChromeDP 初始化:创建 ChromeDP 的执行上下文,并设置一些浏览器选项,如无头模式、GPU 加速、禁用扩展等

func createAllocContext(headless bool) (context.Context, context.CancelFunc) {


    _headless := headless

    boolValue, err := strconv.ParseBool(GetEnv("HEADLESS", "true"))
    if err == nil {
        _headless = boolValue
    }
    logger.Printf("createAllocContext invoke , headless: %v", _headless)

    opts := append(
        chromedp.DefaultExecAllocatorOptions[:],
        chromedp.Flag("headless", _headless),
        chromedp.Flag("disable-gpu", false),
        chromedp.Flag("enable-automation", false),
        chromedp.Flag("disable-extensions", true),
    )
    if (globalConfig.ChromeDP==""){
        return chromedp.NewExecAllocator(context.Background(), opts...)
    }
    fmt.Println("use remote ", globalConfig.ChromeDP)
    return  chromedp.NewRemoteAllocator(context.Background(), globalConfig.ChromeDP)
    
}

2. 登录截取指定页面内容

func ScreenCaptureTasks(targetURLs []string, conn *MonitorConnection) (map[string]string, error) {
    urlToImageMap := make(map[string]string)

    defaultAllocContext, defaultAllocContextCancel := createAllocContext(false)
    defer defaultAllocContextCancel()

    timeContext, cancelFunc := context.WithTimeout(defaultAllocContext, time.Second*90)
    defer cancelFunc()

    defaultChromeContext, defaultChromeContextCancel := chromedp.NewContext(timeContext)
    defer defaultChromeContextCancel()

    fmt.Println(conn)
    err := chromedp.Run(defaultChromeContext, loginGrafanaTasks(conn.URL, conn.Username, conn.Password))
    if err != nil {
        //panic(err)
        return nil, fmt.Errorf("failed to capture screenshot for  %v", err)
    }

    for _, url := range targetURLs {
        var buf []byte
        tasks := chromedp.Tasks{
            chromedp.Navigate(url),
            chromedp.WaitVisible(`.page-dashboard`),
            chromedp.Sleep(2 * time.Second),
            chromedp.CaptureScreenshot(&buf), # 截取屏幕内容
        }

        err := chromedp.Run(defaultChromeContext, tasks)
        if err != nil {
            return nil, fmt.Errorf("failed to capture screenshot for URL %s: %v", url, err)
        }

        // Save the screenshot to a file
        imagePath, err := saveScreenshotToFile(buf)
        if err != nil {
            return nil, fmt.Errorf("failed to save screenshot for URL %s: %v", url, err)
        }

        urlToImageMap[url] = imagePath
    }
    logger.Printf("ScreenCaptureTask completed , %+v", urlToImageMap)
    return urlToImageMap, nil
}

3. 根据预置条件来通过 Claude 3 多模态功能来分析图片内容

func RunCondition(item *MonitorMetric,connection *MonitorConnection) ([]string, error) {

    result, err := ScreenCaptureTasks([]string{
        item.DashboardURL,
    },connection)

    if err != nil {
        return nil, err
    }

    data := map[string]interface{}{
        "context": item.ItemDesc,
    }

    //Extract information from image
    prompt, err := RenderTemplate(data, "txtExtrac", imageTextExtracPromptTemplate)
    if err != nil {
        return nil, err
    }
    // Encode Image with Base64
    image := fmt.Sprintf("./%s", result[item.DashboardURL])
    base64Content, err := getBase64EncodedImage(image)
    if err != nil {
        return nil, err
    }
    //Invoke Claude3
    output := Claude3InvokWithImage(prompt, base64Content, true)
    if output == nil {
        return nil, err
    }

    context := output.Content[0].Text

    //Run condition
    data = map[string]interface{}{
        "context":   context,
        "condition": item.ItemCondition,
    }

    prompt, err = RenderTemplate(data, "runCondition", runConditionPromptTemplate)
    if err != nil {
        return nil, err
    }

    output = Claude3Invok(prompt)
    if output == nil {
        return nil, err
    }

    fmt.Println(output)

    return []string{result[item.DashboardURL], output.Content[0].Text}, nil
}

其中 runConditionPromptTemplate 的提示词模版为:

我们会使用 imageTextExtracPromptTemplate和runConditionPromptTemplate 来完成图片识别和指标条件判断,提示词如下:

imageTextExtracPromptTemplate string = `
    Instruct:  你是AWS IT专家,你会根据我提供的context来提取图片里面的信息,如果是EC2, S3, ALB请带上具体的信息,比如实例id,ARN
    
    使用中文和JSON格式返回,返回的例子
    {"text":###text exctract from image"}
    context: 
    {{.context}}
    `
    
runConditionPromptTemplate string = `
    Instruct: 你是AWS IT专家,你会根据我提供的context和condition进行判断,并且根据condition标注具体的指标,比如EC2实例id, S3 bucketname
    以中文和YAML格式返回, 返回yaml的例子,(不要包含任何markdown标记,纯文本):
    result:
        pass : true 
        reason: |
            your answer

    context: 
    {{.context}}

    condition:
    {{.condition}}

    `

4. 项目下所有指标总结

func RunProjectSummary(items []MonitorMetric) (string, error) {
    itemsString, err := json.Marshal(items)
    if err != nil {
        return "", nil
    }
    data := map[string]interface{}{
        "metrics": string(itemsString),
    }
    //Render template with monitor metrics
    prompt, err := RenderTemplate(data, "txtExtrac", projectSummaryPromptTemplate)
    if err != nil {
        return "", err
    }
    //Summary with Claude3
    output := Claude3Invok(prompt)
    if output == nil {
        return "", err
    }
    logger.Println(output)
    text := output.Content[0].Text
    text = strings.TrimPrefix(text, "```json")
    text = strings.TrimSuffix(text, "```")
    return text, nil
}

其中 projectSummaryPromptTemplate 的提示词模版为:

在整个项目总结中我们使用了 projectSummaryPromptTemplate 提示词

projectSummaryPromptTemplate string = `
    Instruct: you are senior SRE , I give some metrics , you need analtyics and summary IT infrastructure issue or other thing
you need provide detail information in summary and you need return use json and need include all raw metrics

    health level:
    Very Good , all metrics pass
    Good,  95% mestrics pass 
    Not Good 90% mestrics pass
    Bad , less 90% pass 
    Very Bad less 80% pass 
    metrics example:
    [{
            "id": 1,
            "project": "Project1",
            "catalog": "EC2",
            "item_desc": "EC2 实例的CPU用量",
            "item_condition": "如果CPU大于45%则不通过",
            "dashboard_url": "http://localhost:3100/d/tmsOtSxZk/amazon-ec2?orgId=1\u0026viewPanel=2",
            "status": false,
            "status_desc": "根据提供的上下文信息,其中列出了三个 EC2 实例的 ID 及其最大 CPU 利用率。\n根据条件\"如果 CPU 大于 45% 则不通过\"的要求,其中一个实例 i-0cf8b09fb079046e3 的最大 CPU 利用率为 47.5%,超过了 45% 的阈值,因此不通过验证。",
            "screen": "static/9033d403-8c12-4d05-9017-4d9dbec3c29c.png",
            "check_date": "2024-04-25T21:33:17+08:00"
        },
        {
            "id": 3,
            "project": "Project1",
            "catalog": "EC2",
            "item_desc": "出口网络流量",
            "item_condition": "网络流量不大于2k",
            "dashboard_url": "http://localhost:3100/d/tmsOtSxZk/amazon-ec2?orgId=1\u0026viewPanel=18",
            "status": false,
            "status_desc": "根据提供的上下文信息,有一个 EC2 实例 ID i-0cf8b09fb079046e3 在 14:00 左右出现了明显的流量峰值。由于条件是网络流量不大于 2k,因此该实例在该时间段内的网络流量很可能超过了 2k。因此,无法通过条件检查。\n\n具体检查项目:\n  - EC2 实例 ID: \n    - i-0222ef85c03b5b8c5\n    - i-0333da98ab126702b\n    - i-0cf8b09fb079046e3",
            "screen": "static/43ee21f9-1b3e-4b4d-9d1e-e4d50c9fca4d.png",
            "check_date": "2024-04-24T14:44:10+08:00"
            }]
    input metrics
    {{.metrics}}
    output is json , not include any markdown code, your summary, use '\"' replace, not use '"', must be correct json:
    
    {
        "health":"health_level",
        "summary": ##your summary##,
        "metrics":"raw_mestics"
    }
    `

总结

在本文中,我们探讨了如何利用 Amazon Bedrock、Claude 3 大型语言模型和云原始监控工具 Grafana 来构建一个应用智能巡检系统。我们构建了一个端到端的巡检流程,包括创建 CloudWatch 数据元,监控指标创建、仪表盘关键信息提取、巡检等步骤。通过结合 Claude 3 的强大多模态能力和自然语言处理能力,我们能够高效地对多个监控指标进行巡检、分析。让大语言模型自动提取关键信息、分析当前系统状态,并生成总结性报告。我们演示了对 Amazon EC2、RDS 等多个服务进行巡检,可以获得更全面的 IT 服务洞见。总的来说,将 Amazon Bedrock、Claude 3 和 Grafana 结合使用,为构建智能化智能巡检系统系统提供了一种有前景的解决方案。未来,我们计划进一步扩展和优化这一系统,以支持更多功能和更大规模的监控分析、处理需求。

*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。

参考资料

https://docs.aws.amazon.com/bedrock

https://github.com/aws-samples/grafana-smartmonitor-sample

https://github.com/chromedp/chromedp

https://grafana.com

本篇作者

粟伟

亚马逊云科技资深解决方案架构师,专注游戏行业,开源项目爱好者,致力于云原生应用推广、落地。具有 15 年以上的信息技术行业专业经验,担任过高级软件工程师,系统架构师等职位,在加入 AWS 之前曾就职于 Bea,Oracle,IBM 等公司。

郭俊龙

亚马逊云科技解决方案架构师,主要负责游戏行业客户解决方案设计,比较擅长云原生微服务以及大数据方案设计和实践。