☰
Azure 开发实战:托管身份、Bicep 与可观测性避坑指南
2026/10/20 17:41:53 网站建设 项目流程

简介:《Azure开发实战精华》是一本面向云开发者的实战型技术指南,适合具备一定开发基础、希望系统掌握微软Azure平台核心技能的工程师与架构师阅读。全书围绕现代化云应用构建展开,重点讲解Azure App Service、Kubernetes、Service Fabric与Azure Search等关键服务的使用与优化,并覆盖容器、无服务器架构、微服务及AI集成等主流技术方向,兼顾基础入门与进阶提升。资源包内共1个PDF文件,压缩包大小约24.71MB,内容完整、便于离线阅读与检索。书中通过真实案例与实操指导,帮助读者理解PaaS生态的部署流程、性能调优与持续集成思路,并配有配套代码与资源,方便动手实践、加深理解。目前已有55人学习下载,适合希望提升云技能、推动项目落地的开发人员参考。

1. 从一次部署翻车说起:Azure 开发实战到底在解决什么问题

很多团队第一次把内部系统往 Azure 上搬的时候,都会经历一个相似的场景:本地跑得好好的服务,一上云就出现冷启动超时、连接串读不到、托管身份拿不到令牌。表面看是配置问题,根子往往在于没搞清楚 Azure 的开发模型和本地开发有本质区别——它不是"把服务器搬到别人机房",而是一整套围绕身份、配置、资源编排重新组织的运行时。

Azure 开发实战精华这个方向,核心就是把这套运行时讲透:用托管身份替代硬编码密钥,用配置中心替代散落的 appsettings,用基础设施即代码替代手工点门户,用可观测性三件套替代到处打日志。它适合已经会写业务代码、但一上云就踩坑的后端和全栈工程师,也适合需要把部署流程标准化的 DevOps 同学。接下来的内容按"先立住概念、再动手复现、最后讲坑"的顺序展开,每一步都给可抄的命令和参数。

2. 托管身份与配置:把密钥从代码里彻底赶出去

2.1 为什么托管身份是 Azure 开发的第一道分水岭

本地开发时,连接数据库、访问存储、调用其他服务,最省事的做法就是把密钥写进配置文件。这套做法一上云就变成负债:密钥要轮换、要分环境、要控制谁能看。Azure 的托管身份(Managed Identity)解决的正是这个问题——它让计算资源(应用服务、容器应用、虚拟机、函数)在 Azure 内部自动获得一个身份,由平台负责凭证的签发和轮换,代码里只需要向令牌服务要一个 access token,不需要知道任何密钥。

托管身份分两类:系统分配(System-assigned)跟随资源生命周期,资源删了身份就没了;用户分配(User-assigned)是独立资源,可以挂到多个计算资源上,适合多服务共享同一套权限的场景。选型上,如果只是一个应用独占权限,用系统分配最省事;如果是多个服务需要访问同一组资源,用户分配更好管理,因为权限配置只做一次。

代码层面,最推荐的方式是用 Azure 的身份客户端库配合默认凭证链。默认凭证链会依次尝试环境变量、托管身份、开发者本地的登录态,这样同一份代码在本地和云上都能跑,不需要写 if 判断环境。

# 使用默认凭证链获取令牌,本地和云端同一份代码 from azure.identity import DefaultAzureCredential from azure.keyvault.secrets import SecretClient # DefaultAzureCredential 会自动尝试: # 1. 环境变量中的服务主体凭证 # 2. 托管身份(部署到 Azure 计算资源时) # 3. 开发者本地 az login 的登录态 credential = DefaultAzureCredential() # 用凭证访问 Key Vault,代码里不出现任何密钥 client = SecretClient( vault_url="https://<your-vault-name>.vault.azure.cn/", credential=credential ) secret = client.get_secret("db-connection-string") print(secret.value)

这段代码的关键在于DefaultAzureCredential的尝试顺序。本地开发时你只需要执行一次az login,凭证链就会用你的登录态;部署到应用服务并开启托管身份后,凭证链自动切换到托管身份,代码一行不用改。参数上,vault_url必须用你实际创建的 Key Vault 地址,注意不同云环境的域名后缀不同,国内环境是.vault.azure.cn,国际版是.vault.azure.net,写错了会直接报域名解析失败。

