Posted on :: 3923 Words :: Tags: , , ,

初めてのクラウドリソース

理論はもう十分。実際に手を動かしてみましょう。

Part 1 で理論を学び、Part 2 でセットアップを完了しました。ここからは実践です。Terraform を使って AWS 上に実際の EC2 インスタンスを作成します。

ここで腑に落ちるはずです。もしくは、宣言的インフラが嫌いになるかもしれません。どちらにしても、やってみればわかります。

📦 コード例

リポジトリ: terraform-hcl-tutorial-series この Part: Part 3 - First Resource Example

動作するサンプルを取得:

git clone https://github.com/khuongdo/terraform-hcl-tutorial-series.git
cd terraform-hcl-tutorial-series
git checkout part-03
cd examples/part-03-first-resource/

# 初期化とデプロイ
terraform init
terraform plan
terraform apply

# 完了したらクリーンアップ
terraform destroy

構築するもの

このチュートリアルでは、AWS に EC2 インスタンスをデプロイし、Terraform がリソースをどう追跡するかを理解します。そして最後に、課金されないようリソースを削除します。途中で少なくとも1つのエラーに遭遇するでしょう。それは良いことです。成功よりもデバッグの方が学びが多いですから。

料金に関する注意: 今回は t2.micro インスタンスを使用します。これは無料利用枠の対象です。終了時にリソースを削除すれば、料金は $0 です。削除し忘れた場合は月額 $10 程度かかります。カレンダーにリマインダーを設定しておきましょう。

始める前に

Part 2 を完了していることを確認してください。これは重要です。以下のコマンドが失敗する場合は、セットアップを先に完了させてください:

# Terraform はインストール済み?
terraform --version
# v1.6+ が表示されるはず

# AWS 認証情報は設定済み?
aws sts get-caller-identity
# アカウント情報が表示されるはず

# 作業ディレクトリは準備済み?
mkdir -p ~/terraform-tutorial/part-03
cd ~/terraform-tutorial/part-03

エラーが出る場合は先に進まないでください。間違ったところをデバッグする時間の無駄になります。

初めての設定ファイル

main.tf というファイルを作成します:

# Provider configuration
provider "aws" {
  region = "us-east-1"
}

# EC2 instance resource
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name        = "MyFirstTerraformInstance"
    Environment = "tutorial"
    ManagedBy   = "terraform"
  }
}

それぞれ解説します:

provider "aws" は AWS の us-east-1 リージョンを使うことを Terraform に伝えています。シンプルですね。

resource "aws_instance" "web" は EC2 インスタンスを宣言しています。フォーマットは resource "<type>" "<local_name>" です。type (aws_instance) は AWS provider から来ています。local name (web) は任意の名前で、コード内でこのリソースを参照する際に使用します。

ami は Amazon Machine Image のことで、どの OS を使うかを指定します。この例では Ubuntu 20.04 です。AMI ID はリージョン固有なので、us-east-1 以外を使う場合は変更が必要です。この点については後ほどエラーで確認できるでしょう。

instance_type はサイズです。t2.micro = 1 vCPU、1 GB RAM で、無料利用枠の対象です。チュートリアルには十分です。

tags はメタデータです。リソースには必ずタグを付けましょう。ManagedBy = "terraform" タグは、後で何を削除しても安全か判断する際に役立ちます。

Terraform の初期化

Terraform が何かを実行する前に、AWS provider plugin をダウンロードする必要があります:

terraform init

以下のような出力が表示されます:

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)

Terraform has been successfully initialized!

何が起こったのでしょうか? Terraform は main.tf ファイルを読み取り、AWS を使用していることを確認し、AWS provider plugin(約 500 MB)をダウンロードしました。また、.terraform/ ディレクトリと .terraform.lock.hcl ファイルを作成し、provider のバージョンをロックしました。

terraform init はプロジェクトごとに一度だけ、または新しい provider を追加した際に実行します。

terraform plan で変更をプレビュー

これは身につけるべき最も重要な習慣です: terraform apply の前に必ず terraform plan を実行すること

terraform plan

出力:

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami                          = "ami-0c55b159cbfafe1f0"
      + instance_type                = "t2.micro"
      + id                           = (known after apply)
      + public_ip                    = (known after apply)
      + private_ip                   = (known after apply)
      ...
      + tags                         = {
          + "Environment" = "tutorial"
          + "ManagedBy"   = "terraform"
          + "Name"        = "MyFirstTerraformInstance"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

これは Terraform が「これから実行する内容」を伝えています。+ は何かを作成することを意味します。(known after apply) は、AWS が実際にインスタンスを作成するまで存在しない値を表します。

plan の出力を読み取ることはスキルです。本番環境では、見落とした一行がデータベースを削除してしまう可能性があります。今のうちに慎重に読む習慣を身につけましょう。

よくあるエラー(おそらく遭遇します)

エラー: "No valid credential sources found"

Error: No valid credential sources found for AWS Provider

解決方法: aws configure を実行して、アクセスキーを入力してください。

エラー: "AMI not found"

Error: creating EC2 Instance: InvalidAMIID.NotFound

解決方法: AMI ID はリージョン固有です。この例の AMI は us-east-1 用です。別のリージョンを使用している場合は、Ubuntu Cloud Images で AMI を見つけて、main.tfami 値を更新してください。

エラー: "Insufficient IAM permissions"

Error: creating EC2 Instance: UnauthorizedOperation

解決方法: AWS ユーザーに EC2 の権限が必要です。個人アカウントを使用している場合は、とりあえず管理者アクセスを付与してください。会社のアカウントの場合は、管理者に AmazonEC2FullAccess ポリシーをリクエストしてください。

変更を適用(実際にインスタンスを作成)

plan の内容が問題なければ、適用します:

terraform apply

Terraform は再度 plan を表示し、確認を求めます:

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 と入力します(y ではなく完全な単語):

aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Creation complete after 32s [id=i-0abc123def456789]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

おめでとうございます。コードでクラウドインフラを構築できました。

AWS コンソールで確認してみましょう。EC2 → Instances に、"MyFirstTerraformInstance" とタグ付けされたインスタンスが実際に実行されています。

何が起こったのか?

terraform apply を実行したとき、Terraform は以下を行いました:

  1. 依存関係グラフを構築(この場合はリソースが1つだけなので、あまり面白くありませんが)
  2. パラメータを使って AWS API コール(ec2:RunInstances)を実行
  3. AWS がインスタンスをプロビジョニングするのを待機
  4. すべてを terraform.tfstate というファイルに記録
  5. インスタンス ID を返却

この state file は重要です。詳しく見ていきましょう。

State File (terraform.tfstate)

apply の後、新しいファイルが作成されています:

ls -la
# .terraform/
# .terraform.lock.hcl
# main.tf
# terraform.tfstate    <-- これが新規

terraform.tfstate を開いてください。JSON 形式で、このような内容です:

{
  "version": 4,
  "terraform_version": "1.6.0",
  "resources": [
    {
      "type": "aws_instance",
      "name": "web",
      "instances": [
        {
          "attributes": {
            "id": "i-0abc123def456789",
            "ami": "ami-0c55b159cbfafe1f0",
            "instance_type": "t2.micro",
            "public_ip": "54.123.45.67",
            ...
          }
        }
      ]
    }
  ]
}

これは Terraform のメモリです。コード内の aws_instance.web を AWS の i-0abc123def456789 にマッピングしています。このファイルがなければ、Terraform は何を作成したか、何を更新する必要があるか、何を安全に削除できるかがわかりません。

このファイルを手動で編集しないでください。 データベースのように扱ってください。テキストエディタではなく、Terraform コマンドを使って操作します。

本番環境では、state をリモート(S3、Terraform Cloud など)に保存しますが、今はローカルファイルで問題ありません。

Plan と Apply の理解

Terraform のワークフローは 2 ステップです: plan してから apply。これは意図的なものです。

terraform plan は読み取り専用です。AWS にクエリを投げて何が存在するかを確認し、設定と比較して、何が変更されるかを表示します。いつ実行しても安全です。何も変更されません。

git diff をコミット前に実行するようなものです。

terraform apply は実際に変更を加えます。AWS API を呼び出します。リソースを作成、更新、削除します。お金がかかります。

本番環境への git push のようなものです。

黄金律: apply の前に必ず plan を実行。常に出力を読む。反射的に yes と入力しない。

誤って削除したリソース1つで、何時間ものリカバリ作業が発生します。最悪の場合、データが失われます。

リソースへの変更

インスタンスを変更してみましょう。main.tf を更新してタグを追加します:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name        = "MyFirstTerraformInstance"
    Environment = "tutorial"
    ManagedBy   = "terraform"
    Owner       = "YourName"  # <-- これを追加
  }
}

plan を実行:

terraform plan

出力:

