個人開発での実装例

稲葉太一氏(以下、稲葉):より実践的な実装例を書きました。「Notion」で書いていますが長いので、細かく見たい方は後ほどPDFで必要なところだけ共有します。

ここは必要ですね。まず、CDKのStackを直接使わないでStackを継承した自前のStackクラスを作ります。それにスタックのIDを動的に作らせるようにしました。コンストラクタで受け取ったIDに対してステージ名のサフィックスをつけて、親のCDKのStackのコンストラクタへ渡すようにしました。

自分が用意するCDKはすべてこのMyStackを継承してStackを作るというルールにして、Propsも同じようにContextを扱うためのPropsを定義します。cdk.jsonがあって、ここでhostedZoneNameやフロントエンド用のサブドメイン切る、フロントエンドのリソースを入れるバケット名を用意します。

このコンテキストを扱う独自のクラスのネーミングセンスがいまいちですが、コンテキストを扱うcontextという名前にします。

クラス自体はこのようになっていて、ステージ名を受け取ります。nodeはapp.node、tryGetContextを呼ぶためのもので、引数を取って、ステージ名を取って、「my」を呼んでいます。(スライドを指して)myというのはここです。このcontextのmyの中身を環境ごとのものを入れるオブジェクトだとして、myの中のstageをクラスのprivateプロパティに入れておいて、そのほかCDKを書く上で必要そうな諸々のプロパティやメソッドを定義して、実際に使うとこうなります。

cdk.Appを作って、app.node.tryGetContext('stage')でCLIから受け取るステージ名を取ります。この時に、TypeScriptのTypeを使って受け取れるstageの値を絞ります。ここでも予期しないステージ名が入ってこないように安全策を取っています。stageを受け取ったら、自分で作ったコンテキストを取り扱うクラスを作って、stageとapp.nodeを渡すと、このcontextにstageで指定されたcontextの値が入ったもの、オブジェクトが出来上がります。このcontextを実際のスタックに渡してあげます。

例えばホストゾーンを取得して、CloudFrontやAPI Gatewayに証明書を作らなければいけない時に、cdk.jsonに用意したhostedZoneNameを使います。Route 53のHostedZoneからfromLookup。contextのhostedZoneName(今指定されたステージのホストゾーンネーム)が入っているはずなので、これでドメインネームが指定できます。

証明書を作るところですね。今取得したホストゾーンとドメイン名はFQDNを生成するメソッドを生やしてあるので、フロントエンド用のFQDNを取ります。

これはCloudFrontなので、リージョンがus-east-1にならなければいけません。こちらはバックエンドのAPI Gateway用なので、リージョンの指定はなしでバックエンド用のFQDNを取ってくるというように、なかなかプログラムっぽく書けるかなと思います。

コンテキストを取り回すクラスを使うメリット

このコンテキストを取り回すクラスを使うメリットとして、フロントエンドとバックエンドのほか諸々の領域でオブジェクトを自由に区切って、さらにクラスを用意することで意味合いを保てると思います。Interfaceやtypeを使って、今のプロジェクトにはこの値しか使わないはずだという定義ができるし、なおかつIDEや「VS Code」での補完が使えるようになります。TypeScriptの恩恵に与れるということです。

スタックIDもそうなのですが、S3のバケット名は絶対重複できません。AWS全体でユニークな名前にしなければいけないことを時々忘れてしまうのですが、別途S3バケット名を生成するメソッドを用意してあげると、バケット名重複のエラーが起きなくなります。

これはアプリケーションエンジニアだからこその観点かなと思うのですが、アプリケーションエンジニアが気にすべきポイントは、cdk.jsonのcontextの中だけに集中できるという点だと思います。インフラ構成のコードを汚さないでほしいなと思います。

また、おまけとして、TypeScript力がつきます。僕がTypeScriptを始めたのは、このCDKからです。

CDKでコンテキスト扱うようにしてよかったこと

「17.コンテキストを取り扱うようにしてよかったこと」。これは本当にフィクションです。

お客さんのオンプレの環境と「Direct Connect」した先のデータベースをつなぎたい。「やだなあ、面倒くさいな」と思うのですが、これも本番環境の場合はifで括って、その中でvpcのenableVpnGatewayをしたり、ルーティングテーブルを追加したりすれば、本番環境だけDirect Connectがつながります。

実際、Direct Connectの疎通は手動でコンソール上でポチポチやったのですが、その作業内容を該当箇所のソースにコメントで残しました。インフラの構築に関してはCDKの中で完結させたかったので、READMEにも書かずにソースコード上にベタで書きました。スタンスとして、CDKで表現できそうなことをあえて手動でやった場合は、ソースコード上にコメントで残してあげるようにしました。

同じお客さんなのですが、「本番用アカウントとそれ以外用アカウントです」と渡されました。これもスタック重複回避が活きました。

(スライドを指して)これは先ほど追加したのですが、「S3バケット残りがち。素振りで遊ぶ時はRemovalPolicy.DESTROYしておくと楽ですよね。そしてそのまま本番に上がっているのを見つけて震え上がったりするところまででセットですね!」はい、わかります。すごくわかります。やまたつさんの言うことは、すごくわかる。ゆっきーさんは「S3バケットの削除ですが、s3.BucketのpropsにあるautoDeleteObjectsをtrueで設定しておくと『中にオブジェクトがあっても』バケットを削除することが可能です」。

「本番でやったらまずいやつ」を軽く、いいサンプルになるんじゃないかと思って書きました。removalPolicyで、context取り回しクラスの中に、例えばisProductionという判定のメソッドをつけて、trueなら残す、false(本番環境ではない)なら捨てる。autoDeleteObjectsのtrue・falseも、本番環境でなければtrueにするというように、本番環境とそれ以外での動作の環境を振り分けることができました。

