宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
「AWSのインフラ構築、毎回ポチポチとコンソールを操作していて、設定ミスや環境差異が怖い」
「Terraformを使えばインフラをコードで管理できると聞いたが、何から始めればいいのかわからない」

こういった悩みを持つインフラエンジニアは多いでしょう。AWSコンソールでの手作業は、チームで運用するほどに属人化・設定漏れ・環境間の差異という問題を引き起こします。

この記事では、Infrastructure as Code(IaC)の代表格であるTerraformの概念から、HCL構文・状態管理・モジュール設計、そしてAWSでVPCとEC2をコード化するハンズオンまでを体系的に解説します。「なぜTerraformを使うのか」「どう設計するか」「よくあるトラブルをどう乗り越えるか」という3つの軸で、実務で使えるレベルまで踏み込んで説明します。

この記事のポイント

・TerraformはHCLでAWSインフラを宣言的にコード化できるIaCツール
・tfstateで状態を管理し、S3+DynamoDBでチーム共有するのが実務の基本
・モジュール設計により環境差異(dev/stg/prd)を安全に管理できる
・plan/apply失敗の多くは依存関係・権限・tfstate不整合が原因


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

Terraformとは何か — IaCの概念とインフラ自動化の必要性

Terraform(テラフォーム)は、HashiCorp社が開発したオープンソースのInfrastructure as Code(IaC)ツールです。AWSやAzure、GCPといったクラウドプロバイダーのリソースを、HCL(HashiCorp Configuration Language)というコードで定義・管理できます。

「インフラをコードで管理する」とはどういうことか、まず従来の手作業との違いを整理します。

1. 手作業管理の問題点

AWSコンソールでの手動構築には、次のような問題が繰り返し発生します。

再現性がない:「先月と同じ設定で作った」はずが細部が違う
属人化:担当者しか設定内容を把握していない
環境間の差異:開発環境と本番環境で設定がいつの間にかズレる
変更履歴がない:「いつ誰がどの設定を変えたか」が追えない

10台、20台規模になると、これらの問題は致命的になります。本番障害の調査で「手動変更の痕跡が見つからない」という事態は、インフラ管理者にとって最も避けたいシナリオです。

2. IaCで何が変わるか

Terraformでインフラを管理すると、次のことが実現します。

宣言的定義:「あるべき状態」をコードに書けば、Terraformが現在との差分を計算して適用する
バージョン管理:Gitでインフラ変更をコミット・レビューできる
冪等性(べきとうせい):何度実行しても同じ結果になる(2回目は「変更なし」と判断される)
マルチクラウド対応:AWSだけでなくAzure、GCP、Cloudflareなど300以上のプロバイダーに対応

特に「宣言的定義」と「冪等性」の組み合わせは強力です。Ansible(手続き的)と比較しながら、次のセクションで詳しく見ていきます。

Terraform vs Ansible — 役割の違いと使い分け

「TerraformとAnsible、どちらを使えばいいか」という質問は、IaCを学び始めた方が必ずぶつかる疑問です。結論から言うと、これは「どちらか一方」ではなく「組み合わせて使う」ものです。

観点 Terraform Ansible
主な用途 インフラのプロビジョニング(作成・変更・削除) OSレベルの設定・ソフトウェアインストール
記述スタイル 宣言的(あるべき状態を書く) 手続き的(実行する手順を書く)
状態管理 tfstateファイルで管理(差分計算が可能) 状態を持たない(毎回フルスキャンまたは条件分岐)
得意領域 VPC・EC2・RDS・S3などAWSリソースの構築 Webサーバー設定・パッケージ管理・ファイル配置
エージェント 不要(APIベース) 不要(SSHベース)

3. 実務での使い分け方

実際の現場では、次の役割分担が定着しています。

Terraform:EC2インスタンスを作成し、セキュリティグループ・VPC・IAMロールを設定する(インフラ層)
Ansible:作成されたEC2の中にNginxをインストールし、設定ファイルを配置して起動する(OS・ミドルウェア層)

「箱を用意するのがTerraform、箱の中身を整えるのがAnsible」というイメージが最も直感的です。

なお、EC2のプロビジョニング後の初期設定はTerraformの `user_data` を使う方法もありますが、設定が複雑になるほどAnsibleに任せた方がコードの見通しが良くなります。

HCLの基本構文 — リソース/変数/outputsを理解する

TerraformはHCL(HashiCorp Configuration Language)で記述します。JSONとも互換性がありますが、可読性が高いHCLを使うのが基本です。

