LambdaからALB経由でAPIを呼び出し、HTTPステータスを監視してみた
今回は、AWS LambdaからALBのDNS名を利用してAPIを呼び出す構成を作ってみました。
LambdaからALB配下のAPIを実行し、レスポンスコードを確認します。
また、HTTPステータスコードが300以上の場合はLambdaをエラー終了させることで、API監視に利用できる構成にしています。
最終的には、以下のような構成です。
EventBridge Scheduler
│
▼
Lambda
│
│ HTTPS
▼
ALB
│
▼
EC2
記事の目次
今回の構成
今回は、EC2上に簡単なAPIを用意し、ALB経由でLambdaから呼び出します。
- EC2:APIを提供
- ALB:HTTPSリスナー(自己証明書を使用)
- Lambda:APIを呼び出し
- CloudWatch Logs:API呼び出し結果を記録
- EventBridge Scheduler:Lambdaを定期実行
EC2にAPIを用意する
まず、EC2上にAPIとして利用するファイルを作成します。
sudo vi /var/www/html/http-trigger-test以下の内容を記載します。
Hello from EC2 API!ファイルが作成されていることを確認します。
sudo ls /var/www/html/http-trigger-test
ALBのDNS名を利用して、以下のURLでアクセスします。
https://<ALB_DNS_NAME>/http-trigger-test
正常にアクセスできると、以下のレスポンスが返却されます。
Hello from EC2 API!
SAN付きの自己署名証明書を作成する
今回は、LambdaからALBへHTTPS通信を行います。
そのため、ALBのHTTPSリスナーに自己署名証明書を設定します。
秘密鍵を作成する
まず、秘密鍵を作成します。
sudo openssl genrsa -out server.key 2048OpenSSLの設定ファイルを作成する
次に、openssl.cnf を作成します。
vi openssl.cnf以下の内容を記載します。
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = JP
L = Default City
O = Default Company Ltd
CN = <ALB_DNS_NAME>
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = <ALB_DNS_NAME>今回は、SAN(Subject Alternative Name)にALBのDNS名を指定しています。
SAN付き証明書を作成する
以下のコマンドで証明書を作成します。
sudo openssl req -x509 -new -key server.key -out server.crt -days 365 -config openssl.cnfSANを確認する
作成した証明書にSANが設定されているか確認します。
openssl x509 -in server.crt -text -noout | grep -A 1 "Subject Alternative Name"以下のように、ALBのDNS名が表示されればOKです。
X509v3 Subject Alternative Name:
DNS:<ALB_DNS_NAME>

証明書をALBのHTTPSリスナーに紐付ける
作成した自己署名証明書をAWSへインポートします。
その後、ALBのHTTPSリスナーに証明書を紐付けます。
紐付けの方法は下記記事の「ALBのリスナー設定を変更する」を参考にしてください。
【AWS】ALBとEC2間をHTTPS化する① ~まずはクライアント⇔ALB間をHTTPS化してみた~
ALBに設定された証明書を確認する
証明書をALBのリスナーに紐付けた後、証明書のシリアル番号を比較して確認します。
ローカルにある証明書のシリアル番号を確認
openssl x509 -in server.crt -serial -nooutALBから返却される証明書のシリアル番号を確認
openssl s_client \
-connect <ALB_DNS_NAME>:443 \
-servername <ALB_DNS_NAME> \
2>/dev/null | openssl x509 -serial -noout
openssl s_client -connect <ALB_DNS_NAME> -servername alb-api-test-621678629.ap-northeast-1.elb.amazonaws.com 2>/dev/null | openssl x509 -serial -noout2つのシリアル番号が一致すれば、ALBに想定した証明書が設定されています。
Lambdaを作成する
次に、Lambdaを作成します。
今回は、LambdaからHTTPSでALBのAPIを呼び出します。
Lambda用のフォルダは以下の構成にします。
Lambda用フォルダ
├── lambda_function.py
└── server.crt
今回、LambdaからHTTPS通信を行うため、自己署名証明書である server.crt をLambdaに含めます。server.crt は本手順で作成した証明書をダウンロードして(またはコピペ)ください。

