VS の [チーム エクスプローラー] に一部 DevOps 項目が表示されない

Azure Repos でリポジトリ管理しているソリューションを Visual Studio で開いた際、[チーム エクスプローラー] ウィンドウのホームに、一部 DevOps 作業項目が表示されなくなる場合がある。

f:id:poke_dev:20190810233203p:plain

基本的なリポジトリ作業をする分にはこれでも特に支障ないが、他の DevOps 作業も行いたい場合は、以下の手順を行うことで、[チーム エクスプローラー] ウィンドウに他の項目も表示されるようになる。

手順

  1. VS で、[チーム エクスプローラー] ウィンドウを表示
  2. [接続の管理] アイコン (コンセントプラグのアイコン) をクリックして [接続] ウィンドウを表示
  3. [接続の管理] リンクをクリックして、「プロジェクトに接続」をクリック
  4. [プロジェクトに接続] ダイアログで、当該ソリューションが紐づくリポジトリを選択し、[接続] をクリック

f:id:poke_dev:20190810233806p:plain

Visual Studio で TF30063 エラーが表示された時の対応

Visual Studio + Azure Repos で開発している場合、Azure DevOps で使用しているアカウントのパスワードを変更すると、VS でソリューションを開いた際に「TF30063: [リポジトリへのパス] へのアクセスが許可されていません」と表示される場合がある。

ネットを見るとファイル削除で対応している情報も見かけるが、以下の方法をまず試す。

対応手順

  1. VS で、[チーム エクスプローラー] ウィンドウを表示
  2. [接続の管理] アイコン (コンセントプラグのアイコン) をクリックして [接続] ウィンドウを表示
  3. [接続の管理] リンクをクリックして、「プロジェクトに接続」をクリック
  4. [プロジェクトに接続] ダイアログで、対象アカウントをドロップダウンから選択した際に、「資格情報を再入力してください」リンクが表示されるので、クリック
  5. [アカウントにサインイン] ウィンドウが表示されるので、現在のパスワードを入力してサインイン
  6. [プロジェクトに接続] ダイアログに戻って、当該アカウントの Azure Repos リポジトリ一覧が表示されれば、VS が使用している資格情報の更新は完了している。ダイアログはキャンセルして閉じて構わない。次にソリューションを開いた際は、もうエラー表示はされないはず

Azure Redis Cache 使用時の注意点とか

Azure Redis Cache 使用時のクライアント設定など

基本的にはそこまで気にする必要ないが、.NET アプリの場合はスレッド数調整とか行わないと、タイムアウトなど発生する可能性があるっぽい。

参考

Azure ポータルでの操作等について

  • Redis Cache は Azure のメンテナンスなどで再起動が発生するが、再起動に関する通知やログはない。シングルノードのプランの場合は、接続が切れたとしても問題ない前提で使用する必要がある (= テスト環境のみ利用可)

  • リソース正常性の画面で、「現在、この Redis Cache の正常性を確認できません」的なメッセージが記録されていることがあるが、基本無視で問題ない

Azure Repos と Git についてのメモ

Azure Repos と言うか Git として保存されるログの種別は、以下の 3 つ

  • 全コミットログ
  • プッシュログ
  • コミットログ

このうち、「全コミットログ」については特殊で、Azure DevOps UI からは見えない。 「プッシュログ」も「コミットログ」も最終的には「全コミットログ」を参照しているらしいが、全コミットログに関してはプラットフォームの管理者 (= MS) のみ操作可。

歴史改変について

歴史改変が行えるのは、「コミットログ」のみ。「プッシュログ」は全プッシュログを保持しているので、歴史改変を行って過去のコミットログが削除されたとしても、プッシュログからは辿れてしまう。 (GitHub だと、この「プッシュログ」の画面がないらしく、結果的に歴史改変したら改変後のログにしかアクセスできないらしい)

Clone について

Clone は「コミットログ」のみ持ってくるので、歴史改変された場合は、改変後のログのみ取得する。

Azure Repos で歴史改変したい場合

