Infrastructure as Code入門:TerraformでAWSインフラを自動化する実践ガイド

Development tutorial - IT technology blog
Development tutorial - IT technology blog

「コンソールをクリックするだけ」では限界が来たとき

去年の初め、チームでかなり痛い障害が発生した。staging環境が誤って削除されてしまい、ゼロから再構築するのに約3日かかった。infrastructureが複雑だったわけではなく、誰も正確に何を設定したか把握していなかったのが原因だ——各自がバラバラにクリックしていて、明確なドキュメントもなかった。そのときから、Infrastructure as Codeを真剣に調べ始めた。

6ヶ月後、本番環境は安定して稼働している——そして語るべきことがかなりたまった。

考察:なぜ「ClickOps」は危険な落とし穴なのか

AWS Consoleは直感的で、結果もすぐ出る——始めたばかりの人には便利に感じる。しかし代償は後になってからやってきて、たいていは高くつく:

  • Configuration drift:dev、staging、productionが誰も知らぬうちにじわじわと異なる状態になっていく。本番のsecurity groupにport 22を0.0.0.0/0で開けるルールが残っているのを発見したことがある——誰かが「ちょっとテスト」して閉め忘れたまま、約2ヶ月間放置されていた。
  • 再現不可能:productionと全く同じ環境を新たに作りたい?答えは最初にセットアップした人の記憶次第だ。
  • ディザスタリカバリが遅い:regionで障害が発生してmigrateが必要になったら、どのくらいかかる?ClickOpsだと、答えはたいてい「わからない」だ。
  • 意味のあるaudit trailがない:CloudTrailは誰が何を変更したかは記録できるが、なぜ変更したかはわからない——そしてもっと重要なのは、簡単にrollbackできないことだ。

根本的な問題:インフラがコードではなく、人の頭の中にある状態だ。

IaCの選択肢——そしてなぜTerraformが勝ったのか

Terraformにコミットする前に、他の選択肢も試してみた:

AWS CloudFormation

AWSネイティブで追加インストール不要。しかしYAML/JSONのsyntaxが非常に冗長で、シンプルなstackでも300行を超えることがある。さらに重要なのは、AWSに完全にlock-inされることだ。

Ansible

configuration management(パッケージのインストール、既存サーバーのconfig file編集)には優れているが、infrastructureをprovisionするためのツールではない。Ansibleには「state」の概念がなく、現在どのリソースが存在するかをtrackできない。

Pulumi

infrastructureをPython/TypeScriptで書けるのは魅力的に聞こえる。しかしlearning curveが高く、ecosystemもまだ小さく、Terraformに比べて具体的なサンプルを探すのにかなり時間がかかった。

Terraform

Multi-cloud(AWS、GCP、Azure…)対応で、CloudFormationよりHCL syntaxが読みやすく、state managementも優れている。コミュニティが非常に大きく、ほぼどのAWSリソースでもTerraform Registryにサンプルが揃っている。

Terraformのインストールと初期設定

Ubuntu/Debianへのインストール——後のアップデートを楽にするため、HashiCorpの公式apt repoを使う:

wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | \
  sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
terraform --version

次はAWS credentialsの設定だ。IAM userをまだ作成していなければ、今がやるべきタイミング——least-privilege原則で、必要な権限のみを付与する:

aws configure
# AWS Access Key ID: AKIA...
# AWS Secret Access Key: ...
# Default region name: ap-northeast-1
# Default output format: json

最初のTerraform設定:EC2インスタンスを作成する

これが最もシンプルな出発点となる設定だ——東京リージョンのEC2インスタンス1台で、Terraformに何を求めているかを伝えるのに十分な宣言:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"  # 東京
}

resource "aws_instance" "web_server" {
  ami           = "ami-0d52744d6551d851e"  # Ubuntu 22.04 LTS 東京
  instance_type = "t3.micro"

  tags = {
    Name        = "web-server-production"
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

output "instance_ip" {
  value = aws_instance.web_server.public_ip
}

基本的なworkflowはたった3つのコマンドだ。この3つのコマンドを6ヶ月間、毎日使い続けている:

# 初期化 — プロバイダーをローカルにダウンロード
terraform init

# 変更のプレビュー(実行しない)
terraform plan

# 変更を適用
terraform apply

terraform planの出力は明確だ:どのリソースが作成(+)、削除(-)、変更(~)されるかが一目でわかる。applyする前に必ずplanをしっかりレビューする——これが最も重要なsafety netだ。

State File——ほとんどのチュートリアルが見落とす最重要ポイント

Terraformはインフラの状態をterraform.tfstateファイルに保存する。このファイルはTerraformが管理しているリソースを把握するための「信頼できる情報源」だ。

最もよくある間違い:state fileをローカルマシンに保存すること。その結果:

  • チームの他のメンバーがTerraformを実行できない
  • マシンを失う = stateを失う = Terraformが何を管理しているかわからなくなる
  • 2人が同時にterraform applyを実行するとconflictが発生する——それを防ぐものが何もない

解決策:S3 + DynamoDBを使ったremote backendでstateをロックする:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "production/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

まずS3 bucketとDynamoDB tableを作成する(最初の一回だけ手動で):

aws s3 mb s3://my-terraform-state-bucket --region ap-northeast-1
aws s3api put-bucket-versioning \
  --bucket my-terraform-state-bucket \
  --versioning-configuration Status=Enabled

aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region ap-northeast-1

実プロジェクトのためのTerraformコード構成

何度かrefactorした後、現在使っている構成がこれだ——シンプルだが、environmentを追加する際にもscaleできる:

infrastructure/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── production/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
└── modules/
    ├── vpc/
    ├── ec2/
    └── rds/

Variablesを使うとhardcodeを避け、コードを複製せずにdevとproductionの違いを簡単に管理できる:

# variables.tf
variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Environment name"
  type        = string
}
# terraform.tfvars (production)
instance_type = "t3.medium"
environment   = "production"

本番環境6ヶ月で学んだこと

これは自分が学んだこと——そのほとんどは少なくとも一度失敗してから得た教訓だ:

  • terraform fmtterraform validateを毎commit前に実行する——syntax errorのあるコードをpushしないよう、pre-commit hookかCIに組み込む。
  • state fileを手動で編集しない——操作が必要な場合はterraform state mvまたはterraform state rmを使う。
  • 全リソースにtagを付けるManagedBy = "terraform"EnvironmentProject。3ヶ月後にAWS Consoleを見たとき、どのリソースが誰に管理されているかが一目でわかる。
  • CI/CDでplanをレビューする:PRが来たときにterraform planを自動実行するGitHub Actionsをセットアップする。結果が自動的にPRにコメントされ、レビュアーはmerge前にinfrastructureの変更をすぐ確認できる——お互い確認し合う手間が省ける。
  • environmentごとにstateを分割するdev/terraform.tfstateproduction/terraform.tfstateを別々に管理する——誤ったコマンドでproductionをdestroyするリスクを避けるため。

ちょっとした便利ツール:Terraformのoutputをデバッグしたりterraform output -jsonのJSONを確認したいとき、toolcraft.app/ja/tools/developer/json-formatterにペーストしてすぐフォーマットできる——JSONを見るためだけにIDEを開くより断然楽だ。

どこから始めるべきか?

cloudで何かを動かしていてIaCをまだ使っていないなら、毎日密かにtechnical debtが積み上がっている。Terraformは最初の日からセットアップすべきだ——手動でクリックした50のリソースができてからではなく、後から全部importするのは本当に大変だ。

始める最も簡単な方法:今のプロジェクトにあるシンプルなリソース——S3 bucketでも、security groupでも——を一つ選んで、それのTerraformを書いてみることだ。terraform destroyしてからterraform applyして、全てが全く同じように再作成されるのを目の当たりにする感覚は、どんな記事よりも説得力があるはずだ。

Share: