設定と実インフラ差分(drift)の発生時対応のユースケースでTerraformとCloudFormationを比較

# # はじめに

Infrastructure Codeでのインフラ運用にて一般的に課題になるのでYamlファイルでの宣言的設定と実インフラの差分、いわゆるdriftと呼ばれるやつが発生したときの対応です。

この記事ではTerraformとCloudFormationとの比較においてdrift対応にフォーカスし、差分において設定内容を実インフラへ適用させるユースケースと実インフラを設定へ適用させるユースケースの2つで、TerraformとCloudFormationそれぞれの対応方法をみています。

# # 前提

この記事の実験では、AWS EC2インスタンスのタグリソースのみの差分を発生させ検証しています。

従って他のインフラリソースの場合では賄わないといけない観点が抜けていると思います。そこは免責事項としてここで言及しておきます。

# # 環境 バージョン

MacOS Version: 11.0.1

Terraform

```
$ terraform version
Terraform v0.13.5
+ provider registry.terraform.io/hashicorp/aws v3.24.1
```

cloudformation (aws cli)

```
aws-cli/2.1.32 Python/3.8.8 Darwin/20.1.0 exe/x86_64 prompt/off
```

# # 実験のために作成するEC2インスタンス

同じ設定のEC2をterraformとCloudFormationのそれぞれで作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91562/d407fc10-0f97-24a9-87e7-3c116a1ba2ca.png)


## ## Terraform

```terraform:ec2.tf
resource "aws_instance" "test-instance" {
  instance_type = "t3.micro"
  ami = "ami-0ef85cf6e604e5650" # Ubuntu Server 18.04 LTS (HVM), SSD Volume Type (64-bit x86)
  subnet_id = "subnet-00301c4fabb0d2def"
  associate_public_ip_address = true
  security_groups = ["sg-08cb2b147c604dae4"]

  root_block_device {
    volume_type           = "standard"
    volume_size           = 8
    delete_on_termination = true
  }

  tags = {
    Name = "instance-by-terraform"
  }
}
```

## ## CloudFormation

```yaml:template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Cloud Formation example

Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: "ami-0ef85cf6e604e5650"
      InstanceType: "t3.micro"
      SubnetId: "subnet-00301c4fabb0d2def"
      SecurityGroupIds: ["sg-08cb2b147c604dae4"]
      Tags:
        - Key: Name
          Value: "instance-by-cloudformation"
      BlockDeviceMappings:
      - DeviceName: "/dev/sda1"
        Ebs:
          VolumeType: "standard"
          DeleteOnTermination: "true"
          VolumeSize: "8"
```

# # ユースケース1: 設定を実インフラへ適用させたい/戻したい (既存設定が正義のパターン)

現在手元にある設定で差分が出てしまっている実インフラ環境へ適応させたいケースです。意図せずに手動でや別ツールで対象インフラツール外で変更がされてしまった場合などでしょう。

またこのとき、そもそも現状ではどのような差分が存在しているかを確認する必要があります。

今回はEC2インスタンスのタグリソース名をAWS Console上で変更しdriftを意図的に発生させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91562/762316e5-f788-07c4-d2cc-1925298b2154.png)

## ## Terraform

差分を確認したいとき、Terraformでは`terraform plan`コマンドにより、現状の差分を確認できます。

Terraformは`terraform.tfstate`ファイルにて実インフラとのマッピング情報を持つのでその差分を把握し適用させることができます。

今回は対象がEC2なこともあり、Replaceになりますが、タグ名も手動で設定した内容を変更することが確認できます。

```
$ terraform plan
(略)
      ~ tags                         = {
          ~ "Name" = "MANUAL-DONE-instance-by-terraform-up" -> "instance-by-terraform"
        }
(略)
```

実際に適用させるときは`terraform apply`で適用されます。

## ## CloudFormation

CloudFormationで差分を確認するには、Drift Detect機能を利用します。

Console上だと以下のようにStack actionsのプルダウンにボタンがあり非同期に結果を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91562/010a8c3d-826b-ba1b-cb32-6680ce05b7ce.png)

View drift results から結果を確認すると以下のように、リソースタグが異なっていることを示してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91562/80e3cf82-9e1d-d9b0-46d3-8f7fe61737f8.png)

そして、設定ファイルを実インフラに適用したいとき、CloudFormationの場合は、AWS ConsoleまたはAWS CLIでの手動にて実インフラを修正する必要があります。CloudFormationはterraformがstateファイルを持つのとは異なり、差分検知機能はあるけれどもその差分データ自体はCloudFormationとして管理外であるためです。