4. プロバイダーの定義

最初に、どのクラウドに対して操作するかを `provider` ブロックで宣言します。

# main.tf terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "ap-northeast-1" # 東京リージョン }

5. resourceブロック — AWSリソースの定義

`resource` ブロックがTerraformの基本単位です。「何を(リソースタイプ)」「どんな名前で(ローカル名)」作るかを宣言します。

# EC2インスタンスを定義する例 resource "aws_instance" "web_server" { ami = "ami-0d52744d6551d851e" # Amazon Linux 2023 (ap-northeast-1) instance_type = "t3.micro" tags = { Name = "web-server" Env = "dev" } }

リソースタイプは `aws_instance`(EC2)、`aws_vpc`(VPC)、`aws_s3_bucket`(S3)のように `プロバイダー名_リソース名` の形式になっています。

6. variablesブロック — 変数で柔軟性を持たせる

ハードコーディングを避けるために `variable` ブロックを使います。

# variables.tf variable "env" { description = "環境名 (dev/stg/prd)" type = string default = "dev" } variable "instance_type" { description = "EC2インスタンスタイプ" type = string default = "t3.micro" } # main.tfでの参照 resource "aws_instance" "web_server" { instance_type = var.instance_type tags = { Env = var.env } }

7. outputsブロック — 作成後の値を取り出す

`output` ブロックを使うと、作成したリソースの属性(IPアドレスなど)を `terraform output` コマンドで確認したり、他のモジュールに渡したりできます。

# outputs.tf output "instance_public_ip" { description = "EC2インスタンスのパブリックIP" value = aws_instance.web_server.public_ip } # 実行後に確認する # $ terraform output instance_public_ip # "54.xxx.xxx.xxx"

tfstateとbackend — 状態管理の仕組みと共有設計

Terraformの核心的な概念が `terraform.tfstate`(以下、tfstate)です。「現在のインフラの状態」を記録するJSONファイルで、Terraformはこのtfstateと実際のAWSリソースの差分を計算して、何を変更すべきかを判断します。

8. tfstateがなぜ重要か

例えば `terraform plan` を実行すると、次の比較が行われます。

HCLファイル(あるべき状態) vs tfstate(前回の適用状態)
tfstate(前回の適用状態) vs 実際のAWSリソース(現在の状態)

この2段階の比較により、「HCLに書いてあるが作成されていない」「tfstateにあるがAWSから削除された」「手動で変更されてtfstateと乖離している」といったケースを検知します。

9. ローカルtfstateの問題とbackendの必要性

デフォルトではtfstateはローカルの `terraform.tfstate` ファイルに保存されます。個人での学習には問題ありませんが、チームで運用するには致命的な問題があります。

競合:複数人が同時に `terraform apply` を実行するとtfstateが壊れる
消失リスク:開発者のPCにtfstateが存在し、PCの故障で失われる
機密情報:tfstateにはパスワードやキーが平文で含まれることがある

これを解決するのが `backend` の設定です。チームでの実務では、S3をbackendにしてDynamoDBでロック(排他制御)するパターンが標準です。

# backend.tf terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "dev/terraform.tfstate" region = "ap-northeast-1" dynamodb_table = "terraform-state-lock" # ロック用テーブル encrypt = true # 暗号化を必ず有効化 } }

10. backendの初期設定

backendを変更した後、または初めてbackendを設定した後は必ず `terraform init` を再実行します。

# backend設定後は必ずinitを実行 $ terraform init # ローカルtfstateをS3へ移行するか確認される # "Do you want to copy existing state to the new backend?" → yes

モジュール設計 — 再利用可能なインフラコードの作り方

本番レベルのTerraformプロジェクトでは、コードをそのままベタ書きにはしません。「モジュール」という仕組みを使って、再利用可能なコンポーネントに分割します。

11. モジュールとは何か

モジュールは「関連するTerraformファイルをまとめたディレクトリ」です。例えばVPCの設定をモジュール化しておけば、開発環境・ステージング環境・本番環境それぞれで同じモジュールを呼び出し、変数だけを変えてインフラを展開できます。

12. 推奨ディレクトリ構成

実務でよく使われる構成は次の通りです。

terraform-project/ ├── modules/ # 再利用可能なモジュール群 │ ├── vpc/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── outputs.tf │ └── ec2/ │ ├── main.tf │ ├── variables.tf │ └── outputs.tf ├── environments/ # 環境ごとの設定 │ ├── dev/ │ │ ├── main.tf # モジュールの呼び出しと変数の注入 │ │ └── backend.tf │ └── prd/ │ ├── main.tf │ └── backend.tf

