はじめに
この記事はOptimind Advent Calendar 2023の22日目の記事となります。
概要
最近NATゲートウェイなどを経由した(AWS Lambdaなどの)同期通信が350秒程度で通信が切断されてしまうということを知りました(詳しくは以下の記事を参考にしてください)。なので、AWS LambdaやEC2を建ててみて実際にタイムアウトするかを簡単に確認してみたいと思います。
実験
まずAWS LambdaからAWS Lambdaとの同期通信を試してみます。次に、Private Subnetの中に建てたEC2からAWS Lambdaとの同期通信をおこなってみます。
実験のための簡単なプログラムをGPT-4-sanに伺いながら作成しました。lambda1はlambda2との同期通信用のプログラムで、lambda2はsleepするだけの処理をしています。
import json
import boto3
from botocore.config import Config
def lambda_handler(event, context):
config = Config(region_name="ap-northeast-1", read_timeout=900, connect_timeout=900)
client = boto3.client('lambda', config=config)
function_name = 'lambda2'
# Lambda関数の実行
try:
response = client.invoke(
FunctionName=function_name,
InvocationType='RequestResponse',
Payload=json.dumps({})
)
ret = response['Payload'].read()
print (ret)
except Exception as e:
print (e)
return {
'statusCode': 200,
'body': ret
}
import json
import time
def lambda_handler(event, context):
time.sleep(360)
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
AWS Lambda(lambda1)からAWS Lambda(lambda2)を呼び出したときは以下のような結果を得ることができました。360秒のsleepでも問題なく接続できています。
b'{"statusCode": 200, "body": "\\"Hello from Lambda!\\""}'
Private Subnet内のEC2からLambdaを呼び出してみる
EC2からlambda2を呼び出すためのプログラムを作成します。
import json
import boto3
import time
from botocore.config import Config
conf = Config(region_name="ap-northeast-1", read_timeout=400, connect_timeout=400, retries={"mode": "standard", "total_max_attempts": 1})
client = boto3.client('lambda', config=conf)
function_name = 'lambda2'
# Lambda関数の実行
start = time.time()
try:
response = client.invoke(
FunctionName=function_name,
InvocationType='RequestResponse',
Payload=json.dumps({})
)
print(response['Payload'].read())
except Exception as e:
print (e)
print ('elapsed time: ', time.time() - start)
sleep時間が10秒のときの実行は問題なく終了しました。意図通りに実行していそうです。
[ec2-user@ip-xx~]$ python3 invoke_lambda.py
b'{"statusCode": 200, "body": "\\"Hello from Lambda!\\""}'
elapsed time: 10.290184497833252
早速sleep時間が360秒のときを見てみます。対応するlambda2のログのREPORTを見ると360秒で終了しています。readTimeoutの設定時間は400秒にしているため、タイムアウト設定値内に終わっていそうです。
REPORT RequestId: xxx Duration: 360002.70 ms Billed Duration: 360003 ms Memory Size: 128 MB Max Memory Used: 41 MB
しかし、EC2の方を見るとReadTimeoutという結果になりました。elapsedTimeを見てみると400秒と設定されたタイムアウトの時間まで結果を待ち続けているようで、接続の応答が来ていなさそうに見えます。
[ec2-user@ip-xx~]$ python3 invoke_lambda.py
Read timeout on endpoint URL: "https://lambda.ap-northeast-1.amazonaws.com/xxx"
elapsed time: 400.1450889110565
一応どのタイミングでReadTimeoutになるのかを確認してみました。
- 345秒のsleep
[ec2-user@ip-xx~]$ python3 invoke_lambda.py
b'{"statusCode": 200, "body": "\\"Hello from Lambda!\\""}'
elapsed time: 345.31132411956787
- 349秒のsleep
[ec2-user@ip-xx~]$python3 invoke_lambda.py
b'{"statusCode": 200, "body": "\\"Hello from Lambda!\\""}'
elapsed time: 349.288724899292
- 350秒のsleep
[ec2-user@ip-xx~]$ python3 invoke_lambda.py
Read timeout on endpoint URL: "https://lambda.ap-northeast-1.amazonaws.com/xxx"
elapsed time: 400.09561228752136
349秒で成功しますが350秒だとだめみたいでタイムアウトの設定値まで待っており、挙動が一致していそうです。
紹介されている方法を試してみる
紹介されているとおりにTCPキープアライブの動作を設定するためのパラメータの変更と、アプリケーション側でのTCPキープアライブを有効化してみます。詳しい説明は以下の文献を参考にしてください。
初期値は以下の通りでした。
[ec2-user@ip-xx~]$ cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[ec2-user@ip-xx~]$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[ec2-user@ip-xx~]$ cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
350秒で通信が切断しないように設定値を以下のように変更します。
[ec2-user@ip-xx~]$ sudo sysctl -w net.ipv4.tcp_keepalive_time=45
net.ipv4.tcp_keepalive_time = 45
[ec2-user@ip-xx~]$ sudo sysctl -w net.ipv4.tcp_keepalive_intvl=45
net.ipv4.tcp_keepalive_intvl = 45
[ec2-user@ip-xx~]$ sudo sysctl -w net.ipv4.tcp_keepalive_probes=9
net.ipv4.tcp_keepalive_probes = 9
次に、~/.aws/configに以下の設定を書き込みます。
[default]
tcp_keepalive=true
無事通信が成功しました!
[ec2-user@ip-xx~]$ python3 invoke_lambda.py
b'{"statusCode": 200, "body": "\\"Hello from Lambda!\\""}'
elapsed time: 360.0784122943878
おわりに
最近の(?)AWSのアップデートでECS on Fargateでカーネルパラメータを調整できるようになったみたいなのでこちらもまた試してみたいと思います。
参考