データ活用基盤を作ってみたその１２(Terraformのtips)

# はじめに
こんにちは、NTTデータ先端技術の[白木](@shiraki_ils)です。
本記事はデータ活用基盤を作ってみた連載記事の１２(TerraformのTips)です。
本シリーズの取り組みの内容については[その1(構成シナリオ)](https://qiita.com/kamein/private/81df29ef9d32bf0a0810)をご覧ください。
前の記事は、[データ活用基盤を作ってみたその１１（構築・データ活用-Athena）](https://qiita.com/kamein/items/c5c3d6d2c343df4b5c7d)からご覧ください。
今回は、TerraformでAWSの構築を行った際のtipsなどについて記載させていただきます。

# 目次
* 前段
* Tips
* 詰まった点
* 最後に

# 前段
本PJでは、AWS上に下記構成でTerraformを用いてインフラの構築を行いました。
メンバーは7名で実施しましたが、ほぼ全員がTerrafom未経験者だったので、手探りで進めた中でのtipsや詰まった点をまとめました。
構成はこのようになっております。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3361542/670179d7-2b10-60e9-4939-52fa7be4e397.png)

* 環境
    * Terraform: v1.5.2
    * aws provider: v5.5.0
* TerraformによるAWSのインフラ構築
* バックエンドはリモートステート先としてS3バケット、排他制御としてDynamoDBを東京リージョンに作成済み
    * Remote Backendや環境構築については、前章の～～で記載。

# tips
以下に本PJを通してTerraformを使ってみた、tipsについてご紹介します。
## ディレクトリ構成について
[こちら](https://dev.classmethod.jp/articles/terraform-bset-practice-jp/)の記事を参考に、実装当初のディレクトリ構成案は下記としておりました。
最上位はサービスの構成が記載されているmodulesと、各環境の構成が記載されているenvironmentsに分けて管理する一般的な構成です。

```bash
-- TechLearning/
   -- environments/
      -- prd/
         -- main.tf
         -- backend.tf
         -- variables.tf
         -- provider.tf
         -- README.md
         -- outputs.tf
   -- modules/
      -- iam-admin/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- README.md
      -- iam-analytics/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- README.md
      -- ....
```
ですが、実装を進めていく中で拡張性を意識した上記構成案は、Terraform初学者にとっては難易度が高いものでした。また、工数もかけられない状態だったため、今回は拡張性を犠牲にした下記構成といたしました。

```bash
-- TechLearning/
    -- backend.tf
    -- iam-admin.tf
    -- iam-analyst.tf
    -- iam-securityservice.tf
    -- iam-service-role.tf
    -- jobs.tf
    -- provider.tf
    -- README.md
    -- security.tf
    -- storage.tf
```
再利用性・拡張性を意識せず実装を行ったので、速度間をもって開発することができました。
ただ、本PJに関わったことが無い方がコードを読むと理解しずらい構成かつコードになってしまったのは反省です。もし、次回があれば機能追加かつ、コードのリファクタリングと当初のディレクトリ構成にできればと思っています。

## plan実行後のapplyがこける
terraform planを実行し、planの実行結果がsuccessとなってもapplyがsuccessにならないケースがよくありました。そもそも、terraform planとは、
> terraform を実行した際に、クラウド上に実際に構築されるリソースを模擬することができるコマンドのことで、.tf ファイルに記載された情報を元に、どのようなリソースが 作成/修正/削除 されるかを参照することが可能になります。
https://blog.mmmcorp.co.jp/2022/10/31/terraform-apply-safely/

とのことです。そのため、terraform planが失敗すればapplyも失敗するのでapply実行前に確認するのが有効となります。ただ、これが罠でした。planが成功すれば、applyは必ず成功するはずだと思っていたのですが、applyで失敗することが多々ありました。
それもそのはず、本来terraform planは変更の予測を表示するものであり、その変更が正常に行われるかは確約しないのです。

>terraform planはあくまで記述したコードをもとにどのような変更が加えられようとしているかの予測を表示するものであり、最終的にその変更が正常に行われるかを確約しません。
The plan command alone does not actually carry out the proposed changes.
https://www.terraform.io/cli/commands/plan

これを誤認識していたため、Terraformの文法的には正しいのになんでエラーになるのだと最初はエラーの解決に時間を要してしまっていました。初学者だと陥りがちな罠になるかもですね。。

今回このエラーの解決には、自力で確認を行っておりましたが、tflintを使えばある程度「plan実行後のapplyが失敗する」という状況を解消できるようです。
次回はtflintも使ってみたいなと思います。

## plan、apply実行できない
シンプルにTerrafom plan/applyの実行がエラーになる状態です。
エラー内容には、state lockと出力されています。
```
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: resource temporarily unavailable
│ Lock Info:
│   ID: 
│   ...
```
どうやらstateファイルへアクセスする際にロックされ、エラーになるようです。
原因ですが、コマンド実行直前に実行していたapplyコマンドを途中終了させたことだと考えられます。（コードを確認していると別の修正をしたく、実行を停止してしまいました。）
applyやplan実行時は、stateをロックし実行終了時にロックを解除しているのですが、途中中断したことにより、ロックした状態になっていたようです。

解決方法ですが、シンプルにロックを解除してやればコマンドの実行ができるようになります。
{LOCK_ID}はエラーメッセージに出てきたID値です。
```bash
$ terraform force-unlock {LOCK_ID}
```
こちらのコマンドを実行すれば無事ロックが解除できコマンド実行ができるようになりました。
何気なく途中中断してしましましたがやむを得ない場合以外は、途中中断はしない方がよさそうですね。

## Terraform CLoudについて
前章の～～でも記載しましたが、今回の構成としてTerrafom Cloudは利用せずRemote BackendでTerrafomのstate管理を行いました。Terrafom Cloudを利用しなかった訳ですが、Saasサービスということもあり社内承認に時間がとられてしまうこと、そもそもTerrafom初学者しかいない中で新たなサービスを使用するのは難易度が高いと感じたためです。
次回のためにも、事前勉強ということでTerrafom Cloudを利用すると何がいいのか、何ができるのかなどTerrafom Cloudについてご紹介できればと思います。

### Terraform Cloudとは？
Terraform Cloudは、Terraformをチームで共同利用するために、アクセスコントロールやモジュール共有のためのプライベートリポジトリを提供するサービスです。
* 機能
    * Stateファイルの管理
    * デプロイパイプライン
    * ガバナンス機能
    * アクセスコントロール・ポリシー設定・監査ログ等
    * Private Registry
    * moduleやproviderを組織内に公開

参考）https://dev.classmethod.jp/articles/terraform_tfstate_management_tfc/

### Terraform Cloudのいいところ
上記にも記載しましたが、まずstate管理ができる点です。今回のPJではS3とDynamoDBをもちいてStateの管理を行いましたが、Terrafom Cloudであれば別途クラウドリソースを用意することなく、管理をすることができます。

次にStateファイルの変更履歴をGUI上で確認ができる点です。イメージでいうとGitのCommit履歴のような形で誰がいつどのような変更を行ったかが簡単に確認することができます。

また、デプロイパイプラインを作成することができる点もいいところだと思います。
GithubでTerraformのコードを管理している場合、mainブランチなどにマージしたことをイベントにTerrafom Cloud上でterrafom plan、applyを実行してくれます。Github Actionsなどでymalを書かなくともTerraform Cloud上でブランチの指定などを行えば、自動で行ってくれるようです。
参考）https://qiita.com/boccham/items/190f04bfbc9ffc0b5baf

これ以外にもまだいい点はたくさんあると思いますが、代表的なものをご紹介しました。
ぜひ次回はTerraform Cloudを用いてTerraformの管理を行ってみたいですね。

## CloudFormationとの比較

チーム内に、過去の案件にてCloudFormationを利用したことのあるメンバーがいましたので、TerraformとCloudFormationの違いを考えてみました。

### マルチクラウド対応

Terraformのかなり大きなメリットはマルチクラウド対応である点だと思います。
CloudFormationはAWSサービスですので、作成した資材はAWS専用になってしまいます。
一方のTerraformは、AWS, Azure, GCPの三大パブクラからOCIまで、Providerを切り替えることで様々なプラットフォームに対応可能です。

参考：TerraformのProvider一覧
https://registry.terraform.io/browse/providers

ただし、TerraformでAWS向けに作成したTerraformのコード（=providerをAWSにしたコード）は、AWS向けの資材となり、単純にこれを別のクラウドサービスで転用することはできません。
実際にコードを書けば一目瞭然なのですが、例えばAWS向けでEC2インスタンスを作る場合はresource名が「aws_instance」、OCI向けのComputeインスタンスを作る場合はresource名が「oci_core_instance」となり、それぞれ必要なパラメータも異なります。
そのため、マルチクラウドで資材を再利用することは基本的に難しく、Terraformの文法やコマンド・各種Tips等の、ナレッジを共有できる点がメリットと考える方がよさそうです。

### 利用するまでのステップ

CloudFormationはAWSサービスであり、マネジメントコンソールにアクセスするだけで簡単に利用が可能です。
また、CloudFormation デザイナー等の補助サービスも日々アップデートされているため、IaCの入門としては比較的ハードルが低いように思えます。
一方今回Terraformを利用開始するにあたっては、ローカル環境のセットアップやBackend用S3バケットの構築など、いくつかの手順が必要となりました。
※詳細は本連載記事の構築編をご覧ください。
そのため、初学者が利用開始するハードルという観点でいえば、現時点ではCloudFormationのほうが優勢かなという印象です。
ただし、今回はPJの都合上Terraform Cloudを利用していません。SaaSであるTerraform Cloudを利用した場合には上記とは異なる評価になる可能性もあります。


### 複数人利用の場合の注意

CloudFormationにはスタックという概念があり、基本的には一つ（ネスト機能やモジュール機能を使えば複数も可能）のテンプレートから作成されたリソースをスタック単位で管理することができます。
チームでCloudFormationによる構築をしている場合、マネジメントコンソール上でスタックを管理できるのはかなり小回りが利く印象です。
これは特に、テンプレートやスタックに対して担当者が分かれているケースで効果を発揮すると思います。
例えば、NWの担当者がNW関連のテンプレートを修正した際に、NW関連のスタックだけを更新する、というような操作が可能で、その際にその担当者はほかのテンプレートやスタックを意識せずに作業が可能です。（もちろん、他のスタックとの依存関係がないことが前提にはなります。）
これに対してTerraformでは、同じbackendを利用しているtfファイルがすべて含まれた状態で管理されているため、どれか一つのtfファイルだけを更新したい場合でも、すべてのtfファイルの最新版を手元に用意して作業する必要があります。
つまり、いくつかの担当者で分業している場合に、他の担当者の最新版の資材も必要となるため、更新時のデグレ防止が重要になります。
この辺りは普段コードを管理している人であれば当たり前の部分かもしれませんが、AWSマネコンでの手動構築をメインとしているインフラ系の方は、作業分担やコード管理の仕組みについて注意が必要かと思います。
※上記の回避のために、backend自体を担当者ごとに分けてしまうことも机上検討しましたが、そういった方式で開発している例が見つからなかったため採用しませんでした。もし知見がある方いればコメントいただければと思います。

:::note info
なお、上記の点は、さらに開発が高度化してgit及びCI/CDが実現されれば、CloudFormationであってもTerraformであっても差異は減ってくる部分かと思います。
今回はそういった開発環境の準備がありませんでしたので、その前提で記載しています。
:::

### 文法について

CloudFormationがjson/yamlで記載するのに対して、TerraformはHCL（Hashicorp Configuration Language）というJsonに近い文法の独自言語で記載します。
どちらのほうが難しい/読みやすいなどの評価は完全に個人差の部分かなと思いますので優劣をつけるのは避けます。
PJメンバーのスキルセットとしてどちらか一方の言語に強く馴染みがあるのであれば評価の軸にするのは良いと思いますが、そうでないのであれば、初学者向けの学習ハードルとして大きな差はないのではという印象です。

# 最後に
今回がシリーズ最後の記事になります。最後までご覧くださりありがとうございました！
初心者がTerraformでインフラ構築を行った際のtipsについてまとめました。
少しでも、本記事が参考になれば幸いです。
間違い等ございましたら、気軽にコメントください～～