ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

Azure Logic Apps を使って Azure Container Instances の Status を 監視する

gooner.hateblo.jp
前回の記事は負荷テストでしたが、可用性テストの目的で Azure Container Instances(ACI)を使って Cypress を実行した場合に、テストが失敗したことを通知する仕組みを作ってみました。

Cypress のテスト結果を判定する

Cypress が標準出力に書き出すテスト結果を判定する方法もありますが、今回は ACI のコンテナーの Status で判定することにしました。
しかし、現時点では ACI のコンテナーが異常終了したことを検知するトリガーが提供されていません。Event Grid とかでトリガーできるようになると嬉しいかも。
EventGrid notification when ACI container stops – Customer Feedback for ACE Community Tooling
Workaround として、Logic Apps で ACI の Status を定期監視して、Failed ならメール通知する仕組みを構築することにしました。
ACI の Status は、Azure Resource Manager API を呼び出して取得します。

事前準備

とりあえず、Azure Logic Apps と Azure Container Instances を作っておきましょう。

Managed Service Identity(MSI)を設定する

Azure Resource Manager API を呼び出すには、Azure AD 認証したトークンが必要です。Logic Apps の HTTP コネクター では、Azure AD 認証を設定することもできますが、シークレットの管理が手間なので、Managed Service Identity(MSI)を利用します。

Azure Logic Apps の MSI を有効化

先ほど作成した Logic Apps の Identity メニューから、 System assigned を ON にします。

f:id:TonyTonyKun:20201022104504p:plain

Azure Container Instances に MSI を割り当てる

監視する ACI の Access Control(IAM)メニューから、ロールを追加します。Role は Reader を選択し、先ほど MSI を有効化した Logic App を指定します。

f:id:TonyTonyKun:20201022104528p:plain

Azure Logic Apps のフローを作成する

全体フロー

f:id:TonyTonyKun:20201022104544p:plain

Recurrence

定期実行する間隔を指定します。

HTTP

Azure Resource Manager API の Method と URI を指定します。
Authentication type では、Managed IdentitySystem Assigned を選択します。

f:id:TonyTonyKun:20201022104609p:plain

なお、呼び出す API の URI は、Azure Resource Manager で確認できます。

https://management.azure.com/subscriptions/<サブスクリプションID>/resourceGroups/<リソースグループ名>/providers/Microsoft.ContainerInstance/containerGroups/<ACI名>?api-version=2018-10-01

Parse JSON

API のレスポンスで受け取る JSON を解析します。Content は、Body を指定します。
Schema は、Azure Resource Manager で表示される JSON を サンプルとして貼り付けて作成します。

f:id:TonyTonyKun:20201022104625p:plain

Condition

stateFailed の場合、メールを送信する設定を行います。

f:id:TonyTonyKun:20201022104639p:plain

まとめ

Azure Logic Apps を使って Azure Container Instances の Status を 監視する仕組みを作ってみました。
今まで Logic Apps をあまり触ったことなかったのですが、今回のようなユースケースでは便利ですね。Azure CLI のコネクタがあると、さらに捗りそうです。

Azure Container Instances を使って Cypress で負荷テストを実行する

Web アプリケーションの負荷テストを実行したくなったので、Azure Container Instances(ACI)を使って、Cypress を実行する仕組みを作ってみました。

Cypress とは

Cypress は、Web アプリケーションの E2E テストを実行するためのツールです。Selenium よりも、シンプルに導入でき、テスト実行も速いです。
npm でインストールでき、Test Runner は無料で使えるので、CI パイプラインにも組み込みやすいです。
SaaS で提供されている Dashboard を連携させると、テスト結果のエビデンス(スクリーンショットや動画のファイル)が自動でアップロードされます。3ユーザーで月500テストケースまでなら無料です。
www.cypress.io

Cypress でテストケースを作成する

例えば、Google で「cypress.io」を検索して結果ページのタイトルを検証するテストケースであれば、こんな感じで書きます。

const targetUrl = Cypress.env('target_url')

describe('My First Test', function() {
  it('Visit Google', function() {
    cy.visit(targetUrl)

    cy.get("input[name='q']")
      .type('cypress.io')

    cy.contains('Google 検索')
      .click()

    cy.title().should('eq', 'cypress.io - Google 検索')
  })
})

Cypress.env で、環境変数からテスト対象の URL を取得しています。初期値は、cypress.env.json で指定しています。

{
    "target_url": "https://www.google.co.jp"
}

npx cypress run コマンドでテストを実行します。

