导出 (0) 打印
全部展开

在 Windows Azure 中开发 VM 角色的适配器

更新时间: 2011年3月

[Windows Azure 的“VM 角色”功能将于 2013 年 5 月 15 日停用。在该停用日期之后,将删除 VM 角色部署。 要继续使用现有应用程序,你可以使用 Windows Azure 虚拟机。 有关为应用程序使用虚拟机的详细信息,请参阅 Moving from VM Role to Windows Azure Virtual Machines(从 VM 角色转移到 Windows Azure 虚拟机)

您可以编写一个适配器作为 Windows 服务,它在操作系统启动时自动启动,并使用 Microsoft.WindowsAzure.ServiceRuntime API 以利用 Windows Azure 提供的运行时信息。如果需要当前 VM 角色实例或其他服务中运行的角色实例的网络地址信息,如果需要写入本地存储资源,或者如果需要在运行时读取服务配置设置或当它们发生更改时进行响应,则需要编写一个 Windows 服务。

本部分介绍如何构建适配器以展示支持 VM 角色的服务。本节使用的示例在操作系统启动时装载 Windows Azure 驱动器,然后配置 Internet Information Services (IIS) 将 HTTP 日志文件写入该驱动器。

要创建适配器,必须完成以下任务:

  1. 创建存储容器

  2. 创建适配器项目

  3. 添加跟踪角色实例配置更改的功能

  4. 定义当适配器启动时的操作

  5. 定义当适配器停止时的操作

  6. 创建适配器的安装程序

  7. 创建适配器的设置项目

  8. 添加一个生成后事件以纠正程序集不一致问题

  9. 向云服务模型添加配置设置

  10. 安装适配器

适配器使用 Blob 存储实现日志数据在 Windows Azure 驱动器中的存储。您必须具有 Windows Azure 存储帐户的访问权限,且必须创建一个容器供适配器使用。您可以用自己常用的工具在 Windows Azure 存储中创建一个容器,可为其选择任何名称。在配置云服务模型时会用到容器的名称。

Visual Studio 2010 提供创建 Windows 服务使用的模板。您可以使用此模板创建 VM 角色实例的适配器。

  1. 打开 Visual Studio 2010,依次单击“文件”“新建”“项目”

  2. “Visual C#”下的“已安装的模板”窗格中,单击“Windows”,然后在中间窗格单击“Windows 服务”

  3. 为解决方案和适配器项目输入一个名称,然后单击“确定”

  4. 在解决方案资源管理器中右键单击该解决方案,单击“配置管理器”,确保该项目选择“任意 CPU”,然后单击“关闭”

  5. 在解决方案资源管理器中,将 Service1.cs 文件重命名为适配器所使用的名称。本节中的示例使用 AdapterName 占位符,它表示您所选择的名称。

  6. 右键单击该项目,在应用程序页单击“属性”,确保“目标框架”选择“.NET Framework 3.5”

  7. 在项目属性的发布页中单击“系统必备”,确保未选择“.NET Framework 4 (Client Profile)”

  8. 在解决方案资源管理器中添加以下程序集的引用:

    • Microsoft.WindowsAzure.ServiceRuntime.dll

      note注释
      必须确保将此程序集的“复制本地”属性设置为 False

    • Microsoft.WindowsAzure.CloudDrive.dll

    • Microsoft.WindowsAzure.StorageClient.dll

    • Microsoft.Web.Administration.dll

  9. 在解决方案资源管理器中右键单击 AdapterName.cs,再单击“查看代码”

  10. 使用语句添加以下内容:

    
    using Microsoft.WindowsAzure.ServiceRuntime;
    using Microsoft.WindowsAzure.StorageClient;
    using Microsoft.WindowsAzure;
    using Microsoft.Web.Administration;
    using System.IO;
    using System.Threading;
    
  11. 在解决方案资源管理器中右键单击 AdapterName.cs,再单击“视图设计器”

  12. “属性”窗格中设置以下属性:

    • ServiceName - 您可以指定该名称作为适配器在系统中的标识。

    • (Name) - 您可以指定代码中用来表示适配器对象的名称。

    • CanShutdown – 您必须将此属性的值指定为 True,以便当系统关闭时适配器能收到通知。

Program.cs 文件包含以下代码:


static void Main()
{
   ServiceBase[] ServicesToRun;
   ServicesToRun = new ServiceBase[] 
   { 
      new ServiceName() 
   };  
   ServiceBase.Run(ServicesToRun);
}

可以配置当角色实例的配置更改时执行的操作。例如,如果通过更改角色实例的配置来存储日志数据,则可以更改驱动器的位置。为此,请使用 RoleEnvironment 事件。

  1. 在解决方案资源管理器中右键单击 AdapterName.cs,再单击“查看代码”

  2. 通过编辑构造函数定义配置更改事件。下面的代码示例显示添加到构造函数的事件定义:

    
    public AdapterName()
    {
       InitializeComponent();
       RoleEnvironment.Changed += RoleEnvironmentChanged;
       RoleEnvironment.StatusCheck += RoleEnvironmentStatusCheck;
    }
    
  3. 添加当发生更改事件时调用的 RoleEnvironmentChanged 方法。以下代码示例显示的是 RoleEnvironmentChanged 方法,该方法装载新的 Windows Azure 驱动器并将 IIS 配置为将日志文件写入该新驱动器:

    
    private CloudDrive currentDrive;
     
    private void RoleEnvironmentChanged(object sender, RoleEnvironmentChangedEventArgs e)
    {
       if(!e.Changes.OfType<RoleEnvironmentConfigurationSettingChange>().Any(
          c => c.ConfigurationSettingName == "AdapterName.BlobPath"))
          return;
    
       try
       {
          // perform a rolling drive change
          var oldDrive = this.currentDrive;
          var newDrive = MountDrive();
          try
          {
             ConfigureWebServer(newDrive.LocalPath);
          }
          catch (Exception)
          {
             UnmountDrive(newDrive);
             throw;
          }
          this.currentDrive = newDrive;
          UnmountDrive(oldDrive);
       }
       catch (Exception ex)
       {
          this.EventLog.WriteEntry(ex.ToString(), EventLogEntryType.Error);
          throw;
       }
    }
    

    其中 AdapterName 是您为适配器项目提供的名称。

  4. 添加装载和卸载 Windows Azure 驱动器的方法,并添加用来配置 IIS 日志文件位置的方法。下面的代码示例介绍 MountDrive 方法。

    
    private CloudDrive MountDrive()
    {
       // create or mount an instance-specific drive
       var credentials = GetStorageCredentials();
       var driveUri = GetDriveUri();
       var drive = new CloudDrive(driveUri, credentials);
    
       try
       {
          drive.Create(1024);
       }
       catch (Exception ex)
       {
          if (ex.Message != "ERROR_BLOB_ALREADY_EXISTS") throw;
       }
    
       // mount the drive
       string mountPoint = drive.Mount(1024, 
          DriveMountOptions.FixFileSystemErrors | DriveMountOptions.Force);
       this.EventLog.WriteEntry(string.Format("{0} mounted at {1}", drive.Uri, mountPoint));
       return drive;
    }
    
    private Uri GetDriveUri()
    {
       return new Uri(string.Format(
       RoleEnvironment.GetConfigurationSettingValue("AdapterName.BlobPath"),
          RoleEnvironment.CurrentRoleInstance.Id));
    }
    
    private StorageCredentials GetStorageCredentials()
    {
       return new StorageCredentialsAccountAndKey(
       RoleEnvironment.GetConfigurationSettingValue("AdapterName.AccountName"),
       RoleEnvironment.GetConfigurationSettingValue("AdapterName.AccountKey"));
    }
    

    有关如何使用 CloudDrive API 的更多信息,请参见 CloudDriveAdapterName.BlobPath、AdapterName.AccountName 和 AdapterName.AccountKey 设置在云服务定义文件中定义,在服务配置文件中配置。有关如何定义这些设置的更多信息,请参见向云服务模型添加配置设置

    下面的代码示例介绍 UnmountDrive 方法:

    
    private void UnmountDrive(CloudDrive drive)
    {
       drive.Unmount();
       this.EventLog.WriteEntry(string.Format("{0} unmounted", drive.Uri));
    }
    
    

    下面的代码示例介绍 ConfigureWebServer 方法:

    
    private void ConfigureWebServer(string drivePath)
    {
       using (var config = new ServerManager())
       {
          var logdir = Path.Combine(drivePath, @"inetpub\logs\LogFiles");
          config.SiteDefaults.LogFile.Directory = logdir;
    
          config.CommitChanges();
    
          this.EventLog.WriteEntry(string.Format("IIS log location set to '{0}'", logdir));
       }
    }
    
    
  5. 添加用来确定角色实例状态的 RoleEnvironmentStatusCheck 方法。statusCheckWaitHandle 对象用作信号,用来通知负载平衡器角色实例是繁忙还是可用。配置更改完成的信号是调用对象的 Set 方法。下面的代码示例介绍 RoleEnvironmentStatusCheck 方法。

    
    private readonly EventWaitHandle statusCheckWaitHandle = new ManualResetEvent(false);
    private volatile bool busy = true;
    
    private void RoleEnvironmentStatusCheck(object sender, RoleInstanceStatusCheckEventArgs e)
    {
       if (this.busy)
       {
          e.SetBusy();
       }
       statusCheckWaitHandle.Set();
    }
    
    

此示例中,适配器启动时执行以下操作:

  • 初始化驱动器缓存

  • 装载驱动器

  • 将 IIS 配置为使用装载的驱动器

  1. 在解决方案资源管理器中右键单击 AdapterName.cs,再单击“查看代码”。找到在创建项目时自动重写的 OnStart 方法,添加用于检查角色实例的代码以确保其运行,在一个单独的线程上执行适配器的操作,然后使用 statusCheckWaitHandle 对象通知操作完成。

    
    protected override void OnStart(string[] args)
    {
       /// Windows Azure waits for auto-start services to be fully started 
       /// before sending any traffic.  Service Control Manager (SCM) does
       /// not impose a time limit on startup; the service need only 
       /// request additional time.
    
       if (!RoleEnvironment.IsAvailable) return;
          
       var startThread = new Thread(OnStartInternal);
       startThread.Start();
    
       // wait until a status check has occurred, so that Windows Azure 
       // knows we are working on something.
       WaitForHandle(statusCheckWaitHandle);
    }
    

    下面的代码示例介绍 OnStartInternal 方法,该方法装载 Windows Azure 驱动器并更改 IIS 配置以使用该新驱动器写入日志数据:

    
    private void OnStartInternal()
    {
       try
       {
          // initialize the drive cache
          string cachePath = RoleEnvironment.GetLocalResource("Data").RootPath;
          CloudDrive.InitializeCache(cachePath, 4096);
          this.EventLog.WriteEntry("initialization succeeded");
    
          // mount the current drive
          this.currentDrive = MountDrive();
    
          // configure IIS
          ConfigureWebServer(this.currentDrive.LocalPath);
    
          this.busy = false;
       }
       catch (Exception ex)
       {
          this.EventLog.WriteEntry(ex.ToString(), EventLogEntryType.Error);
          throw;
       }
    }
    

    下面的代码示例介绍 WaitForHandle 方法,该方法等待状态检查的发生:

    
    private const int ThreadPollTimeInMilliseconds = 1000;
    
    private void WaitForHandle(WaitHandle handle)
    {
       while (!handle.WaitOne(ThreadPollTimeInMilliseconds))
       {
          this.RequestAdditionalTime(ThreadPollTimeInMilliseconds * 2);
       }
    }
    

在此示例中,适配器重新配置日志文件位置,并在驱动器停止后卸载驱动器。

  1. 在解决方案资源管理器中右键单击 AdapterName.cs,再单击“查看代码”。找到在创建项目时自动重写的 OnStop 方法,然后添加代码以重新配置日志文件位置和卸载驱动器。

    
    protected override void OnStop()
    {
       OnStopInternal();
    }
    
  2. 下面的代码示例介绍 OnShutdown 方法:

    
    protected override void OnShutdown()
    {
       /// Windows Azure stops sending traffic before shutting down.
       /// Note that some requests may still be executing.
       OnStopInternal();
    }
    
  3. 添加用来执行操作的 OnStopInternal 方法。

    
    private void OnStopInternal()
    {
       try
       {
          ConfigureWebServer(@"%SystemDrive%");
    
          if (this.currentDrive != null)
          {
             UnmountDrive(this.currentDrive);
             this.currentDrive = null;
          }
       }
       catch (Exception ex)
       {
          this.EventLog.WriteEntry(ex.ToString(), EventLogEntryType.Error);
          throw;
       }
    }
    
    

安装 Windows 服务时需要执行部分自定义操作,这些操作可由 Installer 类完成。Visual Studio 可以专门为某个 Windows 服务创建这些安装程序,并将它们添加到项目中。

  1. 在解决方案资源管理器中右键单击 AdapterName.cs,再选择“视图设计器”

  2. 单击设计器的背景,选择适配器本身而非其中的任何内容。

  3. 当设计器处于焦点中时,右键单击,然后单击“添加安装程序”

    默认情况下,会向项目添加一个包含两个安装程序的组件类。组件命名为 ProjectInstaller,它包含的安装程序是适配器的安装程序和适配器关联进程的安装程序。

  4. ProjectInstaller 的设计视图中,单击 serviceInstaller1

  5. “属性”窗口中,将 ServiceName 属性设置为适配器的名称。

  6. StartType 属性设置为“自动”

  7. 在设计器中单击 serviceProcessInstaller1。将“Account”属性设置为 LocalService。这样适配器将安装和运行在本地服务帐户上。

  8. 生成项目。

安装项目安装编译的项目文件并运行适配器运行所需的安装程序。要创建完整的安装项目,必须将项目输出添加到安装项目中,然后添加一个自定义操作来安装程序。

  1. 在解决方案资源管理器中,右键单击以选择解决方案,指向“添加”,然后单击“新建项目”

  2. “已安装的模板”窗格中展开“其他项目类型”,展开“安装和部署项目”,然后单击“Visual Studio Installer”

  3. 在中间窗格中,单击“安装项目”

  4. 为安装项目输入名称,然后单击“确定”

  5. 展开“检测到的依赖项”,双击“Microsoft .NET Framework”,然后在“属性”窗格中确保“版本”的值是“.NET Framework 3.5”

  6. 对于“检测到的依赖项”下方的以下每个程序集,右键单击该程序集,然后单击“排除”

    • Microsoft.Web.Administration.dll

    • Microsoft.WindowsAzure.ServiceRuntime.dll

    • msshrtmi.dll

    • mswacdmi.dll

现在,添加用来安装适配器程序文件的自定义操作。

  1. 在解决方案资源管理器中,右键单击安装项目,指向“视图”,然后单击“自定义操作”

  2. “自定义操作”编辑器中,右键单击“自定义操作”节点,然后选择“添加自定义操作”

  3. 双击列表框中的“应用程序文件夹”打开它,然后选择“添加输出”

  4. “添加项目输出组”窗口中选择“主输出”,选择“(活动)”作为“配置”,然后单击“确定”

因为 Visual Studio 在 .msi 文件中注入了 32 位程序集,而 Windows Azure 需要 64 位程序集,所以必须运行一个脚本来纠正不一致的程序集。可以使用一个 JavaScript 文件作为生成后事件来纠正程序集。

  1. 将以下代码保存到安装项目根文件夹中名为 FixMSI.js 的文件中:

    
    // workaround for "BadImageFormatException" issue - see http://msdn.microsoft.com/zh-CN/library/kz0ke5xt.aspx
    
    var msiOpenDatabaseModeTransact = 1;
    var msiViewModifyInsert = 1
    var msiViewModifyUpdate = 2
    var msiViewModifyAssign = 3
    var msiViewModifyReplace = 4
    var msiViewModifyDelete = 6
    
    var filespec = WScript.Arguments(0);
    var frameworkpath = WScript.Arguments(1);
    var installer = WScript.CreateObject("WindowsInstaller.Installer");
    var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
    
    WScript.Echo("Updating file '" + filespec + "' to use a 64-bit custom action...");
    
    Update64Bit();
    
    database.Commit();
    database = null;
    installer = null;
    
    function Update64Bit() {
        var sql;
        var view;
        var record;
        sql = "SELECT * FROM Binary WHERE `Name`='InstallUtil'";
        view = database.OpenView(sql);
        view.Execute();
        record = view.Fetch();
        if (record != null) {
            var dataCol = 2;
            record.SetStream(dataCol, frameworkpath + "\\InstallUtilLib.dll");
            view.Modify(msiViewModifyUpdate, record);
        }
        record = null;
        view.close();
        view = null;
    }
    
  2. 在解决方案资源管理器中,单击刚才创建的安装项目,然后在“属性”窗格中将以下命令添加到“PostBuildEvent”属性:

    
    cd $(ProjectDir) 
    CScript //NoLogo FixMSI.js "$(BuiltOutputPath)" "%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727"
    
  3. 在解决方案资源管理器中,右键单击安装项目,再单击“生成”

为使适配器与 Windows Azure 通信,必须在云服务模型中定义设置。

  1. 打开 VM 角色的 ServiceDefinition.csdef 文件。

  2. 将以下设置添加到 ConfigurationSettings 元素:

    
    <Setting name="AdapterName.BlobPath" />
    <Setting name="AdapterName.AccountName" />
    <Setting name="AdapterName.AccountKey" />
    

    其中,AdapterName 是适配器项目的名称。

  3. 向 LocalResources 元素添加以下设置:

    
    <LocalStorage name="Data" />
    

    有关可以在服务模型中使用的元素的更多信息,请参见 VirtualMachineRole Schema

  4. 保存该文件。

  1. 打开 VM 角色的 ServiceConfiguration.cscfg 文件。

  2. 向文件添加以下配置设置:

    
    <Setting name="AdapterName.BlobPath" value="http://StorageAccountName.blob.core.windows.net/ContainerName/{0}.vhd" />
    <Setting name="AdapterName.AccountName" value="StorageAccountName" />
    <Setting name="AdapterName.AccountKey" value="StorageAccountKey" />
    

    其中,AdapterName 是创建的适配器项目的名称。 StorageAccountName 是存储帐户的名称。StorageAccountKey 是存储帐户的主键。ContainerName 是先前创建的存储容器。有关服务模型配置的更多信息,请参见 Windows Azure Service Configuration Schema。有关如何定义和配置设置的更多信息,请参见创建和部署 VM 角色服务模型

  3. 保存该文件。

现在,可以将适配器安装到要上载到 Windows Azure 的 VHD 上。

在服务模型中创建代码以及定义、配置设置后,可以安装适配器和部署服务模型包。对于 VM 角色,适配器是在安装 Windows Azure 集成组件后在虚拟机上安装的。

为使适配器正常运行,必须启用几个操作系统功能。对要上载到 Windows Azure 的映像运行以下命令:

DISM /Online /Enable-Feature /FeatureName:NetFx3 /FeatureName:IIS-WebServerRole /FeatureName:IIS-WebServer /FeatureName:IIS-CommonHttpFeatures /FeatureName:IIS-HttpErrors /FeatureName:IIS-ApplicationDevelopment /FeatureName:IIS-HealthAndDiagnostics /FeatureName:IIS-HttpLogging /FeatureName:IIS-RequestMonitor /FeatureName:IIS-Security /FeatureName:IIS-RequestFiltering /FeatureName:IIS-Performance /FeatureName:IIS-WebServerManagementTools /FeatureName:IIS-StaticContent /FeatureName:IIS-DefaultDocument /FeatureName:IIS-DirectoryBrowsing /FeatureName:IIS-HttpCompressionStatic /FeatureName:IIS-ManagementConsole

要安装适配器,请浏览到包含 .msi 文件的文件夹,然后双击该文件。此文件位于先前创建安装项目时的 Debug 文件夹中。

另请参阅

社区附加资源

显示:
© 2014 Microsoft