🦍

新卒エンジニアによるTerraform入門

に公開

はじめに

だいぶ日が空いてしまいました、お久しぶりです。新卒エンジニアのアアツです😃
LRM株式会社で開発チームに所属しています。

先日、AWSのコミュニティイベントJAWS-UGに参加しました。
その中で初めてTerraformに触れ、「自分でも手を動かして学んでみたい!」と思ったので、今回学習を進めてみることにしました。

まずは入門として、TerraformでS3バケットを作成する手順を試してみます。

今回の学習で作成したプロジェクトはGitHubに公開しているので、ぜひ覗いてみてください 🙌
https://github.com/AatsuTakahashi/terraform_tutorial

Terraformとは

まず、Terraformとはなんぞや。

TerraformはHashiCorp社が提供する IaC(Infrastructure as Code)ツール です。
(ちなみにAWS Summitでは箸のノベルティを配っていたことでも印象的です 🤭)
https://developer.hashicorp.com/terraform

IaCInfrastructure as Code)とは、インフラをコードで管理する手法のこと。
Terraformを使うと、サーバーやネットワーク、ストレージといったインフラをコード(HCL:HashiCorp Configuration Language)に書いて構築・変更できます。

Terraformを使うメリット

Terraformの主なメリットは次の通りです。

  • 自動化できる:手動操作によるミスを減らせる
  • 再現性がある:同じコードを実行すれば同じ環境を作れる
  • チーム開発に向いている:コードを見れば誰でも環境を理解できる

基本の構成

Terraformの基本は「Terraform」「Provider」「Resource」の3ブロック。
この3つを組み合わせることで、AWSに実際のリソースを作成できます。

  • Terraformブロック:Terraform自体の設定(バージョンやプロバイダ)
  • Providerブロック:接続先クラウド(例:AWS)を定義
  • Resourceブロック:実際に作成するリソース(例:S3バケット)

まずはS3バケットを例に、最小構成のコードを確認してみましょう。

Terraformブロック

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

Terraformブロックでは、Terraform自体の設定を書きます。

  • required_versionで使用するTerraformのバージョンを指定
  • required_providersで利用するプロバイダを指定(AWSを操作するにはhashicorp/awsプロバイダが必要)

Providerブロック

provider "aws" {
  region = "ap-northeast-1"
}

Providerブロックでは、どのクラウドサービスを使うかを設定します。
TerraformでAWSを操作する場合はprovider "aws"を指定し、利用するリージョンをここで決めます。

  • region … AWSのリージョン(例:ap-northeast-1は東京リージョン)
  • 認証情報 … Terraformがアクセスキー(もしくはSSOで発行された一時クレデンシャル) を自動的に読み込んで利用します

Resourceブロック

resource "aws_s3_bucket" "example" {
  bucket = "my-terraform-demo-bucket"
}

Resourceブロックは、Terraformで実際に作りたいクラウドリソースを定義する部分です。
詳しく見ていきます。

aws_s3_bucket : リソースタイプ

どんなリソースを作成するか。
<プロバイダ名>_<リソースタイプ>というように書きます。
上記の例だと、「AWSのS3バケットというリソースタイプ」を作成する、という意味になります。

example : ローカル名

Terraformのコード内で参照するときに使う識別子。
Terraformのコードの中でのみ使う名前であり、AWS上にその名前が作られるわけではない。

my-terraform-demo-bucket : リソース名

AWS上に作られるバケットの名前。

これ以外にもさまざまな設定やオプションが存在していて、Resourceブロックの書き方についての詳細は以下のサイトから確認することができます。
https://registry.terraform.io/

HCLの書き方について理解ができたところで、実際に手を動かしながらTerraformの使い方を学んでいきます!

TerraformでS3バケットを作ってみる

環境準備

まずはTerraformのインストールから行っていきます。
以下のサイトに記載されている手順に従って、作業を進めていきます。
https://developer.hashicorp.com/terraform/install

下記コマンドを入力します。

$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform

無事にインストールができました!

$ terraform -v
Terraform v1.13.1

続いてAWSのバージョンを確認します。
今回は以下のバージョンでハンズオンを進めていきます。

$ aws --version
aws-cli/2.27.43 Python/3.13.4 Darwin/24.5.0 exe/x86_64

プロジェクトを作成する

環境準備が整ったところで、プロジェクトを作成していきます。

まずは下記コマンドを入力して作業ディレクトリを作成します。

$ mkdir terraform_tutorial
$ cd terraform_tutorial

