Azure App Service 部署 .NET Framework 4.8 应用接入观测云最佳实践

    banner.png

    背景

    很多企业仍然有一批运行在 IIS 或 Azure App Service Windows 上的经典 ASP.NET Framework 4.8 应用。这类应用业务稳定、生命周期长,但在可观测性建设上常常面临一个现实问题:应用不是 ASP.NET Core,不能直接套用新框架里的标准接入方式;同时,生产环境又希望把 Trace、Log、Metrics 统一接入观测云,便于在故障排查、依赖分析和性能优化时形成完整链路。

    本文基于一次可运行的 ASP.NET Framework 4.8 Web API 示例,整理从本地 IIS 验证到 Azure App Service Windows 部署,再到手动初始化 OpenTelemetry SDK 并通过 OTLP 上报到观测云 DataKit 的完整实践。这里的“手动集成”不是在每个接口里手写 Span,而是在应用启动阶段统一初始化 OpenTelemetry SDK 和自动采集库,让 ASP.NET 入站请求、HttpClient 依赖调用、ILogger 日志和基础请求指标自动进入观测体系。

    适用场景与边界

    本文适用于运行在 IIS、IIS Express 或 Azure App Service Windows 上的 ASP.NET Framework 4.8 应用,尤其是 Web API、MVC、传统 Web Forms 与混合型老系统。部署目标必须是 Windows App Service,因为 .NET Framework 4.8 不支持 Linux App Service。

    本文推荐的接入方式是应用内手动初始化 OpenTelemetry SDK。它适合希望代码侧可控、采集逻辑透明、可以按服务逐步改造的团队。如果目标是完全无侵入接入,或者应用已经全面迁移到 ASP.NET Core,则应结合 Agent 注入或 ASP.NET Core 原生 OpenTelemetry 接入方式另行评估。

    方案概述

    整体链路可以理解为四层:ASP.NET Framework 4.8 应用运行在 App Service Windows 上;应用启动时初始化 OpenTelemetry .NET SDK;SDK 通过 OTLP HTTP/protobuf 或 gRPC 将 Trace、Log、Metrics 发往 DataKit;DataKit 再把数据转发到观测云工作空间。

    部署 DataKit

    DataKit 是一个开源的、跨平台的数据收集和监控工具,由观测云开发并维护。它旨在帮助用户收集、处理和分析各种数据源,如日志、指标和事件,以便进行有效的监控和故障排查。DataKit 支持多种数据输入和输出格式,可以轻松集成到现有的监控系统中。

    登录观测云控制台,在 集成 -> DataKit 选择对应安装方式,当前采用 Linux 主机部署 DataKit。

    采集器配置

    DataKit 部署完成后,开启opentelemetry 采集器,把 opentelemetry.conf.sample 复制一份到上层目录,去掉.sample 后缀。

    cp /usr/local/datakit/conf.d/samples/opentelemetry.conf.sample  /usr/local/datakit/conf.d/opentelemetry.conf
    

    依赖版本建议

    ASP.NET Framework 4.8 可以使用 OpenTelemetry .NET SDK,但要特别注意包版本一致性。实践中不建议让 NuGet 自动漂移到多个不同大版本,尤其是 Microsoft.Extensions.* 相关包,否则容易出现程序集加载失败、bindingRedirect 不完整或发布目录 DLL 不一致的问题。

    验证通过的一组依赖如下:

    <PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
    <PackageReference Include="Microsoft.AspNet.WebApi.WebHost" Version="5.2.9" />
    
    <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
    <PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
    
    <PackageReference Include="OpenTelemetry" Version="1.15.3" />
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNet" Version="1.15.2" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
    

    生产项目中建议建立包版本基线:OpenTelemetryOpenTelemetry.Exporter.OpenTelemetryProtocol 尽量保持同一小版本;Microsoft.Extensions.* 保持同一个主版本;升级前先在测试环境验证 Trace、Log、Metrics 三类信号。

    ASP.NET Framework 关键配置

    ASP.NET Framework 的入站请求自动采集不是只调用 .AddAspNetInstrumentation() 就完成了,还必须在 Web.config 里注册 TelemetryHttpModule,让 IIS 在请求管道中加载 OpenTelemetry 模块。

    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true">
        <add name="TelemetryHttpModule"
             type="OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule, OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule"
             preCondition="integratedMode,managedHandler" />
      </modules>
      <handlers>
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <remove name="OPTIONSVerbHandler" />
        <remove name="TRACEVerbHandler" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0"
             path="*."
             verb="*"
             type="System.Web.Handlers.TransferRequestHandler"
             preCondition="integratedMode,runtimeVersionv4.0" />
      </handlers>
    </system.webServer>
    

    如果遗漏这一步,常见现象是 outbound 接口能产生依赖调用 span,但普通入口请求没有 span_kind=server。这说明 HttpClient instrumentation 生效了,但 ASP.NET 请求管道没有挂上 OpenTelemetry HTTP Module。

    应用启动阶段初始化 SDK

    推荐在 Global.asax.csApplication_Start() 中统一初始化 OpenTelemetry,在 Application_End() 中释放 provider。这样业务代码不需要关心 exporter 生命周期,也能避免重复初始化。

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            TelemetryBootstrap.Initialize();
        }
    
        protected void Application_End()
        {
            TelemetryBootstrap.Dispose();
        }
    }
    

    初始化类里建议同时配置三类 provider:TracerProviderMeterProviderILoggerFactory。资源信息通过 ResourceBuilder 统一设置,至少包含 service.nameservice.version 和环境标签。

    var serviceName = GetSetting("OTEL_SERVICE_NAME") ?? "aspnet48-otel-demo";
    var resourceAttributes = GetSetting("OTEL_RESOURCE_ATTRIBUTES");
    
    var resourceBuilder = ResourceBuilder.CreateDefault()
        .AddService(serviceName: serviceName, serviceVersion: "1.0.0")
        .AddAttributes(ResourceAttributeParser.Parse(resourceAttributes));
    

    Trace 配置重点是启用 ASP.NET 入站请求和 HttpClient 依赖调用:

    tracerProvider = Sdk.CreateTracerProviderBuilder()
        .SetResourceBuilder(resourceBuilder)
        .AddAspNetInstrumentation(options =>
        {
            options.RecordException = true;
        })
        .AddHttpClientInstrumentation(options =>
        {
            options.RecordException = true;
        })
        .AddOtlpExporter(options =>
        {
            options.Protocol = OtlpExportProtocol.HttpProtobuf;
            options.Endpoint = new Uri(
                GetSetting("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")
                ?? GetSetting("OTEL_EXPORTER_OTLP_ENDPOINT")
                ?? "http://localhost:9529/otel/v1/traces");
        })
        .Build();
    

    Metrics 配置建议单独指向 /otel/v1/metrics。指标通常按周期导出,验证时要多请求几次接口并等待 30 到 60 秒。

    meterProvider = Sdk.CreateMeterProviderBuilder()
        .SetResourceBuilder(resourceBuilder)
        .AddAspNetInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(options =>
        {
            options.Protocol = OtlpExportProtocol.HttpProtobuf;
            options.Endpoint = new Uri(
                GetSetting("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT")
                ?? "http://localhost:9529/otel/v1/metrics");
        })
        .Build();
    

    日志建议统一使用 ILogger,并通过 OpenTelemetry Logs exporter 导出。请求上下文中的日志可以自动关联 trace id;启动阶段、后台线程或非请求上下文中的日志 trace id 为 0 是正常现象。

    loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Information);
        builder.AddOpenTelemetry(options =>
        {
            options.SetResourceBuilder(resourceBuilder);
            options.IncludeFormattedMessage = true;
            options.IncludeScopes = true;
            options.ParseStateValues = true;
            options.AddOtlpExporter(exporterOptions =>
            {
                exporterOptions.Protocol = OtlpExportProtocol.HttpProtobuf;
                exporterOptions.Endpoint = new Uri(
                    GetSetting("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT")
                    ?? GetSetting("OTEL_EXPORTER_OTLP_ENDPOINT")
                    ?? "http://localhost:9529/otel/v1/logs");
            });
        });
    });
    

    配置读取建议优先使用环境变量,再回退到 Web.config。这样本地 IIS 可以使用 Web.config,Azure App Service 则通过应用设置覆盖生产值。

    App Service 部署流程

    Azure 侧需要两个核心资源:App Service Plan 和 Web App。Plan 决定区域、规格和计费方式;Web App 是应用本体,负责承载代码、环境变量、日志和部署记录。

    创建 Windows App Service Plan:

    az appservice plan create \
      --resource-group <resource-group> \
      --name <plan-name> \
      --location <region> \
      --sku F1
    

    创建 ASP.NET 4.8 Web App:

    az webapp create \
      --resource-group <resource-group> \
      --plan <plan-name> \
      --name <webapp-name> \
      --runtime "ASPNET|V4.8"
    

    标准发布建议在 Windows 开发机或构建机上使用 Visual Studio 2022 / MSBuild 发布,确保发布目录包含完整的 bin 依赖。

    msbuild .\src\AspNet48OtelDemo\AspNet48OtelDemo.csproj `
      /p:Configuration=Release `
      /p:DeployOnBuild=true `
      /p:WebPublishMethod=FileSystem `
      /p:PublishUrl=.\publish
    

    然后压缩发布目录内容,并通过 ZIP 部署:

    az webapp deploy \
      --resource-group <resource-group> \
      --name <webapp-name> \
      --src-path publish.zip \
      --type zip
    

    App Service 应用设置

    不要把生产 DataKit 地址硬编码到 Web.config。推荐通过 App Service 的 Configuration / Application settings 配置环境变量。

    HTTP/protobuf 示例:

    az webapp config appsettings set \
      --resource-group <resource-group> \
      --name <webapp-name> \
      --settings \
        OTEL_SERVICE_NAME="aspnet48-otel-demo" \
        OTEL_RESOURCE_ATTRIBUTES="deployment.environment=prod,service.namespace=demo" \
        OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
        OTEL_EXPORTER_OTLP_ENDPOINT="http://<datakit-host>:9529/otel" \
        OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://<datakit-host>:9529/otel/v1/traces" \
        OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://<datakit-host>:9529/otel/v1/metrics" \
        OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://<datakit-host>:9529/otel/v1/logs" \
        Demo__SelfBaseUrl="https://<webapp-name>.azurewebsites.net"
    

    App Service 修改应用设置后会触发重启。如果通过命令行连续配置多个设置,建议配置完成后显式执行一次:

    az webapp restart \
      --resource-group <resource-group> \
      --name <webapp-name>
    

    验证方法

    应用部署后先验证应用本身,再验证观测数据。建议提供一个健康检查接口、一个普通业务接口、一个 outbound 依赖调用接口,以及一个主动抛错接口。

    curl https://<webapp-name>.azurewebsites.net/api/healthz
    curl https://<webapp-name>.azurewebsites.net/api/telemetry/ping
    curl https://<webapp-name>.azurewebsites.net/api/telemetry/outbound
    curl https://<webapp-name>.azurewebsites.net/api/telemetry/fail
    

    预期结果:

    • healthz 返回 200,证明应用已启动。
    • ping 返回 200,产生 ASP.NET 入站请求 span、日志和请求指标。
    • outbound 返回 200,产生入口 server span 和 HttpClient client span。
    • fail 返回 500,产生错误 trace 和异常日志。

    观测云侧重点看四件事:服务名是否等于预期的 service.name;入口请求是否存在 span_kind=server;依赖调用是否存在 span_kind=client;异常接口是否被标记为 error。Metrics 需要等待导出周期,不要按单次请求即时判断。

    Otel 链路示例

    Otel 指标示例

    Otel 日志示例

    生产建议

    生产环境应把 OTEL_SERVICE_NAMEOTEL_RESOURCE_ATTRIBUTES、OTLP endpoint 全部放到 App Service 应用设置中管理,避免把生产地址和敏感信息写入代码或 Web.config。环境标签建议统一使用 deployment.environment=dev|test|prod,并保持服务命名稳定,便于观测云中跨环境筛选。

    Datakit 建议和AppService 部署在同一内网环境下。

    接入Demo 地址参考: https://github.com/GuanceDemo/azure-appservice-aspnet48-otel-demo

    参考资料

    联系我们

    加入社区

    微信扫码
    加入官方交流群

    立即体验

    在线开通,按量计费,真正的云服务!

    立即开始

    选择观测云版本

    代码托管平台