Serverless + AWS Lambda + TypeScriptをデプロイ【まもりあいJapanのコード研究:3】

プログラム全般

前回はローカル環境を動かしました。

今回はServerlessFrameworkを使って、AWS Lambdaにデプロイしてみます。

TypeScript + Lambdaの組み合わせは情報が少ないのでうまくいくかどうか・・(うまく行きました)

※実際の確認手順で書いていますので、情報としてはまとまっていません。逆に、知らないアーキテクチャにどうやって立ち向かってくか、みたいな観点で見ていただけると、参考になるかと思います。

コマンド確認

mamori-i-japan/mamori-i-japan-api
REST API Server for Japanese Exposure Notification App to fight against COVID-19 a.k.a. "まもりあいJAPAN". - mamori-i-japan/mamori-i-japan-api
npm run deploy:dev

ちゃんと設定すれば、1コマンドでデプロイできるみたいですね。

deploy:dev の中身は、

mamori-i-japan/mamori-i-japan-api
REST API Server for Japanese Exposure Notification App to fight against COVID-19 a.k.a. "まもりあいJAPAN". - mamori-i-japan/mamori-i-japan-api
npm run build && serverless create_domain --stage dev && serverless deploy --stage dev

3つのコマンドが連結されているので、それぞれ確認します。

build

build

mamori-i-japan/mamori-i-japan-api
REST API Server for Japanese Exposure Notification App to fight against COVID-19 a.k.a. "まもりあいJAPAN". - mamori-i-japan/mamori-i-japan-api
nest build

のaliasです。

nestjsのマニュアルを確認すると、

Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines ele

wrapper on top of the standard tsc compiler (for standard projects) or the webpack compiler (for monorepos). 

webpackの設定ファイルはなくて tsconfig.json ファイルがあるので、多分standard projectsなんだと思います。

というわけで、TypeScriptのコンパイルコマンドのtscを実行してるだけみたいです。ふむふむ。

serverless create_domain –stage dev

serverlessのドキュメントを検索しても、create_domain なんてコマンドないぞ〜と思ったのですが、google検索したらヒットしました。

How to set up a custom domain name for Lambda & API Gateway with Serverless
Learn how to set up a custom domain name for AWS Lambda & API Gateway using the Serverless Framework to configure a clean domain name for your services.

serverless-domain-manager というpluginのコマンドなんですね。(そもそもserverlessってplugin形式で拡張するんですね)

何もしない場合、AWSの API Gateway で割り振られるドメインはランダム文字列ぽいものになりますが、これを使うとCloudFront 経由で、指定のドメインでアクセスできるようになるようです。

ただし、httpsアクセスに必要なCertificationは自動生成してくれないので、手動でやる必要がある模様。

serverless.yaml ファイルではここらへんの記述が該当。(関係ありそうなところを抜粋)

mamori-i-japan/mamori-i-japan-api
REST API Server for Japanese Exposure Notification App to fight against COVID-19 a.k.a. "まもりあいJAPAN". - mamori-i-japan/mamori-i-japan-api
plugins:
  - serverless-domain-manager

custom:
  customDomain:
    domainName: ${self:custom.SUBDOMAIN.${self:provider.stage}}.${self:custom.DOMAIN}
  DOMAIN: ${ssm:/singleton/domain_name}
  SUBDOMAIN:
    dev: api-dev
    stg: api-stg
    demo: api-demo

DOMAIN${ssm:/singleton/domain_name} と変数で指定されていますね、、、ssmとはなんぞや、ですが。

Serverless Variables
How to use Serverless Variables to insert dynamic configuration info into your serverless.yml

AWSのSystemsManagerの事みたいですね。

AWS Systems Manager パラメータストア - AWS Systems Manager
AWS Systems Manager パラメータストア は、設定データ管理と機密管理のための安全な階層型ストレージを提供します。

key/value形式で、AWSの各サービスで使うパラメータを環境変数的にセットして使えるサービスです。

というわけで、この手順通り動かすためには、

  • DomainをAWSに登録する(手動)
  • CertificationをAWSに登録する(手動)
  • ssmに必要なパラメータを登録する

という手順を踏まないと、このコマンドは動かなさそうです。

オプションの --stage dev

Serverless Variables
How to use Serverless Variables to insert dynamic configuration info into your serverless.yml

設定ファイルの serverless.yml にパラメータを渡せして変数として扱えるようです。

provider:
  stage: ${opt:stage, 'dev'}

となっていて、

${ssm:/envvars/${self:provider.stage}/FIREBASE_DATABASE_URL~true}

だと、${ssm:/envvars/dev/FIREBASE_DATABASE_URL~true}

になるって事ですね。

最後の ~true は、ssmから取得する時に暗号化された値を使う時に必要らしいです。

Serverless Variables
How to use Serverless Variables to insert dynamic configuration info into your serverless.yml

serverless deploy –stage dev

Serverless Framework - AWS Lambda Guide - Deploying
How to deploy your AWS Lambda functions and their required infrastructure

実際にdeployするコマンド。CloudFormation形式に変換して実行する作戦らしい。

  • serverless.yml -> CloudFormationのtemplateに変換
  • ソースコードをzip化
  • zipを(lambdaが読み込む)s3にupload
    • hashのチェックをして差分があった時だけ実行される
  • CloudFormationのtemplate実行

という流れ。CloudFormation遅いのでdeployコマンドも遅いよ、deploy function コマンドを使うと、CloudFormation実行しないので早いよ、というtipsも。

serverless.yml確認

設定ファイルも一通り確認します。

mamori-i-japan/mamori-i-japan-api
REST API Server for Japanese Exposure Notification App to fight against COVID-19 a.k.a. "まもりあいJAPAN". - mamori-i-japan/mamori-i-japan-api

基本的にはここで解説されている。

Serverless Framework - AWS Lambda Guide - Serverless.yml Reference
A list of all available properties on serverless.yml for AWS

plugin確認

serverless-deployment-bucket

MikeSouza/serverless-deployment-bucket
Create and configure the custom Serverless deployment bucket. - MikeSouza/serverless-deployment-bucket

デプロイする時に使うs3のbucket周りを良い感じにしてくれるプラグイン。

serverless-domain-manager

APIGatewayのドメインを指定できるplugin(前述)

serverless-layers

agutoli/serverless-layers
Serverless.js plugin that implements AWS Lambda Layers which reduces drastically lambda size, warm-up and deployment time. - agutoli/serverless-layers

LambdaのLayerを良い感じにしてくれるplugin。

なのだが、そもそもLambda Layerつかってたっけ??? serverless.yml には特に設定は無い。

Serverless Framework - AWS Lambda Guide - Layers
How to configure AWS Lambda layers in the Serverless Framework

うーん??

serverless-offline

dherault/serverless-offline
Emulate AWS λ and API Gateway locally when developing your Serverless project - dherault/serverless-offline

ローカルでLamdaを実行するエミュレーター機能を提供してくれる。

package.jsonsls-offline というコマンドがある。

今回の構成だと、普段は直接Nestjsを叩いて動くので必要ないけど、エンドポイントのコードは別だったりするので、そこら辺を確認したい時に使うと良さそう。

serverless-prune-plugin

claygregory/serverless-prune-plugin
Serverless 1.x plugin to reap unused versions of deployed functions from AWS - claygregory/serverless-prune-plugin
Serverless Frameworkを本番運用する際にやっておいたほうが良い事 - Qiita
この記事の概要 Serverless Framework を本番環境で運用する際にやっておいたほうが良い設定を紹介させて頂きます。 前提条件 以下の要件で運用しているという前提でお話させて頂きます。 AWS Lambd...

古いソースを消してくれるplugin。

いざデプロイ

調査は終わったので、実際に動かしてみます。

事前準備

いくつか手動で設定が必要な内容があるので設定していきます。

AWSの認証設定

Serverless FrameworkがAWSにアクセスできるように設定してあげる必要があります。

基本マニュアル通りなのですが、こちらで詳しく日本語で解説してくれていました。

今から始めるServerless Frameworkで簡単Lambda開発環境の構築 | Developers.IO
こんにちは、臼田です。 皆さん、AWS Lambdaは使っていますか? ちょっとしたスクリプトの実行から、様々なピタゴラ装置までこなせるLambdaはいいですよね。 でも、Lambdaのスクリプトを作成するときのデバッグ …

私は元々aws-cli の設定をしてあるので、特に何もしなくて良い(はず)。

ドメインの設定

