どうも

独り Terraform 研究所, 所長兼研究員のかっぱです.

以下のような Terraform に関する記事を書いてから, 2 年以上が経過して, すごく久しぶりに Terraform を触る機会を得たので 2 年のブランクを少しでも埋めるべく Terraform について, 自分が気になる部分を掻い摘んで研究していきたいと思います.


inokara.hateblo.jp

尚, 本記事で利用している環境は以下の通りとなります.

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G65

$ terraform --version
Terraform v0.11.8

また, 英文の翻訳には Google 翻訳を利用しておりますが, 一部には自分による意訳が含まれている為, 実際の内容とは異なる可能性がある旨, 何卒ご了承の程よろしくお願い致します.

Backends

ドキュメント

A backend in Terraform determines how state is loaded and how an operation such as `apply` is executed. This abstraction enables non-local file state storage, remote execution, etc.

www.terraform.io

状態の管理

  • Terraform で構築したインフラの状態 (State) を管理する
  • Backend 自体は抽象レイヤーで, 実際にはリモートのオブジェクトストレージ等を選択して状態を保存することが出来る
  • デフォルトの設定は local Backend で状態はローカル PC に保存される

メリット

Backend のメリットについては, ドキュメントのおいては以下のように言及されています.

  • リモートに状態を保存した上で, その状態をロックすることで, 状態の破損を防ぐことが出来る
  • リモートストレージを利用することで, 機密情報をローカルディスクに保存する必要がなくなる
  • いくつかの Backend については, 遠隔操作が可能となる為, apply して放置ということが出来る (リモートストレージとロックを組み合わせる)

特にチームで Terraform を触る際にこれらのメリットの恩恵を受けることが出来ると思いますが, 個人だけで触る場合でも, Backend をうまく使いこなすことができれば, どこでも, いつでも同じ状態を簡単に構築, 再現することが出来ると思います.

設定諸々

  • terraform init を実行して, Backend を初期化する必要がある (init は Terraform を始めるにあたり, 必ず実行する必要がある)
  • 設定自体は Terraform ファイルの terraform セクションに記述するか, init コマンドのオプションとして, コマンドラインからも指定することが出来る

以下は Amazon S3 を Backend として利用する場合の設定例です.

teraform {
  backend "s3" {
    bucket = "oreno-terraform-state"
    key    = "terraform.tfstate.docker"
    region = "ap-northeast-1"
  }
}
  • Backend を変更する (例えば, local から s3 に変更する) 場合にも init コマンドを使う

以下は init コマンドの実行例です.

$ terraform init
Terraform has detected you're unconfiguring your previously set "s3" backend.
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "s3" backend to the
  newly configured "local" backend. No existing state was found in the newly
  configured "local" backend. Do you want to copy this state to the new "local"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes




Successfully unset the backend "s3". Terraform will now operate locally.
...

上記の例では, 従来は S3 Backend の利用を定義していたものを削除し, terraform init を実行しています.

  • workspace を利用している場合, 各 workspace 毎に状態 (tfstate) が作成される

以下は local Backend を用いて, test1 及び test2 という workspace を作成して planapply を行った結果です.

$ tree .
.
├── main.tf
├── terraform.tfstate
└── terraform.tfstate.d
    ├── test1
    │   └── terraform.tfstate
    └── test2
        └── terraform.tfstate

3 directories, 4 files

terraform.tfstate.d ディレクトリが作成され, その直下に各 workspace 名のディレクトリが作成され, その直下にそれぞれの tfstate ファイルが作成されています.

Backend Types

二種類の Backend Type

Backend Type は大きく, 提供する機能によって以下の二種類に分けられる.

  • Standard
    • 状態の管理, 状態保存とロック機能を提供
  • Enhanced
    • Standard の機能に合わせて, リモート操作を提供

「リモート操作」については, ここでは大きくは触れないが, ドキュメントによると…

Some backends support the ability to run operations (refresh, plan, apply, etc.) remotely. Terraform will continue to look and behave as if they’re running locally while they in fact run on a remote machine.

と書かれており, Enhanced タイプの Backend を利用することで, Terraform の実行をリモートで行いつつ, あたかもローカル環境で実行されているかのように振る舞う機能が利用出来るようだ.

Enhanced Backends

Enhanced Backends には以下の二種類があります.

  • local
  • remote

local はデフォルトの Backend で明示的に path を指定しない場合にはカレントディレクトリに tfstate ファイルは保存されるようです. 以下は, 明示的に path の設定を行った例です.

terraform {
  backend "local" {
    path = "relative/path/to/terraform.tfstate"
  }
}

Standard Backends

Standard Backends の中でも単体で locking をサポートするものとしないもので分けられているようです. (locking についてはこの後で触れる予定.)

タイプ locking サポート メモ
artifactory no artifactory なんて初めて聞いたサービス.
azurerm yes Azure Storage のネイティブ機能を使用した状態ロックと整合性チェックもサポートする.
consul yes Consul のキー・バリューストアを利用, 状態ロックもサポートする
etcd no etcd v2.x のパスを利用する.
etcdv3 yes etcd v3 のキー・バリューストアを利用, 状態ロックもサポートする.
gcs yes Google Cloud Storage を利用, 状態ロックもサポートする.
http optional REST クライアントを利用して状態を保存する.
manta yes manta というオブジェクトストレージを利用する. manta 内で状態ロックも行う.
s3 DynamoDB を利用 Amazon S3 を利用, バージョニングを有効にすることを推奨, DynamoDB を利用して状態ロックと整合性チェックもサポートする.
swift no オブジェクトストレージの swift を利用する. s3 同様にバージョニングを有効にすることを推奨.
terraform enterprise no 旧 Atlas を利用, locking サポートしているかと思いきや…

State Locking

ドキュメント

Terraform stores state which caches the known state of the world the last time Terraform ran.

www.terraform.io

Backends are configured directly in Terraform files in the `terraform` section.

www.terraform.io

State Locking とは

  • Backend でサポートされている場合, Terraformは状態を書き込む可能性があるすべての操作について状態をロックする
  • 状態をロックすることにより, それぞれ別の変更を加えようとした際に tfstate が破壊されることを防ぐ
  • 状態のロックは, tfstate に書き込みが発生する可能性のあるすべての操作で自動的に行われる
  • 状態のロック自体が失敗すると, Terraform は処理を中断する

例えば…

Backend Type で Amazon S3 を選んだ場合には, S3 自体には状態をロックし, 整合性をチェックするような機能は提供されていない為, DynamoDB テーブルを利用してこれらの機能を提供することになります.

また, ロックの際に保存される情報は, 以下のコードで確認することが出来ます.

Terraform is a tool for building, changing, and combining infrastructure safely and efficiently. - hashicorp/terraform

github.com

  info := &LockInfo{
        ID:      id,
        Who:     fmt.Sprintf("%s@%s", userName, host),
        Version: version.Version,
        Created: time.Now().UTC(),
    }

フムフム.

Docker Providor + Backend Type S3

せっかくなので

Docker Provider を使ってコンテナを作りつつ, Backend の挙動等について確認 (という名のチュートリアル) をしてみたい.

簡単な main.tf

基本となる Terraform ファイルは以下の通りです.

resource "docker_container" "hoge" {
    image = "${docker_image.centos.latest}"
    name = "hoge-${terraform.workspace}"
    hostname = "hoge-${terraform.workspace}"
    command = ["/bin/sh", "-c", "while true ; do sleep 1; hostname -s ; done"]
}

resource "docker_container" "fuga" {
    image = "${docker_image.centos.latest}"
    name = "fuga-${terraform.workspace}"
    hostname = "fuga-${terraform.workspace}"
    command = ["/bin/sh", "-c", "while true ; do sleep 1; hostname -s ; done"]
}

resource "docker_container" "popo" {
    image = "${docker_image.centos.latest}"
    name = "popo-${terraform.workspace}"
    hostname = "popo-${terraform.workspace}"
    command = ["/bin/sh", "-c", "while true ; do sleep 1; hostname -s ; done"]
}

resource "docker_image" "centos" {
    name = "centos:6"
}

コンテナ作成

workspace を事前にいくつか作ってしまっていたので, 今回は default に戻した状態でコンテナを作成します.

$ terraform workspace select default
$ terraform plan
$ terraform apply
...
docker_container.popo: Creation complete after 0s (ID: 69c652297dd3a7cfd30c91a428f0ae026ec8611090914bf8e6a0ba0eb937c796)
docker_container.fuga: Creation complete after 0s (ID: f203969cd96e0de6e1eec8dee1dc866b87d0eeab2c3b0609304d9f575f63b0d2)
docker_container.hoge: Creation complete after 0s (ID: f4a7331dd4e436920a28fcb9c98372c453f95f21d5e7718bf68170266a8bdff2)

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

サクッと, コンテナが起動します.

$ docker ps | grep 'default'
f4a7331dd4e4        b5e5ffb5cdea        "/bin/sh -c 'while t…"   About a minute ago   Up About a minute                       hoge-default
f203969cd96e        b5e5ffb5cdea        "/bin/sh -c 'while t…"   About a minute ago   Up About a minute                       fuga-default
69c652297dd3        b5e5ffb5cdea        "/bin/sh -c 'while t…"   About a minute ago   Up About a minute                       popo-default

この時に tfstate ファイルは, main.tf が保存されているディレクトリと同じディレクトリに作成されています.

