Pull to refresh

Хостим облачную службу Azure на обычных vds

Reading time9 min
Views5.8K
Пишу веб-проекты в visual studio, и с каждой новой версией студии она как будто затачивается для работы с Windows Azure. Мне нравится Азура, хотя я пользуюсь только небольшим набором возможностей. Основное для меня — это Облачная служба. Облачная служба отлично подходит для разворачивания распределенного сервера.

Итак, я создаю облачную службу, в которую добавляю одну веб-роль (виртуальная машина с IIS), один воркер (виртуальная машина без IIS) и общую библиотеку классов. После публикации моим ролям присваивается ip-адрес и разные порты. То есть сразу есть tcp сеть, ручные настройки минимальны и могут делаться в самой студии. Можно, к примеру, сделать общедоступную точку доступа для воркера, но мне это не нужно. Мой воркер будет скрыт от внешних глаз и на нём будет висеть wcf-сервер, а общаться мои роли будут по быстрой локальной сети.

Общие классы я выношу в библиотеку (которую подключаю ко всем ролям), к примеру, интерфейс и канал связи:

    [ServiceContract]
    public interface IwcfChat
    {
        [OperationContract]
        string SendMessage(string userId, string userName, string text);

        [OperationContract]
        string GetMessages(string userId, string userName);
    }

//================

    public sealed class wcfChat:IDisposable
    {
        IwcfChat _channel;
        ChannelFactory<IwcfChat> factory = null;
        public wcfChat()
        {
            NetTcpBinding b = new NetTcpBinding();
            b.Security.Mode = SecurityMode.None;
            b.Security.Message.ClientCredentialType = MessageCredentialType.None;

            #if(DEBUG)
                EndpointAddress address = new EndpointAddress("net.tcp://127.255.0.1:9003/wcfChat");
            #else
                EndpointAddress address = new EndpointAddress("net.tcp://"+spr.wcfIP+":9003/wcfChat"); 
            #endif

                factory = new ChannelFactory<IwcfChat>(b, address);
            factory.Faulted += OnChannelFaulted;
            factory.Open();
        }

        public IwcfChat channel
        {
            get
            {
                if (factory != null && factory.State == CommunicationState.Opened)
                {
                    if(_channel==null) _channel = factory.CreateChannel();
                    return _channel;
                }

                return null;
            }
        }

        void OnChannelFaulted(object sender, EventArgs e)
        {
            factory.Abort();
        }

        public void Dispose()
        {
            factory.Close();
        }
    }


Методы в веб-роли вызываю так:

 using (var chat = new wcfChat())
 {
  res = chat.channel.SendMessage(id, name, text);
 }


Соответственно, в воркере у меня реализация методов из интерфейса (SendMessage, GetMessage), не буду их расписывать, а так же в воркере при старте выполняется код, который делает его хостом для wcf:


 public override bool OnStart()
 {
  // Задайте максимальное число одновременных подключений 
  ServicePointManager.DefaultConnectionLimit = 12;

  // Create the host
  ServiceHost host = new ServiceHost(typeof(wcfChat));

  // Read config parameters
  string hostName = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["port"].IPEndpoint.Address.ToString();

  // Create Metadata
  ServiceMetadataBehavior metadatabehavior = new ServiceMetadataBehavior();
  host.Description.Behaviors.Add(metadatabehavior);

  Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
  string mexendpointurl = string.Format("net.tcp://{0}:{1}/wcfChatMetadata", hostName, 8003);
  host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexendpointurl, new Uri(mexendpointurl));

  // Create end point
  string endpointurl = string.Format("net.tcp://{0}:{1}/wcfChat", hostName, 9003);
  host.AddServiceEndpoint(typeof(IwcfChat), new NetTcpBinding(SecurityMode.None), endpointurl, new Uri(endpointurl));

  // Open the host
  host.Open();

  // Trace output
  Trace.WriteLine("WCF Listening At: " + endpointurl);
  Trace.WriteLine("WCF MetaData Listening At: " + mexendpointurl);


  return base.OnStart();
 }


Тут я показал методы типа string, но тип возвращаемого значения может быть абсолютно любой, к примеру, сложный класс, который описан в моей общей библиотеке. Всё, никаких других настроек в вебконфигах, никаких упаковок в xml или SOAP у меня нет. Нет автоматически генерируемых файлов контрактов. Когда я пытался это сделать, в интернетах при поиске информации про wcf всегда всплывает настройка под http со всеми этими упаковками/распаковками. Да, wcf технология изначально придумана для связи разнородных систем, где необходимо сериализовать и передавать как строку. Но если у нас один язык программирования, а клиент и сервер wcf — это всё наша разработка, то не нужно городить огород, всё прекрасно работает.

Таким образом у меня получается распределенный сервер, для затратных операций я делаю отдельный воркер, тут, к примеру, показан чат. Все функции чата вынесены в отдельную виртуальную машину и я могу увеличивать производительность как самого сайта (веб-роли), так, отдельно, и чата.

