最近用户有个需求:更新 ASP.NET Core 应用时,要让访问不中断且用户无感知,部署环境为 Windows Server + IIS。自然想到了蓝绿部署,之前没有应用过 URL Rewrite + ARR,就趁此实践一下。

原本想着很简单:对 URL 重写规则不熟,直接问 ai。结果反倒被 ai 误导,折腾了好一阵子才搞好,在此分享配置过程、重写规则以及相关代码。


1 概念简介

开始之前,简要说明一下本文涉及的三个关键概念及其在本方案中的作用:

  • URL Rewrite

    IIS 的 URL 重写模块,可根据设置的规则匹配并处理请求。在本方案中,它承担关键的请求分发工作:通过在一个 Switch 站点中配置重写规则,将请求按需转发到 Blue 站点或 Green 站点,实现版本之间的快速切换。

  • ARR(Application Request Routing)

    IIS 的反向代理扩展,提供代理与转发能力。需要说明的是本方案没有使用 ARR 的 Server Farms 功能,而是依赖其反向代理能力以便支持 URL Rewrite 的 “代理式重写”:有了 ARR 重写才能有反代效果。

  • 蓝绿部署(Blue-Green Deployment)

    一种应用程序发布策略,即准备两套功能一致的环境(蓝/绿),在同一时间只有一个环境(如蓝)承载线上流量。新版本部署到闲置环境(绿),测试通过后,通过切换流量瞬间完成发布,实现零停机和快速回滚。


2 最终部署结构

先说结果:

示例里的部署目录结构(本文里会以此目录为例):

即:在 IIS 里创建 3 个站点:Switch 站点、Blue 站点以及 Green 站点,需要共享的配置、缓存、附件等位于站外。数据库自然也用同一数据库。

用户通过 Switch 站点 http://192.168.0.116:9080 访问系统(各站点端口可根据你的实际情况设置),Switch 站点再根据设置的重写规则,将用户请求导向 Blue 站点或 Green 站点。蓝绿站点同时只有其中的一个为用户提供服务。

初始部署时(v1.0.0),应用发布到 Blue 站点,并让 Switch 站点将请求导向 Blue 站点,用户开始正常访问。

第一次更新(v1.0.1),新版本发布到 Green 站点并进行测试、预热,然后让 Switch 站点将请求导向 Green 站点,用户访问不中断。

第二次更新(v1.0.2),Blue 站点此时处于空闲状态,因此可以安全地将其停掉,并删除旧版本、放入新版本进行测试、预热,然后让 Switch 站点再将请求导向 Blue 站点,用户访问仍不会中断。

如此重复,滚动更新。

这里 有个蓝绿部署示例应用,可分别用两个浏览器去登录切换试试:在浏览器A里打开一个列表或编辑页面,然后在浏览器B里切换一下站点,再回到浏览器A里继续进行分页查询或点击保存按钮,响应将依然正常。如果只有一个浏览器,可分别用正常模式和无痕模式去登录。


3 环境准备

先确保 IIS 与 ASP.NET Core 运行环境已安装好,运行环境版本要与你发布应用时指定的一致。

3.1 安装 URL Rewrite

下载地址:https://www.iis.net/downloads/microsoft/url-rewrite,到页面底部下载适用自己的安装包。

要确认是否安装:打开 IIS 管理器,在左侧选中一个站点,看看右侧功能列表里有没有 "URL 重写"

3.2 ARR 安装与配置

下载地址:https://www.iis.net/downloads/microsoft/application-request-routing

安装好后,打开 IIS 管理器,在左侧选中计算机名服务器名,在右侧功能列表里找到 "Application Request Routing Cache"

双击打开,在右侧找到并点击 "Server Proxy Settings"

然后按照下图配置:

为何取消选中 "Reverse rewrite host in response headers":因为选中后响应头中的 Host 会被强行替换成 Switch 的 Host,这在跨域回调时可能会有问题。

至此 ARR 就配置好了,因为我们不需要使用其 Server Farms 功能。


