見出し画像

CDK を使って定義した Stack を CodePipeline からデプロイする (1/2)

Solution Architect の t_maru です。

前回は CDK を使って CodeBuild の Project を定義し、CloudFormation で定義した場合とのどのような違いがあるのかを説明しました。

今回からは、この記事と次回の記事の 2 回に分けて CodePipeline から CDK を使って定義されたリソースをデプロイする方法を複数紹介しそれぞれの特徴を説明します。

CDK を使っている場合のデプロイパターン

CDK を定義したものを Local PC からデプロイする際には、公式のドキュメントに記載されているように `cdk deploy` コマンドを使用してデプロイすれば良いことはすぐに分かると思いますが、CI/CD の仕組を構築し自動で AWS 環境を構築する場合はについては、いくつかやり方が考えられるため今回は以下の 3 パターンを検証した結果を紹介します。

  • CDK CLI を使う方法 (cdk deploy コマンド)

  • CloudFormation を使う方法

  • aws-cdk-lib.pipelines module を使う方法 (次回の記事で紹介)

CDK CLI を使ってデプロイする方法

CDK CLI を使ってデプロイする場合は、Local PC からデプロイする場合とほぼ同じです。

今回は Pipeline 定義と CodeBuild の BuildSpec は以下のようにして動作確認しました。

// Pipeline 定義の抜粋

const cdkDeployProject = new codebuild.PipelineProject(this, 'CdkDeployProject', {
  buildSpec: codebuild.BuildSpec.fromSourceFilename('ci/code-build/deploy-stack.yml'),
  role: adminRole,
  environment: {
    buildImage: codebuild.LinuxBuildImage.STANDARD_6_0,
    computeType: codebuild.ComputeType.SMALL,
  },
});

new codepipeline.Pipeline(this, 'Pipeline', {
  artifactBucket,
  stages: [
    {
      stageName: 'Source',
      actions: [
        new codepipeline_actions.CodeStarConnectionsSourceAction({
          actionName: 'FetchSourceCode',
          owner: githubOwnerName,
          repo: githubRepoName,
          branch: 'main',
          connectionArn: githubConnection.ref,
          output: sourceOutput,
        }),
      ],
    },
    {
      stageName: 'Deploy',
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: 'DeployResources',
          project: cdkDeployProject,
          input: sourceOutput,
        }),
      ],
    },
  ],
});
# Deploy 用の Buildspec 抜粋

version: 0.2
env:
  shell: bash
phases:
  install:
    runtime-versions:
      nodejs: 16
    commands:
      - npm install
  build:
    commands:
      - npm run cdk deploy CognitoStack -- --require-approval never --ci true

完全なテンプレートは下記の URL を参照ください。

今回 BuildSpec を YAML ファイルとして定義し、CodeBuild の Project 定義では `codebuild.BuildSpec.fromSourceFilename('ci/code-build/deploy-stack.yml')` という形で BuildSpec が保存されているファイルパスを指定する形としていますが、他にも以下の 2 つの method が用意されており、Pipeline の定義と同時に選択した言語 (今回は TypeScript) の中で BuildSpec を定義することができます。

  • codebuild.BuildSpec.fromObject({...})

  • codebuild.BuildSpec.fromObjectToYaml({...})

    • Object で記述した設定が CodeBuild 上に YAML で設定されるので本質は上記の `fromObject` と変わりません

これらはリソース定義に使用している言語で設定を記載することができる反面、BuildSpec の内容を書き換えた際に Pipeline を再度デプロイする必要がある点に注意が必要です。このため、CDK CLI を使ってデプロイする例の検証時には `fromSourceFilename(...)` を使用し、ソースコード上に配置されたファイルからビルド実行時に取り込むという形を採用することで、BuildSpec の変更に伴う Pipeline の変更が起こらないように構成しました。

この Pipeline を AWS 上にデプロイし、CodePipeline の Console でワークフローの流れを確認すると下記のようになり、デプロイで使用する CodeBuild のプロジェクトのみが Stage として存在することが確認できます。

cdk cli を使ってデプロイする Pipeline 例

■ CDK CLI を使ってデプロイする場合のメリット

  • Pipeline を自由に構成できる

  • BuildSpec を自由に設定できる

  • 構成が複雑な場合でも template の合成が自動で行われる

■ デメリット

  • CodeBuild へ設定する権限の選定に手間がかかる

    • Deploy 時にアクセスするサービスに対して厳密な権限を設定していくのに手間がかかる

  • AWS リソースデプロイ前にリソース変更のレビュー (CloudFormation ChangeSet の確認) ができない

CloudFormation を使ってデプロイする方法

以前の記事でも説明したように、CDK を使って定義したリソースでも、デプロイには CloudFormation が使われます。

Local PC でリソースをデプロイする場合に使用する `cdk deploy` コマンドは、裏で CloudFormation の template を生成してデプロイに使用しているのですが、このデプロイに使われる CloudFormation の template は `cdk synth` コマンドを実行することで事前に生成することもできます。