13. モジュールの呼び出し方

環境ディレクトリから `module` ブロックでモジュールを呼び出します。

# environments/dev/main.tf module "vpc" { source = "../../modules/vpc" env = "dev" cidr_block = "10.0.0.0/16" } module "ec2" { source = "../../modules/ec2" env = "dev" vpc_id = module.vpc.vpc_id # VPCモジュールのoutputを参照 instance_type = "t3.micro" }

本番環境の `environments/prd/main.tf` では `instance_type = "t3.large"` などと変数を変えるだけで、同じインフラ構成を安全に展開できます。

Terraformをより深く体系的に学びたい方には、リナックスマスターのTerraform実践講座がおすすめです。ハンズオン形式でAWSインフラのコード化を一から習得できます。

AWSでのハンズオン — VPC・EC2を実際にコード化する

ここからは実際にAWSリソースをTerraformでコード化する手順を見ていきます。前提として、AWS CLIの設定(`aws configure`)が完了していることを確認してください。

14. 環境確認とinitの実行

# Terraformのバージョン確認 $ terraform version Terraform v1.8.5 on linux_amd64 # AWS CLIの認証確認 $ aws sts get-caller-identity { "UserId": "AIDAXXXXXXXXXXXXXXXXX", "Account": "123456789012", "Arn": "arn:aws:iam::123456789012:user/terraform-user" } # プロジェクトディレクトリで初期化 $ terraform init Initializing the backend... Initializing provider plugins... - Finding hashicorp/aws versions matching "~> 5.0"... - Installing hashicorp/aws v5.54.1... Terraform has been successfully initialized!

15. VPCとサブネットの定義

実務で最小限必要なVPC構成(VPC本体・パブリックサブネット・インターネットゲートウェイ)をコード化します。

# vpc.tf # VPC本体 resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true tags = { Name = "main-vpc" Env = var.env } } # パブリックサブネット(ap-northeast-1a) resource "aws_subnet" "public_1a" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-1a" map_public_ip_on_launch = true tags = { Name = "public-subnet-1a" Env = var.env } } # インターネットゲートウェイ resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id tags = { Name = "main-igw" Env = var.env } } # ルートテーブル(パブリックサブネット用) 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 = "public-rt" Env = var.env } } # サブネットとルートテーブルの関連付け resource "aws_route_table_association" "public_1a" { subnet_id = aws_subnet.public_1a.id route_table_id = aws_route_table.public.id }

16. セキュリティグループとEC2の定義

# ec2.tf # セキュリティグループ resource "aws_security_group" "web" { name = "web-sg" description = "Allow HTTP and SSH" vpc_id = aws_vpc.main.id ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["xxx.xxx.xxx.xxx/32"] # 自分のIPのみ許可 } ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "web-sg" Env = var.env } } # EC2インスタンス resource "aws_instance" "web" { ami = "ami-0d52744d6551d851e" # Amazon Linux 2023 instance_type = var.instance_type subnet_id = aws_subnet.public_1a.id vpc_security_group_ids = [aws_security_group.web.id] key_name = "my-key-pair" # 事前にAWSで作成したキーペア名 tags = { Name = "web-server" Env = var.env } }

17. planで差分確認 → applyで適用

# 差分の確認(AWSへの変更は行われない) $ terraform plan Terraform will perform the following actions: # aws_instance.web will be created + resource "aws_instance" "web" { + ami = "ami-0d52744d6551d851e" + instance_type = "t3.micro" ... } Plan: 6 to add, 0 to change, 0 to destroy. # 問題なければ適用 $ terraform 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_vpc.main: Creating... aws_vpc.main: Creation complete after 2s [id=vpc-0a1b2c3d4e5f6789] ... Apply complete! Resources: 6 added, 0 changed, 0 destroyed. Outputs: instance_public_ip = "54.xxx.xxx.xxx"

EC2が作成されたら、SSH接続でリモートから操作できる状態になります。Linux ポート確認の全コマンドを参照して、EC2上でポートが正しく開いているか確認するとよいでしょう。

18. リソースの削除

学習環境で作成したリソースは忘れずに削除します。料金が発生し続けるためです。

# 全リソースの削除(本番環境では絶対に間違えないこと) $ terraform destroy Plan: 0 to add, 0 to change, 6 to destroy. Do you really want to destroy all resources? Enter a value: yes Destroy complete! Resources: 6 destroyed.

よくあるトラブルと対処 — plan/apply失敗パターン