2.2 用配置中心统一管理多环境参数

密钥交给 Key Vault 之后,剩下的普通配置(功能开关、超时时间、第三方接口地址)如果还散在各个环境的配置文件里,迟早会出现"测试环境改了忘了同步生产"的事故。Azure 应用配置(App Configuration)就是干这个的:它是一个集中式的键值存储,支持标签(label)区分环境,支持引用 Key Vault 里的密钥,还支持动态刷新。

一个常见的做法是:App Configuration 里存普通配置,Key Vault 里存密钥,App Configuration 通过引用指向 Key Vault,应用只需要连 App Configuration 一个端点。这样权限也简化了——应用只需要对 App Configuration 有读取权限,对 Key Vault 的访问由 App Configuration 的身份代理。

# 创建 App Configuration 实例 az appconfig create \ --name my-appconfig \ --resource-group my-rg \ --location chinanorth2 \ --sku standard # 写入一个带环境标签的配置项 az appconfig kv set \ --name my-appconfig \ --key "OrderService:TimeoutSeconds" \ --value "30" \ --label "production" # 写入一个指向 Key Vault 的引用 az appconfig kv set-keyvault \ --name my-appconfig \ --key "OrderService:DbConnection" \ --secret-identifier "https://my-vault.vault.azure.cn/secrets/db-connection-string" \ --label "production"

命令里的--label是环境隔离的核心,读取时指定 label 就能拿到对应环境的配置。set-keyvault创建的是一个引用而不是明文,应用读取时 App Configuration 会用自身的托管身份去 Key Vault 取真实值,所以应用不需要 Key Vault 的直接权限。这里有个容易忽略的点:App Configuration 的托管身份必须被授予 Key Vault 的"密钥读取者"角色,否则引用会解析失败,报的是权限错误而不是配置缺失,排查时容易走弯路。

2.3 在应用里接入配置中心并做动态刷新

配置写进去了,应用怎么读、怎么在不重启的情况下感知变更,是落地时最实际的问题。以 .NET 为例,官方提供了配置提供程序,几行代码就能接入;Python 和 Java 也有对应的库。动态刷新的原理是应用定期轮询一个哨兵键(sentinel key),哨兵变了就重新拉取全部配置。

// Program.cs 中接入 App Configuration var builder = WebApplication.CreateBuilder(args); // 用默认凭证连接 App Configuration,指定环境标签 builder.Configuration.AddAzureAppConfiguration(options => { options.Connect(new Uri("https://my-appconfig.azconfig.io"), new DefaultAzureCredential()) .Select("OrderService:*", "production") // 只拉取该前缀和标签 .ConfigureKeyVault(kv => { kv.SetCredential(new DefaultAzureCredential()); }) .ConfigureRefresh(refresh => { // 监听哨兵键,变化时刷新全部配置 refresh.Register("Sentinel", "production", refreshAll: true) .SetRefreshInterval(TimeSpan.FromSeconds(30)); }); }); // 启用动态刷新中间件 builder.Services.AddAzureAppConfiguration(); var app = builder.Build(); app.UseAzureAppConfiguration();

Select的参数决定了拉取范围,前缀加标签能有效减少不必要的配置传输。ConfigureRefresh里的哨兵键是一个约定:你改完一批配置后,顺手更新哨兵键的值,应用就会在下一个刷新周期拉取全部变更。SetRefreshInterval设成 30 秒是常见折中,设太短会增加请求量和费用,设太长则变更生效慢。需要提醒的是,动态刷新只对通过配置提供程序读取的值生效,如果你在启动时把配置读进了单例对象,刷新不会自动更新那个对象,这是新手最容易翻车的地方。

3. 基础设施即代码:用 Bicep 把环境一次性描述清楚

3.1 为什么门户点出来的环境不可复现

手工在门户上创建资源,第一次能跑通,但第二次、第三次就会出问题:参数记不全、顺序搞错、命名不一致。更麻烦的是,没人能说清生产环境到底有哪些资源、各自什么配置。基础设施即代码(IaC)要解决的就是这个——把环境的定义写成文件,纳入版本控制,每次部署都是幂等的。