${self:custom.SUBDOMAIN.${self:provider.stage}}.${self:custom.DOMAIN} に該当するドメインをRoute53に設定して、Credientialまで設定する必要があります。

私は、moyashidaisuke.com というドメインを持っていて、Route53に設定済なので、これを流用する事にします。

stageがdev の場合のドメインは、

  SUBDOMAIN:
    dev: api-dev
    stg: api-stg
    demo: api-demo

なので、api-dev.moyashidaisuke.com になるはず。

これを設定していきます。

証明書を登録。us-east-1で設定する事を忘れずに。

ssmに登録

AWS SystemManagerのパラメータストアに環境変数を登録する必要があるので、登録していきます。必要なものは以下の通り。

  • /envvars/dev/FIREBASE_DATABASE_URL
  • /envvars/dev/FIREBASE_STORAGE_BUCKET
  • /envvars/dev/FIREBASE_type
  • /envvars/dev/FIREBASE_project_id
  • /envvars/dev/FIREBASE_private_key_id
  • /envvars/dev/FIREBASE_private_key
  • /envvars/dev/FIREBASE_client_email
  • /envvars/dev/FIREBASE_client_id
  • /envvars/dev/FIREBASE_auth_uri
  • /envvars/dev/FIREBASE_token_uri
  • /envvars/dev/FIREBASE_auth_provider_x509_cert_url
  • /envvars/dev/FIREBASE_client_x509_cert_url
  • /envvars/dev/FIREBASE_WEB_API_KEY
  • /singleton/domain_name

ほとんどがFirebaseの設定です。前回作成したローカル環境用の設定をそのまま流用する事にします。

domain_name 以外は認証有りなので、タイプを「安全な文字列」にする事をわすれずに(domain_name は違うので注意。一回間違えた)。リージョンはap-northeast-1

