もくじ
API Gateway + Lambda開発のコツ
タイムアウト
処理時間の3倍程度をLambda設定のタイムアウトに設定する。
10秒を設定
ログ出し
ログがないとまともな開発はできない
- cloudWatchにIAMロールでCloudWatch Logsのアクセスができるロールを作成
- API Gatewayの設定でarnを指定する
- API Gatewayの設定でログを有効化させる
- CloudWatch Logsからログを見ながら開発する
最初はLambdaで直開発
Lambda側で動作確認できてから、API Gatewayで動かすと開発しやすい
eventが取得できない?
①API GatewayからLambdaにリクエストボディを投げる際は加工する処理が必要
event = json.loads(event['body'])
②Lambda プロキシ統合の使用にチェックが必要
リクエストボディ
devを起動
{"region": "ap-northeast-1","action": "start","app_env": "dev"}
devを停止
{"region": "ap-northeast-1","action": "stop","app_env": "dev"}
事前準備
EC2
- dev
IsDevEc2AutoStartStop - stg
IsStgEc2AutoStartStop
それぞれValueはtrueとする
AutoScalingGroup
- dev
IsDevAutoScalingGroupAutoStartStop - stg
IsStgAutoScalingGroupAutoStartStop
それぞれValueはtrueとする
Lambdaに設定するIAM ロールに設定するポリシー
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "ec2:StartInstances", "ec2:StopInstances", "ec2:DescribeTags", "ec2:Describe*" ], "Effect": "Allow", "Resource": "*" }, { "Effect": "Allow", "Action": "cloudwatch:*", "Resource": "*" }, { "Effect": "Allow", "Action": [ "events:DisableRule", "events:EnableRule", "events:DescribeRule" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "autoscaling:UpdateAutoScalingGroup", "autoscaling:Describe*" ], "Resource": "*" }, { "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" }, { "Sid": "IAMPassRoleForCloudWatchEvents", "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::*:role/AWS_Events_Invoke_Targets" } ] }
API Gatewayに設定するロール
●ロール名
ApiGatewayCloudWatchLogs
●用途
API GatewayのログをCloudWatch Logsに出力するロール
●ポリシー(既存)
AmazonAPIGatewayPushToCloudWatchLogs
Lambda Python3.9
import json import boto3 import botocore import traceback def lambda_handler(event: dict, context: object): headers = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } try: # Lambda直, API Gateway経由の2パターンがあるので調整 if 'body' in event.keys(): # API Gateway経由の場合はbodyキーに格納されているので変換 event = json.loads(event['body']) if _validation(event): region = event['region'] action = event['action'] app_env = event['app_env'] else: message = 'validation error: 必須キーが抜けています' return { "statusCode": 422, 'body': json.dumps(message) } app_env = app_env.capitalize() # dev -> Dev tag = 'Is' + app_env + 'Ec2AutoStartStop' # ex. IsDevEc2AutoStartStop | IsStgEc2AutoStartStop # bot3インスタンス生成 client = boto3.client('ec2', region) # タグで指定してEC2インスタンス情報を取得 responce = client.describe_instances(Filters=[{'Name': 'tag:' + tag, "Values": ['true']}]) target_instans_ids = [] for reservation in responce['Reservations']: for instance in reservation['Instances']: print('start instance') print(instance['InstanceId']) print(instance['State']['Name']) print('end instance') if action == 'start' and instance['State']['Name'] == 'stopped': target_instans_ids.append(instance['InstanceId']) elif action == 'stop' and instance['State']['Name'] == 'running': target_instans_ids.append(instance['InstanceId']) if not target_instans_ids: message = 'There are no target_instans_ids. do nothing.' return { "statusCode": 404, 'headers': headers, 'body': json.dumps(message) } if action == 'start': # EC2の起動 client.start_instances(InstanceIds=target_instans_ids) # AutoScalig Groupの台数を変更 if _updateAutoScalingGroups(action, app_env, region) == False: raise Exception print('started instances.') elif action == 'stop': client.stop_instances(InstanceIds=target_instans_ids) if _updateAutoScalingGroups(action, app_env, region) == False: raise Exception print('stopped instances.') else: print('Invalid action.') message = 'Success' return { "statusCode": 200, 'headers': headers, 'body': json.dumps(message) } except: message = traceback.format_exc() return { 'statusCode': 500, 'headers': headers, 'body': json.dumps(message) } # バリデーション def _validation(event: dict) -> bool: if 'region' in event.keys() and 'action' in event.keys() and 'app_env' in event.keys(): return True else: return False # 起動設定の更新 def _updateAutoScalingGroups(action: str, app_env: str, region: str) -> bool: print('start _updateAutoScalingGroups()') # 起動する場合の最小, 要求数 start_param = dict(MinSize=1, DesiredCapacity=1) # 停止する場合の最小, 要求数 stop_param = dict(MinSize=0, DesiredCapacity=0) autoscaling = boto3.client("autoscaling", region) response = autoscaling.describe_auto_scaling_groups() tag = 'Is' + app_env + 'AutoScalingGroupAutoStartStop' # ex. IsDevAutoScalingGroupAutoStartStop | IsStgAutoScalingGroupAutoStartStop all_autoscaling_groups = response['AutoScalingGroups'] auto_scaling_names = [] auto_scaling_names = _getAutoScalingGroupNames(all_autoscaling_groups, tag) if not auto_scaling_names: print('There are no auto_scaling_names. do nothing') return try: if action == 'start': for i in range(len(auto_scaling_names)): print('start auto_scaling_name') print(auto_scaling_names[i]) print('end auto_scaling_name') autoscaling.update_auto_scaling_group(AutoScalingGroupName=auto_scaling_names[i], **start_param) elif action == 'stop': for i in range(len(auto_scaling_names)): print('start auto_scaling_name') print(auto_scaling_names[i]) print('end auto_scaling_name') autoscaling.update_auto_scaling_group(AutoScalingGroupName=auto_scaling_names[i], **stop_param) else: print('Invalid action.') return True; except: print(traceback.format_exc()) return False; # AutoScaling名取得 def _getAutoScalingGroupNames(all_autoscaling_groups: list, tag: str) -> list[str]: print('start _getAutoScalingGroupNames()') auto_scaling_names = [] for i in range(len(all_autoscaling_groups)): all_tags = all_autoscaling_groups[i]['Tags'] for j in range(len(all_tags)): if all_tags[j]['Key'] == tag and all_tags[j]['Value'] == 'true': auto_scaling_names.append(all_autoscaling_groups[i]['AutoScalingGroupName']) return auto_scaling_names
上記のLambdaを叩く時のスケジューラの関数
import json import boto3 def lambda_handler(event, context): headers = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } try: # Lambda直, API Gateway経由の2パターンがあるので調整 if 'body' in event.keys(): # API Gateway経由の場合はbodyキーに格納されているので変換 event = json.loads(event['body']) if _validation(event): region = event['region'] action = event['action'] rules = event['rules'] else: message = 'validation error: 必須キーが抜けています' return { "statusCode": 422, 'headers': headers, 'body': json.dumps(message) } client = boto3.client('events', region) for i in range(len(rules)): # 無効 if action == 'disable': response = client.disable_rule( Name = rules[i], ) # 有効 elif action == 'enable': response = client.enable_rule( Name = rules[i], ) else: print('Invalid action.') raise Exception # TODO implement return { 'statusCode': 200, 'headers': headers, 'body': json.dumps('Success') } except: message = traceback.format_exc() return { 'statusCode': 500, 'headers': headers, 'body': json.dumps(message) } # バリデーション def _validation(event: dict) -> bool: if 'region' in event.keys() and 'action' in event.keys() and 'rules' in event.keys(): return True else: return False