今回もServerless Frameworkに関する話題。
前回 → Serverless FrameworkでPythonのコードをAWSLambdaにデプロイする
前々回 → シャニマスのノウハウブックを管理するツール作った(Nuxt3+Serverless Framework)
今回はserverless-layersというプラグインを使ってnode_modulesディレクトリをレイヤー化し、複数のLambda関数で使いまわすといった構成を作ったときの備忘録になります。
Serverless Frameworkを導入するリポジトリの概要
- 2つのLambda関数が存在する
- どちらもindex.jsの中にハードコートされたURLの配列に対してaxiosでリクエストを送る
- http-status-200-healthcheck/index.js
- HTTPステータスをチェックする
- レスポンスステータスとして200番が返ってきたら正常と見なす
- certificate-healthcheck/index.js
- SSL証明書の有効期限をチェックする
- レスポンスからSSL証明書の情報を読み取り、有効期限が1週間以上先だったら正常と見なす
- 異常なステータスを検知したときSNSで自分のメールアドレスにメール通知を行う
- ↑のLambda関数を含むリポジトリについて手動でAWSにデプロイしていた頃の自分用の覚え書きが大量に含まれていることや、ハードコートしてるURLの配列などがあるためにプライベートリポジトリとなっています。
- 今回の記事を作成するにあたって2つのLambda関数で1つのレイヤーを使いまわすような構成のリポジトリを別途作成しました。
リポジトリ → https://github.com/imo-tikuwa/serverless-layers-example
- 今回の記事を作成するにあたって2つのLambda関数で1つのレイヤーを使いまわすような構成のリポジトリを別途作成しました。
serverless.ymlや環境の構成について
※SNS_TOPIC_ARN環境変数は↑のリポジトリの概要に書いたメール通知を行うのに必要なものになります。Serverless Frameworkを導入するのに必要なものではないので注意。
service: lambda-healthcheck-functions frameworkVersion: '3' provider: name: aws runtime: nodejs16.x region: ap-northeast-1 # 手動で作成したLambda関数と名前を被らないようにする stage: new # Lambda関数内でメール通知が必要な場合、完了処理としてAWSSNSを使用したメール通知を行う # ※SNSはserverlessの中に仕組化はしておらず、各index.jsの中でaws-sdkを介してのメール通知を行っている # そのため、serverless frameworkが自動生成するIAMロールに以下のロールを明示する必要がある iamRoleStatements: - Effect: "Allow" Action: "sns:Publish" Resource: ${env:SNS_TOPIC_ARN} # パッケージング対象について全てを除外 package: individually: true exclude: - "**/*" functions: # SSL証明書のヘルスチェック certificate-healthcheck: handler: certificate-healthcheck/index.handler timeout: 180 environment: SNS_TOPIC_ARN: ${env:SNS_TOPIC_ARN} # 1日1回日本時間で午前3時に実行 events: - schedule: cron(0 18 * * ? *) # パッケージング対象についてindex.jsのみを指定 package: include: - "certificate-healthcheck/index.js" # HTTPステータスのヘルスチェック http-status-200-healthcheck: handler: http-status-200-healthcheck/index.handler timeout: 180 environment: SNS_TOPIC_ARN: ${env:SNS_TOPIC_ARN} # 2時間毎に実行 events: - schedule: cron(0 1-23/2 * * ? *) # パッケージング対象についてindex.jsのみを指定 package: include: - "http-status-200-healthcheck/index.js" plugins: - serverless-layers custom: serverless-layers: # レイヤー化したいnode_modules/のzipをアップするS3バケット名を指定 layersDeploymentBucket: ${self:service}-layers-${aws:accountId} compatibleRuntimes: ['nodejs16.x']
services: app: build: ./docker/app volumes: # Lambda関数を含むディレクトリ - ./src:/src:cached tty: true environment: AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY SNS_TOPIC_ARN: $SNS_TOPIC_ARN
COMPOSE_PATH_SEPARATOR=: COMPOSE_FILE=docker-compose.yml # serverless frameworkのデプロイが行えるユーザーのアクセスキー/シークレットアクセスキー AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= # SNS通知用のARN SNS_TOPIC_ARN=
- タイムアウト設定、実行時間設定、レイヤーの互換性のあるランタイム情報などは、手動で構築していた頃同じとなるようserverless.ymlで定義
- タイムアウトは各関数内でtimeout: で設定するだけ。
- 実行時間はevents: でcron式を書くだけでEventBridge(CloudWatch Event)を作成してくれてとても便利
- デプロイユーザーの許可ポリシー設定せずにデプロイしてみたら、EventBridgeのPutRuleが必要(events:PutRule)とエラーが出ました
- 今回はEventBridgeのアクションは全部許可しました(events:*)
- serverless removeからLambda関数を削除するときとかevents:DeleteRuleが必要になるんじゃないかなぁと思っている(試していない)
{ "name": "lambda-healthcheck-functions", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "axios": "^0.27.2", "moment": "^2.29.4" } "devDependencies": { "prettier": "^2.8.4", "serverless": "^3.27.0", "serverless-layers": "^2.6.1" } }
{ "name": "certificate-healthcheck", "version": "1.0.0", "description": "", "main": "index.js", "author": "imo-tikuwa", "license": "ISC", "dependencies": { "aws-sdk": "^2.1213.0" } }
{ "name": "http-status-200-healthcheck", "version": "1.0.0", "description": "", "main": "index.js", "author": "imo-tikuwa", "license": "ISC", "dependencies": { "aws-sdk": "^2.1213.0" } }
- serverless.ymlがあるディレクトリのpackage.jsonのdependenciesにレイヤー化したいパッケージを指定
- 別のディレクトリにあるpackage.jsonを使いたい場合、dependenciesPathというオプションでserverless.ymlからの相対パスを指定することで設定可能
- /src/certificate-healthcheck/package.json と /src/http-status-200-healthcheck/package.json はそれぞれの関数内で使用するパッケージを管理します
- 今回はLambda(Node16.xランタイム)でデフォルトで使用可能となっているaws-sdk(v2)を開発環境用にインストールしています
Severless Framework導入の前後の構成図
Serverless Frameworkを導入したリポジトリについて前後のイメージ図を作成してみました。
↑Serverless Framework導入前の構成図
↑Serverless Framework導入後の構成図
- 導入前、導入後ともにLambdaのコード自体はほぼ同一
導入前、レイヤー内に含まれるライブラリ(axios、momentとか)をインポートするのに/opt/node_modules/axios としていたのを /opt/nodejs/node_modules/axios のように修正する必要があった- 2023/03/18追記 これ全くの間違いでした。。
レイヤーに含めるパッケージ特に工夫せずにいつも通りのインポートの記述でよかった// ↓NG const axios = require("/opt/nodejs/node_modules/axios"); const moment = require("/opt/nodejs/node_modules/moment"); // ↓OK const axios = require("axios"); const moment = require("moment");
ローカルでの開発時はserverless-layersプラグインを導入した状態で
serverless invoke localから実行することで、Lambdaで動かすときと同じような感じでレイヤー内のnode_modulesからインポートが行われる模様
- 2023/03/18追記 これ全くの間違いでした。。
- serverless-layersプラグインを使用してレイヤー化する場合、nodejs/node_modulesというディレクトリ構成のzipが作成され、S3にアップ⇒レイヤー化される模様
- SNS自体はserverless.ymlから何かを設定するわけではなく、手動で構築した頃のものをそのまま使用
- そもそもserverlessの設定でメール通知可能な状態まで持っていけるのか不明
- serverlessから構築しているわけではないため、Serverless Frameworkがデプロイ時にLambda関数に対して自動で設定する実行ロールにsns:Publishが不足する
- 口述してるiamRoleStatementsの設定によって解決できた
iamRoleStatementsについて
- デプロイ時にLambda関数と合わせて作成されるIAMロールにロールを追加したいときに使う紐づくIAMロールはLambda関数の設定タブ>アクセス権限>実行ロールより確認可能
iamRoleStatementsを設定しなかった場合以下のようなIAMロールとなるはず。
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup" ], "Resource": [ "arn:aws:logs:ap-northeast-1:@@@@@@@@@@@@:log-group:/aws/lambda/[ServerlessFrameworkのservice名]-[ServerlessFrameworkのstage名]*:*" ], "Effect": "Allow" }, { "Action": [ "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:ap-northeast-1:@@@@@@@@@@@@:log-group:/aws/lambda/[ServerlessFrameworkのservice名]-[ServerlessFrameworkのstage名]*:*:*" ], "Effect": "Allow" } ] }
今回はSNSの送信(Publish)ポリシーが必要で、対象のリソースとなるSNSのARNは環境変数として開発環境内に存在する状態なので以下のようになりました。
provider: iamRoleStatements: - Effect: "Allow" Action: "sns:Publish" Resource: ${env:SNS_TOPIC_ARN}
上記の設定を行った状態でデプロイしたところ↓に添付する画像のようにLambda関数の実行ロールに指定のアクションに関する定義が追加されることが確認できました。
その他、今回は試してはいないですが、以下のような感じでロール自体を別途作成することもできるみたい?(ChatGPTに聞いた)。
# 以下、動作確認できてないです。。 role: name: ${self:service}-sns-role statements: - Effect: Allow Action: - sns:Publish Resource: ${env:SNS_TOPIC_ARN}
パッケージ対象の設定について
最初はserverless.ymlのパッケージ対象の設定について以下のような記述をしていました。
# パッケージング対象について一旦全てを除外し、それぞれの関数をインクルード package: exclude: - "**/*" include: - "certificate-healthcheck/index.js" - "http-status-200-healthcheck/index.js"
上の設定でデプロイした場合、それぞれのLambda関数画面でcertificate-healthcheck/index.jsとhttp-status-200-healthcheck/index.jsがエクスプローラ上に含まれてしまいました。。
これは、参考サイト内の情報を参考に関数ごとにインクルードの設定を記載することで解決しました。
抜粋すると以下のような感じ
# パッケージング対象について一旦全てを除外 package: # ↓関数ごとにzipを作成する individually: true # 除外設定はこのままでOK exclude: - "**/*" functions: # SSL証明書のヘルスチェック certificate-healthcheck: handler: certificate-healthcheck/index.handler package: include: # ↓SSL証明書のヘルスチェック関数のみをパッケージ対象とする - "certificate-healthcheck/index.js" # HTTPステータスのヘルスチェック http-status-200-healthcheck: handler: http-status-200-healthcheck/index.handler package: include: # ↓HTTPステータスのヘルスチェック関数のみをパッケージ対象とする - "http-status-200-healthcheck/index.js"
デプロイした関数やレイヤーの確認
Lambda関数
関数名はServerless Frameworkの [サービス名] + [stage名] + [関数名] となる模様
Lambdaレイヤー
レイヤー名は [サービス名] + [stage名] + “nodejs-default”となった
以下はSSL証明書のヘルスチェック関数の方の概要
関数にレイヤーが紐づいていること、EventBridgeのトリガーが紐づいていることが確認できた
同関数の設定タブ
serverless.ymlで関数内に設定した環境変数が適切に設定されていることが確認できた
EventBridgeのトリガー
defaultのイベントバスにevents:で設定したcron式がそのまま登録されていることを確認
反省点
- 各Lambda関数にハードコートしてしまってるチェック対象のURLについて関数のトリガー(EventBridge)の入力定数を活用して外から渡すようにしてあげるとより使い勝手の良いプログラムになりそう
- https://www.serverless.com/plugins/serverless-event-constant-inputs
- 次回作成するプログラムではこの辺考慮した作りにしたい
参考サイト
- ServerlessFrameworkでfunction毎にデプロイするファイルを指定する方法 – Qiita
- 参考情報の通り、グローバルな設定として全てをexclude、individually:trueを設定してfunctionごとにzip化&functionのそれぞれに個別でinclude設定を書くことでLambda上で意図したファイルの表示が出来るようになりました
- [Tips]Serverless Frameworkでプラグイン「serverless-pseudo-parameters」にさよならバイバイしたお話|ハンズラボ株式会社
- serverless.yml内におけるAWSのアカウントIDなど情報を参照する方法について参考にさせていただきました
- Serverless Framework で iamRoleStatements を設定する – Qiita
- iamRoleStatementsの具体的な設定について参考にさせていただきました
- agutoli/serverless-layers: Serverless.js plugin that implements AWS Lambda Layers which reduces drastically lambda size, warm-up and deployment time.
- serverless-layersプラグインのリポジトリ。Node.jsで使用可能なオプション(dependenciesPath,compatibleRuntimes)について確認するなどしました
- Amazon CloudWatch の許可リファレンス – Amazon CloudWatch
- (AWS公式)events:PutRuleポリシーが無くてデプロイこけたときに読みました
- Serverless FrameworkでAWS Lambda関数をローカル環境でエミュレーションで動かす – CLOVER?
- 今回はリポジトリ内に既存で存在するtest.jsというローカルでの動作確認を行うスクリプトを使用することで動作確認できてるので今回は使いませんでした。
- ただ、serverless frameworkとしてローカルでの動作確認を行う仕組みがあるので、参考サイトにあるようなserverless invoke localから動作確認をするのが良さそう。