📚

【チュートリアル】TerraformでAWSにWebサーバを作成してみよう

2022/06/04に公開

こんにちは、Masuyama です。

AWS や Azure で使える IaC ツールである Terraform を使い、
AWS 上に Web サーバを使ってサイトを公開するチュートリアル記事です。


全体の流れとしては以下のようになっております。

0. Terraform について

1. Terraform をインストールする

2. 初期設定をする

3. VPC を構築する

4. EC2 とインターネットゲートウェイを構築する

5. 構築したものを削除する

Terraform のインストール、および初期設定方法から解説しているので
初めての方でも Terraform を触る良い機会になると思います。

<注意事項>
このチュートリアルでは
とにかく Terraform で AWS リソースを作ってみることを目的としています。

そのため、ステージによってファイルや変数を分けるというような
本番運用を想定したディレクトリ構成にはしていません。

この点について考え方を把握しておきたい方はクラスメソッドさんの Terraformにおけるディレクトリ構造のベストプラクティス をご参照ください。

2016 年と古めの記事ではありますが、考え方は参考になると思います。


では、早速やっていきましょう。

Terraform について

Terraform は HashiCorp社 が提供する
IaC (Infracture as Code) を実現するためのツールです。

Terraform を用いると、インフラ環境をコード(テンプレートファイル)で管理しつつ、
インフラのデプロイを自動化することが可能となります。


他に有名な IaC のツールですと AWS の CloudFormation という機能がありますが、
Terraform では AWS に限らず Azure や GCP など、他のプロバイダにも対応していることが特徴です。

汎用性が高いことから、一度覚えれば使い回しが効くツールと言えるでしょう。

Terraform をインストールする

tfenv のインストール

tfenv は Terraform のバージョン管理ツール (マネージャ) です。

Terraform は頻繁にアップデートがあり、
また、バージョンによって記法が異なる場合も出てくるため
tfenv を使ってバージョンを自在に切り替えられるようにしておくと便利です。

tfenvインストール

Homebrew を使って tfenv をインストールします。

$ brew install tfenv

バージョン確認しつつ、インストールできていることを確認しましょう。

$ tfenv -v
tfenv 2.2.3

Terraform のインストール

では実際に tfenv を用いて Terraform をインストールしてみます。
インストール可能なバージョン一覧は tfenv のコマンドで確認できます。

$ tfenv list-remote
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
...

今回はバージョン切り替えも試してみるため、2つのバージョンをインストールします。

その時々の最新バージョンで OK です。

この記事を執筆している時点では最新の 1.1.7 と、
その一つ前の 1.1.6 をインストールしてみましょう。

$ tfenv install 1.1.7
$ tfenv install 1.1.6

インストールしたバージョンの確認

tfenv list コマンドを使うと、インストールしたバージョンの一覧を確認できます。

$ tfenv list
  1.1.7
  1.1.6

使用するバージョンを指定

tfenv 経由でインストールしたバージョンであれば、自由に切り替えることができます。

今回は 2つインストールした中で
より新しい方である 1.1.7 を使うように指定してみます。

$ tfenv use 1.1.7

この状態で tfenv list と打つと、使用中のバージョンにアスタリスクが付いているはずです。

$ tfenv list
* 1.1.7 (set by /opt/homebrew/Cellar/tfenv/2.2.3/version)
  1.1.6

また、バージョンをセットしてから Terraform のバージョン確認コマンドを叩くと
tfenv use コマンドで指定したバージョンが反映されているはずです。

$ terraform -v
Terraform v1.1.7

ここまでで Terafform のインストールは完了です。

次は、Terraform で AWS 環境を構築するために必要な設定をしていきましょう。

初期設定をする

事前準備

これから AWS リソースを作成していくにあたって、以下の情報は準備しておいてください。

  • EC2インスタンス用のキーペア
  • 以下のリソースを扱える IAM ユーザのアクセスキーとシークレットキー
    • VPC
    • サブネット
    • セキュリティグループ
    • インターネットゲートウェイ
    • EC2

フォルダ作成

本シリーズで使用するファイルを配置する作業用フォルダをローカルに作成します。

名前は任意です。
ここでは例として tf-web としておきます。

$ mkdir tf_web
$ cd tf_web

Terraform での変数の扱い方について

Terraform に IAM アクセスキーのような情報を渡す場合、以下の3つの方式があります。

コマンドオプションで渡す方法

terraform コマンドのオプションで以下のように -var オプションを使うことで、一時的に変数を渡せます。

$ terraform apply \
-var 'aws_access_key=xxxxxxxxxxxxxxxxxxxxxxxxxx' \
-var 'aws_secret_key=xxxxxxxxxxxxxxxxxxxxxxxxxx'

環境変数を渡す方法

terraform を実行する環境に設定されている環境変数を使う方法です。
環境変数の名前を TF_VAR_<変数名> という形式にして設定しておくと、terraform コマンド実行時に自動的に読み込まれます。
オプションとして指定する必要がないので (1) よりは少し楽になります。

変数ファイルで渡す方法

terraform.tfvars というファイルを作成し、その中に以下のような形式でアクセスキー等の変数を記入しておきます。

aws_access_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
aws_secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"

terraform コマンドを実行するフォルダ (今回でいえば tf-web 内) に terraform.tfvars を置いておくと、自動でファイル内に記入されている変数が読み込まれます。

こちらの方法が公式では推奨している方法となるため、この方式を採用していきます。

terraform.tfvars 作成

それでは、tf_web 内で terraform.tfvars を作成していきましょう。

$ touch terraform.tfvars

作成したら、中身は上述の例の通り、用意した AWS アクセスキーとシークレットキーを記載します。

terraform.tfvars

aws_access_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
aws_secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"

また、デフォルトで使用するリージョンもここに記載しておくと便利です。
ap-northeast-1 など、任意のリージョンを記載します。

terraform.tfvars (追記)

aws_access_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
aws_secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
aws_region        = "ap-northeast-1" # 追記

これで、他の Terraform ファイル (.tf ファイル) からは
access_key = "${var.aws_access_key}" という形式で
アクセスキーとシークレットキーを変数として読み込めるようになりました。

変数定義用ファイル作成

続いて variables.tf といった変数定義用のファイルを作成し、terraform.tfvars で指定した変数を .tf ファイルが読み込めるようにします。

variables.tf

variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "aws_region" {}

また、今回作成するリソースであることを識別する Name タグを付けていくため、プレフィックスもこのファイル内で指定しておきます。

variables.tf (追記)

...
variable "r_prefix" {
  default = "tfweb"
}

terraform 実行準備

プロバイダ指定ファイル

Terraform の設定では、Terraform がインストールして使用できるように、
どのプロバイダ (AWS, Azure etc...) を必要としているかを宣言する必要があります。
また、プロバイダによっては、使用する前に設定(エンドポイントURLやクラウドの地域など)が必要なものもあります。

provider.tf というファイルを作成し、
以下のようにプロバイダとして AWS を指定しつつ、
terraform.tfvars で定義した変数を読み込ませます。

provider.tf

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region  = "${var.aws_region}"
}

作業ディレクトリの初期化

terraform init コマンドを使って、Terraform の設定ファイルを含む作業ディレクトリを初期化します。

新しいTerraform設定を書いたり、git 等で既存の設定をクローンしたりした後に最初に実行するコマンドです。

※初期化とは言っていますが、このコマンドは複数回実行しても問題ありません。

$ terraform init

Initializing the backend...

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

...
Terraform has been successfully initialized!

これで Terraform を実行する準備が完了した状態となります。

実行計画 (execution plan) 作成

Terraform の大きな特徴としては Dry-Run による事前確認が簡単ということです。

Dry-Run のコマンドは terraform plan となります。


terraform plan コマンドでは、実行計画を作成することができます。
デフォルトでは、プランの作成は以下のように構成されています。

  1. 既に存在するリモートオブジェクトの現在の状態を読み込み、Terraformの状態が最新であることを確認
  2. 現在の設定を以前の状態と比較し、差分を抽出
  3. 変更アクションを提案し、それを適用することでリモートオブジェクトを設定に一致(更新)


terraform plan コマンド単体では実行計画を作成するだけなので
、変更はまだ実行されません.

そのため、変更を適用する前に
変更が期待したものと一致しているかどうかを確認したり、
変更内容をチームで共有してより広い範囲で確認するためにこのコマンドを使用します。


なお、このチュートリアルの中で後述しますが、
変更を実際に適用する時は terraform apply コマンドを使用します。


現時点ではまだ Terraform の事前設定をしただけですので、
事前計画においても何も変更プランは作成されていないはずです。

この状態で terraform plan を実行すると
Terraform はリソースインスタンスやルートモジュールの出力値に
変更の必要がないことを検出し、terraform plan は何も変更が必要がない旨を返します。

試しに実行してみましょう。

$ terraform plan

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your
configuration and found no differences, so no changes are
needed.

何も変更が無いので "No changes" と出力されていることが分かります。

次は、実際にリソースを作成するための Terraform ファイルを作成し
Terraform ファイルに基づいて AWS リソースを作成してみます。

VPC を構築する

いよいよ、Terraform で AWS リソースを作成していきます。

VPC用tfファイル作成

それでは作業ディレクトリ (terraform init を実行したディレクトリ) に実行用のファイルを作成します。
作るリソースがわかりやすいよう、名前は vpc.tf としておきます。

$ touch vpc.tf

中身は次のように記述します。
(参考:公式ドキュメント)

vpc.tf

resource "aws_vpc" "tfweb_vpc" {
  cidr_block           = "10.100.0.0/16"
  instance_tenancy     = "default"
  enable_dns_hostnames = true
  enable_dns_support   = true
}

それぞれの意味について、軽く解説しておきます。

  • cird_block
    • 文字通り CIDR を指定しています。
    • ここは任意の CIDR に変更いただいて問題ありません。
  • instance_tenancy
    • ここで作成される EC2 インスタンスの物理ハードウェア分散方法を指定しています。
    • 通常は複数の AWS アカウントと共存する形式である共有型 (default) を指定します。
  • enable_dns_hostnames
    • パブリック IP アドレスを持つインスタンスが、対応するパブリック DNS ホスト名を取得するかどうか指定します。
  • enable_dns_support
    • DNS 解決がサポートされているかどうかを示します。

実行計画の作成

AWS リソースを作成するためのファイルが一つできたので、
前項で説明した terraform plan コマンドで実行計画を作成してみます。

$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.tfweb_vpc will be created
  + resource "aws_vpc" "tfweb_vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.100.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags_all                             = (known after apply)
    }

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

このように、どのようなリソースが作られる予定であるかをひと目で確認することが出来ます。

なお、いくつかのパラメータ (ARN) などは known after apply となっていますが、
これは Terraform を実行した後に判明するパラメータであることを示しています。

リソースの作成(terraform apply)

では terraform plan で確認した実行計画に問題がなければ terraform apply コマンドでリソースを実際に作成します。

terraform apply は terraform plan を実行した場合と同様、まず最初に自動的に新しい実行計画を作成し、それを承認するかどうかを確認してから、提案されたアクションを実行します。

$ terraform apply
...
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

途中までは terraform plan コマンド実行時と似ているのですが、
最後に対話式で承認を求められるようになります。

これに対して指示通り "yes" と入力しましょう。

事前に表示されている実行計画通りにリソースが作成されます。

...
  Enter a value: yes

aws_vpc.tfweb_vpc: Creating...
aws_vpc.tfweb_vpc: Still creating... [10s elapsed]
aws_vpc.tfweb_vpc: Creation complete after 12s [id=vpc-0ae1d6e94dd25fe09]

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

