Terraformに便利機能を提供するラッパーツールTerragruntの紹介

2016.08.12

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

はじめに

こんにちは、中山です。

先日GitHubのトレンドを見ていたところ、面白そうなツールを発見しました。Terragruntというものです。これはTerraformのラッパーツールで、いろいろと便利な機能を提供してくれます。今回このツールを利用してみたのでエントリにまとめます。

Terragruntは何を解決するために作られたのか

作者の方のブログ(Add Automatic Remote State Locking and Configuration to Terraform with Terragrunt)や、GitHubのREADMEに詳しく書かれていますが、以下の2つの問題を解決するためのツールです。

  • リモートステートの有効化忘れ
  • ロック機能

以下それぞれについて説明します。

リモートステートの有効化忘れ

Terraformは拡張子に .tfstate が付くファイル(ステートファイル)にリモートのリソース情報をJSON形式で書き込みます。これは、リソースの実際の状態が記述されたファイルです。そして、拡張子に .tf が付くファイルにリソースのあるべき状態を記述します。Terraformはこの2つのファイル間で差分を確認し、変更があったらそれを適用するというモデルで実行されます。そのため、ステートファイルの管理は重要です。コードの管理を一人でしている場合は特に意識しないですが、複数人のチームで管理している場合は、チーム内で常に同じステートファイルを共有する必要があります。

幸い、ステートファイルを共有する機能が標準で提供されています。過去のエントリでも何度か触れていますがリモートステートという機能で、ステートファイルをリモートのストレージ(S3/Atlas/consulなど)に保存することが可能です。リモートステートを利用することで、Terraformを実行する際にステートファイルをリモートから取得し、ローカルで更新したらそれをリモートにも反映することが可能になります。こうすることで、チーム内でステートファイルを共有できるという訳です。

リモートステートの有効化はコマンドラインから実施します。以下はS3をバックエンドにした場合のコマンドです。

$ terraform remote config \
  -backend=s3 \
  -backend-config='bucket=<bucket-name>' \
  -backend-config='key=terraform.tfstate' \
  -backend-config='region=ap-northeast-1'

リモートステートはとても便利な機能なのですが、問題点があります。Terraform実行時にリモートステートの有効化を忘れるという問題です。忘れたままステートファイルを更新し、別の開発者がTerraformを実行しようとした際に、不具合を起こすという問題があります。要はヒューマンエラーです。

Terragruntではこの問題を防止するために、Terraformの実行前に自動でリモートステートを有効化する仕組みを提供してくれます。

ロック機能

Terraformにはもう一つ問題点があります。それはロック機能が標準で無いという点です。つまり、多重処理を実施されてしまう可能性があります。別々の開発者が同時にTerraformを実行した場合、お互いの変更内容がコンフリクトしてしまう可能性があります。

この問題を解決するため、TerragruntはDynamoDBを利用してロック機能を提供します。Terraformの実行前にDynamoDBのテーブルを更新して、テーブル内に特定のアイテムが存在している間はある開発者がTerraform実行中とみなし、他の開発者が実行できないようにしてくれます。

Terragruntが解決しようとしている問題及び解決方法をご紹介しました。では早速使ってみましょう。

コード

サンプルとなるコードをGitHubに置きました。ご自由にお使いください。

インストール

TerragruntはTerraformと同じようにGo言語製のツールなので、インストールは簡単です。現時点(2016/08/12)ではHomebrewなどでインストール出来ないようなので、GitHub Releaseから自分の環境にあった最新バイナリをダウンロードして下さい。ダウンロード完了後、パスを通して以下のように表示されればインストール完了です。

$ terragrunt --help
DESCRIPTION:
   terragrunt - Terragrunt is a thin wrapper for [Terraform](https://www.terraform.io/) that supports locking
   via Amazon's DynamoDB and enforces best practices. Terragrunt forwards almost all commands, arguments, and options
   directly to Terraform, using whatever version of Terraform you already have installed. However, before running
   Terraform, Terragrunt will ensure your remote state is configured according to the settings in the .terragrunt file.
   Moreover, for the apply and destroy commands, Terragrunt will first try to acquire a lock using DynamoDB. For
   documentation, see https://github.com/gruntwork-io/terragrunt/.

USAGE:
   terragrunt <COMMAND>

COMMANDS:
   apply                Acquire a lock and run 'terraform apply'
   destroy              Acquire a lock and run 'terraform destroy'
   release-lock         Release a lock that is left over from some previous command
   *                    Terragrunt forwards all other commands directly to Terraform

GLOBAL OPTIONS:
   --help, -h   show help
   --version, -v    print the version

VERSION:
   v0.0.10

AUTHOR(S):
   Gruntwork <www.gruntwork.io>

.terragrunt ファイルの内容

Terragruntはカレントディレクトリに設置された .terragrunt というファイルから設定内容を読み取ります。書式はHCLで記述します。つまりTerraformのtfファイルと同じ書式で記述可能です。以下に今回使った設定ファイルを記載します。

# Configure Terragrunt to use DynamoDB for locking
dynamoDbLock = {
  stateFileId = "my-app"
  awsRegion = "ap-northeast-1"
  tableName = "terragrunt_locks"
  maxLockRetries = 360
}

# Configure Terragrunt to automatically store tfstate files in S3
remoteState = {
  backend = "s3"
  backendConfigs = {
    encrypt = "true"
    bucket = "terragrunt-demo"
    key = "terraform.tfstate"
    region = "ap-northeast-1"
  }
}

見たままではありますが、それぞれの意味は以下のとおりです。

項目 内容
dynamoDbLock DynamoDBの設定
StateFileId 属性に書き込む内容
awsRegion DynamoDBのリージョン
tableName テーブル名
maxLockRetries ロック取得まで何回リトライ処理をするか
remoteState リモートステートの設定
backend バックエンドの指定
backendConfigs バックエンドの設定
encrypt S3の暗号化を有効化するか(現時点ではまだ実装されてなさそう)
bucket ステートファイルを保存するバケット名
key ステートファイルを保存するキー名
region S3のリージョン

コマンドラインの使い方

Usageを見ると分かりますが、Terragruntは applydestroy サブコマンド実行時にDynamoDBでロック処理を実施し、 release-lock サブコマンドで明示的にロックを解除、その他のコマンドはTerraformにそのまま渡されるという仕組みで動作します。

こちらのコードを見ると、case文に記載されているコマンド入力時に自動でリモートステートが有効化してくれるようです。今回リモートステートのバックエンドにはS3を利用します。まず、ステートファイル保存用のバケットを作成してください。バケット名は .terragrunt ファイルの内容と合わせる必要があります。

$ aws s3 mb s3://terragrunt-demo
make_bucket: s3://terragrunt-demo/

バケットの作成が完了したら、 plan サブコマンドを実行してみましょう。

$ terragrunt plan
[terragrunt] 2016/08/10 10:55:22 Configuring remote state for the s3 backend
[terragrunt] 2016/08/10 10:55:22 Running command: terraform remote config -backend s3 -backend-config=encrypt=true -backend-config=bucket=terragrunt-demo -backend-config=key=terraform.tfstate -backend-config=region=ap-northeast-1
Remote state management enabled
Remote state configured and pulled.
[terragrunt] 2016/08/10 10:55:24 Running command: terraform plan
<snip>

表示内容を確認すると、 plan サブコマンド実行前にリモートステートを有効化しています。カレントディレクトリの .terraform ディレクトリを見てみると、ステートファイルが移動していることが確認できます(リモートステートを有効化するとステートファイルがこの場所に移動します)。

$ ls -l .terraform
total 8
-rw-r--r--  1 knakayama  staff  458 Aug 10 10:55 terraform.tfstate

続いて apply サブコマンドでリソースを作成してみます。

$ terragrunt apply
[terragrunt] 2016/08/10 11:17:41 Remote state is already configured for backend s3
[terragrunt] 2016/08/10 11:17:41 Attempting to acquire lock for state file my-app in DynamoDB
[terragrunt] 2016/08/10 11:17:42 Lock table terragrunt_locks does not exist in DynamoDB. Will need to create it just this first time.
[terragrunt] 2016/08/10 11:17:42 Creating table terragrunt_locks in DynamoDB
[terragrunt] 2016/08/10 11:17:43 Table terragrunt_locks is not yet in active state. Will check again after 10s.
[terragrunt] 2016/08/10 11:17:53 Success! Table terragrunt_locks is now in active state.
[terragrunt] 2016/08/10 11:17:53 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks
[terragrunt] 2016/08/10 11:17:55 Lock acquired!
[terragrunt] 2016/08/10 11:17:55 Running command: terraform apply
<snip>
[terragrunt] 2016/08/10 11:20:58 Attempting to release lock for state file my-app in DynamoDB
[terragrunt] 2016/08/10 11:20:58 Lock released!

表示内容からDynamoDBのテーブル作成、ロックの有効化を実施していることが確認できます。リソースの作成が完了するとロックをリリースした旨表示されています。

DynamoDBのテーブルはどうなっているのでしょうか。確認してみます。

$ aws dynamodb describe-table \
  --table-name terragrunt_locks
{
    "Table": {
        "TableArn": "arn:aws:dynamodb:ap-northeast-1:************:table/terragrunt_locks",
        "AttributeDefinitions": [
            {
                "AttributeName": "StateFileId",
                "AttributeType": "S"
            }
        ],
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "WriteCapacityUnits": 1,
            "ReadCapacityUnits": 1
        },
        "TableSizeBytes": 0,
        "TableName": "terragrunt_locks",
        "TableStatus": "ACTIVE",
        "KeySchema": [
            {
                "KeyType": "HASH",
                "AttributeName": "StateFileId"
            }
        ],
        "ItemCount": 0,
        "CreationDateTime": 1470795463.149
    }
}

.terragrunt ファイルで定義した名前でテーブルが作成され、スキーマとして StateFileId が存在していることが確認できます。アイテムの内容を確認してみましょう。

$ aws dynamodb scan \
  --table-name terragrunt_locks
{
    "Count": 0,
    "Items": [],
    "ScannedCount": 0,
    "ConsumedCapacity": null
}

ロックをリリースしたのでこの時点では空っぽのようです。では、ロック中の内容を確認してみます。 taint サブコマンドで aws_spot_request リソースを「汚染」させ、リソースを意図的に再作成させます。

$ terragrunt taint aws_spot_instance_request.web
[terragrunt] 2016/08/10 11:31:48 Remote state is already configured for backend s3
[terragrunt] 2016/08/10 11:31:48 Running command: terraform taint aws_spot_instance_request.web
The resource aws_spot_instance_request.web in the module root has been marked as tainted!

続いて apply サブコマンドでリソースを作成します。作成中(ロック中)にDynamoDBの内容を表示した結果が以下です。

$ aws dynamodb scan \
  --table-name terragrunt_locks
{
    "Count": 1,
    "Items": [
        {
            "Username": {
                "S": "cm-nakayama.koji"
            },
            "Ip": {
                "S": "192.168.43.4"
            },
            "CreationDate": {
                "S": "2016-08-10 02:33:32.834892784 +0000 UTC"
            },
            "StateFileId": {
                "S": "my-app"
            }
        }
    ],
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

アイテムの中に4つの属性が作成されています。

属性 内容
Username IAMユーザ名
Ip Terragrunt実行ホストのIPアドレス
CreationDate Terragrunt実行時間
StateFileId .terragrunt ファイルで定義した内容

これらのアイテムが存在している間はロック中と判断し、Terraformの同時実行を防止してくれます。もし、ロック中に別の開発者が apply サブコマンドを実行した場合、 .terragrunt で定義した maxLockRetries 実施後、以下のようにエラーを表示してくれます。

$ terragrunt apply
[terragrunt] 2016/08/10 11:41:27 Remote state is already configured for backend s3
[terragrunt] 2016/08/10 11:41:27 Attempting to acquire lock for state file my-app in DynamoDB
[terragrunt] 2016/08/10 11:41:28 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks
[terragrunt] 2016/08/10 11:41:30 Someone already has a lock on state file my-app! cm-nakayama.koji@192.168.43.4 acquired the lock on 2016-08-10 02:41:25.932320746 +0000 UTC.
[terragrunt] 2016/08/10 11:41:30 Will try to acquire lock again in 10s.
[terragrunt] 2016/08/10 11:41:40 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks
[terragrunt] 2016/08/10 11:41:41 Someone already has a lock on state file my-app! cm-nakayama.koji@192.168.43.4 acquired the lock on 2016-08-10 02:41:25.932320746 +0000 UTC.
[terragrunt] 2016/08/10 11:41:41 Will try to acquire lock again in 10s.
[terragrunt] 2016/08/10 11:41:51 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks
[terragrunt] 2016/08/10 11:41:52 Someone already has a lock on state file my-app! cm-nakayama.koji@192.168.43.4 acquired the lock on 2016-08-10 02:41:25.932320746 +0000 UTC.
[terragrunt] 2016/08/10 11:41:52 Will try to acquire lock again in 10s.
[terragrunt] 2016/08/10 11:42:02 Unable to acquire lock for item my-app after 3 retries.

最後に destroy サブコマンドでリソースを削除してみましょう。

$ terragrunt destroy
[terragrunt] 2016/08/10 12:24:21 Remote state is already configured for backend s3
[terragrunt] 2016/08/10 12:24:21 Attempting to acquire lock for state file my-app in DynamoDB
[terragrunt] 2016/08/10 12:24:22 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks
[terragrunt] 2016/08/10 12:24:24 Lock acquired!
[terragrunt] 2016/08/10 12:24:24 Running command: 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
<snip>
[terragrunt] 2016/08/10 12:25:33 Attempting to release lock for state file my-app in DynamoDB
[terragrunt] 2016/08/10 12:25:34 Lock released!

ロックの取得/開放以外はTerraformを直接実行した場合と同じです。

まとめ

いかがでしょうか。

私としてはCircleCIなどのCIサービスと連携させて、Terraformの実行をそこに集約させればリモートステートの有効化忘れ/ロック機能について考慮しなくてもいいのではと考えています。常に一箇所でTerraformを実行させれば、これらの問題を解決できるからです。ただし、CIサービスなどを利用せずに、Terraform単体でこういった機能を実装してくれると、なかなか便利だと思います。

作者の方のブログを見ると、このツールはTerraformに対するPR的な位置付けのようです。つまり、Terraform単体ではこれこれの機能が不足している、だからそれを補うツールを作った。しかし、Terraformのコア機能として取り込もうとすると時間かかるし、現在の実装方法はAWSに寄りすぎてコミュニティに受け入れられるか微妙。コミュニティからのフィードバックを受けて将来的にはTerraformのコア機能として実装したい。

個人的にはとてもおもしろい試みだと思うので、今後も追いかけていこうと思います。

本エントリがみなさんの参考になれば幸いです。