亚马逊AWS官方博客

一键部署在钉钉群里自动创建 AWS Support Case 无服务器解决方案

背景

目前大部分AWS客户使用AWS控制台创建AWS Support Case,但是国内很多客户使用钉钉作为他们的内部工作的沟通平台,客户希望可以在一个钉钉群里创建AWS Support Case,主要基于三个需求:1.创建Case可以更方便快捷;2.提供的Case信息可以公开给其他同事以及对接的AWS架构师;3.某些没有AWS账号的员工或者没有创建Support 权限的员工也可以提Case。

基于以上需求,本博文提出了一种在钉钉群里自动创建AWS Support Case的无服务器解决方案,利用AWS API Gateway、Lambda以及Secrets Manager,并结合了钉钉的企业内部机器人的回调机制实现了一个事件驱动型的Serverless解决方案。为了节省客户的部署时间以及复杂度,本文将方案部署在了AWS SAR (无服务器应用存储库) 中,客户可以直接在Lambda控制台中搜索应用进行一键部署,也可以基于SAR中的源代码进行更多的自定义开发。(注意:只有开启了AWS Business Support和AWS Enterprise Support的客户才能使用此方案。)

 

基础介绍

AWS API Gateway 是一种完全托管的服务,可以帮助开发人员轻松创建、发布、维护、监控和保护任意规模的 API。API 充当应用程序的前门,可从您的后端服务访问数据、业务逻辑或功能。使用 API Gateway,您可以创建 RESTful API 和 WebSocket API,以便实现实时双向通信应用程序。API Gateway 支持容器化和无服务器工作负载,以及 Web 应用程序。

AWS Lambda是一种无服务器的计算服务,让您无需预置或管理服务器、创建可感知工作负载的集群扩展逻辑、维护事件集成或管理运行时,即可运行代码。借助 Lambda,您几乎可以为任何类型的应用程序或后端服务运行代码,而且完全无需管理。只需将您的代码以 ZIP 文件或容器映像的形式上传,Lambda 便会自动、精确地分配计算执行能力,并根据传入的请求或事件运行您的代码,以适应任何规模的流量。

AWS SAM (Serverless Application Model) 是一个用于构建无服务器应用程序的开源框架。它提供了简写语法来表示函数、api、数据库和事件源映射。每个资源只需几行代码就可以定义您想要的应用程序,并使用YAML对其进行建模。

AWS SAR (Serverless Application Repository) 可使开发人员和企业轻松地在 AWS 云中快速查找、部署和发布无服务器应用程序。您可以轻松地发布应用程序,将其与整个社区公开共享,或在团队中或在您的组织中私下共享。AWS SAR与 AWS Lambda 控制台深度集成。此集成意味着所有级别的开发人员都可以开始使用无服务器计算,而无需学习任何新的内容。

AWS Secrects Manager可帮助您保护访问应用程序、服务和 IT 资源所需的密钥。该服务使您能够轻松地跨整个生命周期轮换、管理和检索数据库凭证、API 密钥和其他密钥。用户和应用程序通过调用 Secrets Manager API 来检索密钥,无需对纯文本的敏感信息进行硬编码。

AWS Support提供了各种工具和技术、人员及计划,旨在主动帮助您优化性能、降低成本并更快地进行创新。我们可以帮助您在云中更快地发展并专注于您的核心业务,因此会为您的团队节省时间。我们会竭力帮助客户在其云之旅中取得成功,并满足从回答最佳实践问题、提供配置指导到修复故障和解决问题的各种要求。

钉钉企业内部机器人是钉钉为用户提供的组织内部使用的机器人,为组织数字化转型业务服务。开发者可进行机器人的自主开发和上架,组织内其它成员可通过方便快捷地在群内添加企业机器人,并使用机器人的能力。基于企业机器人的outgoing(回调)机制,用户@机器人之后,钉钉会将消息内容POST到开发者的消息接收地址。开发者解析出消息内容、发送者身份,根据企业的业务逻辑,组装响应的消息内容返回,钉钉会将响应内容发送到群里。

 

方案架构

本次方案的总体架构图如下:

 

  1. 用户在钉钉群中@钉钉企业内部机器人发送交互信息;
  2. 钉钉企业内部机器人接受到消息之后,利用回调机制给API Gateway的HTTP API发送post请求;
  3. API Gateway接受到post请求后触发Lambda function;
  4. Lambda function执行相关业务逻辑;
    1. 调用secret manager获取钉钉机器人的app_secret参数以及钉钉群中钉钉机器人的access_token参数;
    2. 通过计算合法的和真实的时间戳和签名差判断请求是否合法;
    3. 解析接收的请求中文本信息,判断用户的需求
    4. 根据用户不同需求执行不同Support API 方法(查找serviceCode、创建case、释放case)
    5. 将调用support API返回的信息修正成钉钉机器人Message格式;
    6. 给钉钉内部机器人的webhook链接发送post请求
  5. 用户在钉钉群中接收钉钉内部机器人发送的消息。

 

