同僚に「早く言ってよ〜」と言われたTerraform小技

grasys加藤です。

今回はTerraformを使ってデプロイしている横で「え、何、どうやったの?」「早く言ってよ〜」と同僚に言われる小技を紹介します。

折に触れて当社イベントなどで紹介していますが、当社はGCPリソースの管理を HachiCorp 社のTerraformに集約しています。

Infrastructure as Code

再利用可能かつ再現性があることは作業性の向上に直結します。

Google謹製の Deployment Manager も使っていますが、他のリソースをバリバリ参照するプロジェクトで構成を記述するのはちょっと難しいです。(筆者のJinja2への造詣は浅い)


-targetオプション

tfファイルを作成・編集したけれどまだ部分的にデプロイしたい場面がありました。

同僚はおもむろに不要な部分のコメントアウトを始めたのですが、このような実行時コマンドオプションがあります。

terraform [plan|apply] -target=RESOUCE.NAME

例えば次のような静的アドレスとディスクを持つgrasysインスタンスを作成する予定があるとします。

# address
resource "google_compute_address" "address_grasys" {
  name = "grasys"
}

# attached_disk
resource "google_compute_disk" "disk_grasys" {
  name = "grasys"
  zone = "us-central1-a"
}

# instance
resource "google_compute_instance" "instance_grasys" {
  name         = "grasys"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-8"
    }
  }

  attached_disk {
    source  = "${google_compute_disk.disk_grasys.name}"
  }

  network_interface {
    network = "default"

    access_config {
      nat_ip = "${google_compute_address.address_grasys.address}"
    }
  }
}

よくあるパターンとしてはFirewallに登録するなどで先にIPアドレスだけ欲しい場合、tfファイルの6行目から末尾までコメントアウトするのもいいのですが -target オプションをつけると対象のリソースを選択することができます。

-targetなし

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

  + google_compute_address.address_grasys
      id:                                                  <computed>
      address:                                             <computed>
      address_type:                                        "EXTERNAL"
      name:                                                "grasys"
      project:                                             <computed>
      region:                                              <computed>
      self_link:                                           <computed>

  + google_compute_disk.disk_grasys
      id:                                                  <computed>
      disk_encryption_key_sha256:                          <computed>
      label_fingerprint:                                   <computed>
      name:                                                "grasys"
      project:                                             <computed>
      self_link:                                           <computed>
      size:                                                <computed>
      type:                                                "pd-standard"
      users.#:                                             <computed>
      zone:                                                "us-central1-a"

  + google_compute_instance.instance_grasys
      id:                                                  <computed>
      attached_disk.#:                                     "1"
      attached_disk.0.device_name:                         <computed>
      attached_disk.0.disk_encryption_key_sha256:          <computed>
      attached_disk.0.source:                              "grasys"
      boot_disk.#:                                         "1"
      boot_disk.0.auto_delete:                             "true"
      boot_disk.0.device_name:                             <computed>
      boot_disk.0.disk_encryption_key_sha256:              <computed>
      boot_disk.0.initialize_params.#:                     "1"
      boot_disk.0.initialize_params.0.image:               "debian-cloud/debian-8"
      boot_disk.0.initialize_params.0.size:                <computed>
      boot_disk.0.initialize_params.0.type:                <computed>
      can_ip_forward:                                      "false"
      cpu_platform:                                        <computed>
      create_timeout:                                      "4"
      guest_accelerator.#:                                 <computed>
      instance_id:                                         <computed>
      label_fingerprint:                                   <computed>
      machine_type:                                        "n1-standard-1"
      metadata_fingerprint:                                <computed>
      name:                                                "grasys"
      network_interface.#:                                 "1"
      network_interface.0.access_config.#:                 "1"
      network_interface.0.access_config.0.assigned_nat_ip: <computed>
      network_interface.0.access_config.0.nat_ip:          "${google_compute_address.address_grasys.address}"
      network_interface.0.address:                         <computed>
      network_interface.0.name:                            <computed>
      network_interface.0.network:                         "default"
      network_interface.0.network_ip:                      <computed>
      network_interface.0.subnetwork_project:              <computed>
      project:                                             <computed>
      scheduling.#:                                        <computed>
      self_link:                                           <computed>
      tags_fingerprint:                                    <computed>
      zone:                                                "us-central1-a"


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

-targetあり

