AWS CDKでインフラ構築したので、CDKの概要や魅力を紹介
つい先月、私が関わっている案件でインフラリプレースをし、無事完了しました。そのとき弊社では採用例のなかったAWS CDKを使いました。そこで、今回はAWS CDKを紹介する記事を書いてみます。
本当は実際のインフラ構成やデプロイについて振り返りたかったのですが、AWS CDKの紹介だけで長くなってしまったので、まずはこの記事を出します。もうちょっと込み入った話は、後日投稿したいと思います。
ちなみに「AWS CDK」で検索すると似た記事が沢山出てきます。そのうちの一例としてご覧いただけると幸いです。
AWS CDKとは
プログラミング言語を使ってAWSのリソースを定義するフレームワークです。ちゃんと説明するのが意外と難しく、ボロが出そうなので詳細は省略します。
https://aws.amazon.com/jp/cdk/
私はTypeScriptを使っていますが、PythonやJava、C#などでも使えます。ただ、世の中のサンプルコードは9割くらいTypeScriptで、たまにPythonがあるといった印象です。特にWeb系の開発会社ならTypeScript(もしくはJavaScript)が無難かと思います。
ざっくりとしたCDKのコード例
コードを見るのがCDKについて分かりやすいので、いきなりソースコードを出します
VPCを作る(L2 Construct)
EC2やRDSを利用するためにまずはVPCを作りたい、みたいなことは日常生活でよくあると思います。VPCやサブネットを作りたいときは、CDKで
new vpc.Vpc(this, 'example-vpc1', { /* some parameters... */ });
と書くと構築できます。これだけで、VPCのみならず、サブネットやNATゲートウェイ、ルーティングテーブルなども含めてよしなに作ってくれます。
`cdk diff` コマンドで実際に追加・削除されるリソースを確認することができます。実際に確認してみると、
と、いろいろ作ってくれるのが分かります。この例ではパブリックサブネットとプライベートサブネットがあるデフォルトの構成になっていますが、パラメータを色々指定することで好きな構成にできます。このvpc.VpcはL2 Constructと言い、スタンダードなCDKの使い方です。
L1 Construct (VPC)
L2 Constructがあるということは、低レイヤーなL1 Constructもあります。L1はCFnの仕様から自動生成されているそうで、CFnと全く同じです。試しに使ってみると
new vpc.CfnVPC(this, 'example-vpc2', { /* some cfn paramaters */ });
となり、CloudFormationでVPCだけを作ったときと同じ挙動になります。L2 Constructと異なり、サブネットなどは作られません。
積極的にL1 Constructを使うことはありませんが、作りたいAWSリソースにL2 Constructが実装されてないときにはL1 Constructを使います。
L3 Construct (ECS Pattern)
L1とは反対に、さらに高レイヤーにしたL3 Constructもあります。典型的な構成をテンプレにしてくれたもので、例えばこんな風に使います。
import * as cdk from 'aws-cdk-lib'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecsp from 'aws-cdk-lib/aws-ecs-patterns'; import { Vpc } from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; export class ExampleEcsStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new Vpc(this, 'example-vpc', { natGateways: 1, }); new ecsp.ApplicationLoadBalancedFargateService(this, 'example-service', { vpc, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('httpd'), }, }); } }
コード全文でもこの行数です。ApplicationLoadBalancedFargateServiceという名前から分かるように、このConstructはALBとFargateの構成をまとめて作ってくれます。このコードだけで、Apache (httpd)のイメージがECS on Fargateで動くサービスが出来上がります。
`cdk diff`で作成されるリソースを確認するとこんな感じです。
VPCやサブネットの構成は下図のようになります(間違いがあったらすみません)
パラメータをほぼ省略して意図的に短くしたコードですが、10~20行のコードでこの構成が完成します。
ちなみに、暗黙的に生成されたALBを本番環境で使うのは抵抗があるので、ALBを自分で作りたくなります。そのときもApplicationLoadBalancedFargateServiceのpropsにALBを指定するだけで対応できます。そうすると、自分で作ったALBにターゲットグループの設定だけをしてくれます。
もっと言えばCDKの実装は読みやすいため、L3 Construct(やL2 Construct)でやりたいことができないとき、ライブラリの中を見て参考にすることもできます。[1]
CDKで嬉しいこと
上のサンプルコードだけでもCDKのパワフルさや魅力は伝わる気もしますが、改めてメリットを挙げておきます
ブラウザでポチポチ操作せず、コードでインフラ構築できる
AWS CDKというよりIaC (Infrastructure as Code)の利点ですが、個人的に大きい部分なので最初に挙げました。AWSマネジメントコンソールって表示が僅かにもたついて使いづらい気がするんですよね。VSCodeでCDKのコードを書いて、そのまま同じウィンドウでターミナルを開いて`cdk deploy`するだけでインフラを更新できるのはかなり快適です。
真面目な言い方をすれば、CDKで構築すると再現性や冪等性が高いのが利点です。1度手作業で作った環境と同じ構築をするのはかなり大変(時間や手間がかかるしミスも怖い)ですが、インフラをコードで管理することで再構築が簡単になります。
AWSマネジメントコンソールの昔のスクリーンショットを見てもUIが違っていて参考にならない、みたいなこともよくありますが、CDKならそういった問題はありません。
プログラミング言語なので、エディタの支援が手厚い
VSCodeを使っていると、単なるYAMLと比べて格段に開発がしやすいです。「このオプションには何を設定できるんだろう」というときCtrl+スペースで候補を表示してくれます。
また、最近ではGitHub Copilotの予測もとても強力です。うまくいくときはTabキーとEnterキーを適当に押すだけでそれっぽいコードを書いてくれます。「ドキュメントを見つけて、そのコードをコピペして…」でなく「まずはGitHub Copilotで適当なコードをかいて、そこからドキュメントを確認する」みたいな方法で、コーディングが体感かなり早くなりました(結局、ドキュメントは見ておきたいので、体感だけかもしれませんが…)
AWSマネジメントコンソールで操作に悩んだときにアシストしてくれる機能はおそらく無いですが、VSCodeでならTypeScriptもGitHub Copilotも手伝ってくれます。
CFnと比べて抽象的に、短く記述できる
上のサンプルコードで一目瞭然かと思います。CloudFormation (CFn)ではAWSのリソースやパラメータを全てYAMLで書かなくてはいけませんが、CDKではざっくりTypeScriptで書けばよいです。
CFnは必要な全てのAWSのリソースを自分で書かなくてはなりませんが、CDKはAWSで作りたいリソースのみを書けばよい、という感覚です。”作りたいリソース”に必要な別のリソースはいい感じに作ってくれます。
プログラミング言語なので、可読性が高い
抽象的に記述できるため、可読性も高いです。これも上のサンプルコードが特徴的だと思います。
まだ挙げてない例として、IAMやセキュリティグループの設定で便利です。例えば、とあるLambda関数がとあるS3バケットに書き込めるようにしたいとき、CFnではIAMポリシーやIAMロールを定義して…といろいろする必要がありますが、
hogeBucket.grantRead(fugaLambda);
とCDKでは簡潔に書けます。もちろんIAMロールやポリシーを自分で作ることもできますが、このようにCDKに任せるのがベストプラクティスだそうです。[2]
プログラミング言語なので、JSONやYAMLの辛さがない
上の「エディタの支援」や「可読性が高い」と似ていますが、JSONやYAMLではできない・不便なことが簡単にできるのが嬉しいです。
例えば、TypeScriptの作法で文字列操作できます。CDKでは変数を使って、EC2のインスタンス名を`ec2-${appEnv}-instance`
と設定できます。Fn::SubとかFn::Joinとかを使う必要はありません。
パラメータによる条件分岐もCFnのFn::Conditionでなく、TypeScriptではif文や三項演算子で書けます。さらには、YAMLではできないforEachやmapを使ったループ処理も可能です。
条件分岐やループを多用すると読みづらくなるので要注意ですが、適切なConstructやスタックへの分割と併用することで「何を構築したいのか」が分かりやすいコードになります。
CFnよりパラメータの管理がし易い
AWSマネジメントコンソールでCFnをデプロイするとき、ブラウザでパラメータを入力するのが結構面倒です。開発環境・ステージング環境・本番環境でそれぞれ違うパラメータがあると「”インフラパラメータ.xlsx”を作って設定値を管理するようにして、デプロイのときは手作業でコピペして……」とするのはミスも増えるし、あまりやりたくない作業です。
CDKでのパラメータはスタックに渡すpropsです。つまり変数として扱えます。もうちょっと動的に扱いたいときはcdk.jsonのcontext
にJSONを記述するのが定番です。もっと動的にしたいときは、非推奨ですが環境変数を使うケースもあります。
そもそもCDKをデプロイすると、内部的にはCFnのJSONを出力してそのスタックを作るのですが、CFnのパラメータ(Paramtersセクションに書くアレ)は使われていません。CFnを出力した時点で全てのパラメータが確定した状態になっています。そのためデプロイのときに入力するパラメータはありません。
その他
上で書ききれなかったり、まだ私が実感してないメリットは他にこんなのがあります
- LambdaやECSで、デプロイするコードを一緒に管理できる
- CDKのコードに対して、テストコードを書ける
- インフラ構築について、コードをもとにしてレビューできる
- 既存のCFnの資産があれば、インポートして包括することもできるらしい
CDKの短所
環境構築やコーディングのハードル
TypeScript(もしくはJavaScript)でCDKを使うためには、AWS CLIの導入とNode.jsの環境が少なくとも必要です。どちらも難しくはないですが、YAMLを書いてブラウザを開けば良いCFnと比べるとハードルは高いです。また、サクラエディタや秀丸を信仰している方もメリットを享受しにくいかもしれません。これについてはしょうがない[3]です。
ただ、基本的なCDKで使うのはJSONに毛が生えた程度のコードなので、コーディングのハードルは低いと思います。
中身はCloudFormationなので、その知識が必要
AWS CDKのデプロイは、CloudFormationのスタックを作成して反映する仕組みになっています。そのため、CFnのエラーを見て対応・修正することがあります。例えば、
- VPCやElastic IPの個数が既に上限に達していて、新しいリソースを作れなかった
- RDSでAurora 3.03のインスタンスをt2.mediumで作ろうとしたが、インスタンスタイプとDBエンジンのバージョンが対応していなかった
などのエラーがありました。
さらに、CDKのスタックはそのままCFnのスタックとして作成されます。コードを書いているとついスタックを分割したくなりますが、依存関係によっては意外なところで循環参照のエラーが発生します。デプロイ済みのスタックで発生すると厄介です。
また、cdk diff
もCFnの差分をベースにしています。そのため「CDKを使うからCFnは知らなくて良い」なんてことはなく、むしろ必要です。
意図しないリソースの削除や上書きに注意が必要
これはCDKに限らずIaCでの注意事項ですが、コードの差分や手動作業の影響で意図しない変更が発生してしまう危険があります。再現は確認していませんが、思い当たる例としては
- EC2インスタンスに設定していたUserDataに差分が生じたため、EC2インスタンスが再作成され、旧インスタンスが削除される
- CDKでデプロイしたLambda関数を、AWSマネジメントコンソールから手動で変更。その後、CDKで別リソースの変更をデプロイしたときにLamba関数が再デプロイされ、手動での変更が消える
などの恐れがあります。対策としては、
- デプロイ前に`cdk diff`でchange setを確認し、削除されるリソースを確認する
- CDKで作成したスタックに、ドリフトが発生していないかを確認する
- 消えてほしくないリソースに削除ポリシーを設定する
- IaCで管理しているリソースの手動変更は極力避ける
- そもそも削除されても問題ないor被害が少ない構成にする
- 例1)EC2インスタンスでなく、コンテナ化してFargateで運用する
- 例2)スクリプトや自動構成ツールで、環境をすぐ作成できるようにする
などでしょうか。
特に本番環境のサーバをEC2インスタンスで作り、さらにSSHでログインして構築する場合は注意が必要です。
あとがき
あまり上手くまとまらなかったですが、色々思いつく範囲で書いてみました。
残念ながら銀の弾丸にではありませんが、AWS CDKは特に新規構築やリプレースの際には強力なツールだと思います。
[1] 例えば、ApplicationLoadBalancedFargateService (GitHub)や周辺のコードを見ると何をしているのか何となく分かります
[2] AWS CDKでクラウドアプリケーションを開発するためのベストプラクティス – AWSサポート
[3] 「インフラエンジニアには難しい」「手でやったほうが楽」も解消 これからCDKを使う人向けの4つのナレッジ – AWS CDK Conference Japan