Пользователь
0,0
рейтинг
29 октября 2012 в 19:26

Разработка → Мониторинг сервисов Windows средствами PowerShell и Python из песочницы

image
Предыстория:
Сам я работаю в техотделе одной брокерской компании в Торонто, Канаде. Так же у нас есть еще один офис в Калгари. Как-то после планового установления Windows обновлений на единственном доменном контроллере в удаленном офисе не запустился W32Time сервис, который отвечает за синхронизацию времени с внешним источником. Таким образом в течение около недели время на сервере сбилось приблизительно на 20 секунд. Наши рабочие станции на тот момент времени по умолчанию получали время с контроллера. Сами понимаете, что случилось. В торгах время очень важно, разница в секунды может решить многое. Первыми расхождение во времени, к сожалению, заметили наши брокеры. Наш отдел техподдержки, состоящий по сути из 3 человек за это распекли. Надо было срочно что-то делать. Решением было применение групповой политики, которая отсылала все машины к внутреннему NTP серверу, работающему на CentOS. Еще были проблемы с DC Barracuda Agent, сервисом, отвечающим за соединение контроллеров домена с нашим Веб фильтром, и еще парочка сервисов причиняла нам порой беспокойство. Тем не менее решили что-то придумать, чтобы следить за пару сервисами. Я немного погуглил и понял, что есть много решений, в основном коммерчиских для данной проблемы, но так как я хотел научиться какому-нибудь скриптовому языку, то вызвался написать скрипт на Питоне с помощью нашего местного линукс-гуру. В последствие это переросло в скрипт, который проверяет все сервисы, сравнивая их наличие и состояние со списком желаемых сервисов, которые к сожалению надо делать вручную отдельно для каждой машины.

Решение:

На одном из Windows серверов я создал PowerShell скрипт такого вида:
echo "Servername" > C:\Software\Services\Servername.txt
get-date >> C:\Software\Services\Servername.txt
Get-Service -ComputerName Servername | Format-Table -Property status, name >> C:\Software\Services\Servername.txt


В моем случае таких кусков получилось 10 для каждого сервера

В Task Scheduler добавил следующий батник (мне это показлось легче, чем пытаться запусить оттуда PowerShell скрипт напрямую):

powershell.exe C:\Software\Services\cal01script.ps1


Теперь каждый день я получал список со всеми сервисами в отдельном файле для каждого сервера в подобном формате:

Servername

Friday, October 26, 2012 1:24:03 PM

                                 Status Name                                   
                                 ------ ----                                   
                                Stopped Acronis VSS Provider                   
                                Running AcronisAgent                           
                                Running AcronisFS                              
                                Running AcronisPXE                             
                                Running AcrSch2Svc                             
                                Running ADWS                                   
                                Running AeLookupSvc                            
                                Stopped ALG                                    
                                Stopped AppIDSvc                               
                                Running Appinfo                                
                                Running AppMgmt                                
                                Stopped aspnet_state                           
                                Stopped AudioEndpointBuilder                   
                                Stopped AudioSrv                               
                                Running Barracuda DC Agent                     
                                Running BFE                                    
                                Stopped BITS                                   
                                Stopped Browser                                
                                Running CertPropSvc                                  
                                Running WinRM                                  
                                Stopped wmiApSrv                               
                                Stopped WPDBusEnum                             
                                Running wuauserv                               
                                Stopped wudfsvc    


Теперь самая главная часть. На отдельной машине с CentOS на борту я написал сей скрипт:

import sys
import smtplib
import string
from sys import argv
import os, time
import optparse
import glob

# function message that defines the email we get about the status
def message(subjectMessage,msg):
  SUBJECT = subjectMessage
  FROM = "address@domain.com"
  TO = 'address@domain.com'
  BODY =  string.join((
  "From: %s" % FROM,
  "To: %s" % TO,
  "Subject: %s" % SUBJECT ,
  "",
  msg
  ), "\r\n")


  s = smtplib.SMTP('mail.domain.com')
  #s.set_debuglevel(True)
  s.sendmail(FROM, TO, BODY)
  s.quit()
  sys.exit(0)