4 蓝绿站点创建与配置

创建 Blue 站点和 Green 站点,路径分别指向 QAdminAppBlue 目录和 QAdminAppGreen 目录,将用来放置应用程序文件。

让两个站点分别监听 5001 和 5002 端口(端口号你可自行调整),各自使用独立的应用程序池并把应用程序池的 .NET CLR 版本均置为 "无托管代码"

另外,把蓝绿站点均绑定到 IP 地址 127.0.0.1 上:因为你不应允许用户绕过 Switch 站点直接访问 Blue 站点和 Green 站点。

当然也可通过其它途径达到此目的,比如用 Windows 防火墙。


5 Switch 站点创建与配置

这里是本方案里最关键的配置部分。

5.1 创建 Switch 站点

创建 Switch 站点,路径指向 QAdminAppSwitch目录,该目录下将只有个 web.config 文件,内容为 URL 重写规则。

让 Switch 站点监听 9080 端口(我本机 80 已被占用,你按实际情况设置),也使用独立的应用程序池并把其 .NET CLR 版本置为 "无托管代码"

将 Switch 站点绑定到对外使用的一个 IP 地址上(比如 192.168.0.116),如果是要通过域名访问,绑定时再设置一下主机名为你的域名。

用户将通过你设置的 IP 或域名访问应用系统。

5.2 书写 URL 重写规则

在 IIS 管理器 => Switch 站点 => URL 重写 里,可进行重写规则的配置,配置将存到站点根目录下的 web.config 文件里。

以下是所需要的完整的重写规则,你可直接拷贝到 Switch 站点的 web.config 里使用。其中的蓝绿站点端口你按实际情况修改。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules useOriginalURLEncoding="false">
<clear />
<!-- 蓝站点 https 规则 -->
<rule name="RouteToBlueHttps" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="on" ignoreCase="true" />
</conditions>
<serverVariables>
<set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
<set name="HTTP_X_FORWARDED_PROTO" value="https" />
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
</serverVariables>
<action type="Rewrite" url="http://127.0.0.1:5001{UNENCODED_URL}" appendQueryString="false" />
</rule>
<!-- 蓝站点 http 规则 -->
<rule name="RouteToBlueHttp" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<serverVariables>
<set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
<set name="HTTP_X_FORWARDED_PROTO" value="http" />
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
</serverVariables>
<action type="Rewrite" url="http://127.0.0.1:5001{UNENCODED_URL}" appendQueryString="false" />
</rule>
<!-- 绿站点 https 规则 -->
<rule name="RouteToGreenHttps" enabled="false" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="on" ignoreCase="true" />
</conditions>
<serverVariables>
<set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
<set name="HTTP_X_FORWARDED_PROTO" value="https" />
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
</serverVariables>
<action type="Rewrite" url="http://127.0.0.1:5002{UNENCODED_URL}" appendQueryString="false" />
</rule>
<!-- 绿站点 http 规则 -->
<rule name="RouteToGreenHttp" enabled="false" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<serverVariables>
<set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
<set name="HTTP_X_FORWARDED_PROTO" value="http" />
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
</serverVariables>
<action type="Rewrite" url="http://127.0.0.1:5002{UNENCODED_URL}" appendQueryString="false" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

5.3 添加允许的服务器变量

规则里写的转发相关服务器变量需要添加进来才能正常使用。

打开 IIS 管理器,选中 Switch 站点,在右侧功能列表里找到 "URL 重写"

双击打开 "URL 重写",然后点击右侧的 "查看服务器变量"

在该界面将 "HTTP_X_FORWARDED_HOST""HTTP_X_FORWARDED_PROTO""HTTP_X_FORWARDED_FOR" 添加进来:

