你真的了解ASP.NET Core 部署模型吗?

原文:你真的了解ASP.NET Core 部署模型吗?

----------------------------   以下内容针对 ASP.NET Core2.1,2.2出现IIS进程内寄宿 暂不展开讨论--------------------------

相比ASP.NET,ASP.NET Core 2.1出现了3个新的组件:ASP.NET Core Module、Kestrel、dotnet.exe, 后面我们会理清楚这三个组件的作用和组件之间的交互原理。

ASP.NET Core 设计的初衷是开源跨平台、高性能Web服务器,ASP.NET Core跨平台特性相对于早期ASP.NET 是一个显著的飞跃,.NET程序可以理直气壮与JAVA同台竞技,而ASP.NET Core的高性能特性更是成为致胜法宝。

1. ASP.NET Core宏观梳理

为实现跨平台部署.Net程序,微软为ASP.NET Core重新梳理了部署架构:

① 由于各平台都有特定web服务器, 为解耦差异,采用HTTP通信的方式,将web服务器的请求转发到 ASP.NET Core 程序处理

② ASP.NET Core Web进程(dotnet.exe)会使用一个进程内HTTP服务器:Kestrel, 处理转发过来的请求

③ Web服务器现在定位成 反向代理服务器, ASP.NET Core  Module组件负责转发请求到内网Kestrel服务器

常规代理服务器,只用于代理内部网络对外网的连接需求,客户机必须指定代理服务器将本来要直接发送到外网web服务器上的http请求发送到代理服务器,常规的代理服务器不支持外部对内部网络的访问请求;

当一个代理服务器能够代理外部网络的主机,访问内部网络,这种代理服务器的方式称为反向代理服务器 。

Web进程(dotnet.exe)是IIS网站工作进程w3wp.exe 创建出来的子进程, 正因为如此,ASP.NET Core Module对网站工作进程 w3wp.exe 设定的进程内环境变量可以被 dotnet.exe 子进程继承。

验证:

-   任务管理器或 tasklist /fi  "imagename eq dotnet.exe"  命令 找到dotnet.exe进程ID:22792

-   wmic process where ProcessId=22972 get ParentProcessId    返回父进程ID:8232

-  任务管理器或 tasklist /fi  "pid eq 8232"  命令找到 父进程是 w3wp.exe

2. Kestrel: 进程内HTTP服务器

与老牌web服务器解耦,实现跨平台部署

-  进程内Http服务器,ASP.NET Core 保持作为独立Web服务器的能力,可将 ASP.NET Core 网站当可执行程序启动, 在内网部署和开发环境中我们完全可以使用Kestrel来充当web服务器。

-  客观上Kestrel还是作为Http服务器,功能还比不上老牌的web服务器,  可以说在生产环境中要求使用老牌web服务器反向代理请求

Kestrel自诞生之日起还有一些网络安全方面的缺陷,这些缺陷包括一个合适的timeouts,Size limits,和并发数量等

3. ASP.NET Core Module

反向代理服务器的作用是将请求转发给内网的Http服务器,IIS上使用ASP.NET Core Module组件将请求转发到Kestrel Http服务器(注意该组件只在IIS上有效)。

 从整个拓扑图上看,请求首先到达内核态Http.sys Driver,该驱动将请求路由到IIS上指定网站;然后Asp.Net Core Module将请求转发给Kestrel服务器。

3.1  组件能力

作为企业级转发组件ASP.NET Core Module需要完成:

① 进程管理: 控制web启动进程内Kestrel服务器在某端口上启动,并监听转发请求

② 故障恢复: 控制web在1min内崩溃重启

③ 请求转发

④ 启动日志记录: web启动失败,可通过配置将日志输出到指定目录

⑤ 请求头信息转发:dotnet.exe程序需要收到原始的请求信息

代理服务器转发请求时可能丢失的信息:

-  源IP地址丢失

-  scheme:原始请求的scheme:https/http丢失(反向代理服务器和Kestrel之间通过Http交互,并不直接记录原始请求的scheme)

-  IIS/nginx等代理服务器可能修改原始请求的Host消息头

⑥ 转发windiws认证token

以上能力,可以参考https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.1
给出的AspNetCore Module配置参数

3.2  ASP.NET Core Module组件与dotnet.exe 进程结合

        自然可以猜想ASP.NET Core Module与UseIISIntegration()关系很密切:

- Web启动的时候,ASP.NET Core Module会通过进程内环境变量指定kestrel监听的端口