done(めんどくさかった🤮

いざ実行

いざ実行します。

サブコマンドを一つずつ実行します。

$ npm run build                                                  

> mamori-i-japan-api@0.1.0 prebuild /Library/WebServer/mamori-i-japan-api
> rimraf dist


> mamori-i-japan-api@0.1.0 build /Library/WebServer/mamori-i-japan-api
> nest build

無事成功。dist フォルダにjsファイルが生成されています。

$ npx serverless create_domain --stage dev

Serverless: Custom domain api-dev.moyashidaisuke.com was created.
            New domains may take up to 40 minutes to be initialized.

成功っぽい。40分、、、🤮

Route53見たら何かできてました。

$ npx serverless deploy --stage dev
Serverless: [ LayersPlugin ]: => default
... ○ Downloading package.json from bucket...
... ○ package.json does not exists at bucket...
... ○  Changes identified ! Re-installing...
... ∅ [warning] "yarn.lock" file does not exists!
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated buffer@4.9.1: This version of 'buffer' is out-of-date. You must update to v4.9.2 or newer
npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
npm WARN deprecated @types/chalk@2.2.0: This is a stub types definition for chalk (https://github.com/chalk/chalk). chalk provides its own type definitions, so you don't need @types/chalk installed!
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN ws@7.3.0 requires a peer of bufferutil@^4.0.1 but none is installed. You must install peer dependencies yourself.
npm WARN ws@7.3.0 requires a peer of utf-8-validate@^5.0.2 but none is installed. You must install peer dependencies yourself.
npm WARN jsdom@15.2.1 requires a peer of canvas@^2.5.0 but none is installed. You must install peer dependencies yourself.
npm WARN mamori-i-japan-api@0.1.0 No repository field.


> protobufjs@6.9.0 postinstall /Library/WebServer/mamori-i-japan-api/.serverless/layers/nodejs/node_modules/protobufjs
> node scripts/postinstall


> @nestjs/core@7.1.1 postinstall /Library/WebServer/mamori-i-japan-api/.serverless/layers/nodejs/node_modules/@nestjs/core
> opencollective || exit 0

added 742 packages from 426 contributors and audited 2461 packages in 26.872s
found 2 low severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details

... ○ Created layer package /Library/WebServer/mamori-i-japan-api/.serverless/contact-tracing-api-dev-nodejs-default.zip (29.3 MB)
... ○ Uploading layer package...
Access Denied

uploadの前後で失敗するので、s3を見てみるとdeploymentBucket に指定されているcontact-tracing-api-serverless が作成されていない。

serverless-deployment-bucket がうまく動いていないかな〜と思いつつ、bucketを手動で作成しよう、、、と思ったところで、bucket前は全世界で一意である必要があるので、本家さんが作成したbucket名とかぶっていて、bucketの作成に失敗しているのだろう、と。

そこで、設定を少し変えます。

   deploymentBucket:
-    name: contact-tracing-api-serverless
+    name: contact-tracing-api-serverless-daisuke-fukuda

エラーメッセージが変わった。

... ○ Uploading layer package...
The specified bucket does not exist

やっぱり自動でbucketは作成してくれないようなので、手動で作成します。

再チャレンジ。

けっこう時間がかかりますが、進捗状況はCloudFormationで確認できます。


Serverless: [ LayersPlugin ]: => default
... ○ Downloading package.json from bucket...
... ○ package.json does not exists at bucket...
... ○  Changes identified ! Re-installing...
... ∅ [warning] "yarn.lock" file does not exists!
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated buffer@4.9.1: This version of 'buffer' is out-of-date. You must update to v4.9.2 or newer
npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
npm WARN deprecated @types/chalk@2.2.0: This is a stub types definition for chalk (https://github.com/chalk/chalk). chalk provides its own type definitions, so you don't need @types/chalk installed!
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN ws@7.3.0 requires a peer of bufferutil@^4.0.1 but none is installed. You must install peer dependencies yourself.
npm WARN ws@7.3.0 requires a peer of utf-8-validate@^5.0.2 but none is installed. You must install peer dependencies yourself.
npm WARN jsdom@15.2.1 requires a peer of canvas@^2.5.0 but none is installed. You must install peer dependencies yourself.
npm WARN mamori-i-japan-api@0.1.0 No repository field.


> protobufjs@6.9.0 postinstall /Library/WebServer/mamori-i-japan-api/.serverless/layers/nodejs/node_modules/protobufjs
> node scripts/postinstall


> @nestjs/core@7.1.1 postinstall /Library/WebServer/mamori-i-japan-api/.serverless/layers/nodejs/node_modules/@nestjs/core
> opencollective || exit 0

added 742 packages from 426 contributors and audited 2461 packages in 31.271s
found 2 low severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details

... ○ Created layer package /Library/WebServer/mamori-i-japan-api/.serverless/contact-tracing-api-dev-nodejs-default.zip (29.3 MB)
... ○ Uploading layer package...
... ○ OK...
... ○ New layer version published...
... ○ Uploading remote /Library/WebServer/mamori-i-japan-api/package.json...
... ○ OK...
... ○ Adding layers...
... ✓ function.lambda-main - arn:aws:lambda:ap-northeast-1:*********:contact-tracing-api-dev-nodejs-default:1
... ✓ function.lambda-swagger - arn:aws:lambda:ap-northeast-1:*********:contact-tracing-api-dev-nodejs-default:1
... ✓ function.lambda-schedule - arn:aws:lambda:ap-northeast-1:*********:contact-tracing-api-dev-nodejs-default:1


Serverless: Using deployment bucket 'contact-tracing-api-serverless-daisuke-fukuda'
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Installing dependencies for custom CloudFormation resources...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service contact-tracing-api.zip file to S3 (186.39 KB)...
Serverless: Uploading custom CloudFormation resources...
Serverless: Validating template...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
...............................................................................................
Serverless: Stack create finished...
Service Information
service: contact-tracing-api
stage: dev
region: ap-northeast-1
stack: contact-tracing-api-dev
resources: 31
api keys:
  None
endpoints:
  ANY - https://5boc93w30c.execute-api.ap-northeast-1.amazonaws.com/dev/
  ANY - https://5boc93w30c.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
  ANY - https://5boc93w30c.execute-api.ap-northeast-1.amazonaws.com/dev/swagger
  ANY - https://5boc93w30c.execute-api.ap-northeast-1.amazonaws.com/dev/swagger/{proxy+}
functions:
  lambda-main: contact-tracing-api-dev-lambda-main
  lambda-swagger: contact-tracing-api-dev-lambda-swagger
  lambda-schedule: contact-tracing-api-dev-lambda-schedule
layers:
  None


Serverless: [ LayersPlugin ]: => Layers Info
... ○ function.lambda-main = layers.arn:aws:lambda:ap-northeast-1:*********:contact-tracing-api-dev-nodejs-default:1
... ○ function.lambda-swagger = layers.arn:aws:lambda:ap-northeast-1:*********:contact-tracing-api-dev-nodejs-default:1
... ○ function.lambda-schedule = layers.arn:aws:lambda:ap-northeast-1:*********:contact-tracing-api-dev-nodejs-default:1


Serverless: Created basepath mapping.

Serverless Domain Manager Summary
Domain Name
  api-dev.moyashidaisuke.com
Distribution Domain Name
  Target Domain: d13ggeqz72u24d.cloudfront.net
  Hosted Zone Id: Z2FDTNDATAQYW2
Serverless: Prune: Running post-deployment pruning
Serverless: Prune: Querying for deployed function versions
Serverless: Prune: Querying for deployed layer versions
Serverless: Prune: contact-tracing-api-dev-lambda-main has 1 additional version published and 0 aliases, 0 versions selected for deletion
Serverless: Prune: contact-tracing-api-dev-lambda-swagger has 1 additional version published and 0 aliases, 0 versions selected for deletion
Serverless: Prune: contact-tracing-api-dev-lambda-schedule has 1 additional version published and 0 aliases, 0 versions selected for deletion
Serverless: Prune: Pruning complete.
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

正常終了!

作成したドメインにアクセスしてみると、

動いています!

ローカルで動かした管理画面からもつながりましたので、Firebaseへの接続も大丈夫そうです。

デプロイされたもの確認

Lambda Layer

設定ファイルをみた限りでは、LambdaLayer使ってないのではと思ったのですが、デプロイされたものを見ると使われているようです。

Lambdaのメインのところには、dist以下のファイルしか含まれていないので、node_modules 配下のファイルがLayerに含まれていそうです。(デプロイログ的にもそんな雰囲気)

これがどこで行われているのか、改めて調べてみます。

logを眺めた感じserverless-layers がやっているようだったので、こちらをコメントアウトして実行してみた結果、確かにlayerが作成されなかったので、このpluginが実行してるようです。

改めてpluginのドキュメントを読んでみると、

It attaches automatically layers for each function

という事で、plugin突っ込むだけで色々やってくれるようです。素晴らしい。

改めて調べたら、こちらのやり方と一緒でしたね。

Serverless Framework+TypeScriptのプロジェクトでnode_modulesをAWS Lambda Layers化しよう | Developers.IO
Serverless Framework+TypeScriptのプロジェクトで、serverless-layersプラグインを使ってみました。簡単に依存モジュールをLambda Layersとしてデプロイできます。

ちなみに、yarn.lock のwarningが出ているのは、最新だと修正されていました。

Merge pull request #33 from ykoba0523/removed-existence-check · agutoli/serverless-layers@66d3e93
Removed unnecessary existence checks

deploymentBucketが自動生成されない

Bucketがある場合のlogも出力されていないので、そもそもpluginが動いていないと判断。

MikeSouza/serverless-deployment-bucket
Create and configure the custom Serverless deployment bucket. - MikeSouza/serverless-deployment-bucket

試しに他のpluginをコメントアウトして実行したら、何か動きました。plugin同士の噛み合わせが悪い模様。

Serverless: Creating deployment bucket 'contact-tracing-api-serverless-daisuke-fukuda'...
Serverless: Applied SSE (AES256) to deployment bucket

色々組み合わせを試した結果、serverless-layersserverless-deployment-bucket を同時に使用する発生しました。

新規でs3のbuckeを作成する時だけ発生するので、serverless-layers を一回コメントアウトしてdeployしてあげれば、以降は問題なく使えます。

コード読んだのですが、ちょっと良くわからなかったです。pluguin同士の噛み合わせの話なので、どちらにissueを出せば良いんだ、、、

まとめ

微妙にはまりつつも、Serverless Framework + AWS Lambda + TypeScript + Firestore でデプロイが実行できるようになりました。

LambdaLayerまで簡単に設定してくれるのは超良いですね。初めてだったので設定ファイル理解したりで時間とりましたが、一度わかっちゃえばコピペで使い回せますね。

というか割と最近目のpluginも使ってくれていて、これがOSSで公開されてるとかめっちゃ素晴らしいです。改めて感謝です。

次回はFirebaseにデプロイ(rulesとかが対象)の予定。

コメント

タイトルとURLをコピーしました