見出し画像

CloudFormationでインフラストラクチャをコード化する時に考えるべき設計とは

CloudFormationはAWSクラウドの設定をコード化して自動的に適用するための仕組み/サービスです。

CloudFormationは、AWS上のEC2、RDSなどのサービスをプログラムのように宣言的にリソースとして記述することができます。インフラをソフトウェアのように扱える仕組みとしてCloudFormationはとても素晴らしいのですが、これを上手く扱うにはいくつか気を付けるべきポイントがあります。
CloudFormationを使ってコードから同じ環境を何回も作成/削除したい場合や継続的に変更をしたい場合、複数人で保守をつづけたい場合に気を付けたいポイントについて話してみたいと思います。(基本的なCloudFormationの使い方やAWSのクラウドについては詳しく説明しません)

リソースの依存と作成の順番

CloudFormationは、AWS上のさまざまなサービスをリソースという単位で扱います。例えばEC2インスタンス、RDSインスタンス、セキュリティグループ、VPCなどはCloudFormationではリソースとして扱われます。

これはAWSコンソールから作成する場合も同じなのですが、あるリソースを作成するためにはそのリソースに関連付けるリソースを先に作成しておく必要がある場合があります。どいうことかというと、例えばセキュリティグループを作成するためには、そのセキュリティグループを使うVPCを先に作っておく必要があります。これをCloudFromationではセキュリティグループのリソースはVPCのリソースに依存していると言います。下の図は依存関係を矢印で表してみました。

CloudFormationでは、複数のリソースをスタックというくくりでまとめて定義して、作成/更新/削除ができますが、このときスタックの中でのおおよその依存関係は自動的にCloudFormationが解決してくれます。(ときどき、依存関係を自動的に解決してくれない場合がありますがその場合はDependsOn属性をリソースの宣言時に指定して依存関係を明示的にCloudFormationに指示することができます)

リソースの依存の循環を排除せよ

しかし、DependsOn属性を使ってリソースの依存関係を上手く処理できない場合があります。それはスタックの中で、下の図の様にリソース同士が循環して依存している場合です。下記の図ではセキュリティグループAとセキュリティグループBが循環しています。CloudFormationではこのような循環したリソースがあるスタックを新規作成することはできません。

セキュリティグループでは、アクセス許可を記述するために、他のセキュリティグループを参照することができます。これはリソース間のネットワークアクセスのトポロジを定義するのに非常に強力で有用ですが、トポロジを定義するという性質上どうしても循環を作り込んでしまいます。そういった場合は、下の図のようにセキュリティグループBをBとB'にわけて定義して循環を排除することが可能です。

セキュリティグループを分割する場合に注意すべきこととしては、分割は必要最小限にするべきということです。依存関係の把握が難しくなるだけで無く、EC2インスタンスなどのリソース側では一度に指定できるセキュリティグループはデフォルトでは5つまでとなっています(セキュリティグループの個数にも制限があります)。申請で制限を緩和できるかもしれませんが、依存の循環を防止するための過度なリソースの分割はしない方がいいでしょう。

スタックの更新と依存の危ない関係

実は、AWSコンソールから手動でリソースを変更したり、スタックの更新を使用すると簡単に循環した依存を作り込む事が可能です。

スタックを一度作成したあとは手動で変更していく運用では問題になりませんが、同じ環境をCloudFormationのコードから自動的に作成できなくなります。複数人でCloudFormationを開発・保守する場合、手動で変更している箇所についてどのように変更するのか手順書が必要になります。またスタックをつかって変更をした場合でも、一から環境を作るには二つのバージョンのテンプレートを使ってスタックを作成する必要が出てくるでしょう。これらの手順の維持管理をするコストがかかりますし、せっかくCloudFormartionを使っているのに、自動化にはほど遠い状態になってしまいます。

スタックの分割はライフサイクル、所有権、そして依存を意識して

