Summary of Terraform v0.9

terraform0.9が出ました。

tfstateの管理を設定する方法が変わって、これは今のところは後方互換性がありますが0.11で旧方式が廃止されるみたいなので先日移行しました。

今回はほかに追加された機能についてもためしてみます。本家ブログの流れにだいたい沿って見ていきました。

うまく訳せず理解が追いつかないところも多いですがφ(..)メモメモ

Backendsについて

Backendsという概念が追加されて、これが結構重要そうなので先にまとめてみます。

Backendsとはterraformで管理されたリソースの状態(作成したリソースのIPアドレスのような実際の値)をどのように読み込むか、そしてapplyでどのような処理が実行されるかを決定する機構のようです。

いままでのtfstateファイルを抽象化したような感じかな?

Backendsは取得した状態をメモリ上で展開し利用します。ローカルのストレージ(HDDとか)に状態は保存されないので、セキュリティ上の利点があります。

ただし、デフォルトではlocal backendが使用されるので注意が必要です(local backendはその名の通りローカルストレージに状態を保存するBackends)。

  • local backendの場合以下のようにtfstateがapplyを実行したディレクトリに作成される
main.tf
terraform.tfstate
terraform.tfstate.backup

Backendsのストレージにはいくつかの種類がありterraformの状態をどのストレージで管理するか選ぶことが出来ます(S3、Consul、Atlas、GCSなど)。

Backendsを指定した場合は、.terraform/terraform.tfstateにBackendsの情報が保管されます。

{
    "version": 3,
    "serial": 0,
    "lineage": "0a998281-703a-48b2-b026-d4c8bb68e8da",
    "backend": {
        "type": "s3",
        "config": {
            "bucket": "hello-0-9",
            "key": "infra/terraform.tfstate",
            "region": "ap-northeast-1"
        },
        "hash": 4951114416268900123
    },
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {},
            "depends_on": []
        }
    ]
}

また、BackendsにはStandardEnhancedの2つの種類があります。

Standardは状態の管理と状態の保管、可能なら状態のロック(後述する)を提供するBackendsの種類で、EnhancedStandardに加えてplanapplyのリモート実行を提供するBackendsの種類です。

planapplyのリモート実行はまだどのBackendsにも実装されていないらしいです。

しかし、local backendはEnhancedに分類されています...。なんだかよくわかりませんね...。

とりあえずBackendsは既存のremoteコマンドを置き換えるtfstateの管理方法と認識しておけばいいと思われます...。

State Locking

State Lockingとは、状態に変更を加えるような作業(terraform applyなど)が行われている間、状態の変更をロックする仕組みです。

例えばチームで作業しているときに、別々の環境でterraform applyが実行されるなどして状態が壊れてしまったりすることを回避することが出来る仕組みなようです。

今までほしかったやつや…!

このState Lockingは(対応している)Backendが設定されていれば自動で適用され、State Lockingが実行されていることを示す標準出力なども特にありません。

-lockオプションを利用することでほとんどのterraformのコマンドでState Lockingを無効にすることが出来るみたいです(もちろん推奨はされない)。

BackendsのタイプによってState Lockingに対応しているものと対応していないものがあるで注意が必要です。

実際にState Lockingの動作をみてみます。State Lockingに対応しているBackendsと対応していないBackendsで、同時にstateに変更を加えるようなことをするとどのような違いが出るのか見てみます。

  • S3 backend: State Lockingに対応していない
  • local backend: State Lockingに対応している

以下のtfファイルを用意し、同時にapply(Terminalで別窓を開いて同時にapply)してみます。

  • State Lockingに対応していない場合のパターン
