CDK Pipeline を触ってみた

December 21, 2021

CDK Pipeline とは

CDK Pipeline とは CDK を用いて Lambda や API Gateway といった AWS リソースのみならず、それらを CI/CD でデプロイするためのパイプラインも CDK アプリケーション側で定義するためのものです。初めの一回だけ cdk deploy したらあとはリポジトリにプッシュすれば必要に応じてパイプラインそのものを変更しつつ、パイプライン内でインフラストラクチャのデプロイ等も行うといったものになります。
今回はひとまず 前回 まで扱っていた HitCounter 及び TableViewer を用いて CDK Pipeline の構築を試みます。

Pipeline スタックの作成

まずはパイプラインを含むスタックを作成するところから始めます。cdk_workshop ディレクトリ配下に pipeline_stack.py を作成します。

cdk_workshop/pipeline_stack.py
from constructs import Construct
from aws_cdk import (
  Stack
)

class WorkshopPipelineStack(Stack):
  def __init__(self, scope: Construct, id: str, **kwargs) -> None:
    super().__init__(scope, id, **kwargs)

次に app.py でデプロイのエントリポイントを変更します。

app.py
#!/usr/bin/env python3

import aws_cdk as cdk

from cdk_workshop.pipeline_stack import WorkshopPipelineStack
#from cdk_workshop.cdk_workshop_stack import CdkWorkshopStack


app = cdk.App()
#CdkWorkshopStack(app, "cdk-workshop")
WorkshopPipelineStack(app, "WorkshopPipelineStack")

app.synth()

リポジトリの作成

次は CDK アプリケーションのソースコードを格納するリポジトリを用意します。今回は GitHub リポジトリを使用するので、GitHub 側で OAuth トークンを予め控えておき、それを SecretManager のシークレットとして登録しておきます。
また、これまでの CDK アプリケーションの内容をプッシュしておきます。

パイプラインを定義する

リポジトリの作成ができたら、再び pipeline_stack.py をいじっていきます。なお、ソースに GitHub リポジトリを指定する方法は こちら を参考にしました。マネジメントコンソールで認証してからその Connection ARN を利用する方法が推奨されていますが、今回は OAuth トークンを利用します。
実際の pipeline_stack.py は以下の通りです。

cdk_workshop/pipeline_stack.py
from constructs import Construct
from aws_cdk import (
  Stack,
  SecretValue,
  pipelines as pipelines,
)

class WorkshopPipelineStack(Stack):
  def __init__(self, scope: Construct, id: str, **kwargs) -> None:
    super().__init__(scope, id, **kwargs)

    pipeline = pipelines.CodePipeline(
      self,
      "Pipeline",
      synth=pipelines.ShellStep(
        "Synth",
        input=pipelines.CodePipelineSource.git_hub('U-PIN/cdk-pipeline-tutorial', 'main',
          authentication=SecretValue.secrets_manager('GitHubOAuthToken')
        ),
        commands = [
          "npm install -g aws-cdk",
          "pip install -r requirements.txt",
          "npx cdk synth"
        ]
      )
    )

ここまでできたら $ cdk synth$ cdk deploy を実行しましょう。

$ cdk synth

...
$ cdk deploy

...

✅  WorkshopPipelineStack

Stack ARN:
arn:aws:cloudformation:us-west-2:xxxxxxxxxxxx:stack/WorkshopPipelineStack/8a162f90-62c2-11ec-81ab-0a8a4e933019

以下のように CodePipeline 側でパイプラインが作成・実行されれば OK です。Build ステージには SynthUpdatePipeline ステージには SelfMutate というアクションがそれぞれ定義されているのがわかりますね。

cdk_pipeline_tutorial_1

CDK アプリケーション用ステージの追加

ここまでで、変更があった際に自分自身のアップデートを行う CDK Pipeline の構築が完了しました。なので、ここからは実際に CDK アプリケーションをパイプラインに組み込む作業に入ります。
まずは cdk_workshop ディレクトリ配下に pipeline_stage.py を以下の内容で作成します。

cdk_workshop/pipeline_stage.py
from constructs import Construct
from aws_cdk import (
  Stage
)
from .cdk_workshop_stack import CdkWorkshopStack

class WorkshopPipelineStage(Stage):

  def __init__(self, scope: Construct, id: str, **kwargs):
    super().__init__(scope, id, **kwargs)

    service = CdkWorkshopStack(self, 'WebService')

あとは Deploy ステージとして pipeline_stage.py で作成した Stage を pipeline_stack.py で追加するだけです。

