アクセスが多い場合は、API GatewayよりもALB(Application Load Balancer)経由の方がコストは抑えられます。API Gatewayがリクエスト数に対して、ALBは時間+トラフィックの為。
また、キャパ(捌ける数)もALBの方が多いので、API Gatewayスタートでスケールし始めてからALBに切り替えるのが良さそう。
API Gateway+LambdaでDynamoDB(MediaConvertの情報)を返す で作成したLambdaをターゲットにALB経由でアクセスできるようにしてみました。Lambdaは少し改修が必要です。
Saving Money By Replacing API Gateway With Application Load Balancer’s Lambda Integration · Serverless Training by Jeremy Thomerson
Amazon API Gateway のクォータと重要な注意点 – Amazon API Gateway
Application Load Balancer のクォータ – Elastic Load Balancing
セキュリティグループ作成
https://ap-northeast-1.console.aws.amazon.com/ec2/home?region=ap-northeast-1#CreateSecurityGroup
セキュリティグループを作成 基本的な詳細 セキュリティグループ名: AllowHttpGroup 説明: Allow HTTP/HTTPS security group HTTP/HTTPS インバウンドルール [ルールを追加] タイプ: HTTP ソース: AnywhereIPv4 = 0.0.0.0/0 [ルールを追加] タイプ: HTTP ソース: AnywhereIPv4 [ルールを追加] タイプ: HTTPS ソース: AnywhereIPv6 = ::/0 [ルールを追加] タイプ: HTTPS ソース: AnywhereIPv6 アウトバウンドルール [ルールを追加] タイプ: すべてのトラフィック ソース: AnywhereIPv6 [セキュリティグループを作成]
ターゲットグループ作成
https://ap-northeast-1.console.aws.amazon.com/ec2/home?region=ap-northeast-1#CreateTargetGroup:
※mediaConvertInputFileToJobIdApiとmediaConvertJobIdToStatusApiの2つ作成
Specify group details Basic configuration Choose a target type ●Lambda function Target group name mediaConvertInputFileToJobIdApi / mediaConvertJobIdToStatusApi [Next] Lambda function Choose a Lambda function from the list or mediaConvertInputFileToJobIdApi / mediaConvertJobIdToStatusApi [Create target group]
ロードバランサー作成
https://ap-northeast-1.console.aws.amazon.com/ec2/home?region=ap-northeast-1#SelectCreateELBWizard:
Select load balancer type Application Load Balancer [Create] Create Application Load Balancer Basic configuration Load balancer name: api-lb Network mapping Mappings ■ap-northeast-1a ■ap-northeast-1c ■ap-northeast-1d Security groups default [×(削除)] AllowHttpGroup ※上記で作成したセキュリティグループ Listeners and routing Protocol: HTTPS Default action: mediaConvertInputFileToJobIdApi Listeners and routing Default SSL/TLS certificate *.nightonly.com ※作成済みの証明書を選択 [Create load balancer] Create Application Load Balancer [View load balancer] [アクション][属性の編集] ロードバランサーの属性の編集 削除保護: ■有効化 アクセスログ: ■有効化 S3の場所: logs-api-lb.nightonly.com ※新しいバケット名 ■この場所の作成 [保存]
DNS設定
Route53または使用しているDNSサーバーに設定。
CNAME api-lb.nightonly.com api-lb-1445519200.ap-northeast-1.elb.amazonaws.com

