Azure WebApp Logs自动备份到Azure Storage

Azure WebApp比自己租用IIS部署ASP.NET或者ASP.NET CORE程序有很大的优势,包括但不限于自动缩放(当然是要给钱的)、自动缩放无需配置负载均衡、Visual Studio集成好、部署上传速度快、不需要担心IIS环境部署容易出错等问题。

但有一些用户会觉得不太适应,因为Azure WebApp并非一台虚拟机,可以理解为一个类似于Docker容器,主要是给你程序运行资源,而不是给你用于存储的。Azure WebApp连Web访问日志和系统日志都是需要配置到一个Azure Storage Blob存储里面的,所以它的理念是让存储文件更加“分布式”——就是多个WebApp应该都把日志存放在一个集中的Storage里面。

但是这个给传统程序带来一些不方便,使用log4net、nlog等日志写入组件会需要写入磁盘,而Azure Storage不方便挂载到WebApp里面。如果日志写多了,很可能把Azure WebApp写爆。

那么有两种方法,一种是自定义一个log4net、nlog等的日志Appender,让日志写入到Azure Storage Blob存储或者Table存储里面都可以,这部分代码在GitHub上有一些。配置Appender到log4net.config或者nlog.config就可以使用。

但是笔者在自己试过写一个写入Azure Storage Table的日志记录程序之后,发现大部分情况下Azure Storage Table方案显得有点不方便,有一种更简单的适合更小型程序的方式,就是把log4net或者nlog生成的日志文件归档到Azure Storage Blob里面。

以下就是这个自动备份的程序代码:

Gitee代码Repository:https://gitee.com/gzkeith/webapp-logs-backup

首先需要建一个WebJob项目,直接在Visual Studio里面建就可以了,但我们如果要定时,则需要加入Microsoft.Azure.WebJobs.Extensions,使用Nuget添加:

注意我们使用的是2.x版本,需要保证Azure.WebJobs Nuget包和Extensions Nuget包同版本。现在已经有3.0版本刚刚出来了,写法完全不一样,截止到今天网上找不到多少参考,所以千万别直接升级到3.0或以后。

而目前Visual Studio也没有.net core的WebJob项目模板,暂时还是用.NET Framework 4.6来做吧。

WebJobs项目应该自带WindowsAzure.Storage,这个是使用Storage Blob存储必须的。然后笔者是强迫症升级了最新版本了,再之后,加入一个方便处理的包:Microsoft.Azure.Storage.DataMovement

这个包是微软自己写的,用来高效读写Storage的。用了它,减少处理二进制文件的复杂度。使用里面的TransportManager,直接把文件路径或者Stream传进去,设置一个缓存,UploadAsync就会自动分块多线程上传,不用自己做byte[]缓存了,能够在想要简单解决问题的时候满足我们的需要。

加好了Nuget包,记得在启动前设置UseTimers,不然cron表达式会不起作用:

然后我建立一个定时的WebJobs函数,因为是Debug需要,就设置为2分钟一次,实际上要按照日志文件的分割逻辑、写入空间量设定一个合理的备份时间避免写爆磁盘空间,又不要经常做无必要的轮询:

上面这里用了一个环境变量路径:WEBROOT_PATH

这是Azure WebApp自带的,能够直接进到WebApp的运行路径。如果通过GetCurrentDirectory获取执行位置,只会获取到一个临时文件位置,因为WebApp运行会先Copy到一个临时文件位置避免执行时锁死文件路径。

笔者这边使用nlog,直接把文件写到logs目录底下了:

所以直接组合了DirectoryInfo。

后面就是迭代里面的文件了。笔者直接使用nlog的.net core日志模式,文件名是nlog-all-yyyy-MM-dd.log和nlog-own-yyyy-MM-dd.log这样的格式。于是就扫描里面所有的nlog-*.log,除掉当前(必须使用UTC时间,WebApp上面运行是使用UTC时间的)的日志,其他的日志都归档。

这部分请直接参见代码,就不贴图了。

有一个位置需要注意的,使用DataMovement包的TransferManager.UploadAsync最好配置一下如果遇到文件重名就overwrite,否则会报错:

接着就是运行问题了。要保证WebJob按时触发不延迟,是需要把WebApp的“始终可用”打开的:

但是打开这个,显然收费的级别就最少要到B1,费用降不下去了:

WebApp的App Service Plan上去之后,有10GB存储就没那么容易写爆了,好像意义没那么大了。

其实也是可以在ASP.net程序里面做一个Web API接口,按照存储需要手动触发日志备份,这样就可以经济一点,但就需要做一个自己轮询自己的功能,隔一分钟调用自己一次进行扫描,扫描结束之后再延时调用自己下一次轮询。

ASP.NET Core + IIS 502.5错误码0x8007010b的解决方法

