管理你的Azure Roles - With Service Management API

Microsoft於日前釋出了Windows Azure Tools 1.2.......

 

管理你的Azure Roles - With Service Management API
 
 
/黃忠成
 
 
關於Azure Service Management API
 
     Azure已經試營運,據官方訊息,台灣地區於今年7月後將可加入試營運的行列,未來台灣地區的用戶,將可以透過試用或購買時數來建構自己的Azure服務。
 對於Microsoft來說,Azure是一個極耗成本的嘗試,除了建構雲端的硬體設備外,軟體的開發耗費之巨,也是可以想像的。
    除了改造Visual Studio來支援Azure開發外,Azure也是少數提供豐富的服務管理機制的雲端實作體,Azure提供了以REST為基礎的Service Management API
透過這組API,開發者可以程式化部署、Scaling OutScaling Up和監測Azure服務的狀態,這能做什麼?很簡單,你可以寫程式把自己的Azure Project自動DeploymentAzure上,
不用透過Azure Portal!也可以寫程式監測Azure上的Role狀態,諸如DeployingRunningReadyBusy皆可獲知,透過Diagnostics資訊,你甚至可以在Azure服務用量巔峰時,
自動進行Scaling Out,獲取雲端服務獨有的無限擴充優勢。
   想像一下,當你使用傳統ISPHosting服務時,在用量顛峰時,除了做璧上觀外,頂多也只能夠等待看服務人員能不能發個好心接電話,然後多加一台Server來分擔流量,
這種情況在Azure將不復存在,透過Service Management API/Diagnostics等技術,你可以自己寫一個程式,動態的、透過流量及用戶量來添加、或是遞減服務所使用的
Instance(機器/VM)數量。
 
 
關於Windows Azure SDK/Tools 1.2
 
     Microsoft於日前釋出了Windows Azure Tools 1.2,讀者們可由以下網址取得:
除了原本就有的Azure Project Template/Tools外,Microsoft這次更將Azure Monitor機制整合到了Server Explorer中,這意味著,
你將可以在Visual Studio 2010中直接以Server Explorer來觀測Azure服務的執行狀態。
1
Windows Azure Storage上按滑鼠右鍵,將可添加你想觀測的Storage Account
2
3
 
完成後,即可在Server Explorer上看到該Storage Account的狀態。
4
也可看到Table StorageTable的資料。
4-1
透過簡單的Filter機制,可以查詢Table Storage中的資料。
4-2
關於Filter語法,可參照以下網址:
也可以查看或以Blob Name來查詢Blob Storage內的資料。
4-3
Blob上按右鍵可以將其內容開啟或是儲存。
4-4
4-5是開啟Blob中圖形的截圖。
4-5
 
不過相較於我們常用的Azure Storage Explorer工具,這個工具僅提供觀測,並無法對Storage Account中的Blobs/Tables/Queue做變動
的動作,簡單說就是唯讀。
Azure Storage Explorer 可於下列網址取得:
http://azurestorageexplorer.codeplex.com/
Windows Azure Compute上按滑鼠右鍵,則可以添加對Azure Account的監測。
5
6
首次執行時,你必須在【Windows Azure Accounts】上按滑鼠右鍵來鍵入Azure Account資訊。
7
 
8
 
看到這個畫面時,如果玩過Azure Service Management API的人,一定會露出會心的一笑,以前要玩Azure Service Management API,得先弄出一個
Certificate file(憑證檔案)來,這通常是透過IIS 7或是其它工具來產生,Windows Azure Tools 1.2直接把這動作整合了,你只要選【Create】即可(幹得好...)
9
10
鍵入關於此certificate的易記名稱(你可自訂),按下OK後,Azure Tools即為你產生一個certificate
11
接著按下畫面上的【Copy the full path】連結,這個certificate檔案的路徑就會被複製到剪貼簿中,然後再按下下方的【Developer Portal
連結,開啟Azure Portal網站,點選正確的帳號後進入圖12的畫面。
12
接著點選Account頁籤,進入圖13的畫面。
13
OK,現在請點下畫面中的【Manage My API Certificates】連結,準備將certificate檔案上傳到Azure Portal上,這可以開啟存取Service Management API的權限。
14
certificate檔案在那?之前點【Copy the full path】時,這個檔案的完整路徑已經在剪貼簿中了,現在只要按下【瀏覽】按紐,於檔名部份按下貼上即可。
15
16
按下開啟舊檔後回到先前的畫面,再按下【Upload】按紐,即可將此certificate檔案上傳到Azure Portal上。
17
一切無誤的話,你將可於下方看到此certificate
18
最後請再點選上方的Account頁籤,將畫面中最下方的【Subscription ID】複製下來。
19
貼到Azure Tools的畫面下方。
20
最後給他取個名字吧。
21
按下OK後,即完成了此Azure Account的添加動作,現在你應可看到圖22的畫面。
22
當然,如果你的Azure Account中沒有建立任何Service的話,那麼這邊就是空的,你可嘗試到Azure Portal中建立新的Service,這裡就會有如圖22的畫面了。
於此可選擇是要顯示Staging|Production的狀態,此處我們選擇Production,按下OK後,可於Server Explorer中看到圖23畫面。
23
當然,這還是唯讀的,你無法於此刪除或建立某一Azure Hosting Service
 
 
Deployment
 
    Azure Tools 1.2Deployment(部署)機制也加入了,現在不用透過Azure Portal,只要在Azure Project上按下【Publish】後,
即可將Azure Project部署到Azure上了,請先建立一個專案。
24
眼尖的你應該已經發現,Azure已經支援.NET Framework 4了,按下OK後添加一個Web Role,然後於專案上按右鍵
進行Publish部署動作。
25
選擇使用那一個certificate
26
然後Publish Cloud Service會列出此certificate所繫結的Azure Account下所有的ServiceStorage Service供選擇。
27
28
選定後按下OK,即進入部署階段,你可於下方看到部署過程。
29
部署後,Azure Tools即啟動你的Azure Service,也就是說直接就Run了,不用再去Azure Portal上按【Run】。
30
OK,現在換我犯嘀咕了,為何我選了certificate後,Azure Tools可以直接列出所有的ServicesStorage account?即使我把Server Explorer
中關於Azure的東西都刪除,它還是可以列出?如果我沒猜錯的話,Azure Tools已經把Subscription IDcertificateAzure Account資訊存下來了,
所以它才能直接列出,否則不管是取得certificateList Hosted Services,都需要certificatesubscription id,沒這兩個資訊是不可能做到這些動作的。
或許你也會犯嘀咕,為何還要透過Azure Portal來上傳certificate fileService Management API不是也有新增certificate fileAPI嗎?直接來不就好了?
很簡單,Service Management API所有動作都建立在certificate上,沒有certificate前,一切免談。

 

註:呃,上面這段是我自己犯嘀咕......讀者們可以略過,哈。
 

 

註:當你DeploymentSlot已經有東西存在時,Azure Tools會詢問是要刪除先前的Deployment
31
看來目前尚未支援Upgrade
 
 
IntelliTrace
 
     Azure Project部署到雲端後,你就無法再對其做Debug動作,Azure Tools 1.2為了減輕除錯的負擔,提供了IntelliTrace機制,這個機制會將Azure Project的執行過程
記錄下來,並提供一個報表給開發者,在這個報表中將包含該Azure Project執行所在電腦、例外、及Thread List等資訊,對於開發者而言,最重要的就是例外資訊了。
     要使用IntelliTrace for Azure,你得先下載一個Hotfix
安裝後即可於Publish窗中勾選Enable IntelliTrace
32
 
完成部署後,即可於Server Explorer中取得IntelliTrace Log
33
點選後,Azure Tools即開始下載IntelliTrace Log
34
完成後,即顯示報表。
35
我們故意在程式中放入一個例外,看看IntelliTrace能不能在例外發生時記錄並顯示。
36
37
IntelliTrace Log資訊是存在你所提供的Storage Account中,當點選View IntelliTrace Log時,Azure Tools才由Storage Account內的Blob中取回資料。
     當然,IntelliTrace能做到的不只如此,透過IntelliTrace Log,我們可以對Azure上的應用程式除錯,是的!你沒聽錯,是除錯(Debug)!不過這個除錯動作是模擬的,
IntelliTrace會把應用程式執行期間的資訊存在於Log中,透過這個LogVisual Studio 2010可以將整個執行時期進行有限度的還原,讓開發者可以在程式未執行時,
進行模擬除錯。
   請開啟IntelliTrace Log Summary,找到發生例外的地方,按下下方的Start Debuging按紐。
37-1
接著會來到圖37-2的畫面。
37-2
唔,有點熟悉是吧?你可以用F10F11來進行單步除錯,但請注意,這是有限度的模擬而已。
看起來好像很有用的機制是吧?讓我們再用另一個例子,透過Exception Log,我們發現了有某段程式碼丟出了IO Exception
37-3
單看這個Exception,實在很難知道問題發生在那!沒關係,透過IntelliTrace,只要於此按下Start Debugging,就會到達例外的發生地。
37-4
接著你可以按下【Ctrl+Shift+F11】、或是按下左方的向上按紐,會發現到除錯點往後退。
37-5
當退到函式的最開端時,你可以像除錯一般應用程式般,以F10F11來進行單步,有趣的是,當使用F11時,Visual Studio 2010允許你Step Into至呼叫函式。
37-6
於圖37-6處按下F11,會跳到InitializeBlobStorage函式內部,繼續進行除錯。
37-7
OK,這樣看來,IntelliTrace確實可以減輕我們除錯Azure上應用程式的負擔。
 
 
 
