Terraform 0.15の変更点を調べた

2021.05.31

TerraformのVersion 0.15がGAになって(2021/04/15)からだいぶ経ってしまいましたが、Webinarを視聴したり、実際に0.15を触ったりして何が変わったのか色々調べたのでレポートします。 (すべての変更点を網羅しているわけではないので悪しからず。)

0.14、1.0との互換性

Terraform Version 0.15は1つ前の0.14、そして今後リリースされる Version1.0と互換性があります。これはVersion 0.14の紹介でも説明されていましたね。Version間の移行(多くの場合Versionアップだと思いますが)が容易になります。

Remote State Data SourceのVersion間互換

  • ~> 0.15.0
  • ~> 0.14.0
  • ~> 0.13.6
  • ~> 0.12.30

上記Versionに当てはまるTerraformは、互いのRemote State Data Sourceにアクセスできます。今後リリースされる1.0.xまでのVersionについても同じ対応がされる予定です。既存の古いバージョンのTerraformと連携する必要がある場合でも、最新のバージョンのTerraformを使いやすくなりますね。

コンソールエクスペリエンスの統合

サポートしている全プラットフォームで一貫したコンソールエクスペリエンスを得られるようになりました。WindowsにてUTF-8とバーチャルコンソールをサポートしました。バーチャルコンソールに関しては、これまでのTerraformは、Windows独自の(=他プラットフォームと互換性のない)コンソールウインドウに対応するためのレイヤーを使用することで対応していました。ただしこの事により、進行状況インジケーターなど一部のターミナル機能が使えなくなっていました。Windows10のあるアップデート以降、他プラットフォームに似たバーチャルコンソールが導入されたので、バージョン0.15からは前述のレイヤーを使わないように修正が入りました。

※ Windowsわからないのでこのあたりの説明間違っているかもしれません。間違っている箇所あればご指摘いただければ幸いです。

Provider Sensitivity Graduates to Production

0.14でexperimental featureとして提供されていたprovider_sensitive_attrsがGAになりました。つまり、0.14ではterraformブロックにて明示的にこの機能を有効化する必要がありましたが、0.15からはその必要はなく、デフォルトの挙動になりました。

どういう機能か説明します。

0.14で変数の引数として、それ以前のバージョンでもOutputの引数としてsensitiveがありました。これは該当の変数やOutputの値をterraform planterraform applyなどの出力時に隠すものです。例えば、Terraformのアウトプットを何らかのロギングシステムやVCS(バージョンコントールシステム)に記録している場合、この機能が役立つでしょう。(ただし、この機能を使ってもStateファイルに変数値が書き込まれる点は変わりませんので、依然としてStateファイルの扱いには気をつけましょう。)

そして、providerもそこで使われる引数値をsensitiveと定義することができます。例えばRDSインスタンスを作成する aws_db_instanceリソースです。password引数はsensitiveと設定されています。つまりterraform planterraform applyなどの出力時にはこのpasswordの値は隠蔽化されてます。

db-password

なのですが、もしこのpassword値を参照している他の変数やOutputがあった場合、その値は隠蔽化されませんでした。 password-as-output

0.14ではこういった場合、experimental featureを有効化すれば、先のpassword値を参照している他の変数やOutput値も隠蔽化されました。

0.15ではこういった場合、experimental featureを有効化する必要はありません。代わりにエラーになります。

015-propagated

以下のように、sensitive = trueを追加すると…

 output password {
   value = aws_db_instance.default.password
+  sensitive = true
 }

Outputも隠蔽化した状態でapplyが成功しました。 015-sensitive-true

まとめると、providerが設定したsensitive引数に関しても、あなたが設定したsensitiveな変数やOutputと同様な扱いをしてくれるようになったということです。

Sensitive/Nonsensitive Function

上記例でエラーを修正する別パターンとして、nonsensitive Functionを使う方法もあります。これはその名の通り、明示的にsensitive設定を解除する関数です。

 output password {
+   value = nonsensitive(aws_db_instance.default.password)
-   value = aws_db_instance.default.password
 }

もちろんこの場合Output値は隠蔽化されません。 015-nonsensitive

真逆の働きをする sensitive Functionもあります。

Providerの設定にconfiguration_aliases追加

Module内でproviderのaliasを使う際の設定方法の変更です。(これまでそういった構成を経験したことがなかったので理解に手こずりました…)

例えば、2つのリージョンそれぞれに1つずつVPCを作成するようなModuleを作るとします。

0.14の場合

0.14でこういったModuleを作った場合、以下のようなコードになります。