方案部署与演示

1.创建钉钉企业内部机器人

确认钉钉账号属于企业内部开发者(参考成为钉钉开发者文档),并参考钉钉企业内部机器人文档,创建一个企业内部机器人,创建完之后记录AppSecret的值。

创建完之后点击“版本管理与发布”>“调试”,扫码自动进入一个钉钉机器人测试群,在测试群中选择“Group Setting”>“Group Assistant”>“自动创建 AWS Case 机器人”,进入设置界面,修改 Security Setting 中的 Custom Keywords 为“AWS”(必要步骤,代码有引用),并记下 Webhook 中 access_token 参数的值(注意:每一个群中的机器人access_token都不一样)。

2.创建 AWS Secrect Manager

在 AWS Console 中选择 Secret Manager,进入之后选择“存储新的密钥”,并选择其他密钥类型。在“Secret Key/Value”中添加钉钉机器人的两个密钥,key 分别为“access_token”和“app_secret”,value 为上述获得的两个值。并填写secret名称为“dingding-outgoing-robot-env”


3.部署AWS Lambda和AWS API Gateway

进入AWS Lambda Console,选择创建Lambda 函数,并选择从无服务器应用程序存储库中创建,输入应用名称为为”dingding-robot-aws-case”,并点击选择部署。该AWS SAR存储库会自动部署一个AWS Lambda函数以及AWS API Gateway,并赋予了Lambda运行角色策略 “SecretsManagerReadWrite” 和策略 ” AWSSupportAccess”。

 

部署完成后查看创建好的API Gateway,记录”调用URL”的http url链接。

 

为了进行更好的调试,可以给创建好的 API Gateway 打开日志。首先在 CloudWatch Logs 中创建一个日志组。再在API Gateway 中打开访问日志。

4.更新钉钉机器人配置

在钉钉机器人后台点击“开发管理”,在“服务器出口IP”处填写本公司任意一个可以公网访问的IP,消息接收地址即填写上述 API Gateway的调用URL。

5.演示

自动创建 AWS Case 的钉钉机器人使用实验示例如下所示。输入“你好”或者任何其他信息,钉钉机器人会返回介绍,介绍目前三个功能的使用方式。

发送“查找ServiceCode: service name”,钉钉机器人会返回相关服务的serviceCode和categoryCode信息。如果service name错误会返回错误提醒。

发送“提工单”以及相关参数信息,钉钉机器人会返回创建的case id信息。如果输入的格式有误或者缺少信息会返回错误提醒。

发送“释放工单:case_id”,钉钉机器人会返回case 释放情况信息。如果输入的case id不存在会返回错误提醒。

 

方案原理介绍

本节主要讲解Lambda函数中的业务处理逻辑,客户可以基于此逻辑进行修改,可以修改或者集成更多的业务处理逻辑。

lambda_handler 为Lambda函数的主函数,从event参数中解析请求的关键信息,判断请求是否合法并执行相关处理逻辑,最后发送消息回给钉钉机器人。

def lambda_handler(event, context):
    # 获取请求中信息
    request_timestamp = event['headers']['timestamp'].strip()
    request_sign = event['headers']['sign'].strip()
    request_message = json.loads(event['body'])['text']['content'].strip()

    # 计算合法的时间戳和签名
    timestamp, sign = calcu_legal_timestamp_sign(request_timestamp)
    # 判断请求是否合法
    if abs(int(request_timestamp) - int(timestamp)) < 3600000 and request_sign == sign:
        header = {
            "Content-Type": "application/json",
            "Charset": "UTF-8"
        }
        # 拼接发送钉钉消息的webhook
        access_token = get_secret('access_token')
        webhook = "https://oapi.dingtalk.com/robot/send?access_token=" + access_token + "&timestamp=" + timestamp + "&sign=" + sign
        # 发送消息
        message_json = json.dumps(selectMes(request_message))
        # 返回发送状态
        info = requests.post(url=webhook, data=message_json, headers=header)
        print(info.text)
    else:
        print("Warning: Not DingDing's legal post request")

计算合法时间戳和签名的代码如下所示:

