terraform init, plan, applyって何してるの

# はじめに
Terraformの変更を実際のインフラに反映させる時、init, plan, applyすればできるよーと雑に思っていませんか？実際中で何が起きているのか分からず適当にやっていませんか？

自分はそうでした。すみません。

というわけで脳死apply manにならないように調べたことをまとめていきます。

# terraform init
`terraform init`ではざっくり以下のタスクが実行されます。

- プロバイダのインストール
- バックエンドの初期化
- モジュールのダウンロード
- プラグインのキャッシュ管理

ではそれぞれについて見ていきます。

## プロバイダのインストール
1. `.tf`ファイル内の`required_providers`ブロックを読み取る

    ```terraform
    terraform {
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 4.0"
        }
      }
    }
    ```

1. 必要なプロバイダーのバージョンを確認
1. プロバイダーのダウンロード先の決定
    - デフォルトではHashiCorpが運営している公式Terraformレジストリのドメインであるregistry.terraform.ioになる
    - 中身はweb上で閲覧可能
        - https://registry.terraform.io/
1. プロバイダーのダウンロードと検証
    - ダウンロードしたものは`.terraform/providers`ディレクトリに保存される
    - チェックサムの検証を行う
        - ダウンロードの完全性の確認等のため
1. プロバイダーのインストール

## バックエンドの初期化
1. バックエンド設定の読み取り
    - ローカルのterraformブロック内の設定を確認

    ```terraform
    terraform {
      backend "s3" {
        bucket = "my-terraform-state"
        key    = "prod/terraform.tfstate"
        region = "us-west-1"
      }
    }
    ```
1. バックエンドの種類に応じた初期化
    - s3などのバックエンドに設定されたものとの接続テストや初期設定を行う
1. 状態ファイルの同期確認
    - 状態ファイルとの同期確認

## モジュールのダウンロード
1. モジュール参照の検出
    - terraformファイル内のmoduleの定義を検出する
    ```terraform
    module "vpc" {
      source  = "terraform-aws-modules/vpc/aws"
      version = "3.2.0"
      # ... 設定項目
    }
    ```

1. モジュールのダウンロード
    - ダウンロードしたモジュールは`terraform/modules`ディレクトリに保存
1. モジュールの依存関係解決
1. サブモジュールの再帰的なダウンロード

## プラグインキャッシュの管理
1. ダウンロードしたプロバイダーのキャッシュを探す
    - デフォルト位置は`~/.terraform.d/plugin-cache`
    - 環境変数で変更することができる
1. キャッシュの検証
    - バージョンの確認
    - 整合性の確認

# terraform plan
`terraform plan`ではざっくり以下のタスクが実行されます。

- 現在の状態の読み取り
- 設定ファイルの解析
- 差分の計算
- 実行計画の生成

では内部動作を見ていきましょう。

## 現在の状態の読み取り
1. tfstate ファイルの読み込み
    - 状態ファイルへアクセス
    - 状態ファイルのロックを確認
1. 既存リソースの情報を取得
    - 現在のリソースの設定を確認
    - リソースの依存関係を確認

    ちなみに`tf state`ファイルの中身はこのようになっている
    ```
    {
      "version": 4,
      "terraform_version": "1.0.0",
      "resources": [
        {
          "mode": "managed",
          "type": "aws_instance",
          "name": "example",
          "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
          "instances": [...]
        }
      ]
    }
    ```

## 設定ファイルの解析
1. `.tf`ファイルの読み込み
    - 構文解析
    - 変数の評価
    - モジュールの解決
2. リソースの定義の解析
    - プロバイダー設定の確認
    - リソース間の依存関係の分析
3. 変数の処理
    - デフォルト値の適応
    - variables定義ファイルの読み込み
    - 環境変数の読み込み

## 差分の計算
1. リソースごとの差分検出
    - 追加されるリソース
    - 変更されるリソース
    - 削除されるリソース

1. 属性レベルでの変更検出
    - プリミティブ値の変更
    - リスト・マップの変更
    - 計算された属性の評価

1. 依存関係の解析
    - 直接的な依存関係
    - 暗黙的な依存関係
    - 依存関係グラフの構築

## 実行計画の作成
1. 変更の順序決定
    - 依存関係に基づく実行順序
    - 並列実行可能なリソースの特定
1. リソース変更の詳細表示
    - 具体的な変更内容
        - 追加されるものには"+"、変更されるものには"~"、削除されるものには"-"がつけられて出力される
    - 変更理由の説明
1. 実行時の予測
    - 追加/変更/削除されるリソース数
        - 最後に`Plan: 2 to add, 0 to change, 0 to destroy.`といったように表示される
    - 必要な権限の確認

# terraform apply
最後にapplyです。実際に変更を適応するコマンドですが、具体的には以下のような処理を実行しています。

- 実行計画の作成または読み込み
- リソースの作成・更新・削除
- 状態ファイルの更新
- 出力値の表示

ではそれぞれについて見ていきましょう。

## 実行計画の作成または読み込み
1. その場でのplan実行
    - 設定ファイルの解析
    - 現在の状態と差分の計算
    - リソース依存関係グラフの構築

## リソースの作成・更新・削除
1. リソースの並列処理
    - 依存関係グラフに基づく実行順序の決定
    - 並列実行可能なリソースの特定と実行
1. 各リソースの操作実行
    - 作成
    - 更新
    - 削除
1. エラー処理
    - 部分的な失敗の処理

## 状態ファイルの更新
1. 状態ファイルのロック取得
    - 競合防止のためのロック機構を取得
    - タイムアウトを設定する
1. 状態の更新
    - リソースの最新状態の記録
    - メタデータの更新
1. 状態ファイルの保存
    - バックエンドへの保存
    - ロックの解放

## 出力値の表示
1. 適応結果の表示
    - 成功した操作の一覧
    - エラーが発生したリソースの情報
1. output値の表示
    - 定義された出力値の表示

# まとめ
全ては自分も覚えきってはいませんが、「こういうことをやっているんだな」とぼんやりでも覚えておくと順序を間違えたり無駄にコマンドを叩いたりすることは無くなると思います。
誰かの開発の助けになったら嬉しいです。