上述したように、「コミットログ」の歴史改変が行えても、「プッシュログ」から過去のコミットログへアクセス出来てしまうため、意味がない。 それでも歴史改変が必要な場合、既存のリポジトリは捨てる必要がある。必要な作業は、大きく分けると以下。

  1. 当該リポジトリを利用しているユーザーが全て push
  2. 歴史改変を行うユーザーで clone or pull
  3. filter-branch で改変
  4. 改変したコミットログを push
  5. clone --mirror でメタ情報を取得
  6. push --mirror で新しいリポジトリに push (タグ情報もまとめてプッシュされる)
  7. 既存ユーザーには新しいリポジトリを使うように変更してもらい、古いリポジトリは廃棄
参考

Azure Web Apps + ASP.NET での初回ページ表示遅延対策

Azure Web Apps + ASP.NET の組み合わせでシステムを構築した場合、ページの初回表示が非常に遅い現象に見舞われる事がある。 どれぐらい遅いかと言うと、大体 10 ~ 30 秒ぐらい待つ感じ。 主な原因は下記 2 つ。

  • Azure Web Apps の「常時接続」設定がオフになっている
  • ASP.NET のページのプリコンパイルを行っていない

前者の常時接続設定についてはそれ自体も遅延の原因にはなっているが、どちらかと言うと問題のメインは後者のプリコンパイルで、これに常時接続の問題が加わると状況が更に悪化する感じになる。

なお、Web Apps を Azure App Service プランの Basic 以上で作成した場合は既定で常時接続オンになっているはずなので、Free プランで作った環境でなければ常時接続は最初から問題にはなっていない可能性が高い。 以降では、ASP.NETMVC 5 前提とするが、恐らく ASP.NET であれば基本考え方は同じはず。

解決法

Azure Web Apps の「常時接続」設定がオフになっている

Azure ポータルで当該 Web Apps を開き、[構成] - [全般設定] - [常時接続] の設定を「オン」にする。プランが Basic 以上でないとオンを選べないので、注意。

ASP.NET のページのプリコンパイルを行っていない

この問題の解決方法は、デプロイ時に ASP.NET のプリコンパイルを行った状態でデプロイすることだが、デプロイ方法に依存する。

  • Visual Studio の発行を使用してデプロイ

    • 「発行」時に作成するプロファイル設定で、「ファイル発行オプション」の「発行中にプリコンパイルする」にチェックを入れる。但しこれだけだと Global.asax しかプリコンパイルされないため、次の作業も行う

      f:id:poke_dev:20190806225404p:plain

    • 上記項目右にある「設定」リンクをクリックして、「プリコンパイル済みサイトを更新可能にする」のチェックを外す。これを外さないと、各ページのプリコンパイルが行われない

      f:id:poke_dev:20191023205312p:plain

  • Azure ポータルの「デプロイ センター」で、App Service Kudu エンジンを使用してデプロイ

  • Azure DevOps Pipeline を使用してデプロイ (本命)

    • Build の「MSBuild Arguments」に、/p:PrecompileBeforePublish=true を追加する

      f:id:poke_dev:20190806230743p:plain

根本的な要因

根本的な要因は、ASP.NET のページ表示時の仕様に起因している。 ASP.NET では各ページの初回表示時にコンパイルを行い、以降の表示時はコンパイル済みのキャッシュを利用することで、表示時間を短縮している。 この、

  • 各ページの初回表示時にコンパイルを行う
  • 2 回目以降の表示ではコンパイル済みのキャッシュを利用してページ表示を高速化する

の動作は、もともとオンプレミスの ASP.NET でも問題とはなっていたが、Azure Web Apps の仕組みと相性が悪く、Azure Web Apps では発生しやすい状況となっている。 具体的には、Azure Web Apps ではワーカープロセスが様々なタイミングで再起動されるが、そのタイミングでキャッシュファイルが全てなくなるため、ワーカープロセス再起動後は常に「初回表示」状態となり、本遅延問題が発生する。

ワーカープロセス再起動に関しては以下も参考

ASP.NET のページ初回表示時のキャッシュのされ方