Deployment Package via Service Management API
 
    如果你曾經玩過Azure Service Management API的話,那麼Azure Tools 1.2所提供的功能中,除了IntelliTrace外,其實都不會對你造成震憾,
因為這些都是Service Management API本來就提供的東西。
註:Azure Service Management API說明可由下列網址取得,幾乎所有的Method都可以在Microsoft.Samples.WindowsAzure.ServiceManagement.dll中找到對應。
 Azure Tools 1.2之前,Service Management API僅提供REST API介面,並未正式提供Managed Code版本(OK,我說非正式,Managed Code
 Azure Tools 1.2中,這個非正式的Service Management API Managed Code版本,出現在了Azure Tools 1.2的目錄中。
38
這一來,不用去Codeplex下載,我們直接就可以用這個Managed CodeService Management API了。
現在開始建立第一個Service Management API的應用例子,我們一次到位,直接做one-click部署的機制,首先請建立一個Console Project
接著加入Microsoft.Samples.WindowsAzure.ServiceManagement.dllMicrosoft.WindowsAzure.ServiceRuntime.dll
Microsoft.WindowsAzure.StorageClient.dllSystem.ServiceModelSystem.Runtime.Serialization.dllReference
39
記得Target Framework 要選.NET Framework 4
40
要使用Service Management API,我們得先有一個certificate才行,Azure Tools 1.2其實已經幫我們產生了一個,拿來用即可,
然後透過Service Management API Managed Library來部署。
程式 1
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure;
using Microsoft.Samples.WindowsAzure.ServiceManagement;
using System.ServiceModel;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Principal;
 
namespace ConsoleApplication1
{
    class Program
    {
        private const string subscriberID = "<your subscriptioin id>";
 
        private static void InitializeBlobStorage()
        {
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
                RoleEnvironment.Changed += (anotherSender, arg) =>
                {
                    if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
                        .Any((change) => (change.ConfigurationSettingName == configName)))
                    {
                        if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                        {
                            RoleEnvironment.RequestRecycle();
                        }
                    }
                };
            });
        }
 
        private static CloudStorageAccount InitizlieBlob()
        {
            CloudStorageAccount storageAccount = new CloudStorageAccount(
         new StorageCredentialsAccountAndKey("code6421storage1", "<your storage access key"), true);
            CloudBlobClient client = storageAccount.CreateCloudBlobClient();
 
            // Get and create the container
            CloudBlobContainer container = client.GetContainerReference("files");
            container.CreateIfNotExist();
 
            // Setup the permissions on the container to be public
            var permissions = new BlobContainerPermissions();
            permissions.PublicAccess = BlobContainerPublicAccessType.Container;
            container.SetPermissions(permissions);
            return storageAccount;
        }
 
        private static void UploadDeploymentPackageToBlob(IServiceManagement serviceManagement)
        {
            InitializeBlobStorage();
            InitizlieBlob();
            CloudStorageAccount storageAccount = InitizlieBlob();
            CloudBlobClient client = storageAccount.CreateCloudBlobClient();
 
            // Get and create the container
            CloudBlobContainer container = client.GetContainerReference("files");
            CloudBlob pkgBlob = container.GetBlobReference("DemoDiagnostics.cspkg");
            client.WriteBlockSizeInBytes = 512 * 1024;
            pkgBlob.UploadFile(@"c:\temp1\DemoDiagnostics.cspkg");
 
            string reqId = null;
            using (OperationContextScope scope = new OperationContextScope(
                               serviceManagement as IClientChannel))
            {
                serviceManagement.ListHostedServices(subscriberID);
                serviceManagement.CreateOrUpdateDeployment(subscriberID, "code6421", "production",
                   new CreateDeploymentInput()
                   {
                       PackageUrl = pkgBlob.Uri,
                       Label = Convert.ToBase64String(Encoding.UTF8.GetBytes("Deployment 15")),
                       Configuration = Convert.ToBase64String(Encoding.UTF8.GetBytes(
                          String.Join("", File.ReadAllLines(@"c:\temp1\ServiceConfiguration.cscfg")))),
                       Name = "TestRSSWeb" //the name must be Service Label
                   });
                object propertyValue;
                if (OperationContext.Current.IncomingMessageProperties.TryGetValue(
                                "httpResponse", out propertyValue))
                {
                    System.ServiceModel.Channels.HttpResponseMessageProperty response =
                        (System.ServiceModel.Channels.HttpResponseMessageProperty)propertyValue;
                    reqId = response.Headers["x-ms-request-id"];
                }
            }
 
            while (true)
            {
                string status = serviceManagement.GetOperationStatus(subscriberID, reqId).Status;
                Console.WriteLine("Processing...:" + status);
                if (status == "Succeeded")
                    break;
                System.Threading.Thread.Sleep(10000);
            }
 
            RunDelopment(serviceManagement);
        }
 
        private static void RunDelopment(IServiceManagement serviceManagement)
        {
            string reqId = string.Empty;
            object propertyValue;
            using (OperationContextScope scope = new OperationContextScope(
                         serviceManagement as IClientChannel))
            {               
                serviceManagement.UpdateDeploymentStatus(subscriberID, "code6421", "TestRSSWeb",
                                                     new UpdateDeploymentStatusInput() {
                                                      Status = "Running" });
                if (OperationContext.Current.IncomingMessageProperties.TryGetValue(
                           "httpResponse", out propertyValue))
                {
                    System.ServiceModel.Channels.HttpResponseMessageProperty response =
                      (System.ServiceModel.Channels.HttpResponseMessageProperty)propertyValue;
                    reqId = response.Headers["x-ms-request-id"];
                }
            }
 
            while (true)
            {
                string status = serviceManagement.GetOperationStatus(subscriberID, reqId).Status;
                Console.WriteLine("Running...:" + status);
                if (status == "Succeeded")
                    break;
                System.Threading.Thread.Sleep(10000);
            }
 
            while (true)
            {
                Deployment deps = serviceManagement.GetDeploymentBySlot(subscriberID,
                             "code6421", "production");
                bool isRunning = true;
                foreach (var role in deps.RoleInstanceList)
                {
                    Console.WriteLine(string.Format("Role {0} : {1}",
                                      role.RoleName, role.InstanceStatus));
                    if (!role.InstanceStatus.Equals("Ready"))
                        isRunning = false;
                }
                if (isRunning)
                    break;
                System.Threading.Thread.Sleep(10000);
            }
        }
 
        private static X509Certificate2 GetX509Certificate2(String subjectName)
        {
            X509Certificate2 x509Certificate2 = null;
            X509Store store = new X509Store("My", StoreLocation.CurrentUser);
            try
            {
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection x509Certificate2Collection =
                    store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false);
                x509Certificate2 = x509Certificate2Collection[0];
            }
            finally
            {
                store.Close();
            }
            return x509Certificate2;
        }
 
        static void Main(string[] args)
        {
            var serviceManagment =
               ServiceManagementHelper.CreateServiceManagementChannel("WindowsAzureEndPoint",
               GetX509Certificate2("Windows Azure Tools"));
 
            UploadDeploymentPackageToBlob(serviceManagment);
 
            Console.Read();
        }
    }
}
執行前還需修改app.config為下列版本:
<?xmlversion="1.0"?>
<configuration>
 <system.serviceModel>
    <bindings>
      <webHttpBinding>
        <bindingname="WindowsAzureServiceManagement_WebHttpBinding"closeTimeout="00:01:00"openTimeout="00:01:00"receiveTimeout="00:10:00"sendTimeout="00:01:00">
          <readerQuotasmaxStringContentLength="1048576"maxBytesPerRead="131072"/>
          <securitymode="Transport">
            <transportclientCredentialType="Certificate"/>
          </security>
        </binding>
      </webHttpBinding>
    </bindings>
    <client>
      <endpointname="WindowsAzureEndPoint"address="https://management.core.windows.net"binding="webHttpBinding"bindingConfiguration="WindowsAzureServiceManagement_WebHttpBinding"contract="Microsoft.Samples.WindowsAzure.ServiceManagement.IServiceManagement"/>
    </client>
 </system.serviceModel>
