Cloudflare + Hashicorp Terraform [ 後編 ]

[前回の投稿](https://qiita.com/mdhrk/items/ad0450456692e01d3ff2)にてAWSのCloud9上にTerraformとcf-terraformingの実行環境を用意しました.
今回は、この環境を使って実際にCloudflareに対して設定の変更をおこなってみようと思います.

本投稿では、認証関連の情報は作業ディレクトリ配下のファイルに纏めておきます.

``` ~/environment/cloudflare-terraform/.cf-terraforming.yaml
email: "EMAIL-ADDRESS"
key: "CLOUDFLARE-API-KEY"
account: "CLOUDFLARE_ACCOUNT_ID"
zone: "ZONE-ID"
```
動作を再現される際には上記値はご利用の環境に応じたものに置き換えてえ実行してください.

### Cloudflare側の設定
前回記事の最後に、既存の設定を引っ張ってくるという結果を記載いたしましたが、Cloudfareの既存の設定として以下が投入されています. 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/df1ca581-c1c3-4acb-b2bc-3826a4642906.png)


管理中のゾーンのAレコードとして`www1`が登録されています.
(既存管理方法としてCloudflareダッシュボードから設定を投入ずみという想定).

ここに新しく`www2`のAレコードを登録します.

登録する内容は作業ディレクトリ内に`www2.tf`として以下を記述します.
(www1と異なるのは登録しているIPアドレスが異なる点のみです.)

> touch www2.tf

``` www2.tf
resource "cloudflare_record" "www2" {
  name    = "www2"
  proxied = true
  ttl     = 1
  type    = "A"
  value   = "35.73.201.71"
  zone_id = "e877843d3b162b87089f8c956134fc1c"
}

```

こちらをCloud9上で保存して、terraformで設定を反映してみます.
まずはplanを実行.

> terraform plan

```
develop:~/environment/cloudflare-terraform $ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudflare_record.www2 will be created
  + resource "cloudflare_record" "www2" {
      + allow_overwrite = false
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "www2"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = 1
      + type            = "A"
      + value           = "35.73.201.71"
      + zone_id         = "e877843d3b162b87089f8c956134fc1c"
    }

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

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
develop:~/environment/cloudflare-terraform $ 
```

www2のレコードが追加される旨が出力されるので、適用(apply)します.

> terraform apply

```
develop:~/environment/cloudflare-terraform $ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudflare_record.www2 will be created
  + resource "cloudflare_record" "www2" {
      + allow_overwrite = false
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "www2"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = 1
      + type            = "A"
      + value           = "35.73.201.71"
      + zone_id         = "e877843d3b162b87089f8c956134fc1c"
    }

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

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

  Enter a value: yes

cloudflare_record.www2: Creating...
cloudflare_record.www2: Creation complete after 2s [id=c2e59abeb88667392ab9e888e14bad81]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
develop:~/environment/cloudflare-terraform $ 
```
作成されました.
Cloudflareのダッシュボードを確認してみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/ffcd45a9-321c-e495-d70c-463777852621.png)


`www2`が追加されていることが確認できます.
もちろん、`destroy`の実行も可能です.

```
develop:~/environment/cloudflare-terraform $ terraform destroy
cloudflare_record.www2: Refreshing state... [id=c2e59abeb88667392ab9e888e14bad81]

：略：

cloudflare_record.www2: Destroying... [id=c2e59abeb88667392ab9e888e14bad81]
cloudflare_record.www2: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.
develop:~/environment/cloudflare-terraform $ 
```

ここまでの内容を纏めると、

- 新しいレコードをterrafromで管理するためにはTFファイルを新規作成すればよい
- 新しいTFファイルを元にapply/destroyを実施しても既存の設定には影響を与えていない

ことが確認できました.  

では、既存のコンフィグである`www1`をTerraform管理下に持ってきます.
ひとまずは何も考えずに`www1.tf`を作成してみます.
記述内容はcf-terraformingの実行結果をそのままファイルにしています.

```
cf-terraforming generate \
   --account $CLOUDFLARE_ACCOUNT_ID \
   --zone $CLOUDFLARE_ZONE_ID \
   --resource-type "cloudflare_record" > www1.tf
```

``` www1.tf
resource "cloudflare_record" "terraform_managed_resource_65b157b60750cdf47f36a4e275a83764" {
  name    = "www1"
  proxied = true
  ttl     = 1
  type    = "A"
  value   = "176.32.67.255"
  zone_id = "e877843d3b162b87089f8c956134fc1c"
}
```

この状況下で、先ほどと同じようにplanを実行すると、

```
develop:~/environment/cloudflare-terraform $ terraform plan
cloudflare_record.www2: Refreshing state... [id=1bf52e13290d7a3ae776a3e16781b581]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764 will be created
  + resource "cloudflare_record" "terraform_managed_resource_65b157b60750cdf47f36a4e275a83764" {
  + 
：略：

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

`www1`のレコードが新たに作成しようとする旨が表示されます.
このままApplyをしようとすると、、、

```
develop:~/environment/cloudflare-terraform $ terraform apply
cloudflare_record.www2: Refreshing state... [id=1bf52e13290d7a3ae776a3e16781b581]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

：略：

  Enter a value: yes

cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Creating...
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Still creating... [10s elapsed]
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Still creating... [20s elapsed]
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Still creating... [30s elapsed]
╷
│ Error: expected DNS record to not already be present but already exists
│ 
│   with cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764,
│   on www1.tf line 1, in resource "cloudflare_record" "terraform_managed_resource_65b157b60750cdf47f36a4e275a83764":
│    1: resource "cloudflare_record" "terraform_managed_resource_65b157b60750cdf47f36a4e275a83764" {
│ 
╵
develop:~/environment/cloudflare-terraform $ 
```

`Error: expected DNS record to not already be present but already exists`と表示され作成に失敗します.
今回は設定内容が既存と完全に一致していた例ですが、設定の内容によっては一部のコンフィグレーションが変わっていると普通に登録できてしまうものもあります.

例えば、AレコードのIPアドレスだけを別のものに弄ってApplyした場合は以下のようになります.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/bbb4cded-b260-8f4b-cc17-ffdbc51e8c31.png)


上段が、Terraformで新たに追加したレコード、下段が既存のレコードです.

今回はあえて、既存のコンフィグと重なるような設定をいれてみましたが、本来、Terraformで運用を纏める場合にはStateファイルに既存の設定を管理できるように組み込んであげる必要があります.

その手順を記載します.

先に、不要なコンフィグレーションは削除しておきます.（www1の既存コンフィグのみが残ります）

> terraform destroy

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/df1ca581-c1c3-4acb-b2bc-3826a4642906.png)



### terraform import
Terraform Registryの[cloudflare_record](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record)を参考にImportを実施します.

コマンドの書式は
```
$ terraform import cloudflare_record.<管理リソース名> <ZoneID>/<RecordID>
```

となっています.
ZoneIDはCloudflareのダッシュボードのサイトのトップからも確認が可能です.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/bee0be99-414d-eb05-662b-4ba021449818.png)

RecordIDはAPIによって確認ができます(ZoneIDも合わせて確認できます).  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/28cb2968-fc58-57aa-c3c2-4897c4fcfa22.png)

※ APIでリクエストを投げる手順に関しては[こちらの記事](https://qiita.com/atsufumi/items/dc171cdd45c18e894c2f)を参照ください.

このRecordIDとZoneIDはcf-terraformingコマンドで出力したコマンド結果にも表示されています.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/802f5016-b932-6d8e-25bc-bb55cbd668a6.png)

terraform importコマンドを実行します.

```
develop:~/environment/cloudflare-terraform $ terraform import cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764 e877843d3b162b87089f8c956134fc1c/65b157b60750cdf47f36a4e275a83764 
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Importing from ID "e877843d3b162b87089f8c956134fc1c/65b157b60750cdf47f36a4e275a83764"...
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Import prepared!
  Prepared cloudflare_record for import
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Refreshing state... [id=65b157b60750cdf47f36a4e275a83764]

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

StateFileに既存のコンフィグレーションを取り入れることができました.  

上記コマンドの
`cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764`
部分は

`cloudflare_record.`部分は扱うリソースタイプ（今回はDNSレコードを対象としている）、
`terraform_managed_resource_65b157b60750cdf47f36a4e275a83764`部分は、`TFファイルに記述されているリソース名`

をそれぞれ指しています.

今回は`cf-terraforming generate`の出力結果をそのままTFファイルにしたため、
「terraform_managed_resource_」と「リソースID」の組み合わせで記述されていますが、
実際には運用を加味した命名規則で管理すると思います.

その場合、terraform importコマンドとTFコマンドの記述が一致していないとエラーになるのでご注意ください.



この状態で改めてplanを実行すると

```
develop:~/environment/cloudflare-terraform $ terraform plan
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Refreshing state... [id=65b157b60750cdf47f36a4e275a83764]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudflare_record.www2 will be created
  + resource "cloudflare_record" "www2" {
      + allow_overwrite = false
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "www2"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = 1
      + type            = "A"
      + value           = "35.73.201.71"
      + zone_id         = "e877843d3b162b87089f8c956134fc1c"
    }

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

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

となり、先ほどの`destroy`で消してあったwww2のみが作成(www1は改めて作成の対象とはなっていない)ことが確認できます.

```
develop:~/environment/cloudflare-terraform $ terraform apply
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Refreshing state... [id=65b157b60750cdf47f36a4e275a83764]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudflare_record.www2 will be created
  + resource "cloudflare_record" "www2" {
      + allow_overwrite = false
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "www2"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = 1
      + type            = "A"
      + value           = "35.73.201.71"
      + zone_id         = "e877843d3b162b87089f8c956134fc1c"
    }

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

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

  Enter a value: yes

cloudflare_record.www2: Creating...
cloudflare_record.www2: Creation complete after 1s [id=6bc7e3b1de12dd5949ff891de66b767d]

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

今度はエラーなくApplyが実行できました.
(2重に設定が投入されるという事もありません.)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150229/4e2bd093-9668-1473-441f-5ef023fd1aab.png)