キャッシュは、D:\local\Temporary ASP.NET Files 配下に作成される。 D:\local は Azure App Service の一時ストレージで、ワーカープロセス再起動時はここに保存されているファイルは全て消える、ので、ページ表示時に作成されたキャッシュも消える。

なお、App Service のストレージの状況は、ツールでは KUDU でしか見ることができないが、KUDU (= SCM) はそれ自体のワーカープロセス及び一時ストレージを持っており、KUDU で確認できる一時ストレージは KUDU 用の一時ストレージとなる。 つまり、ASP.NET のページ表示時に作成されたキャッシュファイルは、ツールでは通常確認することができない。 この挙動については、アプリ設定で「WEBSITE_DISABLE_SCM_SEPARATION」を追加し、値を「true」に設定することで、KUDU サイトをアプリのワーカープロセス内で実行するように変更できる。 こうすることで、ASP.NET のページ表示時に作成されるキャッシュファイルも、KUDU から確認することができるようになる。

WEBSITE_DISABLE_SCM_SEPARATION アプリ設定は正式にはサポートされていないので、挙動の確認以外では使わないこと

実際のキャッシュのされ方を確認 (常時接続はオン)

プリコンパイルなし

wwwroot/bin 直下の状態。プリコンパイルされたファイルなし。

f:id:poke_dev:20190807234126p:plain

ページの初回表示と、2 回目の表示にかかった時間。初回表示が 15.7 秒、2 回目の表示は 0.2 秒。

f:id:poke_dev:20190807234212p:plain

ページの初回表示後、キャッシュファイルが一時ストレージに作成される。2 回目以降の表示ではキャッシュファイルを使用するため表示が高速化されるが、ワーカープロセス再起動時にファイルはなくなる。

f:id:poke_dev:20190807234355p:plain

プリコンパイルあり (DevOps Pipeline MSBuild での設定)

wwwroot/bin 直下の状態。全ページのプリコンパイル済みキャッシュが、bin に配置されている。ワーカープロセス再起動で消えることはない。

f:id:poke_dev:20190807234527p:plain

ページの初回表示と、2 回目の表示にかかった時間。初回表示は 0.35 秒。2 回目の表示は 0.01 秒。

f:id:poke_dev:20190807234746p:plain

一時ストレージにキャッシュファイルは作成されない。ワーカープロセス再起動で一時フォルダのファイルが消えても、ページの表示速度には影響を及ぼさない。

f:id:poke_dev:20190807234807p:plain

補足

上述したように、Azure ポータルの「デプロイ センター」で自動デプロイ機能を利用する場合、プリコンパイルオプションがないことと、リポジトリにプッシュされる度に自動デプロイが走り、結果ワーカープロセスが再起動する流れになるため、ページ表示速度の面では Azure ポータルでの自動デプロイ機能と ASP.NET アプリはあまり相性が良くない。

開発速度に影響が出そうな場合は、Azure ポータルほどお手軽ではないが DevOps Pipeline を使った方が良いかもしれない。

参考情報

Azure プラットフォームのサービス正常性アラート通知について

Azure プラットフォームでは定期的にメンテナンスが行われ、問題が発生した場合も MS 側で対応が行われるので、利用している側が動く必要は、基本的にあまりない。 但し、例えば障害発生中は Azure 上で動かしているアプリにも影響が出ている可能性があるので、発生している問題の状況については把握しておき、必要に応じて対処する必要がある。

Azure プラットフォームで問題が発生した場合やメンテナンスが予定されている場合などは、Azure モニターのアラート機能を使用して状況の通知を受けることができるので、これを利用する。 基本的にサブスクリプションごとの通知設定が必要となるが、少なくとも、本番環境については通知を受けるようにしておき、状況を常に把握できるようにしておく必要がある。

通知設定手順

  1. 通知設定を行いたいサブスクリプションを Azure ポータルで開く
  2. 左のブレードから、[モニター] - [Service Health] - [正常性アラート] の順にクリックし、[サービス正常性アラートの作成] をクリックする

    f:id:poke_dev:20190803123246p:plain

  3. 「ルールの作成」画面が表示されたら、通知先 (アクション) の情報やルール名などを適宜設定し、ルールを作成する。この際「リージョン」設定については、基本的に全項目を選択しておいた方が無難と思われる (特定リージョンのみで全リソースが作成できることなどほとんどないため)

    f:id:poke_dev:20190803123316p:plain

  4. 当該サブスクリプションに影響する問題が発生した際などに、以下のようなメールが送信される

    f:id:poke_dev:20190806213702p:plain

参考

Actions on Google アプリへの Welcome Intent 定期アクセスについて

少し前から Actions on Google アプリに対して、8 分前後の間隔でヘルスチェック目的のクロールが行われるようになったようで、Dialogflow の History を見ると、GOOGLE_ASSISTANT_WELCOME の呼び出しが定期的に発生していることが確認できる。

Default Welcome Intent で Fulfillment を使っていなければ実害はないが、Webhook 呼び出しを行っている場合、この定期的なヘルスチェックで Webhook 側のリソースが消費されることになってしまう。

このヘルスチェックに対してコストをかけて処理するのは馬鹿らしいので、Webhook 側ではヘルスチェックによるリクエストかを判断して、ヘルスチェックに対しては可能な限りコストをかけずにレスポンスを返して処理を終わらせるのが良いかと思われる。

2019/8/1 現在、Webhook に渡される Json は以下のようになっている。「Crawler」とか「is_health_check」が判定に使えそうな感じ。

{
  "responseId": "xxxxx",
  "queryResult": {
    "queryText": "GOOGLE_ASSISTANT_WELCOME",
    "action": "input.welcome",
    "parameters": {
    },
    "allRequiredParamsPresent": true,
    "fulfillmentText": "知りたい仮想通貨を教えて下さい。",
    "fulfillmentMessages": [{
      "platform": "ACTIONS_ON_GOOGLE",
      "simpleResponses": {
        "simpleResponses": [{
          "textToSpeech": "知りたい仮想通貨を教えて下さい。"
        }]
      }
    }, {
      "platform": "ACTIONS_ON_GOOGLE",
      "suggestions": {
        "suggestions": [{
          "title": "BTC"
        }, {
          "title": "ETH"
        }, {
          "title": "LTC"
        }, {
          "title": "BCH"
        }, {
          "title": "XRP"
        }]
      }
    }, {
      "text": {
        "text": ["知りたい仮想通貨を教えて下さい。"]
      }
    }],
    "outputContexts": [{
      "name": "projects/xxxxx/agent/environments/__aog-4/users/-/sessions/xxxxx/contexts/actions_capability_audio_output"
    }, {
      "name": "projects/xxxxx/agent/environments/__aog-4/users/-/sessions/xxxxx/contexts/google_assistant_input_type_voice"
    }, {
      "name": "projects/xxxxx/agent/environments/__aog-4/users/-/sessions/xxxxx/contexts/google_assistant_welcome"
    }],
    "intent": {
      "name": "projects/xxxxx/agent/intents/xxxxx",
      "displayName": "Default Welcome Intent"
    },
    "intentDetectionConfidence": 1.0,
    "languageCode": "ja"
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "user": {
        "userId": "xxxxx",
        "profile": {
          "givenName": "Google",
          "familyName": "Crawler"
        },
        "locale": "ja-JP"
      },
      "conversation": {
        "conversationId": "xxxxx",
        "type": "NEW"
      },
      "inputs": [{
        "intent": "actions.intent.MAIN",
        "rawInputs": [{
          "inputType": "VOICE",
          "query": "仮想通貨チェック"
        }],
        "arguments": [{
          "name": "is_health_check",
          "boolValue": true,
          "textValue": "1"
        }]
      }],
      "surface": {
        "capabilities": [{
          "name": "actions.capability.AUDIO_OUTPUT"
        }]
      }
    }
  },
  "session": "projects/xxxxx/agent/environments/__aog-4/users/-/sessions/xxxxx"
}
参考