Terraform で始める ThousandEyes の構成管理！

# 要約

- [Terraform](https://www.terraform.io/) は構成管理ツールです。
- [ThousandEyes](https://www.thousandeyes.com/ja/) はネットワークの状況を可視化するツールです (操作簡単なのに強力で、オススメですよ！)
- この記事では Terraform を用いて ThousandEyes を構成管理する手順を説明します。
- ThousandEyes の基本については説明しません。

# ThousandEyes を Terraform で構成管理するメリット

一般的に ThousandEyes は「ブラウザで Web 管理画面にアクセスし、設定する」というケースが多いと思います。 以下は設定画面の一例です。 Web 設定画面から [ドキュメントページ](https://docs.thousandeyes.com/) にアクセスし、各パラメータの意味を調べながら設定することも出来ます。

![01a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/c50c818e-19d3-1d8a-69b5-553cad0f5500.png)

ですが Web 管理画面からの手動設定には以下のような課題があります。

- Web 管理画面から手動で設定する場合の課題
  1. 設定量が多い場合、設定に時間がかかる
  2. 手動での設定は不正確であり、誤りが混入し易い
  3. 「一括して特定のパラメータを変更したい」という場合、一括変更に手間がかかる
  4. 「手動で設定した内容」と「ドキュメント」を同期させる必要がある

これらの課題は Terraform のような構成管理ツールを導入することで解決出来ます。 構成管理ツールのメリットは「DevOps」や「IaC (Infrastructure as Code)」といったキーワードに詳しいところですが、代表的なものとしては以下が挙げられます。

- 構成管理ツールを導入するメリット
  1. 設定変更が手動よりも早い
  2. 定義ファイルに記載した内容通り、正確に設定される
  3. 定義ファイルをそのまま "設定パラメータ" として扱いことで別途、ドキュメント管理する必要が無くなる

ThousandEyes は製品の特性上、「よく似た監視設定を・大量に設定する」ことが想定される為、他のユースケースと同等以上に構成管理ツールのメリットを享受し易いと考えられます。

# Terraform / tfenv のインストール

まず Terraform が利用出来るようにインストールします。 後述しますが、可能であれば (直接 Terraform をインストールするのでは無く) [tfenv](https://github.com/tfutils/tfenv) をインストールし、tfenv を使って Terraform のバージョンを管理するのがオススメです。

## Terraform のインストール

Terraform のソースコードは [GitHub](https://github.com/hashicorp/terraform) 上で管理されています。 Go 言語で実装されており、ビルド済みバイナルは Windows や macOS、Linux 向けに配布されている為、手軽に始めることが可能です。 Windows であれば [Chocolatey](https://chocolatey.org/)、macOS であれば [Homebrew](https://brew.sh/index_ja)、Linux でも各ディストリビューションのパッケージマネージャで簡単にインストール出来ます。

具体的なインストール手順は公式サイトの [Install Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) ページに記載されています。 「Windows 上で Chocolatey」または「macOS 上で Homebrew」を使って Terraform をインストールする手順は以下の通りです。

### Chocolatey on Windows

```sh
choco install terraform
```

### Homebrew on macOS

```sh
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
```

## tfenv のインストール

Terraform はバージョン間で互換性が無い場合があります。 その為、可能であれば「直接、Terraform 本体をインストールする」のでは無く、バージョンマネージャである tfenv をインストールし、必要な Terraform バージョンを複数切り替えて利用する方が安全です。

tfenv のインストール方法は [GitHub の Installation ページ](https://github.com/tfutils/tfenv#installation) に記載されています。 macOS や Linux であれば導入は簡単ですが、Windows は下記と記載されているようにやや注意が必要です。

> Windows (64bit) - only tested in git-bash - currently presumed failing due to symlink issues in git-bash

尚、macOS であれば Terraform 本体同様に Homebrew で簡単にインストール出来ます。

### Homebrew on macOS

```sh
brew install tfenv
```

## tfenv から Terraform をインストールする

tfenv でインストール可能な Terraform のバージョン一覧は `tfenv list-remote` を実行することで表示出来ます。

```sh
$ tfenv list-remote
1.4.0-alpha20221109
1.3.5
1.3.4
  ・
  ・
  ・
```

特定バージョンの Terraform をインストールするには `tfenv install [VERSION]` を実行します。 記事執筆時点で 1.4.0 はアルファ版ですので、今回は 1.3.5 をインストールしてみます。

```sh
tfenv install 1.3.5
```

利用する Terraform バージョンを切り替えるには `tfenv use [VERSION]` を実行します。 先程インストールした 1.3.5 を利用出来るようにしてみます。

```sh
tfenv use 1.3.5
```

「インストール済みの Terraform バージョンの一覧」と「現在、利用中の Terraform バージョン」を表示するには `tfenv list` を実行します。 `*` が表示されているものが現在、利用中のバージョンです。

```sh
$ tfenv list
* 1.3.5 (set by /opt/homebrew/Cellar/tfenv/3.0.0/version)
  1.3.1
  1.2.9
  1.2.7
  1.2.6
  1.2.5
  1.2.4
```

# 認証用トークンを用意する

Terraform から ThousandEyes を構成管理するには認証用トークンが必要です。 認証用トークンは ThousandEyes の Web 管理画面から `Account Settings` → `Users and Roles` → `User API Tokens` へアクセスし、`OAuth Bearer Token` の `Create` をクリックすることで取得可能です。

![02a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/55a37fa0-6585-c814-b303-33f17074a78c.png)

認証用トークンが表示されますので、この値は紛失・漏洩しないよう厳重に保管しておきます。

![03a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/91954f56-8481-7516-0f82-50a06a93f248.png)

一度、認証用トークンを取得すると値は `*` 表示で秘匿され、値を確認することが出来ません。 もし認証用トークンの値が分からなくなってしまった場合は失効 (`Revoke`) させてから再度 `Create` して認証用トークンを生成し直す必要があります。

![04b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/0c1c6069-14fd-7dd6-61b1-8cad3acef0e9.png)

# Terraform で ThousandEyes を構成管理してみる

これまでの手順で事前の準備は完了です。 ここからはいよいよ Terraform を操作していきます。

## Terraform の一般的なライフサイクル

Terraform の一般的なライフサイクルは概ね、以下です。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/2c84e031-b7a7-0b18-764e-d85ef937c437.png)

Terraform の基本的なサブコマンドは以下の 4 つです。

| サブコマンド | 意味                                        |
|--------------|---------------------------------------------|
| `init`       | ワークスペースの初期化を行う                |
| `plan`       | 実行内容の検証を行う。 実際の変更は行わない |
| `apply`      | 変更を行う                                  |
| `destroy`    | 変更を破棄する                              |

## ThousandEyes Provider のバージョン確認

ThousandEyes 用の Terraform Provider に関するドキュメントは [thousandeyes Provider](https://registry.terraform.io/providers/william20111/thousandeyes/latest/docs) で公開されています。 このページでは Terraform Provider で設定可能なパラメータなどの確認が出来るのですが、同時にリリース済み Provider のバージョンも確認出来ます。 記事執筆時点の最新バージョンは 1.3.0 でした。

![06a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/3a2353a3-cdeb-02c4-b750-783fa4865315.png)

## .tf ファイルの準備

Terraform を実行する際は「実行したい内容」を記載した定義ファイルの準備が必要になります。 このファイルは一般的に HCL (HashiCorp Configuration Language) という形式で記載され、拡張子は `.tf` が用いられます。 但し、Terraform の定義ファイルは JSON 形式で記載することも出来、この場合は拡張子として `.tf.json` が用いられます。

今回は以下の内容で `test.tf` ファイルを新規作成しました。 Cloud & Enterprise Agent を利用した、簡単な HTTP テストを定義しています。

`required_providers` に `thousandeyes` を指定し、バージョンは事前に確認した `1.3.0` を指定しています。 商用環境では「一度、正しく動作することを確認出来た」場合、必然的な理由が無い限り Terraform 本体や Provider のバージョンは固定化し、変更しない方がトラブルを避けられるかもしれません。

`token` には事前に ThousandEyes の Web 管理画面で確認した認証用トークンを指定します。

また、このサンプルでは [thousandeyes_agent (Data Source)](https://registry.terraform.io/providers/william20111/thousandeyes/latest/docs/data-sources/agent) や [thousandeyes_http_server (Resource)](https://registry.terraform.io/providers/william20111/thousandeyes/latest/docs/resources/http_server) を利用しています。 これらで指定可能なパラメータ詳細を把握したい場合は前述のリンクからドキュメントを参照し、パラメータを指定していきます。

```tf
terraform {
  required_providers {
    thousandeyes = {
      source  = "thousandeyes/thousandeyes"
      version = "1.3.0"
    }
  }
}
provider "thousandeyes" {
  token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
data "thousandeyes_agent" "agent1" {
  agent_name = "Tokyo, Japan"
}

resource "thousandeyes_http_server" "http1" {
  test_name      = "Example HTTP test set from Terraform provider"
  interval       = 120
  alerts_enabled = false

  url = "https://www.thousandeyes.com"

  agents {
    agent_id = data.thousandeyes_agent.agent1.agent_id
  }
}
```

## 初期化する (terraform init)

ここまでの用意が出来たら `.tf` と同じディレクトリで `terraform init` を実行し、ワークスペースの初期化を行います。 この作業は初回のみ、実行します (※ 複数回、実行しても問題はありません)。 具体的には `.tf` ファイルから参照されている Provider がダウンロードされます。

以下は実行例です。 前述の通り Provider をダウンロードする為、多少時間がかかります。

```sh
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding thousandeyes/thousandeyes versions matching "1.3.0"...
- Installing thousandeyes/thousandeyes v1.3.0...
- Installed thousandeyes/thousandeyes v1.3.0 (self-signed, key ID 02BB91CE566497C7)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

## 検証する (terraform plan)

ワークスペースの初期化が完了したら、次は変更内容の検証を行う為に `terraform plan` を実行します。 これは所謂、[Dry Run](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A9%E3%82%A4%E3%83%A9%E3%83%B3_(%E6%A4%9C%E6%9F%BB)) と言われる動作に該当し、実際の変更は行わずに「構文の妥当性」を確認します。 但し、これはあくまで「構文の確認」レベルですので、例えば「1 〜 100 しか設定出来ない値に 1,000 と設定しようとしている」といった値の妥当性確認は行うことが出来ません。 `plan` を事前に実行し、最低限の事前確認を行うことは重要ですが、同時に「万能では無い」(`plan` が OK であっても実際の実行時エラーになる可能性はある) という点は理解しておく必要があります。

以下は実行例です。 今回は "新規作成" ですので `+create` と表示されています。

```sh
$ terraform plan
data.thousandeyes_agent.agent1: Reading...
data.thousandeyes_agent.agent1: Read complete after 2s [id=0x140004a4f60]

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:

  # thousandeyes_http_server.http1 will be created
  + resource "thousandeyes_http_server" "http1" {
      + alerts_enabled         = false
      + api_links              = (known after apply)
      + auth_type              = "NONE"
      + bandwidth_measurements = false
      + content_regex          = ".*"
      + created_by             = (known after apply)
      + created_date           = (known after apply)
      + enabled                = true
      + follow_redirects       = true
      + http_target_time       = 1000
      + http_time_limit        = 5
      + http_version           = 2
      + id                     = (known after apply)
      + interval               = 120
      + live_share             = (known after apply)
      + modified_by            = (known after apply)
      + modified_date          = (known after apply)
      + network_measurements   = true
      + path_trace_mode        = "classic"
      + probe_mode             = "AUTO"
      + protocol               = "TCP"
      + saved_event            = (known after apply)
      + ssl_version            = (known after apply)
      + ssl_version_id         = 0
      + test_id                = (known after apply)
      + test_name              = "Example HTTP test set from Terraform provider"
      + type                   = (known after apply)
      + url                    = "https://www.thousandeyes.com"
      + verify_certificate     = true

      + agents {
          + agent_id     = 4
          + agent_type   = (known after apply)
          + ip_addresses = (known after apply)
        }
    }

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

## 変更する (terraform apply)

`.tf` ファイルの内容を ThousandEyes に反映します。 `terraform apply` を実行すると以下のように対話的に「継続して良いか？」の確認を求められ、`yes` を入力すると実行が継続されます。

```sh
$ terraform apply
(snip)
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` の入力を必要とせずに実行するには `-auto-approve` オプションを指定して実行します。 以下は実行例です。

```sh
$ terraform apply -auto-approve
data.thousandeyes_agent.agent1: Reading...
data.thousandeyes_agent.agent1: Read complete after 1s [id=0x140003f2d40]

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:

  # thousandeyes_http_server.http1 will be created
  + resource "thousandeyes_http_server" "http1" {
      + alerts_enabled         = false
      + api_links              = (known after apply)
      + auth_type              = "NONE"
      + bandwidth_measurements = false
      + content_regex          = ".*"
      + created_by             = (known after apply)
      + created_date           = (known after apply)
      + enabled                = true
      + follow_redirects       = true
      + http_target_time       = 1000
      + http_time_limit        = 5
      + http_version           = 2
      + id                     = (known after apply)
      + interval               = 120
      + live_share             = (known after apply)
      + modified_by            = (known after apply)
      + modified_date          = (known after apply)
      + network_measurements   = true
      + path_trace_mode        = "classic"
      + probe_mode             = "AUTO"
      + protocol               = "TCP"
      + saved_event            = (known after apply)
      + ssl_version            = (known after apply)
      + ssl_version_id         = 0
      + test_id                = (known after apply)
      + test_name              = "Example HTTP test set from Terraform provider"
      + type                   = (known after apply)
      + url                    = "https://www.thousandeyes.com"
      + verify_certificate     = true

      + agents {
          + agent_id     = 4
          + agent_type   = (known after apply)
          + ip_addresses = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.
thousandeyes_http_server.http1: Creating...
thousandeyes_http_server.http1: Creation complete after 6s [id=3303011]

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

ThousandEyes の Web 管理画面上で確認すると確かに設定されていました。

![07a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960245/e02662d3-ec8e-6422-6194-015e8d5a7d9e.png)

## tfstate ファイルの内容確認

Terraform を実行した履歴 (内容) は `terraform.tfstate` というファイルに保存されます。 この記事では触れませんが、「ThousandEyes に設定済みの内容を Terraform 管理下に置きたい」(`import` したい) という場合は `terraform.tfstate` の内容を理解している必要があります。

テキストエディタや `cat` コマンドで内容を確認すると以下のように表示されます。

```json
{
  "version": 4,
  "terraform_version": "1.3.5",
  "serial": 1,
  "lineage": "f76dbffa-938c-04ce-374b-08683ddc9868",
  "outputs": {},
  "resources": [
    {
      "mode": "data",
      "type": "thousandeyes_agent",
      "name": "agent1",
      "provider": "provider[\"registry.terraform.io/thousandeyes/thousandeyes\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "agent_id": 4,
            "agent_name": "Tokyo, Japan",
            "agent_type": null,
            "id": "0x140003f2d40"
          },
          "sensitive_attributes": []
        }
      ]
    },
    {
      "mode": "managed",
      "type": "thousandeyes_http_server",
      "name": "http1",
      "provider": "provider[\"registry.terraform.io/thousandeyes/thousandeyes\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "agents": [
              {
                "agent_id": 4,
                "agent_name": "",
                "agent_state": "",
                "agent_type": "",
                "cluster_members": [],
                "country_id": "",
                "created_date": "",
                "enabled": false,
                "error_details": [],
                "groups": [],
                "hostname": "",
                "ip_addresses": [],
                "ipv6_policy": "",
                "keep_browser_cache": false,
                "last_seen": "",
                "location": "",
                "network": "",
                "prefix": "",
                "target_for_tests": "",
                "utilization": 0,
                "verify_ssl_certificate": false
              }
            ],
            "alert_rules": [],
            "alerts_enabled": false,
            "api_links": [
              {
                "href": "https://api.thousandeyes.com/v6/tests/3303011",
                "rel": "self"
              },
              {
                "href": "https://api.thousandeyes.com/v6/web/http-server/3303011",
                "rel": "data"
              },
              {
                "href": "https://api.thousandeyes.com/v6/net/metrics/3303011",
                "rel": "data"
              },
              {
                "href": "https://api.thousandeyes.com/v6/net/path-vis/3303011",
                "rel": "data"
              }
            ],
            "auth_type": "NONE",
            "bandwidth_measurements": false,
            "bgp_measurements": false,
            "bgp_monitors": [],
            "client_certificate": null,
            "content_regex": ".*",
            "created_by": "Kotaro Enbutsu (kenbutsu@cisco.com)",
            "created_date": "2022-11-30 13:53:00",
            "custom_headers": null,
            "description": null,
            "desired_status_code": null,
            "dns_override": null,
            "download_limit": null,
            "enabled": true,
            "follow_redirects": true,
            "groups": [],
            "headers": null,
            "http_target_time": 1000,
            "http_time_limit": 5,
            "http_version": 2,
            "id": "3303011",
            "interval": 120,
            "live_share": false,
            "modified_by": null,
            "modified_date": null,
            "mtu_measurements": true,
            "network_measurements": true,
            "num_path_traces": 3,
            "password": null,
            "path_trace_mode": "classic",
            "post_body": null,
            "probe_mode": "AUTO",
            "protocol": "TCP",
            "saved_event": false,
            "shared_with_accounts": [],
            "ssl_version": "Auto",
            "ssl_version_id": 0,
            "test_id": 3303011,
            "test_name": "Example HTTP test set from Terraform provider",
            "type": "http-server",
            "url": "https://www.thousandeyes.com",
            "use_ntlm": false,
            "user_agent": null,
            "username": null,
            "verify_certificate": true
          },
          "sensitive_attributes": [],
          "private": "bnVsbA==",
          "dependencies": [
            "data.thousandeyes_agent.agent1"
          ]
        }
      ]
    }
  ],
  "check_results": null
}
```

## 破棄する (terraform destroy)

設定が不要になったら破棄します。 `terraform destroy` を実行しますが、`apply` 時と同様に `yes` の対話的入力を求められます。 この入力を省略するには `-auto-approve` オプションを指定します。

```sh
$ terraform destroy -auto-approve
data.thousandeyes_agent.agent1: Reading...


data.thousandeyes_agent.agent1: Read complete after 2s [id=0x14000516810]
thousandeyes_http_server.http1: Refreshing state... [id=3303011]

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:

  # thousandeyes_http_server.http1 will be destroyed
  - resource "thousandeyes_http_server" "http1" {
      - alerts_enabled         = false -> null
      - api_links              = [
          - {
              - href = "https://api.thousandeyes.com/v6/tests/3303011"
              - rel  = "self"
            },
          - {
              - href = "https://api.thousandeyes.com/v6/web/http-server/3303011"
              - rel  = "data"
            },
          - {
              - href = "https://api.thousandeyes.com/v6/net/metrics/3303011"
              - rel  = "data"
            },
          - {
              - href = "https://api.thousandeyes.com/v6/net/path-vis/3303011"
              - rel  = "data"
            },
        ] -> null
      - auth_type              = "NONE" -> null
      - bandwidth_measurements = false -> null
      - bgp_measurements       = false -> null
      - content_regex          = ".*" -> null
      - created_by             = "Kotaro Enbutsu (kenbutsu@cisco.com)" -> null
      - created_date           = "2022-11-30 13:53:00" -> null
      - enabled                = true -> null
      - follow_redirects       = true -> null
      - http_target_time       = 1000 -> null
      - http_time_limit        = 5 -> null
      - http_version           = 2 -> null
      - id                     = "3303011" -> null
      - interval               = 120 -> null
      - live_share             = false -> null
      - mtu_measurements       = true -> null
      - network_measurements   = true -> null
      - num_path_traces        = 3 -> null
      - path_trace_mode        = "classic" -> null
      - probe_mode             = "AUTO" -> null
      - protocol               = "TCP" -> null
      - saved_event            = false -> null
      - ssl_version            = "Auto" -> null
      - ssl_version_id         = 0 -> null
      - test_id                = 3303011 -> null
      - test_name              = "Example HTTP test set from Terraform provider" -> null
      - type                   = "http-server" -> null
      - url                    = "https://www.thousandeyes.com" -> null
      - use_ntlm               = false -> null
      - verify_certificate     = true -> null

      - agents {
          - agent_id               = 4 -> null
          - enabled                = false -> null
          - ip_addresses           = [] -> null
          - keep_browser_cache     = false -> null
          - utilization            = 0 -> null
          - verify_ssl_certificate = false -> null
        }
    }

Plan: 0 to add, 0 to change, 1 to destroy.
thousandeyes_http_server.http1: Destroying... [id=3303011]
thousandeyes_http_server.http1: Destruction complete after 1s

Destroy complete! Resources: 1 destroyed.
```

これで変更した内容は破棄されました。

## tfstate ファイルの内容確認 (destroy 後)

この状態で `terraform.tfstate` を確認すると以下のように表示されました。 `destroy` を実行したことにより、変更が破棄されたことが `.tfstate` の内容からも分かります。

```json
{
  "version": 4,
  "terraform_version": "1.3.5",
  "serial": 4,
  "lineage": "f76dbffa-938c-04ce-374b-08683ddc9868",
  "outputs": {},
  "resources": [],
  "check_results": null
}
```

## まとめ

これで Terraform を使って ThousandEyes を構成管理出来るようになりました。 今後の発展例としては「Terraform のループ構文を用いて、よく似た監視設定を大量設定する」など、アイデア次第で様々な応用が考えられます。

この記事がお読みいただいた方の ThousandEyes & Terraform ライフの一助になれば幸いです！