def processing(runningServicesFileName,desiredServicesFileName):

  try:
    desiredServicesFile=open(desiredServicesFileName,'r')
  except (IOError,NameError,TypeError):
    print "The list with the desired state of services either does not exist or the name has been typed incorrectly. Please check it again."
    sys.exit(0)

  try:
    runningServicesFile=open(runningServicesFileName,'r')
  except (IOError,NameError,TypeError):
    print "The dump with services either does not exist or the name has been typed incorrectly. Please check it again."
    sys.exit(0)
  #Defining variables
  readtxt = desiredServicesFile.readlines()
  desiredServices = []
  nLines = 0
  nRunning = 0
  nDesiredServices = len(readtxt)
  faultyServices = []
  missingServices = []
  currentServices = []
  serverName = ''
  dumpdate=''
  errorCount=0
 # Trimming file in order to get a list of desired services. Just readlines did not work putting \n in the end of each line
  for line in readtxt:
    line = line.rstrip()
    desiredServices.append(line)


  # Finding the number of currently running services and those that failed to start
  for line in runningServicesFile:
    nLines+=1
  # 1 is the line where I append the name of each server
    if nLines==1:
      serverName = line.rstrip()
  # 3 is the line in the dump that contains date
    if nLines==3:
      dumpdate=line.rstrip()
  # 7 is the first line that contains valueable date. It is just the way we get these dumps from Microsoft servers.
    if nLines<7:
      continue
  # The last line in these dumps seems to have a blank character that we have to ignore while iterating.
    if len(line)<3:
      break
    line = line.rstrip();
    serviceStatusPair = line.split(None,1)
    currentServices.append(serviceStatusPair[1])
    if serviceStatusPair[1] in desiredServices and serviceStatusPair[0] == 'Running':
      nRunning+=1
    if serviceStatusPair[1] in desiredServices and serviceStatusPair[0] != 'Running':
      faultyServices.append(serviceStatusPair[1])

  if nLines==0:
    statusText='Dumps are empty on %s' % (serverName)
    detailsText='Dumps are empty'

  # Checking if there are any missing services
  for i in range(nDesiredServices):
    if desiredServices[i] not in currentServices:
       missingServices.append(desiredServices[i])
  # Sending the email with results
  if nRunning == nDesiredServices:
    statusText='%s: OK' % (serverName)
    detailsText='%s: OK\nEverything works correctly\nLast dump of running services was taken at:\n%s\nThe list of desired services:\n%s\n' % (serverName,dumpdate,'\n'.join(desiredServices))
  else:
    statusText='%s: Errors' % (serverName)
    detailsText='%s: Errors\n%s out of %s services are running.\nServices failed to start:%s\nMissing services:%s\nLast dump of the running services was taken at:\n%s\n' % (serverName,nRunning,nDesiredServices,faultyServices,missingServices,dumpdate)
    errorCount=errorCount+1
  return (statusText,detailsText,errorCount)
# Defining switches that can be passed to the script
usage = "type -h or --help for help"
parser = optparse.OptionParser(usage,add_help_option=False)
parser.add_option("-h","--help",action="store_true", dest="help",default=False, help="this is help")
parser.add_option("-d","--desired",action="store", dest="desiredServicesFileName", help="list of desired services")
parser.add_option("-r","--running",action="store", dest="runningServicesFileName", help="dump of currently running services")
parser.add_option("-c","--config",action="store", dest="configServicesDirectoryName", help="directory with desired services lists")
(opts, args) = parser.parse_args()
# Outputting a help message and exiting in case -h switch was passed
if opts.help:
  print """
  This script checks all services on selected Windows machines and sends out a report.

  checkServices.py [argument 1] [/argument 2] [/argument 3]

  Arguments:      Description:

  -c, --config - specifies the location of the directory with desired list of services and finds dumps automatically

  -d, --desired - specifies the location of the file with the desired list of services.

  -r, --running - specifies the location of the file with a dump of running services.
  """
  sys.exit(0)

statusMessage = []
detailsMessage = []
body = []
errorCheck=0
directory='%s/*' % opts.configServicesDirectoryName

if opts.configServicesDirectoryName:
  check=glob.glob(directory)
  check.sort()
  if len(check)==0:
    message('Server status check:Error','The directory has not been found. Please check its location and spelling.')
    sys.exit(0)
  for i in check:
    desiredServicesFileName=i
    runningServicesFileName=i.replace('desiredServices', 'runningServices')
    #print runningServicesFileName
    status,details,errors=processing(runningServicesFileName,desiredServicesFileName)
    errorCheck=errorCheck+errors
    statusMessage.append(status)
    detailsMessage.append(details)
  body='%s\n\n%s' % ('\n'.join(statusMessage),'\n'.join(detailsMessage))

  if errorCheck==0:
    message('Server status check:OK',body)
  else:
    message('Server status check:Errors',body)