さらに発展して何ができるか

ここまでが、実際に僕が作って使っている内容です。ここからは想像なのですが、「GitHub」へのプッシュ時にブランチ名をもとにしてQA環境をポコポコ生やすとか、それこそタグ付けでポコポコサブドメインを切ることもできそうです。ホスト名として成り立つブランチでなければダメだけど、それもTypeScriptで文字列の置換をしたり、ルールを設けることで回避したりできるかな、QA環境がデプロイできたら「Slack」に通知することもできるのではないかなと思いますが、そういう人数や規模ではないので、ここまではやっていません。

ここまで駆け足で話しましたが、実は今回登壇が決まった時点ではCDKのv1しか使っていませんでした。イベントの概要文に「本イベントではCDK v2をメインテーマに」とあったので、慌てて既存プロジェクト、CDK v1のプロジェクトをマイグレートしました。そこでゆっきーさんがアドバイスをしてくれました。先ほどのapigatewayv2の話。ツイート、メンションを飛ばしてもらって助かりました。

吉田さん(※吉田真吾氏)などがずっとCDK v2で推しているcdk watchもできました。うれしいですね、cdk watch。もっとLambdaを書きたいです。今回登壇を勧めてくれた吉田さんに「機会を設けてもらってありがとうございました」ということで締めます。ご清聴ありがとうございました。

質疑応答

吉田:ありがとうございました。

新居田晃史氏(以下、新居田):ありがとうございました。

稲葉:大丈夫でしたか?

吉田:バッチリです。Tipsがいっぱいあったので、実際にコピペで使い回そうかなと思いました。

稲葉:だいぶ端折りましたが、このへんは今回の登壇用に書き起こしたものなので、まあまあキレイに書いたつもりです。あとで公開して共有できたらと思います。

新居田:では、QAに移ります。1つ質問が来ています。ふだんプログラムを書いていないインフラエンジニアの方のようですが、「CDKで実装する場合はどのあたりからすべきでしょうか?」。

稲葉:これは僕とは逆のパターンですよね。難しいな。生粋のインフラエンジニアの方、プログラムを書かない方はどうするのでしょうか。

新居田:僕はこのパターンなのですが、インフラエンジニアはだいたい設定書に基づいて設定していくことが多い。そうすると、「いったんCDKのチュートリアルを眺めてやってみる」が一番デビューしやすいのかなと思います。

稲葉:CDKに限ればそうですよね。チュートリアルして、もし書いている中で、例えばTypeScriptがわからなくなったらTypeScriptのことを調べるくらいしかないのかな。それこそプログラムをどうやって学ぶかという話になっちゃいますもんね。

新居田:そうですね。そのへんから入って、だんだん自分の幅を広げていくようにつながるとよいのかなと思います。

稲葉:CDKに絞るのであれば、すでにあるインフラのすごく小さいスコープをCDKでどう表現するか、マッピングみたいなものができるようになったらいいんじゃないかな。すでにあるものを表現するためのコード。逆コンパイルみたいなことですね。

新居田:すでにあるものをCDKでコード化。

稲葉:どんな構成であっても、まっさらの状態からCDKを書くのはたぶんすごくしんどいと思います。でもすでに展開されているインフラは完全にお手本みたいなものなので、「アウトプットがこうなればいい」となると思います。そのためにがんばってCDKでTypeScriptを書いてみてください。

吉田:先ほどのコンテキストの引き回し方も、知らない状態からアイデアを考えながらやっていけば行き着くはずじゃないですか。

稲葉:そうですね。

吉田:でもそれはすごく面倒で大変なので、書き換えてあるものを類型として持っておいて、「こういうパターンでこうやってやったってことは、こういう引き回しの時も同じようにやるといいな。でもその場合、こういうところから値を取りたくないから、今はステージや環境変数から取るようにするけど、こっちに書いてあるファイルから読むようにしようかな」とか、工夫の積み重ねって、ちょっとずつですよね。

稲葉:そうですね。だから、プログラミングができることにすごく自由度の広さがあると思います。CDKのコード上で、外からファイルをダウンロードするとか、API叩いた結果を持ってくるとかもたぶんできると思います。ベストプラクティスって何だろうと考えると、キリがない感じがしますね。

吉田:コード上の内部設計の工夫というところですよね。

稲葉:そうですね。

藤井貴昭氏(以下、藤井):逆に今ある既存から、逆設定みたいなことはできたりするんですか?

稲葉:あるんですかね。例えばCloudFormationにするみたいなものは。

吉田:3つくらい思い付くので、ないことはないですよ。CloudFormationも公式に、ありませんでしたっけ? CloudFormationを入れると、デザイナーが。

稲葉:ああ、デザイナー。たぶん、それを読み解くことになりますよね。

藤井:わかりました。メチャクチャ言いました(笑)。

(一同笑)

吉田:先ほども話していて思ったのですが、やはり内部設計の工夫ですよね。コンテキストは特に最たるものだと思っています。コンテキストから引っ張って引き回すみたいな話。ベストプラクティスがあるのかわからないという話もありましたが、それこそ積み上げていくところじゃないですか。

稲葉:そうですね。どういうふうに仕切るか、どういうふうに決めるかというのは、アプリケーションを書いているのと変わらないです。それこそコンテキストの文字どおり、いかに文脈を持たせるかが大事だと思うんです。

吉田:こっちに閉じ込めるのか、こっちは開いておくのかという内部設計の話だと思う。ぜひ次回も工夫したノウハウがあればお願いします。