f:id:TonyTonyKun:20201018113100p:plain

UI を使ってインタラクティブに実行させたい場合は、npx cypress open コマンドを使います。

Cypress を実行する Docker Images を作成する

Cypress を ACI で動かしたいので、Docker Images を作ります。Cypress の公式イメージを使って、日本語フォントを追加しておきました。

FROM cypress/included:5.3.0

# 日本語フォントを追加
RUN apt-get install --no-install-recommends -y fonts-noto fonts-noto-cjk

WORKDIR /app

COPY ./cypress ./cypress
COPY ./cypress.json ./cypress.json
COPY ./cypress.env.json ./cypress.env.json

RUN npx cypress run

これで Cypress を Docker で実行できるようになりました。

$ docker build -t thara0402/cypress-docker:1.0.0 .
$ docker run --rm -it thara0402/cypress-docker:1.0.0

今回は、Docker Hub に Images をプッシュしておきます。

$ docker push thara0402/cypress-docker:1.0.0

Azure Container Instances で負荷テストを実行する

ACI は、ARM Template を使ってデプロイします。

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "resourceNamePrefix": {
            "type": "string"
        },
        "imageType": {
            "type": "string",
            "allowedValues": [
                "Public",
                "Private"
            ]
        },
        "imageName": {
            "type": "string"
        },
        "osType": {
            "type": "string",
            "allowedValues": [
                "Linux",
                "Windows"
            ]
        },
        "numberCpuCores": {
            "type": "string"
        },
        "memory": {
            "type": "string"
        },
        "restartPolicy": {
            "type": "string",
            "allowedValues": [
                "OnFailure",
                "Always",
                "Never"
            ]
        },
        "ipAddressType": {
            "type": "string"
        },
        "ports": {
            "type": "array"
        },
        "environmentVariables": {
            "type": "array"
        },
        "numberOfInstances": {
            "type": "int",
            "minValue": 1,
            "maxValue": 10
        }
    },
    "variables": {
        "containerInstanceName":"[concat(parameters('resourceNamePrefix'), '-aci')]"
    },
    "resources": [
        {
            "location": "[resourceGroup().location]",
            "name": "[concat(variables('containerInstanceName'), copyindex())]",
            "type": "Microsoft.ContainerInstance/containerGroups",
            "apiVersion": "2019-12-01",
            "properties": {
                "containers": [
                    {
                        "name": "[concat(variables('containerInstanceName'), copyindex())]",
                        "properties": {
                            "image": "[parameters('imageName')]",
                            "resources": {
                                "requests": {
                                    "cpu": "[int(parameters('numberCpuCores'))]",
                                    "memoryInGB": "[float(parameters('memory'))]"
                                }
                            },
                            "environmentVariables": "[parameters('environmentVariables')]",
                            "ports": "[parameters('ports')]"
                        }
                    }
                ],
                "restartPolicy": "[parameters('restartPolicy')]",
                "osType": "[parameters('osType')]",
                "ipAddress": {
                    "type": "[parameters('ipAddressType')]",
                    "ports": "[parameters('ports')]",
                    "dnsNameLabel": "[concat(variables('containerInstanceName'), copyindex())]"
                }
            },
            "tags": {},
            "copy": {
                "name": "containerInstanceLoop",
                "count": "[parameters('numberOfInstances')]"
            }
       }
    ],
    "outputs": {
    }
}

テストが失敗した場合に ACI が再起動されないように、restartPolicyNever を指定しています。環境変数は environmentVariables で指定していて、環境変数名のプレフィックスに cypress_ が必要です。

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "resourceNamePrefix": {
            "value": "<ACIの名前のプレフィックスを指定してください>"
        },
        "imageType": {
            "value": "Public"
        },
        "imageName": {
            "value": "thara0402/cypress-docker:1.3.0"
        },
        "osType": {
            "value": "Linux"
        },
        "numberCpuCores": {
            "value": "1"
        },
        "memory": {
            "value": "1.5"
        },
        "restartPolicy": {
            "value": "Never"
        },
        "ipAddressType": {
            "value": "Public"
        },
        "ports": {
            "value": [
                {
                    "port": "80",
                    "protocol": "TCP"
                }
            ]
        },
        "environmentVariables": {
            "value": [
                {
                    "name": "cypress_target_url",
                    "value": "https://www.google.co.jp"
                }
            ]
        },
        "numberOfInstances": {
            "value": 1
        }
    }
}

numberOfInstances に指定した数で、ACI を使った Cypress の負荷テストを実行できます。

$ az group create -n <ResourceGroup Name> -l japaneast
$ az group deployment create -g <ResourceGroup Name> --template-file template.json --parameters parameters.json

繰り返し負荷テストを実行する場合は、ACI を開始するスクリプトを組めば OK です。

#!/usr/bin/env bash
for aci in $(az container list --resource-group <ResourceGroup Name> --query "[].name" -o tsv); do
    az container start -g <ResourceGroup Name> -n $aci --no-wait
    echo $aci starting...
done

負荷テスト結果を確認する

負荷テスト結果は、ACI のステータスを確認して判断することにします。
Azure CLI の az container list で ACI のリストを取得し、az container show でステータスを取得します。

$ az container list --resource-group <ResourceGroup Name> --query "[].id" -o tsv | xargs -I {} -P 10 az container show --query "{name:name,state:instanceView.state}" -o tsv --ids "{}"
test-runner-aci0    Succeeded
test-runner-aci1    Succeeded
test-runner-aci2    Succeeded

まとめ

Azure Container Instances を使って Cypress で負荷テストを実行してみました。
テスト結果を残したい場合は、Cypress Dashboard と連携させたり、AzCopy で Blob にファイルを転送したりする構成を追加する必要があります。
Docker で動かせるのであれば、Cypress でなくとも使える仕組みです。このようなユースケースは、ACI が向いていると思います。

Azure サブスクリプションでリージョン毎にデプロイできる ACI はクオーターが100個までなので、もっと大規模な負荷テストを実行する場合は、Azure Kubernetes Services を使ったほうがよさそうです。
今回のスクリプト一式は、こちらで公開しています。
github.com
github.com

Azure Well-Architected Framework のすすめ

Azure アーキテクチャセンターでは、Azure Well-Architected Framework が公開されています。
docs.microsoft.com

Azure Well-Architected Framework

一言でいえば、Azure をベースとしたシステムを構築する際に役立つアーキテクチャ設計のガイダンスです。
Azure が提供する PaaS や Serverless のリソースを使えば、簡単かつ短期間にシステムを動かすことはできますが、

  • 本番環境でそのシステムを継続的に稼働させるための運用を想定していますか?
  • 想定されるセキュリティ脅威への対策が組み込まれていますか?

このような留意事項が最初から考慮されているシステムは、意外と少ないのではないでしょうか?

Azure Well-Architected Framework では、フレームワークの柱とされる 5 つの設計観点が紹介されています。

  • コストの最適化
  • オペレーショナル エクセレンス
  • パフォーマンス効率
  • 信頼性
  • セキュリティ

非機能要件の網羅性を検討する際には、ISO9126 の品質特性や IPA の非機能要求グレードを使うことが多いかと思います。その中でもクラウドで重要な項目に特化したものが、この 5 つの柱となります。
それに加えて、設計原則と具体的なソリューションのベストプラクティスも紹介されており、Azure のシステム設計に関わる人であれば、一読しておきたい内容です。

Azure Advisor

Azure Portal には、Azure Advisor という機能があり、構築したリソースの構成内容を分析し、最適なソリューションを提案してくれます。
このカテゴリが Azure Well-Architected Framework の 5 つの設計観点となっているため、うまくリンクさせて活用したい機能です。

f:id:TonyTonyKun:20200930193443p:plain

検知された影響度合いでアラート通知を設定することもできます。最初はちゃんと構成を組んだけど、それ以降はおざなりになることはよくあるので、継続的に構成を監視してくれるのは助かります。
azure.microsoft.com

ラーニングパス

Azure アーキテクチャセンターのドキュメントを読む以外にも、Microsoft Learn にラーニングパスが用意されているので、こちらのコンテンツもお勧めです。

f:id:TonyTonyKun:20200930193646p:plain

docs.microsoft.com

まとめ

漠然とクラウドらしい優れたアーキテクチャを設計しようと考えても、なかなか難しいものです。Azure Well-Architected Framework を使うことで、非機能要件を抜け漏れなく網羅的に検討できると思います。

  • システムといっても範囲は広いので、インフラ、アプリケーション、データのようにレイヤーを分けて考える必要がありますし、共同責任モデルの話も関連してきます。
  • この 5 つの設計観点を完全に満たしたシステムを構築するためには、コストがかかりますし、構造も複雑化するので、トレードオフが発生します。設計の選択に影響を与える方針や優先事項は、システムごとに異なります。

これらも考慮したうえで、設計を決めた根拠を明確にし、その結果に責任を持てるようにしたいです。