if opts.desiredServicesFileName or opts.desiredServicesFileName:
  status,details,errors=processing(opts.runningServicesFileName,opts.desiredServicesFileName)
  message(status,details)


Файлы дампов и списков с желаемыми сервисами должны иметь одинаковые имена. Список с сервисами, за которыми мы следим (desiredServices) должен быть вот такого вида:

Acronis VSS Provider
AcronisAgent
AcronisFS    
AcrSch2Svc      


Скрипт будет проверять сервисы, а потом компоновать все это в одно email сообщение, которое в зависимости от результата будет говорить, что все в порядке в теме сообщения или, что есть ошибки, а в теле сообщения раскрывать, какие это ошибки. Для нас одной проверки в день достаточно, поэтому ранним утром мы получаем уведомление о состоянии наших Windows серверов. Чтобы скопировать файлы с Windows сервера на машину с линуксом, мой коллега помог мне со следующим баш скриптом:

#!/bin/bash

mkdir runningServices
smbclient --user="user%password" "//ServerName.domain.com/software" -c "lcd runningServices; prompt; cd services; mget *.txt"

cd runningServices
for X in `ls *.txt`; do
  iconv -f utf16 -t ascii $X > $X.asc
  mv $X.asc $X
done


Этот скрипт так же меняет кодировку, ибо на моей машине Linux не очень хотел работать с UTF16. Далее, чтобы отчищать папку от дампов с сервисами я добавил батник в Task Scheduler чтобы запускать PowerShell скрипт, который стирает дампы.
Батник:
powershell.exe C:\Software\Services\delete.ps1


Poweshell скрипт:
remove-item C:\Software\Services\ServerName.txt


