はじめに
bravesoft株式会社でエンジニアをしているはっしーです。
最近携わっている受託の案件でLambdaをよく触っているのですが、Serverless Framework(v3)で構築されています。
・2024年度末でv3のサポートが終了するためクリティカルなセキュリティ問題やバグにしか対応されないためセキュリティーリスクが上がってくるのと、いずれかはAWS Lambdaのランタイムサポートが切れるため、v3からv4へのバージョンアップが必要になるかと思います。
・v4から年間売上が200万ドル以上の企業は有償になるためライセンスの問題が発生する
前年度に年間収益が200万ドルを超えた組織がv4を利用する場合に対象となります。ただし、200万ドル以下だったかどうかの審査はないため自己申告のようです
などの問題があるため、この機会にAWS SAMへ移行しようと考えました。
今回はサンプルを実装しながらAWS SAMへ理解を深めてければと思います。
AWS SAMとは?
AWS SAM(Serverless Application Model)は、AWSが提供するサーバーレスアプリケーションの開発、デプロイ、テストを簡素化するためのフレームワークです。
Lambda関数、API Gateway、DynamoDB、Step Functionsなどを1つのテンプレートで簡潔に定義し、サーバーレスアプリケーションを効率的に構築できます。
Serverless Framework と AWS SAM の違いについて
特徴 | Serverless Framework | AWS SAM (Serverless Application Model) |
---|---|---|
提供元 | オープンソースのフレームワーク、サードパーティ製のツール | AWS公式のフレームワーク |
サポートされるクラウド | 主にAWS、他にもAzureやGoogle Cloud、Kubernetes等 | 主にAWS(AWS LambdaやAPI Gateway等のサービスを中心にサポート) |
設定ファイル | serverless.yml |
template.yaml (AWS CloudFormationを拡張したYAMLファイル) |
デプロイツール |
serverless CLI(sls deploy ) |
sam CLI(sam deploy ) |
対応サービス | Lambda、API Gateway、DynamoDB、S3、SNS、SQS、その他のAWSサービスをサポート | Lambda、API Gateway、DynamoDB、S3、SNS、SQSなどAWSの各種サービス |
デプロイの簡便さ | 比較的簡単で、AWS以外のクラウドにも対応 | AWSに特化しているため、AWSでのデプロイが簡単 |
ローカル開発サポート |
serverless-offline プラグインでAPI GatewayとLambdaのローカル実行が可能 |
sam local コマンドを使ってLambdaやAPI Gatewayのローカル実行が可能 |
テンプレートの可用性 | 独自のプラグインやテンプレートが豊富 | 主にAWSサービスに特化した標準テンプレートを提供 |
拡張性 | プラグインを利用して柔軟に拡張可能 | AWSの設定に特化しているが、CloudFormationとの親和性が高い |
モニタリングとトレーシング | AWS CloudWatchと統合、外部ツール(Datadog、NewRelic等)で拡張可能 | CloudWatchと連携したモニタリングやトレーシングが簡単に利用可能 |
学習コスト | 少し高い(AWS以外のクラウドサービスにも対応している分設定が多い) | AWSに特化しているため、AWSユーザーにとっては学習コストが低い |
公式サポート | オープンソースであり、公式サポートはなし | AWSによる公式サポートが提供される |
インフラの管理 | サーバーレスのリソース定義はYAMLで行い、CloudFormationを裏で利用 | 完全にCloudFormationを利用し、リソース管理やデプロイが行われる |
環境情報
今回サンプルでの確認した環境は以下のとおりです。
項目 | バージョン | 備考 |
---|---|---|
Mac | 14.5 | macOSのバージョン |
AWS SAM | 1.126.0 | AWS SAM(Serverless Application Model)のインストール方法については公式ガイドを参照 |
Node | 20.19.0 | Node.jsのバージョン |
Python | 3.9 | Pythonのバージョン |
Docker Desktop | 任意 |
AWS SAMを触ってみる
AWS Lambdaを使用してポケモン情報を取得するLambdaとAPIを構築してみました。
AWS SAM プロジェクトを作成する
1.homebrewでSAM CLIをインストールする
brew install aws-sam-cli
2.プロジェクトを初期化する
sam init
3.テンプレートを選択してサンプルSAMプロジェクトを作成する
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - DynamoDB Example
16 - Machine Learning
Template: 1
Use the most popular runtime and package type? (Python and zip) [y/N]: y
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: y
X-Ray will incur an additional cost. View https://aws.amazon.com/xray/pricing/ for more details
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: N
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: y
Structured Logging in JSON format might incur an additional cost. View https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-pricing for more details
Project name [sam-app]: aws-sam-pokemon-test
4.Pokemonの情報を取得するスクリプトを書いてみる
PokeAPIは簡単に言うとポケモンのデータを取得するAPIで、RESTfulAPIもしくはGraphQLの形式でデータを取得することができます。今回はpokeapiを利用してポケモンの情報を取得する簡単なLambdaを書いてみました
import json
import requests
def lambda_handler(event, context):
pokemon_name = event['queryStringParameters']['name']
if not pokemon_name:
return {
'statusCode': 400,
'body': json.dumps({
'message': 'ポケモンの名前をクエリパラメータで指定してください。'
})
}
url = f'https://pokeapi.co/api/v2/pokemon/{pokemon_name.lower()}'
try:
response = requests.get(url)
response.raise_for_status()
pokemon_data = response.json()
return {
'statusCode': 200,
'body': json.dumps({
'name': pokemon_data['name'],
'id': pokemon_data['id'],
'types': [type_info['type']['name'] for type_info in pokemon_data['types']],
'height': pokemon_data['height'],
'weight': pokemon_data['weight']
})
}
except requests.exceptions.RequestException as e:
return {
'statusCode': 500,
'body': json.dumps({
'message': f'ポケモン情報の取得中にエラーが発生しました: {str(e)}'
})
}
5.依存ライブラリのインストール(必要な場合)
requirements.txt を作成し、そのライブラリをインストールします。pokemon/ フォルダ内に requirements.txt ファイルを作成し、以下のように記述します。
requests
6.ローカルでLambdaを実行する際のデータを生成する
ローカルで Lambda 関数をテストするためには、events/xxx.json ファイルが必要です。
このファイルには、Lambda 関数が受け取るイベントデータが含まれます。以下のような内容にしてください。
{
"queryStringParameters": {
"name": "pikachu"
}
}
7.template.yamlを修正する
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: >
aws-sam-pokemon-test
Sample SAM Template for aws-sam-pokemon-test
Globals:
Function:
Timeout: 3
MemorySize: 128
Tracing: Active
LoggingConfig:
LogFormat: JSON
Api:
TracingEnabled: true
Resources:
GetPokemonFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: pokemon/
Handler: get_pokemon_info.lambda_handler
Runtime: python3.9
MemorySize: 128
Timeout: 10
Events:
ApiGateway:
Type: Api
Properties:
Path: /pokemon
Method: get
8.ビルドする
次のコマンドを実行するとビルドが実行されます。
sam build
最終的なこんな感じの構成になるかと思います。
AWS-SAM-Pokemon-test/
|── .aws-sam # ビルド済みアーティファクトや中間生成物が格納されるディレクトリ gitignoreに追加しましょう。
├── events/ # ローカル実行時に使用するテストイベントを格納するディレクトリ
│ └── get_pokemon.json
├── pokemon # スクリプトの置き場
│ └── gget_pokemon_info.py
│ └── requirements.txt # 必要なPythonライブラリを記述するファイル
├── template.yaml # SAMテンプレートファイルで、AWS のリソース(Lambda、API Gateway など)の設定を定義
├── samconfig.toml # SAM CLIの設定ファイルで、デプロイ時の環境(開発環境や本番環境)の設定を記述
├── buildspec.yml # AWS CodeBuildのビルド設定を記述するファイルです(今回は特に触れません)
ローカルで実行する
SAMでLambda関数のローカルテスト
作成したGetPokemonFunctionを実行する
sam local invoke GetPokemonFunction --event events/get_pokemon.json
API Gatewayのエミュレートテスト
SAMではローカルでAPI Gatewayをエミュレートすることもできます。sam local start-apiコマンドを使ってローカルサーバーを立て、ブラウザやPostmanでAPIのエンドポイントを確認することできます。
-
Docker Desktopでdockerを起動しておく
-
念のため別のターミナルでdocker起動しているかを確認する
docker ps
curlの場合
curl "http://127.0.0.1:3000/pokemon?name=pikachu"
Postmanの場合
GET:http://127.0.0.1:3000/pokemon?name=pikachu
Apidogの場合
GET:http://127.0.0.1:3000/pokemon?name=pikachu
それぞれ問題なくポケモンの情報が取得できました
serveress frameworkからAWS SAMに移行してみた
現状関わっている案件では、serveress frameworkで構築しているため、以下のサンプルを元にAWS SAMへの移行を試してみたいと思います。
例えばuser情報を取得するLambdaをserverless frameworkで構築していた場合
import boto3
import uuid
from boto3.dynamodb.types import TypeSerializer
from boto3.dynamodb.conditions import Key
import simplejson as json
from os import environ
dynamodb = boto3.resource('dynamodb')
stage = environ['stage']
def query_table(table_name, key=None, value=None):
table = dynamodb.Table(table_name)
if key is not None and value is not None:
filtering_exp = Key(key).eq(value)
return table.query(KeyConditionExpression=filtering_exp)
raise ValueError('Parameters missing or invalid')
def lambda_handler(event, context):
serializer = TypeSerializer()
response = query_table(
table_name= f'user_{stage}',
key="user_id",
value=event['pathParameters']['user_id']
)
data = response.get('Items', None)
if data is not None and len(data) > 0:
user = data[0]
else:
user = None
return {
'statusCode': 200,
'headers': {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "GET"
},
'body': json.dumps(user)
}
service: Sample
frameworkVersion: "3"
provider:
name: aws
runtime: python3.9
region: ap-northeast-1
stage: ${opt:stage, 'dev'}
environment:
stage: ${sls:stage}
functions:
function_get_user:
handler: function-get-user.lambda_handler
environment:
user_table_name: user-${self:provider.stage}
events:
- http:
path: user
method: get
request:
parameters:
querystrings:
uid: true
移行プロセス
以下のような手順で今回は移行してみます
STEP1:設定ファイルの移行:serverless.ymlの設定をtemplate.yamlに変換する
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
GetUserFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: user/
Handler: function-get-user.lambda_handler # 関数のエントリーポイント
Runtime: python3.9 # ランタイム
MemorySize: 128 # メモリサイズ
Timeout: 60 # タイムアウト時間
Role: arn:aws:iam::<account-id>:role/service-role/xxxxxxx # ロール
Environment: # 環境変数の定義
Variables:
STAGE: !Ref Stage # stageを環境変数に設定
TABLE_NAME: !Sub "user-${Stage}" # 環境に応じたテーブル名を設定
Events: # HTTPイベントの設定
UserApi:
Type: Api
Properties:
Path: /user # API Gatewayのエンドポイント
Method: get # HTTPメソッド
RequestParameters: # クエリパラメータの指定
querystrings:
user_id: true
Parameters:
Stage:
Type: String
Default: dev # デフォルトのステージは "dev"
AllowedValues:
- dev
- prod
Description: The deployment stage
Serverless FrameworkはYAML設定ファイルで小文字を使うことが多いですが、AWS SAMやLambdaでは大文字の環境変数名が推奨されるため今回大文字に変更しました。
STEP2:samconfig.tomlにて環境情報を修正する
Serverless FrameworkはYAMLにてregionなどの情報を記述してましたがAWS SAMではregionとかの情報などはsamconfig.tomlのというファイルに記述します。必要に応じて修正しましょう
Serverless FrameworkとAWS SAMの比較表
設定項目 | Serverless Framework (serverless.yaml ) |
AWS SAM (samconfig.toml ) |
---|---|---|
リージョン | provider.region |
[環境名.global.parameters].region |
ステージ | provider.stage |
parameter_overrides で指定 |
環境変数 | provider.environment |
template.yaml に記述 |
デプロイ用のスタック名 | CLI またはカスタム設定 | [環境名.global.parameters].stack_name |
ランタイム指定 | provider.runtime |
template.yaml に記述 |
キャッシュや並列設定 | 設定不可 |
[環境名.build.parameters] で指定 |
provider:
name: aws
runtime: python3.9
region: ap-northeast-1
stage: ${opt:stage, 'dev'}
environment:
stage: ${sls:stage}
serverless.yamlのproviderにて定義していたregionとか、stagなどのパラメータをsamconfig.tomlではregionや、environmentで指定したstageなどパラメータの設定を行うことができます。
regionを指定する
環境ごとにリージョンを指定できます。
例として、dev 環境で ap-northeast-1 を指定する場合は以下のように指定します。
[dev.global.parameters]
region = "ap-northeast-1"
パラメータを指定する
parameter_overrides を使用して、キーと値のペアでパラメータを渡します。
例として、STAGE パラメータに dev を設定する場合は以下のように指定します。
[dev.deploy.parameters]
parameter_overrides = "STAGE=dev"
最終的なsamconfig.tomlの例
[default.global.parameters]
version = 0.1
# 開発環境の設定
[dev.global.parameters]
stack_name = "aws-sam-test-dev-stack" # CloudFormation スタック名
[dev.build.parameters]
region = "ap-northeast-1" # AWS リージョン
cached = true
parallel = true
[dev.validate.parameters]
lint = true
[dev.deploy.parameters]
capabilities = "CAPABILITY_IAM" # IAM ロールの権限設定 (CAPABILITY_IAM)
confirm_changeset = true
resolve_s3 = true # 自動的に S3 バケットを解決
parameter_overrides = "STAGE=dev" # テンプレートのパラメータに渡す値をセットする
# 本番環境の情報
[prod.global.parameters]
stack_name = "aws-sam-test-prod-stack"
[prod.build.parameters]
region = "ap-northeast-1"
cached = true
parallel = true
[prod.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
parameter_overrides = "STAGE=prod"
STEP3:コードの修正:環境変数やAPI Gatewayの設定に対応させる
SAM の場合、template.yaml で定義した環境変数やパラメータは、Lambda 関数で利用でるので少しだけ以下のように修正してみました。
import boto3
import uuid
from boto3.dynamodb.types import TypeSerializer
from boto3.dynamodb.conditions import Key
import simplejson as json
from os import environ
dynamodb = boto3.resource('dynamodb')
def query_table(table_name, key=None, value=None):
table = dynamodb.Table(table_name)
if key is not None and value is not None:
filtering_exp = Key(key).eq(value)
return table.query(KeyConditionExpression=filtering_exp)
raise ValueError('Parameters missing or invalid')
def lambda_handler(event, context):
serializer = TypeSerializer()
response = query_table(
table_name= environ['TABLE_NAME'],
key="user_id",
value=event['pathParameters']['user_id']
)
data = response.get('Items', None)
if data is not None and len(data) > 0:
user = data[0]
else:
user = None
return {
'statusCode': 200,
'headers': {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "GET"
},
'body': json.dumps(user)
}
STEP4:ビルドする
Lambda関数をデプロイする前にSAMでビルドする。
sam build
STEP4:ローカルで動作を確認する
sam local invoke GetUserFunction --event events/get_user.json
{"statusCode": 200, "headers": {"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Methods": "GET"}, "body": {"userId": "123", "name": "Yamada Taro", "email": "yamada-taro@example.com"}}
STEP5:インタラクティブモードでAPI Gatewayにdeployしてみる
sam deploy --config-env dev --guided
対話式で必要な情報を入力してください。
samconfig.toml に記載された設定がそのまま適用されますが指定された情報でSAMがCloudFormationを使用してスタックを作成し、API GatewayやLambda関数をデプロイします。
・初回は--guidedを使い、設定を確認しながらデプロイを行うと良いでしょう
・samconfig.tomlに設定を保存すると、次回以降はsam deploy --config-env devで迅速にデプロイ可能です。
・デプロイ環境(devやprod)ごとにパラメータを切り替えられるため、運用が簡単になります
STEP6:Deployが成功したらAPIにアクセスしてみる
curlで呼び出したらこんな感じで結果が返ってきました
curl -X GET "https://{api_id}.execute-api.ap-northeast-1.amazonaws.com/dev/user?uid=123"
{
"userId": "123",
"name": "Yamada Taro",
"email": "yamada-taro@example.com"
}
まとめ
Serverless FrameworkからAWS SAMへの移行は比較的スムーズに行うことができました。
AWS SAMは公式サポートが充実しており、シンプルな構成でAWSサービスとの親和性も高いため、今後のプロジェクトや既存のインフラの見直しの際に移行を検討してみる価値があります。
実際の案件で完全移行が完了した際には、さらに詳しい事例やノウハウを共有する記事を書きたいと思います。
最後までお読みいただきありがとうございました!今後の皆さんのプロジェクトの参考になれば幸いです。
bravesoftではエンジニアを募集しております。採用ページをご確認ください