The Windows Azure Role Startup Task



在Windows Azure所提供的Cloud Services服務裡主要分成兩種Role,一是許多人都熟知的Web Role,用於開發可Host於IIS的Web應用程式,例如ASP.NET及PHP等類。另一類是Worker Role,

用於開發不可Host於IIS的應用程式,例如自行開發的TCP Server或是Java/Node.js等類。

/黃忠成

 

 

Windows Azure Role Life Cycle

 

  在Windows Azure所提供的Cloud Services服務裡主要分成兩種Role,一是許多人都熟知的Web Role,用於開發可Host於IIS的Web應用程式,例如ASP.NET及PHP等類。另一類是Worker Role,

用於開發不可Host於IIS的應用程式,例如自行開發的TCP Server或是Java/Node.js等類。

  不管是Web Role或是 Worker Role,他們的Life Cycle大致上是一致的,如圖1。

圖1

在相關的Packages上載至Windows Azure Cloud Services後,首先Windows Azure會配置相關的硬體及VM,在這些配置完成後開始執行Packages中的Startup Tasks,當使用Web Role時,接著會呼叫IISConfigurator.exe

來配置IIS環境(Worker Role時這段會被省略),在此同時,Role中的OnStart事件會被觸發,緊接著進入執行狀態(Run),這時會有兩種可能,一是執行動作圓滿完成進入Ready狀態,二是執行時期發生錯誤,進入Recycle狀態,

最後不管發生什麼事,Role的OnStop事件都會被觸發。

整個生命週期中牽扯的技術不少,本文先將主軸放在Startup Tasks這個區塊。

 

 

Startup Task in Web Role

 

  在Web Role中,Startup Task的角色是提供開發者在服務啟動前執行配置動作,例如產生本地端快取來加快網站響應速度,亦或是註冊應用程式中會使用到的COM物件。

  當你使用Visual Studio來建立一個ASP.NET Web Role方案時,預設並不會產生這個Startup Task,這是因為ASP.NET Web Role通常只需要IISConfiguator.exe就可以正常運作,如果你想要產生這個Startup Task,

可以在Web Role專案下面建立一個startup.cmd檔案。

圖2

並加入要註冊的MyCOMLibrary1.dll,接著修改.csdef檔案。

 

ServiceDefinition.csdef


<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition name="WindowsAzure6" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2013-03.2.0">

  <WebRole name="WebRole1" vmsize="Small">

    <Startup>

      <Task commandLine="Startup.cmd" executionContext="elevated" taskType="simple">

      </Task>

    </Startup>

    <Sites>

      <Site name="Web">

        <Bindings>

          <Binding name="Endpoint1" endpointName="Endpoint1" />

        </Bindings>

      </Site>

    </Sites>

    <Endpoints>

      <InputEndpoint name="Endpoint1" protocol="http" port="80" />

    </Endpoints>

    <Imports>

      <Import moduleName="Diagnostics" />

    </Imports>

  </WebRole>

</ServiceDefinition>

 

 

需注意一點,目前Startup.cmd只允許ANSI檔案格式,而我們透過Visual Studio建立的會是UTF格式,所以必須透過例如Notepad.exe來修改這個檔案格式。

圖3

完成後就可以在應用程式中使用這個COM物件了。

Default.aspx.cs


using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;


namespace WebRole1

{

    public partial class _Default : Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            Type ts = Type.GetTypeFromProgID("MyCOMLibrary1.SimpleObject");

            dynamic o = Activator.CreateInstance(ts);

            Response.Write(o.Hello());

            Response.End();

        }

    }

}

 

 

在使用Startup Tasks時,須注意以下幾個要點。

  1. 必須為ANSI檔案格式。
  2. 必須是Idempotent,也就是說即使重複執行都不會出錯。
  3. Startup Tasks可以定義ㄧ個以上的.cmd或是.exe等可執行檔案,會依序執行。

 

Only run in not Emulator

 

  在特定情況下,你可能希望某個Startup Task僅執行於真實的Windows Azure環境下,在Emulator狀態下是略過的,這可以透過以下的方式達成。

 

ServiceDefinition.csdef


<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition name="WindowsAzure6" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2013-03.2.0">

  <WebRole name="WebRole1" vmsize="Small">

    <Startup>

      <Task commandLine="startup.cmd" executionContext="elevated" taskType="simple">

        <Environment>

          <Variable name="EMULATED">

            <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />

          </Variable>

        </Environment>

      </Task>

    </Startup>

    <Sites>

      <Site name="Web">

        <Bindings>

          <Binding name="Endpoint1" endpointName="Endpoint1" />

        </Bindings>

      </Site>

    </Sites>

    <Endpoints>

      <InputEndpoint name="Endpoint1" protocol="http" port="80" />

    </Endpoints>

    <Imports>

      <Import moduleName="Diagnostics" />

    </Imports>

  </WebRole>

</ServiceDefinition>

 

Startup.cmd

if "%EMULATED%"=="true" goto :EOF

regsvr32  "%roleroot%\approot\bin\MyCOMLibrary1.dll"

 

 

 

 

executionContext and TaskType

 

  executionContext有兩個可能的值,elevated指的是要求到系統管理員權限,limit則指的是只要求使用者權限。

  TaskType則有三種可能的值,simple是預設值,意思是Windows Azure會等待該Task做完才繼續接下來的動作。

  Background則是告訴Windows Azure可以不等待這個Task完成就執行下一步動作,也就是非同步的意思。

  Foreground與Background都是非同步執行,但是Foreground Task必須要完成(或是失敗跳出)才能進行Role的Restart動作。

一般常用的是simple及Background,Foreground通常是用於執行一定要完成的關鍵任務,由於一旦指定為Foreground後,當Windows Azure發生錯誤需要回收此Role時,

可能會因為Foreground Task尚未執行完畢而無法回收,因此Foreground Task必須負責不管發生任何錯誤都要正常結束。

 

Enable ASP Support

 

  雖然Windows Azure主要是支援ASP.NET、PHP、Java、Node.Js等較新且主流的Web應用程式,但有時也有將傳統的ASP應用程式搬上去的需求,不過預設的IIS 7.5並沒有啟用ASP服務,

這時便可以透過Startup Tasks來完成這個需求。

  通常,當你想要把舊有的ASP應用程式搬上Windows Azure時,通常是在沒有Visual Studio環境的狀態下,這時可以下載Windows Azure Powershell來以命令列方式建立Web Role及發布。

https://www.windowsazure.com/en-us/downloads/

圖4是透過Windows Azure Powershell建立Service Project及加入一個Web Role的命令。

完成後修改.csdef。


<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="azurewithasp" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

  <WebRole name="WebRole1" vmsize="ExtraSmall">

    <Imports>

      <Import moduleName="RemoteForwarder" />

      <Import moduleName="RemoteAccess" />

    </Imports>

    <Startup>

      <Task commandLine="../startup.cmd" executionContext="elevated" />

    </Startup>

    <Endpoints>

      <InputEndpoint name="Endpoint1" protocol="http" port="80" />

    </Endpoints>

    <Sites>

      <Site name="Web" physicalDirectory="D:\code\azurewithasp2\WebRole1">

        <Bindings>

          <Binding name="Endpoint1" endpointName="Endpoint1" />

        </Bindings>

      </Site>

    </Sites>

  </WebRole>

</ServiceDefinition>

 

注意Site name Element中的physicalDirectory,這裡必須是該WebRole真實的路徑。

接著修改startup.cmd。

 

start /w pkgmgr /iu:IIS-ASP

exit /b 0

 

接著在WebRole1目錄下面放入你的asp檔案然後執行以下命令來打包整個Service。

 

cspack .\ServiceDefinition.csdef /out:cloud_package.cspkg

 

最後透過Azure Powershell上載到Windows Azure即可(注意,你必須在Service的目錄下,也就是azurewithasp2目錄下執行下面這段指令)。

 

Publish-AzureServiceProject

 

 

 

Startup Task in Worker Role

 

  相較於Web Role,Worker Role中Startup Tasks的用途就明確許多,目的就是把某個程式搬到Windows Azure上面執行,透過Visual Studio可以快速的建立Worker Role,並令其執行.NET應用程式,本文嘗試以另一個角度來看,

也就是透過Windows Azure Powershell建立Worker Role,然後把Java Application搬上去執行。

  那該怎麼做呢?先讓我們思考一下,執行Java Application需要什麼?JDK或是JRE對吧,那麼只要把JDK/JRE連同Java Application一起部屬到Windows Azure Worker Role上,然後在Startup Tasks中透過java –jar xxx.jar來執行就可以了。

  了解大概的流程後,我們先用Eclipse建立一個簡單的Java應用程式,目的是做一個簡單的HTTP Server。

 


package com.gis;


import java.io.IOException;

import java.io.OutputStream;

import java.net.InetSocketAddress;


import com.sun.net.httpserver.HttpExchange;

import com.sun.net.httpserver.HttpHandler;

import com.sun.net.httpserver.HttpServer;


public class MainClass {


    public static void main(String[] args) throws Exception {

        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);

        server.createContext("/test", new MyHandler());

        server.setExecutor(null); // creates a default executor

        server.start();

    }


    static class MyHandler implements HttpHandler {

        public void handle(HttpExchange t) throws IOException {

            String response = "This is the response";

            t.sendResponseHeaders(200, response.length());

            OutputStream os = t.getResponseBody();

            os.write(response.getBytes());

            os.close();

        }

    }

}

 

這個程式必須使用JDK 1.6來編譯及執行(不能用JDK 1.7哦),然後透過Eclipse的機制打包成一個.jar。

接著要準備上傳的JDK環境,由於JDK目前需要登入Oracle網站才能下載,所以通常我們都是先下載裝在本機上,然後將整個JDK目錄打包成一個.ZIP檔或是直接放到Worker Role目錄下,本例將採用第二種,也就是直接放到Worker Role目錄下。

啟動Windows Powershell,鍵入以下指令來建立新的Azure Service Project及建立Worker Role。

圖5

把JDK1.6的目錄及simpleHttpServer.jar整個放到javaHttpServer\workerrole1目錄下。

圖6

圖7

接著修改worker.cmd(透過Azure Powershell產生的.csdef會把進入點設為worker.cmd)。

 

set JAVA_HOME=%roleroot%\approot\JDK1.6

set PATH=%PATH%;%JAVA_HOME%\bin

java -jar simpleHttpServer.jar

 

接著修改.csdef。

 


<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="javaHttpServer" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

  <WorkerRole name="WorkerRole1">

    <Imports>

      <Import moduleName="RemoteForwarder" />

      <Import moduleName="RemoteAccess" />

    </Imports>

    <Startup>

      <Task commandLine="startup.cmd" executionContext="elevated" />

    </Startup>

    <Endpoints>

      <InputEndpoint name="http" protocol="tcp" port="80" localPort="8000" />

    </Endpoints>

    <Runtime>

      <Environment>

        <Variable name="PORT">

          <RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='HttpIn']/@port" />

        </Variable>

        <Variable name="EMULATED">

          <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />

        </Variable>

      </Environment>

      <EntryPoint>

        <ProgramEntryPoint commandLine="worker.cmd" setReadyOnProcessStart="true" />

      </EntryPoint>

    </Runtime>

  </WorkerRole>

</ServiceDefinition>

 

注意兩個地方,一個是EntryProint部分是執行worker.cmd,而InputEndpoint部分是把localProt對應到public的port 80。

完成後透過Emulator來測試。

圖8

圖9

最後只要透過Publish-AzureServiceProject來部屬到真實的Windows Azure環境即可。

圖10