ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【AWS】AWSのIaCはTerraformとCloudFormationのどちらがよいか

 昨年は忙しくて全く更新できませんでしたが、久々の更新です。

 今回はAWSの開発においてTerraformとCloudFormationの両方を使った経験から、どちらを選択した方が良いかをつらつらと書いていこうと思います。

 あくまで「個人的な意見」なので、そこは注意をお願いします。


 では、始めます。


目次

個人的な結論

 早速の結論ですが、個人的には「Terraformを選択した方が良い」と思っています。

 その理由は以下の通りです。

  • 1:Terraformの方がCloudFormationより制限が緩いので柔軟性がある
  • 2:Terraformの方がCloudFormationよりデプロイ時のシェルが簡単
  • 3:Terraformでは出来るがCloudFormationでは出来ない部分がある

 ではそれぞれの内容を詳しく掘り下げていきます。

1:Terraformの方がCloudFormationより制限が緩いので柔軟性がある

 Terraformでは一度のコマンドでデプロイを行う範囲としてルートディレクトリを作成し、その中にTerraformファイルを作成していきます。
 どのようにファイル分けをしても良く、デプロイ時は基本的にTerraform側でデプロイ順番を考慮してデプロイしてくれます。
 ファイル分けも柔軟かつAWSリソースの依存関係もルートディレクトリ単位で意識すればよい程度です。
 また引数の数やファイルサイズについても特に制限はなく、for文のような繰り返しや数が変動するような場合も一応は対応できる書き方があります。

 しかしCloudFormationの方では制限も多く、厳密に依存関係を意識する必要が出てきます。

 CloudFormationではファイル単位でデプロイするのですが、そのファイルのサイズや引数、Outputの数、スタック数(1つのAWSアカウントで実行できるCloudFormationの数)などに制限があります。
 なので大規模かつ複雑なシステムをAWSに構築する場合は必ずこの制限を意識しなければならなくなります。
 ファイルサイズ制限や引数制限により多くのAWSリソースの作成を1つのファイルに集約できないためファイル分割を強制されますし、ファイルを分割しすぎてもスタック数の制限に引っかからないように調節する必要も出てきます。
 また引数となるファイルがJSON形式なため、コメントを引数ファイル自体に書き込めないという問題もあります。

 そしてファイルを分割するということはそれだけAWSリソースの依存関係を意識する必要も出てきます。
「頻繁に変更されるAWSリソースは個別のファイルに分けておいた方が良いが、あまり変わらないAWSリソースはできるだけ1つのファイルにまとめたい。しかしそうするとファイルサイズの制限や引数制限、依存関係を気にしないといけない」というトレードオフの関係を常に頭の中に入れながらCloudFormationの開発を進めないといけなくなります
 この点はCloudFormationの明確な弱点だと思っています。

 ちなみに以下がAWS公式のCloudFormationの制限一覧となります。

 またCloudFormationにはif文に相当する書き方はできますがfor文に相当する書き方が存在しないため、個数が変動するような場合については都度都度個別のCloudFormationのファイルを作成する必要があります。

 このようにCloudFormationは制限が多くなっていますが、Terraformには基本的にファイル制限や引数制限などがなくFor文などの機能もあるためTerraformの方が優位だと思います。

2:Terraformの方がCloudFormationよりデプロイ時のシェルが簡単

 次にデプロイ時のコマンドについてです。

 1で書いたようにTerraformではルートディレクトリ単位でデプロイをします。
 Terraformはルートディレクトリ内に移動して以下のコマンドを実行するだけでデプロイもアップデートも可能です。

terraform apply <option>

 どのルートディレクトリでもこのコマンドを実行すればデプロイが可能なので、デプロイ自動化のシェルを書く場合はルートディレクトリの順番だけを意識したシェルを書けば良いので比較的簡単に書けます。

 一方CloudFormationはファイル単位でデプロイを行うため、少なくともデプロイするファイルの順番は意識する必要があります。
 加えてCloudFormationはテンプレートとして別のCloudFormationファイルを読み込む(Terraformでのmodule呼び出しのようなものの)場合、一度package化するためのコマンドを実行する必要もあります。

 一般的なCloudFormationのデプロイの流れとしては以下のようになります。

# package化
aws cloudformation package <option>

# package化したファイルをデプロイ
aws cloudformation deploy <option>

 通常であればディレクトリよりもファイルの方が細かく分かれて数が多くなるため、CloudFormationのデプロイ自動化のシェルを書く場合はファイルのデプロイ順番をTerraformの時よりも厳密に管理して書く必要があります。それにpackage化するコマンド自体が失敗する可能性もあるので、そのエラー処理も必要になってきます。

 また差分を取得する際も同様です。

 Terraformでは以下のコマンドだけで差分を取得できます。

