Add annotation to Mackerel when changed infrastructure with Terraform

mackerelにグラフアノテーション機能が追加されました。

グラフアノテーション機能とは先日のmackerelミートアップでも紹介があった通り、デプロイや何かしらの事象をmackerelのグラフ上で注釈することができる機能です。

今回はterraformで特定のリソースに変更があった場合、それをグラフ上に表示させてみます。

まずは普通にAPIを叩いてみる

鬼のようなcurlを実行します。

できた。

Terraformと連携させる方法を考える

今回は例として、DigitalOceanのtagリソースに変更が加えられた場合、グラフに注釈を入れるようにしてみます。

「tagリソースに変更が加えられた場合何かを実行する」方法として、null_resourcelocal-execを使ってみます。

local-execとはterraformの提供するprovisionersの一つで、その名の通り何かしらのリソースと連携させて、ローカル上(terraformコマンドを実行するマシン上)でコマンドを実行するプロビジョナです。

例えば公式のドキュメントではEC2インスタンスが立ち上がった後インスタンスのipアドレスをechoする例を挙げています。

resource "aws_instance" "web" {  
    ...
    provisioner "local-exec" {
        command = "echo ${aws_instance.web.private_ip} >> private_ips.txt"
    }
}

null_resourceとはどのリソースにも(例えばaws_instanceなど)関連付けずにプロビジョナを実行したい場合に使われるものです。ではどのタイミングでプロビジョナが実行されるかというと、

  • null_resource自体に変更が加わったとき(名前を変えたりとか)
  • triggersで指定したリソースのアトリビュートに変更が加えられたとき

に発動します。

つまり、今回は「null_resourceのtriggersにtagリソースを指定」して「null_resourceにlocal-execを組み込む」ことで「tagリソースに変更が加えられたらグラフに注釈を入れるコマンドを実行する」ことができそうです。

やってみる

以下のコードをmain.tfに追記してみました。

  • vault_generic_secretデータソースを使いvaultからmackerelのapiキーを取得
  • null_resourceのtriggersにタグリソースの変数をmodule越しに指定
  • null_resourcelocal-execを所属させ、鬼のようなcurlを実行
data "vault_generic_secret" "mackerel_apikey" {  
  path = "secret/mackerel_apikey"
}

resource "null_resource" "tag_changed_annotation" {  
  triggers {
    value = "${module.droplet.tag_name}"
  }

  provisioner "local-exec" {
    command = "curl -X POST -H \"X-Api-Key: ${data.vault_generic_secret.mackerel_apikey.data["value"]}\" -H \"Content-Type:application/json\" -d \"{\\\"title\\\": \\\"terraform\\\", \\\"description\\\": \\\"Tag changed\\\", \\\"from\\\": $(date +%s), \\\"to\\\": $(date +%s), \\\"service\\\": \\\"VPS\\\", \\\"roles\\\": [\\\"ponpokopon\\\"]}\" https://mackerel.io/api/v0/graph-annotations"
  }
}

${data.vault_generic_secret.mackerel_apikey.data["value"]}の部分でmackerelのapiキーをセットしています。最近マイブームなvaultプロバイダを使用しています。

Applyしてみる

tagを書き換えて...。

 # Create tag
 resource "digitalocean_tag" "ponpokopon" {
-  name = "ponpokopon2"
+  name = "hogehoge"
 }

terraform apply実行!

