この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
こんにちは、中山です。
少し前の話になりますが、CloudFrontのエッジロケーションでLambdaを動作させられるLambda@EdgeがGAになりました。めでたい。エッジコンピューティングのビックウェーブを感じます。
非常に可能性のあるLambda@Edge、とても便利なのですが個人的に少しだけ不満点があります。それはデプロイに手間がかかるという点です。Lambda@Edgeをデプロイするためには通常以下のフローを実施する必要があります。
- CloudFrontディストリビューションの作成
- Lambdaを作成してディストリビューションと関連付ける
- 適宜パーミッションを設定
- Lambdaのコードを修正する度にディストリビューションを更新する
特に4番目が厳しい。執筆時点(2017/08/28)でLambdaとCloudFrontの関連付けはQualified ARNで指定する必要があります。新しいバージョンをPublishする度(つまりLambdaのソースを修正する度)にこの作業を実施しなければならないということです。
サーバレスアーキテクチャを採用している場合、恐らく多くの方はServerless FrameworkやAWS SAMなどのツールを利用していると思います。こういったツールを利用することで簡単にサーバレスアーキテクチャをデプロイ可能です。ただし、残念ながら執筆時点でLambda@Edgeをコア機能としてサポートしているツールは(自分の観測範囲では)無いようでした。
どうしたものかと思っていたのですが、先日Serverless FrameworkでLambda@Edgeをデプロイするためのプラグインが公開されました。これを利用すれば面倒な作業から開放されそうですね。
早速使ってみたので本エントリでご紹介したいと思います。
検証環境
- Serverless Framework: 1.20.2
- serverless-plugin-cloudfront-lambda-edge: 1.0.0
- serverless-s3-sync: 1.3.0
- Node.js: node:8.2.1-alpine(Dockerを利用)
やってみた
詳細な設定方法については該当リポジトリのREADMEを参照してください。といっても使い方は通常のプラグインと同じです。
設定ファイル
今回は以下のような設定にしてみました。
serverless.yml
service: lambda-at-edge
frameworkVersion: ">=1.20.2 <2.0.0"
custom:
config: ${{file(config.yml)}}
s3Sync:
- bucketName: ${{env:S3_BUCKET_NAME}}
localDir: static
provider:
name: aws
runtime: nodejs6.10
stage: ${{self:custom.config.stage}}
region: us-east-1
memorySize: 128
timeout: 1
variableSyntax: "\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}"
package:
individually: true
exclude:
- "**"
plugins:
- serverless-s3-sync
- serverless-plugin-cloudfront-lambda-edge
functions:
rewriter:
handler: src/handlers/rewriter/index.handler
lambdaAtEdge:
distribution: WebsiteDistribution
eventType: viewer-request
package:
include:
- src/handlers/rewriter/*.js
resources: ${{file(resources.yml)}}
27と32 - 34行目が serverless-plugin-cloudfront-edge
の設定です。 eventType
でviewer requestにLambdaを指定しています。 distribution
に設定する文字列はCloudFormationの論理リソース名です。
Lambda@Edgeの場合、デプロイ先となるリージョンは北部バージニアになります。このリージョンから各リージョンにレプリカが作成され、アクセス先となるエッジロケーションでLambdaが実行されるという仕組みになっているようです。
本題から少しはずれますが、7 - 9と26行目はserverless-s3-syncプラグインの設定です。 sls deploy
などのタイミングでローカルディレクトリのファイルをS3バケットにsyncしてくれます。Serverless Framework内でアップロードするオブジェクトも管理したい場合に便利です。今回は static
ディレクトリ以下にHTMLファイルを設置するようにしました。
resources.yml
---
AWSTemplateFormatVersion: "2010-09-09"
Description: Lambda@Edge Sample Stack
Parameters:
RetentionInDays:
Type: Number
Default: ${{self:custom.config.logGroup.retentionInDays}}
S3BucketName:
Type: String
Default: ${{env:S3_BUCKET_NAME}}
AcmIdentifier:
Type: String
Default: ${{env:ACM_IDENTIFIER}}
HostedZoneId:
Type: AWS::Route53::HostedZone::Id
Default: ${{env:HOSTED_ZONE_ID}}
Resources:
RewriterLogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays:
Ref: RetentionInDays
WebsiteBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Ref: S3BucketName
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
WebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: WebsiteBucket
PolicyDocument:
Statement:
- Effect: Allow
Action: s3:GetObject
Resource:
Fn::Sub: ${WebsiteBucket.Arn}/*
Principal: "*"
WebsiteDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Comment:
Ref: AWS::StackName
PriceClass: PriceClass_200
Aliases:
- Ref: S3BucketName
HttpVersion: http2
DefaultRootObject: index.html
Origins:
- Id:
Fn::Sub: S3-${WebsiteBucket}
DomainName:
Fn::GetAtt: [ WebsiteBucket, DomainName ]
S3OriginConfig: {}
DefaultCacheBehavior:
TargetOriginId:
Fn::Sub: S3-${WebsiteBucket}
ForwardedValues:
QueryString: false
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
DefaultTTL: 600
MaxTTL: 600
Compress: true
ViewerCertificate:
SslSupportMethod: sni-only
AcmCertificateArn:
Fn::Sub: arn:aws:acm:${AWS::Region}:${AWS::AccountId}:certificate/${AcmIdentifier}
WebSiteRecordSet:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId:
Ref: HostedZoneId
Name:
Ref: S3BucketName
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2
DNSName:
Fn::GetAtt: [ WebsiteDistribution, DomainName ]
静的ウェブサイト機能を有効化したS3を作成し、それをオリジンとしたCloudFrontディストリビューションを設定しています。今回はカスタムドメインでディストリビューションにアクセスできるよう、レコードセットリソースも作ってみました。
Lambdaのコード
index.html
以外でアクセスしてきた場合は rewrite.html
へ書き換えているだけです。
src/handlers/rewriter/index.js
module.exports.handler = (event, context, callback) => {
console.log(JSON.stringify(event));
const request = event.Records[0].cf.request;
if (request.uri !== '/index.html') {
console.log(`changing ${request.uri} to rewrite.html`);
request.uri = '/rewrite.html';
}
callback(null, request);
};
デプロイ
いつもと同じようにデプロイします。すると以下のような出力が表示されると思います。
$ $(yarn bin)/sls deploy -v
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Updated Lambda assume role policy to allow Lambda@Edge to assume the role
<snip>
Serverless: Checking to see if 1 function needs to be associated to CloudFront
Serverless: Waiting for CloudFront distribution "WebsiteDistribution" to be deployed
.
Serverless: Distribution "WebsiteDistribution" is now in "Deployed" state
Serverless: Adding new Lamba@Edge association for origin-request: arn:aws:lambda:us-east-1:************:function:lambda-at-edge-v1-rewriter:2
Serverless: Updating distribution "WebsiteDistribution" because we updated Lambda@Edge associations on it
Serverless: Waiting for CloudFront distribution "WebsiteDistribution" to be deployed
............................................................................................................................................................................................................................................................
Serverless: Distribution "WebsiteDistribution" is now in "Deployed" state
Serverless: Done updating distribution "WebsiteDistribution"
Done in 528.17s.
ハイライトした箇所に実行内容を記載してくれていますが、まずLambdaのIAM Roleを更新した後、デプロイが終わった段階でディストリビューションを更新、Lambdaとの関連付けを実施しています。
逆に、Lambdaのコードを変更していない(新しいバージョンがPublishされていない)状態で sls deploy
してもディストリビューションの設定は変更されないようです。親切設計です。
$ $(yarn bin)/sls deploy -v
<snip>
Serverless: Waiting for CloudFront distribution "WebsiteDistribution" to be deployed
.
Serverless: Distribution "WebsiteDistribution" is now in "Deployed" state
Serverless: The distribution is already configured with the current versions of each Lambda@Edge function it needs
動作確認
- ディストリビューションにLambdaが関連付けされている
$ aws cloudfront get-distribution-config \
--id E3L5K2SO5WFZ65 \
--query 'DistributionConfig.DefaultCacheBehavior.LambdaFunctionAssociations'
{
"Items": [
{
"EventType": "viewer-request",
"LambdaFunctionARN": "arn:aws:lambda:us-east-1:************:function:lambda-at-edge-v1-rewriter:3"
}
],
"Quantity": 1
}
- IAM Roleの信頼関係に
edgelambda.amazonaws.com
が追加されている
$ aws iam get-role \
--role-name lambda-at-edge-v1-us-east-1-lambdaRole \
--query 'Role.AssumeRolePolicyDocument.Statement[].Principal'
[
{
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
}
]
- Lambdaにリソースポリシーが設定されている
$ aws lambda get-policy \
--function-name arn:aws:lambda:us-east-1:************:function:lambda-at-edge-v1-rewriter:4 \
--region us-east-1 \
--output text | jq
{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "replicator.lambda.GetFunction",
"Effect": "Allow",
"Principal": {
"Service": "replicator.lambda.amazonaws.com"
},
"Action": "lambda:GetFunction",
"Resource": "arn:aws:lambda:us-east-1:************:function:lambda-at-edge-v1-rewriter:4"
}
]
}
test.html
へアクセスするとrewrite.html
が表示される
$ curl https://<FQDN>/test.html
rewirte.html
index.html
にアクセスするとそのまま
$ curl https://<FQDN>/index.html
index.html
まとめ
いかがだったでしょうか。
Serverless Frameworkのプラグインを利用したLambda@Edgeのデプロイについてご紹介しました。今はまだプラグインという位置付けですが、今後コア機能としてLambda@Edgeがイベントタイプにサポートされていくと思います。GitHub上のイシューで議論されているようです。あるいはAWS SAMでもそのうちサポートされていくでしょう。
今回ご紹介したプラグインを使っていて気付いたのですが、イベントタイプの削除には現状対応してないようです。例えば、関連付けるイベントタイプを変更した場合、古い設定が残ってしまっています。このあたり今後に期待したいですね。
本エントリがみなさんの参考になれば幸いに思います。