5.4 规则的简要解释

  • 最关键的要求是:用户在浏览器里输入的 URL,能够完整的、不被做任何改动的转给蓝绿站点里的 App

    这个费了点周折,比如 URL:"/TestPage/aa%2Fbb",本意是请求 "/TestPage" 页面,路由参数为 "aa/bb",因为该参数里有斜杠,因此用编码后的 "aa%2Fbb" 传递。但测试时发现转给 App 的请求是 "/TestPage/aa/bb",造成 404。

    最终在 这里 找到了答案:使用 {UNENCODED_URL} 并设置 useOriginalURLEncodingfalse

  • 里边的服务器变量设置用来确保传递正确的 host、scheme 以及客户端 IP 地址给 App

    比如 App 里拿到的 host 将是 "192.168.0.116:9080" 而不是 "127.0.0.1:5001""127.0.0.1:5002"

    要与代码配合实现,见后边章节。

  • 为何给蓝绿分别设置了两条规则

    因为 URL 重写没法自适应 http/https,只有个 {HTTPS} 变量(值为 "on"/"off"),为了让 http、https 均能正常访问,只能各自写两条规则。

    如果你的应用只需要 http/https 中的一种访问,可以删掉不需要的规则。

  • 蓝绿的切换

    蓝绿的切换就是对应规则的启用与停用,哪个站点规则启用(enabled="true"),就导向哪个站点。不能同时都启用。

    不能在 IIS 里手动去启用、禁用规则,这会造成访问中断,而是要通过代码去实现,见后边章节。


6 应用调整

应用也需要加入一些初始化代码,以及做出一些相应的调整才能适应蓝绿部署环境。

6.1 应用初始化中的两项必要配置

  • 配置数据保护(Data Protection)以共享密钥

    必须使用 AddDataProtection() 指定蓝绿站点使用同一套密钥存储,不然会出现 Cookie 无法识别等问题。

    比如用共享文件夹:
builder.Services.AddDataProtection()
.SetApplicationName("myApp")
// 应用上一级目录的 myAppKeys 目录下
.PersistKeysToFileSystem(new DirectoryInfo($"{AppContext.BaseDirectory}../myAppKeys"));

或存于 Redis:

var redis = ConnectionMultiplexer.Connect("<URI>");
builder.Services.AddDataProtection()
.SetApplicationName("myApp")
.PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
  • 配置转发头中间件(Forwarded Headers)

    必须使用 UseForwardedHeaders() 配置转发头中间件以确保 App 够获取真实的 host、scheme 以及客户端 IP 地址,比如 App 里拿到的 host 将是 "192.168.0.116:9080" 而不是 "127.0.0.1:5001""127.0.0.1:5002",拿到的 scheme 则是实际的 scheme(http/https)。
// 在 builder.Build() 后立即调用:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost
});

6.2 其它事项

  • 识别自己所在环境

    蓝绿部署环境下,应用通常需要知道自己运行在 Blue 还是 Green 里,比如日志要增加 ServerNode 项,以便记录在哪个登录、在哪个执行的操作等等。

    至于如何识别自己所在的环境,可直接根据应用自己所在的目录名称来判断:
private static string _whoami()
{
string dirName = new DirectoryInfo(AppContext.BaseDirectory).Name;
bool isBlue = dirName.Contains("blue", StringComparison.OrdinalIgnoreCase);
bool isGreen = dirName.Contains("green", StringComparison.OrdinalIgnoreCase);
if (isBlue && isGreen)
return "Ambiguous";
if ((!isBlue) && (!isGreen))
return "Unknown";
return isBlue ? "Blue" : "Green";
});
  • 配置共享

    把配置文件放到一个共享目录下,比如应用上一级目录下的 configs 目录。

    就是说应用目录下不要有发布后需要更改的配置文件,这样更新时就可放心地删除旧版、拷贝新版了,不然一旦疏忽会造成混乱或异常。

    以下代码将使应用使用其上一级目录下的 configs 目录下的 appConfig.json 配置文件,供你参考:
string appConfigFile = $"{AppContext.BaseDirectory}../configs/appConfig.json";
string appConfigFileEnv = $"{AppContext.BaseDirectory}../configs/appConfig.{builder.Environment.EnvironmentName}.json";
builder.Configuration.AddJsonFile(appConfigFile, false, true);
builder.Configuration.AddJsonFile(appConfigFileEnv, true, true);

如果蓝绿下的 App 需要不同的配置:在共享配置文件里书写两套配置,App 里则用一套代码就可让蓝绿各自读取自己的:

配置:

{
"MyApp_Blue": {
"Foo": "abc",
},
"MyApp_Green": {
"Foo": "def",
},
}

读取:

// 参见前边的 _whoami()
string foo= builder.Configuration[$"MyApp_{_whoami()}:Foo"];
  • 分布式缓存

    如果有需要共享的缓存,则需要改用分布式缓存。比如用到了 Session。

  • 文件上传

    若有附件上传,则同样要使用同一个共享目录。

  • 后台任务/定时任务

    若有后台任务或定时任务,蓝绿将都在执行。

    如果任务允许蓝绿同时运行,或允许一前一后运行,或者不允许同时运行但可中断,就没什么问题。否则需要将任务独立出来,并独立运行(比如用 Windows Service)。

  • 向后兼容

    应用需要考虑向后兼容性。

    如果新版本使用了与旧版不兼容的会话结构、加密格式、字段结构等等,就无法进行平滑切换,因此需要考虑向后的兼容性,比如新增的字段要确保允许 NULL 或设有默认值等。

    如果确实无法兼容,就只能短时中断访问了,根据实际情况可采取提前通知、低峰操作等方式升级。


7 蓝绿如何切换

蓝绿的切换过程实际上就是启用/禁用 Switch 站点里的对应规则。

但是不能在 IIS 里手动去启用、禁用规则,这会造成访问中断,而是通过用脚本或代码修改 Switch 站点里的 web.config 文件来进行切换:修改对应规则的 enabledtrue/false,比如用 PowerShell 脚本。

我是在应用里设计了一个只有超级管理员用户访问的页面,在其中进行切换操作。

以下是用来获取当前启用的环境以及进行蓝绿切换的 C# 方法,你可直接使用。

