一、背景
Amazon Lightsail轻量服务器,算是在众多大厂服务器中比较划算的一款,3.5美刀一个月就可以获得这样的配置:
| 12
 3
 4
 
 | 512 MB Memory2 vCPUs Processing
 20 GB SSD Storage
 1 TB Transfer
 
 | 
还可以通过吃码的方式获取更优惠的价格。
但是超出流量限额后,每GB流量0.12美金,加上Lightsail的带宽最高可达4Gpbs,如果不注意流量超出限额后继续使用就会很容易产生巨额的账单。
所以,需要找一个方式,超过流量限额后,停止机器的使用,以免产生巨额账单。
二、大致思路
利用Amazon的Lambda函数计算(100W次以内免费),配合Amazon提供的官方Lightsail API,设置定时任务,每10分钟获取当前流量限额和已使用流量,进行对比,如果达到限额的95%,则关闭Lightsail实例:
- 利用 Lightsail 的 API 接口:get_instance,获取账号下在当前区域里的所有 Lightsail 实例。
- 根据 Lightsail 实例的类型,获取每个实例每个月的网络流量配额。
- 根据实例的创建时间,计算出每个实例在当前这个计费周期内的流量配额。
- 通过 API 接口:get_instance_metric_data,获取每个实例已经使用的入站和出站流量总量。
- 如果流量超出当前计费周期的配额的95%,并关闭对应的 Lightsail 实例。
- 通过 EventBridge 以 cron job 的方式定时触发 Lambda,运行此检查逻辑。
三、过程
1. 创建Lambda函数
在AWS控制台进入Lambda函数页面,创建新函数:


Python脚本:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 
 | import jsonimport boto3
 import calendar
 import os
 from datetime import datetime, date, time,timedelta
 
 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')
 
 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
 )
 
 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) * 0.95) < int(total):
 print("(quota * 0.95) < total, soforce close instance: {}".format(1))
 stop_instance(i)
 
 return {
 'statusCode': 200,
 'body': json.dumps('total_data_usage from Lambda!')
 }
 
 
 | 
2. 修改函数运行配置
在之前创建的Lambda函数页面,进入配置——常规配置——编辑:

调大内存和超时时间:

3. 赋予Lambda权限
在之前创建的Lambda函数页面,进入配置——权限——点击链接跳转至IAM权限管理页面:

创建新的策略:

选择JSON,粘贴数据,保存

JSON数据:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | {"Version": "2012-10-17",
 "Statement": [
 {
 "Action": [
 "lightsail:GetInstance",
 "lightsail:GetInstanceMetricData",
 "lightsail:GetInstances",
 "lightsail:StopInstance"
 ],
 "Effect": "Allow",
 "Resource": "*"
 }
 ]
 }
 
 | 
策略创建完成后关闭IAM权限页面,回到Lambda函数页面,测试是否成功:

4. 创建定时任务触发Lambda函数
在Lambda函数页面点击添加触发器:

选择EventBridge,增加定时任务,保存:
表达式:cron(0/10 * * * ? *)

这时就已经基本完成配置了。
Lambda函数每10分钟执行一次,符合条件就会自动关闭实例。
5. 查看历史执行记录
在Lambda函数页面,选择监控,点击查看CloudWatch Logs跳转至日志页面。

选择日志就可以看到日志的详细信息了:

这样就可以查看历史的执行结果了。
后续可以再加入Amazon的订阅通知服务,关机时自动发送邮件给指定邮箱。