def calcu_legal_timestamp_sign(request_timestamp):
    """
    计算合法的时间戳和签名
    """
    app_secret = get_secret('app_secret')
    timestamp = str(round(time.time() * 1000))
    app_secret_enc = app_secret.encode('utf-8')
    string_to_sign = '{}\n{}'.format(request_timestamp, app_secret)
    string_to_sign_enc = string_to_sign.encode('utf-8')
    hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
    sign = base64.b64encode(hmac_code).decode('utf-8')
    return timestamp, sign

解析用户发给钉钉机器人的文本消息并执行不同操作(查询、创建、释放)

def selectMes(request_message):
    """
    根据输入内容进行不同action以及选择不同返回信息
    """
    send_message = 'AWS-[欢迎使用自动创建AWS工单钉钉机器人服务,本机器人目前支持3个功能。请注意换行以及英文逗号]\n\n'+\
        '[功能1:搜索AWS服务的serviceCode和categoryCode。请按以下格式输入]\n查找ServiceCode:{AWS服务名称,支持简写}\n\n'+\
        '[功能2:创建 AWS case。请按以下格式输入]\n提工单\nsubject:{case 主题}\nbody:{case 描述}\n'+\
        'severityCode:{low|normal|high|urgent|critical}\nserviceCode:{aws serviceCode}\ncategoryCode:{aws service categoryCode}\n\n'+\
        '[功能3:释放 AWS case。请按以下格式输入]\n释放case,case_id:{case_id}'

    case_message_dict = get_valid_dict(request_message)
    if case_message_dict is not None:
        send_message = create_aws_case(case_message_dict)
    elif '查找servicecode' in request_message.lower():
        send_message = get_aws_serviceCode_categoryCode(request_message)
    elif '释放' in request_message.lower():
        send_message = resolve_aws_case(request_message)
    return sendText(send_message)

查询serviceCode和categoryCode的方法如下:

def get_aws_serviceCode_categoryCode(request_message):
    """
    根据服务名获取获取 aws servicecode 和 categorycode
    """
    service_name = request_message.split(':')[1]
    response = support_client.describe_services()
    for service in response["services"]:
        if service['name'].lower().find(service_name.lower()) != -1:
            return 'AWS-[serviceCode和categoryCode信息如下]\n' + str(service)
    return 'AWS-[请输入正确的 AWS Service 名称]'

创建aws case的方法如下:

def create_aws_case(case_message_dict):
    """
    创建aws技术case
    """
    subject = case_message_dict['subject']
    communicationBody = case_message_dict['body']
    serviceCode = case_message_dict['serviceCode']
    categoryCode = case_message_dict['categoryCode']
    severityCode = case_message_dict['severityCode']
    try:
        response = support_client.create_case(
            subject=subject,
            serviceCode=serviceCode,
            categoryCode=categoryCode,
            severityCode=severityCode,
            communicationBody=communicationBody,
            # ccEmailAddresses = [
            #         "abc@test.com",
            # ],
            language="en",
            issueType="technical"
        )
        case_id = response["caseId"]
    except:
        return "AWS-[创建工单失败]\n请输入正确的 serviceCode 和 categoryCode"
    else:
        return 'AWS-[创建工单成功]\ncase id: '+case_id

释放aws case的方法如下:

def resolve_aws_case(request_message):
    """
    释放 aws case
    """
    try:
        case_id = request_message.split(':')[1]
        support_client.resolve_case(caseId=case_id)
    except:
        return 'AWS-[释放case失败]\ncase_id:' + case_id + '不存在'
    else:
        return 'AWS-[释放case成功]\ncase_id:' + case_id + '已释放'

 

参考链接

企业内部机器人: https://developers.dingtalk.com/document/app/develop-enterprise-internal-robots?spm=ding_open_doc.document.0.0.74d928e1nXloB5#topic-2026025

AWS Support API 介绍:https://docs.aws.amazon.com/awssupport/latest/user/Welcome.html

AWS Python SDK for AWS Support: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/support.html

AWS API Gateway: https://aws.amazon.com/cn/api-gateway/

AWS Lambda: https://aws.amazon.com/cn/lambda/

AWS Secrects Manager: https://aws.amazon.com/cn/secrets-manager/

AWS Serverless Application Model: https://docs.aws.amazon.com/zh_cn/serverless-application-model/latest/developerguide/what-is-sam.html

AWS Serverless Application Repository: https://docs.aws.amazon.com/zh_cn/serverlessrepo/latest/devguide/what-is-serverlessrepo.html

 

本篇作者

张贝贝

AWS解决方案架构师,负责基于AWS的云计算方案架构咨询和设计,擅长软件开发,机器学习等领域,具有丰富的解决客户实际问题的经验。