これで vpc.tf ファイルで指定した通りに VPC が作成されました!

サブネット用tfファイル作成 & 実行

この調子で、先ほど作成した VPC 内にサブネットを用意します。

ここでポイントになるのが 「どうやって VPC とサブネットを紐付けるか?」 になります。


結論からいうと、VPC の ID をサブネット用の .tf ファイル内で指定することで、どの VPC 内にサブネットを作成するかを紐付けることができます。

先ほど使った vpc.tf ファイルをもう一度みてみましょう。

vpc.tf (再掲)

resource "aws_vpc" "tfweb_vpc" {
  cidr_block           = "10.100.0.0/16"
  instance_tenancy     = "default"
  enable_dns_hostnames = true
  enable_dns_support   = true
}

冒頭の resource "aws_vpc" "tfweb_vpc" ... という部分で
作りたいリソースが aws_vpc、つまり AWS の VPC であることを示していると同時に
実は "tfweb_vpc" という名前を付けているのです。

ここの名前は任意なのですが、この名前を別の .tf ファイルから参照することで
名前で任意のリソースを参照することが可能になります。


実際にやってみましょう。

サブネット用の .tf ファイルとして subnet.tf ファイルを作成し、次のように記述します。

subnet.tf

resource "aws_subnet" "tfweb_subnet" {
  vpc_id                  = "${aws_vpc.tfweb_vpc.id}"
  cidr_block              = "10.100.0.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

vpc_id の欄で、aws_vpc リソースの tfweb_vpc (VPC に付けた一意の名前) を参照し、
さらにその ID を引っ張ってきています。

このように、ファイル間で名前を参照しながら紐付けを行うというのは頻繁に使われる設定です。

また、サブネットにも "tfweb_subnet" という名前をつけて
後で別なファイルから更にサブネットを参照できるようにしています。


では今回は terraform apply で確認と実行を同時に済ませてしまいましょう。

$ terraform apply
...(中略)...
  Enter a value: yes

aws_subnet.tfweb_subnet: Creating...
aws_subnet.tfweb_subnet: Still creating... [10s elapsed]
aws_subnet.tfweb_subnet: Creation complete after 11s [id=subnet-0a2190cc60270affd]

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

サブネットも無事に作成できました。

この章でリソースを作成する方法を学んだので、残りも一気に作っていきましょう。

EC2 とインターネットゲートウェイを構築する

残りのリソースである EC2、そしてインターネットゲートウェイ(以下、IGW) を作成してします。

インターネットゲートウェイ作成

ではIGWと、インターネット向けのルートに関するリソースを作成します。

igw.tf というファイルを作成し、以下のように記述しましょう。

igw.tf

# Internet Gateway
resource "aws_internet_gateway" "tfweb_igw" {
  vpc_id = "${aws_vpc.tfweb_vpc.id}"
}

# Route Table
resource "aws_route_table" "tfweb_rt" {
  vpc_id = "${aws_vpc.tfweb_vpc.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.tfweb_igw.id
  }
}

# Associate route table with subnet
resource "aws_route_table_association" "tfweb_rt_association" {
  subnet_id      = aws_subnet.tfweb_subnet.id
  route_table_id = aws_route_table.tfweb_rt.id
}

上記のファイルでは 3つの操作を Terraform で行うよう、設定しています。

  1. IGW を作成
  2. IGW に向けたルートテーブルを作成
  3. 前回作成したサブネットにルートテーブルを紐付け

EC2インスタンス (Web サーバ) 作成

次に EC2、そして結びつけるセキュリティグループを作成します。

ec2.tf ファイルを作成し、以下のように記述します。

ec2.tf

