AWS, Python

API Gateway + Lambda tagを指定してEC2の起動・停止 | AutoScaling Groupのインスタンス数を変更 Python3.9

AWS

API Gateway + Lambda開発のコツ

 

タイムアウト

 

処理時間の3倍程度をLambda設定のタイムアウトに設定する。

10秒を設定

 

ログ出し

ログがないとまともな開発はできない

  1. cloudWatchにIAMロールでCloudWatch Logsのアクセスができるロールを作成
  2. API Gatewayの設定でarnを指定する
  3. API Gatewayの設定でログを有効化させる
  4. 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

 

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)