LoginSignup
3
6

More than 3 years have passed since last update.

Terraform ハンズオン資料

Last updated at Posted at 2019-10-29

部署内ハンズオンで使った資料です。せっかくなので公開してみました。

Terraform とは

HashiCorp社製の様々なクラウドサービスをコードで管理するためのツールです。
インフラなどの設定をコード化することで、共同作業や使い回しやバージョン管理などが容易になります。
dry run やリソースの依存性の可視化など便利な機能も多く、いわゆる Infrastructure as Code を実現するために用いられます。

Terraform by HashiCorp

TL;DR

.tf ファイルにインフラの設定を記述し、コマンドを実行するとその通りにインフラを構築してくれるツールです。

環境構築

Terraform用のIAMユーザーの作成

Terraform用のIAMユーザーを作成します。以下URLを参考にしながらIAMユーザーを作成します。
今回はハンズオンなので、Admin権限を持つユーザーを作成します。
SecretAccessKey は再発行されないので、無くしてしまったらアカウントを作り直してください。

IAM ユーザーの作成 (コンソール)

AWS CLI のインストール

AWS CLI をインストールし、設定を行います。インストール方法は下記 URL を参考にしてください

macOS に AWS CLI をインストールする
AWS CLI の設定

インストールが完了したら AWS CLI の設定を行い、AWS CLI が実行できることを確認します。

$ aws --version
$ aws configure
$ aws s3api list-buckets  # S3 のバケット一覧を表示するコマンド

tfenv を介してインストール

Terraform は更新が早く、バグもまだまだ多いため、気軽にバージョンを変えられるように tfenv を使うことを強く勧めます。

$ brew install tfenv
$ tfenv -v
$ tfenv list-remote     # インストール可能なバージョンをリモートから取得して列挙します。
$ tfenv install 0.12.10 # 0.12.10 をインストール
$ tfenv use 0.12.10     # 0.12.10 を使用する
$ tfenv list            # インストール済み
$ terraform --version   # バージョン一覧

.terraform-version ファイルを作ることで、.ruby-version のようにバージョン管理なども可能です。

$ touch .terraform-version
$ tfenv use 0.12.10       # このディレクトリ以下で 0.12.10 を使用する

HelloWorld

HelloWorld として S3バケットを Terraform で作ります。

$ mkdir handson
$ cd handson
$ touch main.tf

main.tf ファイルを作成し、以下のように記述します。
tekitouna.domain.nyuryoku の箇所はS3バケット名になるので、urlに使える文字列 かつ グローバルでユニークな任意の値を入れてください。 

main.tf
# Provider
provider "aws" {
  profile = "default"
  region  = "ap-northeast-1"
}

# Resource
resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
 bucket = "tekitouna.domain.nyuryoku"
}

terraform init コマンドでAWSのプロバイダーをダウンロードし、terraform plan コマンドで main.tf に新しく作成されるリソースを確認します。

$ terraform init
$ terraform plan

以下のように新しく作成されるリソースの情報が表示されます。
tekitouna.domain.nyuryoku という名前の新しいバケットが作成されることがわかります。

Terraform will perform the following actions:

  # aws_s3_bucket.tekitouna_domain_nyuryoku will be created
  + resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
      + acceleration_status         = (known after apply)
      + acl                         = "private"
      + arn                         = (known after apply)
      + bucket                      = "tekitouna.domain.nyuryoku"
      + bucket_domain_name          = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + versioning {
          + enabled    = (known after apply)
          + mfa_delete = (known after apply)
        }
    }

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

問題なければ terraform applyコマンドを実行して、S3バケットを作成します。

$ terraform apply

新しくS3バケットが作成されました。
以下のコマンドを実行すると新しくバケットが作られていることがわかります。

aws s3api list-buckets | grep tekitouna.domain.nyuryoku

Applyに成功すると terraform.tfstate が生成されます。
terraform.tfstate にはTerraformでは管理しているインフラの状態が記述されています。
ハンズオンなので詳しい説明はスキップしますが、複数人での開発ではtfstate ファイルを S3 などに置くことをお勧めします。

TerraformでtfstateファイルをS3で管理する

terraform.tfstate
{
  "version": 4,
  "terraform_version": "0.12.10",
  "serial": 1,
  "lineage": "6a7bf262-a78d-decf-166f-9f1eac238641",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_s3_bucket",
      "name": "tekitouna_domain_nyuryoku",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "acceleration_status": "",
            "acl": "private",
            "arn": "arn:aws:s3:::tekitouna.domain.nyuryoku.xxxx",
            "bucket": "tekitouna.domain.nyuryoku.xxxx",
            "bucket_domain_name": "tekitouna.domain.nyuryoku.xxxx.s3.amazonaws.com",
            "bucket_prefix": null,
            "bucket_regional_domain_name": "tekitouna.domain.nyuryoku.xxxx.s3.ap-northeast-1.amazonaws.com",
            "cors_rule": [],
            "force_destroy": false,
            "hosted_zone_id": "Z2M4EHUR26P7ZW",
            "id": "tekitouna.domain.nyuryoku.xxxx",
            "lifecycle_rule": [],
            "logging": [],
            "object_lock_configuration": [],
            "policy": null,
            "region": "ap-northeast-1",
            "replication_configuration": [],
            "request_payer": "BucketOwner",
            "server_side_encryption_configuration": [],
            "tags": null,
            "versioning": [
              {
                "enabled": false,
                "mfa_delete": false
              }
            ],
            "website": [],
            "website_domain": null,
            "website_endpoint": null
          },
          "private": "bnVsbA=="
        }
      ]
    }
  ]
}

次に作成したバケットを削除します。

$ terraform destroy

削除されるリソースの内容が表示されます。

Terraform will perform the following actions:

  # aws_s3_bucket.tekitouna_domain_nyuryoku will be destroyed
  - resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
      - acl                         = "private" -> null
      - arn                         = "arn:aws:s3:::tekitouna.domain.nyuryoku.xxxx" -> null
      - bucket                      = "tekitouna.domain.nyuryoku.xxxx" -> null
      - bucket_domain_name          = "tekitouna.domain.nyuryoku.xxxx.s3.amazonaws.com" -> null
      - bucket_regional_domain_name = "tekitouna.domain.nyuryoku.xxxx.s3.ap-northeast-1.amazonaws.com" -> null
      - force_destroy               = false -> null
      - hosted_zone_id              = "Z2M4EHUR26P7ZW" -> null
      - id                          = "tekitouna.domain.nyuryoku.xxxx" -> null
      - region                      = "ap-northeast-1" -> null
      - request_payer               = "BucketOwner" -> null
      - tags                        = {} -> null

      - versioning {
          - enabled    = false -> null
          - mfa_delete = false -> null
        }
    }

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

変更内容に問題がなければ実行します。
これで Terraform による作成と削除はできました。

コンソールから作ったリソースを Terraform に import してみる

以下の画像の手順を参考に、Webサーバーとして公開されているS3バケットを作成します。

page1.png
page2.png
page3.png
page4.png

バケットポリシーを設定します。

