Azure App Service へのデプロイ
Power Portals Proポータルは、どのスタックを選んでも単一の ASP.NET Coreアプリケーションなので、デプロイは普通のApp Serviceへの通常の dotnet publish です。このページでは、作成すべきリソース、ポータルが読み取る設定、デフォルトとして推奨するもの、そしてPower Portals Pro特有のいくつかのステップについて説明しています。これは ASP.NET Coreに特有ではないものです。
展開するもの
両方のテンプレートは1つのウェブアプリケーションと1つの公開出力を生成します。独立したフロントエンドホストは存在せず、静的サイトリソースも、2つ目のApp Serviceも存在しません。
- Blazor — アプリをホストするサーバープロジェクトです。
ServerまたはAutoのインタラクティビティの下では、Blazor SignalR回線もサービスを提供します。WebAssemblyではクライアントバンドルとクライアントが呼び出す/api/*エンドポイントにもサービスを提供します。 - React —
dotnet publishクライアントプロジェクトのViteビルドを実行し、そのdist/をホストのwwwrootに段階化するため、1つの ASP.NET コアホストが単一の発信元からSPAとAPIをサービスします。追加の配線作業はありません。
注記
Reactのパブリッシュはnpmに出力されるため、Nodeは
dotnet publish動作するマシン、つまりノートパソコンかビルドエージェントにインストールしなければなりません。テンプレートのクライアントはNode^20.19.0 || >=22.12.0を求めます。App Service自体はNodeを必要としません。ビルドされた出力だけを認識します。
推奨されるデフォルト
ルールではなく出発点ですが、新しい制作ポータルとして選ぶのはこういうことです。
- オペレーティングシステム:Linux。 階層ごとのコストが安く、開始も早く、IISや
web.configを完全に排除できます。組織がWindowsを標準化していれば問題なく動作します。 - ランタイムスタック:.NET 10。 テンプレートは
net10.0をターゲットにしています。フレームワーク依存のパブリッシュをし、App Serviceにランタイムを提供させます。 - 計画:B1 — すべての環境、本番環境も含めて。 B1は最も安価で温かく保てる階層で、後からスケールアップすれば再デプロイも意味のあるダウンタイムもないコマンドが一つで済みます。「本番だから」と言われる大きなものをプロビジョニングすると、誰も再検討しない請求書が発生します。ここから始めて、測定やデプロイスロットの必要性が出たら上位に進めてください。それまでは、デプロイスロットなし、オートスケーリングなし(どちらもStandardから始まります。S1と、Linuxで通常安価でメモリも多いP0v3と比較してください)。
- 常時オン:オン。 App Serviceはアイドルアプリをアンロードし、コールドスタートはDataverseへの再接続とメタデータやローカリゼーションキャッシュの再構築を意味します。静かな期間の後、最初の訪問者がすべての費用を支払います。
- HTTPSのみ:オン、最低TLS 1.2、HTTP/2有効。 ポータルは認証クッキーを発行します。普通のHTTPを受け入れる理由はありません。
- ウェブソケット: Blazor ServerまたはAutoでオン。 オフは App Serviceのデフォルトで、これがないと回路は静かに長いポーリングに戻ってしまいます。
- セッションアフィニティ: Blazor ServerやAutoではオン — 回線が1つのインスタンスに属します。ReactやBlazor WebAssemblyポータルは オフ にしてください。これらはHTTP上でステートレスであり、アフィニティはインスタンスのバランスを崩すだけです。
- Key Vault内の秘密は、アプリ設定から参照されます。 App Serviceに管理IDを与え、その設定をVaultに向けて、秘密が設定ブレードやデプロイメントスクリプトに入らないようにしましょう。
- デプロイメントスロットを使った瞬間やインスタンスを1回を超えてスケールするとすぐにBlob Storageにデータ保護キーを入れます。デフォルトのキーリングはスロットごとに割り当てられ、スワップすると全員がサインアウトされます。
WEBSITE_RUN_FROM_PACKAGE=1: オプションであり、それだけの価値があります。デプロイメントは原子的になり、コンテンツディレクトリは読み取り専用になります。ポータルはコンテンツのルートからのみ読み込むため、何も壊れません。アップロードバッファはシステム一時フォルダを通じて記録可能で、書き換え可能なままです。- アプリケーションインサイト:オン。 フレームワークはライセンス拒否やDataverseの障害を
ILoggerを通じて記録します。シンクがなければ、望ましい瞬間にログストリームを手作業で読み込むことになります。
1. アプリサービスの作成
3つのコマンドです。自分の名前を置き換えてください — このページ全体で使われ myportal 。
az group create --name myportal-rg --location eastus
az appservice plan create --name myportal-plan --resource-group myportal-rg --sku B1 --is-linux
az webapp create --name myportal --resource-group myportal-rg --plan myportal-plan --runtime "DOTNETCORE:10.0"
ウェブアプリ名は https://myportal.azurewebsites.net になり、グローバルに一意でなければなりません。意図的に選んでください。カスタムドメインを前に付けない限り、ライセンスキーが割り当てられるURLになります。
2. プラットフォームオプションの設定
これらはポータルとしてデフォルトが間違っている人たちです。
az webapp config set --name myportal --resource-group myportal-rg --always-on true --min-tls-version 1.2 --http20-enabled true --web-sockets-enabled true
az webapp update --name myportal --resource-group myportal-rg --https-only true --client-affinity-enabled true
--web-sockets-enabled true— はBlazor ServerおよびAutoで必要です。ReactやWebAssemblyポータルでは無害で、回路を開かないため。--client-affinity-enabled— Blazor ServerとAutoにはtrueを残し、ReactとBlazor WebAssemblyにはパスfalseしてインスタンス間で均等にリクエストを分散させます。--always-on true— ベーシックティア以上。無料プランや共有プランでは利用できません。
3. 構成
App Serviceのアプリケーション設定は環境変数として届き、 appsettings.jsonを上書きします。まさにあなたが望む通りです:ファイルはそのままにして、ここで環境ごとの値を設定してください。
重要
ユーザーの秘密は移動しません。 開発中に保存したすべての
dotnet user-secretsはマシン上だけに残ります。それらのキーはすべてアプリケーション設定やKey Vault参照として再作成しなければならず、そうでなければ展開したアプリは起動しません。
セクション区切りにはダブルアンダースコアをつけてください:D365:ClientIdD365__ClientIdになります。コロンフォームはWindows App Serviceでは動作しますがLinuxでは使えないため、__フォームはどこでも使うべきです。
常に必要です
D365__Url— Dataverse環境のURL、例えばhttps://yourorg.crm.dynamics.com。秘密ではありません。D365__ClientId— ポータルがDataverseに接続しているEntraアプリ登録のクライアントID。秘密ではありません。D365__ClientSecret— その登録のクライアント秘密。秘密:キー・ボールトの参照を使う。D365__EmailSenderEmailAddress— ポータルがアカウント確認やパスワードリセットメールをDataverse経由で送信する送信先アドレス。appsettings.jsonに入っておらず、アプリが起動しないと起動しないため、最も忘れやすいものです。ASPNETCORE_ENVIRONMENT—Productionに設定してください。これはエラーページを変えるだけでなく、ReactポータルではSPAのフォールバックがDevelopmentの外に登録されているため、Developmentに残ったサイトはアプリを提供する代わりにhttp://localhost:5173への深いリンクをリダイレクトします。
そのサインインプロバイダーを有効にした場合は必須です
Authentication__Microsoft__ClientIdそしてAuthentication__Microsoft__ClientSecret— サインイン登録であり、これはDataverseのものとは 異なる アプリ登録です。 Entra IDサインインを参照してください。Authentication__Google__ClientIdそしてAuthentication__Google__ClientSecret。Authentication__Facebook__AppIdそしてAuthentication__Facebook__AppSecret。
オプション
Azure__Translation__Key、DeepL__Translation__KeyまたはGoogle__Translation__Key— 登録した機械翻訳プロバイダーのいずれかです。設定を解除すると、ローカリゼーション管理者ページは単に翻訳パネルを非表示します;ページの他の部分は引き続き動作します。PortalIdentity__SystemAdminRoleName— Dataverseのセキュリティロールで、ポータル管理者にアクセス権を与える。appsettings.jsonに納入され、環境ごとにロール名が異なる場合はここで上書きします。
az webapp config appsettings set --name myportal --resource-group myportal-rg --settings ASPNETCORE_ENVIRONMENT=Production D365__Url="https://yourorg.crm.dynamics.com" D365__ClientId="00000000-0000-0000-0000-000000000000" D365__EmailSenderEmailAddress="portal@contoso.com"
注記
必要なキーは
GetRequiredValueで読み取られるため、欠けているキーは起動時にスローされ、最初のDataverse呼び出しで失敗することはありません。App Serviceでは、ログストリームを読み込み、例外でキー名を付けます。
4. ポータルに送信先のアドレスを与える
D365__EmailSenderEmailAddress アカウント確認やパスワードリセットメールの元であり、フレームワークはそのアドレスを持つDataverseユーザーを探し、次に キュー、そしてチームを解析します。 キューを使いましょう。 Exchangeで手を加える必要のない唯一のオプションです。Dataverseがキュー用に作成したメールボックスは組織の既存のExchange Onlineプロファイルを指しており、プロビジョニング用のメールボックスや購入ライセンスがありません。設定を実際の人物に向けると、3つのコストがかかります。
- その人の名前はすべての自動メッセージに記載されています。 受信者は、送信していないパスワードリセットの送信者として同僚を認識します。
- 返信は受信箱に届きます。 返信しないメールには返信する人もいますが、その返信は誰も見ていない場所に送られます。
- ポータルは彼らが去ると壊れます。 アカウントを無効にすると送信者も一緒に消え、アカウント確認が静かに機能しないという失敗が表面化します。
3つのステップで、すべてDataverse内で行われます。 MCPサーバー は最初の2つのステップを1回の通話で実行します create_email_queue:
- アドレス(例:
noreply@contoso.com)、受信配信なし、送信配信サーバーサイド同期でプライベートキューを作成します。キューの名前は受信者が送信者として認識するものなので、人ではなくポータルの名前で呼びます。 - アドレスを承認してください。 2つのフラグが一致しなければならず、それぞれ異なるレコードに存在します:キュー自身のメールアドレス承認と 、O365管理者によるメールボックスの承認です。両方の上で保留中のキューが作成されます。
- Power Platform管理センターの設定→メール設定→メール設定→メールボックスをテスト・有効化してください。これは手作業で行う必要がある一つのステップで、単一のボタンです。
重要
未承認またはテストされていないメールボックスは静かに失敗します。 Dataverseはメールを受け取り、記録し、送信しないとポータル側から見ると送信が成功したと区別がつきません。何も送信が起きず、エラーとして記録されることもなく、問題の最初の兆候はユーザーが確認リンクを受け取っていないと言うことです。登録メールが届かない場合は、まずメールボックスの 送信状況 を確認してください。「 実行しない 」はこのステップが見落とされていることを意味します。
5. 秘密をキーヴォールトに保管すること
アプリに管理されたアイデンティティを与え、ボールトへの読み取り権限を付与し、各シークレットのURIをシークレット自体ではなくアプリ設定に配置してください。App Serviceが参照を解決し、その値は設定ブレードやスクリプト、デプロイログには一切現れません。
az webapp identity assign --name myportal --resource-group myportal-rg --query principalId --output tsv
az keyvault create --name myportal-kv --resource-group myportal-rg --enable-rbac-authorization true
az role assignment create --assignee PRINCIPAL_ID --role "Key Vault Secrets User" --scope VAULT_RESOURCE_ID
az keyvault secret set --vault-name myportal-kv --name D365-ClientSecret --value THE_SECRET
az webapp config appsettings set --name myportal --resource-group myportal-rg --settings D365__ClientSecret="@Microsoft.KeyVault(SecretUri=https://myportal-kv.vault.azure.net/secrets/D365-ClientSecret/)"
クライアントのシークレットが期限切れになったら(Entraはデフォルトで1年)、新しいバージョンをボールトに追加し、アプリを再起動します。アプリ設定自体は変わりません。
MCPサーバーはこの一連の手順を行います。create_managed_identityユーザーが割り当てたアイデンティティを作成し、それをアプリにアタッチenable_managed_identity、create_key_vaultボールトを作成し、そのアイデンティティに読み込みを付与し、シークレットツールがdestination: "key-vault"を受け入れます。これにより、Entraから新たに作成されたクライアントシークレットが直接ヴォールトに送信され、アプリ設定には参照だけが残ります。その値は文字通りの設定になることはなく、誰かの手を渡ることもありません。
重要
Key Vaultの名前は削除後90日間予約されます。 ソフト削除はデフォルトでオンでオフにできないため、Vaultを削除しても名前が解放されません。同じ名前のウィンドウ内で再作成するには明確なパージが必要で、それ自体が許可が必要で不可逆的です。ここで唯一、安価に取り消せるリソースなので、App Serviceの名前と同じくらい意図的に名前を選んでください。
注記
さらに良いのは、Dataverseクライアントの秘密を完全に削除することです。 管理されたアイデンティティのクライアントIDに
D365__ManagedIdentityId設定すると、ポータルはそのIDとしてDataverseに認証され、秘密はなく、期限切れもありません。環境チェックもビルドスイッチもありません。設定はApp Serviceの設定に存在し、appsettings.jsonではなく、ローカルには存在し、デプロイ時には存在し、どちらのビルドも正しく動作します。 ユーザー割り当て のIDを使う — システム割り当てのIDはアプリと共に作成・破棄され、デプロイスロットごとに異なるため、アプリを再構築すると新しいIDが作成され、Dataverseアプリケーションユーザーは古いIDを基に構築されてしまいます。アイデンティティは依然としてそのアプリケーションユーザーを必要とし、Azureポータルが最初に表示するプリンシパルIDではなく、 アプリケーション IDから作成されます。
6. 公開
フレームワーク依存のPublishを、出力をzipし、zipをプッシュします。Reactポータルでは同じ dotnet publish がSPAを構築し、 wwwrootにステージ化します。
dotnet publish ./MyPortal/MyPortal.csproj -c Release -o ./publish
Compress-Archive -Path ./publish/* -DestinationPath ./publish.zip -Force
az webapp deploy --name myportal --resource-group myportal-rg --src-path ./publish.zip --type zip
Zipフォルダーの 内容 はzipで削除してください。フォルダ自体ではなく、App Serviceはアーカイブを直接サイトルートにアンパックするため、入れ子状のトップレベルディレクトリは何もサービスしないサイトを生成します。
Visual Studioの Publish ダイアログもインタラクティブに同じことを行い、GitHub ActionsやAzure Pipelinesのワークフローはプッシュ時に行います。Reactポータルの追加ステップは、ビルドより先行してNodeを設定するステップだけです。なぜなら dotnet publish はnpmに出力されるからです。
7. ライセンスをデプロイされたURLに向ける
ウェブサイトのライセンスはURLにバインドされています。Power Portals Proのモデル駆動アプリで、ライセンスキーを保持した Portal Website レコードを開き(または作成)、その URL を展開したポータルの本番URL( https://myportal.azurewebsites.net、またはカスタムドメインが先にある場合は)に設定してください。
本番環境のURLのみを登録してください。バリデーションは-dev、-test、-uat、-stageの接尾辞ホストも受け入れ、リードサブドメインとして同じ4つも受け入れているため、下位環境は別個のレコードを必要としません。ライセンスページに完全なリストがあります。
重要
トラフィックを送信する前にこれを行ってください。すべてのデータサービングエンドポイントはライセンスを確認するため、レコードが存在しホストと一致するまではポータルはレンダリングしますが、すべてのデータ呼び出しは
402返されます。
8. 生産リダイレクトURIを追加する
ポータルがMicrosoftのサインインを提供している場合、サインインアプリの登録は依然としてローカルホストのリダイレクトURIしか認識しません。そこにデプロイされたURIを追加してください。Entraは複数のURIを受け入れているので、ローカルのURIは動作し続けます:
https://myportal.azurewebsites.net/signin-microsoft
他のプロバイダーもそれぞれのコールバックパス(/signin-google、 /signin-facebook)で同じパターンです。環境ごとに1つ、カスタムドメインごとに1つずつ追加してください。
展開枠
ステージングスロットとスワップは、ウォームアップされたアプリと即時のロールバックを提供します。これらはB1を離れる価値のある唯一の理由であり、必要な時に置いておくべき理由でもあります。注意が必要です。
データ保護キーはスロットごとに割り当てられています。 ASP.NET Coreはキーリングをその %HOME%に永続化し、各スロットにはそのコピーがあるため、スワップで置き換えられ、前のスロットで発行された認証クッキーはすべて復号不可能になります。全員がログアウトされます。キーリングを両方のスロット共有する場所に移動させてから、スワッピングに頼る前に:
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri(blobUri), new DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(new Uri(keyUri), new DefaultAzureCredential());
環境固有の設定をスロット設定としてマーク し、スワップ中も残るようにしてください。Dataverse環境のURLが問題です。URLがなければ、サンドボックスを指すステージングスロットを交換すると、そのサンドボックス接続が本番環境に進みます。
拡大
拡大は必要性を測る際に決めるべきもので、プロビジョニング時ではありません。デフォルトティアの一つのインスタンスは、驚くほど多くのポータルトラフィックを運んでいます。
- Blazor Server / Auto: セッションアフィニティをオンにし、自社回線を測定する前に、1回の並行回線あたり約250KB のサーバーメモリを予算化しましょう。Azure SignalR ServiceはApp Service上で必須ではありません。非常に多くの接続数やグローバルに分散したユーザー向けのツールです。
- React / Blazor WebAssembly: セッションアフィニティをオフにして横方向にスケールします。リクエストはステートレスなので、インスタンスは互換性があります。
- テンプレートは
AddDistributedMemoryCache()レジスタをします。これはインスタンスごとにです。複数のインスタンスで、メタデータやローカライゼーションキャッシュを各インスタンスで再構築するのではなく共有できるように、実際の分散キャッシュに交換してください。
これらすべてをAIエージェントから行う
Power Portals Pro MCPサーバー はこのページのすべてのステップを実行できます。フォルダを一度、ターミナルでサブスクリプションに割り当ててください。バインドはそのフォルダに属しているので、複数のプロジェクトが同時に異なる顧客をターゲットにでき、マシン全体での変更はありません。
ppp-mcp azure login
check_deployment— 最初に手を伸ばすべきもの。プロジェクトに必要なものとApp Serviceのものを比較し、問題点を特定します:ユーザーシークレットにのみ存在する必要設定、開発に残された環境、BlazorポータルでWebSocketsをオフにする、ホストをカバーしないPortal Websiteレコード。読み取り専用で、本番環境に対して安全に実行できます。get_deployment_plan— すべてのプランのステップと、Azureの公開価格表から実際の現在の価格が記載されています。create_app_service— リソースグループ、プラン、ウェブアプリ、WebSocketsとアフィニティペアがすでにスタックに設定されています。set_app_service_settings— 設定を置き換えるのではなく統合するため、秘密を保持している設定を消去できません。deploy_to_app_service—dotnet publishを積み重ね、結果を押し進めます。create_dataverse_app_registration、create_signin_app_registration、add_app_registration_redirect_uri— ステップ6と7のEntra側で、自分のサインイン済みアカウントのディレクトリロールを使用します。作成されたクライアントシークレットはユーザーシークレットやApp Service設定に直接入り、表示されません。create_managed_identity、enable_managed_identity、create_key_vault――上のアイデンティティと金庫、あなたのために割り当てられ、それが伝播する間に再試される役割が記されています。create_email_queue— ステップ4のノーレスポンド送信キューで、1回の通話で作成・承認されました。
注記
Azureで何かを変更するすべてのツールは、まず確認を求め、回答する前に実際の月額料金を教えてくれます。リテールリストはあなたが持っているエンタープライズ契約や予約を無視しているという注意書きも添えています。読み取りだけのツールはプロンプトを促しません。サーバーはAzureのCLIを使わず、サインイン状態を読み取ったり変更したりすることもありません。
トラブルシューティング
- そのサイトは一度も表示されません。 通常は必須のアプリ設定が欠けています。スタートアップ例外はキー名を指定します — ログストリーム で読み取るか、 問題を診断して解決してください。
- ポータルはレンダリングされますが、すべてのデータ呼び出しは402を返します。 ライセンスチェックはホストを拒否しています。ポータルウェブサイトのレコードのURLがブラウザが実際に使用しているホストと一致し、管理ソリューションがポータルが接続する環境にインストールされているかを確認してください。拒否は検証された正確なホスト、転送されたホスト、スキームとともに記録されます。
- リダイレクトループや
http://予想通りのURLhttps://。 アプリはApp Serviceのフロントエンドから転送されたヘッダーを尊重していません。リクエストスキームとクライアントIPが元のリクエストを反映するようにASPNETCORE_FORWARDEDHEADERS_ENABLED=true設定してください。 - Blazorポータルのブラウザコンソールで「Long Pollingのフォールバックを使用してWebSockets経由で接続に失敗しました」 — App ServiceのWebソケットはまだオフのままです。
- 展開または交換後、全員がログアウトされます。 データ保護キーリングが変更されました。上記のデプロイスロットを参照してください。
- Reactは404をDeepリンクするか、localhostにリダイレクトします。
ASPNETCORE_ENVIRONMENTはProductionではなく、SPAのフォールバックはDevelopmentの外でのみ登録されています。 - ログには何も役に立 たない。App Serviceアプリケーションログはデフォルトでオフになっている:情報レベルでファイルシステムのログをオンにし、その後
az webapp log tailでフォローする。