Такая архитектура мне кажется правильной и может выдержать более сильную нагрузку, чем монолитный проект. Так же всё это разрабатывается и отлаживается как один проект в студии, то есть при вызове метода по wcf я могу в отладке пройти глубже в код, без сообщений типа «внутри отлаживать я не могу, не знаю что там происходит, вот такой вернулся результат».

С Азуровскими инструментами это делается всё легко и просто, я запускаю весь проект одной кнопкой и отлаживаюсь по нему как по одному проекту. Я помню когда-то давно, на одном из проектов была похожая архитектура, но я был молод, и мне там было очень сложно: какие-то фасады, и, если параметры у метода менялись, я изменения в четырёх местах вносил.

Мне нужно нечто подобное сделать на обычных серверах. Не на своих физических серверах, а на vds/vps виртуалках у какого-нибудь обязательно российского хостера. Я нашел забавную технологию у одного из хостеров (1gb.ru), называется ресурс Windows Azure Pack / COSN. По описанию это урезанная версия Азуры и всё должно было бы «взлететь». Конечно же, у них есть пробный период, я посмотрел. В браузере управление как-будто из старой версии портала Азуры (если кто помнит).
image
Из возможностей есть только создание виртуальных машин, объединение их в сеть (вручную), есть service bus — и всё. Так же нет возможности загрузить эту подписку в студию, а в этом и есть многое из удобств. Плюс нет возможности создания облачной службы. В описании сказано, что это «частное облако» будет дорабатываться и дополняться возможностями с оригинальной Азуры, но поддержка сказала, что всё уже давно заморожено, и вообще, это несовместимые технологии («этот Azure Pack совершенно по смыслу не совместим с настоящим Azure. Там что есть, то есть, и есть там маловато. Идеи там общие, но реализация разная совершенно, API разные и так далее.» (с)).
Если кратко — то не подходит.

Ищем дальше. Я пробегаюсь по виндовым хостерам, ищу возможность аренды обычных vps/vds но с возможностью организовать локальную сеть между ними. У многих попросту отсутствует такая возможность. Да, можно арендовать виртуалку, поднять на ней VPN и другой виртуалкой зацепиться к этой сети. Но такая «локальная» сеть натягивается поверх http, поэтому заведомо медленнее. Я уж было подумал, что у меня ничего не получится, но у хостера 1cloud вижу искомое «Всего в пару кликов бесплатно объединяйте ваши виртуальные машины в частную сеть с пропускной способность 1Гбит/с.» Кстати, у них есть обычные сервера в Питере и высокопроизводительные в Москве. Поддержка говорит, что на московской инфраструктуре разворачиваются локальные сети с пропускной способностью 10Гбит/с.

Отлично, попробуем сделать то же самое, что выше, только для обычного хостинга.

В студии создаем решение, я назвал его sharedTest, в которое добавляем:
1. MVC приложение с аналогичным названием.
2. Библиотеку классов windows, я назвал просто Lib
3. Консольное приложение windows, я назвал его worker
image

Далее, необходимо связать эти проекты воедино, для этого жмём правой кнопкой на наше решение и идём в свойства
image
Тут необходимо в запускаемом проекте выбрать пункт, что у меня несколько запускаемых и мой Worker поднять повыше, чем сайт.
Тут же следующее свойство — зависимость проектов. Библиотека не зависит ни от кого. Сайт sharedTest зависит и от библиотеки и от воркера. Воркер зависит только от библиотеки.

Не забудем в свойствах воркера (так же правой кнопкой и свойства) указать запускаемый объект, чтобы вначале запускался воркер и никого не ждал.
image

Следующий шаг — добавляем зависимости. В библиотеке Lib правой кнопкой по связям (Reference) -> добавить ссылку… и нам понадобится пакет System.ServiceModel. Этот же пакет нам нужен и в воркере. Таким же образом в сайте и воркере добавляем ссылку на нашу библиотеку из проекта Lib.

В библиотеку помещаем (удобно отдельными файлами) public interface IwcfChat и public sealed class wcfChat:IDisposable (мой первый листинг наверху). Так же там находится справочник с общими настройками или структурами, там у меня лежит только айпи адрес (откуда я его взял будет немного позже).

    public static class spr
    {
        public static string wcfIP = "10.0.0.6";
    }


Сделаем наш воркер хостом для службы (аналогично верхнему листингу, только перенес в функцию Main, т.к. у меня консольное приложение)
class Program
    {
        static void Main(string[] args)
        {
            // Задайте максимальное число одновременных подключений 
            ServicePointManager.DefaultConnectionLimit = 12;

            // Create the host
            ServiceHost host = new ServiceHost(typeof(wcfChat));

            // Read config parameters

#if (DEBUG)
            string hostName = "127.255.0.1";
#else
            string hostName = spr.wcfIP;
#endif

            // Create Metadata
            ServiceMetadataBehavior metadatabehavior = new ServiceMetadataBehavior();
            host.Description.Behaviors.Add(metadatabehavior);

            Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
            string mexendpointurl = string.Format("net.tcp://{0}:{1}/wcfChatMetadata", hostName, 8003);
            host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexendpointurl, new Uri(mexendpointurl));

            // Create end point
            string endpointurl = string.Format("net.tcp://{0}:{1}/wcfChat", hostName, 9003);
            host.AddServiceEndpoint(typeof(IwcfChat), new NetTcpBinding(SecurityMode.None), endpointurl, new Uri(endpointurl));

            // Open the host
            host.Open();

            Console.WriteLine("Host opened on "+hostName);
            Console.ReadLine();
        }
    }


