Как стать автором
Обновить

Автоматический перезапуск IIS службой при нарушении в ее работе

Пишем само-восстанавливающийся сервер на IIS


image

Я решил поделиться с Хаброюзерами полезной на мой взгляд программкой, многие сталкивались в работе IIS когда возникает какой то сбой и не всегда можно определить чем он вызван, но сервер перестает работать, возвращает ошибку 500 или еще что то в этом духе, обычно такое лечится перезапуском IIS, но не всегда рядом с сервером есть человек, который может это сделать, в связи с чем возникла идея, почему бы не делать это автоматически, в таком случае восстановление заняло бы максимум 5-10 минут и без присутствия администратора.

Такой подход сработает со многими продуктами, такими как Sharepoint, Webtutor (кто видел тот меня поймет) и любыми другими платформами на IIS, после переделки можно работать и с другими службами, если это нужно.
Описывать код, листинг которого не вижу особого смысла, т.к. его полностью прокомментировал, разберется любой кто хоть немного знаком с C#.
Надеюсь данное приложение поможет кому то в его работе. Буду рад услышать комментарии и пожелания.

P.S. если нужен готовый исходник пишите e-mail в комментариях.

Листинг программы под спойлером
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.ServiceProcess;
using System.Text;
using System.Threading;
//using System.Threading.Tasks;

namespace WebTutorMonitor
{

    public partial class Service1 : ServiceBase
    {
// Коды, ничего не значат, просто цифры
private  int Code_Init=101;
private int Code_Start=102;
private static int Code_CreateLog=103;
private int Code_Stop=104;
private int Code_Resume=105;
private int Code_Pause=106;
private static int Code_DoneOneTask=107;
private int Code_TascControllerStarted=108;
private static int Code_TastWTMonitoringRun=109;

static bool Testing = true;//Если true сервис будет работать с тестом, не с боем
static bool DoStopStart = false;//Если false реально запуска и остановки не будет, только для проверки

static string testserverurl = @"http://webtutor.orient.root.biz";
static string prodserverurl = @"http://wtapp01";
static string testservername = @"webtutor.orient.root.biz";
static string prodservername = @"wtapp01";

        public Service1()
        {
            EventLog ev = new EventLog("Application", System.Environment.MachineName, "WebTutorMonitor");
            //Очистка журнала событий, раскоментировать если нужно почистить журнал
            //EventLog.Clear();
            InitializeComponent();
            CreateLog("Служба WebTutorMonitor инициaлизируется.",EventLogEntryType.Information,Code_Init );
            if (Testing)//Старт в режиме приложения, для Debug'a, произойдет только если Testing=True
            {
                OnStart(null);
            }
        }

        //Действия при запуске службы
        protected override void OnStart(string[] args)
        {
            //Запуск службы
            CreateLog("Служба WebTutorMonitor запущена успешно.", EventLogEntryType.Information,Code_Start);
            MainThread(); //Главный процесс службы
        }

        //Запись события в журнал событий
        static void CreateLog(string text, EventLogEntryType Type,int Code)
        {
            Thread.Sleep(1500);
            if (!(EventLog.SourceExists("WebTutorMonitor", System.Environment.MachineName)))
            {
                EventLog.CreateEventSource("WebTutorMonitor", "WebTutorLog", System.Environment.MachineName);
                CreateLog("Создан журнал событий WebTutorMonitor", EventLogEntryType.Information ,Code_CreateLog);
            }
            EventLog ev = new EventLog("Application", System.Environment.MachineName, "WebTutorMonitor");
            ev.WriteEntry(text, Type, Math.Abs (Code));
            ev.Close();
            GC.Collect();
        }

        //Действия при остановке службы
        protected override void OnStop()
        {
            CreateLog("Служба WebTutorMonitor остановлена.", EventLogEntryType.Warning ,Code_Stop);
            TimerCanceled = true;
        }

        //Действия при возобновлении работы службы
        protected override void OnContinue()
        {
            CreateLog("Служба WebTutorMonitor запущена после паузы.",EventLogEntryType.Information ,Code_Resume); 
            base.OnContinue();
        }