Lambdaのコード
lambda_function.py に以下のコードを記載します。
import urllib.request
import urllib.error
import ssl
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
url = "https://<ALB_DNS_NAME>/http-trigger-test"
cert_path = os.path.join(
os.path.dirname(__file__),
"server.crt"
)
ssl_context = ssl.create_default_context(
cafile=cert_path
)
try:
with urllib.request.urlopen(
url,
context=ssl_context
) as response:
status_code = response.status
body = response.read().decode("utf-8")
except urllib.error.HTTPError as error:
status_code = error.code
body = error.read().decode("utf-8")
print(f"Request URL: {url}")
print(f"Response Status Code: {status_code}")
if status_code >= 300:
logger.error(
f"API request failed. "
f"URL={url}, "
f"StatusCode={status_code}, "
f"ResponseBody={body}"
)
raise RuntimeError(
f"API request failed. "
f"URL={url}, "
f"StatusCode={status_code}, "
f"ResponseBody={body}"
)
return {
"statusCode": status_code,
"body": body
}
コードのポイント
今回のポイントは、HTTPステータスコードが300以上の場合にLambdaをエラー終了させていることです。
if status_code >= 300:
例えば、以下のような場合です。
| HTTPステータス | Lambda |
|---|---|
| 200 | 成功 |
| 201 | 成功 |
| 204 | 成功 |
| 300 | エラー |
| 404 | エラー |
| 500 | エラー |
API監視を目的としているため、300以上を異常として扱うようにしています。
また、以下のログをCloudWatch Logsへ出力します。
print(f"Request URL: {url}")
print(f"Response Status Code: {status_code}")
そのため、どのURLを呼び出したか、どのHTTPステータスが返却されたかを確認できます。
LambdaをZIP化する
Lambdaへアップロードするため、コードと証明書をZIP化します。
zip lambda_function.zip \
lambda_function.py \
server.crt
ここで重要なのは、lambda_function.py と server.crt がZIPの直下に配置されていることです。
lambda_function.zip
├── lambda_function.py
└── server.crt
AWS公式でも、LambdaのPythonコードと必要なファイルはZIPのルートに配置する形が案内されています。
Lambdaをテストする
Lambdaダッシュボードの**「テスト」タブ**を開きます。
「イベントJSON」に以下を入力します。
{}
その後、「テスト」をクリックします。
APIが正常に呼び出せると、以下のようなレスポンスになります。
{
"statusCode": 200,
"body": "Hello from EC2 API!\n"
}
CloudWatch Logsには、以下のようなログが出力されます。
Request URL: https://<ALB_DNS_NAME>/http-trigger-test
Response Status Code: 200
CloudFormationでEventBridge SchedulerとLambdaを作成する
今回は手動でLambdaを作成しました。
しかし、実際には定期的にLambdaを実行してAPIを監視したいため、EventBridge SchedulerからLambdaを呼び出す構成にします。
以下のCloudFormationを利用すると、以下のリソースをまとめて作成できます。
- CloudWatch Logs
- Lambda実行ロール
- Lambda
- EventBridge Scheduler実行ロール
- EventBridge Scheduler
CloudFormationテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: >
CloudFormation template for EventBridge Scheduler and Lambda
Parameters:
LambdaCodeBucket:
Type: String
Description: Lambda deployment package S3 bucket name
LambdaCodeKey:
Type: String
Description: Lambda deployment package S3 key
ScheduleExpression:
Type: String
Default: cron(0 0 * * ? *)
Description: EventBridge Scheduler schedule expression
Resources:
##################################################
# Shared CloudWatch Logs Log Group
##################################################
SharedLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: "/aws/lambda/request-api-logs"
RetentionInDays: 120
##################################################
# Lambda Execution Role
##################################################
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${AWS::StackName}-lambda-execution-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
##################################################
# Lambda
##################################################
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-lambda
Runtime: python3.12
Handler: lambda_function.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
S3Bucket: !Ref LambdaCodeBucket
S3Key: !Ref LambdaCodeKey
Timeout: 60
MemorySize: 128
LoggingConfig:
LogGroup: !Ref SharedLogGroup
##################################################
# EventBridge Scheduler Execution Role
##################################################
SchedulerExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${AWS::StackName}-scheduler-execution-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- scheduler.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: InvokeLambda
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- !GetAtt LambdaFunction.Arn
##################################################
# EventBridge Scheduler
##################################################
EventBridgeScheduler:
Type: AWS::Scheduler::Schedule
Properties:
Name: !Sub ${AWS::StackName}-scheduler
FlexibleTimeWindow:
Mode: 'OFF'
ScheduleExpression: !Ref ScheduleExpression
ScheduleExpressionTimezone: Asia/Tokyo
State: ENABLED
Target:
Arn: !GetAtt LambdaFunction.Arn
RoleArn: !GetAtt SchedulerExecutionRole.Arn
Outputs:
LambdaFunctionArn:
Description: Lambda function ARN
Value: !GetAtt LambdaFunction.Arn
SchedulerArn:
Description: EventBridge Scheduler ARN
Value: !GetAtt EventBridgeScheduler.Arn
SchedulerExecutionRoleArn:
Description: EventBridge Scheduler execution role ARN
Value: !GetAtt SchedulerExecutionRole.Arn
SharedLogGroupName:
Description: Shared CloudWatch Logs log group name
Value: !Ref SharedLogGroup
まとめ
今回は、LambdaからALB経由でHTTPSのAPIを呼び出し、HTTPステータスコードを確認する構成を作成しました。
ポイントは以下です。
- LambdaからALBのDNS名へHTTPSアクセス
- SAN付き自己署名証明書を利用
- LambdaにCA証明書を配置
- APIのレスポンスコードをCloudWatch Logsへ出力
- HTTPステータスコードが300以上の場合はLambdaをエラー終了
- EventBridge SchedulerでLambdaを定期実行
- CloudFormationで関連リソースをIaC化
今回は1つのAPIを対象にしていますが、今後はAPIごとにLambdaを分け、共通のCloudWatch Logsへログを集約する構成も試してみたいと思います。