# EC2 instance
resource "aws_instance" "tfweb_ec2" {
  ami                         = "ami-06098fd00463352b6" # AmazonLinux 2 (x86)
  instance_type               = "t2.micro"
  subnet_id                   = aws_subnet.tfweb_subnet.id
  key_name                    = "xxxxxxxxxx_key" # 既存のキーペアを指定

  vpc_security_group_ids = [
    aws_security_group.tfweb_sg_web.id,
    aws_security_group.tfweb_sg_ssh.id
  ]

  user_data = <<EOF
  #!/bin/bash
  yum install -y httpd
  systemctl start httpd.service
  EOF
}

# Security Group
resource "aws_security_group" "tfweb_sg_web" {
  name   = "tfweb_sg_web"
  vpc_id = aws_vpc.tfweb_vpc.id

  ingress {
    description = "allow http"
    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_security_group" "tfweb_sg_ssh" {
  name   = "tfweb_sg_ssh"
  vpc_id = aws_vpc.tfweb_vpc.id

  ingress {
    description = "allow ssh"
    from_port   = "22"
    to_port     = "22"
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # 必要に応じIP制限を設定してください
  }

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

いくつかポイントがあるので、箇条書きで解説しておきます。

  • キーペア
    • 予め AWS コンソール等から作成しておいた既存のキーペアを指定します
    • キーペアの名前を指定するだけで OK です
  • SSH 用セキュリティグループ内の "ingress" では必要な IP 制限のみ設定
    • EC2 への SSH アクセスを許可する IP アドレスは絞っておきましょう
  • ユーザデータ
    • EC2 インスタンス起動時に実行するスクリプトをユーザデータとして直接記述できます
      • ちなみに別ファイルにまとめておくことも可能
    • ここでは Apache をインストールして起動するスクリプトを指定しています

terraform apply

それでは追加したいリソース設定を terraform apply で適用します。

$ terraform apply
...
Apply complete! Resources: 6 added, 0 changed, 0 destroyed.

これで設定した状態が実現され
既に Apache が起動した Web サーバが立ち上がっているはずです。


次は Webサーバのパブリック IP アドレスを確認し、
ブラウザでアクセスして Apacheの起動を確認してましょう。

terraform showコマンドで確認

作成したリソースの情報は terraform show コマンドから確認できます。

$ terraform show
# aws_instance.tfweb_ec2:
resource "aws_instance" "tfweb_ec2" {
    ami                                  = "ami-06098fd00463352b6"
    arn                                  = "arn:aws:ec2:ap-northeast-1:xxxxxxxxx:instance/i-0a7c3c8f68d9f6785"
    associate_public_ip_address          = true
    availability_zone                    = "ap-northeast-1a"
...

今回探したいパブリックIPは "public_ip" という欄に表示されるので
grep で絞り込みをしながら出力を確認します。

$ terraform show | grep public_ip
    associate_public_ip_address          = true
    public_ip                            = "52.194.219.150"

EC2 インスタンスのパブリック IP が 54.199.52.15であることを確認できました。
(新規作成するたびにIPは変わっているはずです)

Web サーバへアクセス

それでは先ほど確認したパブリック IP へブラウザからアクセスしてみましょう。

問題がなければ、Apache のテストページが表示されているはずです。


お疲れ様でした!
TerraformでAWS上にWebサーバを構築するというチュートリアルは完了です。


このサーバをもう使わないのであれば、最後にお掃除しておきましょう。

構築したものを削除する

Terraform で作成したリソースは terraform destroy コマンドから一括で削除できます。

$ terraform destroy
...
Destroy complete! Resources: 8 destroyed.

コマンドで作成したリソースも、コマンドで綺麗さっぱり削除できました。

(不安であれば、terraform show コマンドを叩いて何も表示されないことを確認してもいいでしょう。)


お疲れ様でした。

[terraform destroy による削除について補足]
本チュートリアルの設定では関係ありませんが、
作成した EC2 に終了保護をかけていたり
作成した S3 バケットが空でない場合は本コマンド一発では削除できないので注意しましょう。

Discussion