Terraform will perform the following actions:

  # aws_instance.web will be updated in-place
  ~ resource "aws_instance" "web" {
        id            = "i-0abc123def456789"
      ~ tags          = {
          + "Owner"       = "YourName"
            # (3 unchanged elements hidden)
        }
        # (20 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

~ 記号に注目してください。これは「in-place で更新」を意味します。Terraform はインスタンスを再作成せずにタグを追加できます。ダウンタイムはありません。

適用します:

terraform apply
# Type: yes

# Output:
# aws_instance.web: Modifying... [id=i-0abc123def456789]
# aws_instance.web: Modifications complete after 2s

完了です。タグが更新され、インスタンスは稼働し続けています。これが宣言的インフラの力です。何が欲しいかを伝えれば、Terraform が必要最小限の変更を見つけ出します。

リソースが置き換えられる場合

一部の変更は in-place で実行できません。古いリソースを削除して新しいものを作成する必要があります。

AMI を変更してみましょう(実際には apply せず、plan だけを見てください):

resource "aws_instance" "web" {
  ami           = "ami-0xyz987new654321"  # 変更
  instance_type = "t2.micro"
  ...
}

plan を実行:

terraform plan
Terraform will perform the following actions:

  # aws_instance.web must be replaced
-/+ resource "aws_instance" "web" {
      ~ ami           = "ami-0c55b159cbfafe1f0" -> "ami-0xyz987new654321" # forces replacement
      ~ id            = "i-0abc123def456789" -> (known after apply)
        ...
    }

Plan: 1 to add, 0 to change, 1 to destroy.

forces replacement が見えますか? AWS は起動後にインスタンスの AMI を変更できません。Terraform は古いインスタンスを削除して新しいものを作成する必要があります。

この変更は apply しないでください。AMI を元の値に戻してください。リソースの置き換えについては Part 6 で詳しく説明します。

クリーンアップ: リソースの削除

実験が終わったら、課金を避けるためにインスタンスを削除します:

terraform destroy

出力:

Terraform will perform the following actions:

  # aws_instance.web will be destroyed
  - resource "aws_instance" "web" {
      - ami           = "ami-0c55b159cbfafe1f0" -> null
      - id            = "i-0abc123def456789" -> null
      ...
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value:

yes と入力:

aws_instance.web: Destroying... [id=i-0abc123def456789]
aws_instance.web: Still destroying... [10s elapsed]
aws_instance.web: Destruction complete after 32s

Destroy complete! Resources: 1 destroyed.

AWS コンソールで確認してください。インスタンスの状態: "Terminated"。

state file はまだ存在します(履歴を追跡しています)が、リソースは削除されました。

他に遭遇する可能性のあるエラー

エラー: "Resource already exists"

手動でインスタンスを作成した後に terraform apply を実行しようとすると、このエラーが発生します。Terraform は手動で作成されたリソースを認識しません。手動のインスタンスを削除するか、Terraform state にインポートしてください(Part 9 で説明します)。

エラー: "State file locked"

Error: Error acquiring the state lock

これは Terraform の安全機構です。apply 中に Ctrl+C を押すと、Terraform は破損を防ぐために state をロックします。2分待つか、強制的にロック解除します:

terraform force-unlock <lock-id-from-error>

他の Terraform プロセスが実行されていないことが確実な場合のみ、強制ロック解除を使用してください。

エラー: "Provider version mismatch"

Error: Provider version constraint not met

誰かが新しい provider バージョンを使用しました。修正方法:

terraform init -upgrade

確認テスト

Part 4 に進む前に、以下に答えられることを確認してください:

  1. terraform plan は何をしますか?
  2. terraform.tfstate とは何で、なぜ重要ですか?
  3. in-place 更新とリソース置き換えの違いは何ですか?
  4. すべての Terraform 管理リソースを削除するコマンドは何ですか?
  5. plan 出力の + 記号は何を意味しますか?

これらに答えられない場合は、該当セクションを読み直してください。これらの概念は基礎となるものです。

学んだこと

コードでクラウドインフラをデプロイしました。これが Terraform の基本的な価値提案です。

また、ワークフローも学びました: コードを書き、plan でプレビューし、apply で実行し、destroy でクリーンアップ。このパターンは、1つの EC2 インスタンスから複数のクラウドにまたがる数千のリソースまでスケールします。

そしておそらくエラーに遭遇したでしょう。良いことです。デバッグは実際の動作を教えてくれます。ドキュメントは正常系のパスしか教えてくれません。

次: HCL の基礎

Part 4 では、HashiCorp Configuration Language を深く掘り下げます。変数、出力、データ型、関数、条件分岐、ループ―柔軟で再利用可能な Terraform 設定を書くために必要なすべてのツールです。

現在の main.tf はハードコードされています。Part 4 では、それを動的にする方法を学びます。

準備はいいですか? Part 4 に進む → (近日公開)


トラブルシューティング: SSH アクセス

インスタンスに SSH 接続したい場合(今回は設定していません)、セキュリティグループが必要です:

resource "aws_security_group" "allow_ssh" {
  name        = "allow_ssh"
  description = "Allow SSH inbound traffic"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # 警告: どこからでも SSH 可能
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.allow_ssh.id]

  tags = {
    Name        = "MyFirstTerraformInstance"
    Environment = "tutorial"
    ManagedBy   = "terraform"
  }
}

セキュリティグループについては Part 5 で適切に説明します。今は、0.0.0.0/0 がどこからでも SSH を許可するため、本番環境では安全でないことを知っておいてください。チュートリアルには問題ありません。


追加リソース


この投稿は「Terraform from Fundamentals to Production」シリーズの一部です。Infrastructure as Code を Terraform でマスターするために、ぜひフォローしてください。

連載ナビゲーション: