Help us understand the problem. What is going on with this article?

AWS CDKをデプロイするCodepipelineをCDK(Python)で構築する

前回やったこと

下記の記事でAWS CDKでPythonのFargateバッチをデプロイする方法を紹介させていただきました。
前回記事: Fargateで起動するPythonの定期バッチをAWS-CDKで構築する
image.png

今回やること

前回作成したCDKをデプロイするCodePipelineをCDKの別スタックで構築します。
image.png

CodePipelineを構築するスタックの初回デプロイのみ、手動で「cdk deploy」コマンドを打つ必要がありますが、
その後はGitHubにプッシュするだけで、CodePipelineによってCDKがデプロイされるようにします。

少し図が複雑になったように見えますが、
CDKを使用すれば、エディタの恩恵を受けながら、100行程度のPythonコードで実現できます。

ソースコードは前回同様全てこちらのリポジトリにあります。
Cloud9の環境構築やCDKの基本的な使い方については前回の記事を参照してください。

それでは、書いていきましょう!

構築開始

必要ライブラリのインストール

# 仮想環境へ入る
$ pipenv shell
# 必要なライブラリをインストール
$ pipenv install boto3 aws_cdk.core aws_cdk.aws_iam aws_cdk.aws_codepipeline_actions aws_cdk.aws_codepipeline aws_cdk.aws_codebuild aws_cdk.app_delivery

※コマンドはpipenvを使用している想定なので、各自使用しているパッケージマネージャに読み替えてください。

前回の構成

前回FargateのPythonバッチを作成した時点では、
CDKのプロジェクトは下記のようなフォルダ配置になっていました。

$ tree
├── app.py
├── aws_cdk_fargate_batch
│   ├── aws_cdk_fargate_batch_stack.py  # VPCやECR,ECSのリソース定義 
│   └── __init__.py
├── batch
├── cdk.context.json
├── cdk.json
├── cdk.out
├── Pipfile
├── Pipfile.lock
└── README.md

app.pyも下記のようにAwsCdkFargateBatchStackだけをデプロイするシンプルな構成です。

app.py
#!/usr/bin/env python3
from aws_cdk import core
from aws_cdk_fargate_batch.aws_cdk_fargate_batch_stack import AwsCdkFargateBatchStack

app = core.App()
AwsCdkFargateBatchStack(app, "aws-cdk-fargate-batch")

app.synth()

CodePipelineStack用のディレクトリとファイル作成

今回作成するCodePipelineのリソースは、
これまで作成したリソースと、次元が違いライフサイクルが異なるので、
別ディレクトリを作成し下記のようなファイルを作成しました。

$ tree
├── app.py
├── aws_cdk_fargate_batch
│   ├── aws_cdk_fargate_batch_stack.py
│   └── __init__.py
├── continuous_delivery                 # 新規作成
│   ├── continuous_delivery_stack.py    # 新規作成
│   └── __init__.py                     # 新規作成
├── batch
├── cdk.context.json
├── cdk.json
├── cdk.out
├── Pipfile
├── Pipfile.lock
└── README.md

実装 - continuous_delivery.py

新しく作成したファイルcontinuous_delivery.pyに、
CodePipelineのリソースを定義した全文がこちらです。

continuous_delivery.py
from aws_cdk import core
from aws_cdk import aws_iam
from aws_cdk import app_delivery
from aws_cdk import aws_codebuild
from aws_cdk import aws_codecommit
from aws_cdk import aws_codepipeline
from aws_cdk import aws_codepipeline_actions
import boto3

