概要
本記事では、Windows Server環境でInstanaによる.NET監視を安定運用するためのベストプラクティスを紹介します。特に、サーバー再起動後の監視継続性を確保する自動化手法について解説します。
背景:よくある問題
Windows Server環境でこんな症状に遭遇したことはありませんか?
- サーバー再起動後、Webアプリケーションは正常動作しているのにInstanaでトレースが取得できない
- 手動で
iisreset
を実行すると、なぜかトレース収集が復活する - 特に深夜のWindows Update後や定期メンテナンス後に頻発する
この現象の原因は、Instana AgentとIISの起動タイミングの競合にあります。
この問題の症状
- インフラ監視は正常動作
- アプリケーションレベルのメトリクス・トレースが収集されない
- Instana UI でアプリケーション情報が表示されない
問題の発生メカニズム
Windows起動時のサービス競合状態
Windows 起動時、IIS(W3SVC)サービスと Instana Agent サービスは並行して自動起動しますが、初期化完了の速度が異なるため競合状態(Race Condition)が発生します。
Windows起動
├── IIS (W3SVC) サービス (高速起動・軽量初期化)
├── Instana Agent サービス (初期化に時間が必要:Java VM起動、プラグイン読み込み、バックエンド接続確立等)
└── その他のサービス
IIS ワーカープロセスの起動タイミング
IIS ワーカープロセス(w3wp.exe)は以下のタイミングで起動します:
- 最初の Web リクエスト受信時(オンデマンド起動・既定動作)
- Application Pool で「Preload Enabled」が有効な場合は IIS サービス起動直後
- Application Initialization 機能が有効な場合(web.config の applicationInitialization 要素設定時)
- Always On 設定(IIS 8.0以降、Idle Timeout対策)
.NET プロファイラーの動作原理と制約
プロファイラーアタッチの仕組み
.NET アプリケーションの監視は、以下の環境変数を使用してプロセス起動時に初期化されます:
.NET Framework用
COR_ENABLE_PROFILING=1 # プロファイラー有効化
COR_PROFILER={CF0D821E-299B-5307-A3D8-B283C03916DD} # InstanaプロファイラーID
COR_PROFILER_PATH=C:\Program Files\Instana\instana-agent\dotnet\InstanaProfiler.dll
.NET Core / .NET 5+用
CORECLR_ENABLE_PROFILING=1
CORECLR_PROFILER={CF0D821E-299B-5307-A3D8-B283C03916DD}
CORECLR_PROFILER_PATH=C:\Program Files\Instana\instana-agent\dotnet\InstanaProfiler.dll
プロファイラーの初期化プロセス
w3wp.exe起動時の詳細な処理順序:
- プロセス起動: w3wp.exe実行開始
- 環境変数読み込み: COR_ENABLE_PROFILING等をチェック
- プロファイラーDLL読み込み: InstanaProfiler.dll をメモリにロード
- Agent接続試行: 127.0.0.1:42699 への接続を試行
-
初期化完了判定:
- 接続成功 → トレース収集開始
- 接続失敗 → 監視なしで動作継続(再接続試行なし)
重要な制約
- 環境変数はInstana Agentインストール時に設定済み
- プロファイラーはプロセス起動時のみアタッチ可能
- 実行中のプロセスには後からアタッチできない(.NET CLR Profiling API の設計上の制約)
- プロファイラーが読み込まれても、Instana Agentに接続できなければトレース収集されない
- 一度接続に失敗すると、そのプロセスでは再接続試行されない
問題の時系列
典型的な失敗パターン
T1: Windows起動開始
└── 各サービス並行起動開始
└── 環境変数は既にInstanaインストール時に設定済み
T2: IIS (W3SVC) サービス起動完了(高速)
├── HTTP.SYSリスナー初期化
└── Application Pool準備完了
T3: 初回Webリクエスト到着 → w3wp.exe起動
└── プロセス起動時に環境変数読み込み
├── COR_ENABLE_PROFILING=1 読み込み成功
├── プロファイラーDLL読み込み成功
└── Instana Agentへの接続試行(127.0.0.1:42699) → 応答なし
└── 結果:監視なしでプロセス動作開始
T4: Instana Agent完全初期化完了(遅延)
└── HTTPエンドポイント(127.0.0.1:42699)応答開始
└── 既存のw3wp.exeには影響なし(一度起動したプロセスは変更不可)
なぜ手動 iisreset
で解決するのか
iisreset実行時点:
├── 環境変数は設定済み(変更なし)
├── Instana Agentは既に応答可能状態(127.0.0.1:42699)
└── IIS完全再起動 → 新しいw3wp.exe起動
└── プロセス起動時処理
├── 環境変数読み込み成功
├── プロファイラーDLL読み込み成功
└── Instana Agentに接続成功 → トレース収集開始
影響範囲
対象環境
影響を受けるアプリケーション
- IIS でホストされる .NET Web アプリケーション(ASP.NET / ASP.NET Core)
- ASP.NET Core In-process: w3wp.exe が対象
- ASP.NET Core Out-of-process: w3wp.exe から起動される dotnet.exe 子プロセスが対象
対象ランタイム
- .NET Framework: 4.7 以降
- .NET Core 2.1 以降 / .NET 5+
(※ 製品サポート範囲はInstanaのドキュメントに従ってください)
非対象
- IIS上の非.NETアプリ(例: PHP, Node.js, 静的コンテンツ)は本件の影響を受けません
- .NET以外の言語で開発されたアプリケーション
よくある発生シナリオ
- 定期メンテナンス後: Windows Update適用後の自動再起動
- 障害復旧時: 電源トラブルやハードウェア障害からの復旧
- インフラ運用: 仮想マシンの移行、メモリ増設等によるサーバー再起動
- 深夜・休日作業: 無人での作業後、翌営業日に問題発覚
- 開発・テスト環境: 頻繁な再起動による継続的な監視停止
補足
IIS以外でも、Instana Agentの初期化完了前に起動した任意の.NETプロセス(例: Windows Service、コンソールアプリケーション)が同じ原理で影響を受ける可能性がありますが、本記事の主眼はIIS起動順序の競合問題とその解決策です。
手動での回避策
もっとも簡単な方法として、サーバー再起動後に 管理者権限のコマンドプロンプトまたは PowerShell から IIS をリセット することで問題を回避できます。
iisreset
- これにより IIS が一度停止・再起動され、Instana Agent が初期化済みの環境変数を正しく読み込んだ状態でワーカープロセスが立ち上がります。
- 環境に応じて、すべてのサービスを落とす
iisreset
の代わりに 特定の App Pool のみ recycle する運用も選択可能です。
この「手動での IISReset 実行」は即効性のある回避策ですが、本番運用では人的作業に依存してしまうため、次の章で紹介する 自動化によるベストプラクティス が推奨されます。
自動化によるベストプラクティス
事前準備
PowerShell実行ポリシーの確認
Windows ではセキュリティ上、デフォルトでPowerShellスクリプトの実行が禁止されています。
自動化スクリプトを実行するため、事前に設定を確認・変更してください。
# 現在のポリシー確認
Get-ExecutionPolicy
# 結果が "Restricted" の場合は、以下で変更(管理者権限必須)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
# 変更後の確認
Get-ExecutionPolicy
設定の意味:
- RemoteSigned: ローカル作成スクリプトは実行可能、外部スクリプトは署名必要
- LocalMachine: コンピュータ全体に適用(全ユーザーが対象)
ステップ1: PowerShellスクリプトの作成
概要
- Instana Agent が Ready になるまで待機
- Agent の起動完了後、IIS の Application Pool を再起動(必要に応じて IIS 全体をリセット)
- ログ出力・エラー処理を実装し、安定運用可能にする
主な機能概要
-
Agent Readiness チェック
-
http://localhost:42699/info
が200 OK
を返すまで待機 - Fallback として
karaf.bat status
の ExitCode=0 を確認
-
-
指数バックオフリトライ
- 初期待機 30 秒、最大 180 秒まで指数的に待機時間を増加
-
安全な IIS 操作
- App Pool のリサイクルを優先
- 失敗時のみ
iisreset
を実行
-
堅牢なログとエラーハンドリング
- すべての実行ログを
C:\Logs\IISReset.log
に記録 - Try/Catch による例外処理
-
Stop-Computer -Force
のような強制シャットダウンは利用しない
- すべての実行ログを
-
同時実行ガード
- Windows Global Mutex により、多重実行を防止
スクリプト実装例
1. スクリプトを保存する
以下の内容を C:\Scripts\InstanaIISReset.ps1
として保存します(管理者権限で実行される前提)。ログは C:\Logs\IISReset.log
に出力されます。
#Requires -RunAsAdministrator
# PowerShell 5.1+ / 7+
$ErrorActionPreference = 'Stop'
# ---------------- CONFIG ----------------
$dateFormat = 'yyyy-MM-dd HH:mm:ss'
$logDir = 'C:\Logs'
$logFile = Join-Path $logDir 'IISReset.log'
$cmdTimeoutSeconds = 120 # iisreset.exe の最大待ち時間
$svcTimeoutSeconds = 90 # サービス停止/開始の個別待ち時間
$extraStabilizeWait = 5 # 起動直後の安定化待ち
$mutexName = 'Global\IISResetStrictGuard' # 多重起動ガード
# ログローテーション設定`
$maxLogBytes = 2MB # ローテーションサイズ (2MB)`
$maxBackups = 3 # バックアップ保持数 (3個)`
# Instana Agent readiness check
$agentInfoUrl = 'http://127.0.0.1:42699/info'
$agentCheckRetries = 4 # 試行回数(総回数 = 1 + リトライ回数)
$agentCheckTimeoutSec = 5 # 1回のHTTPタイムアウト
$agentInitialWaitSec = 5 # 初回待機(秒)
$agentMaxBackoffWaitSec = 60 # バックオフ最大(秒)
# ----------------------------------------
function Ensure-LogRotation {`
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
if (Test-Path $logFile) {
$len = (Get-Item $logFile).Length
if ($len -gt $maxLogBytes) {
# 既存バックアップの管理
$existingBackups = Get-ChildItem -Path $logDir -Filter "IISReset.log.*.bak" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending
# 古いバックアップを削除(maxBackups個を超える分)
if ($existingBackups.Count -ge $maxBackups) {
$toDelete = $existingBackups | Select-Object -Skip ($maxBackups - 1)
foreach ($file in $toDelete) {
try {
Remove-Item $file.FullName -Force -ErrorAction Stop
# ログ出力前なので直接書き込み
$cleanupMsg = ('{0}: Deleted old backup: {1}' -f (Get-Date -Format $dateFormat), $file.Name)
Add-Content -Path $logFile -Value $cleanupMsg -Encoding UTF8 -ErrorAction SilentlyContinue
} catch {
# バックアップ削除に失敗しても継続
}
}
}
# 新しいバックアップを作成
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
$bak = "$logFile.$ts.bak"
try {
Move-Item $logFile $bak -Force -ErrorAction Stop
# 新しいログファイルに記録
$rotateMsg = ('{0}: Log rotated. Backup created: {1}' -f (Get-Date -Format $dateFormat), (Split-Path $bak -Leaf))
Add-Content -Path $logFile -Value $rotateMsg -Encoding UTF8
} catch {
# ローテーションに失敗した場合はそのまま継続
}
}
}
}
function Write-Log {
param([string]$msg)
Ensure-LogRotation
$line = ('{0}: {1}' -f (Get-Date -Format $dateFormat), $msg)
Add-Content -Path $logFile -Value $line -Encoding UTF8
}
# --- プロキシ無効のHTTP GET(PS7+: IWR -NoProxy / PS5.1: HttpWebRequest) ---
function Invoke-HttpGetNoProxy {
param(
[Parameter(Mandatory)] [string]$Uri,
[int]$TimeoutSec = 5
)
if ($PSVersionTable.PSVersion.Major -ge 6) {
return Invoke-WebRequest -Uri $Uri -TimeoutSec $TimeoutSec -NoProxy
} else {
try {
$req = [System.Net.HttpWebRequest]::Create($Uri)
$req.Proxy = $null
$req.Timeout = $TimeoutSec * 1000
$resp = $req.GetResponse()
try {
$sr = New-Object System.IO.StreamReader($resp.GetResponseStream())
$txt = $sr.ReadToEnd()
} finally {
$resp.Close()
}
return [pscustomobject]@{
StatusCode = 200
Content = $txt
}
} catch {
throw $_
}
}
}
# --- Instana Agent readiness(200 OKならReady) ---
function Test-InstanaAgentReady {
param(
[string]$Url,
[int]$Retries = 4,
[int]$TimeoutSec = 5,
[int]$InitialWait = 5,
[int]$MaxWait = 60
)
$attempt = 0
$wait = [Math]::Min($InitialWait, $MaxWait)
Write-Log "Checking Instana Agent readiness at $Url (retries: $Retries, initial wait: ${InitialWait}s)."
do {
try {
$resp = Invoke-HttpGetNoProxy -Uri $Url -TimeoutSec $TimeoutSec
if ($resp.StatusCode -eq 200 -and $resp.Content) {
# 可能ならバージョン抜粋(軽く)
$ver = ($resp.Content -split "`n" | Where-Object { $_ -match 'agent-version' }) -join ''
Write-Log "Agent ready (HTTP 200). $ver"
return $true
} else {
Write-Log "Agent endpoint returned StatusCode=$($resp.StatusCode)."
}
} catch {
Write-Log "Agent not ready yet: $($_.Exception.Message)"
}
if ($attempt -lt $Retries) {
Write-Log "Sleeping ${wait}s before retry $($attempt+1)/$Retries."
Start-Sleep -Seconds $wait
$wait = [Math]::Min([int][Math]::Ceiling($wait * 2), $MaxWait)
}
$attempt++
} while ($attempt -le $Retries)
return $false
}
function Invoke-IISResetExe {
# 32bit PowerShell でも 64bit の iisreset.exe を呼ぶ
if ([Environment]::Is64BitProcess) { $path = Join-Path $env:WINDIR 'System32\iisreset.exe' }
else { $path = Join-Path $env:WINDIR 'Sysnative\iisreset.exe' }
if (-not (Test-Path $path)) { Write-Log "iisreset.exe not found at '$path'"; return $false }
$args = '/restart', "/timeout:$cmdTimeoutSeconds"
Write-Log "Running: $path $($args -join ' ')"
try {
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $path
$psi.Arguments = ($args -join ' ')
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
$p = [System.Diagnostics.Process]::Start($psi)
if (-not $p.WaitForExit($cmdTimeoutSeconds * 1000)) {
try { $p.Kill() } catch {}
Write-Log "iisreset.exe timed out after ${cmdTimeoutSeconds}s."
return $false
}
$stdout = $p.StandardOutput.ReadToEnd().Trim()
$stderr = $p.StandardError.ReadToEnd().Trim()
Write-Log "iisreset exit=$($p.ExitCode)"
if ($stdout) { Write-Log "iisreset stdout: $stdout" }
if ($stderr) { Write-Log "iisreset stderr: $stderr" }
return ($p.ExitCode -eq 0)
} catch {
Write-Log "iisreset.exe failed: $($_.Exception.Message)"
return $false
}
}
function Wait-ServiceState {
param(
[Parameter(Mandatory)][string]$Name,
[Parameter(Mandatory)][ValidateSet('Running','Stopped')][string]$State,
[int]$TimeoutSec = 60
)
$sw = [System.Diagnostics.Stopwatch]::StartNew()
do {
try {
$svc = Get-Service -Name $Name -ErrorAction Stop
if ($svc.Status -eq $State) { return $true }
} catch { }
Start-Sleep -Milliseconds 500
} while ($sw.Elapsed.TotalSeconds -lt $TimeoutSec)
return $false
}
function Restart-IIS-Services {
$svcW3SVC = 'W3SVC' # World Wide Web Publishing Service
$svcWAS = 'WAS' # Windows Process Activation Service
try {
Write-Log "Stopping $svcW3SVC ..."
Stop-Service -Name $svcW3SVC -ErrorAction SilentlyContinue -Force
} catch { Write-Log "Stop $svcW3SVC threw: $($_.Exception.Message)" }
if (-not (Wait-ServiceState -Name $svcW3SVC -State 'Stopped' -TimeoutSec $svcTimeoutSeconds)) {
Write-Log "$svcW3SVC did not stop within ${svcTimeoutSeconds}s (continuing)."
} else {
Write-Log "$svcW3SVC is Stopped."
}
try {
Write-Log "Restarting $svcWAS ..."
Stop-Service -Name $svcWAS -ErrorAction SilentlyContinue -Force
if (-not (Wait-ServiceState -Name $svcWAS -State 'Stopped' -TimeoutSec $svcTimeoutSeconds)) {
Write-Log "$svcWAS did not stop within ${svcTimeoutSeconds}s (continuing)."
}
Start-Service -Name $svcWAS -ErrorAction Stop
} catch { Write-Log "Restart $svcWAS threw: $($_.Exception.Message)" }
if (-not (Wait-ServiceState -Name $svcWAS -State 'Running' -TimeoutSec $svcTimeoutSeconds)) {
Write-Log "$svcWAS not Running after restart."
return $false
}
Write-Log "$svcWAS is Running."
try {
Write-Log "Starting $svcW3SVC ..."
Start-Service -Name $svcW3SVC -ErrorAction Stop
} catch {
Write-Log "Start $svcW3SVC threw: $($_.Exception.Message)"
}
if (-not (Wait-ServiceState -Name $svcW3SVC -State 'Running' -TimeoutSec $svcTimeoutSeconds)) {
Write-Log "$svcW3SVC not Running after start."
return $false
}
Write-Log "$svcW3SVC is Running."
Start-Sleep -Seconds $extraStabilizeWait
return $true
}
# ---- Main ----
# 多重起動ガード
$created = $false
$mtx = [System.Threading.Mutex]::new($true, $mutexName, [ref]$created)
if (-not $created) {
Write-Log "Another instance running. Exit."
return
}
try {
Ensure-LogRotation
Write-Log "---- IISReset-Strict start ----"
Write-Log "Is64BitProcess=$([Environment]::Is64BitProcess) PSHOME=$PSHOME"
# 事前:Instana Agent Ready?
$preReady = Test-InstanaAgentReady -Url $agentInfoUrl -Retries $agentCheckRetries -TimeoutSec $agentCheckTimeoutSec -InitialWait $agentInitialWaitSec -MaxWait $agentMaxBackoffWaitSec
Write-Log ("Pre-check: Instana Agent Ready = {0}" -f $preReady)
# まず公式の iisreset.exe を試す
$ok = Invoke-IISResetExe
if (-not $ok) {
Write-Log "iisreset.exe failed or timed out -> falling back to service-level restart."
$ok = Restart-IIS-Services
}
# 最終検証(IISサービス)
$finalW3 = Wait-ServiceState -Name 'W3SVC' -State 'Running' -TimeoutSec 5
$finalWA = Wait-ServiceState -Name 'WAS' -State 'Running' -TimeoutSec 5
if ($ok -and $finalW3 -and $finalWA) {
Write-Log "SUCCESS: IIS services restarted (W3SVC/WAS Running)."
} else {
Write-Log "FAIL: IIS services not healthy. Check logs above."
}
# 事後:Instana Agent Ready?(念のため)
$postReady = Test-InstanaAgentReady -Url $agentInfoUrl -Retries $agentCheckRetries -TimeoutSec $agentCheckTimeoutSec -InitialWait $agentInitialWaitSec -MaxWait $agentMaxBackoffWaitSec
Write-Log ("Post-check: Instana Agent Ready = {0}" -f $postReady)
Write-Log "---- IISReset-Strict end ----"
}
catch {
Write-Log "FATAL: $($_.Exception.Message)"
throw
}
finally {
if ($mtx) { $mtx.ReleaseMutex(); $mtx.Dispose() }
}
2. スクリプトを実行する
管理者権限の PowerShell から以下を実行します:
powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\InstanaIISReset.ps1"
※ 「最高権限で実行」が必須です(IIS の制御/サービス操作のため)。
ステップ2: タスクスケジューラーでの自動実行設定
設定手順
-
新しいタスクを作成
-
トリガー設定
-
アクション設定
5.1 条件
5.2 設定
最後:保存 & テスト
- 管理者認証を入力し保存
- 右クリック「実行」でテスト →
C:\Logs\IISReset.log
に出力されれば成功
ログ出力例
2025-09-25 04:40:39: ---- IISReset-Strict start ----
2025-09-25 04:40:39: Is64BitProcess=True PSHOME=C:\Windows\System32\WindowsPowerShell\v1.0
2025-09-25 04:40:39: Checking Instana Agent readiness at http://127.0.0.1:42699/info (retries: 4, initial wait: 5s).
2025-09-25 04:40:39: Agent ready (HTTP 200). {"agent-version":"1.1.758","boot-version":"1.2.48","karaf-version":"4.2.16","agent-id":"00:50:56:ff:fe:84:59:c6","agent-pid":"8044","agent-mode":"apm"}
2025-09-25 04:40:39: Pre-check: Instana Agent Ready = True
2025-09-25 04:40:39: Running: C:\WINDOWS\System32\iisreset.exe /restart /timeout:120
2025-09-25 04:40:48: iisreset exit=0
2025-09-25 04:40:48: iisreset stdout: Attempting stop...
Internet services successfully stopped
Attempting start...
Internet services successfully restarted
2025-09-25 04:40:48: SUCCESS: IIS services restarted (W3SVC/WAS Running).
2025-09-25 04:40:48: Checking Instana Agent readiness at http://127.0.0.1:42699/info (retries: 4, initial wait: 5s).
2025-09-25 04:40:48: Agent ready (HTTP 200). {"agent-version":"1.1.758","boot-version":"1.2.48","karaf-version":"4.2.16","agent-id":"00:50:56:ff:fe:84:59:c6","agent-pid":"8044","agent-mode":"apm"}
2025-09-25 04:40:48: Post-check: Instana Agent Ready = True
2025-09-25 04:40:48: ---- IISReset-Strict end ----
ステップ3: 運用オプション(IIS自動起動制御)
より確実な運用のため、以下の設定も検討できます:
# IIS自動起動の無効化(オプション)
Set-Service -Name "W3SVC" -StartupType Manual
Set-Service -Name "WAS" -StartupType Manual
この設定の効果:
- スクリプトがIIS起動タイミングを完全制御
- より確実な起動順序の保証
- 既存設定を変更したくない場合は省略可能
追加考慮事項
-
監視ログ連携
→ Splunk, ELK, Instana 自身にログを転送可能 -
環境差異への調整
→$karafBatPath
,$maxRetries
,$initialWaitSeconds
を環境に合わせて変更 -
導入前テスト
→ ステージング環境で必ず検証してから本番導入
まとめ
Instana で IIS の .NET Web アプリを監視する際には、
- 「Agent Ready → IIS 再起動」シーケンスの自動化
- タスクスケジューラによる安定実行
を組み合わせることがベストプラクティスです。
これにより、サーバー再起動時の計測ロストを防ぎ、監視の安定性を大幅に向上できます。