Terraformのデバッグをして得たノウハウを共有します。

自分がTerraformをデバッグしている時に感じたコツを残しておきます。

# Terraform適用までの流れ

[Terraform for さくらのクラウド入門チュートリアル](https://qiita.com/zembutsu/items/fb0819e38b4fcda49299)
に絵があったので[こちら](https://camo.qiitausercontent.com/e20a14b2ee9bfa37beb7554468ee0d1990c0051f/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f31333030322f62623630343865372d663236662d616365662d373138612d6333656437323464633438662e706e67)

# 考え方

Terraformでエラーが出た場合原因として大まかに7パターン(実質6パターン)が考えられます。上から順に遭遇する確率は大きいです。
特に4以降に遭遇することはあまりないでしょう。

1. Terraformの実行環境の問題
2. Terraformのコードの問題
3. Terraformでプロビジョニングする対象(AWS側)の問題
4. Terraform自体の問題
5. Terraformが内部で使用するAWS SDK Goの問題
6. AWSのAPIの問題->わからなかったらAWSに問い合わせ
7. Go言語の問題(ここまでくることはほぼ0なので対象外)

## 切り分け方

### Terraformの実行環境の問題

terraform -vでバージョンが出れば実行環境としては問題ないです。
これが出ないときは環境構築やterraformのバイナリへのパスが通っていない問題が考えられるので確認します。

### Terraformのコードの問題

1. terraform validateコマンドで構文チェック
2. terraform plan
を実行すると大体解決します。
Terraform自体のバリデーションが甘いこともあるのでこれで問題が解決しないこともあるのですがplanまでエラーなく通ればほぼ問題ないです

なおTerraformの書き方については[Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)のドキュメントに書き方が載っていることが多いです。

### Terraformでプロビジョニングする対象(AWS側)の問題

おそらく、これが遭遇するエラーで一番多いです。
Terraformの定義ファイルは正しいのにエラーとなる場合はほぼ間違いなくAWS側の制約が原因でエラーになっています。
例えば特定のオプションの組み合わせはAWS側として許されていない等のエラーです。
この場合はまずエラーメッセージを読みます。
このエラーメッセージは英語で出るのですが、このメッセージを読まないと原因に到達することはほぼ無理です。
なお、このメッセージが出る段階ではAWSのAPIをTerraform内部で実行した結果が出ていることが多いので[AWSのAPIドキュメント](https://docs.aws.amazon.com/index.html)を確認したり、AWSのコンソールから同じ操作をしたときにエラーとなるか確認すると原因が判明することもあります。

詳細ログを見たい場合は[Debugging Terraform](https://www.terraform.io/docs/internals/debugging.html)にあるように
TF_LOGを環境変数に追加してあげるとログレベルを変えられます。

### Terraform自体の問題

ここからはTerraform内部の話になるのであんまりググっても情報が出てこないことが多いです。
いわゆるTerraform側のバグの可能性です。
こちらに関連するのは主に以下の3つのリポジトリです。
それぞれ

1. [provider](https://github.com/hashicorp/terraform-provider-aws) =>AWS等対象リソースのプロビジョニングの処理が書かれている、だいたいこちらを見ると各リソースをどのように作成/定義しているのかが記載されている
2. [Terraform本体](https://github.com/hashicorp/terraform)=>Terraform共通の処理planとかapplyとかの処理が書かれているが本体が原因となっている場合はあまりなく優先度低め
3. [plugin sdk](https://github.com/hashicorp/terraform-plugin-sdk)=>provider側のコードを読んでいるとなんでこの処理で色々な操作ができるのだろうと思うことがあります。この秘密はこちらで定義されていることが多いです。こちらもほとんど読む必要はないです。

特にprovider側のISSUEやPull Requestに同じような問題がないかうまいキーワードを考えて検索します。だいたい捜査対象のリソースで絞ると問題一覧が出てくるのでここにもなければ新規の問題の可能性が高いです。

### Terraformが内部で使用するAWS SDK Goの問題

*AWSを例にしています。

Terraform provider awsでは[aws-sdk-go](https://github.com/aws/aws-sdk-go)を使用しています。2022/12/15の時点ではV1使っています。こちらの問題の可能性もあります。

#### 調べ方

[Terraform provider aws](https://github.com/hashicorp/terraform-provider-aws)側でどのようにリソースを作成しているのか調べます。
まず当該リソースについて記載されたコードを探します。
コードの命名規則は
/internal/service/<調べたいサービス名>/<調べたい定義名>.go
のようになっているので見つけるのはそんなに難しくないです。

例えばAuroraのインスタンスの場合
<https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/rds/cluster_instance.go>
注目は
resourceClusterInstanceCreate
のようにCreateと記載があるメソッドです、これがリソースの作成処理が書かれています。
terraform-provider-aws各リソース処理のコードは基本的に

- 当該リソースの定義(ResourceClusterInstance:Schemaの箇所)
- CRUD
の構成になっています。
なのでリソースの定義情報を確認したい場合はSchemaの箇所を確認する、作成処理を確認したい場合はCreateとついたメソッドを確認します。

その形でみていくと
<https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/rds/cluster_instance.go#L300>
に

```
  conn.CreateDBInstanceWithContext(ctx, input)
```

のようにconn.<なんかのメソッド>
の処理が出てきます。
ここがAWS SDK経由でAWSのAPIを呼び出している箇所です。
この場合はCreateDBInstanceというSDK側のコードを呼び出しています。
実際aws-sdk-go側に
<https://raw.githubusercontent.com/aws/aws-sdk-go/main/service/rds/api.go>

```
// CreateDBInstanceWithContext is the same as CreateDBInstance with the addition of
// the ability to pass a context and additional request options.
//
// See CreateDBInstance for details on how to use this API operation.
//
// The context must be non-nil and will be used for request cancellation. If
// the context is nil a panic will occur. In the future the SDK may create
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
// for more information on using Contexts.
func (c *RDS) CreateDBInstanceWithContext(ctx aws.Context, input *CreateDBInstanceInput, opts ...request.Option) (*CreateDBInstanceOutput, error) {
	req, out := c.CreateDBInstanceRequest(input)
	req.SetContext(ctx)
	req.ApplyOptions(opts...)
	return out, req.Send()
}

```

のように同じ名前のメソッドがあります。
あとはこのメソッドの中身を確認して実装有無を調べます。

### AWSのAPIの問題

AWS SDK Goまで調べたのに問題がみつからないケースもあります。
この場合はAWS SDK Goで呼び出している[AWS API](https://docs.aws.amazon.com/)の情報も確認して最終的にそれでもわからなければAWS側に原因の問い合わせすることになります。

例えば
<https://github.com/hashicorp/terraform-provider-aws/issues/20789>
のケースだとAWS SDK Go側にタグの定義がなくさらにAWSのAPI側の処理を調べる必要がありました。
<https://docs.aws.amazon.com/cloudhsm/latest/APIReference/API_CreateHsm.html>
APIの定義情報を確認するとそもそもタグが用意されていないかったということがわかりました。

### まとめ

1. Terraformで問題が起きたらTerraformの書き方に問題がないかterraform validate + planで調べる
2. その上でエラーが起きたらエラーメッセージを読む
3. それでもエラーメッセージの意味がわからなかったら結構難しい問題の可能性があるのでみんなにとりあえず聞いてみよう
くらいでデバッグを進めるとよかったです。