- UseIISIntegration() 拿到环境变量进行一系列配置:

① 服务器在http://localhost:{指定端口}上监听

② 根据 token检查请求是否来自AspNet Core Module(非ASPNE TCore Module转发的请求会被拒绝)

③ 保持原始请求信息 :利用ForwardedHeaderMiddleware中间件保存原始请求信息,存储在Header

在IIS部署时, UseIISIntegration()会默认为你配置并启用ForwardedHeaderMiddleware 中间件; 在linux平台部署需要你手动启用ForwardedHeader middleware

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2

通过 UseIISIntegration() 源码快速验证:

//------------- 节选自Microsoft.AspNetCore.Hosting.WebHostBuilderIISExtensions---------------------
   public static class WebHostBuilderIISExtensions
    {
        // These are defined as ASPNETCORE_ environment variables by IIS‘s AspNetCoreModule.
        private static readonly string ServerPort = "PORT";
        private static readonly string ServerPath = "APPL_PATH";
        private static readonly string PairingToken = "TOKEN";
        private static readonly string IISAuth = "IIS_HTTPAUTH";
        private static readonly string IISWebSockets = "IIS_WEBSOCKETS_SUPPORTED";

        /// <summary>
        /// Configures the port and base path the server should listen on when running behind AspNetCoreModule.
        /// The app will also be configured to capture startup errors.
        /// </summary>
        /// <param name="hostBuilder"></param>
        /// <returns></returns>
        public static IWebHostBuilder UseIISIntegration(this IWebHostBuilder hostBuilder)
        {
            if (hostBuilder == null)
            {
                throw new ArgumentNullException(nameof(hostBuilder));
            }

            // Check if `UseIISIntegration` was called already
            if (hostBuilder.GetSetting(nameof(UseIISIntegration)) != null)
            {
                return hostBuilder;
            }

            var port = hostBuilder.GetSetting(ServerPort) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPort}");
            var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}");
            var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}");
            var iisAuth = hostBuilder.GetSetting(IISAuth) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISAuth}");
            var websocketsSupported = hostBuilder.GetSetting(IISWebSockets) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISWebSockets}");

            bool isWebSocketsSupported;
            if (!bool.TryParse(websocketsSupported, out isWebSocketsSupported))
            {
                // If the websocket support variable is not set, we will always fallback to assuming websockets are enabled.
                isWebSocketsSupported = (Environment.OSVersion.Version >= new Version(6, 2));
            }

            if (!string.IsNullOrEmpty(port) && !string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(pairingToken))
            {
                // Set flag to prevent double service configuration
                hostBuilder.UseSetting(nameof(UseIISIntegration), true.ToString());

                var enableAuth = false;
                if (string.IsNullOrEmpty(iisAuth))
                {
                    // back compat with older ANCM versions
                    enableAuth = true;
                }
                else
                {
                    // Lightup a new ANCM variable that tells us if auth is enabled.
                    foreach (var authType in iisAuth.Split(new[] { ‘;‘ }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (!string.Equals(authType, "anonymous", StringComparison.OrdinalIgnoreCase))
                        {
                            enableAuth = true;
                            break;
                        }
                    }
                }

                var address = "http://127.0.0.1:" + port;
                hostBuilder.CaptureStartupErrors(true);

                hostBuilder.ConfigureServices(services =>
                {
                    // Delay register the url so users don‘t accidently overwrite it.
                    hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, address);
                    hostBuilder.PreferHostingUrls(true);
                    services.AddSingleton<IStartupFilter>(new IISSetupFilter(pairingToken, new PathString(path), isWebSocketsSupported));
                    services.Configure<ForwardedHeadersOptions>(options =>
                    {
                        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
                    });
                    services.Configure<IISOptions>(options =>
                    {
                        options.ForwardWindowsAuthentication = enableAuth;
                    });
                    services.AddAuthenticationCore();
                });
            }

            return hostBuilder;
        }
    }

ASP.NET Core程序生成源码:

//---------------------------------节选自Microsoft.AspNetCore.Hosting.Internal.WebHost------------------------------------
  private RequestDelegate BuildApplication()
 {
      try
      {
           _applicationServicesException?.Throw();
           EnsureServer();

          var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
          var builder = builderFactory.CreateBuilder(Server.Features);
          builder.ApplicationServices = _applicationServices;

          var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
          Action<IApplicationBuilder> configure = _startup.Configure;
          foreach (var filter in startupFilters.Reverse())
          {
               configure = filter.Configure(configure);        // 挨个启动功能
          }

          configure(builder);

          return builder.Build();
       }
       ......
}