Azure 上主流的 IaC 工具是 Bicep 和 Terraform。Bicep 是 Azure 原生的领域特定语言,语法比 ARM 模板简洁很多,编译后就是 ARM 模板,和 Azure 的资源模型贴合最紧;Terraform 跨云能力强,生态成熟,但需要维护状态文件。如果团队只做 Azure,Bicep 的学习成本和维护成本都更低;如果有多云诉求,Terraform 更合适。下面以 Bicep 为例,因为它在 Azure 开发实战里出现频率最高。

3.2 用 Bicep 描述一个应用服务加数据库的最小环境

一个可复现的最小环境通常包含:应用服务计划、应用服务、SQL 服务器和数据库、Key Vault、App Configuration,以及它们之间的权限绑定。Bicep 用param声明参数,用resource声明资源,用module拆分复用。

// main.bicep - 最小可运行环境 param location string = resourceGroup().location param appName string param sqlAdminLogin string @secure() param sqlAdminPassword string // 应用服务计划 resource plan 'Microsoft.Web/serverfarms@2022-09-01' = { name: '${appName}-plan' location: location sku: { name: 'B1' // 基础层,开发测试够用 tier: 'Basic' } kind: 'linux' properties: { reserved: true // Linux 计划必须为 true } } // 应用服务,开启系统分配托管身份 resource webApp 'Microsoft.Web/sites@2022-09-01' = { name: appName location: location identity: { type: 'SystemAssigned' } properties: { serverFarmId: plan.id siteConfig: { linuxFxVersion: 'PYTHON|3.11' appSettings: [ { name: 'AppConfigEndpoint' value: appConfig.properties.endpoint } ] } } } // App Configuration resource appConfig 'Microsoft.AppConfiguration/configurationStores@2023-03-01' = { name: '${appName}-config' location: location sku: { name: 'standard' } } // 给应用服务的托管身份授予 App Configuration 数据读取者角色 resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(appConfig.id, webApp.id, 'appconfig-reader') scope: appConfig properties: { roleDefinitionId: subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '516239f1-63e1-4d78-a4de-a76dfb45952d' // App Configuration Data Reader ) principalId: webApp.identity.principalId principalType: 'ServicePrincipal' } }

这段模板里几个参数值得展开。reserved: true是 Linux 应用服务计划的必填项,漏了会创建成 Windows 计划,部署 Linux 运行时直接失败。linuxFxVersion的格式是"运行时|版本",写错版本号会报找不到运行时。角色分配的name用guid()生成是为了保证幂等——同样的三个输入永远生成同一个 GUID,重复部署不会报冲突。roleDefinitionId用的是内置角色的固定 ID,App Configuration Data Reader 的 ID 就是上面那个,写错了会报角色不存在。

3.3 部署命令与参数覆盖

Bicep 写好后,用 Azure CLI 部署。开发环境用资源组范围部署,生产环境通常用订阅范围配合多个资源组。

# 先编译检查语法,不实际部署 az bicep build --file main.bicep # 预览变更(what-if),确认不会误删资源 az deployment group what-if \ --resource-group my-rg \ --template-file main.bicep \ --parameters appName=myapp sqlAdminLogin=sqladmin sqlAdminPassword='<password>' # 正式部署 az deployment group create \ --resource-group my-rg \ --template-file main.bicep \ --parameters appName=myapp sqlAdminLogin=sqladmin sqlAdminPassword='<password>'

what-if是部署前必做的一步,它会列出将要创建、修改、删除的资源。血泪经验是:如果模板里删掉了某个资源定义,what-if会显示删除,正式部署就真删了,数据库这种有状态资源一旦被删很难恢复。所以生产环境的部署一定要先看what-if,确认没有意外的删除操作。参数传递上,密码这类敏感值不要写在命令行历史里,用参数文件配合 Key Vault 引用,或者用环境变量传入。

4. 可观测性落地:日志、指标、追踪三件套怎么配

4.1 应用洞察的接入方式和采样策略