part5.png

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::tekitouna.domain.nyuryoku/*"
    }
  ]
}

ポリシーの設定が完了したら、index.html, error.html という名前で html ファイルを作成し、バケットにアップデートします。

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>タイトル</title>
    </head>
    <body>
        <h1>タイトル</h1>
        <p>Amazon S3を使って静的なウェブページを公開してみた</p>
    </body>
</html>
error.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>Error</title>
    </head>
    <body>
        <h1>Error</h1>
        <p>エラーページです</p>
    </body>
</html>

しばらく待ってから、S3のファイルのURLにアクセスし、ファイルが表示されることを確認します。

http://tekitouna.domain.nyuryoku.s3-website-ap-northeast-1.amazonaws.com/index.html
http://tekitouna.domain.nyuryoku.s3-website-ap-northeast-1.amazonaws.com/error.html

次に作成したS3バケットをTerraform で管理してみようと思います。
以下のコマンドで 作成した S3バケットを Terraform の管理下に取り込みます。

$ terraform import aws_s3_bucket.tekitouna_domain_nyuryoku tekitouna.domain.nyuryoku

plan コマンドで、main.tf ファイルと手動で作成したS3バケットとの差分を確認します。

$ terraform plan

差分を埋める -> terraform plan -> 差分を埋める 作業を繰り返します。
以下の差分を消すことができなかったので、無視しました。

+ acl                         = "none"
+ force_destroy               = true

完成したのがこちらです。
先ほどの手作業をコード化することができました。

resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
 bucket = "tekitouna.domain.nyuryoku"
 website {
    index_document = "index.html"
  }
}

resource "aws_s3_bucket_policy" "tekitouna_domain_nyuryoku" {
  bucket = aws_s3_bucket.tekitouna_domain_nyuryoku.id
  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::tekitouna.domain.nyuryoku/*"
    }
  ]
}
POLICY
}

main.tf と S3バケットとの差分がなくなりました。

[おまけ]機能を追加してみる

前項までにWebサーバーとして公開されているS3バケットを作成しました。
しかしファイルが存在しないページにアクセスすると Access Deniedエラーが表示されてしまいます。
ファイルが存在しないページにアクセスしようとしたらエラーページにリダイレクトするように設定します。

リダイレクトの仕組みはS3に用意されています。
以下サイトを参考にコンソールからの設定を行います。

S3の静的webサイトで404時に別のドメインに飛ばす時

設定後、terraform plan を実行し現在の main.tf ファイルとS3バケットの差分を確認します。

  # aws_s3_bucket.tekitouna_domain_nyuryoku will be updated in-place
  ~ resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
        acl                         = "private"
        arn                         = "arn:aws:s3:::tekitouna.domain.nyuryoku"
        bucket                      = "tekitouna.domain.nyuryoku"
        bucket_domain_name          = "tekitouna.domain.nyuryoku.s3.amazonaws.com"
        bucket_regional_domain_name = "tekitouna.domain.nyuryoku.s3.ap-northeast-1.amazonaws.com"
        force_destroy               = false
        hosted_zone_id              = "Z2M4EHUR26P7ZW"
        id                          = "tekitouna.domain.nyuryoku"
        region                      = "ap-northeast-1"
        request_payer               = "BucketOwner"
        tags                        = {}
        website_domain              = "s3-website-ap-northeast-1.amazonaws.com"
        website_endpoint            = "tekitouna.domain.nyuryoku.s3-website-ap-northeast-1.amazonaws.com"

        versioning {
            enabled    = false
            mfa_delete = false
        }

      ~ website {
            error_document = "error.html"
            index_document = "index.html"
          - routing_rules  = jsonencode(
                [
                  - {
                      - Condition = {
                          - HttpErrorCodeReturnedEquals = "404"
                        }
                      - Redirect  = {
                          - ReplaceKeyPrefixWith = "error.html?referer="
                        }
                    },
                ]
            ) -> null
        }
    }

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

表示された差分を埋めるため、以下のように記述し、terraform plan を実行しても差分が出ないことを確認します。

main.tf

resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
  bucket = "tekitouna.domain.nyuryoku"
  website {
    error_document = "error.html"
    index_document = "index.html"

    routing_rules = <<EOF
[
  {
    "Condition": {
        "HttpErrorCodeReturnedEquals": "404"
    },
    "Redirect": {
        "ReplaceKeyPrefixWith": "error.html?referer="
    }
  }
]
EOF
  }
}

resource "aws_s3_bucket_policy" "tekitouna_domain_nyuryoku" {
  bucket = aws_s3_bucket.tekitouna_domain_nyuryoku.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::tekitouna.domain.nyuryoku/*"
    }
  ]
}
EOF
}

お掃除

terraform destroy コマンドを実行することで、.tfstate で管理されているリソースを全て削除することができます。

参考

3
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6