自己紹介

山本達也(以下、山本):「CDKのコードの読み方とコントリビューション」という題で発表します。よろしくお願いします。

私は「やまたつ」という名前でいろいろやっています。Classmethodという会社で仕事をしていて、IoT周りのCDKを実装しています。aws-iot、aws-iotevents、aws-wafv2は進めていいよとメンテナーに言ってもらいましたが、プルリクとイシューをまだレビューしてもらえていないので、これからという感じです。

aws-iot実装でできたこと

山本:こんな感じでやっています。僕が書いて実装したらどうなったかというところを説明します。(スライドを示して)これはiotのL1しかなかった時代でのCDKコードです。L1は、CFnというプレフィックスが付いていて、CloudFormationと同じ書き方しかできません。その時はこんな感じのコードでした。MQTTプロトコルを受け取ったら、Lambdaを発火して、Firehoseにメッセージを流し込んで、もしそのあたりでエラーがあったら、S3に書き出しておくというコードになっています。

これらはL1なので、ARNをそのまま渡さなければいけなかったり、リソース名を渡さなければいけません。ロールの世話もしてくれないので自分でドキュメントを読んで、それらのロールにバケットの読む権限を渡さなければいけないんだなとか、Firehoseに書き込む権限が必要だから、そのロールを書いてここで渡さなければいけないんだなとか、ドキュメントを読みながら組むしかなかったのですが、L2のコードではそのあたりが攻略できるようになっています。

差分について見ていこうと思います。これは僕が実装する前のもので、僕が実装したのはまだエクスペリメンタルでしかありません。この「-alpha」を付けないとインストールできないので、今もし使う場合には「-alpha」を付けてください。こうやってインポートすると、このあたりのロールの設定が全部要らなくなります。

(スライドを示して)CloudFormationの書き方をしていたコードがこんな感じです。ギュンと減って、「FirehoseStream」をそのまま食べさせて、「FireForceStreamAction」をnewして、「Function」もnewして、Actionsに与えます。エラーも「S3PutObjectAction」でnewすればいいです。ロールの世話をしなくてよくなったし、リソースをそのまま渡すので、「ここはARNを渡さなければいけないんだっけ」「リソース名を渡さなければいけなかったのだっけ」と考えなくても使えるようになっています。そのあたりをうまく実装できたので良かったなと思っています。

状態マシンを作る「aws-iotevents」

山本:これもたぶんあまり使わないと思うのですが、aws-ioteventsという状態マシンを作れるサービスがあります。ここに「states」があって、2つの状態を定義しています。これはL1しかなかった時の書き方です。こういうふうに宣言的にJSONで書くしかなくて、「states」にwarmというstatesと、coldというstatesを作って、その状態遷移としてwarmからcoldに遷移する、coldからwarmに遷移する。

インプットの温度が15未満だった時や、15以上だった時に遷移するという書き方をします。CloudFormationではこういう書き方をするし、L1のCFnというプレフィックスが付いているものでは、こういう書き方しかできなかったのですが、コントリビュートした結果、今はこういうふうに書けるようになっています。(スライドを示して)最新版では、これが書けると思います。書けなかったら次のリリースでは乗ると思います。

状態をこういうふうに定義して、2つの状態を宣言したあとで、トランジションという有機的な書き方ができるようになっています。温度と閾値である「15」を先に宣言して、それの「lt(未満)」と「gte(以上)」、それぞれの時にこういうトランジションするよと有機的な書き方ができます。

先ほどのiotの例では、コードがぎゅっと減っていたと思います。かたや、こちらのコードはそんなにたくさんコードが減っていることはないと思います。ただ、宣言的だけではない説明的な書き方ができるようになる場合が多いと思います。

CDKにはスマートデフォルトというポリシーがあって、場合によってはそのデフォルト値がはまって、すごく少ないコードでうまくワークすることもあると思うのですが、ここは変えたいんだよねと、オプショナルな差しても差さなくてもいい設定をいっぱい差していくと、結局コードが大きくなることもあるだろうなと思っています。とはいえ、こういう書き方ができます。

発表と通して「ぐぬぬ」を「ビカン」にしたい

山本:今日話す概要ですが、どういうコードを書いてあるのかなという話をして、さらにそこに修正したいことがあった場合に、コントリビュートに向けてどういうことをやればいいのかなという話をして、終わりたいと思います。

