terraformについてまとめ

# terraformについてまとめ

# terraformはmain.tfファイルに記載する。フォルダ直下にこのファイル名があるとterraformコマンドで記載した内容が動作する。

## ◇まず、バージョン管理ファイルを作成しよう
バージョンは固定にしないとバグで実行できない

``` main.tf
terraform {
  // terraformのバージョンを指定
  required_version = ">= 1.12.1"

  // awsプロバイダーのバージョンを指定 version = "~> 4.67" ⇒5.80.0
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.80.0"
    }
  }
}
```

## ◇変数
terraformには変数がある。
「Input Variables」と呼ばれる変数をしようする下記参照

https://developer.hashicorp.com/terraform/language/values/variables

例

```main.tf

resource "リソース名aws_s3_bucketなど" "自由に定義できる変数名" {
var.変数名 = 値

]

```

変数の定義ファイルはmain.tfファイルと同じ改装に作成するファイル名は.tfで終わればよい

```values.tf

varibable "変数名" {
    description = "説明"
    type = string
    default = "デフォルト値"
}

```

## データソース
Data Sourceは、外部データを参照できる仕組みです。 Data Sourceを使用することで、Terraform外部で定義されたデータを取得して、Terraformの構成ファイル内で利用することができます。

詳しくはこちら：
https://developer.hashicorp.com/terraform/language/data-sources

### 取得できるデータ例
・AWSサービスの情報:　EC2インスタンス、S3バケット、VPC、IAMロールなど
・外部APIの情報:　GitHubリポジトリ、DNSレコード、OpenWeatherMap天気情報など
・ローカルファイルの情報:　テンプレートファイル、JSONファイル、YAMLファイルなど

#### データプロバイダの定義
例　データソースプロバイダは、Data Sourceが情報を取得する外部データソースの種類を定義する
下記例はawsのリージョン、githubのトークン

``` 
provider "aws" {
  region = "us-east-1"
}

provider "github" {
  token = "GITHUB_TOKEN"
}


```

#### Data Sourceの定義
Data Sourceは、データソース名とローカル名を指定する
階層は１個にする複数階層にならないように定義するのが基本

```
data "aws_ami" "example" {
  most_recent = true

  owners = ["self"]
  tags = {
    Name   = "app-server"
    Tested = "true"
  }
}

output "名前（自由）" {
    出力する名前 = data.aws_ami.example.ami
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.example.ami
  instance_type = "t2.micro"

  tags = {
    Name = "sample"
  }
}
```

## プロビジョナ
プロビジョナを使用することで、ローカルで実行して情報が取得できる
※プロビジョナはリソース内に定義する、複数定義できる

詳しくはこちら
https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax

例

```
resource "xx" "xx" {

    provisioner "local-exec" {
        command = "echo Hello World ${selfprivate-ip}"
    }
    # パワーシェル実行
    provisioner "local-exec2" {
        command = "${path.module}/script.ps1"
        interprieter = ["powershell","- Filer"]
    }
}
```

## ループ処理
３種類のループ処理がある
・count : https://developer.hashicorp.com/terraform/language/meta-arguments/count
・for_each = toset(var.rg_names) ※rg_namesは配列(list(string)) 
    https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
・for(i=0;i<3;i++){ 

・count

例　バケットを５個countで作成するときの定義

```

resource "aws_s3_bucket" "my_s3_buckets" {
  count  = 5 # 5個のバケットを作成
  bucket = "my-unique-s3-bucket-${count.index + 1}-0123456789" # 一意のバケット名に変更してください。末尾にランダムな文字列やハッシュ値などを付加すると衝突しにくいです。
  # acl    = "private" # デフォルトではプライベートに設定することを推奨

  # アクセスログの設定
  logging {
   target_bucket = aws_s3_bucket.access_log_bucket.id
   target_prefix = "logs/my-s3-bucket-${count.index + 1}/" # ログを保存するプレフィックス
  }

  tags = {
    Name        = "My S3 Bucket ${count.index + 1}"
    Environment = "Dev"
    Project     = "TerraformTest"
  }

  # 非推奨のライフサイクルルール設定
  lifecycle_rule {
    id     = "expire-old-${count.index + 1}-objects"
    enabled = true # <-- FIX: Change 'status' to 'enabled' and use boolean true/false

    expiration {
      days = 1
    }
  }
}

# バケット名を出力
output "s3_bucket_names" {
  description = "Names of the created S3 buckets."
  value       = [for bucket in aws_s3_bucket.my_s3_buckets : bucket.bucket]
}
```

・for_eachでバケットを定義する

```
locals {
  environments = ["dev", "stg", "prd"]
  bucket_name_prefix = "my-application"
}

resource "aws_s3_bucket" "environment_buckets" {
  for_each = toset(local.environments)

  bucket = "${local.bucket_name_prefix}-${each.value}-data"
  acl    = "private"

  tags = {
    Environment = each.value
    Project     = "MyApplication"
  }
}

resource "aws_s3_bucket_versioning" "environment_buckets_versioning" {
  for_each = aws_s3_bucket.environment_buckets

  bucket = each.value.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_public_access_block" "environment_buckets_public_access_block" {
  for_each = aws_s3_bucket.environment_buckets

  bucket = each.value.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```


・forで１ソース内で定義してみる

```
# main.tf

# S3バケットの設定をローカル変数で定義
# ここでは、作成したい各バケットの詳細をマップとして管理します。
# マップのキー（"app_logs", "data_storage"など）は、Terraformの状態ファイル内で
# 各バケットインスタンスを一意に識別するために使用されます。
locals {
  s3_bucket_configurations = {
    "app_logs" = {
      name                = "my-app-logs-bucket-20250623" # バケット名はグローバルでユニークである必要があります
      acl                 = "private"
      versioning_enabled  = true
      public_access_block = true
      tags = {
        Project     = "MyApp"
        Environment = "Production"
        Purpose     = "Application Logs"
      }
    },
    "data_storage" = {
      name                = "my-app-data-bucket-20250623" # バケット名はグローバルでユニークである必要があります
      acl                 = "private"
      versioning_enabled  = false
      public_access_block = true
      tags = {
        Project     = "MyApp"
        Environment = "Production"
        Purpose     = "Data Storage"
      }
    },
    "backup_archive" = {
      name                = "my-app-backup-archive-20250623" # バケット名はグローバルでユニークである必要があります
      acl                 = "private"
      versioning_enabled  = true
      public_access_block = true
      tags = {
        Project     = "MyApp"
        Environment = "Production"
        Purpose     = "Backup Archive"
      }
    }
  }
}

# ---
# S3バケットの定義
# ---
resource "aws_s3_bucket" "application_buckets" {
  # local.s3_bucket_configurations マップの各エントリに対して、
  # 1つの aws_s3_bucket リソースインスタンスを作成します。
  for_each = local.s3_bucket_configurations

  # バケット名は、各設定の 'name' 属性から取得します。
  bucket = each.value.name
  # ACLは、各設定の 'acl' 属性から取得します。
  acl    = each.value.acl

  # タグは、各設定の 'tags' マップから取得します。
  tags = each.value.tags

  # バケットに削除保護を適用することも推奨されます（本番環境の場合）
  # force_destroy = true # WARNING: これを true にすると、バケット内にオブジェクトがあっても強制的に削除されます。
                       # 開発・テスト用途以外では false にすることを強く推奨します。
}

# ---
# S3バケットのバージョン管理設定
# 各バケットの設定で versioning_enabled が true の場合のみ有効化します。
# ---
resource "aws_s3_bucket_versioning" "application_buckets_versioning" {
  # aws_s3_bucket.application_buckets から、
  # 元の local.s3_bucket_configurations のキーを使ってフィルタリングします。
  # ここでは、for式を使ってバージョン管理を有効にするバケットのみを選択しています。
  for_each = {
    for key, config in local.s3_bucket_configurations : key => aws_s3_bucket.application_buckets[key]
    if config.versioning_enabled
  }

  bucket = each.value.id
  versioning_configuration {
    status = "Enabled"
  }
}

# ---
# S3バケットのパブリックアクセスブロック設定
# 各バケットの設定で public_access_block が true の場合のみ有効化します。
# ---
resource "aws_s3_bucket_public_access_block" "application_buckets_public_access_block" {
  # aws_s3_bucket.application_buckets から、
  # 元の local.s3_bucket_configurations のキーを使ってフィルタリングします。
  for_each = {
    for key, config in local.s3_bucket_configurations : key => aws_s3_bucket.application_buckets[key]
    if config.public_access_block
  }

  bucket = each.value.id

  # 全てのパブリックアクセスをブロックする設定
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# ---
# 出力値 (オプション)
# 作成されたバケットのIDと名前を表示します。
# ---
output "created_s3_bucket_info" {
  description = "IDs and names of the created S3 buckets."
  value = {
    for key, bucket in aws_s3_bucket.application_buckets : key => {
      id   = bucket.id
      name = bucket.bucket
    }
  }
}
```


## 関数
詳しくはこちら：https://developer.hashicorp.com/terraform/language/expressions/function-calls
　数値、文字など普通の言語並みに用意されている

```
> split(",", "foo,bar,baz")
[
  "foo",
  "bar",
  "baz",
]
> split(",", "foo")
[
  "foo",
]
> split(",", "")
[
  "",
]
```

## 外部ソースに定義する
source = "./app-cluster"

「外部フォルダにまとめて定義している」という状況ですね。Terraformでは、このような場合に\*\*モジュール（Module）\*\*を使用するのが最も一般的なアプローチです。モジュールを使うことで、リソースの集合体を再利用可能なコンポーネントとしてパッケージ化し、別の場所から呼び出すことができます。

今回は、S3バケットの定義を外部フォルダ（モジュール）にまとめる簡単な例を示します。

-----

### ディレクトリ構成

まず、以下のようなディレクトリ構成を作成します。

```
.
├── main.tf                 # メイン設定ファイル (モジュールを呼び出す)
├── variables.tf            # メイン設定の変数
└── modules/
    └── s3_bucket/          # S3バケットモジュール
        ├── main.tf         # モジュール内のS3バケット定義
        ├── variables.tf    # モジュール内の変数
        └── outputs.tf      # モジュール内の出力
```

-----

### 1\. `modules/s3_bucket/main.tf`

これはS3バケット**モジュールの中身**です。このファイルでS3バケットとその関連リソースを定義します。

```terraform
# modules/s3_bucket/main.tf

resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name
  acl    = var.acl

  tags = var.tags
}

resource "aws_s3_bucket_public_access_block" "this" {
  bucket = aws_s3_bucket.this.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "this" {
  count = var.enable_versioning ? 1 : 0 # enable_versioning が true の場合のみ作成

  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = "Enabled"
  }
}
```

-----

### 2\. `modules/s3_bucket/variables.tf`

これはS3バケット**モジュールの変数**です。モジュールが受け取るべき入力を定義します。

```terraform
# modules/s3_bucket/variables.tf

variable "bucket_name" {
  description = "The name of the S3 bucket."
  type        = string
}

variable "acl" {
  description = "The canned ACL to apply. (e.g., private, public-read)"
  type        = string
  default     = "private"
}

variable "tags" {
  description = "A map of tags to assign to the bucket."
  type        = map(string)
  default     = {}
}

variable "enable_versioning" {
  description = "Set to true to enable versioning on the bucket."
  type        = bool
  default     = false
}
```

-----

### 3\. `modules/s3_bucket/outputs.tf`

これはS3バケット**モジュールの出力**です。モジュールが呼び出し元に返すべき値を定義します。

```terraform
# modules/s3_bucket/outputs.tf

output "bucket_id" {
  description = "The ID of the S3 bucket."
  value       = aws_s3_bucket.this.id
}

output "bucket_arn" {
  description = "The ARN of the S3 bucket."
  value       = aws_s3_bucket.this.arn
}

output "bucket_name" {
  description = "The name of the S3 bucket."
  value       = aws_s3_bucket.this.bucket
}
```

-----

### 4\. `main.tf`

これは**メインの設定ファイル**で、上記で定義したS3バケットモジュールを**呼び出し**ます。

```terraform
# main.tf

provider "aws" {
  region = "ap-northeast-1" # 東京リージョン
}

# S3バケットモジュールを呼び出す
# source にはモジュールが置かれているパスを指定します
# 各変数は modules/s3_bucket/variables.tf で定義されたものに対応します
module "testapp_s3_bucket" {
  source = "./modules/s3_bucket" # モジュールへの相対パス

  bucket_name       = var.testapp_bucket_name # variables.tf で定義された変数を参照
  acl               = "private"
  enable_versioning = true # バージョン管理を有効にする

  tags = {
    Name        = var.testapp_bucket_name
    Environment = "Test"
    Project     = "TestApp"
    ManagedBy   = "TerraformModule"
  }
}
```

-----

### 5\. `variables.tf` (メイン設定)

メイン設定ファイルのための変数です。

```terraform
# variables.tf

variable "testapp_bucket_name" {
  description = "The unique name for the TestApp S3 bucket."
  type        = string
  default     = "my-testapp-module-bucket-20250623" # グローバルでユニークな名前を設定してください
}
```

-----

### 実行方法

1.  上記のディレクトリ構成とファイルを正確に作成します。
2.  `main.tf`があるルートディレクトリのターミナルで、以下のコマンドを実行します。
      * **`terraform init`**: プロバイダとモジュールを初期化します。これにより、Terraformは`./modules/s3_bucket`がモジュールであることを認識します。
      * **`terraform plan`**: 実行計画を表示し、何が作成されるかを確認します。
      * **`terraform apply`**: 実際にAWS上にリソースを作成します。確認プロンプトで`yes`と入力してください。
      * **`terraform destroy`**: 作成したリソースを削除します（注意して使用してください）。

このセットアップにより、S3バケットの定義（`aws_s3_bucket`, `aws_s3_bucket_public_access_block`, `aws_s3_bucket_versioning`）が`modules/s3_bucket`という外部フォルダにまとめられ、`main.tf`からシンプルに呼び出すことができました。これはTerraformで大規模なインフラを管理する際の非常に強力なパターンです。

## モジュール
共通で定義したものを使用できる変数クラスのようなもの

詳しくはこちら：　https://developer.hashicorp.com/terraform/language/modules


```
 module "servers" {
  source = "./app-cluster"

  servers = 5
}

resource "aws_elb" "example" {
  # ...

  instances = module.servers.instance_ids
}
```

## terraformのコマンドのなやみどころ

### init
terraform init でlockファイルを作成する。
・対象のterraformのバージョンのプラグインの確認
・インストールされているプラグインの検索
・対象のterraformをダウンロード（プラグイン）
・ロックファイルを作成

### terraformのバージョンアップ
注意：バージョンアップ時にはバグがあり、戻さないと定義できないものがあるためtfstateファイルはバックアップを必ず取得すること。

terraform init -upgrade

で指定バージョンにバージョンを更新できる。

バージョンを挙げて、PLAN⇒APPLYを実行した後、問題があった場合は、

tfstateファイルをバージョンアップ前のものに戻して、次の反映のものを反映するとバージョンを元に戻せる

バージョンアップ時は１回はapply時にエラーが発生するが、２回目で通るようになる１回目でAPPLｙ可能になるような動作をする

### 戻せない場合

terraform reflesh でサーバの定義したものをtfstateファイルに取得できる
terraform rm でtfstaetファイルの中のリソースの定義を一度消せる　※terraformの管理外にすることができる
terraform import で、tfstateファイルのリソースを取得できる.\
ここまで書いたらわかると思うが、削除してimportしてrefleshすればtfstateファイルに定義した設定を戻すことができる

## terraform destroyコマンドは、本当にアプリ終了で定義全部消したい場合以外使用してはいけない
また、tfstateファイルも同様に削除すると前までの定義が全部なくなるので、tfstateファイルはrefleshで定義を元に戻してからapplyを実行すれば大丈夫
結局applyを実行するときにtfstateファイルがしっかりとしていればいいって話
なかなか扱いが難しい。　バグありすぎだろ。