Terraform
DevOpsツール
Terraform
概要
TerraformはHashiCorpが開発するオープンソースのInfrastructure as Code(IaC)ツールです。HCL(HashiCorp Configuration Language)という宣言的な設定言語を使用し、AWS、Azure、Google Cloud、Kubernetesなど複数のクラウドプロバイダーやサービスでインフラリソースを統一的に管理できます。
詳細
Terraformは2014年にHashiCorpによって開発され、現在IaC分野で最も広く使用されているツールです(市場シェア32.8%)。HCL(HashiCorp Configuration Language)と呼ばれる独自の宣言的言語を使用し、コードでインフラを定義します。1,200以上のプロバイダーが利用可能で、AWS、Azure、GCP、Kubernetes、DatadogからGitHubまで、あらゆるサービスを統一的に管理できます。
Terraformの主な特徴は、「宣言的」なアプローチです。「何を作りたいか」を記述すると、Terraformが現在の状態と比較して必要な変更を自動的に計画・実行します。ステート管理機能により、実際のインフラとコードの同期を保ち、チームでの協業を支援します。
プランニング機能により、実際に変更を適用する前に「何が変更されるか」を確認でき、安全なインフラ運用が可能です。また、豊富なモジュールエコシステムにより、再利用可能なインフラパターンを簡単に利用できます。
メリット・デメリット
メリット
- マルチクラウド対応: 1つのツールで複数のクラウドプロバイダーを統一管理
- 豊富なプロバイダー: 1,200以上のプロバイダーで幅広いサービスに対応
- プランニング機能: 変更前に影響を確認できるため安全
- 大規模なコミュニティ: 豊富なモジュールとドキュメント
- 状態管理: インフラの現在状態を追跡し、設定と実際の差分を管理
デメリット
- 学習コスト: HCLの習得が必要で、初心者には敷居が高い
- 状態ファイル管理: ステートファイルの管理が複雑で、チーム開発時に課題
- プロバイダー依存: プロバイダーの更新遅延により最新機能が使えない場合
- ロールバック制限: 設定のロールバックが自動化されていない
参考ページ
公式リソース
- Terraform公式サイト - HashiCorp公式サイト
- Terraform Documentation - 公式ドキュメント
- Terraform Registry - プロバイダーとモジュール検索
- Terraform GitHub - ソースコード
学習リソース
- Learn Terraform - 公式チュートリアル
- Terraform Best Practices - Google Cloud ベストプラクティス
- Terraform AWS Examples - AWS向けモジュール集
書き方の例
基本的なリソース定義
# provider.tf - プロバイダー設定
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.0"
}
provider "aws" {
region = var.aws_region
}
# variables.tf - 変数定義
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "ap-northeast-1"
}
variable "environment" {
description = "Environment name"
type = string
default = "development"
}
VPCとサブネット作成
# vpc.tf - VPCとネットワーク設定
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
Type = "Public"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
# データソース - AZ一覧取得
data "aws_availability_zones" "available" {
state = "available"
}
EC2インスタンス作成
# ec2.tf - EC2インスタンス定義
resource "aws_security_group" "web" {
name_prefix = "${var.environment}-web-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
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 = "${var.environment}-web-sg"
}
}
resource "aws_instance" "web" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public[count.index].id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(<<-EOF
#!/bin/bash
apt update
apt install -y nginx
systemctl start nginx
systemctl enable nginx
echo "<h1>Web Server ${count.index + 1}</h1>" > /var/www/html/index.html
EOF
)
tags = {
Name = "${var.environment}-web-${count.index + 1}"
}
}
# 最新のUbuntu AMI取得
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
}
}
ロードバランサー設定
# alb.tf - Application Load Balancer
resource "aws_lb" "main" {
name = "${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
tags = {
Environment = var.environment
}
}
resource "aws_security_group" "alb" {
name_prefix = "${var.environment}-alb-"
vpc_id = aws_vpc.main.id
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"]
}
}
resource "aws_lb_target_group" "web" {
name = "${var.environment}-web-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
interval = 30
path = "/"
matcher = "200"
}
}
resource "aws_lb_target_group_attachment" "web" {
count = length(aws_instance.web)
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web[count.index].id
port = 80
}
resource "aws_lb_listener" "web" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
状態管理(リモートバックエンド)
# backend.tf - S3バックエンド設定
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "infrastructure/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
# S3バケットとDynamoDBテーブル作成
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
モジュール作成と利用
# modules/vpc/main.tf - VPCモジュール
variable "environment" {
description = "Environment name"
type = string
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
}
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.this.cidr_block
}
# main.tf - モジュール利用
module "vpc" {
source = "./modules/vpc"
environment = var.environment
vpc_cidr = "10.0.0.0/16"
}
module "production_vpc" {
source = "./modules/vpc"
environment = "production"
vpc_cidr = "10.1.0.0/16"
}
Terraform基本コマンド
# 初期化(プロバイダーダウンロード)
terraform init
# フォーマット(コード整形)
terraform fmt
# 検証(構文チェック)
terraform validate
# プラン(変更内容確認)
terraform plan
# 適用(インフラ作成・更新)
terraform apply
# 特定リソースのみ適用
terraform apply -target=aws_instance.web
# 破棄(リソース削除)
terraform destroy
# 状態確認
terraform show
# リソース一覧
terraform state list
# 特定リソース詳細表示
terraform state show aws_instance.web
# インポート(既存リソースをTerraform管理に追加)
terraform import aws_instance.web i-1234567890abcdef0
条件分岐とループ
# 条件分岐の例
resource "aws_instance" "conditional" {
count = var.create_instance ? 1 : 0
ami = data.aws_ami.ubuntu.id
instance_type = var.environment == "production" ? "t3.medium" : "t3.micro"
}
# for_eachを使ったリソース作成
variable "users" {
type = map(object({
groups = list(string)
}))
default = {
"alice" = {
groups = ["developers", "admins"]
}
"bob" = {
groups = ["developers"]
}
}
}
resource "aws_iam_user" "users" {
for_each = var.users
name = each.key
}
resource "aws_iam_user_group_membership" "users" {
for_each = var.users
user = aws_iam_user.users[each.key].name
groups = each.value.groups
}
基本的なワークフロー
1. 初期設定
# 新しいプロジェクト作成
mkdir terraform-project && cd terraform-project
# 設定ファイル作成
cat > main.tf << EOF
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
EOF
# 初期化
terraform init
2. 開発サイクル
# 1. コード作成・編集
vim resources.tf
# 2. フォーマット
terraform fmt
# 3. 検証
terraform validate
# 4. プラン確認
terraform plan
# 5. 適用
terraform apply
# 6. 状態確認
terraform show
3. チーム開発
# リモートバックエンド初期化
terraform init -backend-config=backend.conf
# ワークスペース管理
terraform workspace new production
terraform workspace select development
terraform workspace list
# 状態のロック確認
terraform force-unlock LOCK_ID