Terraform 0.9を使ってみた

Contents
  1. はじめに
  2. 状況
  3. インストール
  4. 手順
  5. 設定ファイルを作る
  6. 初期化
  7. 既存リソースの同期
  8. おわりに

はじめに

Terraform はあんまり好きではないんですが選択肢がないんで仕方なし、って感じで使ってます。 更新がはやくてちょっと使わないと色々変わってたりしますね。

今回使ったバージョンは 0.9.3 でした。

ちょっと躓いたのでメモを残しておきます。

状況

対象のクラウドプロバイダは Google Cloud Platform。 Terraform 使う前にいくつか手動で作ったリソースもあるという状況。

なので Terraform でリソースを追加するために次の 2 ステップが必要。

  • State を既存リソースと同期させる
  • 新規 Resource を追加する

インストール

Homebrew でインストール。

brew install terraform

手順

手順をまとめるとこんな感じ。

  • 設定ファイルを作る
    • Provider
    • Backend
  • 初期化 terraform init
  • 既存リソースの同期
    • terraform import
    • 手動でごにょごにょ
    • terraform refresh
  • 新規リソースを追加

設定ファイルを作る

Provider と Backend の設定を tf ファイルとして作成する。 Provider は対象のクラウドプロバイダ、Backend は State を管理する場所を設定する。Backend を設定することで State をマシンローカルではなく S3 や GCS で管理できる。

terraform {
  backend "gcs" {
    project = "myproject"
    bucket  = "mybucket"
    path    = "terraform/terraform.tfstate"
  }
}

provider "google" {
  project     = "myproject"
  region      = "asia-northeast1"
}

なぜか Backend の方はファイルを読み込めず(String interpolation ができない)、設定ファイル内で JSON キーを指定できなかった。環境変数で指定するのがいいっぽい。

export GOOGLE_CREDENTIALS=credential.json

初期化

terraform init

.terraform/terraform.tfstate というファイルが作成される。tfstate とあるが State を管理するファイルではない。

.terraform/terraform.tfstate
{
    "version": 3,
    "serial": 0,
    "lineage": "1d9b8d9e-f6b3-4078-acfb-52d352f2cb02",
    "backend": {
        "type": "gcs",
        "config": {
            "bucket": "mybucket",
            "path": "terraform/terraform.tfstate",
            "project": "myproject"
        },
        "hash": 9958457764213601045
    },
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {},
            "depends_on": []
        }
    ]
}

既存リソースの同期

完全に新規でクラウド構築する場合以外はこの手順が一番めんどくさいはず。

Terraform は実行時に実在の Resource を取得して設定ファイルにある Resource との差分を計算するわけではなく、手元の State と Resource を照らし合わせて差分を埋めるという挙動になっている。 例えば myapp という VM が実在する状態で、myapp という VM を作成する Terraform を書いてしまうと、Terraform は State に myapp が存在しないのでエラーになったり VM を無理やり再作成したりする。

こういったことを防ぐためには State と既存 Resource とを同期する必要がある。

既存 Resource を State に反映させる公式な手段として terraform import がある。 ただし対応してない Resource もある上 Resource 定義(tf ファイル)は出力してくれない。

今回はダミーResource を定義して apply し、生成されたダミーResource の State を手作業で修正して既存 Resource の State を作成するという地道な作業を行った。

例えば既存の GCS のバケットを Terraform で管理するまでの手順は次のようになる。 まずはダミーの Resource を定義する。

resource "google_storage_bucket" "nownabe-mybucket" {
  name          = "nownabe-mybucket-dummy"
  location      = "ASIA-NORTHEAST1"
  storage_class = "REGIONAL"
}

この Resouce を apply する。

$ terraform plan
+ google_storage_bucket.nownabe-mybucket
    force_destroy: "false"
    location:      "ASIA-NORTHEAST1"
    name:          "nownabe-mybucket-dummy"
    self_link:     "<computed>"
    storage_class: "REGIONAL"


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


$ terraform apply
google_storage_bucket.nownabe-mybucket: Creating...
  force_destroy: "" => "false"
  location:      "" => "ASIA-NORTHEAST1"
  name:          "" => "nownabe-mybucket-dummy"
  self_link:     "" => "<computed>"
  storage_class: "" => "REGIONAL"
google_storage_bucket.nownabe-mybucket: Creation complete (ID: nownabe-mybucket-dummy)

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

State を取得する。

$ terraform state pull
{
    "version": 3,
    "terraform_version": "0.9.3",
    "serial": 0,
    "lineage": "ceab6126-6537-4726-ad0d-f60243c11f65",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "google_storage_bucket.nownabe-mybucket": {
                    "type": "google_storage_bucket",
                    "depends_on": [],
                    "primary": {
                        "id": "nownabe-mybucket-dummy",
                        "attributes": {
                            "force_destroy": "false",
                            "id": "nownabe-mybucket-dummy",
                            "location": "ASIA-NORTHEAST1",
                            "name": "nownabe-mybucket-dummy",
                            "self_link": "https://www.googleapis.com/storage/v1/b/nownabe-mybucket-dummy",
                            "storage_class": "REGIONAL"
                        },
                        "meta": {},
                        "tainted": false
                    },
                    "deposed": [],
                    "provider": ""
                }
            },
            "depends_on": []
        }
    ]
}

これをローカルに保存して、ID 等を既存の正しいものに書き換える。 書き換えが終わったら Backend に Upload する。

terraform state push

最後にダミーの Resource は忘れずに削除しておく。

こんな感じで地道に既存リソースと State を同期させていく。

terraform refresh という今実在している Resource を正として State を修正してくれるコマンドもあるが今回は必要なかったので使っていない。

おわりに

Terraform、ようやく 0.9 まできたかという感じでした。