Предыстория:
Сам я работаю в техотделе одной брокерской компании в Торонто, Канаде. Так же у нас есть еще один офис в Калгари. Как-то после планового установления Windows обновлений на единственном доменном контроллере в удаленном офисе не запустился W32Time сервис, который отвечает за синхронизацию времени с внешним источником. Таким образом в течение около недели время на сервере сбилось приблизительно на 20 секунд. Наши рабочие станции на тот момент времени по умолчанию получали время с контроллера. Сами понимаете, что случилось. В торгах время очень важно, разница в секунды может решить многое. Первыми расхождение во времени, к сожалению, заметили наши брокеры. Наш отдел техподдержки, состоящий по сути из 3 человек за это распекли. Надо было срочно что-то делать. Решением было применение групповой политики, которая отсылала все машины к внутреннему NTP серверу, работающему на CentOS. Еще были проблемы с DC Barracuda Agent, сервисом, отвечающим за соединение контроллеров домена с нашим Веб фильтром, и еще парочка сервисов причиняла нам порой беспокойство. Тем не менее решили что-то придумать, чтобы следить за пару сервисами. Я немного погуглил и понял, что есть много решений, в основном коммерчиских для данной проблемы, но так как я хотел научиться какому-нибудь скриптовому языку, то вызвался написать скрипт на Питоне с помощью нашего местного линукс-гуру. В последствие это переросло в скрипт, который проверяет все сервисы, сравнивая их наличие и состояние со списком желаемых сервисов, которые к сожалению надо делать вручную отдельно для каждой машины.
Решение:
На одном из Windows серверов я создал PowerShell скрипт такого вида:
echo "Servername" > C:SoftwareServicesServername.txt
get-date >> C:SoftwareServicesServername.txt
Get-Service -ComputerName Servername | Format-Table -Property status, name >> C:SoftwareServicesServername.txt
В моем случае таких кусков получилось 10 для каждого сервера
В Task Scheduler добавил следующий батник (мне это показлось легче, чем пытаться запусить оттуда PowerShell скрипт напрямую):
powershell.exe C:SoftwareServicescal01script.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
), "rn")
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: OKnEverything works correctlynLast dump of running services was taken at:n%snThe list of desired services:n%sn' % (serverName,dumpdate,'n'.join(desiredServices))
else:
statusText='%s: Errors' % (serverName)
detailsText='%s: Errorsn%s out of %s services are running.nServices failed to start:%snMissing services:%snLast dump of the running services was taken at:n%sn' % (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='%snn%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:SoftwareServicesdelete.ps1
Poweshell скрипт:
remove-item C:SoftwareServicesServerName.txt
Проект преследовал собой 2 цели — мониторинг сервисов и обучение Питону. Это мой первый пост на Хабре, поэтому я уже ожидаю наплыв критики в свой адрес. Если у Вас есть какие-либо замечания, особенно по улучшению данной системы, то милости прошу, поделитесь. Надеюсь, что это статья покажется кому-нибудь нужной, потому что подобного решения бесплатного и с уведомлением по email я не нашел. Может, что плохо искал.
Автор: tant123