小規模システムであれば、CloudFormationのスタックは一つですむかもしれません。しかし中規模以上のシステムまたは複数人の開発であれば、スタックを分割して利用することをおすすめします。ビジネスでソフトウェアの開発に従事したことがあるエンジニアならピンとくると思いますがスタックを一つにするということは、アプリケーションコードを1ファイルにして開発をするような物です。

では、どのようにスタックを分割するべきなのでしょうか。下記のAWS CloudFormation のベストプラクティスには、ライフサイクルと所有権でスタックを分割する方法が書かれています。

ここでライフサイクルというのは、スタックの作成、削除、更新がどのタイミングで必要になってくるかということです。ライフサイクルが異なるリソースを同じスタックに含めてしまったり、作成、削除の頻度の高いスタックをずっと利用し続けるスタックから依存させてしまうと、そのスタックを削除して作成しなおすことができなくなります。

次に所有権ですが、実際にそのリソースを作成・削除・変更する開発者/チームが複数いるのであれば、その役割に応じてスタックを編集できるように分割するのがよいでしょう。このようにすることで、バージョン管理での差分の確認やレビュー、適用時の影響範囲のチェックが容易になり保守のコストが下がります。

ライフサイクルや所有権を適切に設計するための手法としては、エンタープライズアプリケーションアーキテクチャやドメイン駆動設計などで出てくるレイヤードアーキテクチャを採用するのが一般的なようです。

具体的な例で、AWSのリソースをレイヤードアーキテクチャで整理してスタックを分割してみましょう。例えば次のようなネットワーク構成図の場合について考えてみます。

これを実施のリソースにして依存関係を示すと下記のようなになります。AWSの公式のアイコンが存在しないものは○でリソースを表しています。

これをレイヤードアーキテクチャで分割してみましょう。今回は3つのレイヤーに分割していますが、必要に応じてレイヤーをさらに分割することができます。レイヤーを分割するときに気をつけることは、依存関係に循環がないこと、もっともライフサイクルが長くインフラの基盤となるレイヤー1、ネットワークのトポロジを制御するレイヤー2、その上で扱うサービスを構築するレイヤー3となっています。

ここからさらにスタックを分割していきます。今回は開発時のライフサイクルを考えて、一時的にDBだけ削除して作り直す。ECインスタンスだけ削除して作り直すようなことができるようにレイヤー3のスタックを分割してみました。本番であれば、DBは永続化データを扱うので最もライフサイクルが長いでしょう。

逆にアンチパターンの例も示してみたいと思います。例えばレイヤー2とレイヤー3とレイヤー2にまとめて、セキュリティグループも含めたスタックを作る誘惑にかられるかもしれません。綺麗にスタックを分割できたように思えるかもしれませんが、このように分割すると依存によりレイヤー2のスタック間が非常に密結合になります。例えば、Webのインスタンスを削除したくなったら、DBのスタックを消す必要がでてきます。通常、DBなどの永続層はEC2インスタンスに比べてライフサイクルが長いので後々問題になってくるでしょう。

スタックの作成と削除を習慣化する

さてここまで、CloudFormationの設計をする上で考えるべき内容について依存関係、ライフサイクル、所有権、レイヤードアーキテクチャなどを扱ってきました。これらの設計を具体的にコード化し維持するにはどうすればよいのでしょうか。一つには設計者がこういった設計意図をメンバーと共有することになるでしょう。また保守による度重なる変更によって、いつの間にか循環参照が作られて、一からスタックを作ることができないなどということが起こらないようにするためには、継続的にスタックのインテグレーションを行う必要ででてきます。そのためには、これらのスタックを自動的に作成・削除できる仕組みが必要になるでしょう。

継続的にインテグレーションを行うには、ネステッドスタックなど、CloudFormationの複数スタックをまとめる機能などが役に立ちます。

また別の機会にCloudFormationの継続的インテグレーションについてもノートにしてみたいと思います。

ECプラットフォーム Magentoのアプリケーションの構築を行う、アプリケーション、構成、インフラのエンジニア 趣味でCloudFormation用のツールを作成している https://github.com/Amakata/cfndk