はじめに
この記事は、リレーブログ企画「25新卒リレーブログの記事」です。
こんにちは、7月に「AWS Summit Japanガイド」の記事でご挨拶しましたパクです!
皆さん、AWSでEC2サーバーをどのように作成していますか? おそらくほとんどの方が、AWSマネジメントコンソールにログインしてマウスでクリックして作成する方法を思い浮かべるでしょう。私も入社前まではそうでしたから。しかし、会社ではコードでサーバーを作成し、管理していました。
なぜでしょうか?手動で作業すると些細なミスが起こりやすく、後から誰が何をどのように変更したのかを追跡することも難しいからです。
この記事では、Terraformというツールを使い、EC2インスタンスをコードで管理した経験談を共有したいと思います。
筆者プロフィール
- 入社時期:2025年4月
- 入社前のスキル
- 経験あり: JavaScript, React, Python
- 経験なし: Terraform, AWS
- 現在の担当:サービスシステムグループ
目標
🎯 AWSコンソールで手動作成したEC2インスタンスをTerraformのコードに移行する
目次
- 環境構築
- EC2モジュールの作成
- EC2インスタンスの作成
- 最後に
1. 環境構築
ニフティではチームで協力して開発することが多いため、コードを記述する前に、まず最初に行うべきことは「適切な作業環境」を構築することです。
a. S3バケットの作成
Terraformはコードを実行すると、現在のインフラの状態が記録されるterraform.tfstate
というファイルがローカルPC上に作成されます。この時、もしチームメンバーAがサーバーを追加し、メンバーBがデータベースの設定を変更した場合、二人はそれぞれ内容の異なるterraform.tfstate
ファイルを持つことになります。
このような状況が続くと、後になってどれが本当のインフラの状態なのか分からなくなってしまうため、それぞれのローカルPCではなく、チーム全員が共通でアクセスできるリモートストレージに保管する必要があります。私たちのチームでは、AWSのS3 (Simple Storage Service) バケットにterraform.tfstate
ファイルを保存しています。
まず、以下のターミナルのようにバケット名やAWSプロファイルといった環境変数を設定します。今後実行するコマンドでこれらの変数を参照することになります。
1 2 3 |
$ export BUCKET_NAME=project-tfstate-bucket # バケット名 $ export AWS_PROFILE=terraform-user # プロファイル名 $ export AWS_DEFAULT_REGION=ap-northeast-1 # リージョン |
次に、以下のAWS CLIコマンドを順に実行し、S3バケットを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 1. S3バケットを作成します。 $ aws s3api create-bucket --bucket $BUCKET_NAME --acl private --region $AWS_DEFAULT_REGION --create-bucket-configuration LocationConstraint=$AWS_DEFAULT_REGION # 2. 全てのパブリックアクセスをブロックし、セキュリティを強化します。 $ aws s3api put-public-access-block --bucket $BUCKET_NAME --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" # 3. バケットのバージョニングを有効に設定します。(誤ってファイルを上書き・削除しても復元可能) $ aws s3api put-bucket-versioning --bucket $BUCKET_NAME --versioning-configuration Status=Enabled # 4. バケットに保存される全てのオブジェクトを暗号化するよう設定します。 $ aws s3api put-bucket-encryption --bucket $BUCKET_NAME --server-side-encryption-configuration '{ "Rules": [ { "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "AES256" } } ] }' |
b. Terraform環境の構築 (versions.tf)
S3バケットの準備ができたので、次にTerraformのコードを記述していきましょう。
まず、プロジェクト全体で使用するTerraformとAWS Providerのバージョンを指定するversions.tf
ファイルを作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# versions.tf terraform { # このコードがTerraform 1.0台で実行されるべきことを指定します。 required_version = "~> 1.0" required_providers { # AWSインフラを扱うためにHashiCorp製のAWS Providerを使用します。 aws = { source = "hashicorp/aws" # AWS Providerは6.7.0台を使用するよう指定します。 version = "~> 6.7.0" } } } |
ちょうど今回の作業で、AWS Providerのバージョンも最新の6.7.0
に更新しました!
c. シンボリックリンクの作成
私たちのチームでは、開発環境と本番環境を分離して作業を進めています。そのため、各環境で先ほど作成したversions.tf
ファイルを参照します。
この時、シンボリックリンク (Symbolic Link) を使用します。分かりやすく言えば、「ショートカットアイコン」を作成するようなものです。元のファイル (versions.tf
) を一箇所に置いておき、各環境のディレクトリにはその元ファイルを指すリンクだけを作成するのです。
以下のコマンドでenvironments/development
ディレクトリを作成し、その中にversions.tf
ファイルを指すシンボリックリンクを作成しました。
1 2 3 4 |
$ mkdir -p environments/development # ln -s: シンボリックリンクを作成 $ ln -s ../../versions.tf environments/development/versions.tf |
d. AWS設定ファイル (providers.tf)
最後に、私たちがどのS3バケットとAWSアカウントを使用するのかを指定する設定ファイルを作成します。environments/development
ディレクトリ内にproviders.tf
ファイルを作成し、以下のように記述しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# /environments/development/providers.tf terraform { backend "s3" { bucket = "project-tfstate-bucket" # S3バケット名 key = "terraform.tfstate" # tfstateファイル名 region = "ap-northeast-1" # AWSリージョン profile = "terraform-user" # AWSプロファイル } } # 作業で使用するAWSアカウント provider "aws" { region = "ap-northeast-1" profile = "terraform-user" } |
e. Terraformの初期化
ここまででS3バケットを作成し、「プロジェクトのルール (versions.tf
)」と「接続設定 (providers.tf
)」が含まれたファイルの準備が整いました。いよいよTerraformを初期化し、プロジェクトを開始することを伝える番です。
この役割を担うのがterraform init
コマンドです。このコマンドは、以下の二つの処理を行います。
- プラグインのダウンロード:
versions.tf
ファイルに指定されたAWS Provider(プラグイン)をインターネットから探し、.terraform
ディレクトリ内にインストールします。 - バックエンドへの接続:
providers.tf
ファイルに記述された情報を基にS3バケットへ接続し、.tfstate
ファイルを管理する準備を整えます。
environments/development
ディレクトリへ移動し、コマンドを実行しました。
1 2 |
$ cd environments/development # 作業ディレクトリへ移動 $ terraform init |
コマンドを実行してしばらく待つと、以下のような成功メッセージが表示されます。
1 |
Terraform has been successfully initialized! ✨ |
S3 bucket does not exist
エラー ❗️
私はterraform init
の過程でS3バケット名を間違えて入力してしまい、このエラーに遭遇しました!もし同様のエラーが発生した場合は、設定ファイルにタイポがないか確認してみてください。
terraform init
が成功すると、現在のディレクトリに.terraform
という隠しディレクトリが作成されます。ターミナルでtree
コマンドを使って確認すると、設定通りにAWS Providerが正しくダウンロードされていることが確認できます。
1 2 3 |
$ ls -la environments/development .terraform # .terraform ディレクトリが作成されたことを確認 |
1 2 3 4 5 6 7 8 9 10 11 12 |
$ tree .terraform # .terraform ディレクトリの内部構造を確認 .terraform ├── providers │ └── registry.terraform.io │ └── hashicorp │ └── aws │ └── 6.7.0 # Providerがインストールされたことを確認! │ └── darwin_arm64 │ ├── LICENSE.txt │ └── terraform-provider-aws_v6.7.0_x5 └── terraform.tfstate # S3状態ファイル |
2. EC2モジュールの作成
環境構築が完了したので、いよいよ本格的にEC2インスタンスを作成する時間です!
Terraformでインスタンスを管理する際、すべてのコードを一つのファイルに記述することも可能です。しかし、それではメンテナンスが困難になるため、機能ごとに細かく分割し、モジュール(Module)として管理します。こうすることで、柔軟性と再利用性が大幅に向上します。
そこで、modules/instance
ディレクトリを作成し、その中に必要なモジュールを作成しました。
a. モジュールの設計 (variables.tf)
私たちがレゴブロックを作る時、「どのブロックをどうやって繋げるか」を考えなければなりません。その際、組み立て説明書を参考にすれば、必要な部品と組み立ての順番が分かりますよね。
Terraformのvariables.tf
ファイルが、まさにその「必要な部品リスト」の役割を果たします。このファイルを通じて、EC2インスタンスを作成する際に必要な情報(部品)を明確に伝えることができます。
modules/instance
ディレクトリ内に、以下のようにvariables.tf
ファイルを作成しました。
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 |
# /modules/instance/variables.tf # EC2インスタンスに必要な変数(情報)リスト variable "instance_name_tag" { # インスタンスのNameタグ type = string # データ型 description = "EC2インスタンスのNameタグに使用する値" # 説明 } variable "iam_role_name" { type = string description = "インスタンスにアタッチするIAMロール名" } variable "subnet_id" { type = string description = "インスタンスを配置するサブネットのID" } variable "vpc_security_group_ids" { type = list(string) description = "インスタンスに適用するセキュリティグループIDのリスト" } variable "instance_type" { type = string description = "EC2インスタンスのタイプ" default = "t4g.nano" # デフォルト値を定義 } |
b. モジュールの組み立て (ec2.tf)
ec2.tf
ファイルはTerraformの組み立てプロセスに該当します。variables.tf
で定義した「必要な部品」を使って、EC2インスタンスという成果物を作成するところです。
modules/instance
ディレクトリ内にec2.tf
ファイルを作成し、EC2インスタンスを定義するコードを記述しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# /modules/instance/ec2.tf # 1. EC2インスタンスにインストールするOS data "aws_ami" "amazon_linux_2023" { most_recent = true # 最新バージョンのAMI owners = ["amazon"] # Amazonが提供する公式AMI # 必要なAMIでフィルタリング filter { name = "name" values = ["al2023-ami-2023.*-kernel-*-arm64"] # Amazon Linux 2023 ARM版 } filter { name = "architecture" values = ["arm64"] } } |
data "aws_ami"
: EC2インスタンスを作成する際にはAMI IDが毎回必要です。しかし、このIDは頻繁に変更されるため、その都度修正するのは手間がかかります。そのような場合にdata
ブロックを使用すると、設定した条件(最新版、Amazon Linux 2023など)に合ったAMIを自動で検索してくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# /modules/instance/ec2.tf 続き... # 2. 上記で検索したOSとvariables.tfの部品を使い、EC2インスタンスを作成します。 resource "aws_instance" "main" { ami = data.aws_ami.amazon_linux_2023.id # AMIを設定 instance_type = var.instance_type # インスタンスタイプを設定 subnet_id = var.subnet_id # ネットワークの場所(Subnet)を設定 vpc_security_group_ids = var.vpc_security_group_ids # セキュリティグループを設定 iam_instance_profile = aws_iam_instance_profile.main.name # IAM Role(権限リスト)を設定 # サーバー初回起動時に自動で実行されるスクリプト (PostgreSQLクライアントのインストール) user_data = <<-EOF #!/bin/bash sudo dnf update -y sudo dnf install postgresql16 -y EOF # インスタンスのネームタグ(Name Tag)を設定 tags = { Name = var.instance_name_tag } } |
user_data
: インスタンスの初回起動時に一度だけ実行されるスクリプトです。ここにプログラムのインストールスクリプトなどを記述しておけば、サーバー作成と同時に必要なプログラムが自動でインストールされるため便利です。
c. 権限の追加 (iam.tf)
新しく作成したEC2インスタンスが他のAWSサービス(S3、DBなど)にアクセスするには、権限(IAM Role)が必要です。
IAM関連のコードも、可読性と役割の分離のためにiam.tf
ファイルで管理します。
1 2 3 4 5 6 |
# /modules/instance/iam.tf resource "aws_iam_instance_profile" "main" { name = "${var.instance_name_tag}-profile" role = var.iam_role_name } |
d. 結果の出力 (outputs.tf)
プログラミングで関数がreturn
を通じて結果の値を返すように、Terraformのモジュールもoutputs.tf
を通じて、参照できるように値を返すことができます。
environments/development
でモジュールを使用するコードがインスタンス情報を活用できるよう、modules/instance
ディレクトリ内にoutputs.tf
ファイルを作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 |
# /modules/instance/outputs.tf # 作成されたEC2インスタンスのIDとプライベートIPアドレスを返します。 output "instance_id" { value = aws_instance.main.id # 返す値 description = "作成されたEC2インスタンスのID" # 説明 } output "private_ip" { value = aws_instance.main.private_ip description = "作成されたEC2インスタンスのプライベートIPアドレス" } |
これで入力(variables.tf
)、組み立て(ec2.tf
, iam.tf
)、そして出力(outputs.tf
)まで、Terraformに必要な設計が完了しました!
次のステップでは、この設計を基にEC2インスタンスを作成してみましょう!
3. EC2インスタンスの作成
ここまででmodules/instance
というディレクトリ内に、再利用可能なEC2インスタンスのモジュールを作成しました。これからは、ステップ1で準備したenvironments/development
に戻り、実際に組み立てて作成する番です。
a. モジュールの呼び出し
instance
モジュールを呼び出し、必要なデータを渡すために、environments/development
ディレクトリでmain.tf
ファイルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# /environments/development/main.tf # 'instance'モジュールを'terraform_ec2'という名前で呼び出します。 module "terraform_ec2" { # モジュールのパス source = "../../modules/instance" # インスタンスのネームタグ instance_name_tag = "terraform-instance" # IAMロール iam_role_name = "instance-role" # インスタンスタイプ instance_type = "t4g.nano" # ネットワークの場所(Subnet) subnet_id = "subnet-01e..." # 実際の環境に合った値に変更してください # セキュリティグループ vpc_security_group_ids = ["sg-0fe...", "sg-0e3..."] # 実際の環境に合った値に変更してください } |
source
でモジュールのパスを指定し、その下にvariables.tf
で定義した実際の値を一つずつ記述していきます。
b. デプロイと実行
これで設計と組み立ての準備がすべて完了しました。これからは、実際にAWS上でEC2インスタンスを作成する作業だけが残っています!!
まずterraform plan
コマンドで、どのリソースが作成(+
)、変更(~
)、**削除(-
)**されるのかを事前に確認します。
environments/development
ディレクトリで、以下のコマンドを実行してみましょう。
1 |
$ terraform plan |
すると、Terraformがコードを分析し、これからAWSでどのような作業が実行されるのか、その計画(plan)を表示してくれます。
1 2 3 4 |
Plan: 2 to add, 0 to change, 0 to destroy. + resource "aws_iam_instance_profile" "main" { ... } + resource "aws_instance" "main" { ... } |
計画を見ると、IAMプロファイル1つとEC2インスタンス1つが新しく作成される予定であることが分かりますね!
最終的な計画に問題がなければ、terraform apply
コマンドで実際のデプロイを開始します。
1 |
$ terraform apply |
このコマンドを実行すると、本当に作業を実行するのか、最後の確認を求められます。
1 2 3 4 5 |
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 |
最後にyes
を入力すると、TerraformがAWSと通信してインスタンスの作成を開始します!
作業が完了すると、このようなメッセージが表示されます。
1 |
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. |

AWSコンソールで確認すると、このようにコードのみでEC2インスタンスが作成されたことを確認できます!
4. 最後に
今回の学び
今回の初めてのTerraformプロジェクトを通じて、単にEC2インスタンスをコードで作成する方法だけでなく、インフラを扱う上での実務的な視点を学ぶことができました。
- コードでインフラを管理する理由: マウスクリックではなくコードでインフラを管理することで、再利用可能な「部品(
Modules
)」とそれを組み立てる「空間(Environments
)」を分離し、コードの重複を防ぎ、柔軟性を大幅に向上させることができました。 - リモートでの状態管理:
terraform.tfstate
ファイルをS3に保存することで、チームメンバー全員が同じ状態を基準に作業できる環境を構築できることを学びました。
学生の皆さんへ
私はAWSの経験がほとんどなく、Terraformというツールも名前を聞いたことがある程度でした。しかし、今回プロジェクトを終えてブログとして内容をまとめる過程で、達成感と共に、これからさらに多くの技術に挑戦したいという自信を得ることができました。
私がこのように感じることができたのも、ニフティの新卒向けの体系的な研修と、成長を奨励する文化のおかげだと考えています。今ではAWSやインフラ、バックエンド技術にも挑戦してみたいという目標ができました。
ニフティでは、入社後1ヶ月間の導入研修に始まり、エンジニアの同期たちと共にプロジェクトを進める技術研修、そして3ヶ月ごとに3つの異なるチームを経験しながら先輩たちと実務を学ぶ9ヶ月間のOJTまで、約1年間にわたって新卒が会社に慣れ、成長できるよう体系的なプログラムが整っています。
この記事を読んでくださっている皆さんも、ニフティで私たちと一緒に学び、成長する仲間になれることを願っています。どうぞ多くの関心をお寄せいただければ幸いです 😉
次は、石田さんの出番です。
派手な記事が楽しみです ✨