Порты 8003 и 9003 я взял из головы, главное, чтобы они не были стандартными и не были заняты.

Добавляем в воркер класс wcfChat — реализацию нашего интерфейса
using Lib;

namespace Worker
{
    class wcfChat : IwcfChat
    {

        public string SendMessage(string userId, string userName, string text)
        {
            return "Sending message....";
        }

        public string GetMessages(string userId, string userName)
        {
            return "Get messages";
        }

    }
}


Не стал тут ничего расписывать, но значения приходят. Самое время попробовать — модифицируем наш контроллер Home и его представление
        public ActionResult Index(bool isGet = true)
        {
            string res = "";
            using (var chat = new wcfChat())
            {
                res = isGet ? chat.channel.GetMessages("1", "name") : chat.channel.SendMessage("1", "name", "text");
            }
            return View((object)res);
        }

@model string
@{
    ViewBag.Title = "Home Page";
}
<br /><br />
<div>@Model</div>


Запускаем и видим, что всё работает:
image

Отлично, осталось совсем немного — выложить проект у хостера, напомню, я работаю с 1cloud. Регистрируемся, через панель управления создаем две виртуальные машины нужной нам конфигурации (я взял минимальные в Питерском ЦОДе, но это без разницы). Тестовый период у них несколько часов, поддержка расширила мне по просьбе, и вообще молодцы, идут навстречу.
image

Далее создаем локальную сеть, тоже парой кликов, второй пункт из панели управления, я назвал её testLocal. Кстати, я выключил флажок динамичных айпишников (DHCP), так как мой сервис зависит от статичного айпишника, из-за этого придется еще немного повозиться.
image

После создания, всё так же в панели управления, заходим в каждый сервер, идём на вкладку Настройки, находим Частные сети и переключаем флажок, что мы пользуемся вновь созданной сетью. Этот флаг добавит нам новый адаптер и новую сеть внутри виртуальной машины. Там же нам выдаётся внутренний айпишник, у меня это были 10.0.0.5 и 10.0.0.6 для сайта и воркера. Чтобы сеть заработала лезем по удаленному рабочему столу (RDP) на свои виртуалки и вручную вбиваем эти значения в настройки сети. Инструкции для тех, кто не разбирается, лежат там же рядом.

Хочу предупредить, что сразу у меня не заработало. Я указал сети общедоступными, поэтому пришлось их переделывать в частные вот так . А так же нужно добавить мои используемые порты в исключения брандмауэра. Для этого я вызвал запуск (Run) клавиши win+R и набрал там netsh.exe
после чего в командной строке набрал команду
firewall set portopening protocol = TCP port = 9003 name = myService mode = ENABLE scope = SUBNET profile = CURRENT


Теперь расскажу о публикации. Я настроил IIS по этому мануалу. Проверить работоспособность можно из браузера по внешнему айпишнику. Картиночка из IIS говорит нам о том, что всё работает.
image

Я ничего не делал ни с ftp, ни, тем более, с настройкой публикации из системы контроля версий, это тема другой статьи. Я подправил айпишник на выданный, опубликовал моё веб-приложение в файловую систему и ручками закинул в папочку C:\inetpub\wwwroot на сервере. Обновил страницу и увидел ожидаемую ошибку по адресу Home/Index, так как службы еще не было.

Собственно, мой воркер я так же опубликовал в файловую систему, получился установочник. Тут единственное, нужно не запутаться с DEBUG/RELEASE. Всю папочку я перетащил на рабочий стол второй виртуалки, левой кнопкой мыши установил и запустил. Вылезло моё чёрное окно, которое говорило, что сервис работает. Обновляем сайт, видим, что всё удалось.

image

Таким образом я получил работоспособный распределенный сервер. Чат я привел как пример, хотя в комментариях говорят, что за это нужно бить по рукам ) Вообще эту технологию я использую в браузерной игре, где функции расчета поединка вынесены на отдельный сервер. Из плюсов то, что я могу повысить производительность только одного конкретного сервера, если мне будет необходимо. Таким же образом можно сделать и для двух веб-сайтов, к примеру, обрабатывать фотографии будет первый сервер, а для хранения изображений будет использоваться второй сервер на поддомене (передача изображения идёт один раз, а дальше второй лишь отображает).
Естественно, реализация не идеальна, это лишь оправная точка. Необходимо настроить корректную и удобную публикацию. К тому же тут жесткая связь 1 к 1, а если нужно будет несколько воркеров, то необходим будет балансировщик нагрузки. Так же, по-хорошему, нужно избавиться от статичных внутренний айпишников. Ну и в целом для быстродействия нужно будет заменить wcf связь на реализацию напрямую через tcp сокеты.
Tags:
Hubs:
+5
Comments30

Articles

Change theme settings