亚马逊AWS官方博客

使用 Lambda 监控 Amazon Lightsail 数据流量

Amazon Lightsail 是初创公司、中小型企业和业余开发人员的理想选择,因为它简化了实例、数据库、负载均衡器、CDN 甚至容器的部署。但是,Lightsail 的监控功能有一定局限性,客户无法跟踪 CPU 利用率、网络利用率和实例状态消息之外的指标,并且在官方的指标监控面板上,最长的数据统计周期为 24 小时。

在实际生产中,许多初创公司和小型企业的云上业务会产生大量的互联网流量。根据 Lightsail 的计费模型,在流量超所选机型的配额后,会有高额的网络传输费用产生。客户希望 AWS 能够提供对 Lightsail 实例的进出站总流量做实时的监控,并在流量超出配额时发出告警,关闭该实例。

在本博客中,我将介绍如何使用 Lambda 函数,EventBridge 服务来获取客户账号下所有 Lightsail 实例的流量使用情况,并且在流量超出配额时发送 SNS 提醒,同时关闭流量超限的实例。

方案拓扑

方案思路

本方案的主要实现方式如下:

  • 利用 Lightsail 的 API 接口:get_instance,获取账号下在当前区域里的所有 Lightsail 实例。
  • 根据 Lightsail 实例的类型,获取每个实例每个月的网络流量配额。
  • 根据实例的创建时间,计算出每个实例在当前这个计费周期内的流量配额。
  • 通过 API 接口:get_instance_metric_data,获取每个实例已经使用的入站和出站流量总量。
  • 如果流量超出当前计费周期的配额,则通过 SNS 发送提醒邮件,并关闭对应的 Lightsail 实例。
  • 通过 EventBridge 以 cron job 的方式定时触发 Lambda,运行此检查逻辑。

具体实施步骤

步骤一:

首先创建 SNS 提醒 Topic,用来推送相关 Lightsail 实例的状态信息。进入 SNS 的创建 Topic 页面,选择标准模式,填入 Name 点击创建主题

步骤二:

创建 Lambda 函数,用来获取 Lightsail 数据,以及统计总用量。

在 Lambda 函数页面,输入函数名称,并选择 Python 3.12 的运行时,架构使用 x86_64 架构,点击创建函数。

函数编辑页面,将下列代码拷贝进代码编辑器,并点击部署。

import json
import boto3
import calendar
import os
from datetime import datetime, date, time,timedelta
 
SNS_TOPIC = os.environ['SNS_TOPIC']
 
def get_current_month_first_day_zero_time():
    today = date.today()
    first_day = today.replace(day=1)
    first_day_zero_time = datetime.combine(first_day, time.min)
    return first_day_zero_time
    
def get_current_month_last_day_last_time():
    today = date.today()
    last_day = today.replace(day=calendar.monthrange(today.year, today.month)[1])
    last_day_last_time = datetime.combine(last_day, time(23, 59, 59))
    return last_day_last_time
    
def stop_instance(instance_name):
    client = boto3.client('lightsail')
    response = client.stop_instance(
        instanceName=instance_name,
        force=True
    )
    
def list_instances(instances_list):
    client = boto3.client('lightsail')
    paginator = client.get_paginator('get_instances')
    # Create a PageIterator from the Paginator
    page_iterator = paginator.paginate()
    for page in page_iterator:
        for instance in page['instances']:
            print(instance['name'])
            instances_list.append(instance['name'])
        
        
 
def get_month_dto_quota(instance_name):
    client = boto3.client('lightsail')
    response = client.get_instance(
        instanceName=instance_name
    )
    #print("response : {}".format(response))
    dto_quota = response['instance']['networking']['monthlyTransfer']['gbPerMonthAllocated']
    current_datetime = datetime.now()
    instance_created_datetime = response['instance']['createdAt']
    if (instance_created_datetime.year == current_datetime.year) and (instance_created_datetime.month == current_datetime.month):
        month_ts = get_current_month_last_day_last_time().timestamp() - get_current_month_first_day_zero_time().timestamp()
        instance_valide_ts = get_current_month_last_day_last_time().timestamp() - instance_created_datetime.timestamp()
        dto_quota = (instance_valide_ts/month_ts) * dto_quota
        print("created in current month, quota: {}GB".format(dto_quota))
    else:
        dto_quota = response['instance']['networking']['monthlyTransfer']['gbPerMonthAllocated']
        print("created in previous month, full quota: {}GB".format(dto_quota))
    
    return dto_quota
    
def get_instance_data_usage(instance_name, data_type):
    client = boto3.client('lightsail')
    current_time = datetime.utcnow()
    start_time = get_current_month_first_day_zero_time()
    end_time = get_current_month_last_day_last_time()
    start_time_str = start_time.strftime('%Y-%m-%dT%H:%M:%SZ')
    end_time_str = end_time.strftime('%Y-%m-%dT%H:%M:%SZ')
 
    response = client.get_instance_metric_data(
        instanceName=instance_name,
        metricName=data_type,
        period= 6 * 600 * 24,
        unit='Bytes',
        statistics=[
            'Sum'
        ],
        startTime=start_time_str,
        endTime=end_time_str 
    )
 
    data_points = response['metricData']
    total_data_usage = sum([data_point['sum'] for data_point in data_points])
    print("total {} usage: {}".format(data_type, total_data_usage))
    return total_data_usage
 