cdk_workshop/pipeline_stack.py
from constructs import Construct
from aws_cdk import (
  Stack,
  SecretValue,
  pipelines as pipelines,
)
from .pipeline_stage import WorkshopPipelineStage

class WorkshopPipelineStack(Stack):
  def __init__(self, scope: Construct, id: str, **kwargs) -> None:
    super().__init__(scope, id, **kwargs)

    pipeline = pipelines.CodePipeline(
      self,
      "Pipeline",
      synth=pipelines.ShellStep(
        "Synth",
        input=pipelines.CodePipelineSource.git_hub('U-PIN/cdk-pipeline-tutorial', 'main',
          authentication=SecretValue.secrets_manager('GitHubOAuthToken')
        ),
        commands = [
          "npm install -g aws-cdk",
          "pip install -r requirements.txt",
          "npx cdk synth"
        ]
      )
    )

    deploy = WorkshopPipelineStage(self, "Deploy")
    deploy_stage = pipeline.add_stage(deploy)

さらに requirements.txtcdk-dynamo-table-view を追加しておきます。これは CodeBuild のビルドプロジェクト内で pip install -r requirements.txt をした際に cdk-dynamo-table-view も同時にインストールさせるためです。

requirements.txt
aws-cdk-lib==2.1.0
constructs>=10.0.0,<11.0.0
cdk-dynamo-table-view==0.2.26

あとはリポジトリにプッシュします。

$ git add .
$ git commit -m "Added deploy stage to pipeline" && git push
[main f0909ac] Added deploy stage to pipeline
 12 files changed, 5150 insertions(+), 206 deletions(-)
 create mode 100644 cdk.out/assembly-WorkshopPipelineStack-Deploy/WorkshopPipelineStackDeployWebServiceC0F2BFAD.assets.json
 create mode 100644 cdk.out/assembly-WorkshopPipelineStack-Deploy/WorkshopPipelineStackDeployWebServiceC0F2BFAD.template.json
 create mode 100644 cdk.out/assembly-WorkshopPipelineStack-Deploy/cdk.out
 create mode 100644 cdk.out/assembly-WorkshopPipelineStack-Deploy/manifest.json
 rewrite cdk_workshop/__pycache__/pipeline_stack.cpython-39.pyc (99%)
 create mode 100644 cdk_workshop/__pycache__/pipeline_stage.cpython-39.pyc
 create mode 100644 cdk_workshop/pipeline_stage.py
Enumerating objects: 26, done.
Counting objects: 100% (26/26), done.
Delta compression using up to 8 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (17/17), 21.97 KiB | 3.66 MiB/s, done.
Total 17 (delta 11), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (11/11), completed with 8 local objects.
To https://github.com/U-PIN/cdk-pipeline-tutorial.git
   857f966..f0909ac  main -> main

CodePipeline パイプラインに対して、以下のように Assets 及び Deploy ステージが追加されていれば成功です。

cdk_pipeline_tutorial_2

エンドポイントを出力させる

これまででパイプラインを用いて CDK アプリケーションのデプロイが完了しましたが、TableViewer や Rest API のエンドポイントを出力として得るためにもう少し弄ります。これで例えばフロントのアプリがあった時に API のエンドポイントを REACT_APP_* のような環境変数に入れてビルドする、みたいなことも可能になりそうな気がしてます。
cdk_workshop/cdk_workshop_stack.py を以下のように書き換えましょう。

cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Stack,
    CfnOutput,
    aws_lambda as _lambda,
    aws_apigateway as apigw,
)

from cdk_dynamo_table_view import TableViewer
from .hitcounter import HitCounter

class CdkWorkshopStack(Stack):

    @property
    def hc_endpoint(self):
        return self._hc_endpoint
    
    @property
    def hc_viewer_url(self):
        return self._hc_viewer_url

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        my_lambda = _lambda.Function(
            self, 'HelloHandler',
            runtime=_lambda.Runtime.PYTHON_3_9,
            code=_lambda.Code.from_asset('lambda'),
            handler='hello.handler',
        )

        hello_with_counter = HitCounter(
            self, 'HelloHitCounter',
            downstream=my_lambda,
        )

        gateway = apigw.LambdaRestApi(
            self, 'Endpoint',
            handler=hello_with_counter._handler
        )

        tv = TableViewer(
            self, 'ViewHitCounter',
            title='Hello Hits',
            table=hello_with_counter.table, 
        )

        self._hc_endpoint = CfnOutput(
            self, 'GatewayUrl',
            value=gateway.url
        )

        self._hc_viewer_url = CfnOutput(
            self, 'TableViewerUrl',
            value=tv.endpoint
        )

