このページは、その2(その1のつづき)です。
- その1は、NestJSのWebフレームワークとしてローカル環境として動かす初期構築まで
- その2では、Amazon API Gateway + AWS Lambdaへデプロイを行います
- その3では、AWS App Runner として動かす(仮)
記事 | ブランチ | 内容 |
---|---|---|
その1 | feature/example-step1 | nest cli |
その2 | feature/example-step2-sls | use serverless framework, sls deploy |
将来、1つのdevelop ブランチ にしてしまうかもしれません
Serverless Framework(sls)とAWS Lambda関連の対応
1. パッケージインストール
- 公式のドキュメント
- 公式のexample
をもとに
追加でパッケージインストール
$ npm i @vendia/serverless-express aws-lambda
$ npm i -D @types/aws-lambda serverless-offline
2. ファイルserverless.yml
を作成
service: serverless-example
frameworkVersion: "3"
plugins:
- serverless-offline
provider:
name: aws
runtime: nodejs18.x
functions:
main:
# handler: src/main.handler , srcかdistか、、、AWSでのLambdaの設定に関わる
handler: dist/main.handler
events:
- http:
method: ANY
path: /
- http:
method: ANY
path: '{proxy+}'
上記ファイルは一部省略しているため、GitHubリポジトリの情報を参照してください(ブランチ https://github.com/ssugimoto/serverless-framework-nestjs-example/blob/feature/example-step2-sls/serverless.yml )
3. src/main.ts を AWS Lambdaのhandler向けに変更
- AWS Lambdaの作法に合わせる
import { NestFactory } from '@nestjs/core';
import serverlessExpress from '@vendia/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';
let server: Handler;
async function bootstrap(): Promise<Handler> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
server = server ?? (await bootstrap());
return server(event, context, callback);
};
- 変更前の
src/main.ts
を記録として残しておいた
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
4. 上記では、まだ足らないのでServerless frameworkのpluginを導入
引用
Usage with serverless-offline
The plugin integrates very well with serverless-offline to simulate AWS Lambda and AWS API Gateway locally.
Add the plugins to your serverless.yml file and make sure that serverless-plugin-typescript precedes serverless-offline as the order is important:
serverless.ymlファイルにプラグインを追加し、
順番が重要なのでserverless-plugin-typescriptがserverless-offlineより前にあることを確認する
- plugin の install
npm install -D serverless-plugin-typescript typescript
npm install -D serverless-plugin-optimize
- 実行するとserverless-offline も合わせてdevDependenciesにインストールされる、typescriptはマイナーバージョンアップ
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/aws-lambda": "^8.10.119",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"serverless-offline": "^12.0.4",
"serverless-plugin-typescript": "^2.1.5",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.6"
},
ここまでで、AWSへのデプロイ前までがほぼ完了しています
ここからは、awsへのデプロイとserverless frameworkを使いデプロイします
5. aws profile
- Serverless Frameworkを使い AWSにデプロイする上で、今回は
aws profile
を使います - CI/CDとして使う場合では、別の方法になると思います
- 参考
aws profile 設定の参考です
リージョンはバージニアリージョンにしていますが、serverless.yml
の provider
> region
が使われます。
$ cat ~/.aws/config
[default]
region=us-east-1
$ cat ~/.aws/credentials
[default]
aws_access_key_id=AK....HE
aws_secret_access_key=....
6. sls ビルド準備
- serverless.yml の custom
serverlessPluginTypescript
でtsconfig.jsonとは別のファイルに変更できたりします - ファイル
./tsconfig.json
を使う場合はserverlessPluginTypescript
の設定は不要です
service: serverless-example
frameworkVersion: "3"
plugins:
- serverless-plugin-typescript
- serverless-plugin-optimize
- serverless-offline
custom:
serverless-offline:
httpPort: 3000
serverlessPluginTypescript:
# tsConfigFileLocation: './tsconfig.lambda.json'
tsConfigFileLocation: './tsconfig.build.json'
#tsConfigFileLocation: './tsconfig.json'
defaultStage: dev
...
7.sls package
- パッケージングを実施するコマンド(デプロイまではしない)
- ビルド等の動作確認として使えるコマンドです
- sls deployの前にパッケージングが正しくできるか確認する時に使えます
$ sls package
Running "serverless" from node_modules
Packaging serverless-example for stage dev (us-east-1)
Compiling with Typescript...
Using local tsconfig.json - ./tsconfig.build.json
Typescript compiled.
✔ Service packaged (25s)
1 deprecation found: run 'serverless doctor' for more details
sls package
が成功するとファイル .serverless/main.zip
にパッケージングされたファイルが作成されます
- main.zipのファイルの中身の一例
main
├── dist
│ ├── app.controller.d.ts
│ ├── app.controller.js
│ ├── app.controller.js.map
│ ├── app.module.d.ts
│ ├── app.module.js
│ ├── app.module.js.map
│ ├── app.service.d.ts
│ ├── app.service.js
│ ├── app.service.js.map
│ ├── main.d.ts
│ ├── main.js
│ └── main.js.map
├── node_modules
│ ├── accepts
│ ├── acorn-node
│ ├── align-text
...
│ ├── @nestjs
│ ├── node-fetch
│ ├── @nuxtjs
│ ├── object-assign
...
│ ├── @vendia
│ └── xtend
└── package.json
- 無駄なファイルがたくさん、 node_modules もってきてしまっているため、後々の改善ポイントにしておきます
8. sls deploy
- AWSクラウドへのデプロイを行います
- sls packageが内部で先に実行されます
- Cloudformation+S3が使われます
ビルドしてデプロイ
$ nest build && sls deploy
Running "serverless" from node_modules
Deploying serverless-example to stage dev (us-east-1)
Compiling with Typescript...
Using local tsconfig.json - ./tsconfig.build.json
Typescript compiled.
✔ Service deployed to stack serverless-example-dev (88s)
endpoints:
ANY - https://xxx.execute-api.us-east-1.amazonaws.com/dev/
ANY - https://xxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
main: serverless-example-dev-main (25 MB)
Monitor all your API routes with Serverless Console: run "serverless --console"
- API Gatewayのエンドポイントが生成されます(存在しなければ新規作成、存在していれば更新)
- API Gatewayのエンドポイントは1つです
- トリガー元がAPI GatewayのAWS Lambdaが生成されます(存在しなければ新規作成、存在していれば更新)
- Lambda Functionは複数ではなく、NestJSでのルーティングが使われるためデプロイされるLambdaは1つです
- Lambda内で対応したメソッドと関数が呼び出されます
Cloudformationの結果
APIの動作確認
curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/
curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/say
sls deploy オプション
-
--stage
: サービスのステージ -
--force
: 強制デプロイ
ここまでで、AWSクラウド側に REST APIを作成できました。認証や認可の対応等を機能追加していくとよくあるWebアプリケーションに近づいていきます。
Serverless Frameworkの知らなかった機能
sls print
で Variables、パラメーターを確認できる
service: serverless-example
..略
custom:
serverless-offline:
httpPort: 3000
stageVariables:
foo: "bar"
defaultStage: dev
environment:
dev: ${file(./env/site-dev.yml)}
prod: ${file(./env/site-prod.yml)}
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
stage: ${opt:stage, self:custom.defaultStage}
のような場合に
$ sls print --stage prod
を実行すると、以下のように確認できる
Running "serverless" from node_modules
service: serverless-example
...略
custom:
serverless-offline:
httpPort: 3000
stageVariables:
foo: bar
defaultStage: dev
environment:
dev:
key: sample
prod:
key: prod-sample
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
stage: prod
versionFunctions: true
serverless-offlineの意図せぬ挙動
ドメインRootへのアクセスの挙動(plugin serverless-offline
の挙動)
- ドメインルートにアクセスすると動作しない、sls offline の問題
- ルートアクセス用の
getHello()
に到達しない - src/app.controller.ts
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
console.log('controller start.');
return this.appService.getHello();
}
@Get('say')
getSay(): string {
return 'say.';
}
}
curl でドメインルートアクセスした場合
挙動その1
>curl http://localhost:3000/
{"currentRoute":"get - /","error":"Serverless-offline: route not found.","existingRoutes":["* - /dev","* - /dev/{proxy*}"],"statusCode":404}
挙動その2
>curl http://localhost:3000/dev/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET null</pre>
</body>
</html>
curl でドメインルートに、スラッシュを1つ多くしてアクセスすると getHello()
に到達する
>curl http://localhost:3000/dev//
Hello World!
- curl で API Gatewayのドメインルートにアクセスした場合
>curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/
Hello World!
>curl https://xxx.execute-api.us-east-1.amazonaws.com/dev//
Hello World!
>curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/say
say.
対処というか回避
- https://stackoverflow.com/questions/57733581/serverless-offline-route-not-found-running-an-aws-lambda-function-in-offline
- https://qiita.com/manasan-iTL/items/e38bc39ab90d1191b6e1
- https://github.com/dherault/serverless-offline/issues/911
- https://github.com/dherault/serverless-offline/issues/88
- 放置
- API Gateway + AWS LAmbda でのデプロイ時は上に記述の通り動くので放置しておく
余談、NestJSをAWSクラウドで動かす場合
- EC2 で 動かす(dockerコンテナ使っても良いですし、プロセスとしてミドルウェアとしても動かせる)
- ALB + ECS Fargate で
app.listen(3000);
のようにWebサーバー相当でリクエストを受ける - ✅APIとして API Gateway + AWS Lambdaとして動かす
- 他には
- API Gateway + Fargate
- App Runner もあります
- AWSだと、Codeシリーズを使いCI/CDできます