使用AWS Lambda为Lightsail流量超额发送提醒和自动关机。

bloade 发布于 2025-12-27 322 次阅读


曾经用超了Lightsail的套餐流量,代价甚是高昂。不过后来发现亚马逊的官方博客上其实有提供一个使用Lambda监控流量使用额度,超过设置时自动关机并通过SNS(Simple Notification Service)发送电子邮件提醒的教程。

Lambda自身有免费使用额度,而SNS通知按照使用量计费,价格并不高。邮件通知只会在Lightsail流量超过设置额度时才会发送,因此总的来说要比支付高昂的超额流量费要划算的多。当然,这个邮件通知也并非必要的,不需要的话完全可以注释掉其中有关邮件通知的内容。

虽然官方的教程大部分地方都足够详细,不过有少量几个说的不太清,比较容易让人不知道怎么办的地方。

区域

为了确保Lambda、Lightsail和SNS Topic都能互相通信,这三个东西最好都在同一个区域创建。比如先在us-east-1创建了Lightsail,那另外两个也跟着一起在Lightsail同样的区域中创建。us-west-2或者其他区域也一样。

创建SNS Topic(主题)

官方教程只提到了要创建一个Topic,但是并未告知在哪里创建。当然,在亚马逊的文档里指出了应该在SNS控制台中创建。至于剩下的,只要按照官方教程,类型选择“标准”,然后取个名字就行了,其余设置项保持默认即可。

不过,如果你不需要邮件通知,那这个步骤完全可以跳过。

创建Lambda函数

可以参考Lambda的文档,不过实际上在AWS控制台主页搜索Lambda也能找到Lambda的页面了。然后点击创建函数即可。

接下来参考官方教程,起一个名字,运行时选择Python 3.12,架构选择x86_64就可以了。

编辑Lambda要执行的代码

首先,用请用Chrome或者同内核的浏览器,因为这个Lambda的代码编辑器在其他内核浏览器中有可能不能用。

如果你想按照官方教程使用SNS提醒的话,直接将官方教程的Python脚本代码粘贴进去就行:

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!')
    }

如果你打算做些调整,比如不使用SNS发送邮件提醒,可以注释第7行与第105行;如果你想要降低触发阈值,可以修改第103和第104行为:

        if (int(quota) * 阈值) < int(total):
            print("(quota * 阈值) < total, soforce close instance: {}".format(1))

这里阈值为小数表示,如希望在流量超过90%时关机,那可以改为:

        if (int(quota) * 0.9) < int(total):
            print("(quota *0.9) < total, soforce close instance: {}".format(1))

当Python脚本设置好后,点击Deploy部署即可。

修改Lambda的运行配置和权限配置

修改运行配置很简单,编点击编辑器上面的配置选项卡,选择常规配置,然后照着教程修改配置即可。教程推荐的修改的配置是1024MB内存和10分钟超时时间,对于这段脚本来说其实资源是过多的,改成512MB内存与3分钟超时时间也没有问题。

如果你不使用SNS通知那可以直接看下一段。首先打开SNS的页面,找到之前创建的主题,复制它的ARN。然后在刚刚的Lambda函数的配置选项卡下选择环境变量。在其中创建键为SNS_TOPIC,值为刚才复制的ARN的环境变量。

接下来选择左侧的权限,然后点击角色名称下面的蓝色角色名称连接。在点击权限策略区域的添加权限,然后选择创建内联策略,参考如下内容修改:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
			    "lightsail:GetInstance",
			    "lightsail:GetInstanceMetricData",
			    "lightsail:GetInstances",
			    "lightsail:StopInstance"
			    ],
			"Resource": "*"
		},
		{
		    "Effect": "Allow",
		    "Action": "sns:Publish",
		    "Resource": "粘贴刚才的sns arn到这里"
		}
	]
}

当然,不用邮件通知的话,那么

		{
		    "Effect": "Allow",
		    "Action": "sns:Publish",
		    "Resource": "粘贴刚才的sns arn到这里"
		}

和它们上一行的逗号都去掉就可以了。接下来点击下一步,给权限配置取个名字,然后完成即可。

设置定时触发

回到Lambda的页面,然后选择配置选项卡中的触发器,点击添加触发器。触发器的源选择EventBridge (CloudWatch Events) ,然后选择创建新规则,为规则取一个名字。接下来在规则类型中选择计划表达式,使用cron或rate格式填写触发时间。比如想让他每15分钟触发一次,那填写cron(0/15 * * * ? *),最后点击添加即可。

设置SNS通知邮箱

再次回到之前的SNS页面,选择刚刚创建的主题,在订阅选项卡中点击创建订阅。在创建页面中,协议选择电子邮件,终端节点则填写你想要接收通知的邮箱。当创建订阅后,之前填入的邮箱会收到一封确认邮件,确认之后就设置好了。

参考

使用 Lambda 监控 Amazon Lightsail 数据流量

【教程】使用AWS Lambda 监控 AWS Lightsail 流量限额,超额自动关机