Cloud MonitoringをTerraformで管理する

こんにちは。
現在、GCPを業務で利用していて、モニタリングツールとしては基本的にCloud Monitoringを使っています。今回は、この設定をTerraform化しようとした時の話になります。
Terraformのコードでリソースのあるべき状態を管理しているのでCloud Monitoringも例外なく管理しますが、Cloud Monitoringのアラートポリシーは１からTerraformで作成することはなかなか大変で(`\`でエスケープとかしないといけない)、かつ記述する内容はGUIから判断がつきにくいものになっています(公式にも私が探した限りはない)。なので、基本的にGUIで設定してから`terraform import`コマンドでコード化すればこの点はあまり悩まなくて済みますが、公式の書き方で実行してもなぜかimportできず、沼にはまりかけました。一筋縄ではいかないCloud Monitoringのコード化ですが、ハマったところから無事コード化できるところまで持っていこうと思います。

## GUIで作ってからインポートする
業務では「こんな監視設定にしてほしい。」や「これと同じアラートポリシーを作ってほしい。」などといった声を受けて設定しています。GUIでの設定は比較的簡単ですので、要件に対しては早めにコミットできます。GUIで作ったリソースを、Terraformで作成したリソースのあるべき状態を記しているtfstateに反映させるためには、`terraform import`という素晴らしいコマンドがあります。つけたいリソースネームと、インポートしたいリソースを打ち込むだけでtfstateに反映されます。Cloud Monitoringもこのコマンドでできるはずなのですが、出てきたのはエラーでした。
Githubにも全く同じパターンでハマっている人がいたので別の方法を考えます。
https://github.com/terraform-providers/terraform-provider-google/issues/4610
(Terraformのバージョンを新しくして試してみてください。といったことが書かれていますが今回ここについては掘り下げません。)

## Terraformerを使う
また便利なツールがありまして、コード化したいリソースを指定するだけで自動的にコード化してくれる[Terraformer](https://github.com/GoogleCloudPlatform/terraformer)というツールがあります。元々wazeで作られていたOSSですが、現在はGoogleに買収されており、GoogleのOSSとしてリリースされています。今回使い方については割愛しますが、Terraformerを使ったソースをみてみると、なかなか読みにくいことがわかります。ちなみに、以下がTerraformerで出力されるコードの一例になります。

```tf
resource "google_monitoring_notification_channel" "tfer--projects--project-name--notificationChannels--0123456789" {
  display_name = "hogehoge@testcompany.com"
  enabled      = "true"

  labels = {
    email_address = "hogehoge@testcompany.com"
  }

  project = "projece-name"
  type    = "email"
}

resource "google_monitoring_alert_policy" "tfer--projects--project-name--alertPolicies--17320504" {
  combiner = "OR"

  conditions {
    condition_threshold {
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_MEAN"
      }

      comparison      = "COMPARISON_GT"
      duration        = "60s"
      filter          = "metric.type=\"agent.googleapis.com/disk/percent_used\" resource.type=\"gce_instance\" metric.label.\"state\"=\"used\" metric.label.\"device\"=\"rootfs\" metadata.user_labels.\"name\"=\"sample-instance-1\""
      threshold_value = "80"

      trigger {
        count   = "1"
        percent = "0"
      }
    }

    display_name = "use-80-percent-resource-disk"
  }

  display_name = "use-80-percent-resource-disk"

  enabled               = "true"
  notification_channels = ["projects/project-name/notificationChannels/0123456789"]
  project               = "projet-name"
}
```
上記はアラートが上がったときに通知されるメールアドレスと、ディスクの使用率が80%を超えたときに上がるアラートを定義しています。普段自分でコードを書くのであれば、作成したリソースを別のところで参照したり、`locals`などを使って変数を定義することで可読性やコードの短縮化を図ることができますが、Terraformerで出力されたソースは全てがベタ書きになっています。このコードを読みやすくして、後のメンテナンスや他のアラート作成にも使いやすくなるようにします。

### 読みやすくする
Terraformerで出力されるファイルにはtfstateも含まれます。今回は出力されたtfstateをGCSバケットに置いて作業しています。GCSバケットへのアップロードは`gsutil`コマンドを使ってアップロードします。バケットの指定が適切であれば`terraform state list`でtfstateのリソースを確認でき、上記のリソースは

```shell
$ terraform state list
google_monitoring_notification_channel.tfer--projects--project-name--notificationChannels--0123456789
google_monitoring_alert_policy.tfer--projects--project-name--alertPolicies--17320504
```

と出力されるかと思います。まずはリソース名を分かりやすくしていきます。

### リソース名を変える
リソース名の変更にはterraform state mvコマンドを使って変更します。今回はコードを以下のように修正します。

- `google_monitoring_notification_channel`

```tf
# 変更前
resource "google_monitoring_notification_channel" "tfer--projects--project-name--notificationChannels--0123456789" {
....
}

# 変更後
resource "google_monitoring_notification_channel" "hogehoge_email" {
....
}
```

- `google_monitoring_alert_policy`

```tf
# 変更前
resource "google_monitoring_alert_policy" "tfer--projects--project-name--alertPolicies--17320504" {
....
}

# 変更後
resource "google_monitoring_alert_policy" "use_80_percent_resource_disk" {
....
}
```

あとは以下のコマンドでtfstateとの差異がなくなります。

```shell
$ tf state mv google_monitoring_notification_channel.tfer--projects--project-name--notificationChannels--0123456789 google_monitoring_notification_channel.hogehoge_email
$ tf state mv google_monitoring_alert_policy.tfer--projects--project-name--alertPolicies--17320504 google_monitoring_alert_policy.use_80_percent_resource_disk
```

### ソースコードを読みやすくする
リソース名を変更したことによりそのリソースが何を定義しているものなのか分かりやすくなりました。一方、`google_monitoring_alert_policy`の中で指定されてる`notification_channels`のブロックを見てみると、指定されているチャンネルがパッと見たとき分かりにくいですね。`notification_channels`のブロックで参照しているのは各リソースのidになるので、以下のように書き換えます。

```tf
# 変更前
resource "google_monitoring_alert_policy" "use_80_percent_resource_disk" {
....
  notification_channels = ["projects/project-name/notificationChannels/0123456789"]
....
}

# 変更後
resource "google_monitoring_alert_policy" "use_80_percent_resource_disk" {
....
  notification_channels = [
    google_monitoring_notification_channel.hogehoge_email.id
  ]
....
}
```

このように書き換えてterraform planを実行しましょう。変更が起こらなければ成功です！

## まとめ
何回かCloud MonitoringをTerraform化してきたので、これはナレッジとしてまとめたいなと思って書きました。
Terraformのソースはインフラの設定などを記載してあるので、「どこで、なにが」指定されているかわかりやすくなっていることが望ましいかと考えています。今回のネタはある意味リファクタリングになりますが、Terraformのリファクタリングはどこまでいってもソースとしてのパフォーマンスは上がらないんだよな、とふと最近思っています。（可読性が上がる、という意味で人のパフォーマンスは上がるのかもしれないですが。）