CloudWatch Synthetics で POST してレスポンスの内容を判定する外形監視をしてみた

2021.12.26

CloudWatch Synthetics で外形監視を実現したいです。定期的なPOSTメソッドのリクエストを実行し、ステータスコード200 OKでかつレスポンスボディの内容を判定して意図した内容ものかチェックできるか試してみます。

モニタリング対象のAPIは東京リージョンにある API Gateway + Lambda の構成です。

CloudWatch Synthetics の実態は Lambda ですので同じリージョンの Lambda 周りの障害でサービスも外形監視が止まったら元も子もないので、大阪リージョンから CloudWatch Syntheticsを実行してみます。

大阪リージョンは2021年5月に対応していました。

参考までに New Relic Synthetics でも同様のことを試してみました。

やってみた

カナリーを作成します。

東京リージョンの API Gateway を対象しているためここからは選択するこができない。

API Gateway API にはチェックは入れずに進めます。とくに不都合はないです。

HTTP リクエストを追加します。ステップはPOSTしてレスポンスを判定する処理1個だけ作成する予定です。

ステップは何かしてから何かするみたいな複数のステップを踏むAPIへの柔軟なモニタリングや、複数のHTTPリクエストを各ステップに分けることにより単一のスクリプトですべてのモニタリングを可能にします。syn-nodejs-2.2 以上で利用可能な機能です。

Web UI から入力した内容に基づいてサンプルスクリプトを生成してくれます。

スクリプトは後で修正しますので最低限入力して先に進めます。

登録しました。

ランタイムバージョンを指定します。スクリプトを編集できますが先にカナリー作成だけ終わらせます。

実行間隔を指定します。サンプルスクリプトのままなので作成後すぐに開始のチェックを外しました。

保存するとカナリーの作成処理に入ります。

カナリー作成完了しました。

モニタリングスクリプトの改修

監視対象としているAPIは、リクエストボディで渡す日本語のメッセージを翻訳し、レスポンスボディで返してくれる翻訳APIです。所定の日本語メッセージ「大阪からのチェックです!」をカナリーで定期的に送信します。結果、レスポンスボディが「It's a check from Osaka!」であるかチェックします。あと、ステータスコードが200番台であるかも条件とします。

It's a check from Osaka!の英文については事前に翻訳APIへ投げて、翻訳に利用している AWS Translate の翻訳結果を確認しました。

確認対象のURLへAPOSTメソッドのリクエストを送り、レスポンスのステータスコードが200番台であれば正常と判定するデフォルトのスクリプトを手直ししました。

  • レスポンスボディ内の文字列が所定の文字列かの判定追加
    • リクエストボデイ内の日本語をレスポンスボディでは英訳されることを期待
  • 確認対象のURLなどのパラメータ指定
  • CloudWatch Synthetics のマネージメントコンソール画面にヘッダー、ボディの情報を表示させるか指定
    • リクエストボディと、レスポンスボディを表示させるよういtrueに変更
    • 秘匿情報が含まれている場合はfalseが好ましいです
  • Verify POST translate-apiの箇所がHTTPリクエストのステップ名に該当します

CloudWatch Synthetics のマネージメントコンソール画面の表示例

ステップ名の表示例

var synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const syntheticsConfiguration = synthetics.getConfiguration();


const apiCanaryBlueprint = async function () {

    syntheticsConfiguration.setConfig({
        restrictedHeaders: [], // Value of these headers will be redacted from logs and reports
        restrictedUrlParameters: [] // Values of these url parameters will be redacted from logs and reports
    });

    // Handle validation for positive scenario
    const validateSuccessful = async function(res) {
        return new Promise((resolve, reject) => {
            if (res.statusCode < 200 || res.statusCode > 299) {
                throw res.statusCode + ' ' + res.statusMessage;
            }

            let responseBody = '';
            res.on('data', (d) => {
                responseBody += d;
            });

            res.on('end', () => {
                // Add validation on 'responseBody' here if required.
                const body = JSON.parse(responseBody);
                const translateText = body.output_text;
                const expectText = 'It\'s a check from Osaka!';
                console.log(translateText);

                if (translateText != expectText){
                    reject('expected ' + expectText + ' not to be ' + translateText);
                }

                resolve();
            });
        });
    };


    // Set request option for Verify POST translate-api
    let requestOptionsStep1 = {
        hostname: 'api.example.com',
        method: 'POST',
        path: '/v1/translate',
        port: '443',
        protocol: 'https:',
        body: '大阪からのチェックです!',
        headers: {}
    };
    requestOptionsStep1['headers']['User-Agent'] = [synthetics.getCanaryUserAgentString(), requestOptionsStep1['headers']['User-Agent']].join(' ');

    // Set step config option for Translateチェック
   let stepConfig1 = {
        includeRequestHeaders: false,
        includeResponseHeaders: false,
        includeRequestBody: true,
        includeResponseBody: true,
        continueOnHttpStepFailure: true
    };

    await synthetics.executeHttpStep('Verify POST translate-api', requestOptionsStep1, validateSuccessful, stepConfig1);


};

exports.handler = async () => {
    return await apiCanaryBlueprint();
};

5分間隔で実行するので判定に成功したか一度見届けました。放置して多少データ取った様子を紹介します。

観測

定期実行され青い点が連続して表示されており成功しています。

可用性タブを開いています。スクリプト作成時、動作化確認で試行錯誤していたのでスクリプト実行に失敗して問題が多発していた形跡が見られます。でも、最新の実行や直近は成功しているのでOKです。

可用性タブ内のモニタリングタブの様子です。多少データが溜まったので予測されるようになりました。

ステップ毎に期間(Duartion)を個別に確認できます。

HTTPステップタブを開いています。ステップ全体の実行時間と、リクエスト / レスポンスヘッダー、DNS ルックアップや TCP コネクションの時間を確認できます。リクエスト本文と、レスポンス本文内容を確認でき、日本語が英語に訳されていることが確認できます。

失敗させてみる

日本語から翻訳する言語を英語から別言語に変更しレスポンス内容を見て判定しているか確認します。

翻訳Lambda の設定を変更しAWS Translateでドイツ語翻訳設定にしました。期待通り失敗しています。

ステータスは200 OKですが、レスポンス本文がドイツ語になっています。レスポンス本文はIt's a check from Osaka!であるか否かの判定をしているので期待通りです。正しく機能しています。

翻訳言語設定を戻しました。直近のチェックタイミングで成功に戻りました。モニタリングのスクリプト次第でいろんな外形監視できそうですね。

おわりに

設定周りをあれこれ書いていったら本題にしたかったモニタリングスクリプトをどう書くんだ?という部分が薄れてしまった。マネージメントコンソールからスクリプト編集はやりづらく解決策は以下なのかなと思いながら作業していました。まず何ができるのか確認する分にはマネージメントコンソールから充分でした。

参考