この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ふと気になりました。「Lambdaのデプロイ中にAPI経由でアクセスすると、どういう動きになるんだろう?」と。
- ある時刻を境にバッチリ変わる?
- デプロイ付近で新しいLambdaと古いLambdaがランダムに実行される?
試してみました。
おすすめの方
- AWS SAMを使ってみたい方
- API GatewayとLambdaでAPIを作ってみたい方
- LambdaとDynamoDBを使ってみたい方
- API GatewayとLambdaのデプロイ中にアクセスした場合の挙動に興味がある方
ざっくり概要
APIを1つ作成し、同時アクセスします。APIの裏にいるLambdaにて受け取ったデータをDynamoDBに書き込みます。 APIアクセスを続けている状態でLambdaをデプロイ(コード変更)したとき、どのような動きになるのか?を試してみました。
まずはWebAPIを作成する
SAM Init
sam init \
--runtime python3.7 \
--name api-lambda-deploy-sample \
--app-template hello-world
SAMテンプレートファイル
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: api-lambda-deploy-sample-stack
Parameters:
Number:
Type: String
Resources:
TodoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
Timeout: 10
AutoPublishAlias: Sample
Environment:
Variables:
TABLE_NAME: !Ref TodoTable
Policies:
- arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /todo
Method: post
TodoFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${TodoFunction}
TodoTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub todo-sample-${Number}-table
AttributeDefinitions:
- AttributeName: todoId
AttributeType: S
KeySchema:
- AttributeName: todoId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Outputs:
ApiEndpoint:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/todo"
Lambdaコード
DynamoDBに書き込むデータにlambda:aaa
を含めています。新しいLambdaではこの内容を変更するため、デプロイ前後どちらのLambdaが書き込んだのかを区別できるようにしています。
app.py
import os
import json
import boto3
from datetime import datetime, timezone
dynamodb = boto3.resource('dynamodb')
table_name = os.environ['TABLE_NAME']
def lambda_handler(event, context):
payload = json.loads(event['body'])
table = dynamodb.Table(table_name)
try:
table.put_item(Item={
'todoId': payload['todoId'],
'title': payload['title'],
'createdAt': int(datetime.now(timezone.utc).timestamp() * 1000),
'lambda': 'aaa'
})
except Exception as e:
print(e)
return {
"statusCode": 500,
"body": json.dumps({
"message": "ng",
})
}
return {
"statusCode": 200,
"body": json.dumps({
"message": "ok",
})
}
AWS SAM デプロイ
下記でデプロイします。1
としている部分の数字を変更することで、スタック名を変えて同じ環境を構築できるようにしています。
sam build
sam package \
--output-template-file packaged.yaml \
--s3-bucket cm-fujii.genki-sam-test-bucket
sam deploy \
--template-file packaged.yaml \
--stack-name api-lambda-deploy-sample-1-stack \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset \
--parameter-overrides Number=1
APIエンドポイントを取得
下記コマンドでAPIエンドポイントを取得し、メモしておきます。
aws cloudformation describe-stacks \
--stack-name api-lambda-deploy-sample-1-stack \
--query 'Stacks[].Outputs'
APIアクセスし続けるスクリプトを作成
下記のPythonスクリプトを作成します。同ディレクトリにfinish.txt
が置かれるまで、APIアクセスを続けます。
API_ENDPOINT
にはAPIエンドポイントを記載します。
poster.py
import json
import os
import time
import sys
import requests
# programme finish trigger file
FINISH_FILE = 'finish.txt'
API_ENDPOINT = 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/todo'
def post(index):
count = 1
result_ok = 0
result_ng = 0
while True:
payload = get_payload(index, count)
resp = requests.post(API_ENDPOINT, data=json.dumps(payload))
if resp.status_code == 200:
result_ok += 1
else:
result_ng += 1
if is_finish():
break
time.sleep(0.3)
count += 1
print(f'ok: {result_ok}')
print(f'ng: {result_ng}')
def get_payload(index, count):
return {
'todoId': f't{index:02}-{count:04}',
'title': 'アレを買う'
}
def is_finish():
if os.path.isfile(FINISH_FILE):
return True
return False
if __name__ == "__main__":
args = sys.argv
if len(args) == 2:
post(int(args[1]))
やってみる
APIアクセスを開始する
APIアクセスするスクリプトを並列実行します。
python poster.py 1 &
python poster.py 2 &
python poster.py 3 &
Lambdaコードを修正する
下記に変更します。DynamoDBに書き込むデータについて、一部をaaa
からxxx
に変更しています。
これによって、デプロイ前後のどちらのLambdaから書き込んだのか区別できます。
app.py
import os
import json
import boto3
from datetime import datetime, timezone
dynamodb = boto3.resource('dynamodb')
table_name = os.environ['TABLE_NAME']
def lambda_handler(event, context):
payload = json.loads(event['body'])
table = dynamodb.Table(table_name)
try:
table.put_item(Item={
'todoId': payload['todoId'],
'title': payload['title'],
'createdAt': int(datetime.now(timezone.utc).timestamp() * 1000),
'lambda': 'xxx'
})
except Exception as e:
print(e)
return {
"statusCode": 500,
"body": json.dumps({
"message": "ng",
})
}
return {
"statusCode": 200,
"body": json.dumps({
"message": "ok",
})
}
デプロイする
sam build
sam package \
--output-template-file packaged.yaml \
--s3-bucket cm-fujii.genki-sam-test-bucket
sam deploy \
--template-file packaged.yaml \
--stack-name api-lambda-deploy-sample-1-stack \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset \
--parameter-overrides Number=1
デプロイ完了後、APIアクセスを停止する
touch finish.txt
結果
- デプロイ付近で新しいLambdaと古いLambdaがランダムに実行される
スクリプト(APIアクセス)の結果
デプロイ途中でも、APIアクセス自体は成功しました。
回数 | ok | ng |
---|---|---|
1回目 | 252 | 0 |
2回目 | 280 | 0 |
3回目 | 274 | 0 |
CloudWatch Logsの様子
複数のLambdaが同時実行されました。
1回目
2回目
3回目
DynamoDBテーブルの様子
DynamoDBテーブルから全データを取得し、時刻順にソートしました。デプロイ時刻付近で新しいLambdaと古いLambdaが混在実行されていることが分かります。
1回目抜粋
↑aaaのみ
1598499496969: t03-0056, aaa
1598499497006: t02-0055, aaa
1598499497117: t01-0056, aaa
1598499497417: t02-0056, aaa
1598499497427: t03-0057, aaa
1598499498051: t01-0057, xxx
1598499498363: t03-0058, aaa
1598499498399: t02-0057, xxx
1598499498694: t01-0058, xxx
1598499499022: t03-0059, aaa
1598499499047: t02-0058, xxx
1598499499119: t01-0059, aaa
1598499499471: t03-0060, xxx
1598499499482: t02-0059, aaa
1598499499579: t01-0060, xxx
1598499499882: t02-0060, xxx
1598499499902: t03-0061, xxx
1598499500008: t01-0061, xxx
1598499500319: t02-0061, xxx
1598499500321: t03-0062, xxx
↓xxxのみ
2回目抜粋
↑aaaのみ
1598519657408: t01-0062, aaa
1598519657743: t02-0063, aaa
1598519657809: t03-0063, aaa
1598519658397: t01-0063, yyy
1598519658701: t02-0064, yyy
1598519658727: t03-0064, yyy
1598519659065: t01-0064, yyy
1598519659375: t02-0065, yyy
1598519659389: t03-0065, yyy
1598519659497: t01-0065, yyy
1598519659811: t02-0066, yyy
1598519659825: t03-0066, yyy
1598519659934: t01-0066, yyy
1598519660249: t02-0067, yyy
1598519660262: t03-0067, yyy
1598519660367: t01-0067, yyy
1598519660666: t02-0068, yyy
1598519660825: t01-0068, yyy
1598519661101: t02-0069, yyy
1598519661217: t03-0068, aaa
1598519661262: t01-0069, yyy
1598519661549: t02-0070, yyy
1598519661715: t01-0070, yyy
1598519661860: t03-0069, yyy
1598519661998: t02-0071, aaa
1598519662138: t01-0071, yyy
1598519662287: t03-0070, yyy
1598519662463: t02-0072, yyy
1598519662566: t01-0072, yyy
1598519662746: t03-0071, yyy
1598519662920: t02-0073, yyy
1598519663000: t01-0073, yyy
1598519663201: t03-0072, yyy
1598519663321: t02-0074, yyy
1598519663427: t01-0074, yyy
1598519663642: t03-0073, yyy
1598519663766: t02-0075, aaa
1598519663866: t01-0075, yyy
1598519664101: t03-0074, yyy
1598519664221: t02-0076, yyy
1598519664323: t01-0076, yyy
1598519664540: t03-0075, yyy
1598519664637: t02-0077, yyy
1598519664759: t01-0077, yyy
1598519664957: t03-0076, aaa
1598519665054: t02-0078, yyy
1598519665210: t01-0078, yyy
1598519665403: t03-0077, yyy
1598519665482: t02-0079, yyy
1598519665643: t01-0079, yyy
1598519665846: t03-0078, yyy
1598519665910: t02-0080, aaa
1598519666080: t01-0080, yyy
1598519666294: t03-0079, yyy
1598519666366: t02-0081, yyy
↓yyyのみ
3回目抜粋
↑aaaのみ
1598519930276: t03-0062, aaa
1598519930390: t02-0063, aaa
1598519931209: t03-0063, zzzzz
1598519931261: t01-0064, zzzzz
1598519931317: t02-0064, aaa
1598519931858: t03-0064, zzzzz
1598519931928: t01-0065, zzzzz
1598519931995: t02-0065, aaa
1598519932322: t03-0065, zzzzz
1598519932358: t01-0066, zzzzz
1598519932425: t02-0066, zzzzz
1598519932764: t03-0066, zzzzz
1598519932815: t01-0067, zzzzz
1598519932873: t02-0067, zzzzz
1598519933195: t03-0067, zzzzz
1598519933280: t01-0068, zzzzz
1598519933411: t02-0068, zzzzz
1598519933651: t03-0068, aaa
1598519933734: t01-0069, zzzzz
1598519933854: t02-0069, zzzzz
1598519934090: t03-0069, zzzzz
1598519934177: t01-0070, zzzzz
1598519934332: t02-0070, zzzzz
1598519934541: t03-0070, zzzzz
1598519934629: t01-0071, zzzzz
1598519934771: t02-0071, aaa
1598519934994: t03-0071, zzzzz
1598519935081: t01-0072, zzzzz
1598519935200: t02-0072, zzzzz
1598519935435: t03-0072, zzzzz
1598519935537: t01-0073, zzzzz
1598519935667: t02-0073, zzzzz
1598519935859: t03-0073, zzzzz
1598519935980: t01-0074, zzzzz
1598519936091: t02-0074, zzzzz
1598519936311: t03-0074, zzzzz
1598519936407: t01-0075, zzzzz
1598519936540: t02-0075, zzzzz
1598519936739: t03-0075, zzzzz
1598519936897: t01-0076, zzzzz
1598519936967: t02-0076, zzzzz
1598519937177: t03-0076, zzzzz
1598519937306: t01-0077, zzzzz
1598519937431: t02-0077, zzzzz
1598519937608: t03-0077, zzzzz
1598519937775: t01-0078, zzzzz
1598519937884: t02-0078, zzzzz
1598519938056: t03-0078, aaa
1598519938194: t01-0079, zzzzz
1598519938354: t02-0079, zzzzz
1598519938511: t03-0079, zzzzz
↓zzzzzのみ
(3回目にして、文字数を変えると分かりやすいことに気づきました……)
さいごに
「デプロイ付近で新しいLambdaと古いLambdaがランダムに実行される」という結果になりました。 だからどうだというわけではないですが、なにかの際に役立つかもしれません。