InstanaとNS1を使ったGSLBの構成検討

# 1. はじめに
こんにちは。[Instana Observability Advent Calendar 2023][1-0]の8日目の投稿です。

[1-0]:https://qiita.com/advent-calendar/2023/instana

本稿ではInstanaとNS1 Managed DNSの組み合わせについて検討した内容をまとめます。
Instanaについてはある程度ご存知の方が読者だと想定しており、基本的なところは割愛します。

NS1はいわゆる権威DNSサービスで100%のアップタイム保証に加えて、高機能なトラフィック・ステアリングの機能を提供しています。また、外部のモニタリングツールからの情報を活用することも可能であり、今回はInstanaとの連携を検討していきます。

検討としているのは、今回初めて触ったものが多く、まだかなり手探りだということですね。

## 1-1. GSLBとは？
GSLB（Global Server Load Balancing）は「広域負荷分散」と呼ばれ、複数のデータセンターやクラウドにまたがるロードバランシング、サイトフェイルオーバー、Webトラフィック管理を提供する機能で、DNSを使った実装が主流のようです。

通常のロードバランサとの違いは、ロードバランサは傘下のネットワークに対する負荷分散を提供するのに対し、DNSを使ったGSLBではDNS応答によりターゲットを切り替えるので、災害対策や地理的なトラフィック分散(高可用性・パフォーマンス向上)のために使用することができます。

## 1-2. Instana+NS1を活用したGSLBのイメージ

今回検証した構成は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/63be4840-a8d2-9ad4-b8f0-c42af804fc84.png)


- 2つのサーバー上で同じアプリケーションを稼働させます。(qotd: [Quote of the Day][1-2])
  - それぞれ別のロケーションに立てます。
  - それぞれInstana Agentを導入し、アプリを監視します。
- NS1で名前解決をできるようにします。
  - 1つのレコードに2つのサーバーのIPアドレスを紐付けます。
  - Instanaから2つのサーバーアプリの状態を受信するためのデータ・フィードを構成します。
  - 各IPアドレスとデータ・フィードを紐付けて、サーバーアプリの状態とDNS回答が連動するようにします。

[1-2]:https://gitlab.com/quote-of-the-day/quote-of-the-day

その結果、例えば東京リージョンのアプリで障害が発生した際に、東京リージョンのIPアドレスをDNSが応答で返さなくなることでユーザーは意識することなくアプリに継続的にアクセスすることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/e6eae46b-2e66-4896-19e2-79bb31f98b01.png)


## 1-3. Instanaによる監視のメリットと今回の検証の目標

簡単な死活監視ではNS1単体でもできてしまうため、ここではInstanaと連携するメリットを考えていきます。

通常、ロードバランサーやGSLBでは傘下の死活情報を認識して振り分ける場合、
- 単純なICMPベース
- アプリのヘルスチェック用エンドポイントの応答（ステータスコード、レスポンスタイム等）

が一般的だと考えられます。

逆に言えば、上記が成功している場合は、アプリに問題が発生していても機能しないことになるので、それをInstanaでカバー出来ないかと考えました。

具体的にはInstanaによるアプリケーション定義（アプリケーション・パースペクティブ）でアプリケーションの**内部呼び出しも含むエラー率**が上昇した際に、アプリケーションの状態を変更し、系の切り離しができることを目標に検証を行います。


## 1-4. 環境情報

本手順は以下の環境で実施しています。

[Instana Backend]
- Instana Backend: Self-Hosted on docker (Single) Build 263-1
- Platform: IBM Cloud VSI

[Instana Agent - Server1]
- Platform: AWS EC2 東京リージョン
- OS: CentOS 7.9
- Application: qotd v5.1.0 (docker)

[Instana Agent - Server2]
- Platform: AWS EC2 オハイオリージョン
- OS: CentOS 7.9
- Application: qotd v5.1.0 (docker)


# 2. NS1のセットアップ
まずはNS1側のセットアップについてです。
NS1のアカウントが必要となりますが、こちら誰でも[無料の開発者アカウント][2-0]が作成できます。
ただし、管理するドメインは別途取得が必要です。

[2-0]:https://www.ibm.com/jp-ja/products/ns1-connect

以下は、完了している前提とします。
- NS1アカウントが取得済みであること
- 管理対象ドメインが取得済みであること
- 管理対象ドメインのレジストラにて、NS1のネームサーバーに変更済みであること
- NS1のアカウントにてAPIキーが発行済みであること


## 2-1. データ・ソース、データ・フィードの作成
データ・ソースは外部からデータを取り込むためのAPIエンドポイントを提供します。
データ・フィードはデータ・ソース内のデータを取り込む単位になります。

NS1アカウントにログインし、
[Integration]>[Data Sources]から[+]ボタンで新規データ・ソースを作成します。
[Name]には管理用の名称を入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/d91274ec-1472-51d3-8a3a-76575b71cdd8.png)

データ・ソースを定義すると、そのままデータ・フィードの定義画面へ進みます。
[Name]には管理用の名称を入力します。
[Label]は、正直わかりにくくて悩みましたが、データを識別する情報です。今回はアプリケーションを識別したいため、`qotd-jp`と`qotd-us`の2つのデータ・フィードを定義します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/dbad518b-a00d-828b-42c2-f55a6fde9f80.png)

以下の様に、`Instana-qotd`データ・ソース及びデータ・フィードを作成しました。
Source IDがAPIエンドポイントに含まれる形になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/483b0bc0-27af-ec5b-f7e4-32787a8e06f7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/0e2b94ef-8c80-4de8-8f9d-8876d15f0b51.png)

Data Feed APIにデータをインプットする例は以下の様になります。
ちなみにPOSTのレスポンスは空でした。

```bash
#!/bin/bash

LABEL_NAME="データ・フィードのLabel"
NS1_API_URL=https://api.nsone.net/v1/feed/"データ・ソースID"
NS1_API_KEY="APIキー"

#prepare the Post Data
JSON_DATA=$(cat << EOS
{
  "${LABEL_NAME}": {
    "up": "1"
  }
}
EOS
)

RESPONSE=$(curl -fsSL -X POST -H "Content-Type: application/json" -H "X-NSONE-Key: ${NS1_API_KEY}" -d "${JSON_DATA}" ${NS1_API_URL})
echo ${RESPONSE} | jq
```

Data Feed APIからデータ・フィードの値を確認する例は以下の様になります。
```bash
#!/bin/bash

NS1_API_URL=https://api.nsone.net/v1/feed/"データ・ソースID"
NS1_API_KEY="APIキー"

curl -X GET -H "X-NSONE-Key: ${NS1_API_KEY}" ${NS1_API_URL}
```
応答は以下になります。
```bash
[
  {
    "name": "Instana qotd-jp",
    "config": {
      "label": "qotd-jp"
    },
    "id": "0c59a483f49e616e744aca32",
    "networks": [],
    "data": {
      "up": true
    },
    "destinations": [
      {
        "record": "657484af11a76f000150d76a",
        "desttype": "record",
        "destid": "657484af11a76f000150d76a"
      }
    ]
  },
  {
    "name": "Instana qotd-us",
    "config": {
      "label": "qotd-us"
    },
    "id": "e99664d6ad9a1b15664892dd",
    "networks": [],
    "data": {
      "up": true
    },
    "destinations": [
      {
        "record": "657484af11a76f000150d76a",
        "desttype": "record",
        "destid": "657484af11a76f000150d76a"
      }
    ]
  }
]
```

## 2-2. DNSレコードの設定
NS1アカウントにログインし、
[DNS]から該当のゾーンを選択します。

[Records]タブの画面下部の[+Add record]からレコードを作成します。
今回はユーザーは`qotd.sdn-dev.tokyo`でアプリにアクセスし、アプリが稼働しているサーバーのIPアドレスをレコードのAnswersに記載します。
TTLについては、デフォルト3600秒(1時間)となっていますが、迅速にアプリを切り替えるために60秒(1分)に設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/b61d8601-ae42-b06b-2e07-bf06afd3eeea.png)


## 2.3 フィルター・チェーンの設定
フィルター・チェーンはまさにGSLBの機能を提供するNS1のトラフィック・ステアリングの機能です。
レコードに対して紐付けられたIPアドレス群を複数のフィルターを適用し、最終的にDNSリクエストに対する回答を動的に絞り込んで回答するための機能です。
1つ目のフィルターを適用した結果を(Linuxのパイプ機能のように)2つ目のフィルターに渡し、順に適用していくイメージです。

作成したレコードを選択し、左ペインの[Filter Chain]から[Edit Filter Chain]を選択します。
Filter Chainの画面にて、`Up`, `Priority`, `Select First N`の順に選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/f0f3796a-17b3-ec23-fa5d-dc512eaeba86.png)

各フィルターの設定方法です。
左のリストから`Up`を選択すると、各Answer毎に`up: unset`の設定が出てくるのでこれをクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/4829fd47-3129-a47b-db91-82686e765ce7.png)

Feedの列のボタンを押すと、利用可能なデータ・フィードの一覧が出てくるため、関連付けるデータ・フィードを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/69521052-433e-c257-ac9a-40bef6cf3cb7.png)

各Answerにデータ・フィードを関連づけると以下の様になります。
現在Upがtrueのものは緑、Upがfalseのものは赤で表示されます。
（現状、この設定画面上のupの値はData Feedを更新してもすぐには反映されない様です。DNSの回答にはすぐに反映されることは確認しており、本質的な機能には問題なさそうでした。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/da43e750-5551-9cdd-71a2-22a92ab3c1f5.png)


次に、`Priority`フィルタについても同様に設定していきます。
左のリストから`Priority`を選択すると、各Answer毎に`priority: unset`の設定が出てくるのでこれをクリック。
priority値は低いほど優先順位が高くなります。今回は東京を`priority:1`、USを`priority:2`に設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/996d4910-befc-ddd4-3201-f60703bc8a30.png)

最後の`Select First N`フィルタはデフォルト`1`に設定されており、
回答候補の中から1つだけ応答を返すために使用しています。

以上により、NS1側の設定は完了でデータ・フィードの値によって動的にDNS回答を変更させることができます。

# 3. Instanaのセットアップ
次にInstana側のセットアップについてです。

以下は、完了している前提とします。
- Instana 監視対象サーバーのセットアップ
- アプリケーション(qotd,qotd-load)のインストール
- Instanaバックエンドが構築済みであること

## 3.1 Instana Agentの設定
２台のサーバーそれぞれにInstana Agentをワンライナーで導入し、インフラストラクチャの画面で認識されていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/636ea6a8-0bcc-966a-1ac9-fdfbdbc2bd73.png)

自動化アクションを使いたいので、アクション・スクリプト機能を有効化しておきます。
```bash
cat <<EOF > configuration_action_script.yaml
com.instana.plugin.action.script:
  enabled: true
  runAs: 'centos'
  scriptExecutionHome: '/tmp'
EOF
```

## 3.2 アプリケーション・パースペクティブの設定
２台のサーバーそれぞれで稼働するqotdアプリをそれぞれ`qotd-jp`、`qotd-us`というアプリケーション・パースペクティブを作成します。

- アプリの内部エラーをカウントするため、すべての呼び出しを選択します
- アプリケーション・パースペクティブ名とデータ・フィードのLabelを一致させます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/34beac78-305b-7738-c9fb-e3ce057f2a73.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/8a1e4f03-6b3e-0e5e-cd57-91c59b7ceb35.png)


## 3.3 スマートアラートの設定
2つのアプリケーション・パースペクティブで異常を検知するためのスマート・アラートを作成します。
ステップ１にて**エラーのある呼び出し数**を選択してから、拡張モードに切り替えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/59f96d33-46d0-acdc-03d1-04e055d57cdb.png)

しきい値はデフォルトで、エラー率 >= 1% となっています。（すでにエラー率が高い場合は、直近のエラー率よりも少し大きい値が選択される模様）
動的しきい値なども検討すべきですが、今回はこのままとしました。


早く異常検知する為に、評価の粒度を`5 min`に設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/5ec68a9b-393b-6b98-000b-a63c6668231f.png)

インシデントのトリガーを`ON`に設定します。
追加のプロパティーにて、タイトルの頭にアプリケーション・パースペクティブが分かるような識別子を入れておきます。
これは後述するアクションの設定画面で区別つくようにするためのオマケです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/91233dfb-c198-fc7b-6d82-47a71f816de6.png)


## 3.4 アクションの設定
最後にNS1と連携するためのアクションを作成します。

HTTPアクションの方で、JSONデータの階層構造をどう持たせるかが分からずだったので、
スクリプトタイプで実装していきます。

アプリケーション・パースペクティブで異常が発生した際に
イベントに関連づけられたアクションによって当該アプリケーションの状態をダウンに設定するため、
アプリケーション名をパラメータとして渡す必要があります。
`@@label_name@@`、`@@ns1_api_url@@`、`@@ns1_api_key@@`のように記載しておけば、アクション内で定義したパラメータを渡すことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/a54f4f86-01a0-279e-4340-d9d010604f1c.png)

パラメータの追加によって、`label_name`には動的にアプリケーション・パースペクティブの名前が入るように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/baa04e97-695e-28ac-97ff-c338d82a31a0.png)

`ns1_api_url`と`ns1_api_key`は静的に値を定義します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/420e445f-2687-4f32-3458-7d4dcf811489.png)

最後にスマート・アラートを関連付けます。
（ここで関連付ける時に、スマート・アラートの名前が一緒だと区別がつかないのが不便でした。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/f6188080-3a44-86b3-c9c4-265fa89af14f.png)

# 4. 検証および検証結果
## 4.1 検証内容
2.3 フィルター・チェーンの設定によって、正常時のDNS応答は東京のサーバーのIPになります。
東京のサーバーのアプリに異常があった際に、ステータスを更新することで、DNS応答がUSのサーバーのIPに切り替わることを確認します。

## 4.2 切り替え前の確認
DNS応答は東京のサーバーになっています。

```bash
 % nslookup qotd.sdn-dev.tokyo     

Non-authoritative answer:
Name:	qotd.sdn-dev.tokyo
Address: 18.177.140.xx
```

HTTPアクセスも可能でした。
```bash
 % curl -I http://qotd.sdn-dev.tokyo:3000
HTTP/1.1 200 OK
```

## 4.2 障害発生から切り替えまで
qotdのアノマリージェネレータを使用して、東京のアプリケーションにて異常を発生させます。

少し時間が経つとインシデントが発生します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/42106e9e-e15a-ed5c-9d28-31d39675f72e.png)

現状の仕様なのか、インシデントの画面にはアクションの関連付けが表示されないため、同じく発生している問題(Issue)を開きます。問題の下方に関連付けられたアクションとして`NS1 Data Feed`がありますので、**実行**をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/c0efffd6-0633-34de-292c-787f3651b9c9.png)

ターゲット・エージェントをリストから選択します。１台しか選べなかったので、関連するホストに絞り込まれていると思われます。また、動的パラメータに設定したLabelに相当する値が正しいことを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/fcd85a45-a783-85f3-0bc8-dedc5ed92e9e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319123/dc0cbf14-ddf3-0710-36ff-d904b1351129.png)

## 4.2 切り替え後の確認
DNS応答はUSのサーバーになっています。
切り替え時に1秒毎のDNS応答結果をウォッチしていましたが、大体1分以内には切り替えが完了していました。
TTL=60秒なので、期待通りの動作となりました。

```bash
 % nslookup qotd.sdn-dev.tokyo     

Non-authoritative answer:
Name:	qotd.sdn-dev.tokyo
Address: 3.15.13.xx
```

HTTPアクセスも可能でした。
```bash
 % curl -I http://qotd.sdn-dev.tokyo:3000
HTTP/1.1 200 OK
```

ちなみにデータ・フィードの値は以下のようになっていました。
```bash
[
  {
    "name": "Instana qotd-jp",
    "config": {
      "label": "qotd-jp"
    },
    "id": "0c59a483f49e616e744aca32",
    "networks": [],
    "data": {
      "up": false
    },
    "destinations": [
      {
        "record": "657484af11a76f000150d76a",
        "desttype": "record",
        "destid": "657484af11a76f000150d76a"
      }
    ]
  },
  {
    "name": "Instana qotd-us",
    "config": {
      "label": "qotd-us"
    },
    "id": "e99664d6ad9a1b15664892dd",
    "networks": [],
    "data": {
      "up": true
    },
    "destinations": [
      {
        "record": "657484af11a76f000150d76a",
        "desttype": "record",
        "destid": "657484af11a76f000150d76a"
      }
    ]
  }
]
```

# 5. おわりに
本稿ではInstanaとNS1 Managed DNSの組み合わせによるGSLBの実装について設定例を含めてまとめました。
Instanaのアクションが完全自動ではないので、現状では手動での切り離しにはなってしまいますが、任意のアプリケーション監視の結果に基づいて、ひとまず系を切り離すというオペレーションは実現できそうです。

少しでも参考になれば幸いです。