はじめに
最近は趣味でAWSをちょくちょく触るようになりましたが、構築時に毎回何かしらミスしてしまい、手戻りが多いです。そこで、名前はよく聞くけど触ったことがなかったterraformを使ってみようと思い、まずはチュートリアルをやってみようと思います。
terraformとは
IaCツールの一つです。
手続的な手順を記述するのではなく、宣言的に最終的な姿を記述することでインフラを構築します。現在との差分から必要な手順はterraform側でやってくれるらしいです。
チュートリアルやってみる
というわけでAWS版のチュートリアルをやってみます。
terraformのインストール
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
インストールできれば準備OKです。
任意のディレクトリを作成してプロジェクトを作成していきましょう。
terraform.tf:利用ライブラリの定義
まずは利用するライブラリを定義します。
terraform本体だけでなんでもできるというわけではなく、デプロイ先のベンダーに合わせて必要なライブラリをダウンロードして使うようです。
AWSの場合、公式のhashicorp/awsというライブラリを利用します。
利用するライブラリはterraform.tfというファイルに記述します。
このファイルはPHPでいうcomposer.jsonみたいなイメージのようです。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.92"
}
}
required_version = ">= 1.2"
}
main.tf:インフラ構成をコードで書く
続いてインフラ構成をコードで記述していきましょう。
チュートリアルでは以下のようにmain.tfを作成しました。
これはEC2インスタンスを一つ作成するコードになります。
# 構築するAWSの情報や、構築に利用するprofileを定義
provider "aws" {
region = "ap-northeast-1"
profile = "learn-terraform"
}
# dataには構築で利用する情報を定義する
# のちにEC2インスタンスを立てる際に使用するAMIを検索し、IDなどを取得してする
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
# AMIの提供元のIDを正確に記述。間違えるとよくわからない提供元のAMIを使うことになる。
owners = ["099720109477"] # Canonical
}
# resourceには実際に作成するインフラを記述
# 第一引数の"aws_instance"は作成する対象のサービス。この場合はEC2インスタンス。
# 第二引数の"app_server"は識別子で、他から参照する際に利用する。
resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
tags = {
Name = "learn-terraform"
}
}
profileにはaws_cliのプロファイルを記述しています。
このプロファイルを使って構築していくため、構築に必要な権限を予め付与しておく必要があります。
terraformコマンドでデプロイ
ここまで書けたら実際にデプロイしていきましょう。
まずはinitで必要なライブラリなどをダウンロードしてきます。
$ terraform init
続いて、validateです。
構文エラーの有無を確認します。
$ terraform validate
Success! The configuration is valid.
続いて、terraform planで構築のdry-runをします。
構築前に作成予定の一覧を確認することができます。
$ terraform plan
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-0879aeb9a3801dce6]
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_instance.app_server will be created
+ resource "aws_instance" "app_server" {
+ ami = "ami-0879aeb9a3801dce6"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ enable_primary_ipv6 = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_lifecycle = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ spot_instance_request_id = (known after apply)
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "learn-terraform"
}
+ tags_all = {
+ "Name" = "learn-terraform"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
+ capacity_reservation_specification (known after apply)
+ cpu_options (known after apply)
+ ebs_block_device (known after apply)
+ enclave_options (known after apply)
+ ephemeral_block_device (known after apply)
+ instance_market_options (known after apply)
+ maintenance_options (known after apply)
+ metadata_options (known after apply)
+ network_interface (known after apply)
+ private_dns_name_options (known after apply)
+ root_block_device (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take
exactly these actions if you run "terraform apply" now.
そして、applyで構築です。
これで構築ができます。(今回はエラーになってしまいましたが、、、)
$ terraform apply
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 1s [id=ami-0879aeb9a3801dce6]
aws_instance.app_server: Creating...
╷
│ Error: creating EC2 Instance: operation error EC2: RunInstances, https response error StatusCode: 400, RequestID: , api error VPCIdNotSpecified: No default VPC for this user. GroupName is only supported for EC2-Classic and default VPC.
│
│ with aws_instance.app_server,
│ on main.tf line 18, in resource "aws_instance" "app_server":
│ 18: resource "aws_instance" "app_server" {
どうやらデフォルトのVPCなどが設定されていないことでどこに作ったらいいかわからずエラーとなってしまったようです。
というわけで、チュートリアルではないですが、VPCやサブネット、ルートテーブル、セキュリティグループまで作成するコードをClaudeに手伝ってもらいながら書いて、それをデプロイしてみました。
全部作るコード
provider "aws" {
region = "ap-northeast-1"
profile = "learn-terraform"
allowed_account_ids = ["223417453566"]
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.app_server.id]
tags = {
Name = "learn-terraform"
}
}
# セキュリティグループ。インスタンス単位のファイアウォール
resource "aws_security_group" "app_server" {
name = "learn-terraform-app-server"
description = "Allow all outbound traffic, no inbound"
vpc_id = aws_vpc.main.id
# 外向きの通信は全許可(パッケージ更新などに必要)
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "learn-terraform-app-server"
}
}
# 利用可能なアベイラビリティゾーンを取得する
data "aws_availability_zones" "available" {
state = "available"
}
# VPC 本体。この中に閉じた仮想ネットワークが作られる
resource "aws_vpc" "main" {
cidr_block = "10.20.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "learn-terraform-vpc"
}
}
# インターネットゲートウェイ。VPC を外のインターネットに繋ぐ出入口
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "learn-terraform-igw"
}
}
# パブリックサブネット。ここに置いた EC2 はパブリック IP を持つ
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.20.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "learn-terraform-public"
}
}
# ルートテーブル。VPC 外宛ての通信をインターネットゲートウェイに流す
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "learn-terraform-public-rt"
}
}
# ルートテーブルをサブネットに紐付けて、初めて「パブリック」サブネットになる
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
applyしてデプロイ後に以下のように出れば完了です。
$ terraform apply
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
後片付け
作成後は後片付けしましょう。
destroyコマンドで削除ができるようです。
$ terraform destroy
Destroy complete! Resources: 7 destroyed.
おわりに
今回はterraformを使ってAWSのリソース作成を自動化することを試してみました。
コンソール上で作成するより簡単に、ミスなく作ることができるのは非常に体験が良いです。
実運用する際の勘所などは全然わかっていないですが、今後もこのような感じで作成してみようと思います。
ここまでご覧いただきありがとうございました!