その前に、ゴールの共有です。(スライドを示して)僕はこの発表を通して、こういう顔になっちゃう時を、こういう顔にしたいと思っています。「ぐぬぬ」という時に、「ビカッ」という顔にできたらいいなと思っています。

CDKをやっていてドキュメントが足りない時があると思います。まだまだドキュメントがきちんと書ききれていないところがあるので、そういう時にコードに書いてあるとおりにしか動かないし、コードを読めばいいやとなれたらいいなと思っています。

Propsの差せる属性が足りなかったり、機能が足りなかったり、「うーん、やりたいことができない」となる時に、「チャンス、そこはコントリビュートできるじゃん、やったね」となれたらいいのではないかなと思っています。「ぐぬぬ」が「ビカン」となったらいいなと思っています。

(スライドを示して)コントリビュートに関してですが、レースチャートというよくTwitterで流れてくるものがあります。勝手に更新してくれるので、コントリビュート数が何プルリクマージされたかに応じて、ここに名前が載るようになっています。上位100位を載せているので、7コミット、7プルリクマージされると、下からギュインと上がります。

そこから1プルリクマージされるごとにごぼう抜きの連続になるので、わりとすぐに50位ぐらいに入るかもしれません。ユーザー数で考えると、そのコントリビューターはメチャクチャ多いわけではないので、チャンスだと思います。優良物件です。こういうのもモチベーションの足しにしてもらえたらと思っています。

https://yamatatsu.github.io/aws-cdk-contribution-chart/

CDKの予備知識

山本:では、さっそくCDKのコードの中の話をしていきます。その前に、CDKの予備知識の話をします。L1、L2、L3についてはそんなに話すことがないかもしれません。CDKは「Golang」や「.NET」でも提供されているのですが、管理しているコード自体は、TypeScriptのものしかありません。

そのコードを、AWSが作っているTypeScriptの構文木を読んで、他の言語に変換する装置jsiiで、他の言語とドキュメントなどを自動生成しているので、コントリビュートしたり、コードの中を読んだりする時は、TypeScriptのコードを読むのがたぶん早いです。リポジトリを落としてきて、コードの中を読むとなったら、TypeScriptを読むことになると思います。

L1とL2とL3について。L1は、CloudFormationの定義から自動生成していて、これはもうすでに完備されています。StackSetsとかはできないですが、テンプレートに書ける内容はすべて書けます。L2に関して言うと、私が今回実装したのはL2で、L1をラップしてユーザーに優しくしたクラスを実装しました。

Lambda関数を定義する「function.ts」ファイルを見る

山本:では、「function.ts」というLambda関数を定義しているファイルの中を、実際に見ていこう思います。

(スライドを示して)本当は1000行ぐらいあるのですが、今回はそれをぎゅぎゅぎゅっと見たいところだけに絞って、簡略化したコードで説明させてもらえればと思います。今回説明する内容は、他のクラスにも当てはまります。L2というクラスの目的を説明するので、どのクラスもその目的で動いてるというのがわかると、かなり読みやすくなると思います。そういうコンセプトで話せればと思います。

まず、「Function」。Lambda関数を宣言するクラスです。クラスがあって、リソースを継承しています。これはAWSリソースを抽象しているクラスです。だから、AWSリソースを表現しているクラスは、みんなこれを継承しています。

そこにconstructorが実装されています。CDKを使ったことがある人にはお馴染みだと思いますが、constructorは3つの引数を持っていて、それらの引数を渡します。その3つの引数のうち、3つ目に「props」を渡すのですが、これがクラスごとの特性を決める引数になって、たいてい同じファイルの上のほうに「function props」と「interface」が定義されています。

(スライドを示して)例えばLambdaであれば、ここの中にコードがこういうふうに宣言されているし、そこにはJSDocがきちんと書かれています。だから、コードの中を読むだけで、ある程度どういう動作をするのかがわかるし、デフォルト値もここにきちんと書いてあるので、読めばわかるようになっています。話せばわかるやつなんですよ。

JSDocは絶対にあります。CIはこれを書かないと落ちるようになっていて、JSDocを書いていないやつは通らないので、絶対にJSDocは書いてあります。ただ、時々記載が間違っています。時々なのか、たまになのか、よくなのか、記載が間違っているのでチャンスです。記載間違いを見つけたら、それでもう1ゲットですよ。

(スライドを示して)propsで与えた値はこのようにコンストラクタの中で消費されます。消費される時は「Cfnfunction」、L1にこういうふうに与えます。なので、propsを定義するけれども、それは必ずCloudFormationというかたちで消費されるということを覚えておいてもらえればと思います。このL1クラスと呼ばれて、CloudFormationのインターフェイスがそのまま実装されています。CloudFormationのドキュメントがけっこうしっかり完備されているのを読んだ人もいると思いますが、CloudFormationのドキュメントを読めば、使い方がわかるようになっています。だから、コントリビューションの仕方もわりとわかります。

これはかなり単純化したコードですが、基本的にはこういうふうになっていて、入力のpropsはここで、入力と出力がこういうかたちになっています。基本的なCDKのクラスの目的はこのようになっています。

LambdaのHandlerの実装をシミュレーションしてみる

山本:LambdaのHandlerの実装をシミュレーションしてみましょう。ドキュメントを読めばHandlerをストリングで入れるのがわかるので、まずこうやってCloudFormationにHandlerを足して、propsから取るようにしておいて、propsにHandlerを足して、JSDocを書きます。

JSDocはコントリビューションガイドラインか、デザインガイドラインに公式のドキュメントをコピってくるのがいいよ、自分で作文しないほうがいいよと書かれているので、「あざっす」という感じで引っ張ってきて、DeepLで翻訳して、ちょっと変えればマージされます、大丈夫です。コントリビューションすることが目的だったら、英語を巧みに使えるかどうかは、ただの手段でしかないので、DeepLがそこを代替できるのだったら、コントリビューションの障害にはまったくならないと思っています。

こんな感じで、あとはテストとREADMEを書けばプルリクは出せます。やったね。

コントリビュートに向けて、実際にコントリビュートするとなると、先ほども言ったとおり、テストとREADMEを書いて、プルリクを出し、CIを通す必要があります。テストにはクラスのTypeScriptの動作自体を確認するUnit Test、実際にAWSにデプロイしてみて、きちんとデプロイできるかどうかを確認するInteg Testがあります。

READMEを書いて、プルリクを作って、CIを通すというのが必要な作業です。それについて話そうと思ったのですが、あまりおもしろい話にならないし、グダグダしゃべっちゃう感じになりそうだったので話しません。ブログに書きます。興味があったら、ブログを読んで書けばいいと思います。

「Pulumi」vs「AWS CDK」

山本:もっとエキサイティングな話ができたらいいなと思っています。

(スライドを示して)これは「Pulumi」の公式の記事ですが、「vs.CDK」というちょっと血生臭い記事があります。今は修正されたのですが、ちょっと前はそこに「CDKはCloudFormationのテンプレートを吐くことしかできないから、そこが限界だよね」と書かれていました。

CDKのSlackコミュニティで、「ん?ちょっと待ってよ」という話があって、コントリビューターの1人の方がPulumiにイシューを立ててくれました。これはぜんぜん血生臭くなくて、すごく平和的ですごく紳士的です。文末にも、ちょっと見落としているところがあるのではないかな? という話をして、読んでくれてありがとうと言っている、すごく紳士的なイシューです。

そこにもともとどんなことが書かれていたかというと、「CDKはCloudFormationのYAMLしか吐けないよね。対して、PulumiはYAMLだけではなくて、Dockerコンテナのbuildとpublishもできるし、サーバーレスのリソースのデプロイや、buildもできるようになっているよね」と書かれていました。

そこに関しては、「CDKもできるんだよ」と書いてあります。「CDKでは、JSONやYAMLのテンプレートを吐くのではなくて、Cloud Assemblyという新しい概念で扱えるように、包括した概念としてCloud Assemblyと銘打って扱えるようにしているんだよね」という話をしてくれています。CloudFormationには依存しているけれども、Pulumiに負けない汎用性がある。CDKはすごいという話がありました。

並列デプロイをサポートするプルリクが挙がっている

山本:でもなぁ……CloudFormationはスタックの中であればリソースたちをすごく効率的にデプロイしてくれます。ここは並列にできる、ここは並列にできないと考えて、依存グラフを読んできちんとデプロイしてくれるのですが、CDKはスタックの依存グラフを直列でしかデプロイしてくれません。そのせいでCDに時間がかかったり、CDがタイムアウトしたりする時があります。困った。

でも、大丈夫です。(スライドを示して)今は、こんなプルリクが挙げられていて、まだマージされていないのですが、こちらのプルリクは並列デプロイをサポートするものです。依存グラフを読んで、指定された並列度でデプロイするのを実装したという人が現れました。これはメチャクチャ順調という感じではないですが、けっこう応援の声も多いですし、なんらかのかたちでマージされるのではないかなと思います。並列の時も、このスタックがこういう進捗で、1のスタックはこういう進捗でと見えるようになっています。

https://github.com/aws/aws-cdk/pull/19378

課題はいろいろあって、そう簡単ではないからどう対応していこうかと話されています。将来に期待して、このあたりも来たらいいなと思っています。

CDKインポートのプレビューがデプロイされた

山本:でもなぁ……まだあるんだよな。Terraformは既存のリソースの取り込みがメチャクチャ強いです。あれがやはり、CloudFormationだとできません。

それがこのプルリクです。新しいCDKコマンドのプレビュー版、CDKインポートがすでにマージされて、まさに昨日(※登壇当時)デプロイされました。

CDKインポートを唱えたら、既存のリソースの内容を見て、TypeScriptのコードがバキバキ、ドンと出来上がるなんてことは絶対にあり得ないです。たぶん、その未来は訪れません。これはちょうど昨日(※登壇当時)リリースされて、最新版だとこのCDKインポートが使えます。

CloudFormationはインポートができます。インポートして、JSONやCloudFormationテンプレートをこんな形になるよとできるのですが、それをCDKとマッピングする方法もありました。ちょっと泥臭いですが、マッピングを自動でやってくれます。つまり、既存のリソースをインポートしたCloudFormationテンプレートと同じ形のテンプレートを吐くCDKのコードを用意して、そのうえでCDKインポートを唱えると、このリソースをCDKの支配下に置いてくれるものになっています。

これはCDKインポートがある前からがんばればできていて、その時も「インポートエラーを100万回目にする」というブログをよく読みましたが、エラーはけっこう出ています。

では、このCDKインポートが実装されたら、その100万回のエラーが30回になるかというとなりません。100万回という数字は変わらないけれども、トライ&エラーのサイクルを短くできる機能の追加です。僕はまだ試していないですが使えそうなので、誰か試したらブログを書いてみてください。こんな感じで今も絶賛機能追加されていて、発展的なCDKを見守ってもらえたらと思います。

https://github.com/aws/aws-cdk/pull/17666

「おわりに」です。唐突に終わりますが、先ほど紹介したコードは、実物をかなり単純化したものですが、基本的な読み方に変わりはありません。CLIの部分や、Custom ResourceやAssetsなど例外はあるのですが、基本的にコードを書くAWSリソースを表現するクラスたちの99%は、CFnプレフィックスが付いたあれを出力するために入力をいろいろいじるかたちになっているので、そこがわかっているだけでも読みやすくなるのではないかなと思っています。ドキュメントの不備や生えていない機能、まだ実装されてない機能を見かけたら、コントリビュートしてみてはいかがでしょうか。

以上、ご清聴ありがとうございました。

質疑応答

吉田真吾氏(以下、吉田):ありがとうございました。

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

吉田:そんなにおいしい話ではないと(笑)。

山本:そうですね。高級言語のTypeScriptをバーンと出すのは、たぶん人類にはかなり難しいと思いますね(笑)。

吉田:確かに(笑)。新居田さん、質問は来ていますか?

新居田:1つ来ています。「CDKをIotで利用する場合に、ドキュメントや書籍はありますか」。いかがでしょうか。

山本:iotの書籍、ドキュメントはAWSドキュメントにチュートリアルがあったので、僕は公式ドキュメントを読みました。日本語訳がちょっと上手ではないところもあるので、そこはGoogle翻訳に任せたほうが読みやすいかもしれません。すみません、良い書籍をちょっと知らなくて、回答がないです、ノーアンサーです。

吉田:後ほど岡さん(※岡智也氏)からも書籍の紹介があるようなので、ぜひそちらも聞いてもらえればと思います。

山本:あとはSlackやCDKでイシューを挙げてくれたら、僕が回答します。

吉田:ぜひよろしくお願いします。以上、やまたつさんでした。ありがとうございました。