$ terraform apply -target=google_compute_address.address_grasys

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:

  + google_compute_address.address_grasys
      id:           <computed>
      address:      <computed>
      address_type: "EXTERNAL"
      name:         "grasys"
      project:      <computed>
      region:       <computed>
      self_link:    <computed>


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

小技です。

igonore-changes

tfファイルを編集してplanするとforces new resouurceがでてしまう場面がありました。変更する内容によってはchanageではなくて、destroy & addとなります。

同僚は諦めて別のフォルダにファイルをコピー、既存環境に影響がないように書き換え始めたのですが、よい設定があります。

  lifecycle {
    ignore_changes = [
      "RESOURCE"
    ]
  }

ここで既に3インスタンスがサービスインしているところに、2インスタンスを追加する例です。

追加インスタンスのイメージは稼働中インスタンスから作成したものに置換します。

tfファイルの編集箇所を -> で示しています。

# instance
resource "google_compute_instance" "instance_grasys" {
  name         = "${format("grasys%02d", count.index + 1)}"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"
  count        = 3 -> 5

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-8" -> "CREATED_IMAGE"
    }
  }

  attached_disk {
    source  = "${google_compute_disk.disk_grasys.name}"
  }

  network_interface {
    network = "default"

    access_config {
      nat_ip = "${google_compute_address.address_grasys.address}"
    }
  }
}

このような編集をした場合、既存の3つのインスタンスのimageは変更できませんので、force new resourceと表示されます。このままapplyすると一度destroyされてしまうので一度サービス停止状態になってしまいます。

以下、長くなるので部分部分で省略してます。

-/+ destroy and then create replacement

Terraform will perform the following actions:

-/+ google_compute_instance.instance_grasys[0] (new resource required)
      id:                                                  "grasys01" => <computed> (forces new resource)
      ~
      boot_disk.0.initialize_params.0.image:               "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20180401" => "CREATED_IMAGE" (forces new resource)
      ~
-/+ google_compute_instance.instance_grasys[1] (new resource required)
      id:                                                  "grasys01" => <computed> (forces new resource)
      ~
      boot_disk.0.initialize_params.0.image:               "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20180401" => "CREATED_IMAGE" (forces new resource)
      ~
-/+ google_compute_instance.instance_grasys[2] (new resource required)
      id:                                                  "grasys01" => <computed> (forces new resource)
      ~
      boot_disk.0.initialize_params.0.image:               "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20180401" => "CREATED_IMAGE" (forces new resource)
      ~
  + google_compute_instance.instance_grasys[3]
      ~
  + google_compute_instance.instance_grasys[4]
      ~

Plan: 5 to add, 0 to change, 3 to destroy.

ここでさきほどのignore_changesを使うと変更箇所を無視してくれます。

具体的には変更があるけれど無視するプロパティを指定します。ワイルドカードを使うことも可能です。

# instance
resource "google_compute_instance" "instance_grasys" {
  name         = "${format("grasys%02d", count.index + 1)}"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"
  count        = 3 **-> 5**

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-8" **-> "CREATED_IMAGE"**
    }
  }

  attached_disk {
    source  = "${google_compute_disk.disk_grasys.name}"
  }

  network_interface {
    network = "default"

    access_config {
      nat_ip = "${google_compute_address.address_grasys.address}"
    }
  }

  lifecycle {
    ignore_changes = [
      "boot_disk.0.initialize_params.0.image",
    ]
  }
}

既存インスタンスの変更なしに2台追加することが出来ます。

  + create

Terraform will perform the following actions:

  + google_compute_instance.instance_grasys[3]
      ~
  + google_compute_instance.instance_grasys[4]
      ~

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

-plugin-dir

こちらは同僚から教わったコマンドオプションです。

Terraform v0.10.0から各プロバイダがプラグインになりました。AWS,GCP,Azureなどのファイル、結構大きいので助かります。

でもtfファイルを複数ディレクトリに分割して管理していると、その度にプログインをインストールするの? .terraform/plugins をsymbolic linkで持ち回るの?と思っていたら便利なオプションがありました。

$ terraform init -plugin-dir /path/to/plugins/linux_amd64/

これで1箇所のpluginsディレクトリを参照してくれました。Thanks.


まとめ

普段使いしているミドルウェアにも色々と知らないオプションがあったりするので、たまにはドキュメントを読んでみると良いことがあるかも!