module/main.tf

provider "aws" {
  alias = "second"
}

resource "aws_vpc" "first" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "first"
  }
}

resource "aws_vpc" "second" {
  provider   = aws.second
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "second"
  }
}

最初の3行で、 aws providerのaliasを定義しているのがポイントです。

このModuleを呼び出す側のコード例は以下です。東京リージョンと大阪リージョンでVPCを作成します。

terraform {
  required_version = "~> 0.14.11"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "3.38.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

provider "aws" {
  alias  = "osaka"
  region = "ap-northeast-3"
}

module "vpc" {
  source = "./module/"
  providers = {
    aws.second = aws.osaka
  }
}

最後のModuleブロックでprovidersブロックを定義して、alias=osakaのproviderを渡しています。 aliasのproviderはこのように明示的にModuleに渡す必要があります。一方デフォルトproviderはこのように書かなくてもModuleに自動的に継承されます。

0.15の場合

同じコードを0.15で実行(terraform apply)してみましょう。Warningが出ました。

╷
│ Warning: Empty provider configuration blocks are not required
│ 
│   on module/main.tf line 1:
│   27: provider "aws" {
│ 
│ Remove the aws.second provider block from module.vpc. Add aws.second to the list of configuration_aliases for
│ aws in required_providers to define the provider configuration name.
╵

はい。エラーメッセージに書かれているとおりですが、

  • alias.secondのaws providerブロックが不要になりました。
  • 代わりにterraformブロック内にrequired_providersブロックを定義し、そこの引数configuration_aliasesにalias.secondを書きます。

module/main.tf

+terraform {
+  required_providers {
+    aws = {
+      source = "hashicorp/aws"
+      version = "3.38.0"
+      configuration_aliases = [aws.second]
+    }
+  }
+}
-provider "aws" {
-  alias = "second"
-}

 resource "aws_vpc" "first" {
   cidr_block = "10.0.0.0/16"
   tags = {
     Name = "first"
   }
 }
 
 resource "aws_vpc" "second" {
   provider   = aws.second
   cidr_block = "10.0.0.0/16"
   tags = {
     Name = "second"
   }
 }

実行(terraform planterraform apply)前に一度terraform initする必要があります。

これで先程のWarningを消すことができました。

この変更により、Module内で使われるProvider、またAliasのProviderがわかりやすくなります。特にModule作成者と使用者が異なる場合によりModuleを理解しやすく、使いやすくなるでしょう。

コアとプロバイダーのログレベルを分けて設定できるように

TF_LOG環境変数に値TRACE, DEBUG, INFO, WARN, ERRORを設定することで、Terraformのログを設定した値のレベルで出力できるようになります。0.15よりTerraformのコア部分が吐き出すログと、各プロバイダーが吐くログのレベルを別々に設定できるようになりました。それぞれTF_LOG_CORETF_LOG_PROVIDERという環境変数を使います。設定する値はTF_LOGと同じくTRACE, DEBUG, INFO, WARN, ERRORのいずれかです。

未宣言変数の使用が非推奨に

Terraform内で変数を使う場合、通常はvariableブロックで変数を宣言してから、任意の場所でその変数を参照します。

が、この変数宣言無しで変数を使うこともできます。変数用のファイルを読み込ませる場合です。(参考)

このような使用をした場合は、以下のような、variableブロックちゃんと書いてね、もしくは環境変数にしてね、というwarningが出るようになりました。 015-undeclared-warning

(という説明だったのですが、0.14.0で動作検証してもこのwarning出ました。。いつから追加されたのかよくわからないです…)

list() と map() 関数が tolist() と tomap()に置き換え

Version 0.12からlist()map() 関数はそれぞれ [ ... ]{ ... }に置き換わりました。0.15からはこれらの関数を使うとエラーになります。

╷
│ Error: Error in function call
│ 
│   on variables.tf line 3, in locals:
│    3:   aa = list("a", "b", "c")
│ 
│ Call to function "list" failed: the "list" function was deprecated in Terraform v0.12 and is no longer available; use tolist([
│ ... ]) syntax to write a literal list.

代わりにtolist()tomap()を使いましょう。

- list("a", "b", "c")
+ tolist(["a", "b", "c"])

- map("a", 1, "b", 2)
+ tomap({ a = 1, b = 2 })

もしくは、多くの場合では関数を使わず単に [ ... ]{ ... }を使うだけで十分です。

0.11スタイルの変数型指定廃止、ダブルクオートで囲む必要なし

variable "hoo" {
  type = "string"
}

こういうコードを書くと以下エラーになります。

│ Error: Invalid quoted type constraints
│ 
│   on variables.tf line 8, in variable "aaaa":
│    8:   type = "string"
│ 
│ Terraform 0.11 and earlier required type constraints to be given in quotes, but that form is now deprecated and will be removed
│ in a future version of Terraform. Remove the quotes around "string".
╵

ダブルクオートが不要になりました。

 variable "hoo" {
-  type = "string"
+  type = string
 }

Apple M1対応はまだ

Webinarの中では、Providerも併せて対応しないと意味がないから…今各Providerに展開中だからStay Tuned!みたいな説明でした。

Azure Backend引数からarm_prefixを削除

Azure Backendの引数のうちprefixとしてarm_がついているものは以前より非推奨でしたが、0.15から完全に削除されました。今後はarm_prefixを削除した引数に置き換える必要があります。

terraform plan terraform apply-replace=オプション追加

Version 0.15.2からです。-replace=の引数値としてリソースのアドレス(terraform state listで見れるやつ)を指定すると、そのリソースを置き換えるplanapplyになります。

terraform plan terraform apply-destroyオプション追加

Version 0.15.2からです。terraform apply -destroyterraform destoryのエイリアスです。terraform plan -destroyはdestroyの実行計画のみみるオプションです。

terraform destroyは今後terraform apply -destroyに置き換わるのかな?と思いましたが、terraform destroyを今後非推奨にする計画は今のところ無いそうです。

terraform外での変更を区別可能に

Version 0.15.4からです。terraform plan terraform applyの際に適用される差分が表示されますが、その差分が、Terraformのコードを最後のapply以降更新したから発生したものではなく、Terraform外でリソースを更新したから発生したものである場合、そのことがわかるような出力になります。

例えば、以下のようなVPCを作成するコードがあるとします。

resource "aws_vpc" "first" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "first"
  }
}

