深度理解IIS下部署ASP.NET Core2.1 Web應用拓撲圖
IIS部署ASP.NET Core2.1 應用拓撲圖
我們看到相比Asp.Net, 出現了3個新的組件:ASP.NET Core Module、Kestrel、dotnet.exe, 後面我們會理清楚這三個組件的作用和組件之間的交互原理。
引入Kestrel的原因
進程內HTTP服務器,與老牌web服務器解耦,實現跨平臺部署
IIS、Nginx、Apache等老牌web服務器有他們自己的啟動進程和環境;為了實現跨平臺部署,需要與這些web服務器的功能解耦,網絡通信是一個比較好的選擇。
作為進程內Http服務器,ASP.NET Core 保持獨立運作一個Http服務器的能力,由這些老牌web服務器充當反向代理服務器將請求轉發給進程內Http服務器,這樣保持了開發過程中配置Startup、Program文件的純粹性 和部署時候的跨平臺能力。
客觀上Kestrel還是作為Http服務器,功能還比不上老牌的web服務器
也就是說從主觀和客觀上都要求生產部署使用反向代理服務器Kestrel自誕生之日起還有一些網絡安全方面的缺陷,這些缺陷包括一個合適的timeouts,Size limits,和並發數量等
在內網部署和開發環境中我們完全可以使用Kestrel來充當web服務器。
Kestrel的底層實現細節:Kestrel 要做到跨平臺HTTP服務器,需要脫離底層系統細節實現跨平臺IO,在Asp.NetCore2.1 版本之前使用libuv(一個高性能跨平臺IO庫),2.1 版本之後采用的托管Sockets,這是一個巨大的變化,有興趣的可以搜索一下。
引入ASP.NET Core Module
反向代理服務器的作用是將請求轉發給內網的Http服務器,IIS上使用ASP.NET Core Module組件將請求轉發到Kestrel Http服務器; 註意該組件只在IIS上有效。
從整個拓撲圖上看,請求首先到達內核態Http.sys Driver,該驅動將請求路由到IIS上指定網站;
然後Asp.Net Core Module將請求轉發給Kestrel服務器。
作為企業級服務ASP.NET Core Module需要完成:
-
進程管理: 控制webApp啟動進程內Kestrel服務器在某端口上啟動,並監聽轉發請求
-
故障恢復: 控制webapp在1min內崩潰重啟的次數
-
請求轉發
-
啟動日誌記錄: webapp啟動失敗,可通過配置將日誌輸出到指定目錄
-
請求頭信息轉發:代理服務器需要保證傳遞 源IP地址、源Scheme、原始Host請求頭等信息
-
轉發windiws認證token
以上能力,可以參考https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.1
給出的AspNetCore Module配置參數
自然可以猜想ASP.NET Core Module與IISIntegration()關系很是密切:
-
Web啟動的時候,ASP.NET Core Module會通過環境變量指定kestrel監聽的端口;
-
IIS Integration方法則配置服務器在http://localhost:{指定端口}上監聽。
-
同時開始檢查請求是否來自AspNet Core Module(非ASPNE TCore Module轉發的請求會被拒絕)
薅一下UseIISIntegration() 源碼:另外由於反向代理服務器的存在,webapp可能會丟失原始請求信息; 比如
- 源IP地址
- scheme: 反向代理服務器和Kestrel之間通過Http交互
- 可能被代理服務器修改的原始Host請求頭
這些工作由ForwardedHeader middleware完成,該中間件由UseIISIntegration方法默認開啟。
https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2
//------------- 節選自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; } }
可以看到Web程序在配置啟動時讀取了5個由AspNET Core Module設置的環境變量。
著重理解Kestrel是怎麽拒絕來自非AspNetCore Module轉發的請求:
-
AspNetCore Module 設置TOKEN=XXX環境變量
-
WebApp啟動時讀取Token=XXX環境變量
-
AspNetCore Module在轉發請求時,會在Request裏面加上一個 MS-ASPNETCORE-TOKEN:XXX的請求頭
-
IISMiddleware中間件將會起作用:攜帶了該請求頭的轉發請求被認為有效。
//---------------節選自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; } ...... }
20181205 更新,.NetCore2.2+ ASP.NETCore Module支持進程內托管模型,本文章內容針對AspNetCore2.1
FAQ:
1. 什麽叫反向代理服務器Reverse Proxy Server?
通常的代理服務器,只用於代理內部網絡對Internet的連接需求,客戶機必須指定代理服務器將本來要直接發送到web服務器上的http請求發送到代理服務器中,普通的代理服務器不支持外部對內部網絡的訪問請求;
當一個代理服務器能夠代理外部網絡的主機,訪問內部網絡,這種代理服務器的方式稱為反向代理服務器 。
在AspNet Core Web應用IIS、Ngnix轉發請求時,內部的Kestrel和Applicaiton Code組成完整的內部網絡,IIS、Nginx等web服務器在這裏的角色就起到了反向代理作用。
2. 依照上圖的拓撲圖我們理所當然可以認為背靠在IIS後面的Kestrel應該是可以訪問的,因為它也是一個web服務器,那麽怎樣直接訪問背靠在AspNetCore Module後面的的Kestrel服務器?
按照上文的理論,背靠在AspNetCore Module後面的Web進程是依靠 Token來拒絕【非AspNetCore Module轉發的請求】。
https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.2
因此,將該PairToken拷貝到請求頭,我們也是可以在Kestrel behind IIS的情況下,直接訪問Kestrel(雖然這種情況不常見,但是對於我們理解拓撲圖很有幫助)。
步驟如下:
-
找到dotnet進程: tasklist | findstr "dotnet"
-
找到該進程占用port : netstat -ano | findstr {pid}
-
利用輸出的port: curl localhost:{port}, 會提示400 badrequest, 這與源碼的返回一致
-
從error log 中拷貝出該MS-ASPNETCORE-TOKEN, 附在request header中便可以訪問
--------------如果你覺得文章對你有價值,請點贊或者關註,支持原創勢力,蟹蟹--------------~~。。~~--------------~~。。~~----------------
深度理解IIS下部署ASP.NET Core2.1 Web應用拓撲圖