続いて、 main.tf ファイルを作成します。
Terraformは、そのディレクトリ内のすべての *.tf(と *.tf.json)を読み込んで1つの設定として扱います。
従って、まずは主要な定義の慣習的な置き場であるmain.tfにHCLを書いていく必要があります。

ということで書いていきます。コードの生成はCodexにしてもらいました。

main.tf
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

locals {
  terraform_s3_tutorial = "my-terraform-demo-bucket"
}

resource "aws_s3_bucket" "terraform_s3_tutorial" {
  bucket        = local.terraform_s3_tutorial
  force_destroy = false

  tags = {
    Name      = "terraform_s3_tutorial"
    ManagedBy = "Terraform"
  }
}

output "bucket_id" {
  value = aws_s3_bucket.terraform_s3_tutorial.id
}

output "bucket_arn" {
  value = aws_s3_bucket.terraform_s3_tutorial.arn
}

生成したコードを詳しく見ていきます。

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

terraformブロックとproviderブロックについては、先述したようにバージョンや必要なプロバイダが書かれています。ここはそのままで良さそう。

続いて、resourceブロックについてです。

locals {
  terraform_s3_tutorial = "my-terraform-demo-bucket"
}

resource "aws_s3_bucket" "terraform_s3_tutorial" {
  bucket        = local.terraform_s3_tutorial
  force_destroy = false

  tags = {
    Name      = "terraform_s3_tutorial"
    ManagedBy = "Terraform"
  }
}

まず、localsブロックに分けて書くことで再利用性と拡張性が向上します(今回はシンプルな実装になるので、bucket=リソース名のように直書きしても良さそう)。
ただし、S3バケット名はグローバルに一意である必要があります。そのため、ユニークな名前に変更していきます。

terraform_s3_tutorial = "trbucket-aatsutakahashi-20250901"に変更したいと思います。

force_destroy = falseはバケットにオブジェクトが残っている場合に削除を防止する設定です。

tagsブロックは、リソースを作成する上で必ずしも明記する必要はありません。しかし、「誰が・何のために作ったリソースか」を分かりやすくするためのメタデータとして付与するのが一般的です。

また、S3バケットにタグを付けると、AWSコンソール上で識別しやすくなるだけでなく、運用やコスト管理(プロジェクト別の請求分けなど)にも活用することができます。今回は学習のため、tagsブロックを明記してリソースを作成していきます。

ということで、S3バケット名の変更以外はそのままで進めてよさそうです。

最後に、outputブロックです。

output "bucket_id" {
  value = aws_s3_bucket.terraform_s3_tutorial.id
}

output "bucket_arn" {
  value = aws_s3_bucket.terraform_s3_tutorial.arn
}

呼び出し元(別のモジュール)から値を参照できるようにするのがoutputブロックの目的です。
同じモジュール内ではoutputを使わなくてもリソースを直接参照できますが、モジュール外へ値を渡す際にはoutputが必要になります。

このチュートリアルでは学習のためにoutputを指定し、bucket_idbucket_arnを明示的に出力しています。
outputブロックについての詳細は、こちらから確認できます。

上記の設定に加えて、S3バケットの公開を防ぐためのセーフティーガードを追加していきます。

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

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

AWSでは「S3 バケットを公開してしまった」ことがセキュリティ事故の原因になりやすいため、
2023年4月以降、新規バケットではデフォルトでPublic Access Blockが有効になったそうです。
しかし、コードに書いておくことで「このバケットは絶対公開しない」というのを保証できます。今回は学習も兼ねているので、あえて書いておくことにします。

https://aws.amazon.com/jp/s3/features/block-public-access/#:~:text=すべての新しいバケットでは、ブロックパブリックアクセスがデフォルトで有効になっています。

最終的なmain.tfファイルは以下のようになりました。

main.tf
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

locals {
  terraform_s3_tutorial = "trbucket-aatsutakahashi-20250901"
}

resource "aws_s3_bucket" "terraform_s3_tutorial" {
  bucket        = local.terraform_s3_tutorial
  force_destroy = false

  tags = {
    Name      = "terraform_s3_tutorial"
    ManagedBy = "Terraform"
  }
}

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

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

output "bucket_id" {
  value = aws_s3_bucket.terraform_s3_tutorial.id
}

output "bucket_arn" {
  value = aws_s3_bucket.terraform_s3_tutorial.arn
}

続いて、Terraformの実行コマンドを確認していきます。

実行コマンド

実行コマンドは以下の通りです。

terraform init
terraform fmt
terraform plan
terraform apply
terraform destroy

それぞれのコマンドを詳しく見ていきます。

terraform init

