CDK を使って定義した Stack を CodePipeline からデプロイする (2/2)
Solution Architect の t_maru です。
前回に引き続き CodePipeline から CDK を使って定義されたリソースをデプロイする方法に関して紹介します。
前回と今回の記事で以下の 3 パターンを紹介しておりますが、今回の記事では `aws-cdk-lib.pipelines module を使う方法` にフォーカスしてお届けします。
CDK CLI を使う方法 (cdk deploy コマンド) (前回記事で紹介済み)
CloudFormation を使う方法 (前回記事で紹介済み)
aws-cdk-lib.pipelines module を使う方法
aws-cdk-lib.pipelines module を使う方法
前回から続く、CodePipeline から CDK を使って定義されたリソースをデプロイする方法のうち最後に紹介するのが、cdk の construct として定義されている pipelines module を使う方法です。
この方法を使うと定義されたデプロイ対象リソースに必要な Pipeline Stage が自動的に生成されますので、前回の記事の最後で説明したような自前で必要なテンプレートをアップロードする処理が不要となる点が大きなメリットになります。
// Pipeline 定義の抜粋
import * as pipelines from 'aws-cdk-lib/pipelines';
// ---
const pipeline = new pipelines.CodePipeline(this, 'CdkPipeline', {
selfMutation: true,
synthCodeBuildDefaults: {
partialBuildSpec: codebuild.BuildSpec.fromObject({
phases: {
pre_build: {
commands: `echo '{"githubOwnerName":"${githubOwnerName}","githubRepoName":"${githubRepoName}"}' >> cdk.context.json`,
},
},
}),
},
synth: new pipelines.ShellStep('Synth', {
input: pipelines.CodePipelineSource.connection(`${githubOwnerName}/${githubRepoName}`, 'main', {
connectionArn: githubConnection.ref,
}),
commands: ['npm i', 'npm run cdk synth'],
}),
codePipeline,
});
const frontendResources = new FrontEndResources(this, 'FrontedResources');
pipeline.addStage(frontendResources, {
stackSteps: [
{
stack: frontendResources.dbStack,
changeSet: [new pipelines.ManualApprovalStep('ChangeSetApproval')],
},
],
});
// デプロイ対象リソース定義
import * as cdk from 'aws-cdk-lib';
// ---
export class FrontendResources extends cdk.Stage {
public readonly dbStack: DbStack;
public readonly cognitoStack: CognitoStack;
constructor(scope: Construct, id: string, props?: cdk.StageProps) {
super(scope, id, props);
this.dbStack = new DbStack(this, 'DbStack');
this.cognitoStack = new CognitoStack(this, 'CognitoStack');
}
}
完全なテンプレートは下記の URL を参照ください。
https://github.com/t-maru078/cdk-deploy-from-pipeline/blob/main/lib/pipeline-resources/cdk-pipelines-deployment-stack.ts
Pipeline 定義で今までと違う点として、
CodeBuild Project の定義が BuildSpec を含めてほぼ必要がなくなった
Deploy 対象の Stack 定義が `cdk.Stage` を継承したクラスとなった
という 2 点が大きな変更点かと思います。
まず 1 点目の CodeBuild Project の定義が不要になった点についてですが、pipelines module は CDK を使って作ったリソースの CI/CD に特化したものになりますので、CodeBuild Project のリソースを作るのではなく、synth に必要な input と synth を実行するためのコマンドを設定すればよいだけになっています。
その反面、自由に BuildSpec をカスタマイズできないことになるため注意は必要ですが、下記のように synth などの step に対して追加の BuildSpec を定義することができ、実際の CodeBuild 処理時には追加で定義した BuildSpec と synth の定義時に記載したコマンドが合成された状態の BuildSpec が使われるようになるため、大体のユースケースには対応できるのではないかと思います。
const pipeline = new pipelines.CodePipeline(this, 'CdkPipeline', {
// 追加の BuildSpec に関連する設定のみ抜粋
synthCodeBuildDefaults: {
partialBuildSpec: codebuild.BuildSpec.fromObject({
phases: {
pre_build: {
commands: `echo '{"githubOwnerName":"${githubOwnerName}","githubRepoName":"${githubRepoName}"}' >> cdk.context.json`,
},
},
}),
},
});
上記の例では、cdk の context を使って GitHub 関連のパラメータを渡していたので、synth の際にもこれらのパラメータが使われるように `synthCodeBuildDefaults` の部分に設定を追加をしました。この状態で Pipeline をデプロイし生成された CodeBuild の設定を見ると下記のような形で、synthCodeBuildDefaults で設定した内容と、synth の commands で指定した内容が合成された状態の BuildSpec が設定されていることがわかります。
2 点目の Stack 定義が `cdk.Stage` を継承したクラスにとなった、という点については該当する定義部分を再度掲載します。
// デプロイ対象リソース定義
class FrontendResources extends cdk.Stage {
public readonly dbStack: DbStack;
public readonly cognitoStack: CognitoStack;
constructor(scope: Construct, id: string, props?: cdk.StageProps) {
super(scope, id, props);
this.dbStack = new DbStack(this, 'DbStack');
this.cognitoStack = new CognitoStack(this, 'CognitoStack');
}
}
// Pipeline 定義
const pipeline = new pipelines.CodePipeline(this, 'CdkPipeline', {...});
const frontendResources = new FrontendResources(this, 'FrontendResources');
pipeline.addStage(frontendResources, {
stackSteps: [
{
stack: frontendResources.dbStack,
changeSet: [new pipelines.ManualApprovalStep('ChangeSetApproval')],
},
],
});
上記の例では、cdk.Stage を継承している FrontendResources という class がデプロイ対象のリソース定義となり、これを `pipeline.addStage` メソッドを使い pipeline に add することで Deploy stage を構成することができます。こちらも定義内容が簡潔になったのは良い点かと思いますが、具体的にどのようなステップが設定されるのかリソース定義時点では不明瞭な点がデメリットです。
上記の設定で実際にデプロイした例がこちらです。
先程 `addStage(...)` した内容に従って、CloudFormation change set を使用したデプロイ Stage が自動的に定義されました。今回は AWS リソースをデプロイする前に手動承認プロセスを経由する例で実装したため DbStack に関しては Prepare と Deploy の間に手動承認ステップが追加されています。
定義上の大きな変更点は上記で説明した 2 点ですが、運用上はもう一つ重要な変更点があります。
それは先程記載した `CodeBuild Project の定義が BuildSpec を含めてほぼ必要がなくなった` というメリットから生じる変更点です。
pipelines module 以外を使う例では CodeBuild の設定ファイルを YAML ファイルとして別定義しておりましたが、今回は CDK の template の一部として定義しているため使用する command が変更されるたびに Pipeline の変更が必要になってしまいます。
少し変更を加えるたびに毎回 Pipeline の変更が必要になってしまうのは運用が面倒になるため極力避けたいところですが、pipelines module を使う場合においては Pipeline の設定で `selfMutation` という項目があり、この項目が true であった場合 (デフォルト true) は Pipeline の Stage の一部として自分自身を更新する処理が含まれるようになるため大きな心配は不要です。
自分自身を更新する Pipeline というのもなんだか気持ち悪いですし、Pipeline を更新するための処理が毎回含まれるようになるため Pipeline の全行程が終了するまでの時間が少し伸びるというデメリットもありますが、この設定をしておくことで Pipeline の構成を気にする必要がなくなるため基本的には selfMutation を true にしておくことをおすすめします。
CDK の API Reference にも Pipeline の開発をやりやすくするために一時的に selfMutation を無効にすることができるという表現がなされているため、Pipeline の開発が完了して定常運用に入る際には selfMutation を true で設定しておくと良さそうです。
最後にこのパターンにおけるメリットとデメリットとまとめます。
■ aws-cdk-lib.pipelines module を使う場合のメリット
デプロイ対象のリソースの構成状況に応じて自動的に Pipeline 構成が変更され、複雑なリソースも簡単にデプロイできる
必要最低限の設定を書くだけで良くなるため設定およびコード量が減る
CodeBuild Project 用 BuildSpec の定義もほぼ必要がなくなる
■ デメリット
CI/CD のたびに Pipeline の更新チェックが入るためすべての工程が終わるまでの時間が少し長くなる
設定の記載量が減る反面、リソース定義時にはどのような Step で Pipeline が構成されるか不明瞭
(Pipeline が自分自身の CI/CD 処理で更新されるのが気持ち悪い)
まとめ
前回と今回の 2 回で、CodePipeline から CDK を使って定義されたリソースをデプロイする方法を複数ご紹介しました。
どの方法を使うのかは皆様の置かれている状況により違うと思いますので、一概にどの方法が良いということはお伝えしづらいですが、前回記事と今回記事で紹介した内容が皆様が適切な環境構成を検討する際の参考になれば幸いです。
最後に、再掲になりますが今回の記事で使用している完全なソースコードは下記に保管していますので興味がありましたらご参照ください。