追記
より汎用性が高い方法はこちらです(version 2):
背景
サービスに登録されている各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 610;
} else if (req.http.x-api-parameter == "${backend_name_2}"){
set req.backend = ${backend_name_2};
error 610;
}
# 無効なパラメータには 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 == 610) {
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