なんでもTerraform

こんにちは。@chaspy です。[Terraform-jp](https://terraform-jp.connpass.com/) の運営をやっています。

これは[terraform Advent Calendar 2019](https://qiita.com/advent-calendar/2019/terraform)の25日目の記事です。

state の分割単位とか、Infrastructure as code のメリットとか、CI/CD を用いた Terraform リポジトリの運用とか、そういうまじめな話は他の記事に譲って、最終日である今日は、僕がこれまで Terraform 化してきたものを紹介しようと思います。

## AWS

それはそう、という感じですね。

業務で使用している AWS リソースのほぼすべてが Terraform で管理されています。

ただし、IAM は [miam](https://github.com/codenize-tools/miam), DNS の一部は [roadworker](https://github.com/codenize-tools/roadworker) を使用しており、Terraform を使用していません。

導入したのは私ではないので、正確な背景は把握していませんが、IAM は Developer が加入してきた際に自身のユーザ発行を PR で追加してもらう運用をしており、apply 後に credential を SRE が渡しています。

今でこそ AWS リソースを管理している Terraform リポジトリの運用・管理をようやく Developer に委譲しつつありますが、最初から Terraform で IAM を管理していた場合、state の影響範囲が大きいので、運用上問題が出たんじゃないかなと思います。

IAM は他の AWS リソースとライフサイクルを共にすることもないので、この分け方は賢いなと今思います。

DNS は Public Record を roadworker で管理しています。Private Record は Cloud Resource を作成する際に同時に作成することが多いので、Terraform で作成しています。

管理していないリソースは Cloudfront の一部ぐらいだと思います。

入社してから一年半、触らない日はない、というと過言かもしれませんが、業務のメインと言えるぐらい触っています。

## Deadman's Snitch

ここからは自分が Terraform 化をしたものになります。[Deadman's Snitch](https://deadmanssnitch.com/) というサービスをご存知でない方も多いと思います。

これは Job System の監視に使っています。具体的には Jenkins Job の監視です。

Job の監視というと、失敗時に通知がくるようにすればいい、と思うかもしれませんが、その場合、ジョブが正常に終了せずに数日に渡って実行され続けていた場合や、何かしらの人為的なミスでジョブそのものが実行されていない場合に気づくことができません。

Deadman's Snitch は Snitch という単位がジョブに対応しており、期待するジョブの実行間隔を指定します。

ジョブ側では終了後に特定の url に HTTP GET を行うだけです。

Deadman's Snitch はその期間に通知がこない場合は、ジョブが正常に働いていないということで通知を行います。これは Slack に飛ばしています。

毎日行なっているデータベースのリストアのジョブや、Ansible の Nightly Build のジョブ、あるいは Critical なバッチ処理を行うジョブなど様々に活用されています。

もともと SRE のみが利用していたのですが、Developer でも使えるようにしたほうがいいね、となったときに、コード化することで Pull Requests 経由で Snitch 追加できるようになりました。

https://github.com/plukevdh/terraform-provider-dmsnitch

3rd Party ですが、運用開始して1年程度で一切問題は起きていません。

tf file はこんな感じです。

```hcl
resource "dmsnitch_snitch" "restore_develop_database" {
  name     = "restore_develop_database"
  notes    = "https://jenkins.example.com/view/Database/job/restore_develop_database/"
  interval = "daily"
  type     = "basic"
  tags     = ["develop"]
}
```

notes に job の url を貼るルールにしています。

## Pingdom

外形監視として [Pingdom](https://www.pingdom.com/) を使用しています。

今や [Datadog Synthetics](https://docs.datadoghq.com/synthetics/) や [Amazon Cloudwatch Synthetics](https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazoncloudwatchsynthetics.html) があるわけで、Datadog をメインで使っていることから集約したいという気持ちもありつつ、えいやでコード化したら満足してしまっている現状です。

https://github.com/russellcardullo/terraform-provider-pingdom

こちらも 3rd Party ですが、同じく一切問題は出ていません。便利に使わせてもらっています。

Pingdom は安いんですが、契約の関係上ユーザ数に上限があり、SRE しか設定ができないという状況でした。

まぁ確かに外形監視が増減することってめったにないので、その都度 SRE がやるでもいいといえばいいんですが、やはりコード化しておくと新しくサービス・エンドポイントを作成したい Developer が自分自身で Pull Requests を出せるというのはいいことだなぁと実感しています。

これは [Production Readiness Check](https://quipper.hatenablog.com/entry/2019/06/07/080000) から Link が貼られており、インターネットに露出する（＝外形監視が必要）なサービスを新規に作成する場合に必ず作ってもらうようにしてます。

tf file はこんな感じです。

```hcl
resource "pingdom_check" "example_quipper_com" {
  encryption       = true
  type             = "http"
  name             = "example.quipper.com"
  notifyagainevery = 5
  host             = "example.quipper.com"
  resolution       = 1
  shouldcontain    = "pong"
  tags             = "jp,production,important"
  url              = "/ping"
  userids = [
    12345678
  ]
}
```

## Datadog - SLO(Service Level Objective)

https://www.terraform.io/docs/providers/datadog/r/service_level_objective.html

Quipper では現在 SLI/SLO を定めて、各プロダクトチームでサービスの信頼性を制御するための取り組みをはじめています。

はじめは僕が1人でやっていたので GUI でポチポチでも良かったんですが、最終的に各プロダクトチームで目標値を決めるとなると、それは各自で更新できる必要があります。

また、他のチームが何を SLI として採用し、どれぐらいの SLO を持っているかをすぐに閲覧できることもメリットです。

例えば新しくサービスを追加したとき、いちいち手動で SLO を定めるのも手間ですし、ある SLI を持っている SLO を一括で変更したいときもあるでしょう。そのようなときにコード化しておくメリットを非常に大きく感じます。

tf file はこんな感じです。

```hcl
resource "datadog_service_level_objective" "example_quipper_com_Pingdom_check_error" {
  name = "[SLO - 99.99%]example.quipper.com Pingdom check error"
  type = "metric"
  query {
    numerator   = "sum:pingdom.check.status.count{check:example.quipper.com}.as_count()-(2*sum:pingdom.check.error.count{check:example.quipper.com}.as_count())"
    denominator = "sum:pingdom.check.status.count{check:example.quipper.com}.as_count()-sum:pingdom.check.error.count{check:example.quipper.com}.as_count()"
  }

  thresholds {
    timeframe = "7d"
    target    = 99.9
    warning   = 99.95
  }

  thresholds {
    timeframe = "30d"
    target    = 99.9
    warning   = 99.95
  }

  thresholds {
    timeframe = "90d"
    target    = 99.9
    warning   = 99.95
  }

  tags = ["country:ph", "subdomain:example"]
}

resource "datadog_service_level_objective" "example_quipper_com_http_success_rate" {
  name = "[SLO - 99.95%]example.quipper.com http success rate"
  type = "metric"
  query {
    numerator   = "sum:nginx.status_2xx{environment:production,nginx_domain:example.quipper.com}.as_count()"
    denominator = "sum:nginx.status_2xx{environment:production,nginx_domain:example.quipper.com}.as_count()+sum:nginx.status_5xx{environment:production,nginx_domain:example.quipper.com}.as_count()"
  }

  thresholds {
    timeframe = "7d"
    target    = 99.95
    warning   = 99.99
  }

  thresholds {
    timeframe = "30d"
    target    = 99.95
    warning   = 99.99
  }

  thresholds {
    timeframe = "90d"
    target    = 99.95
    warning   = 99.99
  }

  tags = ["country:ph", "subdomain:example"]
}
```

Availability として Pingdom Check を、Success Rate として Reverse Proxy(Nginx) の Metrics を採用しています。

SLI/SLO の導入、運用については絶賛試行錯誤中です。続きは [SRE NEXT](https://sre-next.dev/schedule) に遊びにきてください。

なおこちらは Datadog の SLO がベータなので API の挙動が少し不安定だったりしますが、目を瞑って運用しています。

## Datadog - Dashboard(TBD)

https://www.terraform.io/docs/providers/datadog/r/dashboard.html

はい。まだできていませんが、いよいよしないといけないと思っているので年明けにはなんとかすると思います。

Datadog Dashboard には特に秩序がなく、好きなひとが好きなものを好きなように作ることができます。

自分で自由に作れるのであればそれはそれでいいのですが、多くの Developer はそもそも何を見るべきなのか、どういう項目を載せるべきなのかについてポリシーを持っていないはずです。

また、Dashboard を作成することが [Production Readiness Check](https://quipper.hatenablog.com/entry/2019/06/07/080000) の項目となっていながら、実質 @chaspy が作業している状態になっていたりして、非常によくない状態です。（せめてテンプレート Dashboard から作っておけば良いんですが、載せる項目が多様だったりして、難しいのです。）

今後は SLI/SLO と合わせて、見るべき項目はパターンに合わせて標準化していくことが必要だと感じています。標準化することで、数パターンぐらいに分けられて、あとはコピペで作れるようになる世界を早いうちに作っていきたいです。

ちなみに言い訳しておくと [Group Widget](https://docs.datadoghq.com/graphing/widgets/group/) がずっと対応されてなかったので諦めていたというのもありました。（僕が [Group Widget](https://docs.datadoghq.com/graphing/widgets/group/) が好きなので。Dashboard って単に並べるだけだと「意図」が失われてしまうので、 Grouping することで意図を残したいのです。）

## おわりに - なんでも Terraform にすること

これらのものを Terraform でコードとして管理してきて思うことは、設定・インフラのコード化は委譲を助けるということです。

SRE にとって、プロダクトチームが自律して、SRE に「お願い」しなくてもできるように、信頼性・安全性を担保しながら、作りたいものを作りたいように作れるプラットフォームを整えることは重要な仕事の1つです。

こうやってコード化しておくことで、プロダクトチームのメンバーは既存のコードを読みながら、仕事を進めることができます。

最初は少しだけ時間がかかりますが、そのコストはすぐに回収できるという実感を得たので、今後も Terraform という[ハンマー](https://en.wiktionary.org/wiki/if_all_you_have_is_a_hammer,_everything_looks_like_a_nail)でありとあらゆるものをコード化していこうと思います。

ちなみにやり方は特に説明しませんでしたが、state を置く S3 バケットを作って、CI 設定を書いて、1つ sample の tf を書いた後は、API を駆使してひたすら tf file / import script を作成して、残りの差分は頑張る、みたいなやり方でやってます。

このへんも HCL を Imput / Output できる Module を使いこなせばもっとスマートにできると思うので、来年以降の課題ということで。

みなさんの Terraform 生活の様子を、ぜひ Terraform meetup tokyo で共有してください！お待ちしています。（次回は2月予定です！）それでは良いお年を！

Happy Terraforming ⭐