Terraform の Configuration Drift を解消する

本記事は [Ateam Finergy Inc. Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ateam-finergy) 4日目の記事です。
## はじめに

本記事を書くにあたり、動作検証に使用した Terraform のバージョンは下記の通りです。

```sh
% terraform -v
Terraform v0.13.2
```

また、想定読者は Terraform を使用していて、Drift の解決に困っている方、および、忘れがちな自分のためのメモです。

## Configuration Drift

Terraform を使用していると、何らかの理由により、 Terraform が管理しているインフラストラクチャの状態ファイル `.tfstate` の内容と、実際のインフラストラクチャの乖離（Configration Drift）が発生する場合があります。

Drift が発生してしまった場合、対応方針としてはいくつか考えられると思いますが、今回は稼働しているインフラ側を正しい状態とし、Terraform で管理する状態の乖離を解消する方法を記載します。

## Drift を解消する

下記のリソースを使用し、Driftが発生した状態を再現してみます。

```tf:main.tf
# AWS EC2 VM with AMI and tags
resource "aws_instance" "example" {
  ami           = "ami-0289fbf73734505e2"
  instance_type = "t3.micro"
  tags = {
    drift_example = "v1"
  }
}
```

`terraform apply` を実行し、インスタンスを作成します。

```sh
% terraform apply
...省略...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

次に、AWSコンソールを使用してインスタンスのタグを手動で `v2` に更新した後、`terraform plan` で実行計画を出力し、Drift を確認します。

```sh
% 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.

aws_instance.example: Refreshing state... [id=i-**********]

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

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_instance.example will be updated in-place
  ~ resource "aws_instance" "example" {
      ~ tags                         = {
          ~ "drift_example" = "v2" -> "v1"
        }
    }

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

------------------------------------------------------------------------
```

Terraform がタグの値を v2 から v1 に更新することがわかります。 Terraform は Drift を修正し、設定の値と一致するようにタグを修正しようとしています。この状態で `terraform apply` を実行すると、実際のインフラストラクチャ側のタグは v1 に戻されてしまいます。

### Terraform の設定と状態を手動で変更する

このように乖離が少ないケースであれば、設定ファイルを手動編集することにより解決することができます。
`.tf` `.tfstate` ファイルを以下のように編集します。
（`.tfstate` を手動更新する場合、serial をインクリメントしておきましょう）

```diff:main.tf
   tags = {
-    drift_example = "v1"
+    drift_example = "v2"
   }
```

```diff:terraform.tfstate
@@ -1,7 +1,7 @@
-  "serial": 1,
+  "serial": 2,
@@ -76,7 +76,7 @@
             "tags": {
-              "drift_example": "v1"
+              "drift_example": "v2"
             },
```

`terraform plan` で実行計画を確認します。

```sh
% 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.

aws_instance.example: Refreshing state... [id=i-**********]

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

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 側からの変更が無くなりました。

### `terraform import` コマンドを使用する

より大きな乖離が発生したケースを想定してみます。例えば、管理するリソースを後から追加したい場合などです。

例として、AWSコンソールからブロックストレージを作成し、先程のインスタンスに紐付けました。

`terraform import` コマンドを使用し、既存のインフラストラクチャの情報を `.tfstate` ファイルに取り込む手順を以下に記載します。

```sh
# import コマンドによる状態の上書きができないので、まず古い情報を削除します
# state rm コマンドは実行時、状態の削除とともに .backup ファイルを作成します
% terraform state rm aws_instance.example
Removed aws_instance.example
Successfully removed 1 resource instance(s).

# import を実行します
% terraform import aws_instance.example i-**********
aws_instance.example: Importing from ID "i-**********"...
aws_instance.example: Import prepared!
  Prepared aws_instance for import
aws_instance.example: Refreshing state... [id=i-**********]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
```

うまく取り込めたようなので、`.tfstate` の差分を確認してみます。

```diff:.tfstate
@@ -1,7 +1,7 @@
-  "serial": 2,
+  "serial": 3,
@@ -26,7 +26,19 @@
-            "ebs_block_device": [],
+            "ebs_block_device": [
+              {
+                "delete_on_termination": false,
+                "device_name": "/dev/sdf",
+                "encrypted": false,
+                "iops": 100,
+                "kms_key_id": "",
+                "snapshot_id": "",
+                "volume_id": "vol-**********",
+                "volume_size": 1,
+                "volume_type": "gp2"
+              }
+            ],
```

先程追加したブロックストレージの情報が無事追加されていました。
`terraform plan` で実行計画も確認しておきましょう。

```sh
% 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.

aws_instance.example: Refreshing state... [id=i-**********]

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

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 側からの変更は行われないので問題なさそうです。

### ツールを使用して Reverse Terraform する

[terraformer](https://github.com/GoogleCloudPlatform/terraformer) や [terraforming](https://github.com/dtan4/terraforming) といったツールを用いて、既存のインフラストラクチャから新たに Terraform で管理する `.tf` `.tfstate` にエクスポートすることもできます。

ストレージやネットワークなどが完全にセットアップされた状態のインスタンスや、複数にわたるクラスター・ノードを、手動で編集・インポートを行うよりも、このようなツールを使用するほうが現実的な方法でしょう。

## まとめ

Terraform の Configuration Drift を解消する方法として下記の3点を紹介しました。状況に応じて活用していただければ幸いです。

* Terraform の設定と状態を手動で変更する
* `terraform import` コマンドを使用する
* ツールを使用して Reverse Terraform する

また、問題の対応方法を知っておくことは大切ですが、Terraform や Infrastructure as Code のベストプラクティスを知り、問題を未然に防ぐことも大切だと思いました。（戒め）

## 参考URL

*  [Import \- Terraform by HashiCorp](https://www.terraform.io/docs/import/index.html)
* [State \- Terraform by HashiCorp](https://www.terraform.io/docs/state/index.html)
* [Detecting and Managing Drift with Terraform](https://www.hashicorp.com/blog/detecting-and-managing-drift-with-terraform)
* [Import Terraform configuration \| Terraform \- HashiCorp Learn](https://learn.hashicorp.com/tutorials/terraform/state-import?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS)
* [How to manage Terraform state\. A guide to file layout, isolation, and… \| by Yevgeniy Brikman \| Gruntwork](https://blog.gruntwork.io/how-to-manage-terraform-state-28f5697e68fa)