概要
aws-cdkをさらに便利にするツールをnpmに公開しました!!
本番環境、検証環境など複数環境を管理するのに便利かと思います。お試しください。
https://www.npmjs.com/package/cdk-env-manager
https://github.com/masahirompp/cdk-env-manager
概要図
利用イメージ
それでは以下詳細。
aws-cdkの考慮事項
aws-cdk(またはCloudFormation)でAWS環境を管理する場合、以下のようなことを考慮すると思います。
-
A. 複数の環境(production, staging, developmentなど)を管理したい
- 環境は無限に増える前提でコーディングすべき
- (ケース・バイ・ケースだが)1つのAWSアカウント上で、複数環境を管理したい
-
B. デプロイ時のパラメータ(URL, InstanceType, RemovalPolicyなど)を環境毎に管理したい
- パラメータの数が増えるとContextや環境変数で渡すのつらい
- パラメータを開発者で共有したいけれど、ローカルのJSONファイルなどにハードコーディングはやめたい、Gitの管理下にも置きたくない
-
C. StackのOutputs(ApiGatewayのEndpointUrlなど)を環境毎に管理したい
- Outputsを開発者で共有したいけれど、ローカルのJSONファイルなどにハードコーディングはやめたい、Gitの管理下にも置きたくない
-
D. デプロイ時の手順を統一したい
- cdk diffで差分を確認してからデプロイするなど
- E. Stackを適切に分けたい
これらをうまく解決するには、
- B, Cの各環境のパラメータやOutputsについては、SSMのパラメータストアで管理することで、ローカルファイルを使わず、開発者間で共有できそうです。
- Aもパラメータストアのパスを上手く区切ればできそうです。
- Dは、aws-cdkのラッパーを作ればできそう。
ということで、これらを解決するツール作りました!!
ツールの使い方
事前準備
aws-cdkなどインストールして、cdk init --language typescript
等の初期設定は済ましておいてください。
(本ツールはTypeScriptのみ対応です)
cdk-env-managerをインストール
yarn add -D cdk-env-manager
# npm install -D cdk-env-manager
cdk Stackの作成
各Stackのコードを作成します。
- cdk-env-managerからCdkStackBaseをimportして継承してください。
- createResourcesメソッドを実装してください。
// S3Stack.ts
import * as cdk from '@aws-cdk/core'
import * as s3 from '@aws-cdk/aws-s3'
import { CdkStackBase } from 'cdk-env-manager'
type Input = { removalPolicy: cdk.RemovalPolicy }
type Output = { myBucketArn: string }
export class S3Stack extends CdkStackBase<Input, Output> {
createResources() {
const myBucket = new s3.Bucket(this, this.name('MyBucket'), {
removalPolicy: this.props.removalPolicy
})
this.createOutputsSsmParameters({ myBucketName: myBucket.bucketName })
return {
myBucketArn: myBucket.bucketArn
}
}
}
補足として
- InputはこのStackが受け取るパラメータです。
- OutputはこのStackが出力するパラメータです。他のStackに渡したい場合に使います。
-
createOutputsSsmParameters(...)
は値をSSMのパラメータストアに書き出します。アプリ側から参照したい設定値などを、SSMに書き出してください。(アプリ側からの参照方法は後述) -
this.name("MyBucket")
は、"DevMyBucket"のように環境名のprefixをつけてくれるヘルパーメソッドです。
同様にここではもう1つStackを作成。
// RoleStack.ts
import * as iam from '@aws-cdk/aws-iam'
import { CdkStackBase } from 'cdk-env-manager'
type Input = { bucketArn: string }
type Output = { myRoleArn: string }
export class RoleStack extends CdkStackBase<Input, Output> {
createResources() {
const myManagedPolicy = new iam.ManagedPolicy(this, this.name('MyManagedRole'), {
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObject'],
resources: [this.props.bucketArn]
})
]
})
const myRole = new iam.Role(this, this.name('MyRole2'), {
assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'),
managedPolicies: [myManagedPolicy]
})
return { myRoleArn: myRole.roleArn }
}
}
cdk appの作成
複数のStackをまとめるcdk appを作成します。
- cdk-env-managerからCdkAppBaseをimportして継承してください。
- createStacksメソッドを実装してください。
- 末尾2行は必須です。
// MyApp.ts
import * as cdk from '@aws-cdk/core'
import { CdkAppBase } from 'cdk-env-manager'
import { RoleStack } from './stacks/RoleStack'
import { S3Stack } from './stacks/S3Stack'
type Parameter = { removalPolicy: cdk.RemovalPolicy }
export class MyApp extends CdkAppBase<Parameter> {
async createStacks() {
const s3Stack = new S3Stack(this, 'S3Stack', {
removalPolicy: this.deployParameters.removalPolicy
})
new RoleStack(this, 'RoleStack', {
bucketArn: s3Stack.exports.myBucketArn
})
}
}
const app = new MyApp()
app.synth()
補足として
- Parameterには環境ごとのパラメータを定義します。詳細は次へ。
パラメータファイルの定義
デプロイ時にパラメータを渡すことができます。パラメータは環境毎に設定できます。
パラメータの既定値をファイルに記載しておく必要があります。
プロジェクトのルートにcdk.parameters.default.env
を作成し、
デフォルトのパラメータをkey=value
形式で指定してください。
# cdk.parameters.default.env
REMOVAL_POLICY=retain
環境変数の設定
デプロイ時に必要な以下の環境変数を設定してください。
- AWS_DEFAULT_REGION
- AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEYのペア、またはAWS_PROFILE
ここでは.env
ファイルに記載している体で進めます。
# .env
AWS_DEFAULT_REGION=ap-northeast-1
AWS_PROFILE=my-profile
デプロイ方法
ここまで来たら準備完了です。デプロイをしてみます。
1. ツールの実行
ここでは、環境変数を.env
から読み込んで実行します。
npx -n '-r dotenv/config' cdk-env-manager
2. デプロイする環境の選択
CdkEnvKeyは環境を識別するキーです。CdkEnvKeyを選択してください。
初回実行時は既存の環境はありませんので、「create new Stacks」を選択します。
3. パラメータの確認と設定
デプロイ時のパラメータを、確認するか、変更するか、そのまま続行するか選んでください。
新規作成時や変更を選択した場合は、パラメータの編集ができます。
cdk.parameters.default.env
のデフォルトパラメータを増やすと、このプロンプトで編集できるパラメータも増えます。
4. 差分の確認
パラメータが決まったら、ツールが内部的にcdk diff
を実行します。
デプロイ時の差分を確認してください。
5. デプロイするStackの選択
diffを確認を受けて、デプロイするStackを選択します。1つでも複数でもOKです。
6. デプロイ実行
5で選択したStackをデプロイします。内部的にはcdk deploy
を呼び出しているだけです。
cdkのデプロイが終わるのをまって完了!!
アプリ側からStackの出力を参照
SSMに書き出された設定値をアプリ側から読み込みます。ここでは以下の手法をサンプルとします。
- アプリ側で利用する環境を、環境変数(CDK_ENV_KEY)で指定
- webpackのビルド時に設定値をSSMから読み込み、DefinePluginでアプリに渡す
import webpack from 'webpack'
import { loadStackParameters } from 'cdk-env-manager'
const configFunction: () => Promise<webpack.Configuration> = async () => {
// load stack parameters from ssm
const params = await loadStackParameters()
return {
entry: 'src/index.ts',
// ...省略
plugins: [
new webpack.DefinePlugin({
...Object.keys(params).reduce(
(payload, key) => ({ ...payload, [key]: JSON.stringify(params[key]) }),
{}
)
})
]
}
}
export default configFunction
SSMからパラメータをまとめて取得するには、getStackParameters
を使えばOKです。
最後に
駆け足になってしまってわかりにくいところもあるかもです。
Github側のREADME.mdなどをこれから整理し、わかりやすくしていく予定です。
手伝ってくれる方募集中です!!
(おまけ)Stackを適切にわけたい
Stack間でパラメータを参照する場合、いくつか方法がありますが、
aws-cdkでStack間でパラメータの受け渡しをすると、自動的にクロススタック参照になります。
クロススタック参照は便利ですが、致命傷を避ける必要があります。
致命傷の詳細は、いつもお世話になっているクラスメソッドさんのブログをご参照ください。
https://dev.classmethod.jp/cloud/aws/aws-all-iac/
私が実施している致命傷を避ける方法として、
運用上削除してはいけないユーザデータを含むリソース(RDS Instance, Cognito UserPool, DynamoDB Tableなど)を1つのStackにまとめ(以下UserDataStackと呼びます)
- UserDataStackを他のStackが参照する(依存する)
- UserDataStackは他のStackを参照してはならない(依存してはならない)。ただし、UserDataStackのリソースを作るためにどうしても必要なリソースのみ(例えば、RDS Instanceに対するVPCなど)、仕方ないので依存を許可。
というルールで構築しています。
こうしておけば、**Stack1〜4をすべて作り直し!!**とかできるので、致命傷になりにくいです。
おわり。