在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時,須注意以下幾個要點。
- 必須為ANSI檔案格式。
 - 必須是Idempotent,也就是說即使重複執行都不會出錯。
 - 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
	