`www1`もTerraformの管理下に入っているので、Destroyをしようとすると

```
develop:~/environment/cloudflare-terraform $ terraform destroy
cloudflare_record.www2: Refreshing state... [id=6bc7e3b1de12dd5949ff891de66b767d]
cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764: Refreshing state... [id=65b157b60750cdf47f36a4e275a83764]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # cloudflare_record.terraform_managed_resource_65b157b60750cdf47f36a4e275a83764 will be destroyed
  - resource "cloudflare_record" "terraform_managed_resource_65b157b60750cdf47f36a4e275a83764" {
      - created_on  = "2021-10-15T05:10:39.311599Z" -> null
      - hostname    = "www1.free.cfpoc.ted-tech.org" -> null
      - id          = "65b157b60750cdf47f36a4e275a83764" -> null
      - metadata    = {
          - "auto_added"             = "false"
          - "managed_by_apps"        = "false"
          - "managed_by_argo_tunnel" = "false"
          - "source"                 = "primary"
        } -> null
      - modified_on = "2021-10-15T05:10:39.311599Z" -> null
      - name        = "www1" -> null
      - proxiable   = true -> null
      - proxied     = true -> null
      - ttl         = 1 -> null
      - type        = "A" -> null
      - value       = "176.32.67.255" -> null
      - zone_id     = "e877843d3b162b87089f8c956134fc1c" -> null

      - timeouts {}
    }

  # cloudflare_record.www2 will be destroyed
  - resource "cloudflare_record" "www2" {
      - allow_overwrite = false -> null
      - created_on      = "2021-12-23T07:01:50.356317Z" -> null
      - hostname        = "www2.free.cfpoc.ted-tech.org" -> null
      - id              = "6bc7e3b1de12dd5949ff891de66b767d" -> null
      - metadata        = {
          - "auto_added"             = "false"
          - "managed_by_apps"        = "false"
          - "managed_by_argo_tunnel" = "false"
          - "source"                 = "primary"
        } -> null
      - modified_on     = "2021-12-23T07:01:50.356317Z" -> null
      - name            = "www2" -> null
      - proxiable       = true -> null
      - proxied         = true -> null
      - ttl             = 1 -> null
      - type            = "A" -> null
      - value           = "35.73.201.71" -> null
      - zone_id         = "e877843d3b162b87089f8c956134fc1c" -> null
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value:
```

削除対象にも含まれていることが確認できます.

これにより`www1`はTFファイルでの管理が可能になります.

### まとめ
terraformを使ってCloudflareの設定をコントロールするという記事を投稿をさせていただきました.
その中で今回は簡易な検証環境の展開方法と、実行する上での注意点を記載させていただきました.

cf-terraforming自体、現時点においてすべてのリソースタイプをカバーしている状況にはありませんが、更新も頻繁におこなわれているので興味がある方はチェックしてみることをお勧めします.
今回は深堀しなかったその他のリソースやcf-terraformingコマンドからのimportなど引き続き試してみたいと思います.

この投稿が皆様のお役に立てれば幸いです.

### 参考リンク
https://github.com/cloudflare/cf-terraforming

https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record

https://blog.cloudflare.com/introducing-cf-terraform/