</configuration>
編譯後即可執行。
41
這個程式會先將Azure Package 上傳至Blob,然後透過Service Management API來部署,部署的動作是非同步的,所以此處我們使用Tracking API
來查詢部署情況,在部署完成後,此程式會再次透過Service Management API來啟動此Service,接著再透過Service Management API來查詢啟動的狀態。
這個程式告訴了我們Azure Tools 1.2玩的把戲,其實背後大多是Service Management API的功勞。
 
 
Dynamic Changing Role Instances(Scaling Out)
 
    選用雲端技術中一個很重要的理由,那就是可以無限的延展,一台機器不夠用,可以快速延展至兩台,兩台不夠用,可以快速的延展至三台或四台。
 Azure支援兩種延展(Scaling)策略,一是Scaling Up,意思是透過升級機器上的CPU數量來進行延展,二是Scaling Out,意指啟用其它同CPU數的機器,
讓原本只有一台機器的服務,晉升為使用兩台機器。
    Scaling Up需要重新部署應用程式至Azure,所以使用上較為麻煩,Scaling Out則不需要,只要透過Service Management API即可動態的增減某個Role
的機器數量。
Scaling Out需要原來的Service Configuration檔案,此例將原來的Service Configuration放一份在temp1目錄下。

 

Program.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Linq;
using Microsoft.Samples.WindowsAzure.ServiceManagement;
using System.ServiceModel;
using System.Security.Cryptography.X509Certificates;
 
