Varnish
HTTP
cdn
VCL
fastly

[Fastly] 登録されている各backendのhealth状態(req.backend.healthy)を監視する方法

背景

サービスに登録されている各backend(origin)がダウンした場合、Fastlyでは設定に応じて様々な切り替えが可能です。

例)
- backendが503を返した場合、カスタムレスポンス を使ってFastlyからエラーページを返す
- backendに health check設定を追加 して、originがダウンした場合に副系のbackendに切り替える
- stale-if-error を使ってキャッシュサーバに残っているコンテンツを返す

backendにhealth check設定が追加されている場合、req.backend.healthy をVCLの中で使うことができるようになり、この変数はbackendがhealthyな場合は 1, unhealthyな場合は0を返します。
しかし現在のところ、各backendのhealth状態を外部から取得できるようなAPIは用意されていません。今回はFastly上でVCLを書く事で、各originの req.backend.healthy の値をJSON形式で返す擬似APIを作り、外部からそれらを監視できるようにしてみたいと思います。

Step1. 各backendにhealth check設定を追加する

req.backend.healthy を使うにはそのbackendにhealth checkが設定されていることが前提条件になります。
詳細な方法についてはここでは割愛しますが、下記のドキュメントもしくはQiita記事が参考になります。

Step2. カスタムVCL or VCLスニペット機能を使って vcl_recv サブルーチン内にコードを追加する

# 今回は例としてヘルスチェックが設定されたbackendが2つ定義されている前提
backend ${backend_name_1} { ... }
backend ${backend_name_2} { ... }


sub vcl_recv {
  if (req.url ~ "/api/status") {
    # URLのクエリパラメータを subfield関数を使って簡易分解し、
    # "/api/status?backend=${backend_name}" の ${backend_name} 部分を切り出す。
    # 参考: https://community.fastly.com/t/accessing-query-string-parameters/1089/3
    set req.http.x-api-parameter = subfield(req.url.qs, "backend", "&");

    # パラメータの値に応じて一時的に req.backend にそのbackendを代入し、
        # JSONのカスタムレスポンスを返すためvcl_error へ移動
    if (req.http.x-api-parameter == "${backend_name_1}") {
      set req.backend = ${backend_name_1};
      error 800;
    } else if (req.http.x-api-parameter == "${backend_name_2}"){
      set req.backend = ${backend_name_2};
      error 800;
    }

    # 無効なパラメータには 403 を返す
    error 403 "Forbidden";
  }


 ...
}

上記の例は /api/status?backend=${backend_name} をURLとして想定していますが、当然このURLがサービスの本当のAPIと競合することもあるかと思います。その場合は必要に応じてサービスと競合しないURLに調整してください。

また、上記の方法では例えばbackendが50個あれば50個分の条件分岐が必要になり冗長です。
その場合、Fastlyの Edge Dictionaries を使って各backendのリストを持つtableを作成し、この辞書に対して table.lookup を行うことで冗長な条件分岐を避けることが出来ます。

URLにアクセス可能なSource IPを制限したい場合は、Fastlyの ACL機能 を使って、if条件にANDでACLチェックを加えることで実現可能です。
if (req.url ~ "/api/status" && client.ip ~ acl_name) { ... }

Step3. カスタムVCL or VCLスニペット機能を使って vcl_error サブルーチン内にコードを追加する

FastlyのカスタムレスポンスはWebコンソールからGUIで作成することもできますが、下記のように synthetic を定義することでも作成可能です。
vcl_error 内で処理を実行していますが、今回はJSONレスポンスを返す事が目的なので 200 OK を返すように設定します。

if (obj.status == 800) {
  synthetic "{" LF
      {"  "timestamp": ""} now {"","} LF
      {"  "req.backend": ""} req.backend {"","} LF
      {"  "req.backend.healthy": "} req.backend.healthy LF
      "}"
  ;
  set obj.status = 200;
  set obj.response = "OK";
  set obj.http.Content-Type = "application/json";
  return (deliver);
}

Step4. 実際にリクエストを投げてみる

Step1 ~ 3が完了したら準備完了です。実際にリクエストを投げて期待するJSONレスポンスが受け取れるか確認してみます。

$ curl http(s)://${hostname}/api/status?backend=${backend_name}

# 下記のようなJSONレスポンスが期待されます (req.backend.healthy 1:healthy, 0:unhealthy)
{
  "timestamp": "Thu, 23 Nov 2017 10:47:06 GMT",
  "req.backend": "${service_id}--${backend_name}",
  "req.backend.healthy": 1
}

数値型で 1 or 0 が返されると思います。これを任意のツールから呼び出すことで外部からの状態監視が実現できます。

その他

  • 今回は vcl_recv -> vcl_error というフローでレスポンスを返しているので、レスポンスは一切キャッシュされません。ページをリロードする毎に timestamp が更新されていることが分かると思います。
  • トラブル時の調査用データとしても、ProductionサービスではFastlyの Real-Time Log Streaming を設定しておくことを推奨します。任意の変数の値をログフォーマットに加えて出力が可能です。(今回使った req.backend.healthy や任意のヘッダの値など)
  • req.backend.healthy の値が更新されるタイミングは、ヘルスチェックの frequency(頻度) 設定に依存します。frequency の設定値によってbackendがunhealthyとみなされるまでにかかる時間が変わります。今回作成した疑似APIにアクセスした際に毎回backendへヘルスチェックリクエストを投げているわけではないのでご注意ください。
    Health check frequency