ASP.NET Core是跨平台的Web技术,像Spring Boot一样使用内置Kestrel的方式就能够作为一个进程自己跑起来承担Web应用服务器的功能。

但是Kestrel强项在于处理动态请求,作为Web服务器的能力稍弱,对于静态文件支持、请求缓存等Web服务器功能支持都不算强,并且不建议使用一个控制台进程的方式把Kestrel直接用于生产。

使用Windows平台,通常使用IIS作为Kestrel的反向代理运行起来。使用Linux,我们可以选择使用NginX作为反向代理。

但是在Visual Studio调试中直接使用IIS调试会非常顺利,碰不到实际上线部署所碰到的问题,例如在低版本Windows里面所出现的部署不成功问题。

现在按照开发人员常见的部署IIS步骤,排除一些里面会出现的问题。

一、首先,把一个ASP.NET Core Web程序发布,以下使用默认发布方式

这是最常见的发布方式。

二、在Win10 IIS里面随便一个站点,创建一个应用程序

三、在确认IIS是否能成功跑起来之前,先用命令行的方式确认直接跑Kestrel能否跑起来

Ketrel端口默认是5000,建议不要占用它,方便随时使用默认的形式调试。命令行使用

1
$ dotnet ****.dll

的方式运行,这个是成功的。

四、IIS跑起来的时候报500.19

如图:

因为我们之前已经确认过Kestrel运行asp.net core站点是正常的,那么到底是什么原因呢?

其实是开发人员在ASP.NET上过于经验丰富,没看说明就跑起来了。

看一下微软的官方指导,如何用IIS Host ASP.NET Core(由于公众号的限制,直接贴网址,网址可能随着版本变,搜索一下就可以了):

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.1&tabs=aspnetcore2x

因为ASP.NET Core的运行方式和原来的ASP.NET有所不同,所以不是安装一个.NET Framework就搞定的,需要安装各种组件适配各种情况。.NET Core的Prerequisite就要求C++ 2015 Redistribution,这个在类似于Windows 2008 R2(Win7)这样的老系统上,一般是没有的。而开发人员如果使用Win7来开发,不会出现这个问题,是因为安装Visual Studio的时候会把Microsoft Visual C++ 2015 Redistribution装上,所以本机根本不会有这个问题。

如果没有安装C++ 2015 Redistribution,是会爆“丢失api-ms-win-crt-runtime-l1-1-0.dll”的:

.NET Core Hosting Bundle就更加不用说了,用VS调试IIS根本不会报问题,因为VS不是使用web.config文件启动ASP.NET Core程序,是有一套自己的启动方式。

五、安装.NET Core Hosting Bundle

但是我们如果没有安装VS,要单独部署ASP.NET Core的话,需要安装.NET Core Hosting Bundle。网上查的资料通常较旧,都是.NET Core 1.x时代的,那个时代.NET Core Hosting Bundle可能还是单独下载的。而到了ASP.NET Core 2.x,则是通过下载这个:

下载一个.NET Core Runtime,在.NET Core SDK的下载页直接就可以看到了。但笔者真的有点奇怪,为什么Runtime不可以直接包含在SDK里面?SDK应该是基于Runtime提供更多的开发支持,而不是用不同的runtime,所以逻辑上应该是可以合二为一的,至于是不是微软某些技术兼容的考虑就不得而知了。

六:真正的问题来了——运行的时候爆IIS 502.5

笔者找这个问题的答案最为费劲,网上什么解答方式都试过,主要都是说安装.NET Core Hosting Bundle就能解决,另外就是看一下Windows日志,看一下报错的问题是什么:

这个问题是从Windows Event Logs里面找到的。错误码0x8007010b。Google一下网上的说法基本上一样,就是安装.NET Core Hosting Bundle,然后针对这条日志记录,看一下web.config文件指示的文件位置是否正确、dotnet命令是否正确等。

实际上我们一开始就已经Kestrel验证过,直接运行“dotnet ***.dll [参数] ”是可行的。那么我们继续排查。

七、尝试解决依赖的部署方式

进程无法启动,意味着不是代码的问题,是IIS运行方式的问题。

我们尝试另一种部署方式:考虑解决所有dll依赖的问题,因为DLL依赖不通过,也是没办法启动起来的,而这通常还无法输出日志。所以我们用SelfContained的发布方式,尽量.NET Runtime启动的时候不报异常:

这个东西是平台相关的,所以如果要跑在Linux等底下就需要另外编译了,有点烦,但是为了先解决问题,暂时不考虑这个。

这样会编译出来很多平台相关的、本来该在.net Core SDK里面的DLL。还有一堆的Nuget引用的DLL都被编译生成出来了,跟原来的.NET程序类似,一个小小的ASP.NET Demo项目就会大概100MB左右。

然后我们再部署到没有VS的干净的环境里,这样就可以了么?

Too young, naive呀

八、NuGet安装Microsoft.AspNetCore.Server.IISIntegration,设置UseIISIntegration()

看上面给过的微软官方的IIS Host指导,我们还有一项没有做,就是这个:

ASP.NET Core模板生成的默认代码里面是Program.cs文件的这里:

但ASP.NET Core都喜欢使用extensions的方法,到底有没有用,是需要看有没有实现的对象对框架进行依赖注入的。而使用IIS集成的依赖注入,就是在NuGet中安装Microsoft.AspNetCore.Server.IISIntegration。

NuGet如何安装这个的就不在这个文章里面细说了。

这些都做完,在Windows 2008 R2上面重启IIS之后,基本上就OK了。来到这里,其实已经基本上解决了ASP.NET Core + IIS 502.5错误码0x8007010b这个问题。

但是如果IIS Host ASP.NET Core在没有安装VS调试过IIS的Win10上面,事情就还没有完,还是IIS 502.5错误码0x8007010b,What the fuck?

后面发现,这个主要的原因,是我们使用服务器跟使用Win10等非服务器系统的不同。

九、设置Windows用户IIS Application Pool对文件访问的权限

通常使用Windows服务器的人都比较懒,直接使用Administrator(或者Administrators用户组,以下不强调了)进行操作,那么很多文件访问的事情都会变得非常方便,因为权限够高。

而通过Administrator登录,安装IIS、创建IIS站点的时候所创建的Application Pool,是继承了拥有足够高的读写权限的。于是Application Pool能够访问除了必须Administrators或者SYSTEM组权限的目录,那么IIS以IIS或者某个IIS Application Pool用户跑Kestrel的时候都能够成功。

而使用Win10等默认给个人使用的操作系统,则可能不会成功,因为我们通常工作在非管理员用户底下,典型的非管理员用户就是使用了Microsoft ID登录Win10,一直没有使用Administrator(从某个时间段开始,微软的操作系统Administrator自动隐藏了,需要net user Administrator /active:yes 才能见到并激活使用)。我们在Win10安装IIS、创建一个新的站点自动创建一个对应的IIS Application Pool用户,其实权限并没有那么高,哪怕安装IIS过程中中间需要弹窗让我们授权。

在Linux底下,需要root权限的命令都需要先sudo,那么补偿的操作,首先不是用Administrator解决所有问题,而是分配一下权限:

在你需要运行ASP.NET Core的目录右键->属性->安全,通过“编辑……”增加一个你所使用的Application Pool对应的用户的权限来解决。

用户名通常是这种格式:IIS AppPool<myappoolname>

例如上面的是Wp2Self,在查找用户的时候则输入“IIS AppPool\Wp2Self”查找就出来了。

具体添加的步骤也不细说,估计大部分的读者没太多经验也都有Google的能力。

这次就应该真的解决了。然而这样还没够。既然Windows使用IIS做反向代理到Kestrel这么麻烦,我们试试Linux使用NginX反向代理如何?

十、安装NginX并设置反向代理

安装Nginx也不说了,网上资料非常多,先测试运行一下保证NginX安装成功:

这次别自作聪明,当个小白,直接看微软的官方指导文档:

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-2.1&tabs=aspnetcore2x

如果不加这个Http Header Forward,则反向代理就不带信息过来了。

然后在/etc/nginx/conf.d/nginx.conf(没有这个文件就创建一个)配置NginX反向代理:

来到这里可能需要注意,NginX反向代理并没有像IIS那样还负责把Kestrel跑起来。所以这里的proxy_pass所指向的http://localhost:5000是需要我们自己通过“dotnet ****.dll”跑起来的。

配置好NginX和跑起Kestrel,sudo nginx -s reload 之后测试一下:

我们的服务器的确是变成了NginX了。但是在Linux平台下Kestrel的稳定性,还是需要像SpringBoot直接自己跑Web应用一样,仔细考虑一下稳定性的。

写在最后

其实真的挺累,部署个ASP.NET Core Hello World 都这么困难。除了要解决环境问题,还要解决可靠性问题。

其实如果条件允许,真没必要去花精力在这些上面,直接使用Azure WebApp一键部署:

不但不用关心基础设施,一步就部署成功,而且Azure WebApp保证运行在里面的程序很高的可用性,还能自动根据负载缩放程序运行的实例等等功能。

我是不会告诉你这是一个一站式解决的方案的。

但是使用Azure WebApp也是有需要注意的地方的。

那就是Azure WebApp未必支持最新的.NET Core,所以需要在控制台里面找Kudu看一下:

部署在Azure WebApp程序的ASP.NET Core的版本,一定要不高于这个版本才行。