tech.guitarrapc.cóm

Technical updates

AzureFunctions におけるStorageTableBindingの選択

Azureには、Storage Table という機能があり単純なテーブル形式でデータが管理できます。 C# のコード的にはTableEntity を継承してデータを表現し、メソッドを組み立ててCRUDを実現できます。

さて、このStorage TableをWebJobs 並びにその実装の1つである Azure Functions で使うときについて考えてみましょう。

目次

TL;DR

Bindingには複数の課題があり、特にStorage Table や DBのバインディングはパラメーターが多く問題が起きやすい。

  • Bindingは属性 + エントリーポイントのエラーのためStackTraceが取れないケースが多い
  • 加えて Binding のリフレクション多用による難しさ
  • StorageTableとDBは特にパラメーターが多くBinding問題に当たりやすい
  • NuGetバージョンのRegression耐性の乏しさ

このため、Storage TableやDBに関してはBindingはやめてFunction内でクライアントを自前で作って安定させるほうが良いケースが多い。

他のHttpTriggerやQueue などはシンプルでトラブルが起こりにくく、自前で定型的に実装することになる機能がサポートされるので使ったほうが楽なBindingと感じます。

Binding にまつわる実装とデバッグの距離

自前コードからの呼び出し口があれば、デバッガを仕掛ける口があり距離としては近づきます。 しかし、Bindingは初期化時に起こるためデバッガとの距離が遠いという、作り上の潜在的な課題があります。

どこまで簡単にデバッグできるかはかなり重要な選択基準になるので順に見てみましょう。

属性 + エントリーポイント

Bindingは属性 + エントリーポイントのエラーのためデバッガを仕掛ける口がFunctionに開いていません。そのため、シンボルをVSで捕まえられるかが容易なデバッグの鍵となりますが、残念ながら多くの場合機能しないことが多いです。

例えば、Microsoft.Azure.WebJobs.Extensions.Storage が 3.0.1のときはシンボルも取れず デバッガはおろかStackTraceも取れません。

Microsoft.Azure.WebJobs.Extensions.Storage が 3.0.1 でTableAttributeBinding でトラブル時は厳しい状況になる

→ Microsoft.Azure.WebJobs.Extensions.Storage 3.0.1から3.0.5に上げることでデバッガがソース内部まで行くように改善されます。(Bindingで落ちることに変わりはない)

この状況は、Bindingのパラメーターを間違えれば容易に再現できるので、エラーから状況を把握することを試してみてください。

リフレクションの多用とRegressionの多さ

WebJobs の Binding はリフレクションを多用しており、些細なミスで機能しないことが起こりえます。

これが顕著なのが、NuGetライブラリバージョンに依存したBinding失敗でバージョンを上げると同一コードで動かないといったRegressionが過去に何度も起こっており、また現在も起こりえます。

例えば、AzureFunctions で利用するプロジェクトが複数あったときに、Microsoft.Azure.WebJobs.Extensions.Storageの下限バージョンとWindowsAzure.Storage バージョンでミスマッチを起こしているとBindingエラーが起こるようになります。(BindingRedirect ではなく、です..... )

この問題は、気づけるなら大したことはありませんが、エントリーポイントのBindingでのエラーのため、自分のパラメーターミスが原因だった時と同じエラーでもあるため気づくのはこんなんです。

結果として、Bindingでのトラブルは解消するのが難しい側面があります。

NuGetバージョンに起因した問題の再現方法

2つプロジェクトを用意し、片方はAzure funcitons のプロジェクトで Microsoft.Azure.WebJobs.Extensions.Storage を入れておきます。(例では3.0.1) もう片方は、WindowsAzure.Storage をNuGetで入れます。(何も考えずにいれると 9.3.3 が入ります)

AzureFunctions で TableBinding をかけて実行すると、即落ちてTableAttributeBindingProvider..cs がないためデバッグがその先のコードにたどり着けません。

TableAttributeBindingProvider..cs がないために落ちた箇所がわかりにくい状況

おもむろに NuGet Package Manager を見ます。最新バージョンの Microsoft.Azure.WebJobs.Extensions.Storage (例は3.0.5) が出ているのでアップグレードします。

Microsoft.Azure.WebJobs.Extensions.Storage を最新版に上げる

コードの変更なく、先程のBinding時のエラーがリフレクションでとってきた tableAttributeのRowKey が null であるためにコケるように変わります。

ライブラリ側のコードで落ちる箇所が表示されるようになる

AzureFunctions プロジェクトの Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5 の依存している WindowsAzure.Storage のバージョンが >= 9.3.1 となっています。

Microsoft.Azure.WebJobs.Extensions.Storageの依存するWindowsAzure.Storageバージョンの確認

依存プロジェクトの WindowsAzure.Storage を 9.3.3 にしていました。

依存プロジェクトのWindowsAzure.Storageに引きずられてアプリの利用バージョンが変わる

そこで依存プロジェクトの WindowsAzure.Storage を 9.3.1 に変更します。

Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5が依存するWindowsAzure.Storageの最小バージョン9.3.1にダウングレードして揃える

AzureFunctions プロジェクトの Microsoft.Azure.WebJobs.Extensions.Storage 3.0.5 が依存している WindowsAzure.Storage も 9.3.1 を見るように変わったことがわかります。

Microsoft.Azure.WebJobs.Extensions.Storage が参照するWindowsAzure.Storageが切り替わったことを確認

コードを何も変更してないけど正常に動くことが確認できます。

問題の回避方法

今、このBinding問題が起こっている方は、多くの場合、適切なバージョンの選択により問題が回避できます。(パラメーターが正しい場合ですが)

Microsoft.Azure.WebJobs.Extensions.Storageの利用しているWindowsAzure.Storage下限バージョンに合わせる

現在 Storage に対するNuGetライブラリは、WindowsAzure.Storage から Microsoft.Azure.Storage.Xxxx への移行途中にあります。特にStorage Tableは影響度が高くしばらくは9.3.1、正しくは Microsoft.Azure.WebJobs.Extensions.Storage の依存しているバージョンに従いましょう。

依存ライブラリに注意する

NuGetバージョン問題は、依存ライブラリになっている場合は個別ライブラリのバージョンに引っ張られる、というNuGet Packageの特性により「自分の気づかない内にバージョンが変わっていた」という状況が起こりやすいようです。

特に、プロジェクト参照していると依存プロジェクトのWindowsAzure.Storageバージョンに引きずられてバージョンが狂ってより死ぬ。という。

Bindingをやめる

そもそもBindingをやめるのが、デバッガビリティ、ライブラリのバージョンに起因したRegressionから簡単に逃げる選択肢になりえます。

つまり、自分でFunction内部でクライアント(CloudTableClientなど) を生成することで、安定した利用ができるでしょう。特にパラメーターが多く問題が起きやすいTableBinding と DB Binding は Binding を行わないのは選択肢となりえます。残念ですが。

複数の方が同じ結論でクライアントを自分で生成されているようです。

Q&A

Table Storage のBindingは空テーブルとか作ってくれて楽なんだけど

Bindingで行われる存在しないTable作成などはTerraformなどの静的な構成管理で担保もできるので、アプリケーションだけでなく全体像でハンドルもありですしコードで担保してもいいでしょう。

Microsoft.Azure.Storage.Xxxx やWindowsAzure.Storage 9.4.xを使い始めるタイミング

Microsoft.Azure.WebJobs.Extensions.Storageに依存しましょう。なお、Storage Tableは 9.4.0 から WindowsAzure.Storage から除外されてCosmosDB ライブラリに移行します。Microsoft.Azure.WebJobs.Extensions.Storageのバージョンが9.4.xを使うまですぐに移行せずに少し様子を見たほうが良さそうです。

https://tech.guitarrapc.com/entry/2019/01/24/041510

BindingでMSIどうなってるの?

Storage Binding はMSIによる認証にも対応していないなど、クラウド側の更新への追随がまだ未整備という側面もあります。

Inboxからのメールクライアント移行

Inbox by Gmail が2019年3月31日で終わりますというアナウンスが流れて半年、ついに4/1です。

じつはまだログインできますがじきにできなくなるでしょう。(4/1 23:00 9 tile からは Inboxが消えました)

メールクライアントをInboxから何に乗り換えたかについてと、その経緯についてメモします。

目次

TL;DR

メールの重要度は下がったもののなくすことは、個人、仕事の両面で不可能なので割り切ります。

自分が欲しい機能を絞ると明確に移行しやすい説があります。

ツールに合わせて欲しい機能を見つめ直し、フローを変えていくのは、知らなかったより良い方法の発見にもなるので今後も基本姿勢にしたいところです。

Webでのメールは使わないフローにしたので、問題になりそうなら課題として解決していくつもり*1

私にとってメールは、「常に受信箱は空に」して、「即時処理(できない場合はスヌーズ)する」、「メールの検索用にタグで分類はするが自動フィルタはかけない」、「不要なメールはそもそも受け取らない、どんどん減らす」のが現状適切に運用の回る方法のようなので、それに適した機能をもったクライアントに移行しました。同じような方にとって参考になれば幸いです。

結論

Edison Mail に移行しました。

mail.edison.tech

アナウンスから半年、Inboxからの移行先を探していたものの見つからず迷っていたましたがアプリに合わせてやり方を修正して移行しています。

変更点は、次のとおりです。

  • メール処理をWeb/スマホの両方で行うのではなく、スマホのみで行うように変更
  • 受信だけはGmailでみることもあるけど、基本的にGmailはアカウントとPCで見る必要があるときだけに割り切り

Inboxで有効に働いた機能

メールを処理するにあたり欲しい機能は、Inbox by Gmail がそのままでした。 もともとOutlook.com で3年ほどメールの確認、振り分けに苦労しており、4桁~メールが常時たまっていました。さらにGmailが加わって過去メールが処理しきれなくなっていい加減捨てるなり継続的に解消できる状況が必要と重い腰を上げました。この状況を改善して、常に未読がない状況を維持できたのはInboxのおかげでした。一晩かけて移行して、その後未読がない状況が維持できたことに感謝して使っていました。

Inboxが有効に機能した理由は次の通りです。

  • 見たときの情報量の少なさ*2
  • メールをフィルターで自動移動させない*3
  • メール通知のスヌーズ機能*4
  • メールの自動的なグルーピング(マネー、ショッピング、トラベル)*5
  • チェックボタン1つでの受信ボックスからの排除*6
  • チェック横からの左クリックのみでタグ移動できる容易さ*7
  • スマホでも同様の操作が提供されている*8

メールの横のチェックボタンやメニューからの移動が最高だった

メールは一日朝と夜の2回しかみたいのですが、メールが来たら次のフローをたどります。

  • プロモーションならまとめて削除。頻度が高すぎるもの、内容によってはメール受信自体を見直し
  • マネーなら入出金確認してチェックぽち
  • Amazonなどで購入したらShoppingに来るのでチェックぽち
  • ほかの自動分類されなかったものはタグをつけてチェックぽち
  • 受信ボックスが何もなくなって終了

見る場所を一か所に限定して、常に受信ボックスがなくなる状態を維持するだけです。

Inboxでのメール表示

Inboxはスマホにおいても同様のUIを提供しており快適度が変わらないのは良かったです。

移行にあたりチェックする機能

Inboxの運用から、今後メールを使っていくにあたり不要な機能がはっきりします。

  • メールのフィルターはしないので不要
  • メールの自動的なグルーピング(マネー、ショッピング、トラベル)はタグで行うため不要
  • チェックボタン1つでの受信ボックスからの排除は、タグ移動で済むので不要
  • クライアントは不要
  • WebもあってるものがなければGmailでテンポラリでみるだけでいいので不要

結果、必要な機能が絞られます。

  • メール通知のスヌーズ機能
  • ワンアクションで既読、タグ移動、削除、スヌーズができる
  • メールアカウントが別れてハンドルできる (まとまってもいいが別れて制御はほしい)

あとは、見た目です。

  • 見たときに情報量が少ない
  • 個別のアカウントにアクセスができる
  • タグ別に確認ができる
  • 動作がスムーズ

残るは動作環境です

  • WebがなければGmail を一時的な閲覧限定で利用
  • Windows/macOS 両方で使うため、クライアントアプリはなし
  • スマホ > Web の利用頻度/優先度のため、Webがないようならスマホアプリをメインに変更 (みるのはiOSのみ)

スマホでは動作のセキュリティと操作を重要視します。

  • FaceId などのセキュリティが組み込まれている
  • バッジ通知が正確(PUSH通知はこなくてもいい)
  • タグ移動で候補がでると最高だがなくてもいい

移行先候補

スマホをメインに移行するかは、Gmail次第です。

Web

  • Gmail : 次節で確認
  • Outlook : そもそも長年Outlook.comを使っておりありえないぐらい使いにくいので全面的やめるように調整済みです

クライアントアプリ

PCでメールのためにアプリをいれたくない、UI/UXが大きく異なるため使いません。

スマホアプリ

  • Gmail
  • Edison Mail
  • Spark
  • Outlook : アプリは比較的悪くないのですがここでは除外 (ヒドイ

移行候補の検証

Gmail

移行先として真っ先にあがるのがGmail です。InboxもGoogleも移行先に推奨しています。

Google 自身がいう、Gmailでの移行は次の内容ですが、ちがうそこじゃない。トップへのピン止めなどせず、さくっと消したいのです。タグでカテゴリわけなどせず見る場所を一か所にとどめてタグは分類置き場にしたいのです。スマートリプライは、相手様が丁寧すぎて使えないケースがあり日本語むずかしい。

support.google.com

Gmailのデフォルト環境では、メールの受信がタブごとに別れます。タブを使った操作は見る場所が複数に散るためかなり好ましくないと判断しました。

Gmail のタブを使った状態

タブをなくしましょう。

メールボックスを一つにした状態

見る場所が一箇所になり良さそうです。

タブをなくしたGmail表示

機能はInboxと同等にあるので、あとは見た目と操作の好みです。順にみてみましょう。

要件 結果
見たときの情報量の少なさ 一メールあたりの送信者、受信本文の量が多くうるさい。カテゴリが折りたたまれておりアクセスが煩雑
メール通知のスヌーズ機能 あります。メールをホバーすると表示とスヌーズが表示
ワンアクションで既読、タグ移動、削除、スヌーズができる アーカイブを使うことで受信ボックスから消えます。削除もあります。しかしタグ移動ができません

スヌーズはok です。

Gmailのスヌーズ

受信ボックスからの排除もアーカイブでokです。

アーカイブ

問題となるのが、タグ移動とメールの自動的なグルーピングです。 Gmail でグループしようと思った場合にやるのが、前述の受信ボックスのタブ、あるいはフィルターでタグ移動ですが両方使いません。 受信ボックスのメールがグループ化されればInboxにほぼ近くよかったのですが、タブで見る場所が分かれるのはナシなのであきらめましょう。

フィルタをしない

手動でタグ移動は別にいいのでやろうかと思いましたが、右クリックから移動はたった1つ動作が増えるだけですがナシです。全メールでやってられないです。

タグの移動は右クリックになる

やりたいことが明確になりましたが、Gmail ではInbox ほどフローの省力化はできないようです。

受信ボックスを限定して、フィルタなどを使わず見る必要のあるメールだけを受け取り、素早く分類して、メールボックスを常に空にする。

見た目がうるさいのはあきらめましょう。

この時点で、Webでのメール処理は現時点でいい方法がないと判断し、割り切ってスマホでの完結を目指します。

Gmail iOSアプリ

スマホアプリはかなり残念です。

スワイプでアーカイブは提供していますが、スヌーズやタグ移動などは個別でメールを開いて処理する必要があります。

スマホではワンアクションで必要な機能をスムーズに呼び出したいため、メールのスワイプ動作をカスタム設定できれば楽なのにという感じはあります。

アカウントが分かれるのは別にいいのですが、統合されたメールボックスの利便性はスマホアプリではWeb以上に感じます。

この時点でGmailのスマホアプリはなしです。

Edison Mail

mail.edison.tech

一通り機能が網羅されています。動作が軽快で、TouchIdがあり通知が正確なのが良さです。

残りは、タグ移動時のスマートな提案がほしいところです。

スワイプの動作がカスタマイズできるため、これらはすべてワンアクションで実行できます。

メール通知のスヌーズ機能 ワンアクションで既読、タグ移動、削除、スヌーズができる

Edison mail でスワイプ操作のカスタマイズ

また、メールアカウントを複数登録したときに、分けて表示もできますが統合メールボックスでまとめてみることもできます。

メールアカウントが別れてハンドルできる (まとまってもいいが別れて制御はほしい)

個別のアカウントにアクセスができる

メールのプレビュー行数を1行にできるので見た目もシンプルです。

見たときに情報量が少ない

メールアカウントを選択するとタグ別に確認もできます。

タグ別に確認ができる

動作もキビキビしており快適です。

動作がスムーズ

FaceId にデフォルトで対応しており嬉しさがあります。

FaceId などのセキュリティが組み込まれている

起動時にFaceId認証

バッジ通知が正確で、新規通知時にもバッジのカウントがずれないので良さがあります。

バッジ通知が正確(PUSH通知はこなくてもいい)

タグ移動時に移動候補はでないので、そこは残念です。

タグ移動で候補がでると最高だがなくてもいい

メールの青ポッチをスワイプで既読にできるのも楽です。

Spark

sparkmailapp.com

こちらも一通り機能が網羅されています。動作が軽快で、スマートな通知があり通知が正確なのが良さです。統合メールボックスもアカウントが適切に出て良さ味があります。

残りは、TouchIdがあれば嬉しいのと、アプリの動きが遅いのが気になります。

スワイプの動作がカスタマイズできるため、これらはすべてワンアクションで実行できます。

メール通知のスヌーズ機能

ワンアクションで既読、タグ移動、削除、スヌーズができる

Sparkのスワイプ設定

また、メールアカウントを複数登録したときに、分けて表示もできますが統合メールボックスでまとめてみることもできます。 この統合メールボックスが、スマートアカウントというだけあってメールアカウントがチラっとグループされていたりしてEdison Mailより便利です。

メールアカウントが別れてハンドルできる (まとまってもいいが別れて制御はほしい)

個別のアカウントにアクセスができる

メールのプレビュー行数は1行で見た目もシンプルです。

見たときに情報量が少ない

メールアカウントを選択するとタグ別に確認もできます。

タグ別に確認ができる

動作は少し重く、時々起動しても真っ白だったりするのは難点です。

動作がスムーズ

パスキーロックには対応していますが、FaceIdには対応していません。パスキーロック嫌いなので残念です。

FaceId などのセキュリティが組み込まれている

バッジ通知はは、新規通知時にもバッジのカウントがずれたりします、既読してもバッジが残ったりするので残念です。

バッジ通知が正確(PUSH通知はこなくてもいい)

タグ移動時に候補はでるのはかなり嬉しいです。便利。

タグ移動で候補がでると最高だがなくてもいい

他に、メールの委任などチームで共有してメール編集できるのが便利です。

Edison か Sparkか

チーム機能を使わないので、機能的にはほとんど変わりません。

このため、動きの機敏さとFaceId とバッジの正確さで Edison Mail を選択しました。

とはいえ、まだ移行完了して2週間なので Edison Mail と Spark の療法をインストールして試しています。

Webは予定どおりメールを見ることがなくなり、何かしらのアカウント作成時のVerify などに限定されていて良い感じです。

移行していて思いましたが、メールに求めることがみんな違うのでいろんな代替案があるのは最高なので、多様な選択肢があることに感謝です。

移行後のフローは、自動振り分けがないので順にやるだけです。この意味でInboxかSpark並みの振り分けがほしい

  • プロモーション系は即削除。頻度が高すぎるもの、内容によってはメール受信自体を見直し
  • マネーなら入出金確認してタグ移動
  • Amazonなどで購入したらタグ移動
  • ほかの自動分類されなかったものはタグ移動
  • 受信ボックスが何もなくなって終了

Edison への移行

Inbox からの移行で困ったのが、Shopping や Money といった自動振り分けはタグではなかったことです。つまり他のメールクライアントでは認識できず受信メールにまとめて表示してしまいます。

せめてInboxからまとめてタグ付けできればよかったのですが、Inboxでメールを複数まとめて振り分け操作はできません。 Gmailを見ても内部的に受信メールを振り分けてくれただけです。

このため、移行後のタグをShopping、Financeと作り、Inboxでちくちく移動させました。しかしGmailで受信メールのタグをが残ったのであとはぺちぺち「タグ移動」。

これで移行完了です。さしみたんぽぽ。

*1:今の所問題なさそう。

*2:メールを見たときの大量の情報はストレスとなりやる気を削ぎます

*3:見る場所を一か所に絞ることで見る場所を探すストレスを下げます

*4:今見たくない、でも後からだと忘れそう、というストレスを解消します

*5:特に無駄なプロモーションをまとめて消せる

*6:操作一つにストレスを受けたくない

*7:移動めんどくさいのストレスを減らす

*8:環境問わず同じ操作ができるストレスのなさ

Deployment Groupを用いてManagedにVMへのデプロイを行う

現状、サーバーサイドの多くはコンテナで動かすことが可能です。そのため、VMに直接アプリケーションをデプロイする機会はかなり減りました。最高ですね。しかし、UWPをはじめとして一定の要件下においてはコンテナ対応が技術的に難しく、VMへのデプロイをせざるを得ないケースがあります。

VM へのデプロイ、古くからおこなわれていますがクラウドネイティブにマネージドにとなると、案外手が限られます。なるべくクラウド側の意図しているであろう方法に沿うように組むのは大事な選択です。

今回は、Azure のVMに対してアプリケーションをデプロイするにあたり、Azure DevOps の Deployment Group を使ってマネージドさを見ながら実現してみましょう。

目次

TL;DR

Azure DevOps Pipeline を使うと、VMへのデプロイ時に動的にDeployment Targetを設定できます。 DevOps Pipeline を使わず、Terraform でVM作成時にDeployment Targetを設定することも可能です。

動的な構成ならAzureDevOps Pipeline 、静的な構成ならTerraform がよいのでオススメです。 いずれの方法でも、VMへのDeployment Targetのインストール/アンインストールがマネージドにハンドルされるので、なるべくこちらを使うといいでしょう。いずれもにしても、「VMにリモートログインしてマニュアルでDeployment Targetをインストールする」のはなるべく避けるのは大事です。

なおDeployment Group を用いるということは、VM Scaleset とは別のコンセプトになるので混ぜるな注意です。

概要

AWS においてVMへのデプロイとなると、Code Deploy を用いてCode Pipeline からの Managedなデプロイが定番です。VMに対してCI/CD側からパッケージを指定して、VM上のエージェントが指示された通り展開するわけです。

これと同様の仕組みをAzure で達成するのが、Azure DevOps のDeployment Group / Deployment Target です。 本稿では、ホストとなるVM群に対して、デプロイ時に停止したVMの開始、Deployment Targetでデプロイを実施、というコストも鑑みつつよくある感じの流れを見てみます。

Deployment Target とは

Deployment Target は、Azure DevOps の用語でデプロイ対象となるホスト(Windows/Linux) をさします。Build Agent と同様に、Deployment Agentがホスト上でサービス稼働し、Azure DevOps でDeployment Group として複数のDeployment Targetがグルーピングさて、生存確認や最終リリースを確認できます。

Deployment Targetは、Azure DevOps Pipeline からのデプロイ指示を受けてデプロイタスクが実行されます。(Build Agent同様ですね) どんな処理を行うかは、DevOps Pipeline 上でタスクとして定義、実行/停止指示ができ、Deployment Targetが入っているホスト上でその処理が直接実行されます。

WinRM/SSHなどを通したリモート展開と比べて考えることが少ない一方で、エージェントを展開しておく必要があります。とはいえ、AzureDevOps的にはDeployment Groupが処理の単位のため、デプロイ前にDeployment Groupを経由してホストの生存が確認できればデプロイに支障がありません。つまり、デプロイ前にインストールされていなくてもデプロイ時にDeployment Agent を動的に展開することで、ホストへの事前インストールを気にせずにデプロイに集中できます。

Use deployment groups in a release pipeline - Azure Pipelines | Microsoft Docs

Deployment Group の作成

Deployment Agent を作成する前に、デプロイ対象をグルーピングする単位である Deployment Groupを作成しておきましょう。

このあたりはドキュメントの通りに進めれば問題ありません。

Use deployment groups in a release pipeline - Azure Pipelines | Microsoft Docs

Azure DevOps で Pipelines から Deployment groups を選択しましょう。

Pipelines > Deployment groups を選択

Deployment Group 一覧が表示されるので、+ New で追加しましょう。

Deployment Group の追加を行う

Azure あるあるですが、ここでつける Deployment Group の名前が Deployment Agentと直接紐づくため重要になります。(つまり変更が難しい)

Deployment group name を付けるのは慎重に

今回は、Hogemoge-Dev-Deploy という名前でDeployment Groupを作成します。

作成したDeployment Group を開くと、次のようにエージェントを展開するための設定が表示されます。

PowerShellやBash でエージェントをインストールするコマンドが表示されている

ポイントはPersonal Access Token で、Deployment Agent が Azure DevOps と通信するときには現状必ずPATが必要です。このあたり、AWSのIAMのような統合ができておらず、本当にイケテナイです。PAT期限がることもあり、なかなか本当に厳しい。

Authenticate with personal access tokens - Azure DevOps | Microsoft Docs

Deployment Agent の展開

さて、AWS の Code Deployの場合は AWS提供ホストには事前にインストールされているのでインストールを考える必要はありません。

CodeDeploy とは - AWS CodeDeploy

しかし、Azure提供ホストには Deployment Agentが事前にインストールされていません。Deployment Agentを展開するには2つの方法があります。

  1. Deployment Agentを手動で展開する
  2. VM拡張を使って展開する

Agent の展開に必要なPersonal Access Tokenの権限

インストールの前に、罠となるPATの権限について触れておきます。Deployment Group Agent の展開に関するドキュメントを見ると、エージェントのインストールには PATを利用するとあります。

Deploy a build and release agent on Windows - Azure Pipelines | Microsoft Docs

ドキュメントにはDeployment GroupsRead & manage権限が必要とありますが、実際にエージェントを展開するときはこの権限だけでは足りず、「Deployment Group が見つかりません」と怒られます。

Deployment Groups の Read & manage権限をつける

追加で、Project and TeamRead 権限をPATに与えてあげましょう。

Project and Team の Read 権限をつける

ではインストールを見ていきます。

Deployment Agentを手動で展開する

この記事の主題ではないので手動展開は無視してokです。しかし、Azure VMではなく、オンプレミスのホストにインストールする場合は手動で展開したいでしょう。その場合は、次の公式ドキュメントを見ればいいです。ただこの方法は、ホスト構成時に手作業やAnsibleなどでの余計な構成が必要となるため、Azure VM が対象の場合は仕方ないとき以外は受け入れたくはない選択肢です。

Deploy a build and release agent on Windows - Azure Pipelines | Microsoft Docs

マニュアルで展開する場合は、Use a personal access token in the script for authentication を有効にして、Deployment Groupの展開PowerShell(Windows)/Bash(Linux) をコピペします。

当初、軽く流すつもりでしたがドキュメントとPowerShellスクリプトの両方に対応できていないトラップがあるので、簡単に触れておきます。自動生成されるPowerShellスクリプトは、--auth と --token 引数が間違っているため、これを削除して実行する必要があります。幸い、Auth と Tokenは実行中に聞かれるので、クリップボードにコピーした値を入れなおしましょう。*1

こんな感じになります。タグは何もなしでok、アカウントもデフォルトでokです。

修正したコマンドで実行する

これでDeployment Target が登録されます。おおむね、Build Agent と一緒です。

Deployment Targetが追加された

VM拡張を使って展開する

手動でインストールをしたくない時に、Azure がVMに対してマネージドに何かしらの処理を差し込むために提供している方法が「VM Extensions (VM拡張) 」です。

Windows 用の Azure VM 拡張機能とその機能 | Microsoft Docs

Azure DevOps の Deployment Agent も VM拡張が提供されており、ドキュメントも提供されています。この方法であれば、VM拡張の導入を定義をするだけで、Deployment Agentのインストール作業はマネージドに行われます。この記事は、マネージドを意図しているのでこちらを利用しましょう。

Provision agents for deployment groups - Azure Pipelines | Microsoft Docs

余談ですが、先ほどの手動エージェントインストールは Concepts-> Agents > Self-hosted <OS> agents にあり、VM拡張でのインストールは Concepts > Releases > Provision Deployment Agents にあるのなんというかドキュメントの作りが残念で実際にたどり着けてない人が周りに多いのでまずそう。

VM拡張でのDeployment Agentの展開

さて、一口にVM拡張でのDeployment Agentの展開といっても方法は2つあります。

  1. VMを構成時にDeployment Agentを展開する
  2. デプロイ時にResource Group単位でホストにDeployment Agentを展開する

それぞれどう違うのでしょうか。

もし、Resource Group内でVM が1つのDeployment Groupにまとめてokなら、「デプロイ時にResource Group単位でホストにDeployment Agentを展開する」を選択するのがオススメです。この方法なら、デプロイ時に自動的にエージェントが展開されて、VMの構成とデプロイが完全に分離されるだけでなく、Deployment Targetの管理が完全にマネージドになります。

一方で、Resource Gorup内の対象VMごとにDeployment Groupを分けたい場合は、構成時にDeployment Agentを展開する必要があります。TerraformやPlumiなどどの方法を使っても、VMに対してエージェント展開を管理する必要があるので避けたいところです。

順番にそれぞれの展開を見てみましょう。

VMを構成時にDeployment Agentを展開する

Terraform のModuleでさくっとやります。Windows を例にしますが、LinuxでもWindowsでも変わりません。

resource "azurerm_virtual_machine" "main" {
  name                          = "${var.name_prefix}-vm"
  location                      = "${var.location}"
  resource_group_name           = "${var.resource_group_name}"
  network_interface_ids         = ["${azurerm_network_interface.main.id}"]
  vm_size                       = "${var.vm_size}"
  delete_os_disk_on_termination = true

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2016-Datacenter"
    version   = "latest"
  }

  storage_os_disk {
    name              = "${var.name_prefix}-os-disk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "${var.managed_disk_type}"
    disk_size_gb      = "${var.os_disk_size_gb}"
  }

  os_profile {
    computer_name  = "${var.computer_name}"
    admin_username = "${var.vm_username}"
    admin_password = "${var.vm_password}"
  }

  os_profile_windows_config = {
    provision_vm_agent = true

    winrm {
      protocol = "HTTP"
    }
  }

  tags = "${var.tags}"
}

resource "azurerm_virtual_machine_extension" "main" {
  name                       = "TeamServicesAgent"
  resource_group_name        = "${data.azurerm_resource_group.current.name}"
  location                   = "${var.location}"
  virtual_machine_name       = "${module.assetgenerator.vm_name}"
  publisher                  = "Microsoft.VisualStudio.Services"
  type                       = "TeamServicesAgent"
  type_handler_version       = "1.0"
  auto_upgrade_minor_version = true
  tags                       = "${local.tags}"

  settings = <<SETTINGS
    {
        "VstsAccountName": "${var.VstsAccountName}",
        "TeamProject": "${var.TeamProject}",
        "DeploymentGroup": "${var.DeploymentGroup}",
        "AgentName": "${module.assetgenerator.vm_name}"
    }
SETTINGS

  protected_settings = <<SETTINGS
    {
        "PATToken": "${var.PAT_DEPLOYMENT_AGENT}"
    }
SETTINGS
}

適当にvariables は公開してもらうとして、azurerm_virtual_machine_extension を使うことでVM作成時にDeployment Agent が自動的に展開されて、Deployment GroupにTargetが登録されます。手動での展開に比べて、圧倒的に楽なうえに自動展開できます。

最も重要なのは、azurerm_virtual_machine リソースの os_profile_windows_config にある provision_vm_agent = true です。(os_profile_linux_configにはない) 有効でないとVM拡張が利用できず、デフォルトが false なので項目が抜けないようにします。provision_vm_agentが有効化できるのは「VM作成時」だけなので作ってから気づいても手遅れです。

Azure Resource Manager: azurerm_virtual_machine - Terraform by HashiCorp

デプロイ時にResource Group単位でホストにDeployment Agentを展開する

Azure DevOps Pipelines のリリース時に、Resource Group内のVMに対してDeployment Agent をインストールします。この方法を使うと、デプロイの流れの中で対象が動的に定まるため、スケールアウト時も考慮不要になります。

リリースパイプラインのタスクを見てみましょう。

リリースパイプラインのタスクはAgent Job と Deployment group job で分けるのがコツ

ポイントは、「Build Agent」と「Deployment Group」の処理が分かれることです。実際のデプロイは、Deployment Group単位で実行されますが、そのデプロイ先をBuild Agent で動的に作成します。

Build Agent でやることは2つです。

  • 停止したVM のStart
  • VM Extensions 経由で Deployment Agent のインストール

YAML を見ていきましょう。

「停止したVM のStart」は、AzureResourceGroupDeployment を使うことでstart actionでAzure DevOps から実行できます。ポイントは、Resource Group単位の処理になります。止めたままにしたいVMとか混じるのだめ。融通があんまり利きません。

steps:
- task: AzureResourceGroupDeployment@2
  displayName: 'Azure Deployment:Start action on VM'
  inputs:
    azureSubscription: YOUR_SUBSCRIPTION
    action: Start
    resourceGroupName: 'YOUR_RESOURCE_GROUP'

「VM Extensions 経由で Deployment Agent のインストール」もAzureResourceGroupDeployment を使うことで、VM拡張でインストールされます。先ほど作ったDeployment Groupを特定できるように AzureDevOps のチーム名、Deployment Group名を指定しましょう。

steps:
- task: AzureResourceGroupDeployment@2
  displayName: 'Azure Deployment:Select Resource Group action on ResourceGroup'
  inputs:
    azureSubscription: YOUR_SUBSCRIPTION
    action: 'Select Resource Group'
    resourceGroupName: 'YOUR_RESOURCE_GROUP'
    enableDeploymentPrerequisites: ConfigureVMWithDGAgent
    teamServicesConnection: 'YOUR_SERVICE_CONNECTION'
    teamProject: 'YOUR_AZUREDEVOPS_TEAM'
    deploymentGroupName: 'YOUR_DEPLOYMENT_GROUP_NAME'

これらの処理がデプロイ前に実行されることで、実際にDeployment Groupでデプロイする前にVMの起動とエージェントの展開が約束されます。

ポイントは、このタスク自体にはPATの指定がなく、Azure DevOps Team Service Connection で設定します。

Azure DevOps Team Service ConnectionでDeployment Group の追加に必要な接続URLやPATを設定する

Connection URL には、https://YOUR_TEAM.visualstudio.com/ と指定します。 PATの権限に注意してください。VMへのエージェントインストール、デプロイ実施の両方が必要なので、

Service connections in Azure Pipelines and Team Foundation Server - Azure Pipelines | Microsoft Docs

あとはいい感じでデプロイしてあげればokです。

Q&A

いくつかやっている中ではまりどころがあったものの、文脈とずれるのでここで。

VM拡張で Deployment Agent をインストールしたあとアンインストールしたい

現時点では、アンインストールしようとするとエラーが出ます。VM拡張でのアンインストールは一部しか対応されていないのでそれに該当するらしい.... つまりVM作り直しましょう。

Failed to delete the virtual machine extension in Azure ARM VM - Stack Overflow

VM拡張のARM Templateはないの

あるっぽ。私は ARM Teamplte は使いません。

VSTS Agent Extension in ARM template · Issue #1044 · Microsoft/azure-pipelines-agent

PATの認証って365日だけどどうするの

永続できないのはセキュリティモデル的にはいいですが、マシンユーザー的な利用を想定していないあたりAzureの融通の利かなさ.... Azure DevOps 的には、Service Connectionを複数作れるので、次のいずれかがいいでしょう。

  • PATのRegenerate で期限を新しくしてPATを再生成して、今のService ConnectionのPATを更新
  • 期限前にPATを新しく発行して別にService Connection を作って割り当てを変える

Authenticate with personal access tokens - Azure DevOps | Microsoft Docs

Ref

Deploying to Azure VM using Deployment Groups | Azure DevOps Hands-on-Labs

How to install VSTS deployment group agents on Azure VMs | Mickaël Derriey's blog

*1:お、AnsibleやTerraform のprovisionerどうするの?隙が多い

MSIを使ったStorage Account(Blob, Queue) の認証を使ってQueueの監視を行う

Azure の Storage Account アクセスといえばConnection String ですが、Managed Service Identity (MSI) による AzureAD認証が可能です。(2019/3/25 に GAしたはず.... あれ?)

ここでは、Storage Account ではなく MSI を使ったAzure Functions からのアクセスについてみてみます。

目次

TL;DR

Azure Functions の Binding でのMSI認証はできない。

StorageCredentials を作ってから、CloudBlobClient/CloudQueueClient を作ることになる。

ローカルのMSI認証は現在バグあり。

なぜMSIなのか

MSIを使うことで認証情報をいちいちStorage Account からひっぱてきて参照可能な形で渡す必要がなくなります。 KeyVaultを使うにしても、AppSettingsを使うにしてもConnectionStrings を埋めるのは嫌ですし、IAM = RBAC = AzureAD の認証下でアプリケーションのアクセスが制御されるのは望ましいでしょう。

とくにTerraform を使って構成している場合は、MSIの有効化、IAM でのStorageへの割り当てまで構成時点で完了するのでアプリケーションから見ると透過的に扱えてより効果的でしょう。

AppServiceでMSIを使う

AppService (AzureFunctions) でMSIを使うには、SystemIdentity を有効にします。

Azure Portal で設定する

Azure Portal だと「アクセスを持ちたい側でMSIを設定」、「アクセスされる側のIAMでロール設定」を行います。

まずは、Function App でMSIを設定します。

FunctionApp > AppSettings > Identity

SystemAssigned を Onにする

これでAzureAD のAppRegistration にアプリケーションが登録されたので、アクセスされるStorage Account のIAMでアクセス権限を設定してあげます。

アクセスを許可するリソースでIAM > Role assignments でロール設定する

IAM は Subscription > Resource Group > Resource で継承されるので、そのResourceに限定したいなら ResourceのIAMでロール設定すればokです。もしResourceGroup全体で利用したいなら、 Resource Group のIAMでルール設定すればok です。

Terraform で構成する

こんなことをやっていたら時間がなくなるので、Terraform でFunctionAppの作成からロール設定をします。

gist.github.com

Terraform でMSIを設定するポイントについて説明します。MSIを使う側であるazurerm_function_app でMSIを有効化するのはidentityです。これはVMでもACIでも変わらないのでまず設定するといいでしょう。

  identity = {
    type = "SystemAssigned"
  }

続いて、azurerm_role_assignment を使ってIAMを設定します。ここではわかりやすいように、Resource Groupに対してContributerロールを設定しています。AzureRM の返り値的に、principal_idがlookup必須なのがあんまりイケテマセンが公式です。

resource "azurerm_role_assignment" "main" {
  scope                = "${data.azurerm_resource_group.current.id}"
  role_definition_name = "Contributor"
  principal_id         = "${lookup(azurerm_function_app.main.identity[0], "principal_id")}"
}

さぁこれでMSIの準備はできました。

AzureFunctions でMSIを使ってStorage Account にアクセスする

前回の記事で、StorageAccountのConnection String を使って認証をとりましたが、これをMSIに切り替えます。

tech.guitarrapc.com

とはいえ、MSIはAzure 上のリソースで利用可能な他、ローカルでも az login や Visual Studio の Option > Account から Azureに接続していれば利用できます。と思うじゃないですか? Storage Account に関しては現状動かないです。

https://github.com/Azure/azure-libraries-for-net/issues/557 Local MSI Login using AAD account · Issue #557 · Azure/azure-libraries-for-net · GitHub

認証ヘッダがうまく動いていないので、直るまでローカルはConnection String にバイパスします。バイパスはAzure環境かどうかを WEBSITE_INSTANCE_ID環境変数でチェックして分岐することにしましょう。

gist.github.com

前回のコードから、CloudQueueClient の取得部分をMSIに変えるようにしてみます。変更箇所はCloudQueueClientの作成部分だけです。

gist.github.com

MSI から取得するときのポイントは、3つあります。

  1. AzureServiceTokenProvider を使って認証TokenのProvider経由で認証を作ります
    • MSIは認証プロバイダー経由で認証を作りましょう
  2. azureServiceTokenProvider.GetAccessTokenAsync("アクセストークンの請求先") で適切なURIを指定する必要があります
  3. new CloudQueueClient(new StorageUri(new Uri($"https://{storageAccountName}.queue.core.windows.net")), storageCredentials);のように、URIとCredentialを指定してクライアントを作成します
    • ConnectionString を使っている時は接続先のStorageAccountが明示されていたので、account.CreateCloudQueueClient(); と書けましたが、MSIではStorageAccountが不明なので接続先が作れません
    • このため、StorageCredential からStorageAccountを作るのではなく、URIでアクセス先のStorageAccountNameと一緒に指定してあげます

MSIがローカルで使えるようにバグ修正されるまで、Azure環境とローカルで分岐してあげます。

// connect to Azure Storage
var queueClient = context.IsAzureEnvironment()
    ? await CreateQueueClientAsync("YOUR_STORAGE_ACCOUNT_NAME")
    : CreateQueueClient(Environment.GetEnvironmentVariable("queue_storage_connection_string"));

ということでMSIに対応したコード全体像です。

gist.github.com

まとめ

MSI を使えるなら使いましょう。ConnectionStringとは使うのやめましょ。

MSI、Azureの仕組みは分かりやすいです。一方で、アプリからの利用が分かりにくいというか整理されたドキュメントがないので認証先のドキュメント把握、仕組みの整理に手間取ったです。

余談

2019/3/25 にStorage Account (Blob, Queue) の AzureAD認証がGAしたはずの記事が出ているのですが、現在アクセスできません。

https://azure.microsoft.com/en-us/blog/azure-storage-support-for-azure-ad-based-access-control-now-generally-available/

これがキャッシュです。

Google Cache : Azure Storage support for Azure Active Directory based access control generally available | Blog | Microsoft Azure

ブログ一覧にもないのと、アクセス先URIでもPreviewになっているので、GAキャンセルでまだPreview っぽい?

Azure Services that support managed identities for Azure resources | Microsoft Docs

Azure updates | Microsoft Azure

Ref:

Azure リソース (プレビュー) の Azure Active Directory 管理 ID を持つ blob およびキューへのアクセスの認証 - Azure Storage | Microsoft Docs

仮想マシン上で Azure リソースのマネージド ID を使用してアクセス トークンを取得する方法 | Microsoft Docs

c# - Azure Storage authentication via AzureServiceTokenProvider for CloudTableClient - Stack Overflow

Azure AD Authentication with Azure Storage + Managed Service Identity - Joonas W's blog

Local MSI Login using AAD account · Issue #557 · Azure/azure-libraries-for-net · GitHub

*1:びっくりするぐらいわかりにくい。

Azure Storage Queue を Application Insightsで監視する

あるあるな Queue の監視ですが、自前でやらなきゃいけないなら Serverless でぺちって任せるのは楽ちんですよ、というのはよくあるパターンです。 実際にQueue Storage のモニタリングをしてみましょう。

TL;DR

Azure Storage Queue や Service Bus などのキューサービスは、疎結合な構成を組んだ時に中心を担うため、そのキュー長が想定よりたまっていないか、推移はどうなっているかなどが気になります。

この監視を、Azure Functions の Timer Trigger で行ってみましょう。

Disclaimer

Datadog や 複数のモニタリングサービスは、Azure のAPIをたたいて自分でクラウドリソースのメトリクスを見に行ってくれます。もしそれらで済むならこの記事は不要です、すぐに閉じましょう。この記事は、自分でリソースの状態をポーリングするときの対応例です。

Consumption Planを前提に毎30秒監視しています。(2 * 60 * 24 * 30 = 86400run) 無料枠内ですが、料金がどうなっても私は責任は取りかねます。(免責事項)

なぜ AzureFunctions なのか

モニタリングはアプリケーションの内部リソースと、クラウドなどの外部リソースで分けて考えられます。

アプリがスケールしたときに個別のアプリのメトリクスが見たければ、アプリケーションで監視するのが妥当な場合が多いのは同意を得られるかと思います。一方で、アプリケーションの外にあるリソースは、アプリケーションからモニタリングする必然性はありません。おのずと次のような欲求が高まってきます。

  • アプリケーション自身のリソースをモニタリングに消費したくない
  • モニタリング自身がスケールして重複した値が取れてほしくない
  • アプリケーション自身が、どのようなリソースに依存んして動いているのか関心がない

自前でメトリクスを見たいときは、どこかで実行する必要があります。しかしアプリケーションでは実行したくありません。監視対象を定義しておいて、メトリクスを取得するだけのシンプルな監視の場合、Serverless でアプリケーションとは別個の存在として実行させるのが便利です。

Azure Storage Queue なら、Azure Functions の Consumption Plan + Timer Trigger が定時ポーリング監視になり、定性モニタリングとして負荷なく実行できるでしょう。

監視対象

Azure でキューといえば、Azure Storage Queue と Service Bus がありますが、さくっと Storage Queue で確認してみましょう。ここでは myqueue というキューを用意して監視することにします。

  • myqueue : Storage Queue API で通常投げつけるQueueです。主にこの子が気になる
  • myqueue-poison : WebJobs で失敗した時に自動的に作成されるQueueで-posison が末尾につきます。手動でなんとかすることになります

なお、Storage Queue は Dead Letter Queue (DLQ) に対応していないのでまぁほげもげ。

実行環境

  • Function Runtime: v2
  • Language: C# (.NET Core 2.1)
  • Trigger: Timer Trigger (毎30秒)
  • Metrics: Queue Length
  • Monitoring: Application Insights

Azure Functions

Azure Functions は、v2 においても裏で Application Insights を使っています。 とはいえ、明示的にMicrosoft.ApplicationInsights パッケージを取り込まないと、アプリからApplication Insights をたたけないので入れておきます。

dotnet add package Microsoft.ApplicationInsights

メトリクスを送信するため、Azure Functions の Application Configで Application Insights の Instrumentation Key を設定します。もし Azure Functions の Application Insights にメトリクスを飛ばすのでいいなら APPINSIGHTS_INSTRUMENTATIONKEY でいいでしょう。

Queueに接続するため、QueueがあるStorage Account のConnection Strings を設定しておきます。ここでは、queue_storage_connection_string としました。

では実際に実行してみましょう。コードはこのような感じになります。

gist.github.com

これで毎10秒ごとにQueue の長さをモニタリングして、Application Insights に投げられます。ローカルで実行した場合、コンソールにQueueの長さがでるでしょう。

Application Insights の確認

今回はAzure FunctionsのApplication Insights 連携に乗っかているので見てみます。

ちょうどQueue を投げてなかったので0 が継続して取れているのがわかります。

QueueLength を Application Insights で監視

あとは Azure Monitor という名のApplication Insights の閾値による Action で Webhook なりを投げるといいでしょう。Body の調整できない子ですが。

Azure Monitor | Microsoft Azure

Tips

Cloud Table とか絡んでくると、いまだにMicrosoft.WindowsAzure.Storage パッケージが安定なので、Azureの NuGet パッケージつらい。

Ref

やろうとしたら、だいたいすでにやってるKloudさんつよい。v1 ですが、だいたいやっていることは一緒です。

Monitoring Azure Storage Queues with Application Insights and Azure Monitor - Kloud Blog