CloudFormation を使ってデプロイする方法では、`cdk synth` により CloudFormation template を生成し、それを使ってリソースをデプロイする CloudFormation の Stage を構成します。

// Pipeline 定義の抜粋

const synthTemplateProject = new codebuild.PipelineProject(this, 'SynthTemplateProject', {
  buildSpec: codebuild.BuildSpec.fromSourceFilename('ci/code-build/synth-template.yml'),
  environment: {
    buildImage: codebuild.LinuxBuildImage.STANDARD_6_0,
    computeType: codebuild.ComputeType.SMALL,
  },
});

new codepipeline.Pipeline(this, 'Pipeline', {
  artifactBucket,
  stages: [
    {
      stageName: 'Source',
      actions: [
        new codepipeline_actions.CodeStarConnectionsSourceAction({
          actionName: 'FetchSourceCode',
          owner: githubOwnerName,
          repo: githubRepoName,
          branch: 'main',
          connectionArn: githubConnection.ref,
          output: sourceOutput,
        }),
      ],
    },
    {
      stageName: 'Build',
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: 'SynthCdkTemplate',
          project: synthTemplateProject,
          input: sourceOutput,
          outputs: [cdkTemplateOutput],
        }),
      ],
    },
    {
      stageName: 'Deploy',
      actions: [
        new codepipeline_actions.CloudFormationCreateReplaceChangeSetAction({
          actionName: 'CreateCfnChangeSet',
          adminPermissions: true,
          stackName: DEPLOY_STACK_NAME,
          changeSetName: CHANGE_SET_NAME,
          templatePath: cdkTemplateOutput.atPath('CognitoStack.template.json'),
          runOrder: 1
        }),
        new codepipeline_actions.ManualApprovalAction({
          actionName: 'ChangeSetReview',
          runOrder: 2,
        }),
        new codepipeline_actions.CloudFormationExecuteChangeSetAction({
          actionName: 'DeployResources',
          stackName: DEPLOY_STACK_NAME,
          changeSetName: CHANGE_SET_NAME,
          runOrder: 3,
        }),
      ],
    },
  ],
});
# Template 生成用の BuildSpec 抜粋

version: 0.2
env:
  shell: bash
phases:
  install:
    runtime-versions:
      nodejs: 16
    commands:
      - npm install
  build:
    commands:
      - npm run cdk synth CognitoStack

artifacts:
  base-directory: cdk.out
  files: ['**/*']

完全なテンプレートは下記の URL を参照ください。

この Pipeline を AWS 上にデプロイし、CodePipeline の Console で見ると下記のようになります。

CloudFormation を使ってデプロイする例

先程も説明したように、まず CodeBuild で `cdk synth` コマンドを使い CloudFormation template を生成します。その後、生成された template を Pipeline の Artifact 経由で CloudFormation に渡しています。

■ CloudFormation を使ってデプロイする場合のメリット

  • Pipeline を自由に構成できる

  • BuildSpec を自由に設定できる

  • CloudFormation Change Set を使った更新の手動レビューも可能

■ デメリット

  • CDK template として定義するものが増える (Pipeline を自由に構成できるメリットとのトレードオフ)

  • 複雑な構成のリソースをデプロイする場合に自前で対応すべき事項が増える

この方法の最大のデメリットは最後に挙げた `複雑な構成のリソースをデプロイする場合に自前で対応すべき事項が増える` という点です。

例として Nested stack を挙げますが、CDK を使って Nest された stack を定義した場合、`cdk synth` により生成される CloudFormation の template では、親の stack に子の stack の Template URL が記載されることになるのですが、この URL は CDK が自動で払い出す値となります。

synth は CloudFormation template を出力するところまでが役割で S3 へのテンプレートのアップロードは行わないため、synth の出力結果を Artifact 経由で CloudFormation の Deploy action に渡してそのまま利用すると、親の template が参照している 子の template が指定された S3 の path 上に存在していない状態となるため、リソースのデプロ時にエラーとなります。

template を解析して、指定されている S3 バケットに子 stack の template をアップロードするスクリプトを作って実験したこともありますが、正直なところかカスタムスクリプトを作りそれを継続的にメンテナンスしていくのはかなり骨が折れる作業ですのであまりおすすめできる方法ではありません。

npm で公開されている `cdk-assets` という module (aws-cdk に含まれる package) を使うことで手動運用した際の手間を多少は軽減できる可能性がありますので、もし良い方法を見つけることができましたら別途紹介いたします。

まとめ

今回は CodePipeline から CDK を使って定義されたリソースをデプロイする方法のうち下記の 2 つの方法のメリット、デメリットをお伝えしました。

  • CDK CLI を使う方法 (cdk deploy コマンド)

  • CloudFormation を使う方法

次回は `aws-cdk-lib.pipelines module を使う方法` にフォーカスをして解説します。

最後に、再掲になりますが今回の記事で使用している完全なソースコードは下記に保管していますので興味がありましたらご参照ください。

https://github.com/t-maru078/cdk-deploy-from-pipeline