Приветствую всех хабра-пользователей в этом скромненьком топике. Тут я попытаюсь «на пальцах» объяснить, что же такое Windows Azure и как писать под нее программы.
На написание статьи меня подтолкнула следующая ситуация: изучить-то Azure хочется, но в интернетах не удалось найти пошагового руководства с объяснением принципов происходящего, поэтому пришлось поднапрячь мозги и разобраться самому, о чем вам и расскажу.
И так, что же такое Windows Azure? Если совсем по-простому, то это операционная система. Такое определение нас не устроит, поэтому смотрим дальше: что она делает? А делает она то, что предоставляет вот такую вот облачную платформу: www.windowsazure.com. Внешний вид веб-интерфейса примерно такой:
Суть платформы в том, чтобы предоставить кое-какие услуги в обмен на кое-какую сумму денег. и так, что же это за услуги? Смотрим на скриншот и перечисляем:
Web-Sites — ну тут все просто, разворачиваем сайты (есть как шаблоны, так и возможность заливки своего скрипта)
Virtual Machines — виртуальные машины. В галерее есть серверные Windows (2k8R2 и 2012) и *nix системы. так же можно заливать свои образы vhd.
Mobile Services — бэкенд сервис для мобильных приложений. Больше нчиего о нем не знаю.
Cloud Services — самая лучшая возможность, а именно: хостинг веб-приложений + фоновые службы (по аналогии со службами Windows)
SQL Database — SQL сервер, управление через Web-интерфейс
Storage — хранилища. Можнохранить файлы, нереляционные таблицы данных и очереди (очередь — просто контейнер для какого-либо сообщения)
Все остальное, что есть на скриншоте — не описываю, так как не представляю для чего это.
Начинаем кодить
Процедуру создания ролей в Azure пропущу, в ней нет ничего сложного. Просто перечислю необходимое:
Cloud Service — одна штука
Storage — одна штука
Создавать крайне желательно в одном регионе.
После того, как все было создано, студия и SDK установлены и настроены, давайте подумаем, что нам можно написать. Что-то простенькое, но в то же время демонстрирующее работу с CloudService и с хранилищем очередей. После недолгих раздумий в голову пришла мысль просто отобразить IP-адрес и DNS-имя компьютера, на котором работает роль, при помощи Cloud Service и Queue. Просто? Просто.
Создание проекта в Visual Studio
Когда мы придумали, что будем делать, нужно это сделать. для начала запускаем Visual Studio и идем в File — New — Project — Cloud — Windows Azure Cloud Service. Даем любое имя и тыкаем «Ок». После того, как появится окошечко с тем, что должна уметь роль, добавьте ей только Worker Role (язык C#):
Жмем «Ок» и создаем проект. Когда проект создали, у нас имеется только Worker Role, т.е. то, что работает постоянно в фоновом режиме. Давайте добавим к ней веб-интерфейс:
ПКМ на решении — Add — New Project
Web — ASP.NET Empty Web Application
Как только был создан веб-интерфейс, нужно связать его с Azure-проектом, для чего делаем следующие шаги: ПКМ по папке Roles в проекте Azure — Add — Web Role Project in this Solution и выбираем наш веб-интерйейс:
Посмотрим что получилось в итоге:
Настройка WebRole, позволяет настраивать строки подключения, оконечные точки и прочее. Настройки хранятся в файлах 3 и 4
Настройка Worker Role, позволяет делать то же самое, что и пункт 1, только для фоновой службы. Настройки хранятся там же.
Хранит настройки облачного проекта для конфигурации Cloud (конфигурация используется при разворачивании проекта в облако)
Хранит настройки облачного проекта лдя конфигурации Local (используется при отладке на локальной машине в эмуляторе windows Azure)
Содержит определения всех частей, входящих в облачный проект (веб-интерфейс и фоновая служба).
Исходный код нашей службы
Настройка
Для настройки тыкаем два раза ЛКМ на WebApplication1 в папке Roles, и из списка Service Confoguration выбираем All Configurations. После этого жмем AddSetting, и даем имя строки DataConnectionString, после чего изменяем ее тип на ConnectionString:
Теперь жмем кнопку с тремя точками справа от нашей DataConnectionString, в открывшемся окне выберем Windows Azure Storage Emulator и нажимаем «Ок»:
Строка подключения должна измениться на UseDevelopmentStorage=true. Все вышеперечисленные действия выполняем и для WorkerRole1.
Теперь необходимо определить т.н. оконечные точки: точки, через которые будет осуществляться обмен данными в нашем проекте.
Для проекта WebApplication1
Для веб-интерфейса проекта необходима как минимум одна точка — для подключения к ней пользователя. Вторая точка будет использоваться для обмена данными с фоновой службой. Настраиваем точки в соответствии со скриншотом:
Немного пояснений: точка с именем Incoming — самая главная. Именно через нее пользователь будет заходить на веб-интерфейс. Собственно говоря имя точки может быть любым…
Вторая точка, UdpCheck, будет использоваться для приема данных от фоновой службы, ее тип Internal говорит о том, что она доступна только внутри датацентра, соответственно траффик по этой точке не будет учитываться биллинговой системой.
Для проекта WorkerRole1
Для роли делаем те же самые действия, только точка будет всего одна, Internal, т.к. служба не будет взаимодействовать с потусторонним миром:
Пишем код службы
Открываем файл WorkerRole.cs и меняем в нем код на нижеприведенный. Код коментирован, так что, думаю, проблемс пониманием сути происходящено возникнуть не должно.
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Xml.Linq;
namespace WorkerRole1
{
public class WorkerRole : RoleEntryPoint
{
CloudStorageAccount StorageAccount { get; set; } // аккаунт хранилища
CloudQueueClient Client { get; set; } // клиент очереди
CloudQueue Queue { get; set; } // вроде как сама очередь
CloudQueueMessage Message { get; set; } // сообщение, получаемое(отправляемое) из(в) очереди(ь)
public override void Run()
{
while (true)
{
Thread.Sleep(10000); // проверяем наличие сообщения в очереди раз в 10 секунд
// получаем сообщение из очереди
Message = Queue.GetMessage();
// если сообщение в очереди есть, то:
if (Message != null)
{
Trace.WriteLine(Message.AsString, "Message");
// получаем экземпляр роли, на которой работает интерфейс
// WebApplication1
RoleInstance myIntEP = RoleEnvironment.Roles["WebApplication1"].Instances[0];
// получаем ссылку на внутреннюю оконечную точку для экземпляра WebApplication1
// нужно для получения адреса, куда отправлять отклик
string addressEP = myIntEP.InstanceEndpoints["UdpCheck"].IPEndpoint.ToString();
// получаем IP-адрес и порт
string IP = addressEP.Split(':').First();
int PORT = Int32.Parse(addressEP.Split(':').Last());
// создаем экземпляр IPEndPoint
IPAddress targetIP = IPAddress.Parse(IP);
IPEndPoint ipEP = new IPEndPoint(targetIP, PORT);
// открываем сокет и отправляем данные в XML
using (UdpClient message = new UdpClient())
{
// формируем отклик
XDocument resp = new XDocument();
resp.Add(new XElement("info",
new XElement("ip", this.GetCurrentIP()),
new XElement("dns", this.GetCurrentDNSName())));
// кодируем отклик и отправляем его в веб-интерфейс
// на котором к этому времени уже прослушивается UDP порт
byte[] sendBytes = Encoding.ASCII.GetBytes(resp.ToString());
message.Send(sendBytes, sendBytes.Length, ipEP);
// удаляем сообщение из очереди
Queue.DeleteMessage(Message);
}
}
else
{
Thread.Sleep(1000);
}
}
}
public override bool OnStart()
{
// Максимальное кол-во подключений
ServicePointManager.DefaultConnectionLimit = 12;
// неведомое заклинание, но без него в последней версии SDK
// не удается получить строку подключения к хранилищу :(
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSettingPublisher) =>
{
var connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
configSettingPublisher(connectionString);
});
// получаем строку подключения к аккаунту хранилища
StorageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
// инициализация клиента очереди
Client = StorageAccount.CreateCloudQueueClient();
// создаем очередь
Queue = Client.GetQueueReference("queueaddress");
// создаем контейнер очереди
Queue.CreateIfNotExist();
return base.OnStart();
}
/// <summary>
/// Получение текущего IP-адреса машины
/// </summary>
/// <returns>IP-адрес</returns>
public string GetCurrentIP()
{
IPAddress[] ips = null;
ips = Dns.GetHostAddresses(Dns.GetHostName());
if (ips != null)
{
foreach (IPAddress i in ips)
{
if (i.AddressFamily == AddressFamily.InterNetwork)
return i.ToString();
}
}
return "о_О? Нету IP-адреса О_О.";
}
/// <summary>
/// Получение текущео имени машины
/// </summary>
/// <returns>DNS-имя</returns>
public string GetCurrentDNSName()
{
return Dns.GetHostName();
}
}
}
Пояснение: усыпение потока нужно для того, чтобы не перегружать бесполезной работой процессор. Так как процессорное время в Azure оплачивается, то это действие будет не лишним.
Метод OnStart() инициализирует службу, настраивая ее до того момента, как она начинает работать.
Метод Run() — сам рабочий процесс службы. Для служб в нем обычно находится бесконечный цикл, опрашивающий точки приема данных.
Создаем веб-интерфейс
В проект WebApplication1 добавляем пустую веб-форму (ПКМ по проекту — Add — Web Form) и даем ей имя Default.aspx, т.к. в Azure все-таки работает IIS, то имена страниц по-умолчанию регламентированы.
Меняем HTML разметку на ту, что указана ниже:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1.Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
Тестовый проект, демонстрирующий особенность работы Windows Azure.
<br />
Для примера, фоновая служба будет получать краткую информацию о сервере, на котором она
<br />
выполняется, а веб-интерфейс эту информацию отобразит.<br />
<br />
</div>
<asp:Button ID="GetInfo" runat="server" Text="Получить информацию" OnClick="GetInfo_Click" />
<p>
<asp:Label ID="Label1" runat="server" Text="IP адрес: "></asp:Label>
<asp:Label ID="IP_Label" runat="server"></asp:Label>
</p>
<asp:Label ID="Label2" runat="server" Text="DNS имя: "></asp:Label>
<asp:Label ID="DNS_Label" runat="server"></asp:Label>
</form>
</body>
</html>
Код формы (кнопка F7) меняем на этот:
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml.Linq;
namespace WebApplication1
{
public partial class Default : System.Web.UI.Page
{
CloudStorageAccount StorageAccount { get; set; } // аккаунт хранилища
CloudQueueClient Client { get; set; } // клиент очереди
CloudQueue Queue { get; set; } // вроде как сама очередь
CloudQueueMessage Message { get; set; } // сообщение, получаемое из очереди
protected void Page_Load(object sender, EventArgs e)
{
// неведомое заклинание, но без него в последней версии SDK
// не удается получить строку подключения к хранилищу :(
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSettingPublisher) =>
{
var connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
configSettingPublisher(connectionString);
});
// получаем строку подключения к аккаунту хранилища
StorageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
// инициализация клиента очереди
Client = StorageAccount.CreateCloudQueueClient();
// создаем очередь (имя очереди ТОЛЬКО МАЛЕНЬКИЕ ЛАТИНСКИЕ СИМВОЛЫ!)
Queue = Client.GetQueueReference("queueaddress");
// создаем контейнер очереди
Queue.CreateIfNotExist();
}
protected void GetInfo_Click(object sender, EventArgs e)
{
// создаем новое сообщение
Message = new CloudQueueMessage("Give me info! Nya-ha-ha-ha!");
// добавляем сообщение в очередь
Queue.AddMessage(Message);
// получаем ссылку на оконечную точку фоновой службы и адрес точки
RoleInstance myIntEP = RoleEnvironment.Roles["WorkerRole1"].Instances[0];
string addressEP = myIntEP.InstanceEndpoints["MyIntEndpoint"].IPEndpoint.ToString();
string IP = addressEP.Split(':').First();
int PORT = Int32.Parse(addressEP.Split(':').Last());
IPAddress targetIP = IPAddress.Parse(IP);
IPEndPoint ipEP = new IPEndPoint(targetIP, PORT);
// получаем порт для прослушки на веб-интерфейсе
RoleInstance myIntEP2 = RoleEnvironment.Roles["WebApplication1"].Instances[0];
string addressEP2 = myIntEP2.InstanceEndpoints["UdpCheck"].IPEndpoint.ToString();
int PORT2 = Int32.Parse(addressEP2.Split(':').Last());
string GetData = null;
// создаем сокет для прослушки
using (UdpClient socket = new UdpClient(PORT2))
{
byte[] recData = socket.Receive(ref ipEP);
GetData = Encoding.ASCII.GetString(recData);
XDocument resp = XDocument.Parse(GetData);
this.IP_Label.Text = resp.Root.Element("ip").Value;
this.DNS_Label.Text = resp.Root.Element("dns").Value;
}
}
}
}
Вот и все! Запускаем наш проект по F5, в открывшемся веб-интерфейсе тыкаем кнопочку «Получить информацию», ждем около 10 секунд и видим результат.
Публикация проекта в Azure
Самая интересная часть. Теперь сделаем наш проект доступным всем желающим. Надеюсь, вы уже создали аккаунт хранилища и аккаунт Cloud Services, поэтому перейдем сразу к настройке проекта.
Настройка достаточно простая, и заключается всего лишь в дополнительной настройки конфигурации (в нашем случае настраивается только DataConnectionString). Поехали.
WindowsAzure1 — Roles — два раза ЛКМ по WebApplication1 — Settings — из списка Cloud — кнопка с тремя точками:
В открывшемся окне переключаемся на Your Subscription и нажимаем на ссылку Download Publish Setting, если потребуется — вводим авторизационные данные для Azure, и сохраняем к себе на жесткий диск файлик. Далее тыкаем Import, выьираем файлик, указываем имя подписки, и название роли. Жмем «Ок».
Открываем WorkerRole1 и делаем то же самое для ее DataConnectionString, только в этот раз загружать профиль н нужно, т.к. настройки уже сохранены в проекте. Достаточно просто выбрать подпсику и имя роли.
Сохраняем изменения, жмем ПКМ на WindowsAzure1 и выбираем Publish:
В открывшемся окне выбираем нашу подписку:
Ставим все как на скрине ниже:
Смотрим суммарную информацию и нажимаем Publish:
Процесс загрузки идет довольно долго, запуск сервиса еще дольше, так что можно сходить попить кофе/покурить. По завершении процедуры развертывания наше приложение будет доступно по адресу, который был указан при создании Cloud Service. Спасибо за внимание.
azurelabr.cloudapp.net — приложение в Азуре
Исходники приложения не предоставляю, так как строки доступа и ключи являются уникальными для каждого пользователя.
Написано по материалам MSDN и книги «Платформа Windows Azure», авт. Теджасви Редкар и Тони Гвидичи, изд. «ДМК-пресс».