「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不整合が原因
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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インフラのコード化を一から習得できます。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます