Terraform 0.9を使ってみた

created at 2017/04/15 23:49:58

はじめに

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まできたかという感じでした。