Terraformでは、作りたいAWSリソースを設定ファイルに記述し、TerraformがAWS APIを呼び出して実際のリソースを作成・変更・削除します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| flowchart LR
subgraph Local[ローカル環境]
Config[Terraform設定ファイル<br/>main.tf]
CLI[Terraform CLI<br/>plan / apply / destroy]
State[Terraform state<br/>terraform.tfstate]
AWSCLI[AWS CLI<br/>SSO login]
end
subgraph AWS[AWSアカウント]
IDC[IAM Identity Center<br/>ユーザー / Permission set]
API[AWS API]
S3[S3バケット]
end
Config -->|読み込み| CLI
CLI <-->|現在の状態を記録・参照| State
AWSCLI -->|SSO認証| IDC
IDC -->|一時認証情報| CLI
CLI -->|AWS Provider経由でAPIを呼び出す| API
API -->|作成・変更・削除| S3
CLI -.->|plan:差分を確認| API
CLI -.->|apply:差分を反映| API
CLI -.->|destroy:管理対象を削除| API
|
図の見方#
main.tf:作りたいAWSリソースの「あるべき状態」を記述するterraform plan:設定ファイルとAWSの現在の状態との差分を確認するterraform apply:差分をAWSに反映するterraform destroy:Terraformが管理しているリソースを削除するterraform.tfstate:Terraformが管理対象と実際のAWSリソースを対応付けるための状態情報- IAM Identity Center:AWSへ接続するためのユーザーと権限を管理する
- AWS Provider:TerraformとAWS APIの間をつなぐプラグイン
重要なのは、Terraformが直接S3を操作するのではなく、AWS Providerを通じてAWS APIを呼び出す点です。また、terraform.tfstateはTerraformの管理に必要な情報を含むため、公開リポジトリにコミットしないようにします。
はじめに#
TerraformとAWS CLIをmacOS(Apple Silicon)にセットアップし、AWS IAM Identity Center(旧AWS SSO)で認証して、TerraformからS3バケットを操作します。
この記事では、ルートユーザーを普段の作業に使わず、IAM Identity Centerで作成したユーザーとPermission setを利用します。
- Mac:Apple Silicon(M1)
- Terraform:1.16.3
- Terraformの実行対象:
darwin_arm64 - AWS CLI:2.33.17
- Homebrew:Apple Silicon版(
/opt/homebrew)
AWS CLIのバージョン確認結果は次のとおりです。
1
| aws-cli/2.33.17 Python/3.13.11 Darwin/25.5.0 exe/arm64
|
1. HomebrewをApple Silicon版にする#
※自分の環境では設定に問題があったため、この手順を実施しています。問題がない場合はスキップしてください。
Intel版Homebrewを使っている場合は、Apple Silicon版に切り替えます。M1 Macでは、ARM64版Homebrewを使うのが基本です。Apple Silicon版Homebrewの標準インストール先は/opt/homebrewで、Intel版は通常/usr/localにインストールされます。
現在のターミナルでARM64版Homebrewを優先する。
1
| eval "$(/opt/homebrew/bin/brew shellenv)"
|
次回以降も有効にするには、~/.zprofileに追加する。
1
| echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> "$HOME/.zprofile"
|
切り替え後は、次のコマンドで確認できる。
1
2
3
| arch
which brew
brew --prefix
|
以下のようになれば、ARM64版Homebrewが使われている。
1
2
3
| arm64
/opt/homebrew/bin/brew
/opt/homebrew
|
HashiCorpのHomebrew tapを追加し、Terraformをインストールする。
1
2
3
| brew update
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
|
インストールを確認する。
出力例:
1
2
| Terraform v1.16.3
on darwin_arm64
|
Terraformのインストール時に、次のようなエラーが出ることがある。
1
| Your Command Line Tools are too outdated.
|
この場合は、macOSの「システム設定」→「一般」→「ソフトウェアアップデート」からCommand Line Toolsを更新する。更新が表示されない場合は、次のコマンドで再インストールする。
1
2
| sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --install
|
これはMacのCPUアーキテクチャとは別の問題で、ARM64版Homebrewを使っていても、Command Line Toolsが古いとインストールに失敗する。
3. AWS CLIをインストールする#
インストールを確認する。バージョン確認にはaws -vではなくaws --versionを使う。
出力例:
1
| aws-cli/2.x Python/3.x Darwin/xx.x exe/arm64
|
4. AWSの認証設定#
TerraformからAWSを操作するには、IAM Identity Centerで作成したユーザーとAWS CLIのSSO認証を使用する。
ユーザー作成、Permission setの割り当て、SSOログイン、Terraformで使用するAWSプロファイルの設定については、次の別記事にまとめています。
→ IAM Identity CenterとAWS CLIのSSO認証
認証設定後、Terraformを実行する前に、次のコマンドで対象アカウントを確認します。
1
| aws sts get-caller-identity
|
リソースを削除する前に、現在のAWSプロファイルとTerraformの実行対象を確認します。
1
2
| echo "$AWS_PROFILE"
aws sts get-caller-identity
|
削除対象を確認するには、terraform plan -destroyを実行する。
1
| terraform plan -destroy
|
内容に問題がなければ、次のコマンドで削除します。
AWSプロファイルをコマンド単位で指定することもできる。
1
2
| AWS_PROFILE=YOUR_PROFILE_NAME terraform plan -destroy
AWS_PROFILE=YOUR_PROFILE_NAME terraform destroy
|
トラブルシューティング:設定ファイルの重複#
次のようなエラーが出た場合は、同じTerraform設定を複数のファイルから読み込んでいる可能性がある。
1
2
3
| Error: Duplicate required providers configuration
Error: Duplicate provider configuration
Error: Duplicate resource configuration
|
Terraformは、同じディレクトリにある拡張子.tfのファイルをすべて設定ファイルとして読み込む。例えば、次のようにバックアップファイルを作成すると、bk_main.tfも有効な設定ファイルとして扱われる。
バックアップを残す場合は、拡張子を.tf以外に変更する。
1
| mv bk_main.tf bk_main.tf.bak
|
その後、設定を検証してから削除を実行する。
1
2
3
| terraform validate
terraform plan -destroy
terraform destroy
|
terraform destroyはTerraformの設定を読み込めないと開始できない。そのため、まず重複エラーを解消する必要がある。
セキュリティ上の注意#
次の情報は、ブログやGitリポジトリにそのまま掲載しない。
- AWSアカウントID
- IAM Identity Centerのユーザー名
- SSO start URLの組織固有部分
- ARN
UserIdやセッション名- ローカルユーザー名やMacのホスト名
- アクセストークン、アクセスキー、シークレットキー
また、Terraformの状態ファイル(terraform.tfstate)にはAWSリソースの情報が含まれる場合があるため、公開リポジトリにはコミットしないようにします。
AWSの設定が終わったので、ここからはAWS CLIとTerraformを使ってAWSリソースを操作します。
1
| export AWS_PROFILE=YOUR_PROFILE_NAME
|
main.tfというファイルを作成します。これは、AWSを最終的にどのような構成にしたいかをTerraformへ伝える設計図です。
main.tfは、Terraformに「AWSを最終的にどのような構成にしたいか」を伝える設計図です。
ただし、main.tfという名前自体に特別な意味はありません。Terraformは、コマンドを実行したディレクトリにあるすべての.tfファイルを自動で読み込みます。
どこから読み込まれるのか#
例えば、次の場所でTerraformを実行するとします。
1
2
3
4
| s3-security-lab/
├ main.tf
├ outputs.tf
└ variables.tf
|
1
2
| cd s3-security-lab
terraform plan
|
Terraformはs3-security-lab直下にある、
1
2
3
| main.tf
outputs.tf
variables.tf
|
をすべて読み込み、1つの設定として合体させます。
main.tfを明示的に指定する処理や、Reactのimportのような記述は必要ありません。
を実行すると、現在のディレクトリにある.tfファイルが自動的に読み込まれます。
main.tfという名前は慣習#
以下はすべて同じように読み込まれます。
1
2
3
4
| main.tf
s3.tf
provider.tf
banana.tf
|
banana.tfでも技術的には動きますが、他の人が困惑するので、一般的には役割ごとに次のような名前を付けます。
1
2
3
4
5
| terraform.tf # TerraformとProviderのバージョン
provider.tf # AWS Providerの設定
main.tf # 中心となるリソース
variables.tf # 入力値
outputs.tf # 実行後に表示する値
|
小規模な学習用プロジェクトでは、全部main.tfに書いて問題ありません。
今回のmain.tfがしていること#
1
2
3
4
5
6
7
8
9
| terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
|
これはTerraform本体の設定です。
1
| required_version = ">= 1.5.0"
|
Terraform 1.5.0以上を使うよう指定しています。
1
2
3
4
5
| required_providers {
aws = {
source = "hashicorp/aws"
}
}
|
AWSを操作するために、HashiCorp公式のAWS Providerを使用すると宣言しています。
terraform initを実行すると、この情報をもとにAWS Providerがダウンロードされます。
1
2
3
4
5
6
7
8
9
10
| provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
Project = "s3-security-lab"
ManagedBy = "Terraform"
}
}
}
|
これはAWS Providerの設定です。
1
| region = "ap-northeast-1"
|
東京リージョンを操作対象にしています。
ただし、S3のバケット名は全世界で一意であり、S3コンソール上もグローバルに見えるため、EC2ほどリージョンを意識しにくい部分はあります。
default_tagsは、Terraformが作成する対応リソースへ共通タグを付けます。
1
2
| Project = s3-security-lab
ManagedBy = Terraform
|
これにより、AWSコンソールで「何の検証用か」「Terraform管理か」を判別できます。
認証情報はここには書きません。Terraformは、ターミナルで指定した以下のプロファイルを利用します。
1
| export AWS_PROFILE=YOUR_PROFILE_NAME
|
1
2
3
| resource "aws_s3_bucket" "lab" {
bucket_prefix = "makiharu-s3-security-lab-"
}
|
ここがS3バケットを定義している部分です。
1
2
3
4
5
6
7
8
9
10
11
| resource
└ AWS上のリソースを管理する宣言
aws_s3_bucket
└ 作成するAWSリソースの種類
lab
└ Terraform内部で使う名前
bucket_prefix
└ 実際のS3バケット名の接頭辞
|
labはAWS上のバケット名ではありません。Terraformコード内で参照する名前です。
例えば、ほかの箇所からは次のように参照します。
実際のバケット名は、次のようになります。
1
| makiharu-s3-security-lab-abc123xyz
|
bucket_prefixを使用しているため、末尾の一意な文字列はTerraform側で追加されます。
1
2
3
4
5
6
7
8
| resource "aws_s3_bucket_public_access_block" "lab" {
bucket = aws_s3_bucket.lab.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
|
これは、作成したS3バケットの外部公開を防ぐ設定です。
特に重要なのが次の参照です。
1
| bucket = aws_s3_bucket.lab.id
|
これは、
aws_s3_bucket.labとして作成したバケットに、このPublic Access Blockを設定する
という意味です。
この参照によって、Terraformは依存関係も判断します。
1
2
3
| S3バケットを作る
↓
そのバケットにPublic Access Blockを設定する
|
コードが記述された上下の順番ではなく、この参照関係から実行順序を決めます。
1
2
3
4
| output "bucket_name" {
description = "Terraformで作成したS3バケット名"
value = aws_s3_bucket.lab.bucket
}
|
これは、作成されたバケット名を実行後に表示する設定です。
の完了後に、次のように表示されます。
1
2
3
| Outputs:
bucket_name = "makiharu-s3-security-lab-abc123xyz"
|
後から個別に確認することもできます。
1
| terraform output bucket_name
|
ファイルを作っただけではAWSは変わらない#
main.tfを保存しただけでは、S3バケットは作られません。
1
2
3
4
5
6
7
8
| main.tfを保存
→ AWSには何も起きない
terraform plan
→ 変更予定を計算するだけ
terraform apply
→ AWS APIを呼び出して実際に作成する
|
実際には次の流れです。
1
2
3
4
5
6
7
| main.tf
↓ 読み込み
Terraform
↓ AWS Provider
AWS API
↓
S3バケット作成
|
Terraformでは、.tfファイル単体よりもディレクトリが1つの単位になります。この単位をルートモジュールと呼びます。
1
2
3
4
| s3-security-lab/ ← ルートモジュール
├ main.tf
├ variables.tf
└ outputs.tf
|
したがって、別のディレクトリでterraform planを実行しても、このmain.tfは読み込まれません。
1
2
| cd s3-security-lab
terraform plan
|
と、対象の.tfファイルがあるディレクトリで実行する必要があります。
要するに、main.tfは特別な起動ファイルではありません。現在のディレクトリにある設計図の一部であり、Terraformがすべての.tfをまとめて読み込むという仕組みです。
「AWS Providerがダウンロードされる」とは、TerraformからAWS APIを操作するための専用プラグインが、PC内へインストールされるという意味です。
Terraform本体だけでは、S3やEC2の作り方を知りません。
1
2
3
4
5
| Terraform本体
「設定を読み、差分を計算する仕組み」
AWS Provider
「S3やEC2をAWS APIで操作する仕組み」
|
全体の関係#
1
2
3
4
5
6
7
8
9
| main.tf
↓ 読み込む
Terraform本体
↓ AWS Providerへ指示
AWS Provider
↓ AWS APIを呼び出す
AWS
↓
S3バケット作成
|
例えば、main.tfに次の記述があります。
1
2
3
4
5
6
7
| terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
|
これはTerraformに、
registry.terraform.io/hashicorp/awsで公開されているAWS Providerが必要です
と伝えています。
を実行すると、おおむね次の処理が行われます。
.tfファイルから必要なProviderを確認する- Terraform Registryへアクセスする
- 使用するAWS Providerのバージョンを決める
- Macに対応したProviderをダウンロードする
.terraform/へ配置する- 選択したバージョンとチェックサムを
.terraform.lock.hclへ記録する
MacがApple Siliconなら、macOS ARM64向けの実行ファイルが取得されます。
ディレクトリは、おおむね次の状態になります。
1
2
3
4
5
6
7
8
| s3-security-lab/
├ .terraform/
│ └ providers/
│ └ registry.terraform.io/
│ └ hashicorp/
│ └ aws/
├ .terraform.lock.hcl
└ main.tf
|
AWS Providerが担当すること#
AWS Providerには、例えば次の処理が実装されています。
aws_s3_bucketをAWSのどのAPIへ変換するか- S3バケットの作成・更新・削除
- 現在のバケット設定の取得
- AWSのレスポンスをTerraform Stateへ変換
- AWS CLIプロファイルや環境変数からの認証
- AWS APIのエラー処理
例えば、
1
2
3
| resource "aws_s3_bucket" "lab" {
bucket_prefix = "makiharu-s3-security-lab-"
}
|
をAWS Providerが解釈し、S3のバケット作成APIを呼び出します。
Terraform本体は、aws_s3_bucketの詳細までは知りません。AWS Providerに「このリソースを作って」と依頼します。
.terraform.lock.hclには、実際に採用されたProviderのバージョンとチェックサムが記録されます。
イメージとしては次のような内容です。
1
2
3
4
5
6
7
8
| provider "registry.terraform.io/hashicorp/aws" {
version = "6.x.x"
hashes = [
"h1:...",
"zh:..."
]
}
|
役割は次の2つです。
- 次回も同じProviderバージョンを使用する
- ダウンロードしたProviderが改ざんされていないか確認する
main.tfのバージョン条件が、
なら、利用可能な6系の中から実際のバージョンを選び、その結果をロックファイルへ固定します。
1
2
3
4
5
| main.tf
「6系を使用してよい」
.terraform.lock.hcl
「今回は具体的に6.x.xを使用した」
|
Git管理の違い#
| 対象 | Git管理 | 理由 |
|---|
.terraform/ | しない | 再ダウンロードでき、サイズも大きい |
.terraform.lock.hcl | する | チームや環境間でProviderバージョンを揃える |
main.tf | する | インフラの設計図 |
別のPCでリポジトリをクローンしてterraform initを実行すると、ロックファイルに従って同じProviderがダウンロードされます。
initだけではS3は作られない#
terraform initは作業環境の準備です。
1
2
3
4
5
6
7
8
9
| terraform init
→ AWS Providerを用意する
→ S3はまだ作られない
terraform plan
→ AWSの現在状態とコードを比較する
terraform apply
→ ProviderがAWS APIを呼び、S3を作る
|
また、Providerをダウンロードしても、AWS本体やAWS CLI、認証情報がダウンロードされるわけではありません。SSO認証情報は別に管理され、Providerが必要なときにその一時認証情報を利用します。
感覚としては、Terraform本体がゲーム機、AWS ProviderがAWSを操作するための専用ソフト、.terraform.lock.hclが使用ソフトのバージョン固定表です。
HashiCorpのAWS Providerは、Terraformの設定をAWS APIの操作へ変換するアダプター兼実装プラグインです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v6.65.0...
- Installed hashicorp/aws v6.65.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
|
実行後は、次のような構成になります。
1
2
3
4
5
6
7
8
| s3-security-lab/
├ .terraform/
│ └ providers/
│ └ registry.terraform.io/
│ └ hashicorp/
│ └ aws/
├ .terraform.lock.hcl
└ main.tf
|
1
2
| terraform plan
terraform apply
|
Terraformから確認を求められたら、yesと入力します。yes以外の入力やEnterだけの入力では、処理は実行されません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| YOUR_USER@YOUR_HOST s3-security-lab % AWS_PROFILE=YOUR_PROFILE_NAME terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.lab will be created
+ resource "aws_s3_bucket" "lab" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = (known after apply)
+ bucket_domain_name = (known after apply)
+ bucket_namespace = (known after apply)
+ bucket_prefix = "makiharu-s3-security-lab-"
+ bucket_region = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = "ap-northeast-1"
+ request_payer = (known after apply)
+ tags_all = {
+ "ManagedBy" = "Terraform"
+ "Project" = "s3-security-lab"
}
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
+ cors_rule (known after apply)
+ grant (known after apply)
+ lifecycle_rule (known after apply)
+ logging (known after apply)
+ object_lock_configuration (known after apply)
+ replication_configuration (known after apply)
+ server_side_encryption_configuration (known after apply)
+ versioning (known after apply)
+ website (known after apply)
}
# aws_s3_bucket_public_access_block.lab will be created
+ resource "aws_s3_bucket_public_access_block" "lab" {
+ block_public_acls = true
+ block_public_policy = true
+ bucket = (known after apply)
+ id = (known after apply)
+ ignore_public_acls = true
+ region = "ap-northeast-1"
+ restrict_public_buckets = true
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ bucket_name = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_s3_bucket.lab: Creating...
aws_s3_bucket.lab: Creation complete after 1s [id=makiharu-s3-security-lab-c4fe4545a78e64ed34b2027d4c]
aws_s3_bucket_public_access_block.lab: Creating...
aws_s3_bucket_public_access_block.lab: Creation complete after 0s [id=makiharu-s3-security-lab-c4fe4545a78e64ed34b2027d4c]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
bucket_name = "makiharu-s3-security-lab-c4fe4545a78e64ed34b2027d4c"
|
S3バケットが作成されました。

S3バケットを削除する#
削除対象を確認するには、次のコマンドを実行します。
何も表示されなければ、Terraformの管理対象は空です。AWSコンソールからも、対象のS3バケットが削除されていることを確認します。
理解度チェック#
Terraformは、どのようにAWSリソースを操作するか?
答え
Terraform本体が設定ファイルを読み込み、AWS Providerを通じてAWS APIを呼び出す。Terraformが直接S3を操作しているわけではない。main.tfというファイル名には、特別な意味があるか?
答え
特別な意味はない。Terraformは、コマンドを実行したディレクトリにあるすべての`.tf`ファイルをまとめて読み込む。`main.tf`は慣習的によく使われる名前。terraform initを実行すると、何が行われるか?
答え
設定に必要なProviderをTerraform Registryから取得し、`.terraform/`に配置する。また、選択したProviderのバージョンとチェックサムを`.terraform.lock.hcl`に記録する。AWSリソースの作成は行わない。terraform planとterraform applyの違いは何か?
答え
`terraform plan`は、設定とAWSの現在の状態との差分を確認するだけ。`terraform apply`は、確認した変更内容をAWSへ反映する。次の設定で、labはAWS上のS3バケット名か?
1
2
3
| resource "aws_s3_bucket" "lab" {
bucket_prefix = "makiharu-s3-security-lab-"
}
|
答え
AWS上のバケット名ではない。`lab`はTerraform内部で参照する名前で、実際のバケット名は`bucket_prefix`に一意な文字列が付いた名前になる。Terraformで作成したProviderのバージョンをチームで揃えるために、Git管理すべきファイルは何か?
答え
`.terraform.lock.hcl`。Providerのバージョンとチェックサムが記録されるため、Git管理する。`.terraform/`ディレクトリは再生成できるため、通常はGit管理しない。S3バケットを削除する前に実行すべき確認と、削除コマンドは何か?
答え
まず`terraform plan -destroy`で削除対象を確認し、問題がなければ`terraform destroy`を実行する。実行前に、対象AWSアカウントとAWSプロファイルも確認する。