IISSetupFilter 内容:

//---------------------------------节选自Microsoft.AspNetCore.Server.IISIntegration.IISSetupFilter------------------------------------
namespace Microsoft.AspNetCore.Server.IISIntegration
{
    internal class IISSetupFilter : IStartupFilter
    {
        private readonly string _pairingToken;
        private readonly PathString _pathBase;
        private readonly bool _isWebsocketsSupported;

        internal IISSetupFilter(string pairingToken, PathString pathBase, bool isWebsocketsSupported)
        {
            _pairingToken = pairingToken;
            _pathBase = pathBase;
            _isWebsocketsSupported = isWebsocketsSupported;
        }

        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
        {
            return app =>
            {
                app.UsePathBase(_pathBase);
                app.UseForwardedHeaders();                                           //  转发时保持原始请求,放在header里面传给kestrel
                app.UseMiddleware<IISMiddleware>(_pairingToken, _isWebsocketsSupported);  //  阻止非aspnetcore module转发的请求
                next(app);
            };
        }
    }
} 

    着重理解下UseIISIntegration第②点配置: 怎样拒绝非ASP. NET Core Module 转发的请求?

       AspNetCore Module 为w3wp.exe 工作进程设置进程内环境变量 ASPNETCORE_TOKEN=******

② dotnet.exe进程继承了父进程 ASPNETCORE_TOKEN=****** 环境变量

③ AspNetCore Module转发请求到kestrel时,会在Request里面加上一个 MS-ASPNETCORE-TOKEN:****** 的请求头;非AspNetCore Module自然没有该请求头

④ IISMiddleware中间件:请求头中匹配该ASPNETCORE_TOKEN=******的请求是有效的

//---------------节选自Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware----------------------
public async Task Invoke(HttpContext httpContext)
{
      if (!string.Equals(_pairingToken, httpContext.Request.Headers[MSAspNetCoreToken], StringComparison.Ordinal))
      {
          _logger.LogError($"‘{MSAspNetCoreToken}‘ does not match the expected pairing token ‘{_pairingToken}‘, request rejected.");
         httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
         return;
      }
      ......
}

 附:部署在IIS后面的Kestrel 也是一个web服务器,怎样Hack访问搭配ASP.NET Core Module的Kestrel服务器?

按照上文的理论,部署在IIS后面的dotnet.exe程序是依靠 AspNetCore Module 设定的进程内环境变量ASPNETCORE-TOKEN来识别【非AspNetCore Module转发的请求】。

因此,理论上将该PairToken拷贝到请求头,可访问部署在IIS后面的Kestrel 服务器(这是一个hack行为,对于理解部署图很有帮助)。

操作方式如下:

① 在任务管理器中找到你要分析的dotnet进程,tasklist  /fi "imagename eq dotnet.exe" ,找到要分析{ pid }

② 找到该进程占用port : netstat -ano | findstr {pid}

③ 利用输出的port: curl localhost:{port}  --verbose:  会提示400 badrequest,这与源码的返回一致

④ 从error log 中拷贝出该环境变量:ASPNETCORE_TOKEN

‘MS-ASPNETCORE-TOKEN‘ does not match the expected pairing token ‘4cdaf1fd-66d5-4b64-b05f-db6cb8d5ebe5‘, request rejected.  

⑤ 在request中添加 MS-ASPNETCORE-TOKEN:****** 请求头

【实际上 ,可以在ASP.NET Core dotnet.exe程序内写日志输出 ASPNETCORE_TOKEN 环境变量值。】

//---------------------截取自 System.Environment类 -------------------// Retrieves the value of an environment variable from the current process or from  the Windows operating system registry key for the current user or local machine
public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target);

//  Retrieves the value of an environment variable from the current process.
public static string GetEnvironmentVariable(string variable);

原文地址:https://www.cnblogs.com/lonelyxmas/p/10646654.html

时间: 2024-11-17 09:15:36

你真的了解ASP.NET Core 部署模型吗?的相关文章

又一篇Centos7下的asp.net core部署教程

原文:又一篇Centos7下的asp.net core部署教程 历程2个多月的学习,我终于从PHP转.Net开发了. 虽然网上已经有很多关于asp.net core在linux下的部署教程了,但我还是想写一篇,主要增强我自己的记忆. 搭建的环境为Centos7 + .net core  + nginx + mysql5.7,我的网站是asp.net core后端,vue前端,数据据mysql: 第一次搭建时遇到不少的坑,我会在下面也说到 前提,你有一台可以联网,有root账号密码,有开通ssh的

