9月30日 15:10 – 15:50(Track H) ブレイクアウトセッションにて、「Lambda@Edge から CloudFront Functions への移行と CI / CDパイプラインの整備」をテーマに登壇いたしました。
当日の資料
CI / CDパイプライン付きCloudFront Functionsテンプレート
今回のセッションで紹介したCI / CDパイプライン付きのAWS CDKスタックテンプレートをGitHubにて公開しています。
lib/
配下にAWS CDKやJestのE2Eテストで利用するインフラ定義を保存しています。
functions/
配下にCloudFront Functionsの実体コードを保存しています。
https://github.com/digitalcube/cloudfront-functions-template/tree/main/functions
let
/ const
/ export
などの手癖で書きがちなコードについては、AWS CDKのUnit Testで存在チェックを行なっています。
import { SynthUtils } from '@aws-cdk/assert';
import '@aws-cdk/assert/jest';
import * as cdk from '@aws-cdk/core';
import * as CloudfrontFunctions from '../../lib/cloudfront-functions-stack';
describe('unit test for the stack', () => {
let app: cdk.App
let stack: cdk.Stack
beforeEach(() => {
app = new cdk.App();
stack = new CloudfrontFunctions.CloudfrontFunctionsStack(app, 'MyTestStack');
})
it('should not contain module.export on the functionCode', () => {
const resources = Object.values(SynthUtils.toCloudFormation(stack).Resources)
.filter((resource: any) => resource.Type === "AWS::CloudFront::Function" )
resources.forEach((resource: any) => {
expect(resource.Properties.FunctionCode).not.toContain('module.exports')
})
})
it('should not contain const on the functionCode', () => {
const resources = Object.values(SynthUtils.toCloudFormation(stack).Resources)
.filter((resource: any) => resource.Type === "AWS::CloudFront::Function" )
resources.forEach((resource: any) => {
expect(resource.Properties.FunctionCode).not.toContain('const')
})
})
it('should not contain let on the functionCode', () => {
const resources = Object.values(SynthUtils.toCloudFormation(stack).Resources)
.filter((resource: any) => resource.Type === "AWS::CloudFront::Function" )
resources.forEach((resource: any) => {
expect(resource.Properties.FunctionCode).not.toContain('let')
})
})
})
AWS CDK / Jest / CIタスクでのリソース定義再利用用ツール: cff-tools
Function名や実装などのインフラ・サービス定義を1元管理するために、cff-tools
というライブラリを公開しています。
リソース定義を以下のように行います。
import { Function } from 'cff-tools';
import { join } from 'path';
const stage = process.env.STAGE || 'development'
export const ViewerRequestFunction = new Function({
name: 'ViewerRequestFunction-' + stage,
runtime: 'cloudfront-js-1.0'
}, {
functionFilePath: join(__dirname, '../functions/viewer_reqeust.js')
})
あとは、export
したFunction定義を、AWS CDKやJestなどで利用するだけです。
AWS CDK
new CfnFunction(this, 'ViewerRequestFunction' + stage, {
functionCode: ViewerRequestFunction.getFunctionCode().toString(),
autoPublish: false,
functionConfig: {
comment: "empty function (boiler plate)",
runtime: ViewerRequestFunction.runtime
},
name: ViewerRequestFunction.name
})
Jest
Jestの場合、CloudFront FunctionsでTestFunction
する場合のPayload Eventを作るためのビルダークラスも用意しています。
describe('ViewerRequestFunction', () => {
it('should return request with no updated', async () => {
const eventBuilder = TestRequestEventFactory.create()
const task = new FunctionTask(ViewerRequestFunction)
const result = await task.runTestToGetFunctionOutput(eventBuilder, 'DEVELOPMENT')
expect(result)
.toEqual({
request: eventBuilder.getEvent().request
})
})
})
CIタスク
const publishFunction = async (targetFunction: Function) => {
const task = new FunctionTask(targetFunction)
const result = await task.publish()
return result
}
const functions = [ViewerRequestFunction, ViewerResponseFunction];
Promise.all(functions.map(publishFunction))
.then(result => {
console.log(result)
})
.catch(e => {
console.error(e)
})
Publish前のE2Eテスト
E2EテストをPublish前に挟むことで、「必要なレスポンスヘッダーが漏れていること」や「そもそもFunctionが実行できない(制限に引っかかったなど)」を検知できます。
おわりに
OSSツールを作ってまで環境を整備するのはToo muchだったかもしれないとは思います。
が、年1〜2回あるかないかの更新作業になりがちなリソースですので、ワークフローを固めておくことで、「久々に触るから、思い出すところからやらないと・・・」や「もう覚えていないから触りたくない・・・」となりがちな部分をつぶしておくことは重要です。
ShifterやAmimotoをより快適にご利用いただくためにも、デジタルキューブではさまざまなAWSサービス・新機能をテストし、導入にチャレンジしてまいります。