CDK Pipelinesを用いたCI/CDパイプラインの作成
概要
以下の記事の続きです。
CDK Pipelinesを使ってCI/CDを実施しました。やや分かりづらいCDK Pipelinesの処理フローをハンズオンと共に説明します。
背景
CDK WorkshopでCDKの学習していると、CDK Pipelinesを使用してCI/CDのパイプラインの作成まできるようだったので、試してみました。
CDK Pipelinesとは
CDK Pipelineとは、CDKのCodePipelineを利用したCI/CDのワークフローを構築してくれる、CDK Constructです。こちらのConstructを使用することで、CI/CD周りの設定もIaC化することが出来ます。
CDK Pipelinesを使用したスタックをデプロイすると、以下のような処理フローが作成されます。
-
Source
こちらの処理では、GitHubやCodeCommitなどのGitリポジトリに変更がpushされた際に、CDKのコードを取得してくる処理を行います。 -
Build
こちらの処理では、取得したCDKのコードから、CloudFormationのスタックテンプレートが正常に生成されるかどうかをテストします。 -
UpdatePipeline
パイプラインの変更を検出して、パイプラインの更新を行います。自分で自分を更新するので、self-mutateという中二病的なかっこいい名前がついています。 -
Assets
CloudFormationのスタックテンプレートやその他のアーティファクトを作成して、S3にアップロードします。 -
Deploy
S3から取得したアーティファクトをAWS環境上にデプロイします。
パイプラインの作成
今回はパイプラインの中でスタックをデプロイするようにするので、App層に記述していたBackendStackおよびFrontendStackを削除します。その代わりに、SpotipyPipelineStackを追加します。
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { SpotipyPipelineStack } from "../lib/pipeline-stack";
const pjPrefix = 'Spotipy';
const app = new cdk.App();
// For pipeline deployment
new SpotipyPipelineStack(app, `${pjPrefix}PipelineStack`, {});
SpotipyPipelineStackは以下のように定義します。
import * as cdk from 'aws-cdk-lib'
import * as codecommit from 'aws-cdk-lib/aws-codecommit';
import { Construct } from 'constructs'
import { CodeBuildStep, CodePipeline, CodePipelineSource } from 'aws-cdk-lib/pipelines';
import { SpotipyPipelineStage } from '../lib/pipeline-stage'
export class SpotipyPipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
//Creates CodeCommit repository called 'SpoityRepo'
const repo = new codecommit.Repository(this, 'SpotipyRepo', {
repositoryName: "SpotipyRepo"
});
const pipeline = new CodePipeline(this, 'Pipeline', {
pipelineName: 'SpotipyPipeline',
synth: new CodeBuildStep('SynthStep', {
input: CodePipelineSource.codeCommit(repo, 'master'),
installCommands: [
'npm install -g aws-cdk',
'npm install -g npm',
],
commands: [
'mkdir -p lambda_layer',
'pip install -r requirements.txt -t ./lambda_layer/python/lib/python3.9/site-packages',
'npm ci',
'npm run build',
'npx cdk synth'
]
},
),
});
//Adds deploy stage
const deploy = new SpotipyPipelineStage(this, 'Deploy');
const deployStage = pipeline.addStage(deploy);
//Adds test stage
deployStage.addPost(
new CodeBuildStep('TestAPIGatewayEndpoint', {
projectName: 'TestAPIGatewayEndpoint',
envFromCfnOutputs: {
ENDPOINT_URL: deploy.APIEndpoint
},
commands: [
'curl -X POST --data-urlencode "track_num=1" "${ENDPOINT_URL}sync"'
'curl -X POST --data-urlencode "track_num=1" "${ENDPOINT_URL}async"'
]
})
)
}
}
以下の部分でCodeCommitのレポジトリを定義します。レポジトリ名はSpoityRepo
とします。
//Creates CodeCommit repository called 'SpoityRepo'
const repo = new codecommit.Repository(this, 'SpotipyRepo', {
repositoryName: "SpotipyRepo"
});
CIの部分では、SpoityRepoのmasterブランチへのpushをトリガーに処理が開始するようにします。InstallCommandsを以下のようにすることで、npmでCDKのモジュールのインストールとnpmのアップデートを行います。npmのアップデートを行っているのは、CodeBuildが古いバージョンのnpmを使用していて、npm ciコマンドを実行した際にエラーが出るためです。
installCommands: [
'npm install -g npm',
'npm install -g aws-cdk',
],
commandsの部分では、以下の処理を行います。
- lambda_layerディレクトリの作成
- Pythonのプログラムに必要なライブラリのインストール
- npmで必要なパッケージのインストール
- CDKのコードのビルド
- CloudFormationのテンプレートの生成
commands: [
'mkdir -p lambda_layer',
'pip install -r requirements.txt -t ./lambda_layer/python/lib/python3.9/site-packages',
'npm ci',
'npm run build',
'npx cdk synth'
]
なお、Pythonのライブラリのインストールには、requirements.txtを用いて行います。以下のように必要なライブラリを記載しておくことで、pipで一括インストールが出来ます。
async-timeout==4.0.2
boto3==1.26.23
botocore==1.29.23
certifi==2022.9.24
charset-normalizer==2.1.1
idna==3.4
jmespath==1.0.1
pycodestyle==2.10.0
python-dateutil==2.8.2
redis==4.4.0
requests==2.28.1
s3transfer==0.6.0
six==1.16.0
spotipy==2.21.0
tomli==2.0.1
urllib3==1.26.13
CDで行う処理は、以下のようなクラスを用いて別ファイル内に定義します。app.node.tryGetContext()
から必要な値をとってきたり、デプロイするスタックを指定する部分は一緒ですが、クラス変数にFrontendStackのAPIEndpoint
を代入します。APIEndpoint
にはスタックのデプロイ時にAPIエンドポイントのURIが文字列として代入されます。この変数は後のAPIエンドポイントをテストする処理で使用します。
CDの処理内容を定義したクラスを用意したら、以下のようにして処理内容をパイプラインに追加することが出来ます。
//Adds deploy stage
const deploy = new SpotipyPipelineStage(this, 'Deploy');
const deployStage = pipeline.addStage(deploy);
最後に、APIGatewayのエンドポイントのテストを行う処理も追加します。
//Adds test stage
deployStage.addPost(
new CodeBuildStep('TestAPIGatewayEndpoint', {
projectName: 'TestAPIGatewayEndpoint',
envFromCfnOutputs: {
ENDPOINT_URL: deploy.APIEndpoint
},
commands: [
'curl -X POST --data-urlencode "track_num=1" "${ENDPOINT_URL}sync"',
'curl -X POST --data-urlencode "track_num=1" "${ENDPOINT_URL}async"'
]
})
)
ENDPOINT_URL: deploy.APIEndpoint
とすることで、環境変数ENDPOINT_URL
にAPIエンドポイントを埋め込みます。curlコマンドではこちらの環境変数を参照して処理を行います。
パイプラインのデプロイ
上記の準備が出来たら、cdk deploy
コマンドでパイプラインをデプロイします。なお、このデプロイを行う必要があるのは最初の一回のみです。二回目以降は、パイプラインの変更をcommit&pushすればUpdatePipelineの部分で自動で更新されます。
スタックをデプロイすると、CodeCommitのレポジトリと以下のようなパイプラインが作成されます。
初回はリポジトリに何も入っていいので、Sourceの段階で処理が失敗します。
パイプラインを作成したら、以下のコマンドでローカルの変更内容をコミットしておきます。
git add .
git commit -m "hogehoge"
今回は、HTTPS Git認証を用いてCodeCommitに接続します。なお、この操作を行うには事前にIAMでHTTPS Gitの認証情報を発行しておく必要があります。
認証情報を発行したら、以下のコマンドでリモートリポジトリを追加します。URLにCodeCommitのユーザー名とパスワードを埋め込むのがミソです。
git remote add origin https://"ユーザー名":"パスワード"@git-codecommit.us-east-1.amazonaws.com/v1/repos/SpotipyRepo
リモートリポジトリを追加したら、以下のコマンドでpushします。pushすると、パイプラインの処理が開始します。
git push --set-upstream origin master
今度は最後の処理まで成功しました!
Buildのフェーズはコードで定義した通りなので、それ以降の処理で何が行われてるか見てみましょう。UpdatePipelineのBuildspecを確認してみます。
{
"version": "0.2",
"phases": {
"install": {
"commands": [
"npm install -g aws-cdk@2"
]
},
"build": {
"commands": [
"cdk -a . deploy SpotipyPipelineStack --require-approval=never --verbose"
]
}
}
}
いろいろオプションがついていますが、要するにパイプラインのスタックをcdk deploy
でデプロイしています。
AssetsのBuildspecも確認してみます。FileAsset1を見てみます。
{
"version": "0.2",
"phases": {
"install": {
"commands": [
"npm install -g cdk-assets@2"
]
},
"build": {
"commands": [
"cdk-assets --path \"assembly-SpotipyPipelineStack-Deploy/SpotipyPipelineStackDeploySpotipyBackendStack16DCEC9E.assets.json\" --verbose publish \"796bd4da63bf70b119d38a693031420393e51d39067b4120bf236a007133de49:current_account-current_region\""
]
}
}
}
cdk-assetsというコマンドが出てきました。これは何かというと、CDKのアセットをAWS上にデプロイするためのコマンドです。
アセットとは、CDKのライブラリやアプリケーションにバンドルできるローカルファイルやディレクトリ、Dockerイメージのことです。今回の例でいうとLambda関数を構成するPythonのファイルやS3に格納するhtmlファイルが該当します。要するにデプロイに必要なアーティファクトのことですね。CDKはアセットを参照するようなアプリをデプロイする場合、最初にアセットを準備した上でS3またはECRにアップロードして、その後にスタックをデプロイします。CDKでは、公開されたアセットの場所をCloudFormationのパラメータとして関連するスタックに指定して、その情報を元にCDKのアプリ内でアセットの場所を参照出来るようにしています。
以上のようにAssetsの処理でアセットを準備できたら、後続の処理でCloudFormationがテンプレートのデプロイをしてくれます。なお、アセットはcdk.out
フォルダに格納されます。
処理フローの図解
処理の流れがわかりづらいので、図で説明してみます。
まず、ローカルからパイプラインのスタックをデプロイすると、AWS環境上に以下のようにパイプラインが作成されます。
ソースコードの変更をpushすると、処理が開始します。
Buildでは、cdk synth
でCloudFormationのテンプレートの生成をテストします。
Update Pipelineでは、パイプラインの変更を検出して、パイプラインのスタックのデプロイを行います。デプロイ後は、また最初から処理を行います。パイプラインに変更がない場合は何もしません。
Assetsでは、アセットとCloudFormationのテンプレートをS3にアップロードします。
最後にDeployで、CloudFormationがアセットとテンプレートをもとに、AWS上にリソースをデプロイします。
以上がCDK Pipelinesの処理フローです。
感想
パイプラインもコードで記述のは便利ですが、最初はself-mutateやアセットの処理部分で何をしているかが分かりづらかったです。CDK Pipelinesに限らずこういうお手軽に実装できるものほど、エラーが出た時の対応や細かい処理内容を追いづらいと感じている今日この頃です。
Discussion