def push_notification(arn, msg):
    sns_client = boto3.client('sns')
    print("sqs arn: {}".format(arn))
    response = sns_client.publish(
        TopicArn=arn,
        Message=msg,
        Subject='Lightsail NetworkOut exceeded quota '
    )
 
def lambda_handler(event, context):
    instance_name= []
    list_instances(instance_name)
    for i in instance_name:
        quota = get_month_dto_quota(i) * 1000 * 1000 * 1000
        total = get_instance_data_usage(i, "NetworkOut") + get_instance_data_usage(i, "NetworkIn") 
        msg = f"instance_name: {i} \nusage: {total} Byte \nquota: {quota} Byte \nusage percent: {(total/quota)*100} %"
        print(msg)
        
        if int(quota) < int(total):
            print("quota < total, soforce close instance: {}".format(1))
            push_notification(SNS_TOPIC, msg)
            stop_instance(i)
             
    return {
        'statusCode': 200,
        'body': json.dumps('total_data_usage from Lambda!')
    }

步骤三:

配置 Lambda 函数的运行内存。在 Lambda 函数的配置标签下,选择左侧面板的常规配置, 将内存改为 1024MB,运行时超时改为 10 分钟 0 秒

配置环境变量。在Lambda函数的配置标签下,选择左侧面板的环境变量选项,将第一步的 SNS 主题的 Arn 填入 Lambda 的环境变量中。

权限配置。 由于默认创建的 Lambda 函数没有权限调用 Lightsail 和 SNS 的 API,所以我们需要将相关权限,服务此 Lambda 函数的执行 Role。在 Lambda 函数的配置标签下,选择左侧面板的权限。然后点击下图红框所示的角色名称,进入 IAM 页面,添加权限。

在 IAM 页面,添加如下 Policy。

步骤四:

通过 EventBridge 配置 Cron job,每 15 分钟定时启动 Lambda 函数,获取 Lightsail 实例的流量数据。

在 Lambda Function 页面,点击添加触发器,进入触发器编辑页面。分别填入,触发器名称、出发表达式,最后点击确定。

至此,整个方案的部署全部完成,Lightsail 实例会在流量配额耗尽时,被 Lambda 函数自动关闭,并且 SNS 的订阅客户可以收到对应的提醒短信。

使用 CDK 自动化部署

步骤一:确认已经安装 AWS CLI,并使用如下命令配置 AWS credentials

aws configure

步骤二:安装 AWS CDK 工具包(cdk 命令)

npm install -g aws-cdk

步骤三:从 GitHub 克隆 CDK 代码

git clone https://github.com/heqiqi/lightsail-auto-stop.git

步骤四:在代码目录下,安装所有依赖包

npm install

步骤五:使用如下命令,部署本解决方案

cdk deploy --require-approval never

部署完成后,terminal 会显示如下图所示的部署结果,包含了 Lambda 函数的名称和 SNS topic 名称。

步骤六:根据步骤五中的 SNS Topic 去 SNS Console 下添加通知接收者邮箱

至此,整个使用 CDK 的部署过程已经全部完成,客户可以在流量超出配额时,收到对应的提醒短信。

此方案的部署代码是由亚马逊云科技的合作伙伴合肥芝麻客完成开发、测试,并且在多家客户账户的生产环境内部署实施的,极大地提高了客户的运营效率和成本管理水平。安徽合肥芝麻客信息技术有限公司以云计算、大数据、物联网、人工智能技术引领创新,致力于通过技术领先的解决方案和全生命周期的 IT 咨询管理能力服务客户,为客户的数智化转型助力。

总结

我们介绍了 Lightsail 的基本功能,以及如何使用 Lambda Function 通过 API 的方式获取、计算、统计相应数据。并与 AWS 其他服务联动,达到满足客户的需求的同时,提高运营效率,降低使用成本的目的。上述大部分内容和资源都已经由  Cloud Development Kit 预置或预定义,简单操作即可开箱即用,构建安全合规的云上环境及访问方法。您也可联系亚马逊云科技专家进一步了解,助力您高效开展安全运维云上业务旅程。

本篇作者

林立武

合肥芝麻客信息有限公司技术负责人,从事 IT 行业 20 余年,在进入云计算领域之前主要从事软件开发、系统集成方面的工作。2017 年进入云计算领域投身于亚马逊云,从事系统架构工作,在计算、网络、安全方面以及 Serverless 领域有较强的经验和研究。

何奇

亚马逊云科技解决方案架构师,目前专注于企业级的云上解决方案。在加入 AWS 之前,曾就职于摩托罗拉、联想等科技公司,从事 Web 领域应用、移动应用相关的开发工作,拥有十余年技术服务经验。