云上排查问题和本地最大的区别是:你不能登到机器上看日志。可观测性必须提前埋好,出事时才有数据。Azure 上的可观测性核心是 Application Insights,它收集请求、依赖、异常、追踪和自定义指标。接入方式有两种:自动插桩(改配置就行)和手动埋点(用 SDK)。自动插桩适合快速起步,手动埋点适合需要精细控制的场景。

采样是必须配置的,否则高流量下日志量会失控,费用飙升。自适应采样是默认行为,它会根据流量自动调整采样率;固定速率采样则按你设定的比例采。开发环境可以全采,生产环境建议开自适应采样,同时对关键业务操作强制保留。

# Python 应用接入 Application Insights from azure.monitor.opentelemetry import configure_azure_monitor from opentelemetry import trace # 连接字符串从环境变量读取,不硬编码 configure_azure_monitor( connection_string="InstrumentationKey=<your-key>;IngestionEndpoint=https://chinanorth2-1.in.applicationinsights.azure.cn/" ) tracer = trace.get_tracer(__name__) def process_order(order_id): # 自定义 span,把业务操作纳入追踪 with tracer.start_as_current_span("process_order") as span: span.set_attribute("order.id", order_id) # 业务逻辑... span.set_attribute("order.status", "completed")

连接字符串里的IngestionEndpoint在国内环境必须指向.azure.cn的端点,用默认的国际版端点会导致数据上报失败,而且失败是静默的——应用不报错,但 Application Insights 里查不到数据,这个坑很多人踩过。set_attribute添加的自定义属性会成为可查询的维度,排查时能按订单号精确定位,比翻日志高效得多。

4.2 用 KQL 查询定位线上问题

数据上报之后,真正体现功力的是查询。Application Insights 用 KQL(Kusto 查询语言)查询,语法上手快,但要用好需要理解表结构。常用的表有requests(请求)、dependencies(依赖调用)、exceptions(异常)、traces(日志)。

// 找出过去一小时失败率最高的 5 个接口 requests | where timestamp > ago(1h) | summarize total = count(), failed = countif(success == false) by name | extend failureRate = failed * 100.0 / total | where total > 10 // 过滤掉样本太少的接口 | top 5 by failureRate desc | project name, total, failed, failureRate

这个查询先按接口名聚合,算出总数和失败数,再算失败率,最后取前五。where total > 10这个过滤很关键,否则样本只有一两次的接口会以 100% 失败率排在最前面,干扰判断。实际排查时,定位到接口后,下一步是关联dependencies表看是哪个下游调用慢或失败,再关联exceptions看具体异常堆栈,这样一层层缩小范围。

4.3 告警规则怎么设才不吵人

告警设得太松,出事不报;设得太紧,天天响,最后没人看。合理的做法是分层:核心业务指标(下单成功率、支付成功率)设严格阈值,基础设施指标(CPU、内存)设宽松阈值,再配一个综合的健康检查告警兜底。

指标告警的评估频率和聚合窗口要匹配业务节奏。比如下单成功率,用 5 分钟窗口、1 分钟评估频率比较合适,既能快速发现,又不会被瞬时抖动触发。静态阈值适合有明确基线的指标,动态阈值适合波动大的指标,它会根据历史数据自动学习正常范围。告警动作上,建议先接到一个统一的告警通道,再由通道分发,避免每个告警单独配接收人导致维护混乱。

5. 避坑与排查:那些文档里不会写的翻车现场

5.1 托管身份报错但角色明明配了

现象:应用启动时报 403 或"身份验证失败",但检查角色分配,明明已经给了权限。原因通常是角色分配的传播延迟,或者分配给了错误的身份。系统分配身份在资源创建时才生成,如果角色分配和资源创建写在同一个模板里,可能出现 principalId 还没生成就尝试分配的情况。解决:把角色分配拆成独立部署,或者在模板里用dependsOn显式声明依赖。另外,用户分配身份要确认应用实际挂载的是哪一个,多身份场景下容易搞混。

5.2 配置中心读取超时导致启动失败

