まずは、AWS SAM(Serverless Application Model)、AWS CDK(Cloud Development Kit)、Serverless Frameworkの違いを表にまとめます。
特徴 | AWS SAM | AWS CDK | Serverless Framework |
---|---|---|---|
主な目的 | サーバーレスアプリケーションの構築と管理 | AWSリソースのプログラム的定義と管理 | サーバーレスアプリケーションの構築と管理 |
開発モデル/言語サポート | YAML | TypeScript, Python, Java, C#, Go | YAML, TypeScript |
ローカル実行・テスト |
| ローカルでのサポートは弱い |
|
デプロイ方法 |
|
|
|
学習曲線 | 比較的簡単 | プログラミング知識が必要 | 比較的簡単 |
スタックの管理 | CloudFormationに依存 | CloudFormationに依存 | CloudFormationに依存 |
プラグイン拡張 | なし | なし | コミュニティプラグインにより拡張可能 |
公式サポート | AWS公式 | AWS公式 | Serverless.inc |
費用 | 無料 | 無料 | 年間200万ドル以上の売上のある組織では有料 |
特徴的な違いとして、ツールとしての思想がAWS CDKと、Serverless Framework及びSAMとで大きく違うことが分かります。Serverless Framework及びSAMはYAMLベースで設定を記述していくのに対してAWS CDKはプログラムベースで設定を記述していきます。これはサーバレスの歴史的な経緯が作用しているとも言えます。サーバレスがまだ黎明期だったころの2015 - 2016年頃にServerless Framework及びSAMはリリースされました。この頃はAWS Lambdaを中心としてAmazon DynamoDBやAmazon API Gatewayとつなぐだけなど、小規模なアーキテクチャが多かったです。しかし、新たに様々なサーバレスなサービスが登場するにつれてやれることも増えた反面、そのアーキテクチャは複雑になりました。結果、膨大なYAMLの設定が必要になる中で、その可読性の悪さがIaCにおけるひとつの課題になりました。その中で現れたのがAWS CDKであり、プログラミングでインフラを定義するという新しいやり方でした。
この特徴を踏まえて言うと、Serverless Framework及びSAMはそれなりにシンプルなサーバレスアプリケーションに向いていますし、一定以上のAWSリソースを使ってIaCをやるとなるとやはりAWS CDKが向いていることになると考えています。しかし、その反面プログラムにあまり慣れていないという場合はAWS CDKを使うことへの学習コストに時間が割かれるでしょう。
SAMとServerless Frameworkを比較すると、やはりAWS公式であることとそうでないこと、無料か有料である部分をが大きなポイントになるでしょう。そう考えると今後はやはりSAMを選ぶ方が多くなってくるのではないでしょうか。また、Serverless Frameworkはコミュニティプラグンインが充実していますが、有料となった今、そのオープンソースコミュニティとしての熱量は下がってきているようにも感じます。その中での開発の継続性を考えた時に、AWS公式であるSAMが有利と言わざるえを得ないかと考えています。
TypeScriptの使用
TypeScriptは、JavaScriptに型の概念を導入したプログラミング言語で、大規模なプロジェクトでのコードの安全性や保守性を向上させることを目的としています。AWS Lambdaの開発においても生のJavaScriptで開発するよりもTypeScriptを使用した方が確実に生産性は向上するでしょう。さらに今回取り上げるデプロイツールと組み合わせて、デプロイ時にテストやトランスパイラといったCI/CDのワークフローを実施することで、快適な開発環境を手に入れることが出来ます。以降はTypeScriptベースでツールのインストールからデプロイまでを確認していきます。
AWS CDK + TypeScriptを使ったアプリケーションのデプロイ
AWS CDKは、AWSのインフラをプログラムコードとして定義するためのフレームワークです。AWSリソースをプログラムによって宣言的に作成、設定、管理できるようにするツールです。JSONやYAMLで管理するツールと比較するとプログラムで記述でき、ループ処理などが使えるためより柔軟な設定が可能となります。
ここではAWS CDK+TypeScriptを使った開発において、インストールからAWS環境にAmazon API Gateway + AWS Lambdaをデプロイするまでの手順を説明します。まずはAWS CDK自体をインストールします。Amazon Linuxなどnpmが使える環境ではnpmのコマンドでインストールできますし、Macを使用している場合にはbrewを使ってもインストールできます。
% npm install -g aws-sdk
or
% brew install aws-sdk
初回のデプロイが実施されていない場合はcdk bootstrapコマンドを実行してAWS環境をセットアップします。
cdk bootstrap
⏳ Bootstrapping environment aws://99999999999/us-east-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
CDKToolkit: creating CloudFormation changeset...
✅ Environment aws://99999999999/us-east-1 bootstrapped.
プロジェクトのセットアップをします。cdk initコマンドを実行すると言語ごとのプロジェクトのスケルトンを生成してくれます。今回はTyprScriptを使用するためこれを引数に指定します。まずは専用のディレクトリを作成してそこに移動、その後、cdk initを実行します。
% mkdir hello-cdk
% cd hello-cdk
% cdk init app --language typescript
AWS Lambda用の型定義ファイルをインストールします。
% npm install -D @types/aws-lambda
そして以下のようなフォルダ構成を作ります。今回は最小限のスタックをデプロイするので、それに必要なファイルのみをピックアップしています。
bin/hello-cdk.ts
:cdk deploy
実行時に実行されるスクリプトlib/hello-cdk-stack.ts
:CDKスタックの最上位のコンストラクタのコレクションlib/hello-cdk.ts
:Amazon API Gateway + AWS Lambdaのスタックが定義されたファイル。lib/hello-cdk-stack.ts
から呼び出されますlib/hello-cdk.function.ts
:Lambdaファンクションのコードを格納したファイルです。
├── bin
│ └── hello-cdk.ts
├── lib
│ ├── hello-cdk-stack.ts
│ ├── hello-cdk.function.ts
│ └── hello-cdk.ts
lib/hello-cdk.ts
でAmazon API Gateway + AWS Lambdaのスタックを構成します。NodejsFunction
コンストラクタでLambdaファンクションを、LambdaRestApi
コンストラクタで定義したLambdaファンクションをAmazon API Gatewayにアタッチした構成を記述できます。
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';
export class HelloCdk extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
const helloFunction = new NodejsFunction(this, 'function');
new LambdaRestApi(this, 'apigw', {
handler: helloFunction,
});
}
}
lib/hello-cdk.function.ts
では、実際に実行されるLambda ファンクション内のソースコードを定義します。以下はHello WorldのメッセージとHTTPのステータスコードの200を返すだけの関数です。
import { Context, APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';
export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);
return {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
}),
};
};
lib/hello-cdk-stack.ts
で、Amazon API Gateway + AWS LambdaのHelloCdk
スタックを呼び出します。
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { HelloCdk } from './hello-cdk';
export class HelloCdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new HelloCdk(this, 'hello-world');
}
}
bin/hello-cdk.ts
にCDKがデプロイされるときに走るコードを定義します。
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { HelloCdkStack } from '../lib/hello-cdk-stack';
const app = new cdk.App();
new HelloCdkStack(app, 'HelloCdkStack', {});
cdk deployを実行します。実行するとDockerコンテナが呼び出されて、Lambdaファンクションをトランスパイルしデプロイを行います。
% cdk deploy
デプロイ後に発行されたエンドポイントにGETリクエストを送るとちゃんとメッセージが返ってきました。
% curl https://jei2j6v5x6.execute-api.us-east-1.amazonaws.com/prod/
{"message":"hello world"}
SAM + TypeScriptを使ったアプリケーションのデプロイ
SAMはAWSが提供するデプロイツールです。特にサーバーレスアプリケーションを簡潔に定義することに特化しており、AWS Lambda関数やAPI Gatewayの設定をYAML/JSONのシンプルなテンプレート形式で書くことができます。また、サーバレスアプリケーションの開発時にローカル開発が難しいという課題がありますが、これを解決するためのローカル開発のツールも提供されています。複雑なプログラミングの知識を必要としないため、CDKと比べるとその学習コストは低いと言えるでしょう。
まずは、SAM CLIをインストールします。AWS SAM CLI のインストールの手順に従って環境に合わせて行ってください。そして、sam initコマンドでプロジェクトを生成します。対話形式で答えていく形式になるので、適切なものを選んでいきましょう。
% sam init
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.
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]: N
Which runtime would you like to use?
1 - dotnet8
2 - dotnet6
3 - go (provided.al2)
4 - go (provided.al2023)
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java21
8 - java17
9 - java11
10 - java8.al2
11 - nodejs20.x
12 - nodejs18.x
13 - nodejs16.x
14 - python3.9
15 - python3.8
16 - python3.12
17 - python3.11
18 - python3.10
19 - ruby3.3
20 - ruby3.2
21 - rust (provided.al2)
22 - rust (provided.al2023)
Runtime: 11
What package type would you like to use?
1 - Zip
2 - Image
Package type: 1
Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.
Select your starter template
1 - Hello World Example
2 - Hello World Example TypeScript
Template: 2
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: N
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]:
-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: nodejs20.x
Architectures: x86_64
Dependency Manager: npm
Application Template: hello-world-typescript
Output Directory: .
Configuration file: sam-app/samconfig.toml
Next steps can be found in the README file at sam-app/README.md
すると、プロジェクト名のフォルダが生成されてその配下の以下のような構成が生成されます。
├── README.md
├── events
│ └── event.json
├── hello-world
│ ├── app.ts
│ ├── jest.config.ts
│ ├── package.json
│ ├── tests
│ │ └── unit
│ │ └── test-handler.test.ts
│ └── tsconfig.json
├── samconfig.toml
└── template.yaml
template.ymlがCloudFormationの構成を決めるファイルですが以下のようになっています。hello-worldというLambdaファンクションが生成されて、API Gatewayによるイベントが定義されているのがわかります。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
# You can add LoggingConfig parameters such as the Logformat, Log Group, and SystemLogLevel or ApplicationLogLevel. Learn more here https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-loggingconfig.
LoggingConfig:
LogFormat: JSON
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs20.x
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Metadata: # Manage esbuild properties
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: es2020
Sourcemap: true
EntryPoints:
- app.ts
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: API Gateway endpoint URL for Prod stage for Hello World function
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: Hello World Lambda Function ARN
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: Implicit IAM Role created for Hello World function
Value: !GetAtt HelloWorldFunctionRole.Arn
Lambdaファンクションの本体であるhello-world/app.ts
を見てみるとAWS CDKの時のサンプルと同様にHello WorldとHTTPのステータスコード200を返すだけの関数が定義されています。
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
return {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
}),
};
} catch (err) {
console.log(err);
return {
statusCode: 500,
body: JSON.stringify({
message: 'some error happened',
}),
};
}
};
hello-worldディレクトリに移動してnpm installを実行し、定義済みのライブラリをインストールします。
% cd hello-world
% npm install
元のディレクトリに戻ってビルドを行います。成功するとデプロイパッケージが生成されます。
% sam build
Starting Build use cache
Manifest file is changed (new hash: 3a55b648027d1145164f225fad481127) or dependency folder (.aws-sam/deps/41716e1e-726a-47c1-b428-8140a0db3ff5) is missing for (HelloWorldFunction), downloading dependencies and copying/building source
Building codeuri: /Users/xxxxxxxxxx/src/sam-app/hello-world runtime: nodejs20.x architecture: x86_64 functions: HelloWorldFunction
Running NodejsNpmEsbuildBuilder:CopySource
Running NodejsNpmEsbuildBuilder:NpmInstall
Running NodejsNpmEsbuildBuilder:EsbuildBundle
Running NodejsNpmEsbuildBuilder:CleanUp
Running NodejsNpmEsbuildBuilder:MoveDependencies
Sourcemap set without --enable-source-maps, adding --enable-source-maps to function HelloWorldFunction NODE_OPTIONS
You are using source maps, note that this comes with a performance hit! Set Sourcemap to false and remove NODE_OPTIONS: --enable-source-maps to disable source maps.
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
sam deploy
コマンドでデプロイパッケージをAWS環境にデプロイします。
% sam deploy
デプロイ後に表示されたエンドポイントにGETリクエストを送ると正しくHello Worldが返ってきました。
% curl https://2ejhbbxb1l.execute-api.us-east-1.amazonaws.com/Prod/hello/
{"message":"hello world"}
Serverless Framework + TypeScriptを使ったアプリケーションのデプロイ
Serverless FrameworkはSAMと同様のYAMLベースかつサーバレスアプリケーションに特化したデプロイツールです。SAMとの違いとしては、コミュニティベースのプラグインが多数開発されているため拡張性が大きいというところ、Serverless.incという会社がメンテナンスを行っているところ、Serverless Framework V4の変更点まとめの記事にあるように年間200万ドル以上の売上のある組織では有料でサブスクリプションを購入する必要があるところです。条件次第では有料にはなっていますが、サーバレスアプリケーションのデプロイツールの先駆け的な存在でもあるツールで未だに人気は衰えません。
まずはnpmでServerless Frameworkをインストールします。
% npm i serverless -g
そしてserverless
というコマンドを打つと対話形式でのプロジェクトのセットアップが開始されますが、この対話式のコマンドではTypeScriptのテンプレートが提供されていません。なので、独自で作っていきます。まずは以下のようなフォルダ構成を作ります。
% tree
sls-test
├── handler.ts
├── serverless.yml
├── package.json
└── tsconfig.json
そして、npmで最低限必要なファイルをインストールしていきましょう。
% npm install -D ts-node
% npm install -D typescript
% npm install esbuild
% npm install -D @types/aws-lambda
次にtsconfig.jsonを以下のように設定します。
{
"compilerOptions": {
"target": "es2020",
"strict": true,
"preserveConstEnums": true,
"noEmit": true,
"sourceMap": false,
"module":"es2015",
"moduleResolution":"node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
},
"exclude": ["node_modules", "**/*.test.ts"]
}
serverless.ymlを設定します。AWS LambdaにAPI Gatewayをアタッチした設定になっています。Serverless Framework V4からは特別な設定がなくともTypescriptでコードが記述されている場合にはデプロイ時に自動でビルドを行ってくれます。また、Serverless Framework V4からはbuild
というセクションが新たに導入されました。esbuildのカスタマイズが必要な場合にはその配下にesbuild
の項目を設定が可能になります。詳細は、AWS Lambda Build Configurationをご参照ください。
service: sls-test
provider:
name: aws
runtime: nodejs20.x
functions:
hello:
handler: handler.hello
events:
- httpApi:
path: /
method: get
Lambdaファンクション本体は以下のようにHello WorldとHTTPステータスコードの200を返すように記述します
import { Context, APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';
export const hello = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);
return {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
}),
};
};
serverless deployコマンドでトランスパイルからAWSへのデプロイまでを実施します。
% serverless deploy
Deploying "sls-test" to stage "dev" (us-east-1)
✔ Service deployed to stack sls-test-dev (56s)
endpoint: GET - https://lqdc2dwf74.execute-api.us-east-1.amazonaws.com/
functions:
hello: sls-test-dev-hello (1.3 kB)
デプロイ後に表示されたエンドポイントにアクセスすると正しくHello Worldが返ってきます。
% curl https://lqdc2dwf74.execute-api.us-east-1.amazonaws.com/
{"message":"hello world"}
まとめ
以上で、AWS CDKとSAMとServerless Frameworkを使ってツールのインストールからAPIアプリケーションのデプロイまでをやってみました。それぞれ、非常にセットアップはシンプルで簡単にデプロイまでが可能なことがわかります。最初に書いた通り、この3つのツールはやれることや学習コストなどを軸に違いがあります。皆さんのやりたいことやチームとしてのスキルセットに合わせて、この記事がツールの選定のための参考になれば幸いです。