Terraform v0.9→v0.11までの変更点をまとめてみた

2017.11.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、佐伯です。

私自身がTerraformのバージョンアップについていけてなかったので、キャッチアップすべくいっきにv0.9からv0.11までの大きな変更をざっくり追ってみたいと思います。v0.9からなのはこちらでv0.8の紹介をしているためです。なお、プロバイダーごとの変更などは省いており、Terraform本体の機能についての変更を挙げています。

0.9.0 (March 15, 2017)

Remote Backends

v0.8では、Terraform管理対象リソースの状態を保存しているtfstateファイルをリモートに保存するためにterraform remote configでバックエンドの設定をしていました。v0.9からはremoteサブコマンドが廃止され、Terraformファイルで設定できるようになりました。

以下はS3にtfstateを保存する場合の例です。

v0.8:
v0.8までは初回にterraformn remote configでバックエンドの設定を行っていました。しかしTerraformファイルに書くことができず、メンバーやプロジェクトが増えるたびに「バックエンドの設定どうやるんだっけ?」とハマることが結構ありました。

$ terraform remote config \
    -backend=S3 \
    -backend-config="bucket=terraform-tfstate-bucket" \
    -backend-config="key=tfstate"

Remote configuration updated
Remote state configured and pulled.

v0.9:
v0.9からは以下のようにTerraformファイルにバックエンドの設定ができるようになり、設定手順などを別途用意する必要がなくなりました。

$ cat backend.tf
terraform {
  backend "s3" {
    bucket = "terraform-tfstate-bucket"
    key    = "tfstate"
    region = "ap-northeast-1"
  }
}
# terraform initで初期化または既存のtfstateと同期する
$ terraform init
Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

State Locking

複数の環境から同時にterraform applyを実行してしまうと、意図せぬ変更になったりtfstateを壊してしまう可能性があるため、planapplyを実行してる間、tfstateをロックする機能が追加されました。以下はS3をバックエンドにしてい場合の例です。S3がバックエンドの場合はDynamoDBを利用してState Lockを実装しています。

まず、State Lockの設定をする前にDynamoDBを作成しなければならないので、以下の設定でDynamoDBを作成します。RCU/WCUは1としています。

