【CDK】クロスアカウントでCloudFrontのキャッシュをクリアする構成
概要
今回はCodePipline
の最後で「他のAWSアカウントのCloudFront
のキャッシュを削除する」方法について紹介します。
だいぶニッチな構成なので、そもそも前提が当てはまらない方が大半だと思いますが、自分の作業メモも兼ねて試行錯誤の記録として残します。
前提
以下2つのAWSアカウントを持っているものとします。その配下にそれぞれリソースを持っています。
- Aアカウント(ソース管理)
- CodeCommit
- CodeBuild
- CodeDeploy
- CodePipeline
- Bアカウント(SPAの配信)
- CloudFront
- S3
見てわかる通り、Aアカウントはソース管理およびCI/CDによる配信関連のリソースを、そしてBアカウントはCI/CDの成果物の配信のための役割を持っています。
アプリケーションはシンプルにReact
を用いたSPAを想定します。
今回の目的は最初に書いた通り「CodePipeline
が走ったあとにCloudFront
のキャッシュをクリアする」です。
これらが同じアカウントに存在する場合、比較的簡単に実現できます。
以下の記事にもあるように、「CloudFront
のキャッシュをクリアするLambda
」を用意して、CodePipline
のステージとして配置するだけです。
しかし今回はCodePipeline
とCloudFront
が別々のアカウントに存在します。
その場合、少しだけ手間がかかったので、その紆余曲折を紹介していきます。
うまくいかなかったケース
まず初めに失敗したケースから紹介していきます。
考え方として「CloudFront
のキャッシュをクリアするLambda
」をA,Bどちらのアカウントで用意していくか、を中心にしたためこのようなアプローチになりました。
①LambdaをBアカウントに作るケース
Bアカウント。つまりCloudFront
がある方のアカウントでLambda
を作成していくパターンです。
手順としては
- Bアカウントで
Lambda
を作成 - その
Lambda
のリソースベースのポリシーを修正し、Aアカウントからの呼び出しを許可する - Aアカウントの
CodePipeline
の最後でLambda
を呼び出す
となります。クロスアカウントでの操作は何度か経験したため、最初はこれでいけるかなと思ってましたがダメでした。
Aアカウント側のCDKのデプロイ時に以下のエラーが出ました。
Stack name must match the regular expression: /^[A-Za-z][A-Za-z0-9-]*$/, got '${Token[TOKEN.xxx]}-support-yyyyyyyyyyyyy'
クロスアカウントまわりのパーミッションでダメになるかと思いきやスタック名の規則エラーが出ています。
正直、このエラーの原因がはっきりしないまま他の方法で取り掛かってしまったので、もしかしたら何らか改善すればこのやり方でいける可能性もあります。
②LambdaをAアカウントに作るケース
Aアカウント。つまりCodePipeline
がある方のアカウントでLambda
を作成するパターンです。
この場合、外部のアカウントのCloudFront
のキャッシュ削除権限をどうやってLambda
に持たせるか、がポイントだと思うので
- Bアカウントで
CloudFront
のキャッシュ削除権限を持ったロールを作成 - Aアカウントで
Lambda
を作成し、実行ロールとして上記のロールを割り当てる - Aアカウントの
CodePipeline
の最後でLambda
を呼び出す
の手順としました。
ロールだけクロスアカウントで持たせるイメージです。
この方法はもともとダメそうだなという感覚だったのですが、やはりそうでした。
こちらもAアカウント側のCDKのデプロイ時に以下のエラーが出ました。
Cross-account pass role is not allowed.
クロスアカウントでのPassRoleはできないよ、とのこと。
スイッチロール的なノリで使おうとしましたが、仕様的にNGそうなので断念しました。
うまくいったケース
次にうまくいったケースです。
③LambdaをA,Bに1つずつ作り、invokeする形で対応する
- Bアカウントで
CloudFront
のキャッシュを削除するLambda
を作成 - Aアカウントで上記の
Lambda
を呼び出すLambda
を作成、CodePipeline
の最後で呼び出す
の手順になります。
少し冗長ですが、アカウントをまたいだinvokeは可能なことを元から知っていたのでこの方法に頼りました。
一部ですがCDKのコードを紹介します
BアカウントのLambdaとCDK
Lambda
// lambda/deleteCloudFrontCacheFunc/index.ts
import {
CloudFrontClient,
CreateInvalidationCommand
} from '@aws-sdk/client-cloudfront';
// CloudFrontを操作するためのClientを宣言
const client = new CloudFrontClient({
region: 'us-east-1'
});
export async function handler(event: any): Promise<any> {
try {
// 環境変数からCloudFrontのIDを取得
const distributionId = process.env.DISTRIBUTION_ID;
// キャッシュ削除処理
await client.send(new CreateInvalidationCommand({
DistributionId: distributionId,
InvalidationBatch: {
CallerReference: `invalidate-${new Date().getTime()}`,
Paths: {
Quantity: 1,
Items: ['/*']
}
}
});
} catch (error) {
console.error('Error :', error);
}
}
CDK
// キャッシュ削除用のLambdaを宣言
const func = new NodejsFunction(this, 'Func', {
entry: 'lambda/deleteCloudFrontCacheFunc/index.ts',
runtime: Runtime.NODEJS_18_X,
environment: {
DISTRIBUTION_ID: "xxxxxx"
},
});
// CloudFrontのキャッシュ削除権限を追加
func.addToRolePolicy(
new iam.PolicyStatement({
actions: ['cloudfront:CreateInvalidation'],
resources: ['CloudFrontのARN']
})
);
// Aアカウントからの呼び出しを許可
func.addPermission('CrossAccountPermission', {
principal: new iam.AccountPrincipal("AアカウントのID"),
action: 'lambda:InvokeFunction'
});
AアカウントのLambdaとCDK
Lambda
// lambda/invoker/index.ts
import { Handler } from 'aws-lambda';
import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda';
// Lambda操作のためのClientを宣言
const client = new LambdaClient({
region: 'ap-northeast-1'
});
export const handler: Handler = async (event) => {
try {
// 環境変数から呼び出したいLambdaのARNを取得
const targetLambdaArn = process.env.TARGET_LAMBDA_ARN;
await client.send(
new InvokeCommand({
FunctionName: targetLambdaArn,
InvocationType: 'Event'
})
);
} catch (error) {
console.error('Error :', error);
}
};
CDK
// Lambdaを宣言
const invoker = new NodejsFunction(
this,
'invoker',
{
entry: 'lambda/invoker/index.ts',
runtime: Runtime.NODEJS_18_X,
environment: {
TARGET_LAMBDA_ARN: "xxxxxxxxxxxxxxx"
}
}
);
// invokeの実行権限をつける
invoker.addToRolePolicy(
new iam.PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [cdk.Fn.sub("xxxxxxxxxxxxxxx")]
})
);
// CodePipeline宣言
const pipeline = new Pipeline(/* ...略...*/);
// 最後にLambdaを呼び出すステージを追加
pipeline.addStage({
stageName: 'InvokeLambda',
actions: [
new LambdaInvokeAction({
actionName: 'invokeLambda',
lambda: invoker
})
]
});
これでパイプラインを動かした最後で、他のAWSアカウントのCloudFront
のキャッシュを削除する構成が出来上がりました。
まとめ
冒頭でも書いた通り「他のAWSアカウントのCloudFront
のキャッシュを削除する」方法について紹介しました。
正直なところ途中の注意書きにもある通り、ベストプラクティスでない可能性もあります。というかそうじゃないかと疑っています。
これ以外の構成で同じことができた方がいましたら、コメントいただけると大変喜びます!
Discussion