これをterraform applyしてVPCを作成した後に、AWSのマネコンにて以下のようにVPC名を変えたとします。 015-vpc-name-modified

その後再度 terraform planterraform applyすると以下のような出力になります。 横線で区切られるまでの前半部分が、Terraform外でリソース更新を検知したことを伝えています。

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # module.vpc.aws_vpc.first has been changed
  ~ resource "aws_vpc" "first" {
        id                               = "vpc-0f483d0fdb450f1e3"
      ~ tags                             = {
          ~ "Name" = "first" -> "first-modified"
        }
      ~ tags_all                         = {
          ~ "Name" = "first" -> "first-modified"
        }
        # (14 unchanged attributes hidden)
    }

Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.vpc.aws_vpc.first will be updated in-place
  ~ resource "aws_vpc" "first" {
        id                               = "vpc-0f483d0fdb450f1e3"
      ~ tags                             = {
          ~ "Name" = "first-modified" -> "first"
        }
      ~ tags_all                         = {
          ~ "Name" = "first-modified" -> "first"
        }
        # (14 unchanged attributes hidden)
    }

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

これは嬉しい機能ですね!差分がコードの更新によって発生したものなのか、はたまたTerraform外で修正したから発生したものなのかの判断が容易になり、より自信をもってapplyできるようになります。

また、これに伴い、terraform apply-refresh-onlyというオプションが追加されました。先程の例でこのオプションを使うと、以下のようになります。

% terraform apply -refresh-only
module.vpc.aws_vpc.first: Refreshing state... [id=vpc-0f483d0fdb450f1e3]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # module.vpc.aws_vpc.first has been changed
  ~ resource "aws_vpc" "first" {
        id                               = "vpc-0f483d0fdb450f1e3"
      ~ tags                             = {
          ~ "Name" = "first" -> "first-modified"
        }
      ~ tags_all                         = {
          ~ "Name" = "first" -> "first-modified"
        }
        # (14 unchanged attributes hidden)
    }

This is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without changing any remote
objects.

Would you like to update the Terraform state to reflect these detected changes?
  Terraform will write these changes to the state without modifying any real infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value:

はい。-refresh-onlyという名前通り、Stateファイルの更新のみを行なうapplyです。リソースの更新は行ないません。従来から似たような機能として terraform refreshというコマンドがありますが、こちらはただStateファイルを現在のリソースの状態に同期させるもので、どのような差分があったのか出力してくれませんし、また terraform apply -refresh-only のように更新してよいかどうか確認もしてくれません。そのため今後は多くのケースでterraform apply -refresh-onlyを使っていくほうが良いかと思います。

参考情報