カテゴリー
Serverless FrameworkでもAWS で「APIGateway」→「VPC Lambda」→「EFS」を一発で構築してみる
※ 当ページには【広告/PR】を含む場合があります。
2023/04/20
2024/01/19

前回はAWS VPC Lambdaの構築方法の基礎を学ぶためにダッシュボードから手動で一つ一つサービスを立ち上げていきました。
やっていただくと分かるように、ダッシュボードからVPC Lambdaを構築する場合、非常に面倒な手順を正しい順番に設定していく必要がありました。
今回はこの話の続きで、Serverless Frameworkを使ってダッシュボードなしのコマンドから楽に一発構築を目指してみましょう。
通常のSAMをServerless Framework(SLS)で一発構築
ここからは復習も兼ねて、nodejsプロジェクトでSLSの導入〜簡単なLambdaのデプロイまでの手順を解説してみます。
SLSをnodejsプロジェクトに導入する
お手元の環境に既にnode.jsは導入済みで、
yarn init
それでは、以下のコマンドでSLSをローカルに導入します。
$ yarn add serverless -D
$ npx serverless --version
Framework Core: 3.38.0 (local)
Plugin: 7.2.0
SDK: 4.5.1
もしも、yarn等でslsコマンドを呼び出したい場合には、
package.json
scripts
{
...
"scripts": {
"serverless": "serverless",
"sls": "sls",
...
},
...
}
SLSのインストールも終わったので、プロジェクトフォルダに最小の構成として、
handler.mjs
serverless.yml
おおむね以下のような感じの配置から実装をスタートします。
$ touch handler.mjs serverless.yml
$ tree
.
├── package.json
├── handler.mjs
└── serverless.yml
SLSでLambdaをデプロイする
最初はもっとも簡単なServerlessの"Hello World"を普通のLambdaで作成してみます。
先程生成した空のファイル2つを以下の内容で編集しておきましょう。
service: apigw-vpclmb-lab
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs20.x
stage: dev
region: ap-northeast-1
package:
patterns:
- '!**'
- handler.mjs
functions:
hello:
handler: handler.handler
events:
- httpApi:
path: /
method: '*'
ちなみに、AWS Lambdaのnodejsの最新のランタイムは
nodejs20
ここらへんは、気づいたときにはランタイムのバージョンがどんどん非推奨になってしまうため、可能な限り最新のバージョンのランタイムにしておくといざというとき延命できます。
また、ハンドラは以下の通りです。
export const handler = async(event) => {
const response = {
statusCode: 200,
body: JSON.stringify('こんにちは!はじめてのVPC Lambda From Serverless!!'),
};
return response;
};
こちらもすでに
nodejs18
Commonjs
handler.js
ESModule
handler.mjs
これをデプロイ後に実行してみると、
$ npx sls deploy --verbose
$ npx sls invoke --function hello --verbose
{
"statusCode": 200,
"body": "\"こんにちは!はじめてのVPC Lambda From Serverless!!\""
}
とレスポンスが当然ながら返ってきます。
curlなどでAPIレスポンスも試してみましょう。
$ curl -XGET https://*************.execute-api.ap-northeast-1.amazonaws.com/
"こんにちは!はじめてのVPC Lambda From Serverless!!"
もしくはブラウザから直接APIのエンドポイントURLを叩いてみても、
723x175

となりAWS上の指定したリージョンのどこかに無事デプロイされていることが確認できます。
この時点では、単なる
Http API
681x216

この場合、Lambdaはリージョンの「どこか」には存在しますが、ユーザーの所有下にあるVPC内にはいないので、「VPC Lambda」ではありません。
ではここから、Serverless FrameworkでVPC Lambdaに仕上げていくように変更していきます。
VPC Lambdaの追加手順としては、
1. serverless.ymlから.envファイルを使えるようにする
2. VPC Lambdaに対応したIAMロールを指定する
3. VPC Lambdaに必要な関数設定を作成する
4. リソースでVPCの設定(セキュリティグループ)を行う
5. リソースでEFSの設定(FS本体・マウントターゲット・アクセスポイント)を行う
という点を踏まえた上で、以降で詳しく解説していきます。
VPC LambdaをServerlessでも一括構築する
そのまま先程の
serverless.yml
682x328

