
简介《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 有免费层 F1Cosmos 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/serverfarms2022-03-01 { name: plan-${environment} location: location sku: { name: B1 tier: Basic } } resource webApp Microsoft.Web/sites2022-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 environmentdev--parameters environmentdev会把 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 eastasiausing关键字把这个参数文件和主模板绑定之后运行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/checkoutv4 - name: Log into Azure uses: azure/loginv2 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Deploy infrastructure uses: azure/arm-deployv2 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-deployv3 with: app-name: app-dev-demo package: publishazure/loginv2读取的AZURE_CREDENTIALSsecret就是之前在创建服务主体时拿到的 JSON 字符串不要把它存成普通文本要存成 secret。azure/arm-deployv2负责把 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 全部绿浏览器一打开就是 500Azure 门户里的日志流什么都没输出。原因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/databaseAccounts2023-04-15 { name: cosmos-${environment}-demo location: location kind: GlobalDocumentDB ... }部署时统一传--parameters locationeastasia。想确认两个资源是不是在同一个区域用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 项目带得更稳一点也希望你少走我走过的这些弯路。本文还有配套的精品资源点击获取