動作確認
% nslookup api-lb.nightonly.com api-lb.nightonly.com canonical name = api-lb-1445519200.ap-northeast-1.elb.amazonaws.com. Name: api-lb-1445519200.ap-northeast-1.elb.amazonaws.com Address: 3.112.139.157 Name: api-lb-1445519200.ap-northeast-1.elb.amazonaws.com Address: 54.95.69.24 Name: api-lb-1445519200.ap-northeast-1.elb.amazonaws.com Address: 52.192.148.61
ルール編集
リスナー
[ルールの表示/編集]
api-lb | HTTPS:443
[+(追加)]
[ルールの挿入]
ルールID: 1
パス: /media/job_id
転送先: mediaConvertInputFileToJobIdApi
[保存]
[ルールの挿入]
ルールID: 2
パス: /media/status
転送先: mediaConvertJobIdToStatusApi
[保存]
[鉛筆(変更)]
ルールID: 最後
転送先: mediaConvertInputFileToJobIdApi -> [削除]
固定レスポンスを返す
レスポンスコード: 404
Content-Type: application/json
レスポンス本文: {"message":"Not Found"}
[更新]
[<(戻る)]
動作確認
・https://api-lb.nightonly.com/
{"message":"Not Found"}
・https://api-lb.nightonly.com/media/job_id
・https://api-lb.nightonly.com/media/status
502 Bad Gateway
・https://api-lb.nightonly.com/media/job_id?input_file=Big%20Buck%20Bunny.mp4
・https://api-lb.nightonly.com/media/status?job_id=c123456789012-smb6o7
502 Bad Gateway
エラーになってしまいましたが、CloudWatch LogsにLambdaのログが出ていたので、疎通はOK。
リスナー追加
http:// の場合に https:// にリダイレクトするようにしておきます。
リスナー [リスナーの追加] Listener details Default actions Redirect Port: 443 [Add] [View listeners]
動作確認
・http://api-lb.nightonly.com/
→ https://api-lb.nightonly.com/
{"message":"Not Found"}
・http://api-lb-1445519200.ap-northeast-1.elb.amazonaws.com
→ https://api-lb-1445519200.ap-northeast-1.elb.amazonaws.com
証明書エラー → 普通には使えないのでOK
・http://api-lb.nightonly.com/media/job_id
→ https://api-lb.nightonly.com/media/job_id
502 Bad Gateway
SSLリダイレクトしているのでOK
Lambdaコード(API GatewayとALBの違い)
ALB経由かの判断は、requestContext → elbが存在するかで出来る。
1. メソッドの場所が違う
API Gateway: requestContext → http → method
ALB: httpMethod
mediaConvertInputFileToJobIdApi, mediaConvertJobIdToStatusApi
+ if 'elb' in event['requestContext']:
+ method = event['httpMethod']
+ else:
method = event['requestContext']['http']['method']
2. パラメータがデコードされない
/media/job_id?input_file=Big%20Buck%20Bunny.mp4 の例
API Gateway: Big Buck Bunny.mp4
ALB: Big+Buck+Bunny.mp4
mediaConvertInputFileToJobIdApi
input_file = event['queryStringParameters']['input_file']
+ if 'elb' in event['requestContext']:
+ input_file = urllib.parse.unquote_plus(input_file)
mediaConvertJobIdToStatusApi ※job_idは半角英数・ハイフンのみっぽいけど念の為
job_id = event['queryStringParameters']['job_id']
+ if 'elb' in event['requestContext']:
+ job_id = urllib.parse.unquote_plus(job_id)
3. headersに数値型を渡すと「502 Bad Gateway」になる
ログにエラー内容が出ないので気付きにくかったですが、数値で渡すとエラーになってしまいます。
mediaConvertInputFileToJobIdApi, mediaConvertJobIdToStatusApi
headers = { # Tips: API GatewayのCORS設定が効かない為
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': '*', # Tips: ChromeでCORSエラーになる為
- 'Access-Control-Max-Age': 7200,
+ 'Access-Control-Max-Age': '7200', # Tips: LB経由で数値型を渡すと「502 Bad Gateway」になる
'Content-Type': 'application/json; charset=UTF-8'
}
Lambdaコード(最終版)
最後にそれぞれのコードを記載しておきます。
似た所が多いので共通化したい所ですが。。。難しいかな?
mediaConvertInputFileToJobIdApi
最新のコードこちら → https://dev.azure.com/nightonly/_git/lambda-origin?path=/mediaConvertInputFileToJobIdApi/lambda_function.py
import boto3
import os
import re
import urllib.parse
import json
dynamodb = boto3.resource('dynamodb')
table_to_job_id = dynamodb.Table('MediaConvertInputFileToJobId')
ALLOWED_ORIGINS = os.environ['ALLOWED_ORIGINS']
headers = { # Tips: API GatewayのCORS設定が効かない為
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': '*', # Tips: ChromeでCORSエラーになる為
'Access-Control-Max-Age': '7200', # Tips: LB経由で数値型を渡すと「502 Bad Gateway」になる
'Content-Type': 'application/json; charset=UTF-8'
}
def lambda_handler(event, context):
print(event)
# メソッドチェック
if 'elb' in event['requestContext']:
method = event['httpMethod']
else:
method = event['requestContext']['http']['method']
if method == 'OPTIONS':
return option_response()
elif method != 'GET':
return error_response(404, 'No route matches.')
# オリジンチェク # Tips: 存在する場合のみ
if 'origin' in event['headers']:
origin = event['headers']['origin']
if origin != '' and origin != None:
allowd_origins = ALLOWED_ORIGINS.split()
print({ 'origin': origin, 'allowd_origins': allowd_origins })
allowd = False
for allowd_origin in allowd_origins:
if re.fullmatch(allowd_origin, origin):
allowd = True
break
if not allowd:
return error_response(422, 'ORIGIN is invalid.')
# パラメータチェック
if 'queryStringParameters' not in event:
return error_response(400, 'Not found parameter.')
elif 'input_file' not in event['queryStringParameters']:
return error_response(400, 'Not found parameter input_file.')
input_file = event['queryStringParameters']['input_file']
if 'elb' in event['requestContext']:
input_file = urllib.parse.unquote_plus(input_file)
print({ 'input_file': input_file })
if input_file == '' or input_file == None:
return error_response(422, 'Not exist parameter input_file.')
# DynamoDBから出力情報取得
response = table_to_job_id.get_item(
Key = {
'InputFile': input_file
}
)
print(response)
if 'Item' not in response:
return error_response(422, 'Not found item.')
return success_response(response['Item']['JobId'])
def option_response():
return { 'statusCode': 200, 'headers': headers }
def error_response(code, message):
return { 'statusCode': code, 'headers': headers, 'body': json.dumps({ 'success': False, 'message': message }) }
def success_response(job_id):
return { 'statusCode': 200, 'headers': headers, 'body': json.dumps({ 'success': True, 'job_id': job_id }) }
mediaConvertJobIdToStatusApi
最新のコードこちら → https://dev.azure.com/nightonly/_git/lambda-origin?path=/mediaConvertJobIdToStatusApi/lambda_function.py
import boto3
import os
import re
import urllib.parse
import json
dynamodb = boto3.resource('dynamodb')
table_to_status = dynamodb.Table('MediaConvertJobIdToStatus')
ALLOWED_ORIGINS = os.environ['ALLOWED_ORIGINS']
headers = { # Tips: API GatewayのCORS設定が効かない為
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': '*', # Tips: ChromeでCORSエラーになる為
'Access-Control-Max-Age': '7200', # Tips: LB経由で数値型を渡すと「502 Bad Gateway」になる
'Content-Type': 'application/json; charset=UTF-8'
}
def lambda_handler(event, context):
print(event)
# メソッドチェック
if 'elb' in event['requestContext']:
method = event['httpMethod']
else:
method = event['requestContext']['http']['method']
if method == 'OPTIONS':
return option_response()
elif method != 'GET':
return error_response(404, 'No route matches.')
# オリジンチェク # Tips: 存在する場合のみ
if 'origin' in event['headers']:
origin = event['headers']['origin']
if origin != '' and origin != None:
allowd_origins = ALLOWED_ORIGINS.split()
print({ 'origin': origin, 'allowd_origins': allowd_origins })
allowd = False
for allowd_origin in allowd_origins:
if re.fullmatch(allowd_origin, origin):
allowd = True
break
if not allowd:
return error_response(422, 'ORIGIN is invalid.')
# パラメータチェック
if 'queryStringParameters' not in event:
return error_response(400, 'Not found parameter.')
elif 'job_id' not in event['queryStringParameters']:
return error_response(400, 'Not found parameter job_id.')
job_id = event['queryStringParameters']['job_id']
if 'elb' in event['requestContext']:
job_id = urllib.parse.unquote_plus(job_id)
print({ 'job_id': job_id })
if job_id == '' or job_id == None:
return error_response(422, 'Not exist parameter job_id.')
# DynamoDBからステータス取得
response = table_to_status.get_item(
Key = {
'JobId': job_id
}
)
print(response)
if 'Item' not in response:
return error_response(422, 'Not found item.')
return success_response(response['Item'])
def option_response():
return { 'statusCode': 200, 'headers': headers }
def error_response(code, message):
return { 'statusCode': code, 'headers': headers, 'body': json.dumps({ 'success': False, 'message': message }) }
def success_response(item):
job_status = item['JobStatus']
result = { 'success': True, 'job_status': job_status, 'progress_rate': int(item['ProgressRate']) }
info = {}
if job_status == 'COMPLETE':
info = { 'duration_ms': int(item['DurationMs']), 'width_px': int(item['WidthPx']), 'height_px': int(item['HeightPx']),
'output_path': item['OutputPath'], 'playlists': item['Playlists'] }
elif job_status == 'ERROR':
info = { 'error_code': int(item['ErrorCode']), 'error_message': item['ErrorMessage'] }
return { 'statusCode': 200, 'headers': headers, 'body': json.dumps({**result, **info}) }

“ALB+LambdaでDynamoDB(MediaConvertの情報)を返す” に対して1件のコメントがあります。