Terraform を使って IaC を始める

はじめに

  • AWS をはじめとした各種クラウドサービスのインフラを安全かつ効率的に管理するためのツールである Terraform を導入します。
    • 実際に、いくつかの AWS のリソースを Terraform で作成してみます。
  • Terraform を導入するにあたって IaC (Infrastructure as Code)の考え方にも触れていきます。

IaC とは

  • IaC (Infrastructure as Code) とは、インフラ構成をコード化して管理する考え方を指します。
    • 自動化、バージョン管理、テスト、継続的インテグレーションといったソフトウェアで用いられる開発手法をインフラや構成管理に応用することを目的としています。
  • どこまでコードの管理対象とするかは、ツールによって考え方が異なるようです。

IaC のメリット

インフラの現在の状態や変更の履歴を管理しやすい

  • インフラの定義をコードに落とし込めるので Git などを使ってバージョン管理ができます。
    • Git で変更管理可能なので、インフラ変更の差分がレビューしやすくなります。
  • 新たなメンバーにインフラ構成を説明・共有する際にも利用できます。

インフラ変更作業のオペレーションミスを減らすことができる

  • IaC ツールは事前にコードで宣言されたインフラを自動で構築するため、「マニュアルを見ながら GUI を通して人間が手動で作業を行う」という方法よりミスが格段に減ります。
  • インフラを設定した人がその場にいなければ、それ以外の人が設定を変更出来ない、ということも少なくなります。
  • サービスクローズ時の不要リソースの削除も抜けもれなく実行可能になります。

同一環境を横展開することが容易になる

  • コードの内容を共有すれば、複数の開発者・チームで同一のインフラ構成が展開できます。
  • 変数等を用いれば、開発環境・ステージング環境・本番環境といった切り替えも容易に行えるようになります。

IaC のデメリット

ツールの敷居の高さ

  • GUI の画面でポチポチと操作していくのに比べ、ツールの使い方を新たに学ばねばならないです。
  • コードの書き方などに慣れていなければ、実際に適用してみて失敗か成功かを見てみなければわからない、といったこともあり、余計に時間がかかってしまうケースもあります。

ツールが追いついていない場合がある

  • AWSGCP の進化はめざましく、日々新しいアップデートが提供されたり、新しいサービスが開始されたりしますが、その変更にツールが追いついていない場合もあります。
    • ツールのバージョンアップを待たねばなりません。

Terraform とは

f:id:linkode-okazaki:20200831121314p:plain

  • 公式サイト
  • HashiCorp 開発を進めている、 IaC を実現するためのツールです。
  • 特徴としては、複数のクラウドサービスやプロバイダーに対応していることです。
    • AWS のみならず、 Azure, GCP, Heroku にも対応しています。
  • 拡張性や機能性が高く、ツールの開発が活発に行われていることも特徴です。

Terraform のセットアップ

  • macOS でのセットアップの流れを説明します。
    • Homebrew と Docker はインストール済みであるとします。

Terraform 専用 IAM ユーザーの作成

  • AWS マネジメントコンソール から IAM に移動し、 Terraform 用のユーザーを作成します。
  • 以下の点に気をつけて設定を行います。
    • 「アクセスの種類」の「AWS マネジメントコンソールへのアクセス」は外しておきます。
      • このユーザーで AWS マネジメントコンソールへのサインインは行わないためです。
    • 権限としては、「AdministratorAccess」のポリシーをアタッチします。

f:id:linkode-okazaki:20200831121412p:plain

f:id:linkode-okazaki:20200831121310p:plain

  • 完了したら、「アクセスキー ID」と「シークレットアクセスキー」が発行されるため、控えておきます。
    • AdministratorAccess のポリシーがアタッチされているアクセスキーの権限は強力なため、流出しないよう最新の注意を払います。

AWS CLI のインストールと設定

  • Python のパッケージマネージャーの pip3 からインストールします。
$ pip3 install awscli --upgrade
  • ~/.aws/credentials に Terraform 用に作成したユーザーの設定を記述しておきます。
[default]
aws_access_key_id=XXXXXX
aws_secret_access_key=XXXXXX

[terraformer]
aws_access_key_id=XXXXXX
aws_secret_access_key=XXXXXX
  • Terraform を使う前には、プロファイルを切り替えて利用します。
$ export AWS_DEFAULT_PROFILE=terraformer

Terraform のインストール

  • 単純には、 Homebrew を用いて以下のようにインストールすれば良いのですが、Terraform は頻繁にバージョンアップが発生するため、実運用においてはバージョンを開発者間で合わせることが求められます。
$ brew install terraform
  • そこで、Terraform のパッケージマネージャーである tfenv をインストールします。これにより、異なるバージョンの Terraform を簡単に扱えます。
$ brew install tfenv
$ tfenv --version
tfenv 2.0.0
  • tfenv-remote で現在利用可能なバージョン一覧が取得できます。
$ tfenv list-remote
0.13.1
0.13.0
0.13.0-rc1
0.13.0-beta3
0.13.0-beta2
0.13.0-beta1
0.12.29
...
  • インストールするときは、 tfenv install [バージョン] です。
$ tfenv install 0.12.29
  • tfenv list で現在インストール済みのバージョン一覧が取得できます。
$ tfenv list
  0.13.1
* 0.12.29 (set by /usr/local/Cellar/tfenv/2.0.0/version)
  • バージョンを切り替えるときは、 tfenv use [バージョン] で切り替わります
  • チーム開発の際は、 .terraform-version ファイルをリポジトリに格納しておきます。
    • このファイルにバージョンを記述すると、tfenv install コマンドを実行するだけで、バージョンが統一できます。
$ echo 0.12.29 > .terraform-version
$ tfenv install

git-secrets

  • Terraform の設定ファイル内に、うっかりクレデンシャルを書き込んでしまって、それを Git のリモートリポジトリにプッシュしてしまったりしないように、 git-secrets を導入し、設定しておくことをおすすめします。
$ brew install git-secrets
$ git secrets --register-aws --global
$ git secrets --install ~/.git-templates/git-secrets
$ git config --global init.templatedir '~/.git-templates/git-secrets'

Terraform の基本操作

  • 以下、 Terraform はバージョン 0.12 以降であることを前提として記載します。
  • 実際に EC2 インスタンスを作成し、設定変更・再作成・削除を行ってみます。
  • エディタは VSCode で、 Terraform の拡張機能 をインストールすると便利に使えます。

リソースの作成

  • Terraform では HCL (HashiCorp Configuration Language) という言語で tf ファイルにコードを記述していきます。
  • Amazon Linux2 の AMI をベースとした EC2 インスタンスを作成してみます。
    • Amazon Linux2 の AMI ID は こちらに載っています。
    • 今回は、 us-east-2 での AMI を利用します。
  • 適当なディレクトリの下に main.tf を作成し、以下の内容を記述します。
provider "aws" {
  version = "~> 2.0"
  profile = "terraformer"
  region  = "us-east-2"
}

terraform {
  required_version = "0.12.29"
}

resource "aws_instance" "example" {
  ami           = "ami-07c8bc5c1ce9598c3"
  instance_type = "t3.micro"
}⏎
  • コードを作成したら、 terraform init コマンドを実行し、リソース作成に必要なバイナリファイルをダウンロードします。
    • 下のように Terraform has been successfully initialized! と表示されれば成功です。
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.70.0...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
  • init が完了したら、 terraform plan で「実行計画」を出力します。
    • どのようなリソースが作成/削除されるのかを確認することが出来ます。
    • 追加されるリソースには + のマークが付きます。
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.example will be created
  + resource "aws_instance" "example" {
      + ami                          = "ami-07c8bc5c1ce9598c3"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t3.micro"
      ......
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
  • 上記結果に問題がなければ、 terraform apply を実行して、リソースを作成します。
    • もう一度、plan で表示された内容が表示され、改めて、本当に実行してよいかの確認が出ます。
    • Enter a value: が表示されたときに yes と入力すれば、リソース作成が実行されます。
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.example will be created
  + resource "aws_instance" "example" {
      + ami                          = "ami-07c8bc5c1ce9598c3"
      ......
    }

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: yes

aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Creation complete after 18s [id=i-0180ac5080ce6e5d0]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
  • マネジメントコンソールで確認すると、正常にリソースが作成されていることが確認できます。

f:id:linkode-okazaki:20200831121306p:plain

リソースの更新

  • main.tf を以下のように変更します。
provider "aws" {
  version = "~> 2.0"
  profile = "terraformer"
  region  = "us-east-2"
}

terraform {
  required_version = "0.12.29"
}

resource "aws_instance" "example" {
  ami           = "ami-07c8bc5c1ce9598c3"
  instance_type = "t3.micro"
  
  # 以下の行を追加
  tags = {
    Name = "example" 
  }
}
  • 変更したら、再び terraform apply を実行します。
    • 変更箇所には ~ のマークが付きます。
$ terraform apply
aws_instance.example: Refreshing state... [id=i-0180ac5080ce6e5d0]
......
Terraform will perform the following actions:

  # aws_instance.example will be updated in-place
  ~ resource "aws_instance" "example" {
        ami                          = "ami-07c8bc5c1ce9598c3"
        ......
      ~ tags                         = {
          + "Name" = "example"
        }
        ......
    }

Plan: 0 to add, 1 to change, 0 to destroy.
  • 先ほどと同じ用に yes を入力して先に進むと、無事に変更が適用されます。

f:id:linkode-okazaki:20200831121302p:plain

リソースの再生成

  • 以下のように main.tf を変更し、 EC2 インスタンス内に Apache をインストールするようにします。
provider "aws" {
  version = "~> 2.0"
  profile = "terraformer"
  region  = "us-east-2"
}

terraform {
  required_version = "0.12.29"
}

resource "aws_instance" "example" {
  ami           = "ami-07c8bc5c1ce9598c3"
  instance_type = "t3.micro"

  # 以下、変更箇所
  user_data = <<EOF
    #!/bin/bash
    yum install -y httpd
    systemctl start httpd.service
EOF
}
  • terraform apply を実行すると、resource のところに -/+ のマークが付きます。
    • この場合、既存のリソースを一旦削除してまた新しいリソースを作成するという操作になるため、注意が必要です。
    • リソース削除によって、サービスダウンを引き起こします。
$ terraform apply
aws_instance.example: Refreshing state... [id=i-0180ac5080ce6e5d0]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_instance.example must be replaced
-/+ resource "aws_instance" "example" {
        ami                          = "ami-07c8bc5c1ce9598c3"
      ~ arn                          = "arn:aws:ec2:us-east-2:740204728684:instance/i-0180ac5080ce6e5d0" -> (known after apply)
      ~ associate_public_ip_address  = true -> (known after apply)
      ......
      + user_data                    = "655c303ddd9e02635f849fe2993693f147f4baf1" # forces replacement
      ......
    }

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

f:id:linkode-okazaki:20200831121257p:plain

  • このように、Terraform によるリソースの更新は既存のリソースをそのまま変更する場合とリソースが作り直しになる場合があるため、plan コマンドの出力結果を注意して確認しておく必要があります。

tfstate ファイルについて

  • Terraform のコマンドを利用すると、AWS 上の現在の状況と tf ファイルに書かれた内容を比較し、必要な部分だけ適切に変更できました。
  • その判断をするのに基にしているファイルが tfstate ファイルです。
$ cat terraform.tfstate
{
  "version": 4,
  "terraform_version": "0.12.29",
  "serial": 6,
  "lineage": "b6d66cd5-6a04-839f-39a4-d32295b1f424",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "example",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "ami": "ami-07c8bc5c1ce9598c3",
            "id": "i-0a1a67b91bd2e2f8c",
           ......
}
  • tfstate ファイルには、現在の状態が記録されます。
  • Terraform は tfstate ファイルの状態と HCL のコードに差分があれば、その差分のみを変更します。

リソースの削除

$ terraform destroy
aws_instance.example: Refreshing state... [id=i-0a1a67b91bd2e2f8c]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_instance.example will be destroyed
  - resource "aws_instance" "example" {
      - ami                          = "ami-07c8bc5c1ce9598c3" -> null
      - arn                          = "arn:aws:ec2:us-east-2:740204728684:instance/i-0a1a67b91bd2e2f8c" -> null
      ......

Plan: 0 to add, 0 to change, 1 to destroy.
  • ここでは、削除されるリソースに - のマークが付きます。
  • apply コマンド同様、確認がなされるので「yes」を入力して削除を実行します。
  • AWS マネジメントを確認すると、インスタンスterminated になり、削除されていることが確認できます。

まとめ

  • IaC の概念とそれを実現する Terraform の基本事項について確認しました。
  • 実際に Terraform を使って AWS のリソースを作成してみることで、 Terraform の基本操作も学びました。
  • Terraform の使い方については、今後も継続的に紹介していきます。

参考資料