Terraformを使い始めて必ずぶつかるエラーとその対処法を整理します。エラーメッセージを冷静に読めば、ほとんどのケースで原因は特定できます。

19. 「Error: No valid credential sources found」— 認証エラー

AWSの認証情報が見つからない場合に発生します。

# 対処: AWS CLIの認証設定を確認 $ aws configure list Name Value Type Location ---- ----- ---- -------- profile None None access_key ****************XXXX shared-credentials-file secret_key ****************XXXX shared-credentials-file region ap-northeast-1 config-file ~/.aws/config # 認証情報が未設定の場合 $ aws configure AWS Access Key ID [None]: AKIAXXXXXXXXXXXXXXXXX AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Default region name [None]: ap-northeast-1

20. 「Error: Error creating VPC: VpcLimitExceeded」— リソース上限エラー

AWSアカウントのデフォルトVPC上限(5個)に達している場合に発生します。

対処1:使っていないVPCをAWSコンソールで削除する
対処2:AWSサポートにVPC上限の引き上げをリクエストする

21. 「Error: Terraform state is locked」— tfstateロックエラー

S3 backendを使用中に、前の `terraform apply` が異常終了してDynamoDBのロックが残ってしまった場合に発生します。

# エラーメッセージ例 Error: Error locking state: Error acquiring the state lock # 対処: ロックを強制解除(前のapplyが本当に完了または失敗したことを確認してから実行) $ terraform force-unlock LOCK_ID # LOCK_IDはエラーメッセージ中に表示される # 例: "ID: 12345678-1234-1234-1234-123456789012"

22. 「Error: Reference to undeclared resource」— 参照エラー

他のリソースを参照する際に、リソース名をタイプミスした場合に発生します。

# NG例: リソース名が一致していない resource "aws_instance" "web_server" { subnet_id = aws_subnet.public.id # "public"というサブネットは存在しない } # OK例: 定義したリソース名と一致させる resource "aws_subnet" "public_1a" { ... } resource "aws_instance" "web_server" { subnet_id = aws_subnet.public_1a.id # 正しく参照 }

23. tfstateと実リソースの乖離(ドリフト)

誰かがコンソールから手動でリソースを変更した場合、tfstateと実際のAWSリソースに差異(ドリフト)が生じます。

# 実リソースの現状をtfstateに取り込む(plan前に状態を同期) $ terraform refresh # または planで差分を確認(refreshが自動で行われる) $ terraform plan

実務では「コンソールからの手動変更禁止」をルール化することが、このトラブルの最大の予防策です。Linux DNS 設定の基本のように、サーバー設定も可能な限りコードで管理する習慣が重要です。

まとめ — 学習ロードマップと次のステップ

この記事で解説した内容を整理します。

テーマ ポイント
Terraformとは HCLでAWSリソースを宣言的に定義するIaCツール。冪等性と差分管理が強み
Terraform vs Ansible Terraformはインフラ層(VPC/EC2作成)、AnsibleはOS層(設定/ソフトウェア)で役割分担
HCL基本構文 resource/variable/outputsの3ブロックが中心。変数で環境差異を吸収する
tfstateとbackend チーム開発ではS3+DynamoDBをbackendに設定してtfstateを共有・ロック管理する
モジュール設計 modules/に再利用コンポーネントを置き、environments/から呼び出す構成が標準
AWSハンズオン VPC・サブネット・IGW・SG・EC2の順で定義し、terraform plan→applyで適用
よくあるトラブル 認証エラー・リソース上限・stateロック・ドリフトの4パターンを押さえる

Terraformを習得するロードマップとして、次の順番で学習を進めることをお勧めします。

Step 1:公式ドキュメントのGet Startedでローカルに慣れる(tfstateがどこに作られるか確認)
Step 2:この記事のハンズオンをそのまま試す(VPC+EC2を実際に作って、destroyする)
Step 3:S3 backendとDynamoDBロックを設定し、チーム共有できる状態を作る
Step 4:モジュール分割を試す(dev/prd環境の同時管理)
Step 5:GitHub ActionsでCI/CDパイプラインに組み込む(plan結果をPRコメントに出す)

Linuxの基本操作が不安な方は、Linux 基本コマンドの解説で土台を固めてから取り組むと、Terraform学習がスムーズになります。

Terraformをより深く体系的に学びたい方には、リナックスマスターのTerraform実践講座がおすすめです。ハンズオン形式でAWSインフラのコード化を一から習得できます。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。


  • この記事の属するカテゴリ:Terraformへ戻る