Azure 云服务(外延支持)将于 2027 年 3 月 31 日停用:迁移 Web 角色和辅助角色

微软已于 2025 年 3 月 31 日弃用 Azure 云服务(外延支持),并将在 2027 年 3 月 31 日完全停用。如果您有某个业务应用以 Web 角色和辅助角色的形式运行,它必须在那天之前迁移到 Azure 上的其他地方运行。停用常见问题解答对大家最先问的两个问题回答得很直接:微软“无法批准延期请求”,而且“没有可用的一键迁移工具”。
从今天 2026 年 10 月 3 日算起,还剩六个月。
本文写给谁
典型情况是:一个运行在 .NET Framework 上的 ASP.NET 应用,前面是一个 Web 角色,后面是一两个处理队列的辅助角色,由某家外包公司在八到十年前开发。那家外包公司往往早已不再参与,应用却仍在处理订单或运行客户门户,而自上次迁移以来,没有人打开过 .csdef 文件。
要确认您是否受影响,打开 Azure 门户,列出类型为“云服务(外延支持)”的资源。微软的停用通知直接链接到这个视图。如果列表是空的,您就没事了。
本文不涉及已于 2024 年停用的云服务(经典)。如果产品由供应商替您托管,迁移是供应商的事:请对方以书面形式给出日期。下面的内容写给拥有代码的团队,或者名义上拥有代码、却得先找到一个能看懂它的人的团队。
为什么这次比 2024 年的迁移更难
很多公司在 2024 年经典版停用时迁移到了外延支持。那次迁移在设计上就很便宜。微软的外延支持概述说,.csdef、.cscfg 和 .cspkg 文件“会被沿用,格式没有变化”,并且“运行时代码无需任何更改”。当时甚至还有就地迁移。同一页面还建议不再演进的应用使用外延支持,因为“它提供了一条快速迁移路径”。
这一次没有同类的迁移目标。用微软的话说,云服务“关乎将应用程序部署为虚拟机。您编写的代码与虚拟机实例紧密耦合”。您的代码知道自己运行在一个角色中。它从角色读取配置,通过角色找到磁盘空间,由角色安装证书,并在角色启动之前以管理员身份运行安装脚本。所有这些都必须被替换。
在您阅读官方指南之前,还有一点。微软的停用通知和常见问题解答只点名了一个目标:Service Fabric 托管群集。概述页面列出了五个,而微软自己的迁移决策矩阵比较了七个。Service Fabric 是默认选项,不是必选项,而对许多 Web 角色来说,它是错误的选择。
迁移目标,以及各自适用的场合
Web 角色和辅助角色不必落在同一个地方。按角色逐个选择目标。
App Service(Windows)。 对 ASP.NET 应用来说,这是最接近 Web 角色的东西。Windows 实例预装了受支持的 .NET Framework 版本,所以 Web Forms 和 MVC 5 无需重写就能运行。辅助角色可以作为 WebJobs 跟过来,它们“在与 Web 应用相同的实例中”运行,不另收费。局限在于机器本身:微软把需要 COM 组件、注册表访问或 MSI 安装程序的应用引向 Managed Instance,所以在标准计划上,需要提升权限的启动任务无处可去。
App Service Managed Instance。 专为遗留 Windows Web 应用打造。根据微软的概述,它“已在部分区域面向 Windows Web 应用正式发布”,仅限 Pv4 和 Pmv4 计划,预装 .NET Framework 3.5 和 4.8,并提供 PowerShell 安装脚本,可以注册 COM 组件、写入注册表项、运行 MSI 安装程序和配置 IIS。这覆盖了提升权限的启动任务过去所做的大部分事情。局限是:仅支持 Web 应用(没有 WebJobs),不支持容器,只支持 Entra ID 和托管标识(不支持加入域、NTLM 或 Kerberos),而且撰写本文时列出的唯一欧洲区域是北欧。
Container Apps。 一旦辅助角色迁到了现代 .NET,这是个好选择:基于队列的扩缩、定时和事件触发的作业、缩容到零。但容器要求写明“必须使用基于 Linux(linux/amd64)的容器镜像”。运行在 .NET Framework 上的代码,在移植之前无法在那里运行。
Azure Kubernetes 服务(AKS)。 在 Windows 节点池中运行 Windows Server 容器,所以 .NET Framework 角色可以容器化后迁移过去。决策矩阵把它的迁移复杂度和运维开销都评为高。如果您已经在运行 Kubernetes,它是合适的;如果只是为一个遗留应用搭建第一个群集,就不合适。
虚拟机规模集。 决策矩阵称它“更接近云服务模型,更容易直接迁移”。您重新拿回了虚拟机,同时也拿回了补丁更新、镜像构建和 IIS 配置这些过去由角色替您完成的工作。
Service Fabric 托管群集。 微软点名的目标。辅助角色可以干净地映射过去。Web 角色往往不行:Service Fabric“不支持 IIS”,而转换指南把 ASP.NET Web Forms 列为不支持,给出的路径是转换为 ASP.NET Core MVC。Service Fabric 迁移指南还补充说,托管群集“目前不支持容器”,所以依赖 IIS 的应用需要使用传统群集,运维工作更多。
决策表
| 您的角色是这样的 | 可能的目标 | 工作量在哪里 |
|---|---|---|
| ASP.NET Web Forms 或 MVC 5 Web 角色,启动任务很简单或没有 | App Service(Windows) | 配置、证书、部署流水线 |
| 启动任务要安装 COM 组件、MSI 或注册表项的 Web 角色 | App Service Managed Instance | 把启动任务改写为安装脚本;核实区域和计划 |
| 轮询队列、负载适中的 .NET Framework 辅助角色 | 与 Web 应用并存的 WebJob | 用控制台宿主替换 RoleEntryPoint |
| 您愿意移植到现代 .NET 的辅助角色 | Container Apps | 移植本身,然后是容器镜像 |
| 角色很多,团队已经在运行 Kubernetes | 带 Windows 节点池的 AKS | 镜像、群集运维 |
| 原生依赖很重,不想改代码 | 虚拟机规模集 | 操作系统补丁和镜像维护,永久性的 |
| 以辅助角色为主的系统,Web 层已经在 ASP.NET Core 上 | Service Fabric 托管群集 | 学习这个平台;没有 IIS,没有容器 |
代码里有什么要改
在解决方案中搜索 Microsoft.WindowsAzure.ServiceRuntime。每一个导入它的文件都在清单上。
RoleEntryPoint
辅助角色是一个继承 RoleEntryPoint 并重写 OnStart、Run 和 OnStop 的类。如果 Run 返回,实例就会回收。Service Fabric 把这三者合并成一个 RunAsync,它应当在“RunAsync 方法的 CancellationToken 收到信号时”停止。在 App Service 上或容器中,同样的逻辑变成一个控制台应用,或一个带循环和取消令牌的托管后台服务。
大家容易忽略的是关闭过程。OnStop 给了您一点时间来处理完手头的消息。确保新的宿主会传递取消信号,并确保处理到一半被放弃的消息可以安全地再处理一次。
Web 角色往往也有一个,通常是 WebRole.cs。如果它的 OnStart 做了什么事情(调整 IIS、缓存预热),先弄清楚是什么再删除。
RoleEnvironment
RoleEnvironment.GetConfigurationSettingValue("Key") 从 .cscfg 读取设置。云服务之外没有任何东西提供它。在迁移任何东西之前,把所有调用都包进一个小的配置接口,然后在新的宿主上让这个接口指向应用设置、环境变量或 Key Vault。这是整个项目中最便宜的改动,而且它让其余部分可以在笔记本电脑上测试。
还要找另外三种用法:
RoleEnvironment.Changed,它可以在不重启的情况下应用配置更改。Service Fabric 有对应的事件。在其他地方,要假定更改设置会重启进程,并测试这对正在处理的工作有什么影响。- 用
RoleEnvironment.CurrentRoleInstance选出一个实例来执行定时工作。触发式 WebJobs 在单个实例上运行;连续式 WebJobs 除非加以限制,否则在所有实例上运行。要明确做出决定。 RoleEnvironment.IsAvailable和IsEmulated分支。它们把“云端”路径和“本地”路径分开,其中一条马上就会变成死代码。
.cscfg 和 .csdef
.cscfg 保存按环境区分的设置、实例数量和证书指纹。.csdef 保存终结点、虚拟机大小、本地存储、启动任务、证书存储,有时还在一个 Web 角色中包含多个 IIS 站点。逐行过一遍这两个文件,并写下每一项迁移后放在哪里:应用设置、Key Vault 引用、基础设施代码,还是不再需要。让角色之间直接互相调用的内部终结点需要替代方案,要么是一个服务地址,要么是一个队列。
证书
外延支持已经迫使证书进入 Key Vault,所以 2024 年的那部分工作现在有了回报。变化在于代码如何找到证书。.csdef 把证书安装到一个指定的存储中,通常是 LocalMachine。在 Windows App Service 上,WEBSITE_LOAD_CERTIFICATES 设置让证书出现在 Current User\My 中。打开 LocalMachine 存储的代码什么也找不到,而第一个需要证书的调用就会失败。在 Linux 容器上,则改为在启动时从 Key Vault 加载。
启动任务
打开 Startup.cmd。意外就藏在这里,通常以 executionContext="elevated" 运行:生成 PDF 用的字体、一个 COM 组件、一个 IIS 重写模块、一条为 TLS 修改的注册表项。每一行都有三种可能的结局:不再需要、移入 Managed Instance 上的安装脚本,或者烘焙进容器或虚拟机镜像。
本地存储
.csdef 中的 LocalStorage 资源通过 RoleEnvironment.GetLocalResource 读取,为每个实例提供临时磁盘。真正的临时文件,使用平台的临时目录。任何必须在重启后保留的东西,包括某人以为会永久保存的文件,都放到 Blob 存储。
Web Forms
这是决定其余一切的那个决策。Web Forms 构建在 System.Web 之上,没有 ASP.NET Core 版本,所以把 Web Forms 应用迁移到 Service Fabric 或 Container Apps,意味着重写它的用户界面。App Service、Managed Instance、Windows 容器或规模集都可以原样运行它。照原样迁移,把现代化作为一个单独的项目,配上单独的预算。用户界面重写不应该出现在一个停服日期的关键路径上。
其他
两个云服务之间的 VIP 交换,变成 App Service 上的部署槽位或 Container Apps 上的修订版本。通过诊断(WAD)扩展发送的日志需要一个新的目的地,通常是 Application Insights。标准 App Service 和 Container Apps 不提供远程桌面;Managed Instance 允许通过 Azure Bastion 进行远程桌面,但仅用于诊断。
六个月计划
从 2027 年 3 月 31 日倒推,中间夹着 12 月的假期。
10 月:盘点并选择目标。 列出每一个外延支持部署。对每个角色,记录 .NET Framework 版本、是 Web Forms 还是 MVC、每一处 RoleEnvironment 调用、启动任务、本地存储资源、证书和终结点。然后检查那个让人不舒服的问题:您能用手头的源代码构建出已部署的包吗?对于外包公司开发的系统,答案有时是否定的,而 10 月正是弄清这一点的时候。按角色选择目标。
11 月:一个角色,端到端打通。 加上配置封装层,把目标环境写成基础设施代码,并让一个角色(通常是最简单的那个辅助角色)在测试环境中运行起来,日志、证书和流水线齐备。
12 月和 1 月:移植其余部分。 替换入口点、启动任务和本地存储。用接近生产的数据对预发布环境做负载测试:WebJob 或容器的吞吐量,未必比得上一台专用的角色虚拟机。
2 月:并行运行。 让新环境承接真实流量。对于与旧角色共享队列的辅助角色,要么先让处理过程具备幂等性,要么先停掉旧的辅助角色再启动新的。
3 月上旬:切换。 留出充足的时间切换 DNS。让旧部署保持停止但完好的状态一两周,然后删除。不要把切换安排在 3 月的最后一周:那样的话,切换失败就没有第二次机会了。
如果您是从 1 月才开始,就完全放弃现代化。选择改代码最少的目标(App Service、Managed Instance 或规模集),先迁移,之后再重构。
尚不清楚的部分
微软说这项服务将被“完全停用”,需要迁移“以避免服务中断”。我们读过的页面没有说明 2027 年 4 月 1 日仍在运行的部署会怎样。不要打算到时候去弄清楚。Managed Instance 的区域和计划在未来几个月也很可能变化,所以要在做决定时确认,而不是以本文为准。
从哪里获得帮助
我们接手别人开发的应用,弄清它们如何运行,然后把它们迁移出去:盘点、按角色选择目标、RoleEnvironment 和启动任务的改造,以及切换。我们的遗留系统维护工作涵盖 .NET Framework 和 Web Forms;如果迁移由您自己的团队来做、只是需要更多人手,请参阅人员增补。
如果您有一个云服务部署,而期限只剩六个月,请写信到 office@c9group.dev。