まずは一気に修正済みの
serverless.yml
service: apigw-vpclmb-lab
frameworkVersion: '3'
#👇解説ポイント①
useDotenv: true
provider:
name: aws
runtime: nodejs20.x
stage: dev
region: ap-northeast-1
#👇解説ポイント②
iam:
role: !Sub arn:aws:iam::${AWS::AccountId}:role/${env:LAMBDA_CUSTOM_ROLE}
package:
patterns:
- '!**'
- handler.mjs
functions:
hello:
handler: handler.handler
#👇解説ポイント③
environment:
EFS_MOUNT_DIR: ${env:EFS_MOUNT_DIR}
fileSystemConfig:
localMountPath: ${env:EFS_MOUNT_DIR}
arn: !GetAtt ["EFSAccessPoint", "Arn"]
vpc:
securityGroupIds:
- !GetAtt ["LambdaSecurityGroup", "GroupId"]
subnetIds:
- ${env:LAMBDA_SUBNET_ID}
dependsOn:
- EFSMountTargetA
- EFSMountTargetB
- EFSMountTargetC
events:
- httpApi:
path: /
method: '*'
resources:
Resources:
#👇解説ポイント④
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
#GroupName: sg-lambda 👈 スタックエラーを起こすので、固定でグループ名を与えてはNG
GroupDescription: Lambda Access for EFS
VpcId: ${env:VPC_ID}
SecurityGroupIngress:
- IpProtocol: "-1"
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 2049
ToPort: 2049
Tags:
- Key: Name
Value: LambdaEFSSecurityGroup
EFSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
#GroupName: sg-efs 👈 スタックエラーを起こすので、固定でグループ名を与えてはNG
GroupDescription: EFS Allowed Ports
VpcId: ${env:VPC_ID}
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 2049
ToPort: 2049
SourceSecurityGroupId: !GetAtt ["LambdaSecurityGroup", "GroupId"]
Description: from Lambda
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
IpProtocol: "-1"
Tags:
- Key: Name
Value: EFSSecurityGroup
#👇解説ポイント⑤
EFSFileSystem:
Type: AWS::EFS::FileSystem
Properties:
FileSystemTags:
- Key: Name
Value: MyFileSystem
BackupPolicy:
Status: ENABLED
Encrypted: true
LifecyclePolicies:
- TransitionToIA: AFTER_30_DAYS
PerformanceMode: generalPurpose
#👇解説ポイント⑥
#MountTargetをアベイラビリティゾーンに全て置く場合には複数を個別に書く
EFSMountTargetA:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SecurityGroups:
- !Ref EFSSecurityGroup
SubnetId: ${env:LAMBDA_SUBNET_ID_1}
DependsOn: EFSFileSystem
EFSMountTargetB:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SecurityGroups:
- !Ref EFSSecurityGroup
SubnetId: ${env:LAMBDA_SUBNET_ID_2}
DependsOn: EFSFileSystem
EFSMountTargetC:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SecurityGroups:
- !Ref EFSSecurityGroup
SubnetId: ${env:LAMBDA_SUBNET_ID_3}
DependsOn: EFSFileSystem
#👇解説ポイント⑦
EFSAccessPoint:
Type: AWS::EFS::AccessPoint
Properties:
FileSystemId: !Ref EFSFileSystem
PosixUser:
Uid: "1001"
Gid: "1001"
RootDirectory:
Path: ${env:EFS_ROOT_DIR}
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "770"
AccessPointTags:
- Key: Name
Value: hello-func-ap
DependsOn: EFSFileSystem
はい、VPCにしたいだけなのですが一気に複雑化&難解化しました。
では追記した一つ一つ解説していきます。
解説ポイント① 〜 .envファイルで個人情報の漏洩をガードする
今回の話に限らず、プライベートな趣味レベルのボッチ開発などと違い、第三者とプロジェクトを共有したい場合、
serverless
ということで、通常はgitから除外した自分だけが参照できる
.envファイル
Serverless Frameworkの場合、以下を書き足すと.envファイルをビルド時に読み込んでくれます。
例えば、以下のような
.env
HOGE_SECERT=hogeno_himitu
この
.env
serverless.yml
...
useDotenv: true
...
#👇ENV変数を呼び出す
USER: ${env:HOGE_SECERT}
という用法で利用することができます。
今回の実装を試されたい方は、以下のような
.env
VPC_ID=vpc-<あなたのVPC ID>
AWS_REGION=ap-northeast-1
LAMBDA_SUBNET_ID_1=subnet-<1つ目のアベイラビリティゾーンのサブネットID>
LAMBDA_SUBNET_ID_2=subnet-<2つ目のアベイラビリティゾーンのサブネットID>
LAMBDA_SUBNET_ID_3=subnet-<3つ目のアベイラビリティゾーンのサブネットID>
EFS_MOUNT_DIR=/mnt/efs
EFS_ROOT_DIR=/hello-func
LAMBDA_CUSTOM_ROLE=my-vpclambda-efs-role
なお、各ENV変数の諸元は、
解説ポイント② 〜 登録済みのIAMロールをアタッチする
次にデプロイするlambdaに必要な実行権限ロールを
[provider] > [iam] > [role]
ロールの割り当て方法がいくつかありますが、前回と同様、既に作成していた
my-vpclambda-efs-role
...
provider:
...
iam:
role: !Sub arn:aws:iam::${AWS::AccountId}:role/${env:LAMBDA_CUSTOM_ROLE}
YAML形式で、CloudFormationの組み込み関数(の略記形)である
「!Sub」
${AWS::AccountId}
${env:LAMBDA_CUSTOM_ROLE}
!Sub
解説ポイント③ 〜 VPC Lambdaに仕上げる
通常のLambdaをVPC Lambdaにするためには、通常の関数定義に加えて、
fileSystemConfig
vpc
該当する部分を抜粋したものが以下のコードです。
...
functions:
hello:
handler: handler.handler
#👇Lambdaが使う環境変数を登録
environment:
EFS_MOUNT_DIR: ${env:EFS_MOUNT_DIR}
#👇EFSの指定
fileSystemConfig:
localMountPath: ${env:EFS_MOUNT_DIR}
arn: !GetAtt ["EFSAccessPoint", "Arn"]
#👇VPC設定
vpc:
securityGroupIds:
- !GetAtt ["LambdaSecurityGroup", "GroupId"]
subnetIds:
- ${env:LAMBDA_SUBNET_ID}
#👇リソースへの依存性
dependsOn:
- EFSMountTargetA
- EFSMountTargetB
- EFSMountTargetC
#...
前回も解説しましたが、VPC Lambdaを利用するには、
そこで、
fileSystemConfig
また、VPC LambdaのVPC設置として、属する
vpc
VPC Lambdaへ仕上げるためには、ここでは以下のリソースが新たに必要となります。
+ LambdaSecurityGroup
+ EFSSecurityGroup
+ EFSFileSystem
+ EFSAccessPoint
+ EFSMountTargetA
+ EFSMountTargetB
+ EFSMountTargetC
ちなみにリソースの名前は自由に決定できます。
なお、
dependsOn
ここでは
dependsOn
さて、これらのリソースは、Serverless Frameworkネイティブでは直接生成する機能をサポートされていないため、
[resources] > [Resources]
解説ポイント④ 〜 VPCのセキュリティグループを作成する
ではまず、デフォルトVPCにセキュリティグループを追加していきましょう。
...
resources:
Resources:
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
#GroupName: sg-lambda 👈 スタックエラーを起こすので、固定でグループ名を与えてはNG
GroupDescription: Lambda Access for EFS
VpcId: ${env:VPC_ID}
SecurityGroupIngress:
- IpProtocol: "-1"
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 2049
ToPort: 2049
Tags:
- Key: Name
Value: LambdaEFSSecurityGroup
EFSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
#GroupName: sg-efs 👈 スタックエラーを起こすので、固定でグループ名を与えてはNG
GroupDescription: EFS Allowed Ports
VpcId: ${env:VPC_ID}
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 2049
ToPort: 2049
SourceSecurityGroupId: !GetAtt ["LambdaSecurityGroup", "GroupId"]
Description: from Lambda
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
IpProtocol: "-1"
Tags:
- Key: Name
Value: EFSSecurityGroup
#...
ここでは、セキュリティグループのリソースを
LambdaSecurityGroup
EFSSecurityGroup
セキュリティグループの作成方法もまた前回詳しく説明しましたので、ここでは割愛します。
ポイントはセキュリティグループのインバウンドルールは
SecurityGroupIngress
SecurityGroupEgress
余談でセキュリティグループの名前を
GroupName
Serverless Frameworkを使う場合、セキュリティグループの名前を自分で付けることは一旦諦めましょう。
解説ポイント⑤ 〜 EFSを準備する
次にEFS本体のリソースを
EFSFileSystem
...
resources:
Resources:
#...
EFSFileSystem:
Type: AWS::EFS::FileSystem
Properties:
FileSystemTags:
- Key: Name
Value: MyFileSystem
BackupPolicy:
Status: ENABLED
Encrypted: true
LifecyclePolicies:
- TransitionToIA: AFTER_30_DAYS
PerformanceMode: generalPurpose
#...
こちらも
解説ポイント⑥ 〜 マウントターゲットを作成する
先程作成したEFSに更に「マウントターゲット」を追加していきます。
マウントターゲットは各アベイラビリティゾーンに一つ設置することができます。
練習用ではどこか1つのアベイラビリティゾーンを使うくらいでよいと思いますが、本番アプリなどを運用する際には、ネットワークの冗長性を考えて、全てのアベイラビリティゾーンにマウントターゲットを設けることも検討しましょう。
またマウントターゲットの生成の前には必ずEFS本体の生成が完了していないといけませんので、
DependsOn
EFSFileSystem
...
resources:
Resources:
#...
EFSMountTargetA:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SecurityGroups:
- !Ref EFSSecurityGroup
SubnetId: ${env:LAMBDA_SUBNET_ID_1}
DependsOn: EFSFileSystem
EFSMountTargetB:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SecurityGroups:
- !Ref EFSSecurityGroup
SubnetId: ${env:LAMBDA_SUBNET_ID_2}
DependsOn: EFSFileSystem
EFSMountTargetC:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SecurityGroups:
- !Ref EFSSecurityGroup
SubnetId: ${env:LAMBDA_SUBNET_ID_3}
DependsOn: EFSFileSystem
#...
見てのように、全てのアベイラビリティゾーンにマウントターゲットを設置する場合、マウントターゲットごとに設定を記述しないといけません。
また、マウントターゲットには正しいセキュリティグループが設定されているかも良く確認しておくと良いでしょう。
解説ポイント⑦ 〜 アクセスポイントの追加
最後に、EFSのアクセスポイントのリソースを生成します。
...
resources:
Resources:
#...
EFSAccessPoint:
Type: AWS::EFS::AccessPoint
Properties:
FileSystemId: !Ref EFSFileSystem
PosixUser:
Uid: "1001"
Gid: "1001"
RootDirectory:
Path: ${env:EFS_ROOT_DIR}
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "770"
AccessPointTags:
- Key: Name
Value: hello-func-ap
DependsOn: EFSFileSystem
こちらも先行して
EFSFileSystem
DependOn
あとは
また、別記事で
デプロイと実行
VPC Lambdaといえども、Serverless Frameworkでの扱いは通常のLambdaと特に変わりません。
$ npx sls deploy --verbose
Deploying apigw-vpclmb-lab to stage dev (ap-northeast-1)
#...スタックが沢山構成される
✔ Service deployed to stack apigw-vpclmb-lab-dev (113s)
endpoint: ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/
functions:
hello: apigw-vpclmb-lab-dev-hello (79 kB)
Stack Outputs:
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:ap-northeast-1:123456789012:function:apigw-vpclmb-lab-dev-hello:1
HttpApiId: xxxxxxxxxx
ServerlessDeploymentBucketName: apigw-vpclmb-lab-dev-serverlessdeploymentbucket-xxxxxxxxxxxx
HttpApiUrl: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
今回はリソースを多く生成しているので、デプロイ完了まで数分程度時間がかかります。
しばらく待ってエラーが出なければ上手くVPC Lambdaがデプロイされたようです。
あとは実行確認で、
sls invoke
$ npx sls invoke --function hello --verbose
{
"statusCode": 200,
"body": "\"こんにちは!はじめてのVPC Lambda From Serverless!!\""
}
もしくは
curl
$ curl -XGET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
{
"statusCode": 200,
"body": "\"こんにちは!はじめてのVPC Lambda From Serverless!!\""
}
ここまでで、「LambdaはEFSちゃんと使えているの...?どう使うの?」とまっとうなご意見が聞こえて来そうですが、問題なく利用できています。
VPC LambdaとEFSの使い方に関しては、今後
まとめ
今回はVPC Lambdaを一発構築するためのServerless Frameworkの設定を考えていきました。
もう一度、手順のポイントをおさらいすると、
1. serverless.ymlから.envファイルを使えるようにする
2. VPC Lambdaに対応したIAMロールを指定する
3. VPC Lambdaに必要な関数設定を作成する
4. リソースでVPCの設定(セキュリティグループ)を行う
5. リソースでEFSの設定(FS本体・マウントターゲット・アクセスポイント)を行う
というポイントに注意する必要がありました。
手順の内容のイメージがつかみにくい場合には、一度AWSダッシュボードから手動で動作の確認をしてみるとよいでしょう。
参考サイト
以下の参考記事はランタイムをPythonで紹介されている方の構築手順になります。
普段nodejsを使わない方は参考になるかも知れません。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー