Azure PaaS - Cloud Service服务架构及快速调试

开发Windows Azure云服务(Cloud Service)过程中,难免会出现一些代码异常或者部署失败,对于Azure开发者,有必要了解一下Azure云服务在云端的实际服务架构,并借此来调试和诊断Azure云服务,快速找出已有云服务中存在或隐藏的问题。本篇将覆盖以下两方面的内容:

1.了解Azure云服务的服务架构

2.了解云服务的快速调试方法

 

博主有幸在2013年的微软TechED会议(北京-上海) 上对本主题进行了讲解,本篇在之前的研究基础上,进行了部分更新。

 

1. 了解Azure云服务的服务架构

Microsoft Azure托管服务(Cloud Service)的启动和运行过程如图所示。
 

            

 

 

  • „ RDFE:Azure平台对外统一接口。图中步骤1表示开发者发起操作请求到RDFE,如上传发布包、更新托管服务配置、管理证书等。操作可以通过Azure管理门户来完成,也可以通过Visual Studio、PowerShell等工具完成。FFE模块将外部请求转成内部命令,模块查找到目标计算机群,开始与集群总控通信。
  • „ Fabric Controller:集群总控,维护和监控数据中心的所有计算资源。集群总控寻找可用的计算资源(CPU、Memory等),并与相应的Host Agent进行通信,将发布包和配置文件等信息复制到目标宿主服务器。
  • „ Host Agent:存在于Host OS中,负责准备Guest OS,并与Guest OS通信(WaAppAgent),10分钟无信号即重启Guest OS。
  • „ WaAppAgent:安装、配置、更新WindowsAzureGuestAgent。
  • „ WindowsAzureGuestAgent:配置Guest OS,包括防火墙、本地缓存、发布包、执行账号等;与集群总控Fabric Controller保持心跳同步;启动WaHostBootstrapper。
  • „ WaHostBootstrapper:读取配置,执行启动任务和部署发布包,监测所有子进程并汇报StatusCheck结果。
  • „ IISConfigurator:适用于SDK 1.2以上的发布包部署(非HWC模式),负责安装IIS并清空IIS下默认的Application Pool,待Startup Task执行完后,IISConfigurator完成网站部署和配置等。
  • „ Startup Tasks:预启动任务,包括用户自定义的依赖任务和角色需要的预装服务,以三种模式运行。对于background模式的预启动任务,被启动之后,WaHostBootstrapper不等待其返回值直接进入下一个Startup Task或后续工作流。其他两种模式的预启动任务,WaHostBootstrapper需要等待其返回值,确认预启动任务正确执行后,进入下一个流程。
  • „ WaWorkerHost:Worker Role程序的宿主进程,所有Worker Role相关的DLL和EntryPoint代码运行于此。
  • „ WaWebHost:适用于SDK 1.2等HWC(Hostable Web Core)模式的发布包,该模式下网站服务直接运行在该宿主进程下,不依赖于IIS(Application Pool)。
  • „ WaIISHost:Full IIS模式下的Web Role的宿主进程,加载、执行RoleEntryPoint代码(e:\entrypoint)。
  • „ W3WP:IIS子进程,负责托管网站服务,即IIS中Application Pool对应的进程。
  • „Load Balancer:负载均衡,外部请求被均分到同一个Web(Worker)Role下的多个实例中。 

值得一提的是,如之前的文章https://blogs.msdn.com/b/jianwu/archive/2014/08/15/azure-paas-3-azure-caching.aspx 中所提,云服务在实际运行过程中,可能会因为云平台的更新或者云服务本身的程序问题而Recycling,或者有可能因为用户的操作而重启或者重新镜像还原(reimage),故此,需要了解一下,这些活动对云服务的最终影响,如下:

虚机内部重启:

  • C: 不变
  • D: 不变
  • E: 不变

从管理门户上重启虚机:

  • C: 不变
  • D: 不变
  • E: 还原到原始部署状态

从管理门户是镜像还原(reimage):

  • C: 不变
  • D: 还原到原始部署状态
  • E: 还原到原始部署状态

更新部署(in-place upgrade):

  • C: 不变
  • D: 不变
  • E: 还原到原始部署状态

后端节点迁移:

  • C: 还原到原始部署状态
  • D: 还原到原始部署状态
  • E: 还原到原始部署状态

基于上述云服务活动中虚机上磁盘的变化,在设计云服务时,务必要注意:不要使用虚机上的磁盘作为永久存储,不要在虚机上手动安装部署环境提供永久使用。

参考出处:

https://blogs.msdn.com/b/kwill/archive/2011/05/05/windows-azure-role-architecture.aspx

https://msdn.microsoft.com/en-us/library/azure/hh472157.aspx

 

2. 了解云服务的快速调试方法

 a. 本地调试云服务

调试工具跟踪包括本地常见的工具:模拟客户端、进程分析工具等等

 启用IntelliTrace或者Profiling是跟踪云服务的一大特色:

 

 使用Visual Studio Premium或者Ultimate即可查看已经部署的云服务中运行的IntelliTrace或者Profiling详细情况。(具体步骤是 View --> Server Explorer --> Windows Azure --> 目标云服务 -->  目标云服务虚机 --> 右键查看IntelliTrace或者Profiling)

 

 b. 远程调试云服务

通过配置远程连接(remote access),开发者可以登录到云服务虚机中进行服务调试和跟踪,主要包括以下两种方式:

1.)查看服务日志

鉴于上述对云服务启动架构的了解,开发者可以逐一检查各个模块的运行日志,找出云服务运行过程中的异常所在。具体的日志、输出等项目的查看路径和方法如下。

  • „ Windows Azure Event Logs:Azure运行时(Runtime)过程中的日志信息,包括OnStart、Startup Task、Crash等,如图所示。(打开方式:“Event Viewer”→“Applications and Services Logs”→“Windows Azure”。)
     
  • „ Application Event Logs:本地日志信息,同样适用于Azure调试。W3WP和Worker Role进程抛出的应用程序信息会集中于此。(打开方式:“Event Viewer”→“Windows Logs”→“Application”。)
  • „ App Agent Runtime Logs:存储在C:\Logs\AppAgentRuntime.log目录下,是WindowsAzureGuestAgent的输出日志,包括防火墙设置、角色状态变化等。
  • App Agent Heartbeat Logs:存储在C:\Logs\WaAppAgent.log目录下,是WindowsAzureGuestAgent的输出日志,包括心跳监测的结果。
  • Host Bootstrapper Logs:WaHostBootstrapper执行日志,对于SDK 2.1及以上的托管服务,该日志存储在C:\Resources目录下,SDK 2.1以下的存储在C:\Resources\Directory\ {DeploymentID}.{Rolename}.DiagnosticStore\WaHostBootstrapper.log目录下,其详细记录了WaHostBootstrapper启动各子进程的过程和后续跟踪,WaHostBootstrapper每次启动时重新创建新的日志文件。
  • IIS Logs:IIS网站的访问日志,记录实时流量处理的详细信息,存储在C:\Resources\ Directory\{DeploymentID}.{Rolename}.DiagnosticStore\LogFiles\Web中。
  • „ HTTP.SYS Logs:存储在D:\WIndows\System32\LogFiles\HTTPERR目录下,IIS未能处理的请求会在此记录,配合IIS Log能反映出所有进来的流量情况。
  • „ IIS Failed Request Log Files:IIS处理失败的流量请求信息,存储在C:\Resources\Directory\ {DeploymentID}.{Rolename}.DiagnosticStore\FailedReqLogFiles中。
  • „ Windows Azure Diagnostics Tables and Configuration:Windows Azure诊断(Diagnostics)和Performance Counters的监控输出,临时缓存在C:\Resources\Directory\{DeploymentID}. {Rolename}.DiagnosticStore\Monitor中。
  • „ Windows Azure Caching Log Files:若开启了Windows Azure Caching的日志功能(在默认情况下该功能是开启的),Caching服务的日志缓存在C:\Resources\Directory\ {DeploymentID}.{Rolename}.DiagnosticStore\AzureCaching中。
  • „ WaIISHost Logs:WaIISHost 进程的实时输出日志,存储在C:\Resources\Directory\{DeploymentID}.{Rolename}.DiagnosticStore\WaIISHost.log中。
  • „ IISConfigurator Logs:IISConfigurator进程的实时输出日志,存储在C:\Resources\ Directory\{DeploymentID}.{Rolename}.DiagnosticStore\IISConfigurator.log中。
  • „ Role Configuration Files:发布包的配置文件和定义文件,分别缓存在C:\Config\ {DeploymentID}.{DeploymentID.{Rolename}.{Version}.xml和E:\RoleModel.xml(或F:\ RoleModel.xml)中。

 

2.)配置Windows Azure Diagnostics诊断功能

 如 https://blogs.msdn.com/b/jianwu/archive/2014/08/15/azure-paas-4.aspx 所述,在云服务中配置WAD(Windows Azure Diagnostics)可以实时将云服务虚机内部的实时信息(如应用程序日志、Windows事件日志、虚机CPU/内存使用率等)适时存储到Table Storage中,开发者可以通过分析Table Storage中永久存储的记录来分析云服务的运行质量和历史问题。

 

3.)在虚机上使用调试工具

 

 上述网页页面是一个示例,其中的很多按钮事件会导致后端代码错误,但由于代码中对有些错误做了封装处理,因此用户操作该网页时,有些错误能直接从页面上看到,有些错误看不出来,因此需要通过Windows 事件查看器、DebugView工具、ProcessExplorer、TaskManager(create dump)已经Windbg等工具来分析。

这个示例网页的主要代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.WindowsAzure.Storage;

using mjcomn;
using System.Diagnostics;

using System.Text;

namespace WebRole1
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            SqlDataReader sqldr = null;

            SqlConnection cn = new SqlConnection("Server=tcp:a0yvwcchux.database.windows.net,1433;Database=mytestdb;User ID=testlogin@a0yvxxchux;Password=mypassword@test;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;");

            SqlCommand cmd = new SqlCommand("select * from Persons", cn);

            //try to connect and read data
            Console.WriteLine("try to connect and read data...");
            try
            {
                cmd.Connection.Open();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.Read();
            }
            Console.WriteLine("connected.");
            //Console.Read();

            sqldr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
            DataTable dt = new DataTable();
            dt.Load(sqldr);
            cmd.Connection.Close();

            this.GridView1.DataSource = dt;
            this.GridView1.DataBind();
        }

运行效果如下,正常工作。

        protected void Button4_Click(object sender, EventArgs e)
        {
            SqlConnection cn = new SqlConnection("Server=tcp:a0yvwcchux.database.windows.net,1433;Database=mytestdb;User ID=testlogin@a0yxxchux;Password=mypassword@test;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;");

            SqlCommand cmd = new SqlCommand("update Persons set City='China' where LastName='testor4'", cn);

            cmd.Connection.Open();
            cmd.ExecuteNonQuery();

            cmd.Connection.Close();
        }

操作数据库异常,该按钮背后的事件执行不成功,远程登录到虚机上,在Windows 事件查看器中可以找到问题的根源:数据库是只读的,因此写操作失败。

        protected void Button2_Click(object sender, EventArgs e)
        {
            //Azure存储帐号
            var storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=portalvhds303zv6wrv9j9q;AccountKey=WNydsmystoragekey==");
            var blobclient = storageAccount.CreateCloudBlobClient();

            var container = blobclient.GetContainerReference("helloworldcontainer");
            container.CreateIfNotExists();
            var blob = container.GetBlockBlobReference("100");

            try
            {
                MemoryStream stream = new MemoryStream();
                blob.DownloadToStream(stream, null, null, null);
                TextBox1.Text = System.Text.UTF8Encoding.UTF8.GetString(stream.ToArray());
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + " : " + ex.Message);
            }
        }

读取WAS操作异常,通过远程登录,开发者可以通过本地地址访问该页面,并点击该按钮重现问题。在页面返回的信息中,可以找到问题的原因:WAS访问权限不够。

 

       protected void Button3_Click(object sender, EventArgs e)
        {
            //Azure存储帐号
            var storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=portalvhds303zv6wrv9j9q;AccountKey=WNydsi2FU6Gmystoragekey==");
            var blobclient = storageAccount.CreateCloudBlobClient();

            var container = blobclient.GetContainerReference("helloworldcontainer");
            //container.CreateIfNotExists();
            var blob = container.GetBlockBlobReference("100");

            try
            {

                byte[] buffer = System.Text.Encoding.Default.GetBytes(this.TextBox1.Text);

                blob.PutBlock(Convert.ToBase64String(System.BitConverter.GetBytes(100)), new MemoryStream(buffer, true), null);
                List<string> blocklist = new List<string>();
                blocklist.Add(Convert.ToBase64String(System.BitConverter.GetBytes(100)));

                blob.PutBlockList(blocklist);
            }
            catch (StorageException ex)
            {
                System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + " : " + ex.Message + "\r\n" + ex.StackTrace.ToString());
            }
            finally
            {
                this.TextBox1.Text = DateTime.Now.ToString() + " : Write data into WAS operation failed .";
            }
        }

本用例依然是操作WAS,但上述代码对程序异常进行了初步处理,导致异常返回的错误信息既不显示在返回页面上,也不返回在Windows事件查看器中。但是,开发者可以使用DebugView工具进行跟踪,实时捕捉到该异常,如图: 

        protected void Button5_Click(object sender, EventArgs e)
        {
            mjcomn.theApp app = new theApp();
            app.WriteINI("KeyValues", "Date", DateTime.Now.ToString(), "./myout.ini");
            this.TextBox1.Text = DateTime.Now.ToString() + " : succeeded using custom reference.";
        }

访问本地文件测试,开发者可以以独占的方式使用目标文件(通过代码可以实现,如FileStream objFileStream = new FileStream(@"c:\a.txt", FileMode.Append, FileAccess.ReadWrite, FileShare.None); ),接下来点击该按钮时会导致访问异常access denied,此时使用Process Monitor工具可以查看到当前目标文件被哪个程序占用,从而找到该问题的根源。

        protected void Button6_Click(object sender, EventArgs e)
        {
            PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total");

            this.TextBox1.Text = p.NextValue().ToString();

            float targetCPU = 85;

            while (true)
            {
                if (p.NextValue() > targetCPU)
                {
                    System.Threading.Thread.Sleep(2);
                }
            }
        }

        protected void Button7_Click(object sender, EventArgs e)
        {
            PerformanceCounter p = new PerformanceCounter("Memory", "Available Bytes");

            float targetFreeMem = 100000000; //100MB

            StringBuilder myStr = new StringBuilder("hello,World,this is test for Mem.<EOM>");

            while (true)
            {
                if (p.NextValue() > targetFreeMem)
                {
                    myStr.Append(myStr.ToString());
                }
                else
                {
                    System.Threading.Thread.Sleep(30000);
                }
            }
        }
    }
}

上面两处用例的目的是造成虚机的高CPU/Memory,使得虚机瞬间慢下来。对于此类问题,可以采用传统的分析工具Windbg来分析目标进程的转储文件(dump file),一个收集目标进程dump file的简单方法是,如下,在任务管理器中,选中CPU/Memory使用率最高的进程,选择属性中的创建转储文件(Create Dump File),即收集到了“问题”进程的转储文件。下图为样图,实际操作时,目标进程很有可能是w3wp.exe.

对于产生的转储文件,分别使用上述提到的Windbg工具分析,可以得到以下线索,开发者可以依此来进一步修改代码。

 关于Dump文件分析及Windbg工具的使用,博主以后会尝试对该主题进行总结分享。开发者可以快速参考以下文章,快速学习:

https://www.cnblogs.com/yuanxiaoping_21cn_com/archive/2012/09/21/2697520.html

https://blogs.msdn.com/b/kaevans/archive/2011/04/11/intro-to-windbg-for-net-developers.aspx

 

关于上述用到的DebugView,Process Monitor,Windbg等工具,开发者可以直接在搜索引擎中下载,或者到以下链接进行下载:

https://blogs.msdn.com/b/azure-cn/archive/2013/12/06/debugging-tools-shared-in-teched-2013.aspx