$ ls main.tf terraform.tfstate
main.tf                 terraform.tfstate

Backend を S3 に変える

この状態から Backend を Amazon S3 (以後, S3) に変更してみたいので main.cf に以下を追記します.

terraform {
  backend "s3" {
    bucket = "oreno-terraform-state"
    key    = "terraform.tfstate.docker"
    region = "ap-northeast-1"
  }
}

順番はどちらでも良いけど, S3 バケットを作成します. この S3 バケット自体も terraform で作ることが出来るようですが, 今回は AWS CLI で作成します.

# バケットを作成
$ aws s3 mb s3://oreno-terraform-state

# バケットバージョニングを有効
$ aws s3api put-bucket-versioning \
  --bucket=oreno-terraform-state \
  --versioning-configuration Status=Enabled

# バケットを暗号化する
$ aws s3api put-bucket-encryption --bucket=oreno-terraform-state \
  --server-side-encryption-configuration '{
  "Rules": [
    {
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      }
    }
  ]
}'

作成したら, terraform init コマンドを --reconfigure オプションをつけて実行します.

$ terraform init --reconfigure

Initializing the backend...
Do you want to migrate all workspaces to "s3"?
  Both the existing "local" backend and the newly configured "s3" backend support
  workspaces. When migrating between backends, Terraform will copy all
  workspaces (with the same names). THIS WILL OVERWRITE any conflicting
  states in the destination.

  Terraform initialization doesn't currently migrate only select workspaces.
  If you want to migrate a select number of workspaces, you must manually
  pull and push those states.

  If you answer "yes", Terraform will migrate all states. If you answer
  "no", Terraform will abort.

  Enter a value: yes


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

上記のように, tfstate ファイルは S3 にコピーする旨の確認メッセージが出力され, yes を入力すると S3 にコピーが完了します. 念の為, 確認してみると以下のように S3 にアップロードされていることが判ります.

# S3 上の tfstate ファイルを確認
$ aws s3 ls s3://oreno-terraform-state/
                           PRE env:/
2018-09-08 14:51:53       5662 terraform.tfstate.docker

# オブジェクト (tfstate ファイル) がバージョニングされていることを確認
$ aws s3api list-object-versions --bucket=oreno-terraform-state
{
    "Versions": [
        {
            "ETag": "\"64b530af2d748c77a5d68b5448ac76de\"",
            "Size": 5649,
            "StorageClass": "STANDARD",
            "Key": "env:/test1/terraform.tfstate.docker",
            "VersionId": "2.eNdgnznHlbxkc_hMJ6NEz9AIcibcwO",
            "IsLatest": true,
            "LastModified": "2018-09-08T05:51:57.000Z",
            "Owner": {
                "DisplayName": "kappaahoaho",
                "ID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            }
        }
...
        

# オブジェクト (tfstate ファイル) が暗号化されていることを確認
$ aws s3api head-object --bucket=oreno-terraform-state --key=terraform.tfstate.docker
{
    "AcceptRanges": "bytes",
    "LastModified": "Sat, 08 Sep 2018 05:51:53 GMT",
    "ContentLength": 5662,
    "ETag": "\"355017b22a2af13dff9581b638461897\"",
    "VersionId": "tAciP72ATbpem.sDjNueU3sw7iJswg0p",
    "ContentType": "application/json",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

そして, ローカルの tfstate ファイルを見てみると…

$ ls -l terraform.tfstate
-rw-r--r--  1 user  group  0  9  8 14:51 terraform.tfstate

空になっていることが判ります.

この状態で terraform plan を実行すると, 以下のように出力されます.

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

docker_image.centos: Refreshing state... (ID: sha256:b5e5ffb5cdea24c637511e05e1bbe2c92207ae954559d4c7b32e36433035c368centos:6)
docker_container.hoge: Refreshing state... (ID: f4a7331dd4e436920a28fcb9c98372c453f95f21d5e7718bf68170266a8bdff2)
docker_container.popo: Refreshing state... (ID: 69c652297dd3a7cfd30c91a428f0ae026ec8611090914bf8e6a0ba0eb937c796)
docker_container.fuga: Refreshing state... (ID: f203969cd96e0de6e1eec8dee1dc866b87d0eeab2c3b0609304d9f575f63b0d2)

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

いい感じです.

現場から以上です

超駆け足ですが, Terraform の Backends について研究しつつ, 簡単なチュートリアルをやってみました. 状態ロックを併用することでチーム内で Terraform をいじる際に強力にサポートしてくれるものであることが解りました. また, Backend の実装にも興味が出てきましたので, HTTP のバックエンドサーバーを実装してみたいと考えています.

元記事はこちら

独り Terraform 研究所 (1) 〜 Backend についてドキュメントを読んだり, チュートリアルしたり 〜