Uptime Robotの設定をTerraformでやってみた。

## これはなに？

外形監視の[Uptime Robot](https://uptimerobot.com/)の設定をTerraformを使ってやってみた、というお話しです。
今回は、[terraform-provider-uptimerobot](https://github.com/louy/terraform-provider-uptimerobot) を使って試しています！

## やりたいこと

[前回書いた記事](https://qiita.com/akiko-pusu/items/6f0a262726fa5492fecf) で、Redmineのテーマを作成したのですが、Heroku上にそのデモ環境を立ててみることにしました。

今回は、このデモ環境に外形監視を組み込みたい、というものです。

### なぜやるの？

#### 30分アクセスがないとスリープしてしまう

デモ環境はHerokuの無償プランを使っています。
Herokuのアプリケーションは無償プランだと30分アクセスがない（とか動きがない）と、スリープしてしまいます。

- https://devcenter.heroku.com/articles/how-heroku-works#runtime

> Applications that use the free dyno type will sleep after 30 minutes of inactivity. Scaling to multiple web dynos, or a different dyno type, will avoid this.

実際はDockerのイメージからコンテナを立ち上げているのですが、このコンテナがスリープしてしまいます。

30分以上経過した後にアクセスすると、前のコンテナでなく、あたらしくイメージをもとにコンテナが起動するので、初回の起動も遅くなります。
また、アプリケーションにアップした画像やデータは外部に永続化しておかないと、消えてしまいます。

対応はアプリケーションの数を増やす（スケールさせる）か、プランを変えるといった方法や、アドオンを使ったりする方法があります。

#### 定期アクセスのついでに監視もしよう

今回は、「なにかしら**定期的にアクセスを発生させればいいよね？**」と思い立ち、「**ついでに外形監視までやってみるといいよね！**」ということで、Uptime Robotを設定することにしました。
（監視まで行わず、cronで外部から適度にアクセスを設定というのでも、スリープ回避はできます）

## まずはUptime Robotのアカウント作成

Uptime Robotはシンプルな外形監視のサービスです。
無料プランでも基本的なものは利用できます。
Qiitaの記事にもいくつかHeroku & Uptime Robotの組み合わせの記事がいくつかあるので、Web UIからの設定の場合はご参考に。

- [HEROKU_URL が効かず Hubot がアイドル状態になる問題の対策](https://qiita.com/uasi/items/159d113eb262a498e352)(``@uasi``さま)
- [Herokuでアイドルさせないようにした際のメモ](https://qiita.com/udon242/items/b8efd594e380aaf830b3) (``@udon242``さま)

### APIキーを取得

設定はWeb UIではなくTerraformを使って試すため、まずはMAIN APIキーを取得します。

<img width="613" alt="main-api-key.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/b61468f2-69a9-7279-11da-e6ccf5259485.png">


## Terraformのプロバイダ用意

### terraform-provider-uptimerobot を利用する

まずは [terraform-provider-uptimerobot](https://github.com/louy/terraform-provider-uptimerobot) のREADMEの通りに、設定ファイルを追加してみます。

#### 設定ファイル

こんな感じになります。

```
provider "uptimerobot" {
  api_key = "${var.UPTIMEROBOT_API_KEY}"
}

data "uptimerobot_account" "account" {}

data "uptimerobot_alert_contact" "default_alert_contact" {
  friendly_name = "${data.uptimerobot_account.account.email}"
}

variable "UPTIMEROBOT_API_KEY" {
  type = string
  description = "API key for Uptime Robot."
}

variable "SLACK_WEBHOOK_URL" {
  type = string
  description = "Webhook url for slack"
}

resource "uptimerobot_alert_contact" "slack" {
  friendly_name = "Slack #精神と時の部屋"
  type          = "slack"
  value         = "${var.SLACK_WEBHOOK_URL}"
}

resource "uptimerobot_monitor" "main" {
  friendly_name = "Kodomo Redmine Midori Monitor"
  type          = "http"
  url           = "https://kodomo-redmine-midori.herokuapp.com"
  # pro allows 60 seconds / free plan 5min
  interval      = 300

  alert_contact {
    id = "${uptimerobot_alert_contact.slack.id}"
    # threshold  = 0  # pro only
    # recurrence = 0  # pro only
  }

  alert_contact {
    id = "${data.uptimerobot_alert_contact.default_alert_contact.id}"
  }
}

# Status Pageも作ってみよう！
resource "uptimerobot_status_page" "main" {
  friendly_name  = "Kodomo Redmine Midori Status Page"
  sort           = "down-up-paused"
  monitors       = ["${uptimerobot_monitor.main.id}"]
}
```

#### 実行してみると....?

SlackのWebhook URLとUptime RobotのAPI Keyは、環境変数で渡すことにします。

```bash
export TF_VAR_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/xxxxx'
export TF_VAR_UPTIMEROBOT_API_KEY='xxxxxxxxxxxxx'

# とりあえず通知先のslack設定を追加してみます
terraform plan -target=uptimerobot_alert_contact.slack
```

結果、プラグインがないとエラーが出ました！

```bash
Error: Could not satisfy plugin requirements


Plugin reinitialization required. Please run "terraform init".

Plugins are external binaries that Terraform uses to access and manipulate
resources. The configuration provided requires plugins which can't be located,
don't satisfy the version constraints, or are otherwise incompatible.

Terraform automatically discovers provider requirements from your
configuration, including providers used in child modules. To see the
requirements and constraints from each module, run "terraform providers".



Error: provider.uptimerobot: no suitable version installed
  version requirements: "(any version)"
  versions installed: none
```

### terraform-provider-uptimerobot を追加する

このプラグインはまだ公式サイトにエントリされていないため、自分でローカル環境に配置しないといけないようです。

やってみて初めて気がついたのですが、

- GitHubのリポジトリからソースコードをとってくるのではダメ
- 実際はGo言語なので**コンパイルしてOSに合ったバイナリをプラグインとして組み込まないといけない**

というあたりでした。

#### 配置手順: リリースのページへ

まずは、以下のリリースのページへ。

- https://github.com/louy/terraform-provider-uptimerobot/releases

バイナリが添付されているので、私の場合は ``terraform-provider-uptimerobot_0.4.3_darwin_amd64.tar.gz
`` をダウンロードしました。

- terraform-provider-uptimerobot_0.4.3_darwin_amd64.tar.gz
 を展開
- Terraformの作業用フォルダに terraform.d というディレクトリを作成
- plugins 以下に展開したファイルを配置

```bash
tree terraform.d/
terraform.d/
└── plugins
    └── darwin_amd64
        ├── CHANGELOG.md
        ├── LICENSE
        ├── README.md
        └── terraform-provider-uptimerobot

2 directories, 4 files

# バイナリです
file terraform-provider-uptimerobot
terraform-provider-uptimerobot: Mach-O 64-bit executable x86_64

# 動かしてみると、「これはプラグインだよ！」というメッセージが
./terraform-provider-uptimerobot
This binary is a plugin. These are not meant to be executed directly.
Please execute the program that
```

配置した上で、再度 terraform planしてみます。
こんどはうまくいったようです！！

```bash

terraform plan -target=uptimerobot_alert_contact.slack
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # uptimerobot_alert_contact.slack will be created
  + resource "uptimerobot_alert_contact" "slack" {
      + friendly_name = "Slack #精神と時の部屋"
      + id            = (known after apply)
      + status        = (known after apply)
      + type          = "slack"
      + value         = "https://hooks.slack.com/services/xxxxxxxxx"
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Releasing state lock. This may take a few moments...
```

### 順番に適用してみる

うまく動きそうなので、順番に適用してみます。
ターゲットのリソース1つ1つに対し実行していきます。

- uptimerobot_alert_contact.slack (Slackの通知設定追加)
- uptimerobot_monitor.main (Herokuのアプリケーションの監視追加)
- uptimerobot_status_page.main (StatusPageの追加)

```bash

terraform apply -target=uptimerobot_alert_contact.slack
terraform apply -target=uptimerobot_monitor.main
terraform apply -target=uptimerobot_status_page.main

....

# うまくいった模様
uptimerobot_status_page.main: Creating...
uptimerobot_status_page.main: Creation complete after 1s [id=180831]

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

### できたか確認してみます

Uptime Robot側に出来上がっているか確認してみます。

#### Slack通知設定

表示では文字化けしていますが、出来ています！

<img width="720" alt="notification-slack.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/f0f3988e-734d-ce36-f54b-be5e85ccfbe3.png">

#### 監視設定

こちらも追加されました！

<img width="1027" alt="main-monitor-added.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/7bf420b3-a32e-f6f8-c827-d7cecf6088eb.png">

#### Status Page

同じく出来た模様！

<img width="1027" alt="StatusPage-setting.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/dbb3c416-65e5-327d-114f-ca10c3a6d83c.png">


### Heroku側を確認

Heroku側で、対象のアプリケーションにアクセスがあるか確認してみます。

```bash
heroku logs -t  -a kodomo-redmine-midori

.... [中略] ....

2020-05-27T04:29:08.396035+00:00 heroku[router]: at=info method=HEAD path="/" host=kodomo-redmine-midori.herokuapp.com request_id=xxxxx fwd="xxxxx" dyno=web.1 connect=0ms service=60ms status=200 bytes=865 protocol=https
2020-05-27T04:29:37.309943+00:00 heroku[router]: at=info method=HEAD path="/" host=kodomo-redmine-midori.herokuapp.com request_id=xxxxx fwd="xxxxx"  dyno=web.1 connect=1ms service=32ms status=200 bytes=865 protocol=https

```

Uptime Robotのフリープランでは、HEADメソッドを使ってのアクセスになります。
ちゃんとアクセスが来ているのがわかります。

### Herokuのアプリをダウンさせてみる

監視が正しく動いているか、通知が来るかの確認のため、Herokuのアプリをダウンさせてみます。

```bash
heroku maintenance:on -a kodomo-redmine-midori
Enabling maintenance mode for ⬢ kodomo-redmine-midori... done
```

#### 通知が来たよ！

ダウン（メンテナンスモード）に切り替えると、Herokuのアプリケーションは503を返します。
しばらくして、Slackに通知が届きました！

<img width="795" alt="Slack-result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/cc0cc45a-7cfb-b386-f00d-11f0ca87b4f9.png">


#### 画面はどうなった？

Slackの通知も届いたので、画面を確認するとDownになっています。
今度はメンテナンスモードをoffにしてアプリケーションを再開します。

```bash
heroku maintenance:on -a kodomo-redmine-midori
Enabling maintenance mode for ⬢ kodomo-redmine-midori... done
```
しばらくすると、画面も Down -> Upが表示されました！

<img width="1010" alt="monitor-up-down.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/67f8a78a-e402-df46-630a-ccd7c76f020c.png">

再開の場合も、設定次第ですがSlackで通知してくれます。


### Status Pageを表示してみる

パブリックアクセスできるStatus Pageはこんな感じです。
うまくデータも取れている模様。
ありがたいですね！

<img width="994" alt="StatusPage-view.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27976/fd74928a-06bf-52c2-41cd-1d118536803b.png">

## 結果的に

定期的なアクセスが発生するおかげで、スリープすることもなく稼働してくれているようで、なによりです！

## ソースを眺める

動くのは確認できましたが、どうやって作っているのか、どんなAPIを叩いているのかが気になります。
まずはこの辺を眺めます。

- https://github.com/louy/terraform-provider-uptimerobot/tree/master/uptimerobot

Uptime RobotのAPIはv2を使っているようです。

- https://github.com/louy/terraform-provider-uptimerobot/blob/cdb6af763b95e39a82c1aa2830f807187d4fb900/uptimerobot/api/client.go#L27
- https://uptimerobot.com/api

ほとんど機能としてカバーしていて、メンテナンスウィンドウの設定だけはカバーしていないようですが、そこまで重要じゃないみたいなのと、OSS参加のビギナー向けに取り組む良い課題 (good first issue) だとして、プルリクエストを受けつけているようです！

- [Support for maintenance windows ](https://github.com/louy/terraform-provider-uptimerobot/issues/35)

## 次やりたいこと

「やってみた」だけの記事ではありますが、結果的にバイナリの配置の件も含めて、新たに覚えることも出てきました。

リポジトリを作ってくださった作者の方の応援とか、できればissueへのお手伝いなどを通して、もう少し学んで行けたらなと思います！