ASP.net Core部署说明(Ubuntu) [转]

最近在学习asp.net core,当然学习的目的是想了解一下,Asp.net core是否真的能够是先跨平台部署. 根据目前官网资料说明,asp.net core只有在Redhat 企业版上,才能够实现所有的功能.不过这个版本是收费的,可能不是大多数人的最佳选择. 笔者目前选择的实验环境是Ubuntu 16.4 和CentOS7这两个平台. 从长远角度来说,CentOS7维护周期长,更加适合企业生产环境.Ubuntu择以更加活跃的社区,更加快速的更新受到欢迎,不过他的维护周期一般只有2~3年,

asp.net core 部署 提示DataProtectionServices 错误

今天在部署asp.net core网站时,因为调用到阿里云的api,api的参数需要加密签名,系统报出了如下错误: warn: Microsoft.Extensions.DependencyInjection.DataProtectionServices[59] Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailabl

asp.net core部署到iis

asp.net core项目部署到IIS稍微不同于之前,记录几个要点: 一.下载安装AspNetCoreModule模块,它包含在.NET Core Windows Server Hosting bundle里面,这个链接或许不是最新的了,官方文档里面有这个链接,到下图所示位置即可找到. 安装完之后,会发现多了个模块: 然后重启iis.如果还是出现以下错误: HTTP Error 502.5 - Process Failure Common causes of this issue: The a

ASP.NET Core部署到Windows IIS

网上已经有许多ASP.NET Core关于Widows IIS部署的文章,在部署到服务器时遇到了一些问题,在这里我就不再对原理进行阐释(复制)了,只写下一些关键环节,想看原理的同学请参考官网,此文章作为留用. 步骤: 1.ASP.NET Core程序内配置 2.Windows Server配置  一.ASP.NET Core应用程序配置 web.config 配置(官方教程) 重点修改 processPath 和 arguments 两个参数 processPath 修改为 dotnet arg

ASP.NET Core部署系列一:发布到IIS上

前言: 当构建一个ASP.NET Core应用程序并且计划将其运行在IIS中时,你会发现Core应用程序和之前版本的ASP.NET程序在IIS中的运行方式是完全不一样的.与ASP.NET时代不同,ASP.NET Core不再是由IIS工作进程(w3wp.exe)托管,而是使用自托管Web服务器(Kestrel)运行,IIS则是作为反向代理的角色转发请求到Kestrel不同端口的ASP.NET Core程序中,随后就将接收到的请求推送至中间件管道中去,处理完你的请求和相关业务逻辑之后再将HTTP响

解决ASP.NET Core部署到IIS,更新项目&quot;另一个程序正在使用此文件,进程无法访问&quot;

问题:部署到IIS上的ASP.NET Core项目,在更新的时候会进程占用的错误 初步解决方案: 1,关闭应用程序池 2,关闭网站 3,更新项目 缺点:网站没法访问,部署项目停的时间过长 查询官方文档后,官方给出的方案: 结合官方文档,找到了一个目前看算是比较好的解决方案(一定要看底部的特别注意) 好点的解决方案(一次配置后,1秒钟更新): 1,在D盘根目录创建一个app_offline.htm文件,注意是.htm文件,不是.html 2,创建一个待发布文件夹,把需要更新的项目文件放到该目录下

Asp.Net Core部署:史上最简单的Web部署

简介 .net core的部署方式多种多样,最好的方式可能就是docker部署了,简单快速还可以集成CI/CD,不过这里给大家介绍的是最简单快速的部署方式,适合新手尝鲜也适合小型项目的单独部署. 本篇介绍的是Windows Server环境部署,了解其他部署方式的客官请止步. 准备 Windows Server服务器,安装.Net Core3.1 部署 Step1:发布Asp.Net Core项目 使用VS或cli dotnet publish -o publish发布项目,建议直接将发布到项目

asp.net core部署到iis中出现 HTTP Error 502.5 - Process Failure的问题

环境是windows Server2012  问题的原因是缺少文件:api-ms-win-crt-runtimel1-1-0.dll, dotnet 启动程序失败. 解决方案1: 安装系统补丁: 2012 R2对应>Windows8.1-KB2999226-x64.msu 下载地址: https://www.microsoft.com/zh-CN/download/details.aspx?id=49063 2012 对应>Windows8-RT-KB2999226-x64.msu 下载地址: