ごった煮

色々な事を書いてます

Managed Identity に繋がったアプリをローカルデバッグしたい

Azure のサービスに繋ぎに行くとき、Managed Identity を使ってセキュアに接続する方法がありますが、ローカルで動かしたいときは、少しややこしいのでメモとして残します

ローカルで Managed Identity に繋ぐ

因みに今回は、Azure Appn Configuration を例に出します。

用意するもの

Azure のアカウントに Managed Identity での接続を許可する

まず Azure のアカウントに権限を付与します。 Managed Identity で接続したいサービスの IAM > Role Assaignments > Add role assaignment を選択します。 f:id:papemk2:20200129135658p:plain

次に設定するロールを選択します。 App Configuration の場合は、App Configuration Data Reader で大丈夫です

f:id:papemk2:20200129140521p:plain

Assign access to は、Azure AD User, group, or service principal を選択します。

Visual Studio を設定する

ローカルから Managed Identity で繋ぐ場合の認証情報は、Visual Studio に設定されている認証情報を使用します。 そのため Visual Studio でログインしている MSアカウント or 組織アカウント に Managed Identity でリソースにアクセスする権限が付与されていれば、そのまま接続されます。

先ほど権限を付与したアカウントで Visual Studio にログイン出来る場合は、これで繋がるようになるはずです。

権限を付与したアカウントと Visual Studio のライセンスを持っているアカウントが異なる場合は、別の方法で設定します。

Visual Studio の Tools > Options > Azure Service Authentication の項目に移動します。

f:id:papemk2:20200129140938p:plain

この部分は、デフォルトで Visual Studio のログインに使用しているアカウントが選択されます。 なのでここで先ほど権限付与を行ったアカウントを選択 or 登録すると Visual Studio のログイン用アカウントと Azure との接続に使用するアカウント情報を別々にすることが出来ます。

まとめ

Managed Identity は、かなり便利なので活用してみましょう

Azure Pipeline の通知を Slack で受け取る

この記事は、Azure DevOps アドベントカレンダーの 15日目です。

Release pipeline は、各処理の結果をSlack に通知することが出来ます。 Azure pipeline の機能で通知する方法と Slack アプリを使用する方法がありますが、今回は、Slack アプリの方を説明します。

アプリを入れる

この方法には、Slack 用のアプリを入れて行います。 以下からインストールします

slack.com

このアプリは、ビルド、リリースの各処理が成功・失敗した時、リリース承認が必要な時などに通知を行うことが出来ます。

使えるようにする

プライベートチャンネルのみ、アプリをチャンネルに追加する必要があります。

/invite @azpipelines

で、アプリをSlack に登録します。 するとこんな感じになります。 f:id:papemk2:20191214212041p:plain

次に Azure にサインインします。

/azpipelines signin

を呼ぶと、以下のような感じ f:id:papemk2:20191214212256p:plain

SignIn ボタンを押すと、Azure のサインイン画面にジャンプします。 サインインが出来ると画面に数字が表示されます。 f:id:papemk2:20191214212429p:plain

Slack の画面に戻り、Enter code を選択すると、その数字を入力する画面が表示されるので入力します。 これでサインイン完了です。

Pipeline を登録する

サインインまでできたら、パイプラインを登録します。

/azpipelines subscribe パイプラインのURL

を指定するとパイプラインの通知を受け取ることが出来るようになります。 登録しているパイプラインを確認するときは、

/azpipelines subscriptions

すると、以下のような感じでパイプラインの管理が出来ます。 f:id:papemk2:20191214214425p:plain

パイプラインを登録すると、デフォルトでは、全てのステータス変更などが通知される設定になっているので、通知がうるさく感じる場合などは、細かく設定します。 Add subscription ボタンを押すと、細かい登録を行うための画面が出てきます。 例えば、ビルドパイプラインが失敗した場合は、以下のような感じ f:id:papemk2:20191214214737p:plain

リリース承認を行う

このアプリを使うと、Slack からリリースパイプラインの認証処理が行えます。 リリースパイプラインを登録した後に、そのリリースパイプライン承認が必要になると、Slack に通知が来ます。 f:id:papemk2:20191214215002p:plain

Approval を押すと承認、Reject を押すと承認しないことになります。

Slack から承認をする場合は、プロジェクトの Admin にユーザを設定する + Permissions > Service Account > Make requests on behalf of others を Allow にします。 これをしておかないと権限不足でエラーになります。

まとめ

Slack を通知対象にしておくと、メールなどよりも人によっては、見落としにくくなるかなと思います。 ビルドが成功した場合などは、あまり監視しなくても良いですが、失敗した場合の監視は、見落とすと厄介なので、ぜひ通知を活用してみましょう。

Azure DevOps で最新の.NET Core をビルドしたい

.NET Core 3.1 が出ましたが、Azure DevOps のビルドマシンに即日展開されるってことは、無いです。 ですがバージョンアップ後にビルドできなくなるのも困りものなので、最新のバージョンを使う方法をメモします。

Yaml を書く

とりあえず Yaml に定義を追加するだけです。 使いコマンドは、Use .NET Core f:id:papemk2:20191210181709p:plain

設定項目は、ランタイムを入れるか SDK を入れるか、入れたいバージョンなどです。 今回は、3.1系なので、3.1.x を指定します。 f:id:papemk2:20191210181819p:plain

Yaml の定義は以下のようになります。

- task: UseDotNet@2
  inputs:
    packageType: 'sdk'
    version: '3.1.x'

とても簡単に入ります。

まとめ

プレビューバージョンを指定するオプションなんかも用意されているので、必要に応じて追加しましょう。

Azure Monitor と Log Analytics を組み合わせてアプリケーションを監視する

この記事は、Azure アドベントカレンダー 2019 の8日目の投稿です。

Azure には、アプリケーションの監視用に Application Insights(以下 AI ) があります。 AI は、ログの分析を行う Log Analytics と組み合わせることによって柔軟にログを扱うことが出来ます。

今回は、AI で収集したログを Log Analytics で集約し、Azure Monitor を使って Azure Functions にエラーメッセージを送る方法についてまとめます。

※今回は、AI とアプリケーションの繋ぎ込みなどは、扱わないのでそういった情報は、以下のようなところから調べてみましょう。

docs.microsoft.com

docs.microsoft.com

Log Analytics でログを分析する

Log Analytics でログ分析を行う場合は、基本的に SQL ライクな KQL という言語を使用してクエリを書きます。

Log Analytics のクエリエディタは、Azure ポータルの AI の画面から飛ぶことが出来ます。 AI の画面上部にある Log(Analytics) を選択します。 f:id:papemk2:20191206225727p:plain

クエリエディタは、以下のような画面になります。 赤枠の中にクエリを書いていきます。 f:id:papemk2:20191206231018p:plain

クエリを書いたら Run ボタンを押します。

ログは、以下の項目が検索できます。

  • traces
  • customEvents
  • pageViews
  • requests
  • dependencies
  • exceptions
  • availabilityResults
  • customMetrics
  • performanceCounters
  • browserTimings

自分が良く使う項目は、requests、dependencies、exceptions です。 requests は、アプリケーションに送られてくるリクエスト内容 dependencies は、DB、Web API などアプリケーション外にある依存関係 exceptions は、アプリケーション内で発生した例外が含まれます。

一番簡単なクエリは、以下です。

requests

これを実行すると、ログが表組されて表示されます。 f:id:papemk2:20191206231143p:plain

条件分は、SQL のように書きます。

requests
| where resultCode == 404 or resultCode == 500

カラムの絞り込みは、以下のような感じ

requests
| where resultCode == 404 or resultCode == 500
| project resultCode , url , name 

f:id:papemk2:20191206231805p:plain

サマリーしたいときは、以下のような感じ

requests
| where resultCode == 404 or resultCode == 500
| project resultCode , url , name 
| summarize count(resultCode) by resultCode, url, name

f:id:papemk2:20191206232436p:plain

Log Analytics と Azure Monitor を組み合わせる

先ほど書いたクエリ結果をAzure Monitor と組み合わせてみます。 これは、Azure Monitor の画面から行います。 Azure Monitor のモニタルールは、ポータルの Azure Monitor > Alerts > New alert rule から進みます。 進むと以下のような画面になります。 f:id:papemk2:20191206233303p:plain

Resource で監視対象の AI を指定します。 赤枠の中に Application Insights と入れると絞り込みがされます。

f:id:papemk2:20191206233749p:plain

Condition は、リソースの状態を指定する項目です。 ここで Log Analytics のクエリを指定します。

Custom log search を指定します。

f:id:papemk2:20191206233936p:plain

赤枠のSearch query にクエリを貼り付けます。 今回は、エラーが発生したら通知を行いたいので、閾値(Threshold value)は、0にします。(0を指定すると0回以上になるので、イベントが発生したら全て拾うことになります。) 時間の範囲は、デフォルト(5分)に設定します。 f:id:papemk2:20191206234115p:plain

Actions は、アラートの発行先を指定する項目です。 今回は、Function App を指定します。 Create action group を選択します。 選択後、Action Type に Azure Functions を指定します。 次に出てくるウィンドウで、リソースグループを選択するとHttp Trigger が含まれている Function App が表示されるのでアラートを送りたい Function App を指定します。 f:id:papemk2:20191206234626p:plain

最後に Alert Details で、Alert rule name を指定します。 これで、アラートが発生すると Function App のエンドポイントが呼び出されます。

Azure Functions でメッセージを受け取る

Function App が受け取るペイロードは、以下のような形式です。

{
    "schemaId": "Microsoft.Insights/LogAlert",
    "data": {
        "SubscriptionId": "Your own subscription id",
        "AlertRuleName": "Qiita-Monitoring-2019",
        "SearchQuery": "requests\n| where resultCode == 404 or resultCode == 500\n| project resultCode , url , name \n| summarize count(resultCode) by resultCode, url, name",
        "SearchIntervalStartTimeUtc": "2019-12-06T15:03:56",
        "SearchIntervalEndtimeUtc": "2019-12-06T15:08:56",
        "AlertThresholdOperator": "Greater Than",
        "AlertThresholdValue": 0,
        "ResultCount": 2,
        "SearchIntervalInSeconds": 300,
        "LinkToSearchResults": "https://portal.azure.com#@ef5d34b2-4e7d-468e-b184-b17e34c6be36/blade/Microsoft_OperationsManagementSuite_Workspace/AnalyticsBlade/initiator/AnalyticsShareLinkToQuery/isQueryEditorVisible/true/scope/%7B%22resources%22%3A%5B%7B%22resourceId%22%3A%22%2Fsubscriptions%2F30bc1c24-7c50-471e-97c4-b86a3d9cedb1%2FresourceGroups%2FQiita%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2FQiita-Monitoring-2019%22%7D%5D%7D/query/requests%0A%7C%20where%20resultCode%20%3D%3D%20404%20or%20resultCode%20%3D%3D%20500%0A%7C%20project%20resultCode%20%2C%20url%20%2C%20name%20%0A%7C%20summarize%20count%28resultCode%29%20by%20resultCode%2C%20url%2C%20name/isQuerybase64Compressed/false/timespanInIsoFormat/2019-12-06T15%3a03%3a56.0000000Z%2f2019-12-06T15%3a08%3a56.0000000Z",
        "Description": "",
        "Severity": "3",
        "SearchResult": {
            "tables": [
                {
                    "name": "PrimaryResult",
                    "columns": [
                        {
                            "name": "resultCode",
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "type": "string"
                        },
                        {
                            "name": "name",
                            "type": "string"
                        },
                        {
                            "name": "count_resultCode",
                            "type": "long"
                        }
                    ],
                    "rows": [
                        [
                            "500",
                            "https://localhost:44355/home/error500",
                            "GET /home/error500",
                            8
                        ],
                        [
                            "404",
                            "https://localhost:44355/error",
                            "GET /error",
                            15
                        ]
                    ]
                }
            ],
            "dataSources": [
                {
                    "resourceId": "/subscriptions/30bc1c24-7c50-471e-97c4-b86a3d9cedb1/resourcegroups/qiita/providers/microsoft.insights/components/qiita-monitoring-2019",
                    "tables": [
                        "requests"
                    ]
                }
            ]
        },
        "ApplicationId": "dcd854c3-e6a8-4f05-b58c-8272660d3427",
        "AlertType": "Number of results"
    }
}

columns が、テーブルのカラム名、rows が結果にリンクします

LinkToSearchResults の URL は、Log Analytics の クエリエディタに繋がります。

このペイロードを Slack に送るなどするとアプリのモニタリングがしやすいかと思います。

ペイロードを受け取る場合のC#の例を示します。

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    
    log.LogInformation(requestBody);

    return (ActionResult)new OkObjectResult(requestBody);
}

Request Body に json が入ってくるので、それを加工して次の処理の送るといった感じで使用します。 Logic App に送るといったことも可能ですが、例えば Slack 通知などは、デフォルトの Slack コネクタだとリッチなメッセージが送れないので Function App が向いているかなという気がします。

まとめ

これらの学習には、Microsoft Learn がおすすめです。

docs.microsoft.com

アプリケーションの監視は、サービス運用の要なので、この機会に是非試してみましょう。

Webフロントエンドを Application Insights で監視する

Application Insightsは、アプリケーション解析に便利なログ収集、解析ツールです。

.NET などは、SDK を入れるだけで簡単にフロント、バックエンド問わずアプリケーション解析の為のログを収集できます。

また単純なフロントエンドだけ(JavaScript)のアプリケーションでも簡単にログを収集することが出来るのが非常に魅力的です。

試してみる

いくつかセットアップ方法がありますが、今回は、一番簡単なスニペットを貼り付けるだけのセットアップ方法を紹介します。 以下のスニペットを記述するだけで AI のセットアップは、完了です。

   <script type="text/javascript">
      var sdkInstance = "appInsightsSDK"; window[sdkInstance] = "appInsights"; var aiName = window[sdkInstance], aisdk = window[aiName] || function (e) { function n(e) { t[e] = function () { var n = arguments; t.queue.push(function () { t[e].apply(t, n) }) } } var t = { config: e }; t.initialize = !0; var i = document, a = window; setTimeout(function () { var n = i.createElement("script"); n.src = e.url || "https://az416426.vo.msecnd.net/scripts/b/ai.2.min.js", i.getElementsByTagName("script")[0].parentNode.appendChild(n) }); try { t.cookie = i.cookie } catch (e) { } t.queue = [], t.version = 2; for (var r = ["Event", "PageView", "Exception", "Trace", "DependencyData", "Metric", "PageViewPerformance"]; r.length;)n("track" + r.pop()); n("startTrackPage"), n("stopTrackPage"); var s = "Track" + r[0]; if (n("start" + s), n("stop" + s), n("addTelemetryInitializer"), n("setAuthenticatedUserContext"), n("clearAuthenticatedUserContext"), n("flush"), t.SeverityLevel = { Verbose: 0, Information: 1, Warning: 2, Error: 3, Critical: 4 }, !(!0 === e.disableExceptionTracking || e.extensionConfig && e.extensionConfig.ApplicationInsightsAnalytics && !0 === e.extensionConfig.ApplicationInsightsAnalytics.disableExceptionTracking)) { n("_" + (r = "onerror")); var o = a[r]; a[r] = function (e, n, i, a, s) { var c = o && o(e, n, i, a, s); return !0 !== c && t["_" + r]({ message: e, url: n, lineNumber: i, columnNumber: a, error: s }), c }, e.autoExceptionInstrumented = !0 } return t }(
         {
            instrumentationKey: "Your own instrumentation key"
         }
      ); window[aiName] = aisdk, aisdk.queue && 0 === aisdk.queue.length && aisdk.trackPageView({});
   </script>

instrumentationKey の部分にポータルから取得した AI のキーを挿入します。

アプリケーション全体を監視するためにも、Head タグの一番上に記述することが推奨されています。

これだけで、ページ内のエラーが AI に送られるようになります。

以下のコードを試してみます。 jQuery を読み込んでいないのに $() を使おうとしているので、エラーになります。

<!DOCTYPE html>
<html lang="en">

<head>
   <script type="text/javascript">
      var sdkInstance = "appInsightsSDK"; window[sdkInstance] = "appInsights"; var aiName = window[sdkInstance], aisdk = window[aiName] || function (e) { function n(e) { t[e] = function () { var n = arguments; t.queue.push(function () { t[e].apply(t, n) }) } } var t = { config: e }; t.initialize = !0; var i = document, a = window; setTimeout(function () { var n = i.createElement("script"); n.src = e.url || "https://az416426.vo.msecnd.net/scripts/b/ai.2.min.js", i.getElementsByTagName("script")[0].parentNode.appendChild(n) }); try { t.cookie = i.cookie } catch (e) { } t.queue = [], t.version = 2; for (var r = ["Event", "PageView", "Exception", "Trace", "DependencyData", "Metric", "PageViewPerformance"]; r.length;)n("track" + r.pop()); n("startTrackPage"), n("stopTrackPage"); var s = "Track" + r[0]; if (n("start" + s), n("stop" + s), n("addTelemetryInitializer"), n("setAuthenticatedUserContext"), n("clearAuthenticatedUserContext"), n("flush"), t.SeverityLevel = { Verbose: 0, Information: 1, Warning: 2, Error: 3, Critical: 4 }, !(!0 === e.disableExceptionTracking || e.extensionConfig && e.extensionConfig.ApplicationInsightsAnalytics && !0 === e.extensionConfig.ApplicationInsightsAnalytics.disableExceptionTracking)) { n("_" + (r = "onerror")); var o = a[r]; a[r] = function (e, n, i, a, s) { var c = o && o(e, n, i, a, s); return !0 !== c && t["_" + r]({ message: e, url: n, lineNumber: i, columnNumber: a, error: s }), c }, e.autoExceptionInstrumented = !0 } return t }(
         {
            instrumentationKey: "My own instrumentation key"
         }
      ); window[aiName] = aisdk, aisdk.queue && 0 === aisdk.queue.length && aisdk.trackPageView({});
   </script>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Document</title>
</head>

<body>
   <h1>Hello World !</h1>
</body>

<script type="text/javascript">
   window.onload = function () {
      $("selector").on("click", function() {
         console.log("error");
      });
   }
</script>

</html>

エラーが発生するとログとして送信されるので、以下のようにクエリエディタで確認ができます。

f:id:papemk2:20191207160801p:plain

まとめ

これでフロントエンドの監視も見落とさないようになれるといいなあ。。。

ASP.NET (.NET Framework) のプロジェクトを Azure DevOps でビルドする

Azure DevOps 便利ですよね ASP.NET Core ならテンプレートも充実してるしすぐにCICD を組むことが出来ます。 ですが意外と .NET Framework のプロジェクトをビルドするのはややこしいと思ったので備忘録にします。

実際にやってみる

結局 MSBuild を頑張る形になります。 普段は、Visual Studio が頑張ってくれるので MSBuild の使い方を意識することは、あまりなくそれがハードルの高さに感じます。

因みに今回は、クラシックエディタを想定しています。(OtherGit を使う想定です)

パイプライン作成時のテンプレートで以下のものを選択します。 f:id:papemk2:20191126184536p:plain

テンプレートが出来ると以下のような感じになります。 f:id:papemk2:20191126184842p:plain

ここで大事なのは、Build Solution の Job です。 1 ソリューション、1プロジェクトみたいな形ならこのままでOKですが、複数プロジェクトが入っていたりすると MSBuild Arguments を書く必要が出ます。 f:id:papemk2:20191126185033p:plain

デフォルトだと、特定のプロジェクトだけデプロイしたい場合に対応できないのでここを頑張ります。

MSBuild arguments

デフォルトでは、以下の記述があります。

/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\\"

これだとプロジェクトが指定されていないので、全てのプロジェクトがビルドされます。 なので特定のプロジェクトだけビルドしたい場合は、以下のようにします。

/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\\" /t:"ターゲットプロジェクト:Clean;Build"

/t ターゲットプロジェクト の後ろのClean;Build は、クリーンしてからビルドするコマンドになります。 これを入れておくのが無難です。

ターゲットプロジェクトは、ソリューション内でフォルダが切られてその下にいる場合は、フォルダ名\プロジェクト名とします。 プロジェクト名に . が含まれる場合は、. を入れられない仕様なので _ に置き換えます。 例えば Target.Project.csproj の場合は、Target_Project にします。

これでビルドを回せば、デプロイ用のパッケージがいい感じに作られます。

MSBuild が入っている環境ならコマンドを試せるので、手元の環境で試して問題が無かったら DevOps に反映みたいな形がいいかと思います。 その場合は、

msbuild ソリューション名.sln 上記のコマンド

とすると実行できます。

まとめ

MSBuild は、Visual Studio があるとうまく隠蔽されていて触る機会が少なくハードルが高いですが、一度覚えると便利なので試してみるといいかもしれません。

ARM Template を YAML で書きたい

インフラを構成管理するという意味で、ARM Template は、とても便利ですが、如何せん Json なので何かと不便な面もあります。 個人的には、コメントが書ける Yaml の方が構成ファイル用のフォーマットとしては、優秀だと感じています。

そこでARM Template を Yaml で記述する方法を紹介します。

どうやるのか

最終的に YamlJson に変換することが出来れば OK です。 ということで Yaml に変換するツールを持ってきます。

ツールの紹介

今回は、Yarm というツールを使用します。

github.com

このツール群は、Arm Template 風に書かれた Yaml を実際の Arm Template に変換してくれるツールです。 このツールは、現役のMS社員の方がメインになって開発をしているツールのようです。

ソースコードをビルドして使用する他にバイナリが配布されているのでそれを使用することも可能です。 とりあえずバイナリをダウンロードしましょう。

github.com

使い方

落としてきたファイルを解凍すると、その中に yarm.cmd というファイルがあるのでこちらを実行します。

コマンドのオプションは、以下の通り

  • -i, --input 入力ファイルのパスを指定します。ローカルのファイルパスに加え、Web上のファイルも指定できます。Yaml or Json ファイルは、自動判別されます。(必須)
  • -o, --output 出力ファイルのパスを指定します。指定しない場合は、入力ファイルと同一の場所に返還後のファイルが出力されます。
  • --help ヘルプ
  • --version バージョン表示
Yaml to Json
yarm -i [Yamlファイル]

例えば

yarm -i deploy-function.yaml

とすると、deploy-function.yaml と同じ階層に deploy-function.json が出力されます。

yarm -i deploy-function.yaml -o function-template.json

とすると、function-template.json の名前で変換結果が出力されます。

Json to Yaml

そもそも Yaml のテンプレート自体非公式なものでドキュメントが存在しないので、最初のテンプレート起こしの意味合いなどでも JsonYaml に変換したい場面があるかと思います。 その場合でも簡単に動かせます。

yarm -i deploy-function.json

のようにすると、deploy-function.yaml として出力されます。

yarm -i deploy-function.json -o function-template.yaml

とすると、function-template.yaml として出力されます。 JsonYaml を気にせず使えるのは、とてもありがたいですね。

またコマンド紹介の部分でも書きましたが以下のようにしても動きます。

yarm -i https://github.com/Azure/azure-quickstart-templates/blob/master/101-app-service-certificate-standard/azuredeploy.json

わざわざローカルに落としてこなくても 変換できるので色々な場面で使えそうです。

まとめ

Yaml は、完璧なフォーマットでないので、劇的に ARM Template を扱うのが楽になるかといわれると微妙ですが、Json と比べて 記述量が少なくなったり、コメントを書いてわかりやすくしたりといったことが出来る分扱いやすくなるかなと思います、

興味のある方は、ぜひ使ってみてください。