namespace ScalingOutSamples
{
    public partial class Form1 : Form
    {
        internal static string _subscriberID = "<your subscription id>;
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void ChangeInstaceCount(IServiceManagement serviceManagement)
        {
            string reqId = string.Empty;
            object propertyValue;
            using (OperationContextScope scope =
                            new OperationContextScope(serviceManagement as IClientChannel))
            {
                XDocument doc = XDocument.Load(@"c:\temp1\ServiceConfiguration.cscfg");
                var result = (from s1 in doc.Descendants(XName.Get("Instances",                                   "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"))
                              where
                                  s1.Parent.Attribute("name").Value == "WebRole1"
                              select s1).FirstOrDefault();
                result.Attribute("count").Value = textBox1.Text;
                string finalContent = doc.ToString();
                serviceManagement.ChangeConfigurationBySlot(Form1._subscriberID,
                                         "code6421", "Production",
                                         new ChangeConfigurationInput()
                                         {
                                             Configuration =
                                   Convert.ToBase64String(Encoding.UTF8.GetBytes(finalContent))
                                         });
                if (OperationContext.Current.IncomingMessageProperties.TryGetValue("httpResponse",
                            out propertyValue))
                {
                    System.ServiceModel.Channels.HttpResponseMessageProperty response =
                     (System.ServiceModel.Channels.HttpResponseMessageProperty)propertyValue;
                    reqId = response.Headers["x-ms-request-id"];
                }
            }
        }
 
        private static X509Certificate2 GetX509Certificate2(String subjectName)
        {
            X509Certificate2 x509Certificate2 = null;
            X509Store store = new X509Store("My", StoreLocation.CurrentUser);
            try
            {
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection x509Certificate2Collection =
                    store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false);
                x509Certificate2 = x509Certificate2Collection[0];
            }
            finally
            {
                store.Close();
            }
            return x509Certificate2;
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            var serviceManagment = ServiceManagementHelper.CreateServiceManagementChannel("WindowsAzureEndPoint",
               GetX509Certificate2("Windows Azure Tools"));
            ChangeInstaceCount(serviceManagment);
        }
    }
}
 

 

App.config
<?xmlversion="1.0"?>
<configuration>
 <system.serviceModel>
    <bindings>
      <webHttpBinding>
        <bindingname="WindowsAzureServiceManagement_WebHttpBinding"closeTimeout="00:01:00"openTimeout="00:01:00"receiveTimeout="00:10:00"sendTimeout="00:01:00" >
          <readerQuotasmaxStringContentLength="1048576"maxBytesPerRead="131072"/>
          <securitymode="Transport">
            <transportclientCredentialType="Certificate"/>
          </security>
        </binding>
      </webHttpBinding>
    </bindings>
    <client>
      <endpointname="WindowsAzureEndPoint"address="https://management.core.windows.net"binding="webHttpBinding"bindingConfiguration="WindowsAzureServiceManagement_WebHttpBinding"contract="Microsoft.Samples.WindowsAzure.ServiceManagement.IServiceManagement"/>
    </client>
 </system.serviceModel>
</configuration>
此例為Windows Form專案,ReferenceMicrosoft.Samples.WindowsAzure.ServiceManagement.dllSystem.ServiceMode
System.RunTime.Serialization
當改變了TextBox上的數字並按下OK後,你會發現該Service正在啟動其它的Instances,而原來的Instance仍處於可用狀態。
 
42
43
 當我們可以動態的改變Role所使用的機器數量時,Scaling Out便成了Azure的利器,你可以選擇使用日期(例如周一、五使用2台,六日使用4)
或是透過Diagnostics來監測CPU用量來動態調整使用的機器數量。