简介:《Azure开发实战精华》是一本面向云应用开发者的实战指南,由Packt Publishing出品,围绕微软Azure平台系统讲解现代化云应用的构建方法。内容从应用服务的部署与持续集成讲起,逐步深入容器编排、微服务框架、无服务器架构及人工智能服务集成,并通过真实案例演示搜索服务的索引构建与查询优化。书中针对应用服务性能调优、容器化迁移、微服务间通信与安全配置等常见场景也给出了可操作的建议,无论初学者还是进阶工程师都能从中找到适合自己阶段的落地方案。压缩包约24.71MB,内含1个PDF文件,完整保留全书内容,便于按章节查阅与精读。目前已有55人学习下载,读者可复用书中的代码示例和架构方案,减少独立摸索成本,在Azure平台上更快完成从开发、部署到优化的闭环。
1. 把“Azure开发实战精华”拆开看,最值钱的是“实战”两个字
带着 Azure 开发实战精华这个目标动手时,我和团队反复强调的一件事是:Azure 开发不是云上写代码,而是先把身份、资源和部署边界理清楚。很多从业者在本地把 Web API 跑通很轻松,一上 Azure 就找不到日志,部署了三次还不知道资源去了哪个资源组;也有人把 ARM/Bicep 模板当成负担,每次上线全靠手点门户。这份笔记想解决的正是这两类问题:讲清楚订阅、资源组、身份、App Service 和 Cosmos DB 这条最小闭环,再用一条 GitHub Actions 流水线把代码从仓库送到云端,最后把我踩过的坑按“现象→原因→解决”逐条列出来。适合想把 Azure 真正用起来、而不是只停留在概念图里的人,也适合前端开发、agent 开发方向想快速托管一个后端服务的从业者。
2. 开发前的三件套:订阅、资源组与本地工具链
2.1 先建资源组,而不是先找服务
Azure 的计费边界是订阅,逻辑边界是资源组。我见过不少新手在门户里直接点“创建虚拟机”,创建完才发现资源被随机丢进了一个叫 Default 的资源组,后续要按项目做权限隔离、成本核算时全都乱了。资源组本身不产生费用,但它决定了资源的生命周期:删除一个资源组,组内所有资源会一起被清掉,这既是后悔药也是引爆点。
我一般把“项目 + 环境”作为资源组的命名维度,比如 rg-demo-dev、rg-demo-prod,而不是用“default”或者个人名字。这样做有两个直接好处:第一,成本分析里按资源组聚合,你一眼能看出 dev 环境花了多少钱;第二,权限可以精确到资源组,给外包或测试人员只开 dev 资源组的读权限,不至于碰到生产资源。资源组的创建用命令行就能完成,比在门户里点来点去更可复现:
az login az account show --output table az group create --name rg-demo-dev --location eastasia第一条az login会在浏览器里走一次设备码登录,完成之后你的本地终端就持有 Azure CLI 的访问令牌。第二条az account show用来确认当前订阅是对的,这一步千万别跳,尤其是在一个账号关联了多个订阅时,后续所有命令都会打到当前订阅上。第三条创建资源组,--name要全局唯一吗?不需要,资源组名称只在订阅内唯一;--location决定资源组所在的区域元数据位置,但注意资源组区域不代表里面每个资源都必须用同一个区域,只是推荐统一。
2.2 本地身份与 CI 身份是两套玩法
本地开发用az login交互登录,但在 GitHub Actions 或 Jenkins 里跑同样的命令,马上会遇到“No subscriptions found”之类的问题,原因很简单:无人值守环境无法完成浏览器交互,而且交互登录拿到的身份在 runner 上没有经过授权映射。CI 里应该用服务主体(Service Principal),这是一种应用身份,类似为流水线单独配一张门禁卡。
创建服务主体的命令是:
az ad sp create-for-rbac \ --name sp-demo-cicd \ --role contributor \ --scopes /subscriptions/<subscription-id>/resourceGroups/rg-demo-dev--role contributor表示在指定范围内拥有管理权限,但拿不到读取 AAD 用户这类更高层权限;--scopes把权限范围限定在 rg-demo-dev 这一个资源组,避免 CI 身份误操作其他环境。执行完会输出 clientId、clientSecret 和 tenantId 三个值,把这三样存到你 CI 平台的 Secrets 里,之后流水线运行时用az login --service-principal -u <clientId> -p <clientSecret> --tenant <tenantId>登录。这套做法同样适用于 agent 开发、前端开发场景里需要自动构建后端的项目,身份链路是一致的。
2.3 本地工具链:CLI、Bicep 与模拟器
如果只装一个东西,我推荐 Azure CLI。它既是命令入口,也是 Bicep 编译器的宿主。Visual Studio Code 里的 Azure 扩展我当作辅助用,比如浏览资源、查看日志流,但真正要写基础设施代码时还是要回到 Bicep。Bicep 是 Azure 的声明式模板语言,它比 ARM JSON 简洁得多,编译之后会生成标准 ARM 模板,部署时不需要额外装插件。
本地开发还有一个容易被忽略的环节:模拟器。像 Azurite 可以在本地模拟 Blob、Queue 和 Table 存储,开发阶段往本地环境写代码,不用每次读写都连云端产生费用,也避开网络抖动。对于 App Service 和 Cosmos DB 这类 PaaS 服务没有官方本地模拟器,我的做法是先把业务逻辑抽象成接口,本地用内存实现,云上才走真实服务,等部署到 dev 环境再联调。这样本地迭代速度最快,也不会早早就欠下云资源账单。
az bicep install az bicep version az extension add --name azure-devopsaz bicep install负责安装 Bicep CLI,安装完以后az bicep version能确认版本。az extension add --name azure-devops是可选命令,只有当你需要从命令行操作 Azure DevOps 流水线时才需要装。还有一点值得提醒:Azure CLI 的地域扩展名不是所有服务都一样,比如东亚区域是 eastasia,而美国西部是 westus,在脚本里把 region 抽成变量可以省掉很多后续替换。
3. 最小可运行:用 App Service + Cosmos DB 把一个 Web API 跑上云
3.1 为什么选这两个服务当起点
把一个 Web API 跑上云,可选的路线很多:虚拟机自建 Nginx、AKS 容器编排、Function 无服务器。我为什么推荐 App Service 配 Cosmos DB 作为第一套组合?因为它的心智负担最低。App Service 负责进程托管和自动伸缩,你不需要维护虚拟机补丁;Cosmos DB 负责文档存储,提供 SQL API 兼容的访问方式,比直接上手 AKS 加 PostgreSQL 运维要轻得多。
分布式开发场景里,Cosmos DB 的底气是它在多个区域可以复制数据,写多区域或多写配置能动态调整,但对新手来说这不是第一优先级。第一优先级是“能有一个持久化存储,并且代码里读写的姿势和普通 NoSQL 客户端一样”。Cosmos DB 的 SQL API 用起来接近 MongoDB 的直觉,学习成本低;而 App Service 的部署只需要一个 ZIP 包或者 git push,后面接 GitHub Actions 也顺。另一个理由是成本可控,App Service 有免费层 F1,Cosmos DB 有免费层账户,个人项目或 demo 完全够用,不至于一上来就烧钱。
3.2 用 Bicep 声明基础设施,而不是手点门户
我见过太多团队在门户里手动创建 App Service,然后谁都不知道这个资源的配置在代码库里怎么追溯。Azure 的声明式部署最佳实践是用模板管理,Bicep 就是用来干这个的。下面这份精简的 main.bicep 声明了两个资源:App Service 计划和一个带系统托管身份(System Assigned Managed Identity)的 Web App。
param environment string = 'dev' param location string = resourceGroup().location param appName string = 'app-${environment}-demo' resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { name: 'plan-${environment}' location: location sku: { name: 'B1' tier: 'Basic' } } resource webApp 'Microsoft.Web/sites@2022-03-01' = { name: appName location: location properties: { serverFarmId: appServicePlan.id httpsOnly: true } identity: { type: 'SystemAssigned' } }这段模板里,resourceGroup().location是 Bicep 提供的内置函数,它直接取当前部署资源组的位置,省去在每个参数文件里重复写 location 的麻烦。但要注意,如果你的 Cosmos DB 希望放在另一个区域,这里就不能偷懒,必须显式传参。sku.name: 'B1'是 Basic 层最小的付费规格,适合测试,不是生产。httpsOnly: true会强制 HTTP 跳转到 HTTPS,默认情况下 App Service 允许 HTTP 访问,你不想上线后被人用明文流量打,就把这个开关打开。identity.type: 'SystemAssigned'是后面用托管身份访问其他资源的前提,它会自动生成一个 AAD 身份,而你不需要管密钥轮换。
部署这份模板只需要一条命令:
az deployment group create \ --resource-group rg-demo-dev \ --template-file main.bicep \ --parameters environment=dev--parameters environment=dev会把 Bicep 模板里的environment参数覆盖为 dev,于是生成的 App Service 实例名是 app-dev-demo。部署完成后,用az webapp show --name app-dev-demo --resource-group rg-demo-dev --query hostNames -o tsv就能拿到公网域名。执行这条命令前请确认你已经az login,并且当前订阅是开发订阅。
3.3 把连接字符串喂给应用,但不要让代码硬编码
App Service 的应用设置(App Settings)本质上是注入到进程的环境变量。代码里读取方式与本地开发完全一致,比如 .NET 用IConfiguration["COSMOS_CONNECTION_STRING"],Node.js 用process.env.COSMOS_CONNECTION_STRING。问题是连接字符串从哪来、怎么安全地写进设置。
Cosmos DB 的账号创建后,可以用命令行查连接字符串,再以应用设置的方式注入:
az cosmosdb create \ --name cosmos-dev-demo \ --resource-group rg-demo-dev \ --kind GlobalDocumentDB az webapp config appsettings set \ --name app-dev-demo \ --resource-group rg-demo-dev \ --settings "COSMOS_CONNECTION_STRING=$(az cosmosdb keys list \ --name cosmos-dev-demo \ --resource-group rg-demo-dev \ --type connection-strings \ --query '[0].connectionString' -o tsv)"第一段创建 Cosmos DB 账户,--kind GlobalDocumentDB表示默认的 SQL API 类型。第二段命令把连接字符串输出到应用设置里,这里用了$(...)命令替换,执行顺序是先查连接字符串再写入 App Service。注意这个做法适合入门演示,生产环境我一般把连接字符串放进 Key Vault,然后在 App Service 里用 Key Vault 引用,而不是直接在命令行里传递连接字符串。
如果不想折腾连接字符串,还有一条更优雅的路线:托管身份。前面 Bicep 里已经给 Web App 开了 SystemAssigned 身份,你需要把这个身份添加到 Cosmos DB 的某类角色上,让它能读数据。常见的做法是分配“Cosmos DB Built-in Data Reader”这样的数据平面角色:
az role assignment create \ --assignee $(az webapp identity show --name app-dev-demo -g rg-demo-dev --query principalId -o tsv) \ --role "Cosmos DB Built-in Data Reader" \ --scope $(az cosmosdb show --name cosmos-dev-demo -g rg-demo-dev --query id -o tsv)这里第一层$(...)取的是 Web App 托管身份的 objectId,第二层$(...)取的 Cosmos DB 账户的资源 ID。--role的完整列表会因为 Azure 环境有细微差异,你可以先用az role definition list --query "[?contains(roleName, 'Cosmos DB')].roleName"查一下当前环境准确的角色名。托管身份的好处是密钥不会出现在任何地方,代码里不需要保存密码,拿到的是无密钥环境变量。代价是排查权限问题时需要多理解一层 AAD 角色分配,新手容易在这里卡住。
4. 把部署交给流水线:Bicep 参数化与 GitHub Actions 的配合
4.1 环境差异交给参数文件,不要复制模板
当你有 dev、stage、prod 三个环境时,最忌讳的做法是把 main.bicep 复制成 three 份,然后各自修改。Bicep 原生支持参数文件,环境差异只体现在参数上,基础设施的声明只有一份。Bicep 的参数化功能比 ARM JSON 更直白,用.bicepparam文件即可:
// infra/main.dev.bicepparam using './main.bicep' param environment = 'dev' param location = 'eastasia'using关键字把这个参数文件和主模板绑定,之后运行az deployment group create --parameters infra/main.dev.bicepparam就能按 dev 的配置部署。你还可以在里面对照设置 prod 的 location、sku 大小、是否开启自动缩放。参数文件的真正价值是让“部署哪个环境”变成一个命令行参数,而不是一套堆满 if else 的模板。
有不少从 ARM JSON 转过来的同学会有疑问:bicepparam 是不是要装新版本 CLI?确实,旧版 Azure CLI 不认这个文件格式,所以我在项目里会先在 README 里标注 “请使用 Azure CLI 2.40 以上版本”,然后执行az bicep build做编译校验。如果团队里有人还在用老版本且不方便升级,退一步用 JSON 参数文件也能达到同样的目的,只是语法更啰嗦。
4.2 一条能复用的 GitHub Actions 流水线
下面这条流水线是我常用的最小组合:checkout 代码 → 登录 Azure → 部署基础设施 → 编译后端 → 发布到 App Service。它把基础设施部署和应用发布放在同一条流水线里,环境差异靠前面说的 bicepparam 控制。
name: deploy-azure-demo on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Log into Azure uses: azure/login@v2 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Deploy infrastructure uses: azure/arm-deploy@v2 with: scope: resourcegroup resourceGroupName: rg-demo-dev template: infra/main.bicep parameters: infra/main.dev.bicepparam - name: Build .NET API run: | dotnet publish -c Release -o publish - name: Deploy Web App uses: azure/webapps-deploy@v3 with: app-name: app-dev-demo package: publishazure/login@v2读取的AZURE_CREDENTIALSsecret,就是之前在创建服务主体时拿到的 JSON 字符串,不要把它存成普通文本,要存成 secret。azure/arm-deploy@v2负责把 Bicep 模板部署到指定资源组,template和parameters路径要写准确,runner 工作目录是仓库根目录。dotnet publish -c Release -o publish这步是 .NET 的发布行为,如果你是 Node.js 项目,替换成npm ci && npm run build即可。最后webapps-deploy把发布产物打到 App Service。
这套组合有几个容易忽略的点。第一,arm-deploy默认会做增量部署,它不会删除模板里没有的资源,所以你如果手动在门户创建了一个额外的 App Settings,这次部署不会帮你去除。第二,webapps-deploy会先停止 App Service 再同步文件,如果你的应用有正在处理的长任务,会出现几秒钟的停机窗口。第三,actions 的版本号最好锁定到具体 release tag,不要用@main这类随时变动的引用,否则上游一次变更可能让你整条流水线翻车。
4.3 先部署基础设施,还是先发布应用?
这里我踩过多次。假如你对 Bicep 模板做了修改,比如改了 App Service 的名称,而流水线里先跑 webapps-deploy,那这个步骤会因为找不到原名字的 App Service 而直接失败。反过来,如果先部署基础设施,后发布应用,即使 Web App 的名字没变,Bicep 部署也会先把 App Service 的配置收敛成模板定义的状态,再等发布步骤把代码推上去,顺序上没有冲突。
我推荐的前后顺序是:编译代码可以先做,因为代码编译失败就不应该碰任何云资源;但“部署基础设施”必须早于“部署应用”。如果你有数据库迁移脚本,我会在发布应用之前先跑迁移,或者用 Web App 的启动命令来触发迁移,这取决于你的团队是否接受应用启动时自动迁移。顺序定下来之后,整个发布过程就是线性的,遇到失败也容易从中间节点重跑,而不是把流水线画成一张网状结构。
5. Azure 开发避坑手记:五个高频翻车现场
5.1 部署成功,访问却 500,且日志流一片空白
现象:GitHub Actions 全部绿,浏览器一打开就是 500,Azure 门户里的日志流什么都没输出。
原因:App Service 默认没有开启“应用日志”,或者日志级别设置成了 Error,而你的代码只打了 Info 级别;也有可能是应用启动即崩溃,日志还没写进文件系统。
解决:先用命令把日志打开,再做一次部署:
az webapp log config \ --name app-dev-demo \ --resource-group rg-demo-dev \ --application-logging true \ --detailed-error-messages true \ --failed-request-tracing true \ --level information--level information会同时打开 stdout 和文件系统日志,--detailed-error-messages和--failed-request-tracing会让 App Service 在发生 500 时生成详细错误页面和失败请求追踪。改完设置后不用重启,在门户日志流里刷新即可看到输出。如果仍然没有日志,检查你的应用是不是用了 stdout 之外的日志库,比如 .NET 的 Serilog 默认写文件,文件路径不一定在LogFiles目录下。
5.2 CI 里报 No subscriptions found
现象:本地 az login 一切正常,推到 GitHub Actions 后报“No subscriptions found”或“The provided client secret keys for appId XXXX were not found”。
原因:无人值守 runner 无法完成浏览器交互登录,而且服务主体的 clientSecret 可能已经过期或压根没有存进 secret。
解决:确认AZURE_CREDENTIALSsecret 的值是az ad sp create-for-rbac输出的完整 JSON,然后检查服务主体过期时间。你可以在本地用下面的命令验证:
az login --service-principal \ --username <clientId> \ --password <clientSecret> \ --tenant <tenantId>如果这一步能过,问题就出在 workflow 文件里creds的读取方式上。另外提醒一句,服务主体的 clientSecret 默认有效期是一年,条件允许就直接在流水线里加一个定期轮换任务,或者用 Azure Trusted Publishing 这类免密钥方案。
5.3 资源都建成了,但接口延迟高得离谱
现象:接口请求平均 300ms 以上,某些操作超过 1s,而数据库查询本身并不复杂。
原因:最常见的是 Web App 和 Cosmos DB 被部署到了不同区域,比如 Web App 在 eastasia,数据库在 southeastasia,每次请求都在跨区域绕圈子;另一个常见原因是资源组位置和资源位置不一致,加上使用了resourceGroup().location,导致所有资源被隐式放到了资源组所在区域。
解决:把 location 参数作为唯一来源,并且在 Bicep 里显式使用:
param location string resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { name: 'cosmos-${environment}-demo' location: location kind: 'GlobalDocumentDB' ... }部署时统一传--parameters location=eastasia。想确认两个资源是不是在同一个区域,用az webapp show --query location和az cosmosdb show --query location对比即可。还有一种隐藏因素:Cosmos DB 默认的索引策略会对每一个写入字段建索引,查询时会比预期慢不少,可以按业务字段裁剪索引,但这是后话。
5.4 运维在门户手动改了配置,下次部署全被覆盖
现象:测试人员临时在 Azure 门户里调大了 App Service 的 Always On 开关,或者加了几个应用设置,下次流水线跑完,这些改动全部消失。
原因:声明式部署的语义就是把资源状态收敛到模板状态,任何模板外的手工修改都会被下一次部署视为“漂移”并清除。
解决:所有该长期存在的配置都写进 Bicep 或参数文件,临时调试用的配置不要依赖门户,用命令动态修改并在调试完立即恢复;或者在流水线里把手工配置加入白名单。实际上没有官方“白名单”这种东西,我通常是给 dev 资源组单独开放手动修改权限,prod 资源组锁住,这样团队既能临时调试,又不会污染生产配置。这个习惯能帮你省掉很多“谁动了我的配置”的争论。
5.5 连接字符串里的特殊字符把 shell 和配置一起坑了
现象:在命令行里执行az webapp config appsettings set,连接字符串有一条长串包含;、=和斜杠,命令执行完应用设置里看起来没问题,但代码读出来是错的;更糟的情况是 shell 直接报错。
原因:没有给命令替换加双引号,于是分号和等于号被 shell 解析成了分割符;或者在 GitHub Actions 的 secret 里存了带引号的值,导致 JSON 解析出错。
解决:命令行操作时统一用--settings "KEY=$(...)"的外层双引号方式,前面示例里我已经这样写了;在 workflow 文件里则要用env:关键字把连接字符串先转成环境变量,再传给后续脚本。我自己的经验是:能用 Key Vault 引用就别在命令里传连接字符串,Key Vault 的 secret URI 相对干净,而且天然带了版本管理,后续轮换密钥不需要改代码。
6. 上线之后的最后一公里:日志、监控与运维习惯
很多团队的 Azure 项目死在“上线后第一次故障找不到根因”。App Service 自带的日志流只能看到 stdout,看不到真实的调用链和异常上下文。我的习惯是上线后第一件事就接入 Application Insights,把请求数、失败率、依赖调用耗时和异常堆栈全都收到一个地方。只要在应用里加一个 SDK 包,然后在 App Service 的应用设置里加上APPINSIGHTS_INSTRUMENTATIONKEY,一分钟内就能看到遥测数据。
还有一个容易忽略的运维动作:成本预算警报。Azure 门户里可以设置预算警报,但 CLI 也能创建,我把它放在部署流水线之后的脚本里:
az consumption budget create \ --resource-group rg-demo-dev \ --name monthly-budget-dev \ --amount 200 \ --time-grain Monthly \ --start-date 2025-01-01 \ --end-date 2025-12-31这条命令给开发环境设置一个每月 200 的预算上限,超了之后会触发邮件警报。成本控制不是限制开发,而是让团队在资源被浪费之前就知道哪个环境超支。另一个我喜欢用的运维技巧是启动诊断工具:Azure App Service 自带的“诊断工具”面板有很多现成分析入口,比如检测 CPU 高、内存增长和请求失败,它会直接给出可能原因,不用你自己刷日志。
文章写到最后,我想说一个自己的习惯:每次把新服务部署到 Azure,我不是先看绿没绿,而是先去日志流转一圈,再打开 Application Insights 看一眼请求分布,最后把资源组的成本分析页面截个图。这套流程帮我早早就发现了不少问题,比如某个测试环境的 Application Insights 采样率被调成了 100% 导致成本翻倍,比如某个 API 在高峰期覆盖率不足导致误判。把“上线即监控”变成肌肉记忆之后,Azure 开发带给你的就不再是玄学,而是一条条可以预判的系统行为。希望这份实战笔记能把你的第一个 Azure 项目带得更稳一点,也希望你少走我走过的这些弯路。
本文还有配套的精品资源,点击获取