Проект преследовал собой 2 цели — мониторинг сервисов и обучение Питону. Это мой первый пост на Хабре, поэтому я уже ожидаю наплыв критики в свой адрес. Если у Вас есть какие-либо замечания, особенно по улучшению данной системы, то милости прошу, поделитесь. Надеюсь, что это статья покажется кому-нибудь нужной, потому что подобного решения бесплатного и с уведомлением по email я не нашел. Может, что плохо искал.
@tant123
карма
5,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (21)

  • +4
    Отличная картинка в заголовке с разрешением 1700х1100
    • –4
      Отличный комментарий по теме статьи!
  • 0
    Картинка и правда не очень подходит…

    А по теме статьи — вы не рассматривали такие готовые решения с напильником как zabbix, Nagios, Munin?
    Вот вам ссылки на связанные тему:
    ru.wikipedia.org/wiki/Zabbix
    habrahabr.ru/post/30494/
    habrahabr.ru/qa/16919/

    Как я понял, у вас нет проблем с серверами не на Винде ( тот же CentOS).
    • 0
      Спасибо за замечание по делу. Для этого, как я понимаю, нужно, чтобы SNMP Service был настроен на Windows машинах. А у нас эти сервисы на данный момент даже не установлены. Я хотел решение с как можно меньшим вмешательством в нашу среду. А насчет тех замечательных программ, мы уже используем Zenos и пытаемся еще применить Zabbix, но это больше вотчина моего коллеги, вышеупомянутого Линукс-гуру. Линукс проблемы это уже его головная боль, хотя бывают они крайне редко. Мы же, виндузятники, на данный момент используем для наблюдения за всей нашей сетью What's Up Gold 2006, а теперь так же мой небольшой скрипт.
      • 0
        snmp клиент разворачивается тем же повершеллом, фаервольная политика для открытия snmp — дело еще 10 минут.
        вообще какая-то странная смесь и каша, если честно. Я бы уже решал на чем-то одном, то же powershell прекрасно умеет даже письма писать, и сервисы рестартовать в случае падения
  • 0
    А не проще через WMI обойти все хосты и нужные сервисы ремотно стартовать или что там надо сделать, а результаты скинуть в лог?
    • 0
      Ну WMI я использовал, чтобы собрать дампы о состоянии, а именно PoweShell. А дальше уже использовал Питон, ибо еще одной целью было обучение ему. Но я полностью с Вами согласен, что все можно было бы сделать и через WMI, обходя использования Линукса и Питона. В конце концов я получил удовольствие от знакомства с этим замечательным языком.
  • +1
    Я, уж простите, не могу понять, как можно получать удовольствие от Питона, реализуя на нем примитивный парсинг текстового выхлопа, полученный заталкиванием в тектовый файл объектного выхлопа из Powershell, тогда как можно напрямую обращаться к свойству Status сервиса. Зачем тогда вообще powershell, если можно было сделать «sc query > services.txt»? Хорошо бы, в рамках процесса обучения, о котором так много было сказано, начать с понимания — когда и какой инструмент надо применять. Например, выборка списка проверяемых хостов и сервисов из конфига в XML с последующим опросом серверов на предмет запущенности этих сервисов выглядит как:

    [xml]$services = Get-Content d:\test.xml
    $services.services.service | %{ get-service -ComputerName $_.host -Name $_.service } | ? { $_.Status -ne "Running" }
    

    Вместо XML можно использовать Import-CSV, результат можно выдать в Export-CSV, еще несколько строк займет отправка почты.
  • 0
    Sc query было бы труднее парсить. А вот использование xml Горд (вышеупомянутый линукс-гуру) мне предложил, но так как моя программа довольно проста, то я решил оставить это все в виде обычных текстовых файлов. Да и потом PoweShell я знаю очень поверхносто на уровне опрашивания системы по сервисам или разные атрибуты Active Directory. Но мне нравится Ваша идея, а именно ее локаничность. Если Вас не затруднит, не могли бы Вы подробно прокомментировать свой код и описать структуру xml файла? Или мне сразу стоит отправиться в Гугл за мануалами по PoweShell?
    • 0
      Вам за пределы powershell можно вообще не выходить. проверка запущенности нужных сервисов это реально пару команд, в случае их внезапного падения — попытка перезапуска, в случае проблемы и тут — письмо администратору.
  • 0
    А не проще ли обойтись без PowerShell, только питоном? Пакет pywin32 умеет не только проверять статусы служб, но и запускать/останавливать их.
    • 0
      а зачем городить внешний софт если вполне хватает штатно продуманного psh?
  • 0
    Про pywin32 не знал, спасибо, надо будет проверить. А Python был выбран в целяъ обучения. Я не отрицаю, что можно было все сделать в psh. Кстати, я думаю получше познакомиться с shell скриптами.
  • 0
    Несколько заметок
    1. Сервисы windows by default умеют «самоперезапускаться». Это можно настроить на соответствующей вкладке свойств сервиса.
    2. Там же есть параметр, позволяющий запустить внешнее приложение или скрипт в ответ на падение. Я не пробовал, поймет ли он просто ps1 файл, но можно испытать. В вашем случае было бы достаточно просто отправлять скриптом почту на Nй раз падения сервиса. И не нужно городить такой огород.
    • 0
      psh скрипт можно запустить как c:\...\....\powershell.exe c:\scripts\test.ps1, например. только установить executialpolicy в unrestricted или подписать скрипты
    • 0
      Вы правы, но что случится, если сервис поменяет свой Startup Type?
      • 0
        ну, вообще в рамках изменения конфигурации такие вещи отслеживаемы. А если не в рамках, апррувер изменения получит по ушам за изменние конфигурации «by default»
      • 0
        Мне думается что сам он вряд ли это сделает. А для того чтобы это не происходило есть GPO, где вы можете создать базовую конфигурацию. В случае изменения GPO вернет все «взад» на следующей итерации. Чтобы отслеживать такие вещи, нужно, видимо, иметь некую базу, хранящую дефолтные состояния. В общем это усложнение, мне думается
  • 0
    Если честно, то один раз такое случилось, когда мне надо было давать по шапке. Однажды на Exchange сервере одно из обновлений, по-моему это .NET 4, занимало слишком длительное время, я, ничтоже сумнящеся, перезагрузил сервер. Половина сервисов поменяло стартап тип на Manual. Восстановили, смотря на второй почтовый сервер с примерно такой же конфигурацией. Так что с тех пор я держу список для каждого сервера с необходимыми сервисами.
    • 0
      Для этого есть тестовая лаборатория, по хорошему. где можно и понаблюдать за поведением зверушки.
      PS. а зачем мониторить установку обновлений то? поставил ставиться ми пошел себе по делам, как захочет перезагрузиться — само скажет, а не захочет — вот и кейс что перезагрузка не нужна — аппрувим на один из серверов, ждём пару дней, если всё ок — аппрувим на второй, и недайбоже это делать во второй половине недели
  • 0
    Мы по старинке вручную обновляем обычно в вечер пятницы или на выходных. Серверов не так много, а их работа очень критична.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.