class ContinuousDeliveryStack(core.Stack):

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

        # ========================================
        # CodePipeline
        # ========================================
        codepipeline = aws_codepipeline.Pipeline(
            self,
            id='sample_pipeline',
            pipeline_name='sample_pipeline',
        )


        # ============ source stage start ============
        source_output = aws_codepipeline.Artifact('source_output')

        # Change to your setting.
        owner = 'joe-king-sh'
        repo = 'aws-cdk-fargate-batch'
        branch = 'master'
        oauth_token = get_parameters('GITHUB_OAUTH_TOKEN')

        # Create source collect stage.
        source_action = aws_codepipeline_actions.GitHubSourceAction(
            action_name='source_collect_action_from_github',
            owner=owner,
            repo=repo,
            trigger=aws_codepipeline_actions.GitHubTrigger.POLL,
            oauth_token=core.SecretValue.plain_text(oauth_token),
            output=source_output
        )
        # Add source stage to my pipeline.
        codepipeline.add_stage(
            stage_name='Source',
            actions=[source_action]
        )
        # ============ source stage end ==============


        # ============ build stage start =============
        # Create build project.
        project = aws_codebuild.PipelineProject(
            self,
            id='sample_build_project',
            project_name='sample_build_project'
        )

        # Add policies to code build role to allow access to the Parameter store.
        project.add_to_role_policy(
            aws_iam.PolicyStatement(
                resources=['*'],
                actions=['ssm:GetParameters']
            )
        )

        # Add build stage to my pipeline.
        build_output = aws_codepipeline.Artifact('build_output')
        codepipeline.add_stage(
            stage_name='Build',
            actions=[
                aws_codepipeline_actions.CodeBuildAction(
                    action_name='CodeBuild',
                    project=project,
                    input=source_output,
                    outputs=[build_output]
                )
           ]
        )
        # ============ build stage end ===============

        # ============ deploy stage start ============
        # Add deploy stage to pipeline.
        codepipeline.add_stage(
            stage_name='Deploy',
            actions=[
                app_delivery.PipelineDeployStackAction(
                    stack=deploy_stack,
                    input=build_output,
                    admin_permissions=True,
                    change_set_name='sample-change-set'
                )
            ]
        )
        # ============ deploy stage end ==============


def get_parameters(param_key):
    """
    Get parameter encrypted from parameter store.
    """
    ssm = boto3.client('ssm', region_name='ap-northeast-1')
    response = ssm.get_parameters(
        Names=[
            param_key,
        ],
        WithDecryption=True
    )
    return response['Parameters'][0]['Value']

一つずつ順番に追っていっていきましょう。

1. CodePipeline用のStackとCodePipelineリソースを定義

continuous_delivery.py
from aws_cdk import core
from aws_cdk import aws_iam
from aws_cdk import app_delivery
from aws_cdk import aws_codebuild
from aws_cdk import aws_codecommit
from aws_cdk import aws_codepipeline
from aws_cdk import aws_codepipeline_actions
import boto3

class ContinuousDeliveryStack(core.Stack):

    # deploy_stackはこのPipelineでデプロイするCDKのStack
    def __init__(self, scope: core.Construct, id: str, deploy_stack: core.Stack, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # ========================================
        # CodePipeline
        # ========================================
        codepipeline = aws_codepipeline.Pipeline(
            self,
            id='sample_pipeline',
            pipeline_name='sample_pipeline',
        )

ContinuousDeliveryStackのコンストラクタにdeploy_stackを追加しました。
このPipelineを使用してデプロイするスタックを、外(app.py)から渡す想定です。

aws_codepipeline.Pipelineには最低限の引数を渡してあげます。

2. Source Stageを定義

  1. GitHubTokenを取得
    今回はGitHubをリポジトリに使用しているのでこちらを参考にGitHubのOauthトークンを取得します。

  2. Tokenの格納
    取得したトークンはParameterStoreに格納しました。
    image.png

  3. コーディング

continuous_delivery.py
        # Source StageのOutputのArtifact
        source_output = aws_codepipeline.Artifact('source_output')

        # 自身の設定に変更すること
        owner = 'joe-king-sh'
        repo = 'aws-cdk-fargate-batch'
        branch = 'master']

        # GitHub oauth_tokenをSSMから取得する
        oauth_token = get_parameters('GITHUB_OAUTH_TOKEN')

        # GitHubのSourceActionを定義
        source_action = aws_codepipeline_actions.GitHubSourceAction(
            action_name='source_collect_action_from_github',
            owner=owner,
            repo=repo,
            trigger=aws_codepipeline_actions.GitHubTrigger.POLL,
            oauth_token=core.SecretValue.plain_text(oauth_token),
            output=source_output
        )
        # PipelineにSource Stageを追加し、GitHubからソースを取得するActionを追加
        codepipeline.add_stage(
            stage_name='Source',
            actions=[source_action]
        )

get_parametersは後の方に定義したSSMを扱うヘルパー関数です。

※リポジトリにCodeCommitを使用する場合

CodeCommitを使用したい場合はActionを下記にように書けば良いです。

CodeCommit使用時
        # ARNからCodeCommitのリポジトリを取得
        repository = codecommit.Repository.from_repository_arn(self, 'sample-repository', 'arn:XXXXX')

        # CodeCommitからソースを取得するアクションを定義
        source_action =  codepipeline_actions.CodeCommitSourceAction(
            repository=repository,
            branch='master',
            action_name='source_collect_action_from_codecommit',
            output=source_output,
            trigger=codepipeline_actions.CodeCommitTrigger.EVENTS
        )

3. Build Stageを定義

