在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