        //Действия при паузе службы
        protected override void OnPause()
        {
            CreateLog("Служба WebTutorMonitor приостановлена.", EventLogEntryType.Warning,Code_Pause ); 
            base.OnPause();
        }

        //Главный поток службы
        void MainThread()
        {
            //Запуск обработки заданий в отдельном потоке, чтобы освободить текущий поток для диспетчера служб
            System.Threading.Thread tr = new System.Threading.Thread(new System.Threading.ThreadStart (TimerTask));//Работа потока проходит в процедуре TimerTask
            tr.Start();//Запуск процедуры TimerTask в отдельном потоке.
            CreateLog("Менеджер заданий запущен.", EventLogEntryType.Information , Code_TascControllerStarted);
        }

       static bool TimerCanceled = false;//Если True поток сервиса получает комманду на остановку

        //Основная процедура службы
        static private void TimerTask()
        {
            CreateLog("Задача монитор WT запущена.", EventLogEntryType.Information, Code_TastWTMonitoringRun); 
            rr:
                rcl++;
                if (TimerCanceled)//Если получен True выкодим из потока
                {
                    CreateLog("Логирование завершено  " + DateTime.Now.ToString() + ". Выполнено " + rcl.ToString() + " итераций логирования.", EventLogEntryType.Information, Code_DoneOneTask);
                    goto ext;
                }
                else
                {
                    StartLogIteration();//Если еще рано выходить запускаем итерацию рабочего процесса
                }
                Thread.Sleep(100);
                goto rr;
            ext:
                return; //Выход из потока
        }

        //Выключить себя, можно применять для завершения работы службы
        static void ShutdownMeNow(string reason)
        {
            CreateLog("Завершение работы службы: " + reason, EventLogEntryType.Information, Code_DoneOneTask);
            ServiceController sc = new ServiceController("Webtutormonitor", System.Environment.MachineName);//Webtutormonitor это название службы
            if (sc.CanStop == true)
            {
                sc.Stop();//Посылаем команду на отключение службы на текущем компьютере
            }
        }

        //Выполняем действие (сканируем или что либо другое)
       static  void StartLogIteration()
        {
            RunNewScan(); //Сканируем сервер на работоспособность
            //CreateLog("Работа выполнена  " + DateTime.Now.ToString() , EventLogEntryType.Information, Code_DoneOneTask); //Можем написать что бы сделали действие в лог
            System.Threading.Thread.Sleep(5000);//Поспим, чтобы не слишком часто работать
        }

        //Выполняем сканирование сервера
      static void RunNewScan()
       {
           string statdisk;//Переменная для хранения коментария ответа от сервера
           string urlparam;//Переменная для хранения адреса сервера
           if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
           {
               urlparam = testserverurl;//тестовый
           }
           else { urlparam = prodserverurl; }//боевой
           int stat = ServerStat(out statdisk, new Uri(urlparam));//Получаем данные от сервера, его текущее состояние
           if (stat!=200)//Если не 200 значит у нас проблемы
           {
               CreateLog("Ошибка в работе WT: " + statdisk, EventLogEntryType.Error  , stat);
               if (stat==503)//Если 503 значит он в процессе перезапуска либо у него проблема с учетной записью
               {
                   DoRestartIIS();//Если первое проверим, перезапустим если нужно
               }
               else//другие ошибки
               {
                   if (stat==404)//Сервер выключен, пробуем включить
	                {
                        if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
                          {
                              StartIISWTNow(testservername);//тестовый
                          }
                        else { StartIISWTNow(prodservername); }//боевой
	                }
               }
           }
       }

        //процедура перезапуска IIS
      static void DoRestartIIS()
      {
          ServiceController sc;//Контроллер сервисов
          //Инициируем контроллер для нужного нам сервиса
          if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
           {
               sc = new ServiceController("W3SVC", testservername);//тестовый
           }
           else
           {
               sc = new ServiceController("W3SVC", prodservername);//боевой
           }
          CreateLog("Предпринимаю попытку восстановить сервер путем перезапуска IIS!", EventLogEntryType.Error, 1001);

          //Пробуем выключить службу веб-публикаций на сервере
          if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
           {
                CreateLog("Типа выключаю сервис!", EventLogEntryType.Error, 1202);
                ShutdownIISWTNow(testservername);//тестовый
           }
          else { ShutdownIISWTNow(prodservername); }//боевой
          //Если мы тут, то мы смогли выключить службу, иначе до сих пор долбимся в попытках ее отключить
          //Служба отключена- включаем ее обратно
          if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
          {
               CreateLog("Типа включаю сервис!", EventLogEntryType.Error, 1201);
               StartIISWTNow(testservername);//тестовый
          }
          else { StartIISWTNow(prodservername); }//боевой
          //Если мы тут, значит сервер сказал что служба запущена и работает
          Thread.Sleep(5000);//Дадим немного времени
          sc.Refresh();//Проверим, может врет?
          //Выводим текущее состояние в лог
          CreateLog("Сервер в состоянии  " + sc.Status.ToString(), EventLogEntryType.Error, 1301);
          if (sc.Status == ServiceControllerStatus.Running )//Если сработало пишем соответствующую запись в лог, если нет тоже
          {
              CreateLog("Попытка перезапутить IIS Удалася!:) ", EventLogEntryType.Error, 1101);
          }
          else
          {
              CreateLog("Не могу сделать рестарт IIS Функция отключена:( ", EventLogEntryType.Error, 1101);
          }
      }

      //Процедура выключения IIS на сервере, которая используется в процедуре DoRestartIIS
      static void ShutdownIISWTNow(string compname = testservername)
      {
          ServiceController sc = new ServiceController("W3SVC", compname);//Инициализируем контроллер для данного сервера, W3SVC это имя службы веб публикаций на сервере Windows Server 2008 R2
      gg: sc.Refresh();//Тут точка возврата, будем возвращаться сюда, если не получилось выключить службу
          CreateLog("Попытка завершения службы на сервере " + compname, EventLogEntryType.Information, Code_DoneOneTask);
          if (sc.CanStop == true)//Спрашиваем у сервере, можем ли мы отключить сейчас iis
          {
              try //Если да, то пробуем это сделать
	            {
                    if (DoStopStart)//Если false не будем реально ничего делать, просто сделаем вид что пытались
                    {
                        sc.Stop();//Посылаем комманду на отключение службы
                        CreateLog("Сервер получил команду на остановку службы :)", EventLogEntryType.Error, 1004);
                    }
                    else
                    {
                        CreateLog("Сервер не получил команду на остановку службы потому что приложение работает в демо режиме", EventLogEntryType.Error, 1204);
                    }
	            }
	            catch (Exception eee)//Если возникла ошибка пишем ее в лог
	            {
		            CreateLog("Не получилось остановить службу :( " + eee.Message , EventLogEntryType.Error , 1005);
	            }

          }
          else//Если сервер говорит, что нельзя останавливаться идем сушить бублики до следующего раза
          {
              CreateLog("Её нельзя сейчас останавливать :( Статус сервера: " + sc.Status.ToString(), EventLogEntryType.Error, 1003);
          }
          if (sc.Status == ServiceControllerStatus.Stopped)//Если сервер говорит что служба остановлена радуемся, у нас получилось
          {
              CreateLog(@"Я остановил службу!!! =8 Ха! Ха! хА! =\" + sc.Status.ToString(), EventLogEntryType.Warning, 1043);
              return;//Выходим, чтобы попробовать запустить ее снова.
          }
          Thread.Sleep(10000);//если мы пришли сюда значит сервер еще не остановился, а значит у нас почему то не получилось его остановить, 
          //может быть он просто сейчас пытается остановиться но  еще не успел закончить, подождем 10 секунд и попробуем снова
          //так как не остановив запускать его бессмысленно будем долбиться пока не сможем его выключить
          goto gg;//пробуем снова
      }

      //Запуск IIS все  по аналогии с остановкой.
      static void StartIISWTNow(string compname = testservername)
      {
          ServiceController sc = new ServiceController("W3SVC", compname);
      gg: sc.Refresh();
          CreateLog("Запуска службы на сервере " + compname, EventLogEntryType.Information, Code_DoneOneTask);
          if (sc.Status == ServiceControllerStatus.Stopped)
          {
              try
              {
                  if (DoStopStart)
                  {
                      sc.Start();
                      sc.Refresh();
                      sc.WaitForStatus(ServiceControllerStatus.Running , new TimeSpan(0, 0, 10));
                      CreateLog("Сервер получил команду на запуск службы :)", EventLogEntryType.Warning, 1211);
                  }
                  else
                  {
                      CreateLog("Сервер не получил команду на запуск службы потому что приложение работает в демо режиме", EventLogEntryType.Error, 1204);
                  }  
              }
              catch (Exception eee)
              {
                  CreateLog("Не получилось запустить службу :( " + eee.Message, EventLogEntryType.Error, 1012);
              }

          } else
              {
                  CreateLog("Её нельзя сейчас запускать :( Статус сервера: " + sc.Status.ToString(), EventLogEntryType.Error, 1013);
              }
            if (sc.Status == ServiceControllerStatus.Running )
              {
                  CreateLog("У меня получилось запустить службу:)" , EventLogEntryType.Warning , 1023);
                  return;
              }
          Thread.Sleep(10000);
          goto gg;
      }

      //Определяем статус сервера в данный момент, descript это дополнительная информация о состоянии, текст ошибки например, url-это адрес узла сервера для проверки
      static int ServerStat(out string descript, Uri url)
       {
           int statusCode=-1;
           var myCred;//учетные данные, т.к. мы будем входить на сервер IIS под NTLM, передалать на другие типы авторизации проще
           if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
           {
               myCred = new NetworkCredential("vipuhov", "123456", "ORIENT");//тестовый тут мы используем такие учетные данные
           }
           else { myCred = CredentialCache.DefaultNetworkCredentials; }//боевой тут мы настроили под какими учетными данными будет запускаться служба
           Uri urll = url;
           var myCache = new CredentialCache { { urll, "NTLM", myCred } };//тут мы привязываем учетные данные к адресу сайта, чтобы установить NTLM авторизацию
           try
           {
               var request = (HttpWebRequest)WebRequest.Create(urll);//создаем объект веб запроса
               request.Timeout = 50000;//устанавливаем таймаут соединения, 50 секунд это перебор для любого сайта, можно говорить что ему плохо если он минуту страницу показать не может
               //настраиваем учетные данные и метод, нам сама страница не нужна, поэтому берем только заголовок
               request.Credentials = myCache;
               request.Method = WebRequestMethods.Http.Head;
               request.Accept = @"*/*";
               var response = (HttpWebResponse)request.GetResponse();//делаем запрос
               statusCode = (int)response.StatusCode;//получаем код ответа от сервера
               response.Close();//закрываем запрос, нам он больше не нужен
               GC.Collect();//убираем мусор:)
               descript = "OK";//если мы тут значит все ОК:)
               return statusCode;//возвращаем код, скорее всего 200
           }
           catch (WebException ex)//тут ловим ошибки
           {
               if (ex.Response == null)//если нет ответа от сервера скорее всего ошибка 404 сервер недоступен
               {
                   descript = ex.Message;//возвращаем текст ошибки
               }
               else
               {
                   statusCode = (int)((HttpWebResponse)ex.Response).StatusCode;//если запрос прошел но сервер возвратил ошибку пишем ошибку возвращаем его код
                   descript = ((HttpWebResponse)ex.Response).StatusDescription;//пишем сообщение которое нам прислал сервер вместе с ошибкой
               }
               GC.Collect();//чистим мусор:)
               return statusCode;//возращаем код ошибки
           }
       }

    }
}

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.