$ terraform apply
...
null_resource.tag_changed_annotation (local-exec): Executing: /bin/sh -c "curl -X POST -H "X-Api-Key: XXXXXXXX" -H "Content-Type:application/json" -d "{\"title\": \"terraform\", \"description\": \"Tag changed\", \"from\": $(date +%s), \"to\": $(date +%s), \"service\": \"VPS\", \"roles\": [\"ponpokopon\"]}" https://mackerel.io/api/v0/graph-annotations"  
null_resource.tag_changed_annotation (local-exec):   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current  
null_resource.tag_changed_annotation (local-exec):                                  Dload  Upload   Total   Spent    Left  Speed  
null_resource.tag_changed_annotation (local-exec):   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  
null_resource.tag_changed_annotation (local-exec):   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  
null_resource.tag_changed_annotation (local-exec): 100   274  100   141  100   133    209    197 --:--:-- --:--:-- --:--:--   209  
null_resource.tag_changed_annotation (local-exec): {"id":"2VaicFLCaTN","title":"terraform","description":"Tag changed","from":1485687817,"to":1485687817,"service":"VPS","roles":["ponpokopon"]}  
...

良さそう!グラフ確認。

Yay!!

課題

  • 全てのリソースを対象に変更を検知し、local-execを適用したい
  • 下記エラーが出る事がある
null_resource.tag_changed_annotation: Destruction complete  
Error applying plan:

1 error(s) occurred:

* digitalocean_tag.ponpokopon: Failed to update tag: PUT https://api.digitalocean.com/v2/tags/ponpokopon2: 422 Error processing request: operation failed. Please try again.
module.droplet.digitalocean_tag.ponpokopon: Creation complete  
Error applying plan:

2 error(s) occurred:

* Resource 'digitalocean_tag.ponpokopon' does not have attribute 'id' for variable 'digitalocean_tag.ponpokopon.id'
* null_resource.tag_changed_annotation: diffs didn't match during apply. This is a bug with Terraform and should be reported as a GitHub Issue.

Please include the following information in your report:

    Terraform Version: 0.8.5
    Resource ID: null_resource.tag_changed_annotation
    Mismatch reason: attribute mismatch: triggers.value
    Diff One (usually from plan): *terraform.InstanceDiff{mu:sync.Mutex{state:0, sema:0x0}, Attributes:map[string]*terraform.ResourceAttrDiff{"triggers.%":*terraform.ResourceAttrDiff{Old:"", New:"1", NewComputed:false, NewRemoved:false, NewExtra:interface {}(nil), RequiresNew:true, Sensitive:false, Type:0x0}, "triggers.value":*terraform.ResourceAttrDiff{Old:"", New:"ponpokopon2", NewComputed:false, NewRemoved:false, NewExtra:interface {}(nil), RequiresNew:true, Sensitive:false, Type:0x0}}, Destroy:false, DestroyDeposed:false, DestroyTainted:false}
    Diff Two (usually from apply): *terraform.InstanceDiff{mu:sync.Mutex{state:0, sema:0x0}, Attributes:map[string]*terraform.ResourceAttrDiff{"triggers.%":*terraform.ResourceAttrDiff{Old:"", New:"", NewComputed:true, NewRemoved:false, NewExtra:interface {}(nil), RequiresNew:true, Sensitive:false, Type:0x0}}, Destroy:false, DestroyDeposed:false, DestroyTainted:false}

Also include as much context as you can about your config, state, and the steps you performed to trigger this error.  
  • また、既に作成したことのあるtagに変更しようとすると下記エラーになる場合がある
module.droplet.digitalocean_tag.ponpokopon: Modifying...  
  name: "hogehoge" => "ponpokopon"
Error applying plan:

1 error(s) occurred:

* digitalocean_tag.ponpokopon: Failed to update tag: PUT https://api.digitalocean.com/v2/tags/hogehoge: 422 Error processing request to tags server: Both origin and destination tags already exists. Please try again.

感想

多少強引だけどterraformとmackerelを連携させることが出来ました。

以前個人サーバのdnsの設定をいつの間にか変えてしまったことがあり、Githubの変更から変えてしまったタイミングを探したことがありましたが、グラフアノテーションをうまく使ってインフラの変更を時系列で見ていければ便利だなと思いました。

あとは例えばawsのamiやインスタンスタイプを変更した前後でグラフがどう変わるかとか見れたら楽しそう。

今後はansibleとも連携させてみようと思っています。

参考