现象:应用启动时卡住,最后报配置读取超时。原因一般是网络访问控制没放行,或者托管身份没有数据读取者角色。App Configuration 如果开了私有终结点或防火墙,应用的出站流量必须能到达;角色没配的话,请求会返回 403,但有些 SDK 会重试多次才报错,表现为启动慢。解决:先用az appconfig kv list确认 CLI 能读到,再检查应用的托管身份角色,最后确认网络规则。

5.3 Bicep 部署报资源已存在但无法更新

现象:部署时报"资源已存在"或"属性冲突",但资源确实是模板里定义的。原因通常是资源名称或类型和现有资源不完全匹配,比如大小写差异、区域不同,或者资源是被其他方式创建的,属性不兼容。解决:用what-if看具体差异,必要时先导入现有资源(az deployment group create配合--mode Incremental),或者手动对齐属性。切忌直接删了重建,有状态资源会丢数据。

5.4 日志查不到但应用明明在跑

现象:应用正常运行,但 Application Insights 里没有任何数据。原因通常是连接字符串的端点不对(国内环境用了国际版端点),或者采样率设成了 0,或者 SDK 初始化在日志框架之后导致早期日志丢失。解决:先确认连接字符串的IngestionEndpoint后缀正确,再检查采样配置,最后确认 SDK 初始化顺序——configure_azure_monitor要在其他日志配置之前调用。

5.5 动态刷新不生效

现象:在配置中心改了值,应用没反应。原因通常是没启用刷新中间件,或者哨兵键没更新,或者读取配置的方式绕过了配置提供程序。解决:确认UseAzureAppConfiguration中间件已注册,确认哨兵键的值确实变了,确认业务代码是通过IConfiguration或等价接口读取而不是启动时缓存到单例。刷新间隔也要检查,设成几分钟的话,等待时间会很长。

6. 进阶技巧:用部署槽做零停机发布和快速回滚

前面讲的都是把环境跑起来,这一章讲怎么安全地更新它。应用服务的部署槽(Deployment Slot)是一个被低估的功能:它让你在不动生产流量的情况下,把新版本部署到一个独立的槽里,预热好之后再切换流量。切换是瞬时的,出问题再切回来也是瞬时的,这就是零停机发布和快速回滚的实现方式。

典型流程是:创建 staging 槽,把新版本部署到 staging,等它在 staging 上预热完成(应用启动、缓存加载、连接池建立),然后执行交换。交换的本质是把两个槽的配置和流量路由对调,所以生产槽的配置(比如连接字符串)会跟着过去,这一点要特别注意——如果 staging 和生产用不同的数据库,交换后生产会连到 staging 的数据库,这是常见的翻车点。解决办法是用槽特定的配置设置(slot setting),把不希望被交换的配置标记为"部署槽设置"。

# 创建 staging 槽 az webapp deployment slot create \ --name myapp \ --resource-group my-rg \ --slot staging # 部署新版本到 staging(以容器为例) az webapp config container set \ --name myapp \ --resource-group my-rg \ --slot staging \ --docker-custom-image-name myregistry.azurecr.cn/myapp:v2 # 预热:直接请求 staging 的地址,确认健康 curl -f https://myapp-staging.chinacloudsites.cn/health # 执行交换 az webapp deployment slot swap \ --name myapp \ --resource-group my-rg \ --slot staging \ --target-slot production

交换前一定要做预热请求,否则第一个真实用户会承担冷启动的代价。curl -f的-f参数让 HTTP 错误码返回非零退出码,方便在脚本里判断。交换后如果发现问题,再执行一次同样的 swap 命令就能切回来,因为交换是对调,再对调一次就恢复了。这个"后悔药"机制是部署槽最大的价值。

验证发布是否成功,不能只看交换命令返回成功,要看几个信号:生产端点的健康检查返回 200、Application Insights 里新版本的请求成功率正常、错误率没有突增、关键依赖的调用延迟在基线范围内。我一般会在交换后观察 5 到 10 分钟,确认这几个指标都平稳,才算发布完成。

一个我踩过的坑:曾经把连接字符串配在了应用设置里但没标记为槽设置,交换后生产环境连到了测试数据库,好在发现得早。从那以后,凡是环境相关的配置,一律标记为部署槽设置,这个习惯帮我省了很多麻烦。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询