従って、差分はDrift detectではっきりするので手動で実インフラを修正して直します。手動またはツールでできた差分なので同様に直しましょうということですね。

### ### CloudFormationにて自動的に差分を直したい場合は

CloudFormationにて自動的に差分を直したい場合は、以下の公式ブログ記事のようにLambdaとCloudWatch Eventを利用すれば実現としては可能です。

https://aws.amazon.com/jp/blogs/mt/implement-automatic-drift-remediation-for-aws-cloudformation-using-amazon-cloudwatch-and-aws-lambda/

# # ユースケース2: 実インフラを設定ファイルへ適用させたい(現状の実インフラを正義にしたいパターン)

実インフラで先に必要な修正をしてしまい、後からインフラ設定が追従することになった場合のケースです。

## ## Terraform

Terraformの場合、`terraform refresh`で現状の状態をtfstateファイルへ更新し、差分を確認しながら変更したい項目をtfファイルの設定へ適用させる必要があります。`terraform state show aws_instance.test-instance`などを使うと差分が少し見やすくなったりします。

手動で変更した後は`terraform plan`で差分として扱われていないかで修正が問題ないか確認できます。

```
$ terraform plan
(略)
        tags                         = {
            "Name" = "MANUAL-DONE-instance-by-terraform-up"
        }
(略)
```

### ### terraform importを使う場合

そもそもTerraformの管轄外で作成したリソースをterraformで管理するようにしたい場合はterraform importが使えます。

以下の公式チュートリアルで具体的なステップが記載されています。

https://learn.hashicorp.com/tutorials/terraform/state-import?in=terraform/state&utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS

Stepは以下です。(↓は私の訳です)。このように、結局はstateファイルの内容を手動でtfファイルへと落とし込む作業が必要になります。上記URLページに書いてあるように、stateファイルの内容すべてをtfファイルに落とし込む必要は無いためです。

0. インポートする既存インフラリソースを定める
0. Terraform stateへインフラをインポートする
0. インフラリソースに合うTerraform設定を記述する
0. 記述した設定が想定するstateなインフラリソースと一致するかをTerraform planで確認する
0. Terraform stateを更新するために設定を適用(terraform apply)させる

## ## CloudFormation

CloudFormationの場合、先ほどと同様にdrift detect機能で差分を検知し、変更したいtemplateの部分を手動で更新することになります。

CloudFormationの場合は、設定変更はCloudFormation上のStackとして適用しないといつまでもdrift扱いになってしまうので`aws cloudformation create-change-set` => `execute-change-set`をするか`update-stack`でStackを更新する必要があります。

```
$ aws cloudformation create-change-set --stack-name TestCFnEC2 --change-set-name test-cs --template-body file://template.yml
```

```
$ aws cloudformation execute-change-set --change-set-name test-cs --stack-name TestCFnEC2
```

### ### CloudFormationにて設定を実インフラへ Replacement (削除&新規作成) 無しに適用させたい

上記のステップでStackを更新する際、対象リソースをReplaceすることになるとCloudFormationが示している場合にて、リソースの削除を避けてtemplate.ymlを更新しUpdate Stackしたいとき、AWS更新では以下のページにてそのステップを示しています。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-resolve-drift.html

https://aws.amazon.com/jp/blogs/mt/remediate-drift-via-resource-import-with-aws-cloudformation/

ステップの概要を日本語訳すると以下のようになります。

Step 1. Deletion PolicyをRetainになるようにStackを更新する
Step 2. template.yml上のdriftなものを削除しUpdate Stackで適用させる
Step 3. Import機能でtemplate.ymlを更新する

# # おわりに

driftを観点にTerraformとCloudFormationを比較するとやはりCloudFormationの面倒さが目立つという印象でした。terraformのtfstateでの実環境と設定のマッピング管理は管理コストがでる代わりに強力であることが確認できました。

特にterraformの削除制限(destroy lock)については触れられなかったので別記事でまた比較としてまとめようかと考えています。

# # 参考

* https://www.hashicorp.com/blog/detecting-and-managing-drift-with-terraform
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html
* https://medium.com/@cep21/after-using-both-i-regretted-switching-from-terraform-to-cloudformation-8a6b043ad97a
* https://speakerdeck.com/dena_tech/techcon2021-19?slide=87