[monitor-opentelemetry] azureVmDetector probes IMDS unconditionally on App Service, emitting a 100%-failure dependency into customer telemetry
questioncustomer-reportedClientneeds-team-triageMonitor - Distro
- **Package Name**: @azure/monitor-opentelemetry
- **Package Version**: 1.18.2 (via applicationinsights 3.15.1)
- **Operating system**: Linux (Azure App Service)
- [x] **nodejs**
- **version**: 24.x
- [ ] **browser**
- **name/version**:
- [ ] **typescript**
- **version**:
- Is the bug related to **documentation** in
- [ ] README.md
- [ ] source code documentation
- [ ] SDK API docs on https://learn.microsoft.com
**Describe the bug**
`@azure/monitor-opentelemetry` runs `azureVmDetector` on every process start regardless of the detected hosting platform. On Azure App Service (Linux), IMDS at `169.254.169.254` is not routable, so this probe fails 100% of the time — and because it runs during SDK initialization, the detector's internal `suppressTracing` does not take effect, so the failed probe is recorded as a **failed dependency in the customer's own Application Insights resource**.
The result is a permanent stream of failed dependencies that the customer did not originate, cannot correlate to any request, and has no supported way to turn off.
In `dist/commonjs/shared/config.js` (v1.18.2), `_setDefaultResource()` synchronously runs the platform detectors and then unconditionally kicks off the VM detector:
```js
// Load resource attributes from Azure
const azureResource = detectResources({
detectors: [azureAksDetector, azureAppServiceDetector, azureFunctionsDetector],
});
this._resource = resource.merge(azureResource);
// Handle VM resource detection asynchronously to avoid warnings
// about accessing resource attributes before async attributes are settled
this._initializeVmResourceAsync();
```
```js
_initializeVmResourceAsync() {
const vmResource = detectResources({
detectors: [azureVmDetector],
});
...
}
```
By the time `_initializeVmResourceAsync()` is reached, `azureAppServiceDetector` has already positively identified the platform from `WEBSITE_SITE_NAME`. The VM probe is therefore known-futile before it is issued, but there is no guard.
Two things make this more than cosmetic:
1. **The probe is guaranteed to fail on App Service.** IMDS is a VM/VMSS facility and is not reachable from App Service PaaS. `azureVmDetector` imposes its own 1s timeout and then calls `req.destroy()`, so every probe aborts client-side.
2. **The failure is visible to the customer.** `azureVmDetector` wraps its request in `context.with(suppressTracing(...), ...)`, which would normally keep it out of telemetry. But resource detection runs during SDK init, before the async context manager is registered, so the suppression is not in effect when the HTTP instrumentation observes the request. The probe surfaces as a dependency with a client-abort result code and ~1s+ duration.
Notably, the statsbeat implementation in the sibling `@azure/monitor-opentelemetry-exporter` package already does the correct thing — `StatsbeatMetrics.getResourceProvider()` checks `AKS_ARM_NAMESPACE_ID`, then `WEBSITE_SITE_NAME`, then `FUNCTIONS_WORKER_RUNTIME`, and only falls through to `getAzureComputeMetadata()` (the same IMDS endpoint) when none are set. We observe zero statsbeat IMDS probes on the same hosts, confirming that guard works. The distro's VM detector path is inconsistent with it.
**To Reproduce**
Steps to reproduce the behavior:
1. Deploy a Node app to Linux App Service with `applicationinsights` (v3.x) enabled and `APPLICATIONINSIGHTS_CONNECTION_STRING` set.
2. Let it run and restart normally.
3. Query the App Insights resource:
```kusto
dependencies
| where target contains "169.254.169.254"
| summarize calls = sum(itemCount) by name, resultCode, success
```
Every process start produces one failed dependency to `http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json`, success = false, with a client-abort result code and a duration at or above the detector's 1s internal timeout.
**Expected behavior**
`_initializeVmResourceAsync()` should be skipped when the hosting platform has already been identified as non-VM. Concretely: return early when `WEBSITE_SITE_NAME`, `FUNCTIONS_WORKER_RUNTIME`, or `AKS_ARM_NAMESPACE_ID` is set, matching the precedence order statsbeat already uses in `getResourceProvider()`.
Failing that, the probe should at minimum not surface in customer telemetry — but skipping a known-futile network call is the better fix.
**Additional context**
There is currently no supported way for a customer to opt out. `OTEL_NODE_RESOURCE_DETECTORS` does not gate this path (it feeds a separate `NodeSDK` detector list, which excludes the Azure detectors by default), and the detector list passed to `detectResources()` here is hardcoded. The only workarounds are patching the dependency or accepting the noise.
Impact in our case is modest in absolute terms — the probe is fire-and-forget, uses one socket, and is freed at the 1s timeout, so it is not a latency contributor. The cost is telemetry hygiene: a permanent 100%-failure dependency that shows up in failure dashboards, availability rollups, and any alerting keyed on dependency success rate, and that sends engineers chasing a phantom managed-identity problem. We spent real time establishing it was benign.
Volume scales with process restart frequency, not time, since it fires once per process start.
**Happy to send a PR**
If the team is open to it, I'm glad to put this up. The change I have in mind is an early return in `_initializeVmResourceAsync()`, using the same precedence order `StatsbeatMetrics.getResourceProvider()` already uses:
```ts
private _initializeVmResourceAsync(): void {
// IMDS is unroutable on AKS / App Service / Functions, and the failed probe
// surfaces as a dependency in customer telemetry — skip it when the platform
// has already been identified.
if (
process.env.AKS_ARM_NAMESPACE_ID ||
process.env.WEBSITE_SITE_NAME ||
process.env.FUNCTIONS_WORKER_RUNTIME
) {
return;
}
// ...existing detection
}
```
Completely understand if you'd rather handle it internally — just let me know either way.
2 条评论