terraform {
  backend "s3" {
    bucket = "hello-0-9"
    key    = "infra/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

resource "arukas_container" "hello_0-9" {
  name      = "hello_0-9"
  image     = "nginx:latest"
  instances = 1
  memory    = 256

  ports = {
    protocol = "tcp"
    number   = "80"
  }

  provisioner "local-exec" {
    command = "echo Destroyed!!!"

    when = "destroy"
  }
}

すると、

  • リソースは2つ作成される
  • 先にapplyされた方がBackendsに記録される

という結果になりました。terraformの管理外のリソースができてしまいました。あかんですね。

次に、BackendsをS3 backendではなくlocal backendにして同様の処理を行ってみます。

  • State Lockingに対応している場合のパターン
resource "arukas_container" "hello_0-9" {
  name      = "hello_0-9"
  image     = "nginx:latest"
  instances = 1
  memory    = 256

  ports = {
    protocol = "tcp"
    number   = "80"
  }

  provisioner "local-exec" {
    command = "echo Destroyed!!!"

    when = "destroy"
  }
}

すると、以下のような結果となりました。

  • 後に実行したapplyがエラーを出して失敗する
  • .terraform.tfstate.lock.infoというファイルが作成される

Lockingされた!!✨

エラーの内容は以下です。

$ terraform apply
Error locking state: Error acquiring the state lock: resource temporarily unavailable
Lock Info:
  ID:        b3507eb4-b1df-9acb-35de-a37f4c43fbc9
  Path:      terraform.tfstate
  Operation: OperationTypeApply
  Who:       licorice@whiteoak.local
  Version:   0.9.1
  Created:   2017-03-19 06:07:24.995795437 +0000 UTC
  Info:


Terraform acquires a state lock to protect the state from being written
by multiple users at the same time. Please resolve the issue above and try
again. For most commands, you can disable locking with the "-lock=false"
flag, but this is not recommended.

.terraform.tfstate.lock.infoの内容は以下。

{
  "ID": "b3507eb4-b1df-9acb-35de-a37f4c43fbc9",
  "Operation": "OperationTypeApply",
  "Info": "",
  "Who": "licorice@whiteoak.local",
  "Version": "0.9.1",
  "Created": "2017-03-19T06:07:24.995795437Z",
  "Path": "terraform.tfstate"
}

ちなみに今のところState Lockingに対応しているBackendsはLocalとDynamoDBを経由したS3、Consulだけみたいです。ドキュメントを見るとS3 backendのページに以下の注意書きがあります。

Kind: Standard (with locking via DynamoDB)

This backend also supports state locking via Dynamo DB.

GCSの場合は完全にサポートしていない。

Kind: Standard (with no locking)

各BackendsもこのKindの部分をチェックしていけばState Lockingの対応の可否がわかりそうですね。

Destroy Provisioners

Destroy Provisionersとはリソースをdestroyした際に実行されるプロビジョナです。

例えば以下のプロビジョナがあったとして、

provisioner "local-exec" {
  command = "echo Destroyed!!!"
}

これをリソースが削除された場合にのみ実行したい場合、以下のように書くことが出来るようになりました。

provisioner "local-exec" {
  command = "echo Destroyed!!!"

  when = "destroy"
}

さっそく試してみます。以下のようなtfファイルを用意します。

resource "arukas_container" "hello_0-9" {
  name      = "hello_0-9"
  image     = "nginx:latest"
  instances = 1
  memory    = 256

  ports = {
    protocol = "tcp"
    number   = "80"
  }

  provisioner "local-exec" {
    command = "echo Destroyed!!!"

    when = "destroy"
  }
}

リソース作成時はlocal-execは実行されず、

$ terraform apply
arukas_container.hello_0-9: Creating...
  app_id:                 "" => "<computed>"
  endpoint:               "" => "<computed>"
  endpoint_full_hostname: "" => "<computed>"
  endpoint_full_url:      "" => "<computed>"
  image:                  "" => "nginx:latest"
  instances:              "" => "1"
  memory:                 "" => "256"
  name:                   "" => "hello_0-9"
  port_mappings.#:        "" => "<computed>"
  ports.#:                "" => "1"
  ports.0.number:         "" => "80"
  ports.0.protocol:       "" => "tcp"
arukas_container.hello_0-9: Still creating... (10s elapsed)
arukas_container.hello_0-9: Still creating... (20s elapsed)
arukas_container.hello_0-9: Still creating... (30s elapsed)
arukas_container.hello_0-9: Still creating... (40s elapsed)
arukas_container.hello_0-9: Still creating... (50s elapsed)
arukas_container.hello_0-9: Still creating... (1m0s elapsed)
arukas_container.hello_0-9: Still creating... (1m10s elapsed)
arukas_container.hello_0-9: Creation complete (ID: 29ab4cd2-...efe91004)

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

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path:

リソース削除時には実行されました!✨ ((local-exec): ...の部分)。

$ terraform destroy
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

arukas_container.hello_0-9: Refreshing state... (ID: 29ab4cd2-...efe91004)
arukas_container.hello_0-9: Destroying... (ID: 29ab4cd2-...efe91004)
arukas_container.hello_0-9: Provisioning with 'local-exec'...
arukas_container.hello_0-9 (local-exec): Executing: /bin/sh -c "echo Destroyed!!!"
arukas_container.hello_0-9 (local-exec): Destroyed!!!
arukas_container.hello_0-9: Destruction complete

使い所としては、destroy時にクラスタから離脱するようなコマンドを実行させたり、mackerelやslackなどにリソースがdestroyされたことを通知させたりできそうだな〜と思いました。

Interruptable Provisioners

時間のかかっているプロビジョナをすぐに中断できるようになりました。

以前は例えばapply中にCtrl-Cを叩いても、その時点で実行中のプロビジョナが終わるまで待っていましたが、これからはすぐに中断されるようになります。

即時中断されるのはプロビジョナの話で、リソースの作成は今まで通りGracefulに行われるっぽいです。

$ terraform apply

...
  ports.0.protocol:       "" => "tcp"
^CInterrupt received. Gracefully shutting down...
Interrupt received. Gracefully shutting down...
...

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

State Environments

State Environmentsとは同一のtfファイルのあるディレクトリで、複数のインフラストラクチャを作成できるものです。

これも今まで欲しかったやつ!

実際にやってみます。まずデフォルトのenvironmentを確認してみます。

$ terraform env list
named states not supported

エラー!!!

Backendsの種類によってはうまくいかない?local backendで再度試してみます。

$ terraform env list
* default

出来た。次に新しいenvironmentを作成してみます。

$ terraform env new test
Created and switched to environment "test"!

You're now on a new, empty environment. Environments isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

$ terraform env list
  default
* test

作成&選択されました!

この状態でapplyしておきます。

terraform apply

次に、デフォルトのenvironmentに切り替えます。

$ terraform env select default
Switched to environment "default"!

$ terraform env list
* default
  test

この状態でapplyしてみます。

terraform apply

すると、リソースが2つ作成されました!

terraform的には、terraform.tfstate.dディレクトリが掘られ、その下に作成したenvironmentのtfstateが作成されていました(defaultはその名の通りデフォルト用の特別なenvironmentなのでterraform.tfstate.d管理下には無い)。

terraform.tfstate.d
└── test
    └── terraform.tfstate

リモートのBackendsを利用している場合エラーが出たのはこの辺の操作が関係していそう...。

このState Environments機能を使えばProductionとStagingのインフラストラクチャの管理がより簡潔にできそうですね。

State Environmentsのベストプラクティス

最後にState Environmentsのベストプラクティスが書いてあったので読んでみます。

まず、State Environmentsはこの機能一つでProductionとStaging環境を分けて管理するような使い方をするものではありません。

ベストプラクティスは一つの大きな構成を複数の小さな構成に分割して、それぞれにterraform_remote_stateデータソースをリンクさせ管理します。

こうすることで、それぞれの小さな単位でProductionやStagingなどのenvironmentを分けることが出来ます。するとそれぞれの影響範囲を小さく抑えることが可能になり、チームで所有権を移譲しながら部分部分を開発していくことが容易になります。

ようはモノリシックな大きいtfファイルをProductionとStagingにわけると影響が大きすぎるし管理しづらいからtfstateごと分割してしまい、爆発しないようにしてバラバラに状態を環境をもたせると良いと言う感じ??

なんとなくわかるようなわからんような...。実際やってみないとしっくりこないかも。

Remote State Revamp

remoteのtfstateの管理周りについて大きく変わったけれど後方互換はterraform 0.11まであります。

初期化やセットアップはterraform initコマンドに一元化されました。

全てのterraformコマンド(applyやplan,consoleなど)はリモートの状態とシームレスに連携するようになったのでリーム開発がよりしやすくなりました。

あとはリモートの状態をより厳密にチェックするようになり、リモートの状態の操作信頼性がより向上したとのこと。

Provider Changes

今後Providerに大きな変更があり、タイムアウトを設定できるようになったりするらしい。

感想

疲れた!!!!!

新機能、熱いですね。ただまだ動作が怪しい感じがあるので、原因がわかればissue立てるなりしていきたいですね...。原因がわかれば...。

個人的に気になるのは、State Environmentsのベストプラクティス。今まで困っていたようなこと(リモートステートのチーム間での運用とか)が解消できそうな雰囲気が文章からにじみ出ているように感じました。

好きにできる環境でremote stateの分割とenvironmentの分割を試してみようと思います。

参考リンク

Backends周り。

State Locking周り。

State Environments周り。

Destroy Provisioners周り。