あとはリポジトリにプッシュすればパイプラインにより変更がデプロイされます。

$ git add .
$ git commit -m "Added CfnOutput" && git push

パイプライン実行後、Deploy-WebService という CloudFormation スタックで以下のような出力があれば成功です。

キー
GatewayUrl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/prod/
TableViewerUrl https://yyyyyyyyyy.execute-api.us-west-2.amazonaws.com/prod/

Validation Test を加える

ここまでで CDK Pipeline 及び TableViewer や API のデプロイが継続的に行えるようになりましたが、さらに TableViewer と API のテストを行うステージを追加しましょう。
まずは、Deploy ステージがそれぞれのエンドポイントをプロパティとして保持できるように、cdk_workshop/pipeline_stage.py を以下のように書き換えます。

cdk_workshop/pipeline_stage.py
from constructs import Construct
from aws_cdk import (
  Stage
)
from .cdk_workshop_stack import CdkWorkshopStack

class WorkshopPipelineStage(Stage):

  @property
  def hc_endpoint(self):
    return self._hc_endpoint

  @property
  def hc_viewer_url(self):
    return self._hc_viewer_url

  def __init__(self, scope: Construct, id: str, **kwargs):
    super().__init__(scope, id, **kwargs)

    service = CdkWorkshopStack(self, 'WebService')

    self._hc_endpoint = service.hc_endpoint
    self._hc_viewer_url = service.hc_viewer_url

次に、テスト用のアクションを追加するために cdk_workshop/pipeline_stack.py を以下のように書き換えます。

cdk_workshop/pipeline_stack.py
from constructs import Construct
from aws_cdk import (
  Stack,
  SecretValue,
  pipelines as pipelines,
)
from .pipeline_stage import WorkshopPipelineStage

class WorkshopPipelineStack(Stack):
  def __init__(self, scope: Construct, id: str, **kwargs) -> None:
    super().__init__(scope, id, **kwargs)

    pipeline = pipelines.CodePipeline(
      self,
      "Pipeline",
      synth=pipelines.ShellStep(
        "Synth",
        input=pipelines.CodePipelineSource.git_hub('U-PIN/cdk-pipeline-tutorial', 'main',
          authentication=SecretValue.secrets_manager('GitHubOAuthToken')
        ),
        commands = [
          "npm install -g aws-cdk",
          "pip install -r requirements.txt",
          "npx cdk synth"
        ]
      )
    )

    deploy = WorkshopPipelineStage(self, "Deploy")
    deploy_stage = pipeline.add_stage(deploy)
    deploy_stage.add_post(
      pipelines.ShellStep(
        "TestViewerEndpoint",
        env_from_cfn_outputs={
          "ENDPOINT_URL": deploy.hc_viewer_url
        },
        commands=["curl -Ssf $ENDPOINT_URL"],
      )
    )
    deploy_stage.add_post(
      pipelines.ShellStep(
        "TestAPIGatewayEndpoint",
        env_from_cfn_outputs={
          "ENDPOINT_URL": deploy.hc_endpoint
        },
        commands=[
          "curl -Ssf $ENDPOINT_URL",
          "curl -Ssf $ENDPOINT_URL/hello",
          "curl -Ssf $ENDPOINT_URL/test",
        ],
      )
    )

あとはプッシュしましょう。

$ git add .
$ git commit -m "Added validation tests" && git push

パイプライン実行後、以下のように Validation Test 用のアクションが Deploy ステージに追加されていれば OK です。

cdk_pipeline_tutorial_3

なお、例えば API テスト用のビルドログを CodeBuild コンソールから見ると以下のようにテストが行われていることが確認できます。

[Container] 2021/12/22 07:05:35 Entering phase BUILD
[Container] 2021/12/22 07:05:35 Running command curl -Ssf $ENDPOINT_URL
Hello, CDK! You have hit /
[Container] 2021/12/22 07:05:38 Running command curl -Ssf $ENDPOINT_URL/hello
Hello, CDK! You have hit /hello
[Container] 2021/12/22 07:05:38 Running command curl -Ssf $ENDPOINT_URL/test
Hello, CDK! You have hit /test
[Container] 2021/12/22 07:05:38 Phase complete: BUILD State: SUCCEEDED

また、実際に TableViewer にブラウザからアクセスすると、テスト時のリクエストがカウントされていることがわかりますね。

cdk_pipeline_tutorial_4


 © 2023, Dealing with Ambiguity