Serverless Frameworkのプラグインを利用したLambda@Edgeのデプロイ
はじめに
こんにちは、中山です。
少し前の話になりますが、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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | --- 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
1 2 3 4 5 6 7 8 9 10 11 12 | 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); }; |
デプロイ
いつもと同じようにデプロイします。すると以下のような出力が表示されると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ $(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
してもディストリビューションの設定は変更されないようです。親切設計です。
1 2 3 4 5 6 | $ $(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が関連付けされている
1 2 3 4 5 6 7 8 9 10 11 12 | $ 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
が追加されている
1 2 3 4 5 6 7 8 9 10 11 | $ 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にリソースポリシーが設定されている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ 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
が表示される
1 2 | $ curl https: // <FQDN> /test .html rewirte.html |
index.html
にアクセスするとそのまま
1 2 | $ curl https: // <FQDN> /index .html index.html |
まとめ
いかがだったでしょうか。
Serverless Frameworkのプラグインを利用したLambda@Edgeのデプロイについてご紹介しました。今はまだプラグインという位置付けですが、今後コア機能としてLambda@Edgeがイベントタイプにサポートされていくと思います。GitHub上のイシューで議論されているようです。あるいはAWS SAMでもそのうちサポートされていくでしょう。
今回ご紹介したプラグインを使っていて気付いたのですが、イベントタイプの削除には現状対応してないようです。例えば、関連付けるイベントタイプを変更した場合、古い設定が残ってしまっています。このあたり今後に期待したいですね。
本エントリがみなさんの参考になれば幸いに思います。