Application InsightsとAzure Container InstancesとSeleniumでWebシナリオ監視

こんにちは、サイオステクノロジー武井です。今回は、Azure MonitorとAzure Container InstancesとSeleniumでWebシナリオ監視をやってみたいと思います。

Webシナリオ監視とは?

一般的に監視というと、PingやTCPポート監視、プロセス監視、CPUやネットワーク送受信量などのメトリックを利用した監視などがありますが、Webシナリオ監視(一般的な名称かどうかは不明ですが)というのもあります。これは、ユーザーがブラウザで行う一連の操作を自動化し、さらにその結果を監視対象とするものです。

例えば、ログイン画面を表示し、ID・パスワードを入力して、画面右上にユーザー名が表示されるWebアプリケーションがあった場合、Webシナリオ監視では以下の一連の操作を自動化して、監視します。

  1. ログイン画面の表示
  2. ユーザーIDとパスワードを入力
  3. 画面右上に表示されたユーザー名をチェック
  4. 結果を通知

この監視のいいところは実際のユーザーの操作を具現化しているおかげで、Ping監視やTCPポート監視ではわからない問題は発見できることです。

例えばWebアプリケーションの場合TCPポートは生きているのにプロセスが中途半端な形でハングって、アプリケーション自体が応答しないということがあります。これはTCPポート監視やPing監視だけだと問題が発見出来ません。でもWebシナリオ監視であれば、このような状態のときには実際にログインが出来ず、Webシナリオ監視が失敗するので、問題を発見することができます。

Webシナリオ監視の方法

Webシナリオ監視には、大きく分けて2つの方法があります。

HTTPリクエスト

HTTPリクエストを生成して、Webアプリケーションに送ることで、監視に必要な一連のシナリオを実現する方法です。例えばログイン処理の場合は、MethodはPost、Content-Typeはapplication/x-www-form-urlencoded、リクエストボディにIDとパスワードを埋め込んでWebアプリケーションに送付します。

JMeterやGatlingといったWebアプリケーション負荷試験ツールで採用されている方法で、ZabbixのWebシナリオ監視でも同様の方法が採用されています。

非常に高速で動作するのがメリットですが(負荷試験にも使われているくらいなので)、複雑なシナリオを再現するためにはひと手間必要になります。

例えば、昨今のWebアプリケーションには、CSRF(クロスサイトリクエストフォージェリ)対策として、ログイン画面に一意のトークンが埋め込まれているケースが多いです。HTTPリクエストによる方法では、ログイン画面のHTMLソースからトークンを抽出して、ユーザーID・パスワードとともにそのトークンを送付するという複雑な処理を行う必要があります。

さらにログイン処理において、OpenID ConnectやSAML等といったシングルサインオンを行っている場合、そのシーケンスはとても複雑なものになるので、それを再現するHTTPリクエストを生成するのもまたかなりの手間を必要とします。

ヘッドレスブラウザ

ヘッドレスブラウザとは、端的に言いますと「GUIを持たないブラウザ」です。GUIを持たないブラウザを何に使うのかというと、最も多いユースケースは「テストの自動化」になります。GUIを持たないだけで、きちんとHTMLをレンダリングしてくれますし、サーバーサイドから発行されたCookieも保存してくれますし、もちろんlinkやbuttonをクリックすると言った動作も再現することが可能です。

HTTPリクエストの場合と比べますと、なんと言ってもそのメリットは「シナリオ生成の簡単さ」に上げられます。

HTTPリクエストの例のときに紹介したCSRFトークン送付をヘッドレスブラウザでは簡単に実現出来ます。ヘッドレスブラウザではHTTPリクエストと違い、「ブラウザの操作を再現する」ので、シナリオ生成の際に行うことは以下だけです。

  1. ログイン画面にアクセスしてHTMLを取得する。
  2. HTML内にあるユーザーIDとパスワードのテキスト要素にそれぞれユーザーIDとパスワードを設定する。
  3. submitボタンをクリックする。

そんなヘッドレスブラウザにも難点はありまして、それは起動のオーバーヘッドがあるということです。ブラウザを起動してHTMLをレンダリングして、、、といった感じで実行していることはPC上で動作するGUIのブラウザと大差ないので当然といえば当然です。つまり大量のリクエストを投げつけるような負荷試験には向きません。

今回やること

Azureのサービスを使ってWebシナリオ監視をやるわけですが、実はAzure Monitorで「複数ステップWebテスト」というのが用意されています。これを使ってもいいのですが、以下のような制限があります。

  • Visual Studio 2017 Enterprise以降が必要
  • 複数ステップWebテストの提供がVisual Studio 2019で終わり
  • HTTPリクエスト形式なので複雑なシナリオを作るのがちょっと大変

ということで、今回はSeleniumを使ってみたいと思います。Seleniumを採用した理由は、OSSなので幅広く利用できること(Visual Studio不要)、ヘッドレスブラウザを利用した方式により複雑なシナリオに対応できることの2つです。

で、肝心のSeleniumを動かす環境ですが、Azure上で動かしているので、パブリッククラウドならではのマネージドなサービスを活用して、クラウドネイティブな感じに仕上げることとします。餅は餅屋に任せたほうが、自分のやりたいことに集中出来るので、そこがクラウドのよいところですよね。

ということで以下のような構成にしました。

 

監視対象Webアプリケーションを監視するSeleniumのシナリオ(Java)をコンテナ化して、AzureのマネージドなコンテナレジストリであるAzure Container Registryにpushします(①)。

そして、コンテナを実行するためのマネージドでサーバーレスなサービス「Azure Container Instances」で、先ほどのDockerイメージをpullしてます(②)。

実行方法はデーモンとしてJavaプロセスが稼働し、一定間隔でWebシナリオを実行します(③)。

監視結果をマネージドなアプリケーションパフォーマンス管理サービスであるApplication Insightsに出力します(④)。

失敗のログが一定期間内に一定数出力されたら運用者にアラートをあげます(⑤)。

 

というような感じですが、短い間隔で実施しようとすると、どうしてもヘッドレスブラウザの起動のオーバーヘッドがあってうまくいかないので、常駐型デーモンとしました。

実践してみましょうヮ(゚д゚)ォ!

では先の構成に従って実践してみましょう!!

Seleniumスクリプトの作成

まずは監視をするためのSeleniumスクリプトを作成します。このスクリプトの基本的な動きは以下のようなシナリオになっております。

  1. ログイン画面にアクセスする。
  2. ユーザーIDとパスワードを入力する。
  3. ログイン後の画面に表示されるユーザーIDをチェックする。
  4. 期待したユーザーIDでなかった場合には「Login Failed」とログに出力する。

実は既に作成済みでして、以下のスクリプトは以下のGitHubで公開しております。

https://github.com/noriyukitakei/selenium-weblogin-sample.git

こちらはあくまでサンプルですので、実際のユースケースに合わせて適宜書き換えて頂ければと思います。上記リポジトリの中のメイン(というかこれ一つだけですが)のスクリプトを抜粋致します。

package jp.noriyukitakei;

import java.net.MalformedURLException;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.bonigarcia.wdm.WebDriverManager;

public class App {
    public static void main(String[] args) throws MalformedURLException
    {
        Logger logger = LoggerFactory.getLogger(App.class);

        try {
            // インストールされているブラウザのバージョンに対応したWebDriverを
            // 自動的にダウンロードします。
            WebDriverManager.chromedriver().setup();

            // オプションを指定します。
            ChromeOptions options = new ChromeOptions();
            options.addArguments("headless");
            options.addArguments("--disable-dev-shm-usage");
            options.addArguments("--no-sandbox");

            while(true) {
                WebDriver driver = new ChromeDriver(options);

                try {
                    // ログイン画面にアクセスする。
                    driver.get("https://hoge.example.com/login.php");

                    // ユーザーIDのテキストボックスが表示されるまで10秒待つ。
                    WebDriverWait wait = new WebDriverWait(driver, 10);
                    WebElement element01 = wait.until(ExpectedConditions.
                                    presenceOfElementLocated(By.id("username")));

                    // ユーザー名をテキストボックスにセットする。
                    element01.sendKeys("test001");

                    // パスワードをテキストボックスにセットする。
                    // 本番ではAzure Key Vaultを利用する等セキュアな構成にして下さい。
                    WebElement element02 = driver.findElement(By.id("password"));
                    element02.sendKeys("password");

                    // ログインボタンをクリックする。
                    WebElement element03 = driver.findElement(By.id("login"));
                    element03.click();

                    // ログイン後の画面が表示されるまで5秒待つ。
                    // WebDriverWaitとExpectedConditionsでログイン後の画面にユーザーIDが表示されるまで
                    // 待とうかと思ったのだがうまくいかず、、、普通にSleepしてる。
                    TimeUnit.SECONDS.sleep(5);

                    // ログイン後の画面に表示されたユーザーIDが期待したものかどうかをチェックしています。
                    if (driver.findElement(By.tagName("body")).getText().equals("test001")) {
                        logger.info("Login Succeeded");
                    } else {
                        logger.error("Login Failed");
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage(),e);
                    logger.error("Login Failed");
                } finally {
                    // 念の為Cookieを全て消してからブラウザを終了しています。
                    driver.manage().deleteAllCookies();
                    driver.quit();

                    // 次の監視間隔まで30秒待っています。
                    TimeUnit.SECONDS.sleep(30);
                }
            }
        } catch(Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

Application Insightsの作成

Webアプリケーショのパフォーマンス監視サービスであるApplication Insightsのリソースを作成します。今回の用途としてはSeleniumスクリプトから出力するログをAzure Monitor logs(旧Log Analytics)に出力するだけですし、そもそもWebアプリケーションではないのですが、Application InsightsのSDKをJava起動時の-javaagentに指定すると、logbackで標準出力に出力したログをAzure Monitor Logsに記録してくれるので、今回Application Insightsを使います。

まずApplication Insightsのログを保存するために必要なデータストアである「Log Analyticsワークスペース」を作成します。

Azureポータルにアクセスして「リソースの作成」をクリックして、以下のように「log analytics workspace」と入力してエンターを押します。

 

「作成」をクリックします。

 

名前と地域を入力して下さい。最後に「確認および作成」をクリックします。

 

「作成」をクリックして、しばらくすると出来上がります。

 

Azureポータルにアクセスして「リソースの作成」をクリックして、以下のように「application insights」と入力してエンターを押します。

 

「作成」をクリックします。

 

「名前」については、Azure全体で一意に識別できるものをつけて下さい。「ワークスペースの詳細」は先ほど作成したLog Analyticsのワークスペースを選択してください。最後に「確認および作成」をクリックします。

 

内容に問題がなければ「作成」をクリックします。

 

先ほど作成したApplication Insightsのリソースにアクセスして、「接続文字列」をメモっておきます。

Azure Container Registryの作成

Seleniumスクリプトが実行するDockerイメージを格納するAzure Container Registryを作成します。Azureポータルにアクセスして「リソースの作成」をクリックして、以下のように「Container Registry」と入力してエンターを押します。

 

「作成」をクリックします。

 

リソースグループ名やサブスクリプション、レジストリ名を入力します。SKUは検証なので一番安い「BASIC」でいいと思います。最後に「確認および作成」をクリックします。

 

「作成」をクリックします。しばらくすると出来上がります。

 

Dockerイメージの作成

SeleniumスクリプトをDockerコンテナ化するためのDockerfileを作成します。その前に、先ほどのSeleniumスクリプトをビルドしましょう。

$ git clone https://github.com/noriyukitakei/selenium-weblogin-sample.git
$ cd selenium-weblogin-sample
$ mvn clean package

 

そして以下のDockerfileを作成して下さい。

FROM openjdk:11.0.9.1

# Chromeをインストールします。
RUN apt-get update && \
    apt-get install -y wget && \
    echo "deb https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list && \
    wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
    apt-get update && \
    apt-get install -y google-chrome-stable

RUN mkdir /opt/web-scenario-monitor

# Seleniumスクリプトをビルドしたjarをコンテナにコピーします。
ADD web-scenario-monitor.jar /opt/web-scenario-monitor

# Application Insightsのライブラリをコンテナにコピーします。
ADD applicationinsights-agent-3.0.0.jar /opt/web-scenario-monitor
ADD applicationinsights.json /opt/web-scenario-monitor

# Seleniumを起動します。
CMD ["java","-javaagent:/opt/web-scenario-monitor/applicationinsights-agent-3.0.0.jar","-jar","/opt/web-scenario-monitor/web-scenario-monitor.jar"]

 

docker buildする前に以下のことを実施して下さい。

  • Dockerfileのあるディレクトリと同じ場所に、先ほどビルドしたSeleniumスクリプトのjarファイルをweb-scenario-monitor.jarという名前で配置して下さい。
  • Dockerfileのあるディレクトリと同じ場所に、このURLからダウンロードできるApplication InsightsのSDK(2021年1月22日現在ではバージョン3.0.0でファイル名はapplicationinsights-agent-3.0.0.jar)を配置して下さい。
  • Dockerfileのあるディレクトリと同じ場所に、applicationinsights.jsonというファイルを作成して以下の内容を追記してください。
{
  "connectionString": "Application Insigths作成時にメモした接続文字列"
}

 

でゎ、Docker biuldしてAzure Container Registryにpushします。

$ docker build -t web-scenario-monitor .
$ docker tag web-scenario-monitor:latest [Azure Container Registryのホスト名]/web-scenario-monitor:1.0.0
$ docker push [Azure Container Registryのホスト名]/web-scenario-monitor:1.0.0

Azure Container Instancesの作成

ではいよいよ最後の仕上げであるAzure Container Instancesを作成します。まず以下のYAMLファイルを作成します。これはAzureContainer Instancesを作成するための定義ファイルです。

apiVersion: '2019-12-01'
# コンテナグループを作成するリージョンを指定します。
location: eastus
# このファイルで作成するコンテナが所属するコンテナグループを指定します。
name: web-scenario-monitor-aci
properties:
  containers:
  # コンテナ名を指定します。WordPressのコンテナです。
  - name: web-scenario-monitor
    properties:
      # コンテナのイメージ名を指定します。
      image: [Azure Container Registryのホスト名]/web-scenario-monitor:1.0.0
      resources:
        requests:
          # コンテナに割り当てるCPUとメモリを指定します。
          cpu: 1
          memoryInGb: 0.5
  imageRegistryCredentials:
  - server: [Azure Container Registryのホスト名]
    username: [Azure Container Registryに接続できる権限を持ったサービスプリンシパルのアプリケーションID]
    password: [Azure Container Registryに接続できる権限を持ったサービスプリンシパルのシークレット]
  # OSのタイプを指定します。
  osType: Linux
# 以下は決り文句です。(MS公式ドキュメントそのまんまです)
tags: null
type: Microsoft.ContainerInstance/containerGroups

 

以下のコマンドを実行して、コンテナを作成します。

$ az container create --resource-group web-scenario-monitor --file aci.yml

 

これでコンテナが作成されて監視が開始されます。

Application Insightsのアラートの作成

これだけでは監視でエラーが発生してもアラートが上がりません。アラートを作成しましょう。

Application Insightsのリソースにアクセスして「アラート」をクリックします。

 

「+新しいアラートルール」をクリックします。

 

「条件の追加」をクリックします。

 

「シグナルの種類」で「ログ」を選択し、「Custom log search」をクリックします。

 

以下のように設定します。

上図の設定内容について詳細に説明致します。

■ 検索クエリ

以下のクエリは、severityLevel == 3つまりログレベルがエラーであることを表しています。そして、message contains “Login Failed”はSeleniumスクリプトから出力したログに「Login Failed」という文字列が含まれていることを表しています。つまりログレベルがエラーかつログの内容に「Login Failed」が含まれるログを抽出するという意味になります。

traces 
| where severityLevel == 3
| where message contains "Login Failed"

■ アラートロジック

上図の設定の内容は、検索クエリで抽出したログの数が5より多く出現したら、、、ということを表す条件式になります。

■ 評価基準

期間(分単位)を5、頻度(分単位)を5としました。これは、5分ごとに検索クエリを実施して、実施してから5分前までさかのぼり、アラートロジックで定義した数(つまり5より大きい)のログが出現設楽アラートを上げるという意味になります。

 

これでアラートを上げる条件は出来たので、次はアラートが発生したときのアクションを定義します。今回は特定のメールアドレスにメールを送ることとしましょう。

「アクショングループの追加」をクリックします。

 

「+アクショングループの作成」をクリックします。

 

「アクショングループ名」「表示名」にわかりやすい名称を入力します。そして「次へ:通知>」をクリックします。

 

「通知の種類」で「電子メール/SMS…」を選択します。そして、「電子メール」のチェックボックスにチェックを入れて、テキストボックスに通知したいメールアドレスを入力して「OK」をクリックします。

 

「名前」に識別しやすい名称を入れて「確認および作成」をクリックします。

 

内容に問題がなければ「作成」をクリックします。

 

最後の仕上げです。「メールの件名」をチェックして、わかりやすい件名を入力します。これがアラート時のメールの件名になります。「アラートルール名」はなんかわかりやすい名称でOKです。任意ですが、「アラートを表示しない」をチェックして「60」と入力します。こうしておくとアラートが発生しても60分間は再びメールが来ません。60分してもエラーが解消されない場合にはまたメールが来ます。

 

これらの設定をすることで、Webアプリケーションへのログインを30秒ごとに行い、5分間の間に5回を超えて失敗すると、アラートメールが飛ぶようになります。

まとめ

Webシナリオ監視で、ユーザーのオペレーションを再現したユーザー目線での監視が可能になります。これでより細かい監視ができるようになり、サービスレベルもきっと上がることでしょうヮ(゚д゚)ォ!

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です