/// <summary>
/// 获取当前 web.config 里启用的环境。
/// </summary>
/// <param name="webConfigPath">Switch 站点的 web.config 文件完整路径。</param>
/// <returns></returns>
private static string _getCurrentEnvironmentInConfiguration(string webConfigPath)
{
if (!System.IO.File.Exists(webConfigPath))
throw new FileNotFoundException("未找到 Switch 站点的 web.config 文件。", webConfigPath); XDocument doc = XDocument.Load(webConfigPath); // 所有 Blue 规则节点
var blueRules = doc
.Descendants("rule")
.Where(r => ((string)r.Attribute("name")).StartsWith("RouteToBlue", StringComparison.OrdinalIgnoreCase))
.ToList(); // 所有 Green 规则节点
var greenRules = doc
.Descendants("rule")
.Where(r => ((string)r.Attribute("name")).StartsWith("RouteToGreen", StringComparison.OrdinalIgnoreCase))
.ToList(); if (blueRules.Count == 0 || greenRules.Count == 0)
throw new InvalidOperationException("未找到 RouteToBlue 或 RouteToGreen 规则,请检查 web.config。"); bool blueEnabled = blueRules.All(r => (string)r.Attribute("enabled") != "false");
bool greenEnabled = greenRules.All(r => (string)r.Attribute("enabled") != "false"); if (blueEnabled && greenEnabled)
return "All";
if ((!blueEnabled) && (!greenEnabled))
return "NoneOrAmbiguous";
return blueEnabled ? "Blue" : "Green";
} /// <summary>
/// 切换蓝绿环境。
/// </summary>
/// <param name="webConfigPath">Switch 站点的 web.config 文件完整路径。</param>
/// <returns>返回已启用的环境。</returns>
private static string _toggleEnvironment(string webConfigPath)
{
if (!System.IO.File.Exists(webConfigPath))
throw new FileNotFoundException("未找到 Switch 站点的 web.config 文件。", webConfigPath); XDocument doc = XDocument.Load(webConfigPath); // 所有 Blue 规则节点
var blueRules = doc
.Descendants("rule")
.Where(r => ((string)r.Attribute("name")).StartsWith("RouteToBlue", StringComparison.OrdinalIgnoreCase))
.ToList(); // 所有 Green 规则节点
var greenRules = doc
.Descendants("rule")
.Where(r => ((string)r.Attribute("name")).StartsWith("RouteToGreen", StringComparison.OrdinalIgnoreCase))
.ToList(); if (blueRules.Count == 0 || greenRules.Count == 0)
throw new InvalidOperationException("未找到 RouteToBlue 或 RouteToGreen 规则,请检查 web.config。"); bool blueEnabled = blueRules.All(r => (string)r.Attribute("enabled") != "false");
bool greenEnabled = greenRules.All(r => (string)r.Attribute("enabled") != "false"); string targetEnv;
if (blueEnabled && !greenEnabled)
{
// 当前是蓝 → 切换到绿
foreach (var r in blueRules)
r.SetAttributeValue("enabled", "false");
foreach (var r in greenRules)
r.SetAttributeValue("enabled", "true");
targetEnv = "Green";
}
else if (greenEnabled && !blueEnabled)
{
// 当前是绿 → 切换到蓝
foreach (var r in blueRules)
r.SetAttributeValue("enabled", "true");
foreach (var r in greenRules)
r.SetAttributeValue("enabled", "false");
targetEnv = "Blue";
}
else
{
// 若都已启用、都已停用或状态混杂,则切换到蓝
foreach (var r in blueRules)
r.SetAttributeValue("enabled", "true");
foreach (var r in greenRules)
r.SetAttributeValue("enabled", "false");
targetEnv = "Blue";
} // UTF-8 编码保存,并确保不写入 BOM,以防止 IIS 读取出错
using (var writer = new StreamWriter(webConfigPath, false, new System.Text.UTF8Encoding(false)))
{
doc.Save(writer);
} return targetEnv;
}

8 切换测试

用 k6 分别对 Windows Server 2012 R2 + IIS8.5 和 Win11 + IIS10 下的蓝绿部署进行了切换测试,尚未出现访问中断的情况。

作者:木南W

出处:https://www.cnblogs.com/munanwang/p/19234857

转载请注明作者并在页面明显位置给出原文链接。

