ROMANCE DAWN for the new world

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

Azure Application Insights のテレメトリデータを Stream Analytics を使って転送する

Azure Application Insights に蓄積されるテレメトリデータを活用して、独自の分析を行いたくなったので、Stream Analytics を使って転送してみました。
公式ドキュメントのチュートリアルに記載されているとおりですが、ページビューではなくカスタムイベントを題材に、SQL Database と Functions に転送しました。
docs.microsoft.com

事前準備

Azure Portal から Web Apps と Application Insights を作成し、ASP.NET Core のアプリケーションをデプロイします。
Privacy ページがクリックされたら、TelemetryClient を使って Custom Event を送信するようにしました。

public class HomeController : Controller
{
    private readonly TelemetryClient _telemetryClient;

    public HomeController(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    public IActionResult Privacy()
    {
        _telemetryClient.TrackEvent("Click Privacy Page");
        return View();
    }
}

Application Insights のテレメトリデータを Blob Storage に転送する

Application Insights の Continuous Export を利用して、テレメトリデータを Blob Storage に転送します。
Azure Portal から Storage を作成し、Continuous Export を設定します。Blob の appinsights というコンテナに転送するようにします。
少し待つと、Application Insights のテレメトリデータが転送されます。

f:id:TonyTonyKun:20200328133029p:plain

転送先の SQL Database を作成する

Azure Portal から SQL Database の telemetry というデータベースを作成し、Custom Event を転送するテーブルも作成します。

CREATE TABLE [dbo].[CustomEventTable](
    [eventName] [nvarchar](max) NOT NULL,
    [eventTime] [datetime2] NOT NULL
)

CREATE CLUSTERED INDEX [eventTblIdx] ON [dbo].[CustomEventTable]
(
    [eventTime] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

Custom Event の名前とイベント発生時刻だけを保持するシンプルなテーブルレイアウトにしました。

Stream Analytics を使って SQL Database に転送する

Azure Portal から SQL Database に転送する Stream Analytics Job を作成し、JOB TOPOLOGY メニューから各種設定を行います。

Inputs

BlobInput という名前で、転送元の Blob Storage を設定します。Storage Account や Container は迷うことはないと思いますが、重要なのは Path pattern です。
実際に転送された Blob のディレクトリ構造を確認してみます。

  • Application Insights 名 + Instrumental key
    • Event
      • 日付
        • 時刻

このディレクトリ構造に対応させると、下記のような Path pattern となります。

ApplicationInsightsName_InstrumentalKey/Event/{date}/{time}

合わせて、Date Format を区切り文字をダッシュの YYYY-MM-DD に変更します。

f:id:TonyTonyKun:20200328181308p:plain

Outputs

SqlDbOutput という名前で、転送先の SQL Database を設定します。データベースやテーブル名は迷うことはないと思います。

Query

Query メニューでクエリを書き換えます。

SELECT
    event.ArrayValue.name as eventName,
    Input.context.data.eventTime as eventTime
INTO
    [SqlDbOutput]
FROM
    [BlobInput] Input
    CROSS APPLY GetElements(Input.[event]) as event

1つの Blob ファイルに複数の Custom Event が書き込まれるので、CROSS APPLY を使っています。
Custom Event のデータモデルの詳細は、公式ドキュメントを参照してください。
docs.microsoft.com

あらかじめ Blob ファイルをダウンロードしておくと、実データを使ったテストを行いながらクエリを書くことができます。

f:id:TonyTonyKun:20200328134153p:plain

転送先の Functions を作成する

Azure Portal から Function App を作成します。Stream Analytics 向けの Binding は用意されていないので、HTTP Trigger の Function を追加します。

public static class SaFunction
{
    [FunctionName("SaFunction")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Stream Analytics trigger function processed a request.");

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        log.LogInformation("requestBody : " + requestBody);

        log.LogInformation("Stream Analytics trigger function is success.");
        return new OkResult();
    }
}

Stream Analytics を使って Functions に転送する

Azure Portal から Functions に転送する Stream Analytics Job を作成し、JOB TOPOLOGY メニューから各種設定を行います。

Inputs

先ほどと同様に BlobInput という名前で、転送元の Blob Storage を設定します。

Outputs

FuncOutput という名前で、転送先の Function App を設定します。Function や Key は迷うことはないと思います。

Query

Query メニューでクエリを書き換えます。INTO 句の部分を SqlDbOutput から FuncOutput に変更した以外は、先ほどと同様のクエリです。

SELECT
    event.ArrayValue.name as eventName,
    Input.context.data.eventTime as eventTime
INTO
    [FuncOutput]
FROM
    [BlobInput] Input
    CROSS APPLY GetElements(Input.[event]) as event

結果確認

Web Apps にデプロイした ASP.NET Core のアプリケーションで Privacy ページを数回クリックします。
Blob Storage に転送された Application Insights のテレメトリデータが、Stream Analytics によって SQL Database と Functions に転送されたことを確認できます。

f:id:TonyTonyKun:20200328132302p:plain
SQL Database

f:id:TonyTonyKun:20200328132207p:plain
Functions

まとめ

Application Insights に蓄積されるテレメトリデータを Stream Analytics を使って、SQL Database と Functions に転送してみました。
Application Insights では直近で保持したい期間とサイズを設定しておき、古いデータは Blob Storage に転送しておくと、課金コストを抑えることができます。
Stream Analytics 以外にも、EventGrid や Data Factory などを活用すると、幅広い用途でデータの分析などが行えるようになります。

Azure DevOps の Multi-Stage Pipelines でよく使うパターンのテンプレートを作成してみた

現時点では、Multi-Stage Pipelines はプレビュー機能ですが、従来の Build と Release のパイプラインは Classic 扱いとなっているので、よく使うパターンのテンプレートを作成してみました。

f:id:TonyTonyKun:20191220105519p:plain

Azure Web Apps 向けパイプライン

Azure Web Apps に ASP.NET Core のアプリケーションをデプロイするシナリオです。
gooner.hateblo.jp
gooner.hateblo.jp

Azure Functions 向けパイプライン

Azure Functions に .NET Core のアプリケーションをデプロイするシナリオです。
gooner.hateblo.jp

Azure Storage の Static website 向けパイプライン

Azure Storage の Static website に Vue.js のアプリケーションをデプロイするシナリオです。フロントには、Azure CDN を配置しています。
gooner.hateblo.jp
Azure Storage ではなく、Azure Web Apps を使う場合はこちらを参照してください。
github.com

まとめ

パイプラインの YAML ファイルは、下記のリポジトリで公開しています。気が向いたら、他のパターンも追記する予定です。
github.com

Azure DevOps の Multi-Stage Pipelines で Azure Storage の Static website のパイプラインを構築する

先日の記事で、Azure DevOps の Multi-Stage Pipelines を使って Azure Web Apps と Functions にデプロイする内容を記載しました。
gooner.hateblo.jp
gooner.hateblo.jp
今回は、Azure Storage の Static website 向けのパイプラインを構築します。Static website の場合、カスタムドメインで HTTPS を構成するために、Azure CDN や Front Door を配置する必要があります。今回は、Azure CDN を配置する構成を選択しました。アプリケーションは、Vue.js をデプロイします。

パイプラインの全体設計

先日の記事と同様に、パイプラインは、自動デプロイされる開発環境向け(Commit Stage)と承認デプロイされる本番環境向け(Production Stage)を作ります。YAML を Build と Release を分けて、可変部をパラメータで切り替えできるテンプレートを作ることで再利用性を向上させます。

開発環境向けパイプライン

まずは、パイプライン起動用の azure-pipelines.yml です。

trigger:
- master

variables:
- group: variable-group-commit
- name: imageName
  value: 'ubuntu-latest'
- name: nodeVersion
  value: '10.x'
- name: azureSubscription
  value: 'AzureSponsorships'
- name: environment
  value: 'Commit-Stage'

stages:
- stage: Build
  jobs:
  - template: pipelines/build-pipelines.yml
    parameters:
      imageName: $(imageName)
      nodeVersion: $(nodeVersion)
    
- stage: Release
  dependsOn:
  - Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
  jobs:
  - template: pipelines/release-pipelines.yml
    parameters:
      imageName: $(imageName)
      azureSubscription: $(azureSubscription)
      storageAccountName: $(storageAccountName)
      environment: $(environment)
      cdnResourseGroupName: $(cdnResourseGroupName)
      cdnEndpointName: $(cdnEndpointName)
      cdnProfileName: $(cdnProfileName)

Azure Web Apps との違いは、Azure Storage と CDN の名前を渡している部分ぐらいで、全体の構成は変わりません。

次に、ビルド用の build-pipelines.yml です。

parameters:
  imageName: ''
  nodeVersion: ''

jobs:
- job: Build
  pool:
    vmImage: ${{parameters.imageName}}
  steps:
  - task: NodeTool@0
    inputs:
      versionSpec: ${{parameters.nodeVersion}}
    displayName: 'Install Node.js'
  
  - script: |
      npm install
      npm run build
    displayName: 'npm install and build'
  
  - task: ArchiveFiles@2
    inputs:
      rootFolderOrFile: '$(Build.SourcesDirectory)/dist'
      includeRootFolder: false
      archiveType: 'zip'
      archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildNumber).zip'
      replaceExistingArchive: true
    displayName: 'Archive artifact'

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'webapp'
      publishLocation: 'Container'
    displayName: 'Publish artifact'

Node.js Tool Installer task で npm コマンドでインストールとビルドを行い、Archive Files task で dist ディレクトリに出力されたファイル群を zip にまとめています。
最後に、Publish Build Artifacts task で、リリースパイプラインに渡すための zip をアーティファクトへ発行しています。

最後に、リリース用の release-pipelines.yml です。

parameters:
  imageName: ''
  azureSubscription: ''
  storageAccountName: ''
  environment: ''
  cdnResourseGroupName: ''
  cdnEndpointName: ''
  cdnProfileName: ''

jobs:
- deployment: Deploy_Static_Website_with_Azure_Storage
  displayName: 'Release'
  pool:
    vmImage: ${{parameters.imageName}}
  environment: ${{parameters.environment}}
  strategy:
    runOnce:
      deploy:
        steps:
        - script: |
            wget https://aka.ms/downloadazcopy-v10-linux
            tar -xvf downloadazcopy-v10-linux
            cp ./azcopy_linux_amd64_*/azcopy ./
          displayName: 'Install AzCopy v10'

        - task: ExtractFiles@1
          inputs:
            archiveFilePatterns: '$(Pipeline.Workspace)/**/*.zip'
            destinationFolder: '$(Pipeline.Workspace)/deploy'
            cleanDestinationFolder: true
          displayName: 'Extracting site content'
        
        - task: AzureCLI@1
          inputs:
            azureSubscription: ${{parameters.azureSubscription}}
            scriptLocation: 'inlineScript'
            inlineScript: |
              end=`date -d "5 minutes" '+%Y-%m-%dT%H:%M:%SZ'`
              sas=`az storage container generate-sas -n '$web' --account-name $1 --https-only --permissions dlrw --expiry $end -otsv`
              ./azcopy sync "$(Pipeline.Workspace)/deploy/" "https://$1.blob.core.windows.net/\$web?$sas" --recursive --delete-destination=true
            arguments: ${{parameters.storageAccountName}}
          displayName: 'Deploy to Static Website with Azure Storage'

        - task: AzureCLI@1
          inputs:
            azureSubscription: ${{parameters.azureSubscription}}
            scriptLocation: 'inlineScript'
            inlineScript: |
              az cdn endpoint purge -g ${{parameters.cdnResourseGroupName}} -n ${{parameters.cdnEndpointName}} --profile-name ${{parameters.cdnProfileName}} --content-paths '/*'
          displayName: 'Purge content for a CDN endpoint'

デプロイする Vue.js アプリケーションのファイル群は、AzCopy v10 を使って Blob Storage にアップロードしたいので、まずはインストールです。
インストールが完了したら、Extract Files task でビルドパイプラインから渡されたアーティファクトの zip を解凍し、AzCopy でアップロードします。
AzCopy は copy コマンドではなく、sync コマンドを使うことで、ファイルの削除や移動を気にせずにデプロイできます。Blob Storage へのアクセスには、Azure CLI task で SAS を発行して、一時的な権限を発行しています。
デプロイ自体はこれで完了ですが、最後に Azure CDN のエンドポイントにあるコンテンツを Purge しています。これでパイプライン完了後、Vue.js アプリケーションの更新をすぐに反映できます。

本番環境向けパイプライン

パイプライン起動用の azure-pipelines-production.yml です。

trigger:
- production

variables:
- group: variable-group-production
- name: imageName
  value: 'ubuntu-latest'
- name: nodeVersion
  value: '10.x'
- name: azureSubscription
  value: 'AzureSponsorships'
- name: environment
  value: 'Production-Stage'

stages:
- stage: Build
  jobs:
  - template: pipelines/build-pipelines.yml
    parameters:
      imageName: $(imageName)
      nodeVersion: $(nodeVersion)
    
- stage: Release
  dependsOn:
  - Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/production'))
  jobs:
  - template: pipelines/release-pipelines.yml
    parameters:
      imageName: $(imageName)
      azureSubscription: $(azureSubscription)
      storageAccountName: $(storageAccountName)
      environment: $(environment)
      cdnResourseGroupName: $(cdnResourseGroupName)
      cdnEndpointName: $(cdnEndpointName)
      cdnProfileName: $(cdnProfileName)

トリガーとなるブランチが production となっている箇所が開発環境向けパイプラインとの違いです。
Azure Web Apps の Deployment Slot 機能のような機能がないので、そのままデプロイしています。承認デプロイは使いたいので、承認設定した Environments を Production-Stage で登録しておきます。

まとめ

Azure DevOps の Multi-Stage Pipelines を使って、Azure Storage の Static website 向けのパイプラインを構築してみました。
Vue.js のような静的な Web アプリケーションの場合、Azure Web Apps を使う選択肢も十分に考慮しておきたいところです。

  • Azure CDN や Front Door を必須にしたくない
  • Blue / Green デプロイを実現したい(Deployment Slot 機能)
  • Easy Auth 機能で認証したい
  • Application Insight のモニタリングを活用したい

このような要件がある場合には、リリースパイプラインを Web Apps 向けに差し替えて使ってください。
こちらから、パイプライン YAML の全体を確認できます。
github.com