resource "aws_dynamodb_table" "terraform-state-lock" {
  name = "terraform-state-lock"
  read_capacity  = 1
  write_capacity = 1
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

バックエンドの設定にlock_tableを追加します。値は先ほど作成したDynamoDBのテーブル名です。(v0.9.7からlock_tableはDeprecated、代わりにdynamodb_tableで設定します)

terraform {
  backend "s3" {
    bucket     = "terraform-tfstate-bucket"
    key        = "tfstate"
    region     = "ap-northeast-1"
    lock_table = "terraform-state-lock"
  }
}

State Lockの設定を追加すると、バックエンド設定を変更したらterraform initせよとエラーメッセージが出力されます。

$ terraform plan
Backend reinitialization required. Please run "terraform init".
Reason: Backend configuration changed for "s3"

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

Changes to backend configurations require reinitialization. This allows
Terraform to setup the new configuration, copy existing state, etc. This is
only done during "terraform init". Please run that command now then try again.

If the change reason above is incorrect, please verify your configuration
hasn't changed and try again. At this point, no changes to your existing
configuration or state have been made.

Failed to load backend: Initialization required. Please see the error message above.

素直にterraform initを実行します。tfstateを新しいバックエンドにコピーする?と聞かれますが、今回はバケットもキーも変更していない同じバックエンドでありコピーする必要がないのでnoを選択しました。

$ terraform init
Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now reconfigure for this backend. If you didn't
intend to reconfigure your backend please undo any changes to the "backend"
section in your Terraform configuration.

Do you want to copy the state from "s3"?
Would you like to copy the state from your prior backend "s3" to the
newly configured "s3" backend? If you're reconfiguring the same backend,
answering "yes" or "no" shouldn't make a difference. Please answer exactly
"yes" or "no".

Enter a value: no

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your environment. If you forget, other
commands will detect it and remind you to do so if necessary.

以上でState Lockの設定は完了です。planapplyを実行するとDynamoDBのLockIDとInfoに下記のようなレコードが追加され、ロック中はレコードが存在しplanapplyが実行できません。アンロック時にレコードが削除される仕組みです。

なお、ロック中にplanapplyを実行すると以下のようなエラーメッセージが出力されます。ユーザーとホスト名出力されるのはわかりやすいですね。

$ terraform plan
Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed
status code: 400, request id: R0UOE0SCP1IJ4NDCM6K6FMIO1BVV4KQNSO5AEMVJF66Q9ASUAAJG
Lock Info:
ID: 6959406a-d096-173c-c9df-130d4a0c879d
Path: terraform-tfstate-bucket/tfstate
Operation: OperationTypePlan
Who: saiki.ko@HL00245.local
Version: 0.9.0
Created: 2017-11-20 10:13:42.610607 +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.

Destroy Provisioners

Provisionersがリソースの削除時にも実行できるようになりました。Provisioner自体使ったことないので具体的にこんなときに便利だよ!っていう具体的なユースケースが思いつきませんでした。すみません…。

0.10.0 (August 2, 2017)

Separate Provider Releases

AWS, GCP, Azureなどの各プロバイダーがTerraformコアから分割され、プラグイン形式で提供されるようになりました。それに伴いGitHubリポジトリも分割されています。

Terraform Providers · GitHub

Automatic Provider Installation

プロバイダーのプラグイン形式での提供に伴い、terraform initでプロバイダープラグインがカレントディレクトリの.terraform/plugins配下にダウンロードされるようになりました。

なお、v0.10.7のアップデートでプラグインキャッシュディレクトリを設定できるようになり、何度も同じプロバイダープラグインをダウンロードする必要がなくなりました。以下のように$HOME/.terraformrcまたは環境変数に設定をすることができます。

$HOME/.terraformrc:

plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"

環境変数:

export TF_PLUGIN_CACHE_DIR="$HOME/.terraform.d/plugin-cache"

プラグインキャッシュディレクトリを有効にすると、terraform init実行時にプラグイン配布サーバーから使用可能なプラグインの情報を取得します。プラグインのバージョンが選択されると、プラグイン配布サーバーからダウンロードするのではなく、まずプラグインキャッシュディレクトリに当該プラグインが存在しているかを確認し、存在していればそのプラグインをカレントディレクトリの.terraform/plugins配下にコピーして使用します。存在していなければプラグインキャッシュディレクトリにダウンロード後、カレントディレクトリの.terraform/plugins配下にコピーします。

CI/CDなどでTerraformを使用している場合、便利な機能だと思います。

Provider Constraints

こちらも同じくプロバイダーのプラグイン形式での提供に伴い、プロバイダープラグインのバージョンを設定ファイルで指定することができます。以下はAWSプロバイダープラグインのバージョンを1.3.1に固定しています。~> 1.0と設定し、1.0以上といった指定をすることもできます。

provider "aws" {
  version = "1.3.1"
}

State Environments

State Environmentsはv0.9のアップデートで追加された機能ですが、v0.10でサブコマンドがenvからworkspaceにリネームされたので、こちらに記載したいと思います。

これまでProduction, Staging, Developなど環境ごとにリソースを作成する場合、ディレクトリを環境ごとに分割しリソースを管理していました。v0.9からディレクトリを分割せずに複数環境を作成することができるようになりました。

workspaceはデフォルトだとdefaultが選択されています。

$ terraform workspace list
* default

terraform workspace newで新しいworkspaceを作成してみます。

$ terraform workspace new prod
Created and switched to workspace "prod"!

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

新しくworkspaceを作成すると作成したworkspaceが選択された状態になります。

$ terraform workspace list
default
* prod

workspaceを作成したタイミングでバックエンドにenv:/prod/tfstateが作成されています。

$ aws s3 ls s3://terraform-tfstate-bucket/env:/prod/tfstate
2017-11-21 14:58:32 282 tfstate

上記の様にenv:<workspace name>をバックエンド設定に追加して、tfstateを環境ごとにわけているようです。なお、今回はバックエンドをS3に設定している場合の例ですが、バックエンド設定を行っていない場合は.terraform.tfstate.d/に環境ごとのtfstateが保存されます。

しかし、ベストプラクティスには次ような記述がありました。

Workspaces can be used to manage small differences between development, staging, and production, but they should not be treated as the only isolation mechanism. As Terraform configurations get larger, it's much more manageable and safer to split one large configuration into many smaller ones linked together with the terraform_remote_state data source. This allows teams to delegate ownership and reduce the potential impact of changes. For each smaller configuration, you can use workspaces to model the differences between development, staging, and production. However, if you have one large Terraform configuration, it is riskier and not recommended to use workspaces to handle those differences.

大きな構成をworkspaceだけで分割すると管理対象リソース増えて大変だし、リソースが増えてくるとProductionにはこれが必要だけどStagingには必要なくて、Productionには不要だけどStaging, Developには必要みたいなことが多くなってくるのでworkspaceのみで環境の分離を実現するのではなく、小さな構成ごとにTerraformファイルを分割し、terraform_remote_stateデータソースを使用してtfstateをリンクするような形がいいよ!ということだと解釈しました。

terraform_remote_stateデータソースについては後日調べたうえでプログに書きたいと思います。なお、すでにworkspaceとterraform_remote_stateを使用したベストプラクティス構成を考えている方がいらっしゃったので、後日参考に試してみたいと思います。

0.11.0 (November 16, 2017)

CHANGELOGにそれっぽいタイトルがなくて、適当にタイトルをつけてますのでご了承ください。

モジュールバージョン設定の追加

v0.10.6からTerraformモジュールをTerraform Module Registryよりインストールして利用できるようになりました。v0.11.0からはTerraform Module Registryで公開されているモジュールバージョンの指定ができるようになりました。

Terraform v0.11.0でterraform-aws-modules/vpc/awsを使用してみます。

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a", "ap-northeast-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

上記設定でterraform initを実行すると1.3.0がダウンロードされました。

$ terraform init
Initializing modules...
- module.vpc
  Found version 1.3.0 of terraform-aws-modules/vpc/aws on registry.terraform.io
  Getting source "terraform-aws-modules/vpc/aws"

バージョンを1.0.0に固定しました。

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "1.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a", "ap-northeast-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

指定したバージョンがダウンロードされています。

$ terraform init
Initializing modules...
- module.vpc
  Found version 1.0.0 of terraform-aws-modules/vpc/aws on registry.terraform.io
  Getting source "terraform-aws-modules/vpc/aws"

モジュールバージョンの詳しい指定方法についてはこちらに記載があります。(なお、terraform-aws-modules/vpc/aws 1.0.0だとUsage通りの設定では動きませんでした…)

モジュールのプロバイダ設定継承ルールの変更

これまでマルチプロバイダー設定をしていた場合、モジュールのプロバイダー設定は呼び出し元のプロバイダー設定が継承され上書きされていました。v0.11.0からはエイリアスで追加したプロバイダー設定をモジュールで使用する場合は明示的にモジュールへ渡す必要があるとのことです。

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

provider "aws" {
  alias  = "virginia"
  region = "us-east-1"
}

module "example" {
  source    = "./example"
  providers = {
    aws = "aws.virginia"
  }
}

Applyはデフォルトで対話形式に変更

以前からterraform plan-outオプションは存在していましたが、terraform apply時にplanで出力したファイルを指定しないとyes or noの入力を求められるようになりました。

$ terraform apply 

~省略~

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:
$ terraform apply "<path/to/file>"

~省略~

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

terraform.tfstate.backupは作成されなくなる

リモートバックエンド設定時、ローカルのterraform.tfstate.backupは作成されなくなるので、各バックエンド側の機能でなんとかしてね!とのことです。S3の場合はバージョニング機能があるのでS3をリモートバックエンドに設定する際は必ず有効にしましょう。

アップグレードガイド

なお、Terraformのアップグレードを行う場合はアップグレードガイドがありますのでご確認ください。

最後に

全ての変更を拾えていないですが、いろいろなアップデートがあり纏めるのに時間がかかりました。また、拙い英語力で試しながら確認したので誤り等あればご指摘ください。

参考リンク