依存プロバイダ(aws、randomなど)やモジュールをダウンロードし、作業ディレクトリを初期化します。

terraform fmt

HCLのコードを公式スタイルに整形します。

terraform plan

“現在の状態→コードで意図した状態”の差分を表示します。

terraform apply

planで確認した変更を実行します。

terraform destroy

そのディレクトリ配下で Terraform 管理下のリソースが不要になった際に削除します。

terraformコマンドの詳細は以下の記事を参考にしました。
https://zenn.dev/youtuber/articles/476f032cee4dc4

準備ができたところで、実際にS3バケットを作成していきます!

S3バケットの作成

では実際にTerraformコマンドを入力して、S3バケットを作成していきます。

まず、下記のコマンドを入力して作業ディレクトリを初期化していきます。

$ terraform init
実行結果はこちら
$ terraform init

Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.100.0...
- Installed hashicorp/aws v5.100.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.

続いて、main.tfのコードを整形していきます。

$ terraform fmt

変更点はありませんでした。

続いて、現在の状態→コードで意図した状態の差分を確認します。

$ terraform plan

実行結果は下記のようになります(多少省略しています)。

実行結果はこちら
$ terraform plan

Plan: 2 to add, 0 to change, 0 to destroy.

# aws_s3_bucket.terraform_s3_tutorial
+ bucket = "trbucket-aatsutakahashi-20250901"
+ tags = {
    "ManagedBy" = "Terraform"
    "Name"      = "terraform_s3_tutorial"
  }

# aws_s3_bucket_public_access_block.terraform_s3_tutorial
+ block_public_acls       = true
+ block_public_policy     = true
+ ignore_public_acls      = true
+ restrict_public_buckets = true

Changes to Outputs:
+ bucket_arn = (known after apply)
+ bucket_id  = (known after apply)

最後に、planで確認した変更を実行していきます。

$ terraform apply
実行結果はこちら
$ terraform apply

Acquiring state lock. This may take a few moments...

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.terraform_s3_tutorial will be created
  + resource "aws_s3_bucket" "terraform_s3_tutorial" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "trbucket-aatsutakahashi-20250901"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (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                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "ManagedBy" = "Terraform"
          + "Name"      = "terraform_s3_tutorial"
        }
      + tags_all                    = {
          + "ManagedBy" = "Terraform"
          + "Name"      = "terraform_s3_tutorial"
        }
      + 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.terraform_s3_tutorial will be created
  + resource "aws_s3_bucket_public_access_block" "terraform_s3_tutorial" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + bucket_arn = (known after apply)
  + bucket_id  = (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.terraform_s3_tutorial: Creating...
aws_s3_bucket.terraform_s3_tutorial: Creation complete after 2s [id=trbucket-aatsutakahashi-20250901]
aws_s3_bucket_public_access_block.terraform_s3_tutorial: Creating...
aws_s3_bucket_public_access_block.terraform_s3_tutorial: Creation complete after 0s [id=trbucket-aatsutakahashi-20250901]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

bucket_arn = "arn:aws:s3:::trbucket-aatsutakahashi-20250901"
bucket_id = "trbucket-aatsutakahashi-20250901"

S3バケットの作成が完了したようです!!

AWSマネジメントコンソールにログインして確認してみましょう。

無事にS3バケットが作成できているようです!!

さいごに

今回は、Terraformを使ってS3バケットを作成する方法を学びました。
AWSコンソールからクリックで作成するのとは違い、HCLでインフラを定義して構築する流れはとても新鮮でした。
私自身、JAWS-UGの勉強会ではついていくのに精一杯で、なかなか理解が追いつかない部分もありました。しかし今回、自分で手を動かして学習することで、Terraformの基本的な仕組みをしっかりと掴むことができました。

次回はさらに一歩進んで、S3にオブジェクトを配置してAthenaでクエリすることに挑戦してみたいと思います。

ここまで読んでいただき、ありがとうございました。
ご質問やご感想がありましたら、お気軽にコメントしていただけると嬉しいです🙌

参考

https://developer.hashicorp.com/terraform
https://developer.hashicorp.com/terraform/install
https://registry.terraform.io/modules/terraform-aws-modules/s3-bucket/aws/latest?tab=outputs
https://aws.amazon.com/jp/s3/features/block-public-access/#:~:text=すべての新しいバケットでは、ブロックパブリックアクセスがデフォルトで有効になっています。
https://zenn.dev/youtuber/articles/476f032cee4dc4
https://zenn.dev/oyasumipants/articles/6f8c03380d7171

LRM Tech Blog

Discussion