实践笔记:IIS + URL Rewrite + ARR 实现 ASP.NET Core 蓝绿部署的更多相关文章

  1. Contour 学习笔记(二):使用级联功能实现蓝绿部署和金丝雀发布

    上篇文章介绍了 Contour 分布式架构的工作原理,顺便简单介绍了下 IngressRoute 的使用方式.本文将探讨 IngressRoute 更高级的用法,其中级联功能是重点. 1. Ingre ...

  2. IIS URL Rewrite – Installation and Use

    IIS URL Rewrite – Installation and Use Posted by Nick LeFevre | Leave a reply IIS URL Rewrite Instal ...

  3. IIS URL Rewrite(URL 重写)-使用教程

    IIS URL Rewrite(URL 重写)-使用教程 作者:vkvi 来源:千一网络(原创) 日期:2011-8-17  http://www.cftea.com/c/2011/08/9CRXOL ...

  4. IIS URL Rewrite redirect from one Domain to another

    IIS URL Rewrite enables Web administrators to create powerful rules to implement URLs that are easie ...

  5. Windows10中的IIS10安装php manager和IIS URL Rewrite 2.0组件的方法

    Windows10中自带的Server:Microsoft-IIS/10.0,然后这个10却让原本支持组件无法安装了,php manager组件安装时提示“必须安装IIS7以上才可以安装”.那是不是真 ...

  6. IIS URL Rewrite Module防盗链规则配置方法

    IIS版本:IIS 7.5 URL Rewrite组件:IIS URL Rewrite Module(http://www.iis.net/downloads/microsoft/url-rewrit ...

  7. IIS URL Rewrite Module的防盗链规则设置

    IIS版本:IIS 7.5 URL Rewrite组件:IIS URL Rewrite Module(http://www.iis.net/downloads/microsoft/url-rewrit ...

  8. Windows10 IIS安装php manager和IIS URL Rewrite 2.0组件的方法

    Windows10中自带的Server:Microsoft-IIS///8.5/10上安装.微软脑子秀逗,跳过了9,以为能解决版本识别的问题,没想到弄成10,还是出现了版本识别的问题,真是自己打自己的 ...

  9. 在IIS上发布并运行ASP.NET Core

    英文原文地址:https://weblog.west-wind.com/posts/2016/Jun/06/Publishing-and-Running-ASPNET-Core-Application ...

  10. ASP.NET Core开发期间部署到IIS自定义主机域名并附加进程调试

    在.NET Framework环境下,我们经常会这么做 把一个web项目不经过发布直接部署到IIS里去,配置上主机名,修改一下hosts文件,就可以用自定义的域名来访问我们的应用程序,使用附加到进程( ...

随机推荐

  1. 基于FPGA的按键提示音设计

    1. 设计要求 按键按下,蜂鸣器要"叮"一声. 2. 设计分析 该设计方案采用野火征途MiNi FPGA开发板(intel-Cyclone IV -EP4CE10F17C8)实现, ...

  2. 【学习】重学Swift5-错误处理、三方库等

    九.其他话题 错误处理 enum VendingMachineError: Error { case invalidSelection case insufficientFunds(coinsNeed ...

  3. iOS UIBezierPath简单实用

     转载请注明出处!!! 1.介绍  UIBezierPath这个类在UIKit中, 是Core Graphics框架关于path的一个封装,使用此类可以定义简单的形状,比如我们常用到,矩形,圆形,椭圆 ...

  4. C++ 三之法则、五之法则和零之法则

    1.三之法则 如果一个类需要显式定义以下三个特殊成员函数中的任意一个,通常需要同时定义全部三个: 析构函数(Destructor):释放资源(如 delete 动态内存). 拷贝构造函数(Copy C ...

  5. B站油管抖音一键笔记

    在最近,我有一个需求就是需要对视频内容进行一个总结,做成一个笔记,但是又不想自己手动去写,于是我找到了一个项目 BiliNote,BiliNote 是一个开源的 AI 视频笔记助手,支持通过哔哩哔哩. ...

  6. 【光照】Unity[光照烘焙]的原理与具体流程

    [从UnityURP开始探索游戏渲染]专栏-直达 URP光照烘焙介绍 Unity通用渲染管线(URP)的光照烘焙系统是用于预计算全局光照(GI)的核心技术,它将静态光源的光照效果预先计算并存储在光照贴 ...

  7. 如何使用国内资源在 RKE2 上安装 Rancher HA

    作者简介 袁振,SUSE Rancher 技术支持经理,负责订阅客户售后技术支持团队,为订阅客户提供技术支持服务.2016 年开始接触容器.Kubernetes 技术,对自动化运维.Devops.Ku ...

  8. 折腾笔记:免费用上 Claude Code 的两个方案

    Claude Code API 中转服务配置指南:88code 和 AnyRouter 使用教程 前言 Claude Code 是 Anthropic 推出的 AI 编程辅助工具,在代码生成.代码审查 ...

  9. 技术 | LLaMA Factory微调记录重修版

    之前投的那篇教程我自己回看一遍都不太搞得明白,从新梳理一遍 1. 云服务器准备 恒源云 (gpushare.com) 配置建议: GPU: RTX 3090 (24GB) 或 RTX 4090 (24 ...

  10. Roslyn 技术解析:如何利用它做代码生成?

    前两天聊了下Roslyn,如果您耐心看完,也算是入了门,那么今天继续分享它的另外一大特性,那就是 Source Generator,(源代码生成器)是 (Roslyn)提供的一项强大功能,它允许开发者 ...