terraform plan

 しかしCloudFormationでは以下のようにpackage化した後にChangeSetというものを作成し、その後に差分を取得するコマンドを実行する必要があります。

# package化
aws cloudformation package <option>

# package化したファイルのChangeSetを作成
aws cloudformation deploy --no-execute-changeset <option>

# 差分を取得
aws cloudformation describe-change-set <option>

 こちらも差分取得の前に2つのコマンドが実行されているため、それぞれにおいてエラー処理を入れる必要も出てきます。

 このようにデプロイや差分を取得するにもCloudFormationの方がコマンドの数が多くなりますし、その分デプロイ自動化のシェルを書く場合にはエラー処理などを書かなくてはならなくなるため、Terraformの方がデプロイ時のシェル作成はかなり楽になります。

3:Terraformでは出来るがCloudFormationでは出来ない部分がある

 ここが一番致命的な部分になるかと思います。
 いくつかあるのでそれを書いていきます。

CloudFormationはVPC Endpoint(Interface)のIPアドレスが固定できない

 例えばVPC Endpoint(Interface)のS3を「IPアドレスを固定して」作成したい場合があったとします。

 その場合、Terraformであれば以下のように書けばTerraformの記述方式で作成できます。

resource "aws_vpc_endpoint" "s3_endpoint_01" {
  service_name        = "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type   = "Interface"

  # --- 省略 ---
  subnet_configuration {
    ipv4      = "10.0.16.20"
    subnet_id = aws_subnet.subnet_01.id
  }
  subnet_configuration {
    ipv4      = "10.0.32.20"
    subnet_id = aws_subnet.subnet_02.id
  }
  # --- 省略 ---
}

 しかしCloudFormationの場合は以下のVPC Endpointを作成する際のCloudFormationの設定値にあるようにIPアドレスを指定する項目がありません。

 なのでこの場合はCloudFormationのみで完結できなくなり、この部分だけTerraformを使用するかAWS CLIを使ってコマンドだけで無理やり作成させるようにするかなどの回避策が必要となります。

CloudFormationは現在のAWSリソース作成状態との差分を取得するのが面倒

 Terraformではplanのコマンド自体が「現在のAWSリソース作成状態とIaCコードの差分」を取得してくれるため、AWSマネジメントコンソール上で手動で変更があったとしてもちゃんと「現状のAWS」との差分を取ってくれます。

 しかしCloudFormationでは2でやったChangeSetとの比較では「過去に実行した内容との差分」となるため、AWSマネジメントコンソール上で手動で変更があった場合と乖離が発生します。
 なのでCloudFormationでは以下のように「過去に実行した内容と現状のAWSとの差分」であるドリフトを取得する必要があります。

# ドリフト検出開始
aws cloudformation detect-stack-drift <option>

# ドリフト結果確認
aws cloudformation describe-stack-resource-drifts <option>

 ドリフトで過去に実行した内容と現状のAWSで差分がないかを確認し、その後に変更したCloudFormationコードとの差分を確認する流れになるため、かなり手間がかかります。

 Terraformであればplanコマンドだけで済むことが、CloudFormationでは現状との差分とコードの差分の両方を取得して確認する必要が出てきます。

CloudFormationは作成済みのAWSリソース情報を取得できない

 Terraformであれば既に作成されているAWSリソースについてdataを使って検索しそのIDなどを取得することが可能ですが、CloudFormationでは既存のAWSリソースについて検索して情報を取得することができません。

 CloudFormationにはTerraformでいうところのOutput機能しか存在しないため、Outputされていない情報については一切CloudFormation側で取得することができなくなっています。そのため、先に例として挙げたVPC EndpointでのIPアドレス固定などをする場合は、TerraformやAWS CLIなどで作成した時に必要な情報をParameterStoreなどに保存しておいて、その値をCloudFormationで取得して使用するという形にする必要があります。

 これらのように、Terraformに出来ることがCloudFormationでは出来ないということが色々とあります。


 以上がTerraformとCloudFormationを両方触ってみた自分なりの見解です。


 色々と書いてきましたが、冒頭に書いた通りAWSのIaCはTerraformを選択した方が良い」というのが個人的な結論です。

 もちろんCloudFormationはAWS公式ツールですしデプロイに失敗した時にはロールバックが実行されて自動的に元に戻るなど利点もありますが、実際に両方開発を経験した自分としては開発の負担が増える(意識したり考えることが多くなる)のと機能的に出来ない部分があるのが致命的な弱点だと思っています。

 小規模であまりトリッキーなことをしなくて変更するのが変数の値ぐらいというものであればCloudFormationを採用しても良い気はします。が、大規模だったりVPC EndpointのIPアドレスを固定する必要があるようなトリッキーな構成にする場合はCloudFormationを選択するとかなりつらくなるので選択しない方が良いと思います。


参考資料