continuous_delivery.py
        # CodeBuildProjectの定義
        project = aws_codebuild.PipelineProject(
            self,
            id='sample_build_project',
            project_name='sample_build_project_name'
        )

        # CodeBuild実行ロールでParameterStoreにアクセスできるようにPolicyを追加
        project.add_to_role_policy(
            aws_iam.PolicyStatement(
                resources=['*'],
                actions=['ssm:GetParameters']
            )
        )

        # PipelineにBuild Stageを追加
        build_output = aws_codepipeline.Artifact('build_output')
        codepipeline.add_stage(
            stage_name='Build',
            actions=[
                aws_codepipeline_actions.CodeBuildAction(
                    action_name='CodeBuild',
                    project=project,
                    input=source_output,
                    outputs=[build_output]
                )
           ]
        )

ssm:GetParametersをCodeBuildロールに付与しないと、
Buildステージで権限不足でビルド失敗するので注意。

4. Deploy Stageを追加

continuous_delivery.py
        # Add deploy stage to pipeline.
        codepipeline.add_stage(
            stage_name='Deploy',
            actions=[
                app_delivery.PipelineDeployStackAction(
                    stack=deploy_stack,
                    input=build_output,
                    admin_permissions=True,
                    change_set_name='sample-change-set'
                )
            ]
        )

app_delivery.PipelineDeployStackActionのstackに、デプロイするstackを指定します。

実装 - app.py

app.py
#!/usr/bin/env python3
from aws_cdk import core
from aws_cdk_fargate_batch.aws_cdk_fargate_batch_stack import AwsCdkFargateBatchStack
from continuous_delivery.continuous_delivery_stack import ContinuousDeliveryStack
app = core.App()

fargate_batch_stack = AwsCdkFargateBatchStack(app, "aws-cdk-fargate-batch")

ContinuousDeliveryStack(app, id="continuous-delivery", deploy_stack=fargate_batch_stack)

app.synth()

fargate_batch_stackを今回新たに作成したContinuousDeliveryStackに渡します。

実装 - buildspec.yml

buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.7
    commands:
      - |
        # AWS-CDKとPython実行環境をインストール
        echo Instaling packages started..

        python3 -m pip install pipenv
        npm install -g aws-cdk
        pipenv install

  build:
    commands:
      - |
        # Python仮想環境を立ち上げ
        echo Launching virtual environments started..
        export VENV_HOME_DIR=$(pipenv --venv)
        . $VENV_HOME_DIR/bin/activate

        # CDKからCloudformationに変換
        echo Translating into cloudformation started..
        cdk synth aws-cdk-fargate-batch

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

いざデプロイ

下記コマンドでCodePipelineのスタックのみを手動でデプロイします。

$ cdk deploy continuous-delivery
  1. continuous-delivery スタック image.png continuous-deliveryスタックのデプロイが始まりましたー!:zap:

image.png
continuous-deliveryスタックのデプロイが終わるとCodePipeLineが構築されています。

  1. aws-cdk-fargate-batch スタック image.png image.png

 先程のPipelineを通して、無事AwsCdkFargateBatchStack達がデプロイされました:zap:

お掃除

下記cdkコマンドでStackを削除します。

$ cdk ls
aws-cdk-fargate-batch
continuous-delivery
$ cdk destroy aws-cdk-fargate-batch
Are you sure you want to delete: aws-cdk-fargate-batch (y/n)? y
$ cdk destroy continuous-delivery
Are you sure you want to delete: continuous-delivery (y/n)? y

必ずaws-cdk-fargate-batchから消すこと。
先にcodepipelineを消すと色々と面倒になる可能性があります。

※前回同様ECRとCloudWatchLogGroupに加え、
 Codepipelineで使用するArtifact格納用のS3は自動では消えません。手動で削除してください。

まとめ

通常のCfnでCfnをデプロイするCodePipelineを作成する場合と違い、
CDKでは、Codebuildの中で「cdk synth」してCloudformationを出力する必要があります。

今回は、例として前回構築したFargateバッチを使用しましたが、
PipelineStackに渡すStackを変更すれば、色々と応用が効くと思います。

次回は、CDKでSAMがデプロイできると聞いたので、調べて書いてみようと思います。
それではまた:raised_hands:

参考にさせていただきました

[AWS CDK] CodePipelineのソース元を色々指定してみました(CodeCommitとか、Githubとか、S3 Bucketとか、BacklogのGitとか)
CloudFormationの全てを味わいつくせ!「AWSの全てをコードで管理する方法〜その理想と現実〜」 #cmdevio
Continuous Integration / Continuous Delivery for CDK Applications

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした