Azure Batch のプール(Windows Server 2016 など)のノード上で .NET 8 コンソールアプリを実行したいのに、ランタイム未導入で動かない/Invoke-WebRequest が TLS エラーで落ちる――この手の詰まりは「導入方式」と「アウトバウンド設計」を分けて考えると一気に解消します。IaC 前提で再現性を高める現実解を、選び方から実装パターンまでまとめます。
最初に押さえる:Azure Batch ノードは“揮発的”で、手作業前提の構成が崩れやすい
Azure Batch のノードはスケールアウト/スケールイン、再起動、リイメージ(reimage)などで状態が変わります。つまり「一度入れたから永続する」前提は崩れがちで、再現性(同じ設定を何度でも自動で再現できること)が運用性を左右します。
また、.NET 8 ランタイムが入っていない Windows Server イメージは珍しくありません。結果として、タスク実行時に以下のような症状に遭遇します。
dotnetコマンドが見つからない(PATH にない/そもそも未導入)dotnet-install.ps1をノードからオンライン取得しようとして “Could not create SSL/TLS secure channel” で失敗- そもそもノードが外部へ HTTPS(443) で出られず、ダウンロードに失敗
結論:おすすめの優先順位と“選び方”
先に結論を言うと、安定性・IaC 運用・障害対応のしやすさで見るなら、優先度は以下です。
| 方式 | おすすめ度 | ネットワーク依存 | 再現性 | 向いているケース | ハマりどころ |
|---|---|---|---|---|---|
| カスタムイメージ(.NET 8 入り) | 高 | 低(起動時DL不要) | 非常に高い | 同じ環境を大量ノードへ、長期運用したい | イメージ更新のライフサイクル設計 |
| Application Packages+Start Task | 高 | 中(Storage/配布経路は必要) | 高い | イメージは固定のまま、ランタイムだけ確実に配布したい | パッケージ登録の自動化・権限・パス設計 |
| dotnet-install.ps1(オンライン導入) | 中 | 高(外部到達・TLS・CDN許可) | 中 | 短期検証/ネットワークが自由/CI的な使い方 | TLS/プロキシ/許可ドメイン変更で突然死 |
| 自己完結型(self-contained)で配布 | 中〜高 | 低(ランタイム不要) | 高い | アプリ配布だけで完結させたい(バッチ用途と相性良) | 成果物サイズ増、RID/CPU差分の管理 |
このあと各方式を「どこで詰まるか」まで含めて具体化します。
最も堅い:.NET 8 ランタイム入りカスタムイメージでプールを作る
運用が一番ラクになるのは、最初から .NET 8 ランタイムを含めた Windows イメージを用意し、それを参照してプールを作る方法です。Batch のカスタムイメージは Azure Compute Gallery(旧 Shared Image Gallery)などを使って管理するのが推奨パターンです。
メリット
- 起動時の外部ダウンロードが不要:TLS やプロキシ、CDN 変更に左右されにくい。
- ノード間で状態が揃う:原因調査が「そのノードだけ違う」になりにくい。
- スケール時に速い:Start Task で大きなインストールを繰り返さない。
IaC に寄せる実装の型(手作業を減らす)
「カスタムイメージ=手作業が必要」というイメージが強いですが、実務では以下のようにパイプライン化できます。
| 工程 | やること | 自動化のコツ |
|---|---|---|
| イメージビルド | Windows 更新、.NET 8 Runtime/SDK、必要ツールの導入 | Packer などでスクリプト化し、変更履歴を残す |
| イメージ登録 | Azure Compute Gallery へバージョン登録 | バージョンタグ(例:2025.12.1)でロールバック可能に |
| プール作成 | プールの imageReference を ACG に向ける | Pulumi/ARM/Terraform で参照先だけ差し替え |
| 検証 | ノード上で dotnet --info、タスク実行確認 | スモークテスト用ジョブを自動で流す |
注意点(ここを設計しておくと後悔しない)
- 更新頻度:.NET の更新(セキュリティ含む)と OS 更新を「いつ反映するか」を決め、イメージの世代管理をする。
- ノードの再イメージ:ノードが reimage されたときも同一イメージで戻るのが理想。イメージ破棄や世代切替のルールを作る。
- “古いOS”問題:Windows Server 2016 は TLS/暗号スイート/証明書周りで引っかかりやすいので、可能なら新しい OS イメージへ寄せるのが中長期的に安定します。
次点で強い:Application Packages+Start Task で .NET 8 を配布する
カスタムイメージほどの重さを避けつつ、オンライン導入の不安定さも避けたい場合は、Azure Batch の Application Packages を使って “配布物を固定” し、必要なら Start Task でセットアップするのが現実的です。Application Packages は複数バージョンを管理でき、プール(全ノード)またはタスク単位で自動デプロイできます。
Application Packages の要点(知っておくと設計が速い)
- Application Package は zip 形式のみ 対応。
- プールに紐づけた場合、ノード参加時/再起動/reimage のタイミングで各ノードへ配布されます。
- 使用には Batch アカウントに Azure Storage アカウントのリンクが必要です。
- Storage 側の設定によっては Application Packages が使えない制約があります(例:Firewall ルールや Hierarchical namespace 有効化など)。
- Start Task 文字数制限(32,768 文字)に引っかかりそうなら、Application Packages を使って “Start Task を短くする” のが定番回避策です。
実装パターン:.NET 8 ランタイムを “専用パッケージ” として固定する
おすすめは、アプリ本体と .NET 8 ランタイムを分ける構成です。
- パッケージA:dotnet8_runtime(.NET 8 ランタイム一式を格納)
- パッケージB:my_console_app(あなたのアプリと設定)
Batch はアプリパッケージの展開先を環境変数で教えてくれます。Windows では AZ_BATCH_APP_PACKAGE_アプリ名#バージョン の形式が基本です。
たとえばアプリ名を DOTNET8、バージョンを 8.0 にした場合、ノード上で概ね次のような参照ができます(実際の環境変数名はアプリ名の命名規則で変わるため、まず cmd /c set 等で確認するのが確実です)。
rem .NET ランタイムの場所(例)
%AZ_BATCH_APP_PACKAGE_DOTNET8#8.0%\dotnet.exe --info
rem あなたのアプリが DLL 実行なら
%AZ_BATCH_APP_PACKAGE_DOTNET8#8.0%\dotnet.exe %AZ_BATCH_APP_PACKAGE_MYAPP#1.0%\MyApp.dll
Start Task を使う場合の考え方(PATH に頼らないのがコツ)
Start Task は「ノードの準備が終わるまで次のタスクを走らせない」ための仕組みとして使えます。ただし、Windows で PATH を恒久設定しようとすると admin 権限や再ログインが絡んで面倒になりがちです。バッチ用途では、PATH に入れずにフルパスで呼び出す方がトラブルが減ります。
Start Task の作業ディレクトリや共有領域は環境変数で把握できます(例:AZ_BATCH_NODE_STARTUP_DIR、AZ_BATCH_NODE_SHARED_DIR)。
IaC で“アプリパッケージ登録”を自動化する現実解
ここが一番ハマりやすいポイントです。リソース定義(Batch account / Pool / Application 定義)を IaC で管理できても、zip 実体のアップロードを IaC の枠内だけで完結させるのは難しいケースがあります。実際、Application Packages のアップロードは ARM テンプレートだけでは完結しない旨が明記されています。
その代わり、CI/CD パイプラインで次のどちらかを組み合わせるのが実務での定番です。
- Azure CLI:
az batch application package createなどで zip を登録(必要なら activate)。 - Azure PowerShell:
New-AzBatchApplicationPackageで作成・アップロード。
つまり「インフラは Pulumi で作る」+「配布物はパイプラインで登録する」という分業にすると、手作業ゼロで回せます。
dotnet-install.ps1(オンライン導入)を使うなら、見るべきは TLS と “到達先ドメイン”
dotnet-install.ps1 は自動化に便利ですが、前提条件が揃わないと失敗します。加えて、スクリプト自体は “管理者インストール” ではなく、zip を落として配置するスタイルで、Windows のレジストリ更新も行いません。
公式にも、このスクリプトは CI 的な用途(短命環境での導入)を主目的としていることが説明されています。Batch のノードはまさに短命になり得るため相性は悪くない一方で、ネットワークが硬い環境だと途端に不安定になります。
TLS エラー “Could not create SSL/TLS secure channel” の定番対処
Windows Server 2016 世代の PowerShell(Windows PowerShell 5.1)では、既定の TLS 設定のまま Invoke-WebRequest を叩くと TLS 1.2 を使えず失敗することがあります。まずは TLS 1.2 を明示してから取得するのが定番です。
$ErrorActionPreference = "Stop"
# TLS 1.2 を明示(PowerShell 5.1 で効くことが多い)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$scriptUrl = "https://dot.net/v1/dotnet-install.ps1"
$scriptPath = "C:\Temp\dotnet-install.ps1"
New-Item -ItemType Directory -Force -Path (Split-Path $scriptPath) | Out-Null
Invoke-WebRequest -Uri $scriptUrl -OutFile $scriptPath
# 例:.NET 8 Runtime を任意のフォルダへ(PATH 変更なし)
$installDir = "C:\dotnet"
& $scriptPath -Runtime dotnet -Channel 8.0 -InstallDir $installDir -NoPath
# 動作確認
& "$installDir\dotnet.exe" --info
これで解決しない場合は、TLS バージョン以外(プロキシ、証明書チェーン、暗号スイート、ノードの日時ズレ、ルート証明書更新停止など)を疑います。特に企業ネットワークや Private 化した VNet 配下では「通信は出ているが途中で検査されて握手に失敗」が起きやすいです。
2025 年以降に増えた落とし穴:.NET の配布ドメインが変わり、許可リストが古いと失敗する
.NET の配布は CDN 事情によりドメイン移行が行われ、従来の azureedge.net 系ドメインが影響を受ける(または廃止される)流れがありました。公式告知では、影響を受けるドメインとして dotnetcli.azureedge.net などが挙げられ、新しい CDN として builds.dotnet.microsoft.com や ci.dot.net が提示されています。ファイアウォールの許可リストが古いと、突然ダウンロードできなくなる典型パターンです。
つまり、オンライン導入を選ぶ場合は、少なくとも次をチェック対象にしてください。
| チェック項目 | なぜ必要? | 確認コマンド例(Windows) |
|---|---|---|
| HTTPS(443) のアウトバウンドが許可されている | スクリプト取得/ランタイムDLに必須 | Test-NetConnection dot.net -Port 443 |
| 許可ドメインが最新(dotnet の新 CDN) | CDN移行で旧ドメインが使われない可能性 | 通信先をFWログで確認、必要なら許可 |
| TLS 1.2 を使える | 古い既定値だと TLS 握手に失敗 | 上記 PowerShell の TLS 指定 |
| プロキシ要件(HTTP(S) Proxy) | 企業環境で直通できない場合がある | netsh winhttp show proxy |
“そもそも外へ出られない”問題:Batch のネットワーク設計を整理する
TLS 以前に、ノードが外へ出られる前提が崩れていることがあります。特に 2025 年は、Azure 側でアウトバウンドの前提が変わった話題が多く、過去に動いていた構成が新規作成で動かないケースもあります。
Batch アカウント側:Public network access を無効にすると、私設経路が必須になる
Batch アカウントには「All networks / Selected networks / Disabled」のような公開アクセス設定があり、Disabled にするとプライベート エンドポイントが必須になります(IP ルールよりも Disabled が優先されます)。
ここで重要なのは、これは主に「Batch のエンドポイントへアクセスできるか(管理・制御プレーン)」の話であり、ノードがインターネットへ出られるか(データプレーン)とは別問題になり得る点です。混同すると「RDP できるのに外に出られない」「管理はできるのに dotnet が落とせない」などのズレが起きます。
プール側:NoPublicIPAddresses の場合、インターネットアウトバウンドは“自動で付かない”
Batch の VM 構成プールは、既定ではノードに Public IP が付与され、それがアウトバウンド(インターネット)とインバウンド(外部からノードへ)に使われます。一方、ノードの露出を減らすために Public IP を付けないプール(NoPublicIPAddresses)を作ることもでき、その場合はアウトバウンドが前提になりません。
NoPublicIPAddresses のプールでは、Batch のノード管理(node management)へ出るための経路として「nodeManagement のプライベート エンドポイント」または「自前のインターネットアウトバウンド経路」を用意する必要があります。また、ネットワークを適切に構成しない限り、ノードはパブリックインターネットへ出られません(例:NAT の導入)。
さらに重要:2025/9/30 以降、特定の Batch プールで “既定のインターネットアウトバウンド” が廃止
Azure の更新情報として、“Simplified node communication” かつ “Public IP なし” の Batch プールが頼っていた既定のインターネットアウトバウンドが 2025 年 9 月 30 日に廃止される(された)旨が告知されています。これに該当する構成では、以前よりも明示的なアウトバウンド設計(NAT Gateway など)が必要になります。
アウトバウンド設計の基本形:NAT Gateway を付けて “出どころIP” を固定する
VNet 配下のノードからインターネットへ出したい場合、構成を読みやすく、運用を安定させるなら NAT Gateway が選択肢として強力です。NAT Gateway はサブネットへ関連付けるだけでアウトバウンドが有効化され、サブネットの既定ルートとして機能します。
ドメイン許可や IP 制限が必要な環境では、NAT Gateway で egress IP を固定し、許可リスト運用を成立させやすくなります。
“オンライン導入が通る最小ネットワーク条件”チェックリスト
dotnet-install などで外部から取得したい場合、最低限こうなっている必要があります。
| 観点 | 最低条件 | 補足 |
|---|---|---|
| アウトバウンド | TCP 443 が許可 | NSG/Firewall/UDR/プロキシを含めて確認 |
| DNS | 外部 FQDN を解決できる | プライベートDNSの設定ミスで詰まりがち |
| 到達先 | dot.net と .NET 配布 CDN へ到達 | CDN 移行により許可先が変わる可能性 |
| Batch 基盤 | Batch ノード管理エンドポイントへ到達 | 簡易通信モードは必要最小の outbound で成立 |
なお、Batch のアウトバウンド依存関係は List Outbound Network Dependencies Endpoints API で確認できる、と明記されています。閉域設計で「どこを許可すべきか」を当てずっぽうにしないために有効です。
補足の強手:自己完結型(self-contained)で “ランタイム導入そのもの” を消す
バッチ用途では、ランタイム導入を頑張るよりも、アプリ側を自己完結型にして配布を単純化した方がトータルで安定することがあります。自己完結型ならノードに .NET 8 Runtime が入っていなくても動きます。
代表的な publish 例(Windows x64 の自己完結)です。
dotnet publish -c Release -r win-x64 --self-contained true
さらに配布を単純化したい場合は、単一ファイル化なども検討できます(ただしサイズ増や起動特性の差が出る場合があるため、Batch のワークロード特性に合わせて選びます)。
よくある落とし穴と切り分け(TLS か、ネットワークか、権限か)
同じ「落ちた」でも原因が違うと対処が真逆になります。切り分けの観点を表にまとめます。
| 症状 | まず疑う | 確認ポイント | 対処の方向性 |
|---|---|---|---|
| “Could not create SSL/TLS secure channel” | TLS 設定/証明書/プロキシ | TLS1.2 明示で改善するか | TLS1.2 指定、証明書更新、プロキシ設定 |
| タイムアウト/名前解決失敗 | アウトバウンド/DNS | Test-NetConnection / nslookup | NSG/Firewall/UDR/NAT/Private DNS を見直す |
インストールできたのにタスクで dotnet が見つからない | PATH と実行ユーザー | タスク実行ユーザーと導入先の整合 | フルパス実行/共有ディレクトリに配置 |
| MSI で入れようとして失敗 | 権限不足 | タスクが NonAdmin で動いていないか | Start Task を Admin にする、もしくは non-admin install を採用 |
Batch では、既定でタスクは標準ユーザー(NonAdmin)で実行されます。インストール系の処理をタスクでやるなら、権限設計が必要です。
おすすめの実装ロードマップ(IaC前提で “まず動かす→堅くする”)
ステップ1:まずは最短で安定させる
- ネットワークが閉じている/変更しづらい → Application Packages または 自己完結型 を優先
- ネットワークが自由で検証段階 → dotnet-install.ps1 で素早く動作確認(ただし許可ドメインと TLS をチェック)
ステップ2:運用でコケない形に寄せる
- 長期運用・大規模スケール → カスタムイメージ に寄せて起動時の不確実性を排除
- 更新が頻繁なアプリ → アプリはパッケージで差し替え、ランタイムはイメージor固定パッケージで管理
ステップ3:ネットワークは “到達先” を明文化して保守する
「外へ出る」ではなく「どこへ出る」を決めると、セキュリティと運用性が両立します。Batch 依存先は API で確認し、.NET の配布先は CDN 移行を前提に許可リストを定期点検する、という運用にすると事故が減ります。
ここまでの方針で設計すると、Windows Server 2016 世代のノードでも「再現性のある導入」と「TLS/ネットワーク起因の不安定さ」を切り離して対処できます。特に IaC で自動構築している場合は、“配布物(ランタイム・アプリ)の固定”と“アウトバウンドの明示(NAT/Private Endpoint/許可先)”をセットで考えるのが最短ルートです。

コメント