ყოველდღიური ანგარიშები ვირტუალური მანქანების ჯანმრთელობის შესახებ R და PowerShell-ის გამოყენებით

ყოველდღიური ანგარიშები ვირტუალური მანქანების ჯანმრთელობის შესახებ R და PowerShell-ის გამოყენებით

Entry

Საღამო მშვიდობისა. უკვე ნახევარი წელია, რაც ჩვენ ვაწარმოებთ სკრიპტს (უფრო სწორად სკრიპტების კომპლექტს), რომელიც ქმნის ანგარიშებს ვირტუალური მანქანების (და არა მხოლოდ) სტატუსის შესახებ. გადავწყვიტე გამეზიარებინა ჩემი შექმნის გამოცდილება და თავად კოდი. კრიტიკის იმედი მაქვს და ეს მასალა ვინმეს გამოადგება.

საჭიროების ფორმირება

ჩვენ გვაქვს ბევრი ვირტუალური მანქანა (დაახლოებით 1500 VM განაწილებული 3 vCenters-ში). ახლები იქმნება და ძველები საკმაოდ ხშირად იშლება. წესრიგის შესანარჩუნებლად, vCenter-ს დაემატა რამდენიმე მორგებული ველი, რათა დაყოს VM ქვესისტემებად, მიუთითოს არის თუ არა ისინი სატესტო და ვის მიერ და როდის შეიქმნა. ადამიანურმა ფაქტორმა განაპირობა ის, რომ მანქანების ნახევარზე მეტი დარჩა ცარიელი ველებით, რამაც გაართულა მუშაობა. ექვს თვეში ერთხელ ვიღაც ბრაზდებოდა და დაიწყო ამ მონაცემების განახლებაზე მუშაობა, მაგრამ შედეგი კვირანახევრის შემდეგ აღარ იყო აქტუალური.
ნება მომეცით დაუყოვნებლივ განვმარტო, რომ ყველას ესმის, რომ უნდა არსებობდეს განაცხადები მანქანების შესაქმნელად, მათი შექმნის პროცესი და ა.შ. და ასე შემდეგ. და ამავდროულად, ყველა მკაცრად იცავს ამ პროცესს და ყველაფერი რიგზეა. სამწუხაროდ, აქ ასე არ არის, მაგრამ ეს არ არის სტატიის თემა :)

ზოგადად მიღებული იქნა გადაწყვეტილება ველების შევსების სისწორის შემოწმების ავტომატიზირებაზე.
ჩვენ გადავწყვიტეთ, რომ ყოველდღიური წერილი არასწორად შევსებული მანქანების სიით ყველა პასუხისმგებელი ინჟინრისთვის და მათი უფროსებისთვის კარგი დასაწყისი იქნებოდა.

ამ ეტაპზე, ჩემმა ერთ-ერთმა კოლეგამ უკვე დანერგა სკრიპტი PowerShell-ში, რომელიც ყოველდღე, გრაფიკის მიხედვით, აგროვებდა ინფორმაციას ყველა vCenter-ის ყველა მანქანაზე და აგენერირებდა 3 csv დოკუმენტს (თითოეული საკუთარი vCenter-ისთვის), რომლებიც იტვირთებოდა საერთო დისკი. გადაწყდა, რომ ეს სკრიპტი საფუძვლად მიგვეღო და დაემატებინა ჩეკებით R ენის გამოყენებით, რომელთანაც გარკვეული გამოცდილება გვქონდა.

დასრულების პროცესში გადაწყვეტამ მოიპოვა ინფორმაცია ფოსტით, მონაცემთა ბაზა ძირითადი და ისტორიული ცხრილით (დაწვრილებით ამის შესახებ მოგვიანებით), ასევე vSphere ჟურნალების ანალიზი vm-ის რეალური შემქმნელების და მათი შექმნის დროის მოსაძებნად.

განვითარებისთვის გამოყენებული იყო IDE RStudio Desktop და PowerShell ISE.

სკრიპტი გაშვებულია ჩვეულებრივი Windows ვირტუალური მანქანიდან.

ზოგადი ლოგიკის აღწერა.

სკრიპტების ზოგადი ლოგიკა ასეთია.

  • ჩვენ ვაგროვებთ მონაცემებს ვირტუალურ მანქანებზე PowerShell სკრიპტის გამოყენებით, რომელსაც ვუწოდებთ R-ის მეშვეობით, და ვაერთიანებთ შედეგს ერთ csv-ში. ანალოგიურად ხდება ენებს შორის საპირისპირო ურთიერთქმედება. (შესაძლებელია მონაცემების გადატანა პირდაპირ R-დან PowerShell-ში ცვლადების სახით, მაგრამ ეს რთულია და შუალედური csv-ების არსებობა აადვილებს შუალედური შედეგების გამართვას და ვინმესთან გაზიარებას).
  • R-ის გამოყენებით ჩვენ ვქმნით მოქმედ პარამეტრებს იმ ველებისთვის, რომელთა მნიშვნელობებს ვამოწმებთ. — ჩვენ ვქმნით word დოკუმენტს, რომელიც შეიცავს ამ ველების მნიშვნელობებს საინფორმაციო წერილში ჩასართავად, რომელიც იქნება პასუხი კოლეგების კითხვებზე „არა, მაგრამ როგორ უნდა შეავსო ეს?
  • ჩვენ ვტვირთავთ ყველა VM-ს მონაცემებს csv-დან R-ის გამოყენებით, ვქმნით მონაცემთა ჩარჩოს, ვხსნით არასაჭირო ველებს და ვქმნით საინფორმაციო xlsx დოკუმენტს, რომელიც შეიცავს შემაჯამებელ ინფორმაციას ყველა VM-ისთვის, რომელსაც ვტვირთავთ საერთო რესურსზე.
  • ჩვენ ვიყენებთ ყველა შემოწმებას ველების შევსების სისწორისთვის მონაცემთა ჩარჩოში ყველა VM-ისთვის და ვქმნით ცხრილს, რომელიც შეიცავს მხოლოდ VM-ებს არასწორად შევსებული ველებით (და მხოლოდ ამ ველებით).
  • ჩვენ ვაგზავნით VM-ების ჩამონათვალს სხვა PowerShell სკრიპტზე, რომელიც გადახედავს vCenter ჟურნალებს VM-ის შექმნის მოვლენებისთვის, რაც საშუალებას მოგვცემს მივუთითოთ VM-ის შექმნის სავარაუდო დრო და სავარაუდო შემქმნელი. ეს იმ შემთხვევისთვის, როცა არავინ აღიარებს ვისი მანქანაა. ეს სკრიპტი სწრაფად არ მუშაობს, მით უმეტეს, თუ ბევრი ჟურნალია, ამიტომ ჩვენ ვუყურებთ მხოლოდ ბოლო 2 კვირას და ასევე ვიყენებთ სამუშაო პროცესს, რომელიც საშუალებას გაძლევთ ერთდროულად მოიძიოთ ინფორმაცია რამდენიმე VM-ზე. მაგალითის სკრიპტი შეიცავს დეტალურ კომენტარებს ამ მექანიზმის შესახებ. შედეგს ვამატებთ csv-ში, რომელსაც ისევ ვტვირთავთ R-ში.
  • ჩვენ ვქმნით ლამაზად ფორმატირებულ xlsx დოკუმენტს, რომელშიც არასწორად შევსებული ველები იქნება მონიშნული წითლად, ფილტრები გამოყენებული იქნება ზოგიერთ სვეტზე და მითითებული იქნება დამატებითი სვეტები, რომლებიც შეიცავს განზრახ შემქმნელებს და VM-ის შექმნის დროს.
  • ჩვენ ვქმნით ელფოსტას, სადაც ვამაგრებთ დოკუმენტს, რომელიც აღწერს სწორი ველის მნიშვნელობებს, ასევე ცხრილს არასწორად შევსებული ველებით. ტექსტში მივუთითებთ არასწორად შექმნილი VM-ების საერთო რაოდენობას, საერთო რესურსის ბმულს და სამოტივაციო სურათს. თუ არ არის არასწორად შევსებული VMs, ჩვენ ვაგზავნით კიდევ ერთ წერილს უფრო ბედნიერი მოტივაციური სურათით.
  • ჩვენ ჩავწერთ ყველა VM-ის მონაცემებს SQL Server-ის მონაცემთა ბაზაში, ისტორიული ცხრილების დანერგილი მექანიზმის გათვალისწინებით (ძალიან საინტერესო მექანიზმია - ამის შესახებ მოგვიანებით)

რეალურად სკრიპტები

მთავარი R კოდი ფაილი

# Путь к рабочей директории (нужно для корректной работы через виндовый планировщик заданий)
setwd("C:ScriptsgetVm")
#### Подгружаем необходимые пакеты ####
library(tidyverse)
library(xlsx)
library(mailR)
library(rmarkdown)
##### Определяем пути к исходным файлам и другие переменные #####
source(file = "const.R", local = T, encoding = "utf-8")
# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameVmCreationRules)) {file.remove(filenameVmCreationRules)}
#### Создаём вордовский документ с допустимыми полями
render("VM_name_rules.Rmd",
output_format = word_document(),
output_file = filenameVmCreationRules)
# Проверяем существование файла со всеми ВМ и удаляем, если есть
if (file.exists(allVmXlsxPath)) {file.remove(allVmXlsxPath)}
#### Забираем данные по всем машинам через PowerShell скрипт. На выходе получим csv.
system(paste0("powershell -File ", getVmPsPath))
# Полный df
fullXslx_df <- allVmXlsxPath %>% 
read.csv2(stringsAsFactors = FALSE)
# Проверяем корректность заполненных полей
full_df <- fullXslx_df %>%
mutate(
# Сначала убираем все лишние пробелы и табуляции, потом учитываем разделитель запятую, потом проверяем вхождение в допустимые значения,
isSubsystemCorrect = Subsystem %>% 
gsub("[[:space:]]", "", .) %>% 
str_split(., ",") %>% 
map(function(x) (all(x %in% AllowedValues$Subsystem))) %>%
as.logical(),
isOwnerCorrect = Owner %in% AllowedValues$Owner,
isCategoryCorrect = Category %in% AllowedValues$Category,
isCreatorCorrect = (!is.na(Creator) & Creator != ''),
isCreation.DateCorrect = map(Creation.Date, IsDate)
)
# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameAll)) {file.remove(filenameAll)}
#### Формируем xslx файл с отчётом ####
# Общие данные на отдельный лист
full_df %>% write.xlsx(file=filenameAll,
sheetName=names[1],
col.names=TRUE,
row.names=FALSE,
append=FALSE)
#### Формируем xslx файл с неправильно заполненными полями ####
# Формируем df
incorrect_df <- full_df %>%
select(VM.Name, 
IP.s, 
Owner,
Subsystem,
Creator,
Category,
Creation.Date,
isOwnerCorrect, 
isSubsystemCorrect, 
isCategoryCorrect,
isCreatorCorrect,
vCenter.Name) %>%
filter(isSubsystemCorrect == F | 
isOwnerCorrect == F |
isCategoryCorrect == F |
isCreatorCorrect == F)
# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}
# Сохраняем список VM с незаполненными полями в csv
incorrect_df %>%
select(VM.Name) %>%
write_csv2(path = filenameIncVM, append = FALSE)
# Фильтруем для вставки в почту
incorrect_df_filtered <- incorrect_df %>% 
select(VM.Name, 
IP.s, 
Owner, 
Subsystem, 
Category,
Creator,
vCenter.Name,
Creation.Date
)
# Считаем количество строк
numberOfRows <- nrow(incorrect_df)
#### Начало условия ####
# Дальше либо у нас есть неправильно заполненные поля, либо нет.
# Если есть - запускаем ещё один скрипт
if (numberOfRows > 0) {
# Проверяем существование файла с создателями и удаляем, если есть.
if (file.exists(creatorsFilePath)) {file.remove(creatorsFilePath)}
# Запускаем PowerShell скрипт, который найдёт создателей найденных VM. На выходе получим csv.
system(paste0("powershell -File ", getCreatorsPath))
# Читаем файл с создателями
creators_df <- creatorsFilePath %>%
read.csv2(stringsAsFactors = FALSE)
# Фильтруем для вставки в почту, добавляем данные из таблицы с создателями
incorrect_df_filtered <- incorrect_df_filtered %>% 
select(VM.Name, 
IP.s, 
Owner, 
Subsystem, 
Category,
Creator,
vCenter.Name,
Creation.Date
) %>% 
left_join(creators_df, by = "VM.Name") %>% 
rename(`Предполагаемый создатель` = CreatedBy, 
`Предполагаемая дата создания` = CreatedOn)  
# Формируем тело письма
emailBody <- paste0(
'<html>
<h3>Добрый день, уважаемые коллеги.</h3>
<p>Полную актуальную информацию по виртуальным машинам вы можете посмотреть на диске H: вот тут:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Также во вложении список ВМ с <strong>некорректно заполненными</strong> полями. Всего их <strong>', numberOfRows,'</strong>.</p>
<p>В таблице появилось 2 дополнительные колонки. <strong>Предполагаемый создатель</strong> и <strong>Предполагаемая дата создания</strong>, которые достаются из логов vCenter за последние 2 недели</p>
<p>Просьба создателей машин уточнить данные и заполнить поля корректно. Правила заполнения полей также во вложении</p>
<p><img src="data/meme.jpg"></p>
</html>'
)
# Проверяем существование файла
if (file.exists(filenameIncorrect)) {file.remove(filenameIncorrect)}
# Формируем красивую таблицу с форматами и т.д.
source(file = "email.R", local = T, encoding = "utf-8")
#### Формируем письмо с плохо подписанными машинами ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "ВМ с некорректно заполненными полями",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
attach.files = c(filenameIncorrect, filenameVmCreationRules),
debug = FALSE)
#### Дальше пойдёт блок, если нет проблем с ВМ ####
} else {
# Формируем тело письма
emailBody <- paste0(
'<html>
<h3>Добрый день, уважаемые коллеги</h3>
<p>Полную актуальную информацию по виртуальным машинам вы можете посмотреть на диске H: вот тут:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Также, на текущий момент, все поля ВМ корректно заполнены</p>
<p><img src="data/meme_correct.jpg"></p>
</html>'
)
#### Формируем письмо без плохо заполненных VM ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "Сводная информация",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
debug = FALSE)
}
####### Записываем данные в БД #####
source(file = "DB.R", local = T, encoding = "utf-8")

სკრიპტი PowerShell-ში vm-ის სიის მისაღებად

# Данные для подключения и другие переменные
$vCenterNames = @(
"vcenter01", 
"vcenter02", 
"vcenter03"
)
$vCenterUsername = "myusername"
$vCenterPassword = "mypassword"
$filename = "C:ScriptsgetVmdataallvmall-vm-$(get-date -f yyyy-MM-dd).csv"
$destinationSMB = "server.rumyfolder$vm"
$IP0=""
$IP1=""
$IP2=""
$IP3=""
$IP4=""
$IP5=""
# Подключение ко всем vCenter, что содержатся в переменной. Будет работать, если логин и пароль одинаковые (например, доменные)
Connect-VIServer -Server $vCenterNames -User $vCenterUsername -Password $vCenterPassword
write-host ""
# Создаём функцию с циклом по всем vCenter-ам
function Get-VMinventory {
# В этой переменной будет списко всех ВМ, как объектов
$AllVM = Get-VM | Sort Name
$cnt = $AllVM.Count
$count = 1
# Начинаем цикл по всем ВМ и собираем необходимые параметры каждого объекта
foreach ($vm in $AllVM) {
$StartTime = $(get-date)
$IP0 = $vm.Guest.IPAddress[0]
$IP1 = $vm.Guest.IPAddress[1]
$IP2 = $vm.Guest.IPAddress[2]
$IP3 = $vm.Guest.IPAddress[3]
$IP4 = $vm.Guest.IPAddress[4]
$IP5 = $vm.Guest.IPAddress[5]
If ($IP0 -ne $null) {If ($IP0.Contains(":") -ne 0) {$IP0=""}}
If ($IP1 -ne $null) {If ($IP1.Contains(":") -ne 0) {$IP1=""}}
If ($IP2 -ne $null) {If ($IP2.Contains(":") -ne 0) {$IP2=""}}
If ($IP3 -ne $null) {If ($IP3.Contains(":") -ne 0) {$IP3=""}}
If ($IP4 -ne $null) {If ($IP4.Contains(":") -ne 0) {$IP4=""}}
If ($IP5 -ne $null) {If ($IP5.Contains(":") -ne 0) {$IP5=""}}
$cluster = $vm | Get-Cluster | Select-Object -ExpandProperty name  
$Bootime = $vm.ExtensionData.Runtime.BootTime
$TotalHDDs = $vm.ProvisionedSpaceGB -as [int]
$CreationDate = $vm.CustomFields.Item("CreationDate") -as [string]
$Creator = $vm.CustomFields.Item("Creator") -as [string]
$Category = $vm.CustomFields.Item("Category") -as [string]
$Owner = $vm.CustomFields.Item("Owner") -as [string]
$Subsystem = $vm.CustomFields.Item("Subsystem") -as [string]
$IPS = $vm.CustomFields.Item("IP") -as [string]
$vCPU = $vm.NumCpu
$CorePerSocket = $vm.ExtensionData.config.hardware.NumCoresPerSocket
$Sockets = $vCPU/$CorePerSocket
$Id = $vm.Id.Split('-')[2] -as [int]
# Собираем все параметры в один объект
$Vmresult = New-Object PSObject
$Vmresult | add-member -MemberType NoteProperty -Name "Id" -Value $Id   
$Vmresult | add-member -MemberType NoteProperty -Name "VM Name" -Value $vm.Name  
$Vmresult | add-member -MemberType NoteProperty -Name "Cluster" -Value $cluster  
$Vmresult | add-member -MemberType NoteProperty -Name "Esxi Host" -Value $VM.VMHost  
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 1" -Value $IP0
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 2" -Value $IP1
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 3" -Value $IP2
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 4" -Value $IP3
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 5" -Value $IP4
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 6" -Value $IP5
$Vmresult | add-member -MemberType NoteProperty -Name "vCPU" -Value $vCPU
$Vmresult | Add-Member -MemberType NoteProperty -Name "CPU Sockets" -Value $Sockets
$Vmresult | Add-Member -MemberType NoteProperty -Name "Core per Socket" -Value $CorePerSocket
$Vmresult | add-member -MemberType NoteProperty -Name "RAM (GB)" -Value $vm.MemoryGB
$Vmresult | add-member -MemberType NoteProperty -Name "Total-HDD (GB)" -Value $TotalHDDs
$Vmresult | add-member -MemberType NoteProperty -Name "Power State" -Value $vm.PowerState
$Vmresult | add-member -MemberType NoteProperty -Name "OS" -Value $VM.ExtensionData.summary.config.guestfullname  
$Vmresult | Add-Member -MemberType NoteProperty -Name "Boot Time" -Value $Bootime
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Status" -Value $vm.ExtensionData.Guest.ToolsStatus  
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Version" -Value $vm.ExtensionData.Guest.ToolsVersion  
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Version Status" -Value $vm.ExtensionData.Guest.ToolsVersionStatus  
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Running Status" -Value $vm.ExtensionData.Guest.ToolsRunningStatus  
$Vmresult | add-member -MemberType NoteProperty -Name "Creation Date" -Value $CreationDate
$Vmresult | add-member -MemberType NoteProperty -Name "Creator" -Value $Creator
$Vmresult | add-member -MemberType NoteProperty -Name "Category" -Value $Category
$Vmresult | add-member -MemberType NoteProperty -Name "Owner" -Value $Owner
$Vmresult | add-member -MemberType NoteProperty -Name "Subsystem" -Value $Subsystem
$Vmresult | add-member -MemberType NoteProperty -Name "IP's" -Value $IPS
$Vmresult | add-member -MemberType NoteProperty -Name "vCenter Name" -Value $vm.Uid.Split('@')[1].Split(':')[0]  
# Считаем общее и оставшееся время выполнения и выводим на экран результаты. Использовалось для тестирования, но по факту оказалось очень удобно.
$elapsedTime = $(get-date) - $StartTime
$totalTime = "{0:HH:mm:ss}" -f ([datetime]($elapsedTime.Ticks*($cnt - $count)))
clear-host
Write-Host "Processing" $count "from" $cnt 
Write-host "Progress:" ([math]::Round($count/$cnt*100, 2)) "%" 
Write-host "You have about " $totalTime "for cofee"
Write-host ""
$count++
# Выводим результат, чтобы цикл "знал" что является результатом выполнения одного прохода
$Vmresult
}
}
# Вызываем получившуюся функцию и сразу выгружаем результат в csv
$allVm = Get-VMinventory | Export-CSV -Path $filename -NoTypeInformation -UseCulture -Force
# Пытаемся выложить полученный файл в нужное нам место и, в случае ошибки, пишем лог.
try
{
Copy-Item $filename -Destination $destinationSMB -Force -ErrorAction SilentlyContinue
}
catch
{
$error | Export-CSV -Path $filename".error" -NoTypeInformation -UseCulture -Force
}

PowerShell სკრიპტი, რომელიც ამოიღებს ჟურნალებიდან ვირტუალური მანქანების შემქმნელებს და მათი შექმნის თარიღს

# Путь к файлу, из которого будем доставать список VM
$VMfilePath = "C:ScriptsgetVmcreators_VMcreators_VM_$(get-date -f yyyy-MM-dd).csv"
# Путь к файлу, в который будем записывать результат
$filePath = "C:ScriptsgetVmdatacreatorscreators-$(get-date -f yyyy-MM-dd).csv"
# Создаём вокрфлоу
Workflow GetCreators-Wf
{
# Параметры, которые можно будет передать при вызове скрипта
param([string[]]$VMfilePath)
# Параметры, которые доступны только внутри workflow
$vCenterUsername = "myusername"
$vCenterPassword = "mypassword"
$daysToLook = 14
$start = (get-date).AddDays(-$daysToLook)
$finish = get-date
# Значения, которые будут вписаны в csv для машин, по которым не будет ничего найдено
$UnknownUser = "UNKNOWN"
$UnknownCreatedTime = "0000-00-00"
# Определяем параметры подключения и выводной файл, которые будут доступны во всём скрипте.
$vCenterNames = @(
"vcenter01", 
"vcenter02", 
"vcenter03"
)
# Получаем список VM из csv и загружаем соответствующие объекты
$list = Import-Csv $VMfilePath -UseCulture | select -ExpandProperty VM.Name
# Цикл, который будет выполняться параллельно (по 5 машин за раз)
foreach -parallel ($row in $list)
{
# Это скрипт, который видит только свои переменные и те, которые ему переданы через $Using
InlineScript {
# Время начала выполнения отдельного блока
$StartTime = $(get-date)
Write-Host ""
Write-Host "Processing $Using:row started at $StartTime"
Write-Host ""
# Подключение оборачиваем в переменную, чтобы информация о нём не мешалась в консоли
$con = Connect-VIServer -Server $Using:vCenterNames -User $Using:vCenterUsername -Password $Using:vCenterPassword
# Получаем объект vm
$vm = Get-VM -Name $Using:row
# Ниже 2 одинаковые команды. Одна с фильтром по времени, вторая - без. Можно пользоваться тем,
$Event = $vm | Get-VIEvent -Start $Using:start -Finish $Using:finish -Types Info | Where { $_.Gettype().Name -eq "VmBeingDeployedEvent" -or $_.Gettype().Name -eq "VmCreatedEvent" -or $_.Gettype().Name -eq "VmRegisteredEvent" -or $_.Gettype().Name -eq "VmClonedEvent"}
# $Event = $vm | Get-VIEvent -Types Info | Where { $_.Gettype().Name -eq "VmBeingDeployedEvent" -or $_.Gettype().Name -eq "VmCreatedEvent" -or $_.Gettype().Name -eq "VmRegisteredEvent" -or $_.Gettype().Name -eq "VmClonedEvent"}
# Заполняем параметры в зависимости от того, удалось ли в логах найти что-то
If (($Event | Measure-Object).Count -eq 0){
$User = $Using:UnknownUser
$Created = $Using:UnknownCreatedTime
$CreatedFormat = $Using:UnknownCreatedTime
} Else {
If ($Event.Username -eq "" -or $Event.Username -eq $null) {
$User = $Using:UnknownUser
} Else {
$User = $Event.Username
} # Else
$CreatedFormat = $Event.CreatedTime
# Один из коллег отдельно просил, чтобы время было в таком формате, поэтому дублируем его. А в БД пойдёт нормальный формат.
$Created = $Event.CreatedTime.ToString('yyyy-MM-dd')
} # Else
Write-Host "Creator for $vm is $User. Creating object."
# Создаём объект. Добавляем параметры.
$Vmresult = New-Object PSObject
$Vmresult | add-member -MemberType NoteProperty -Name "VM Name" -Value $vm.Name  
$Vmresult | add-member -MemberType NoteProperty -Name "CreatedBy" -Value $User
$Vmresult | add-member -MemberType NoteProperty -Name "CreatedOn" -Value $CreatedFormat
$Vmresult | add-member -MemberType NoteProperty -Name "CreatedOnFormat" -Value $Created           
# Выводим результаты
$Vmresult
} # Inline
} # ForEach
}
$Creators = GetCreators-Wf $VMfilePath
# Записываем результат в файл
$Creators | select 'VM Name', CreatedBy, CreatedOn | Export-Csv -Path $filePath -NoTypeInformation -UseCulture -Force
Write-Host "CSV generetion finisghed at $(get-date). PROFIT"

ბიბლიოთეკა განსაკუთრებულ ყურადღებას იმსახურებს xlsx, რამაც შესაძლებელი გახადა ასოზე დანართი მკაფიოდ დაფორმატებული ყოფილიყო (როგორც მენეჯმენტს მოსწონს) და არა მხოლოდ CSV ცხრილი.

ლამაზი xlsx დოკუმენტის გენერირება არასწორად შევსებული მანქანების სიით

# Создаём новую книгу
# Возможные значения : "xls" и "xlsx"
wb<-createWorkbook(type="xlsx")
# Стили для имён рядов и колонок в таблицах
TABLE_ROWNAMES_STYLE <- CellStyle(wb) + Font(wb, isBold=TRUE)
TABLE_COLNAMES_STYLE <- CellStyle(wb) + Font(wb, isBold=TRUE) +
Alignment(wrapText=TRUE, horizontal="ALIGN_CENTER") +
Border(color="black", position=c("TOP", "BOTTOM"), 
pen=c("BORDER_THIN", "BORDER_THICK"))
# Создаём новый лист
sheet <- createSheet(wb, sheetName = names[2])
# Добавляем таблицу
addDataFrame(incorrect_df_filtered, 
sheet, startRow=1, startColumn=1,  row.names=FALSE, byrow=FALSE,
colnamesStyle = TABLE_COLNAMES_STYLE,
rownamesStyle = TABLE_ROWNAMES_STYLE)
# Меняем ширину, чтобы форматирование было автоматическим
autoSizeColumn(sheet = sheet, colIndex=c(1:ncol(incorrect_df)))
# Добавляем фильтры
addAutoFilter(sheet, cellRange = "C1:G1")
# Определяем стиль
fo2 <- Fill(foregroundColor="red")
cs2 <- CellStyle(wb, 
fill = fo2, 
dataFormat = DataFormat("@"))
# Находим ряды с неверно заполненным полем Владельца и применяем к ним определённый стиль
rowsOwner <- getRows(sheet, rowIndex = (which(!incorrect_df$isOwnerCorrect) + 1))
cellsOwner <- getCells(rowsOwner, colIndex = which( colnames(incorrect_df_filtered) == "Owner" )) 
lapply(names(cellsOwner), function(x) setCellStyle(cellsOwner[[x]], cs2))
# Находим ряды с неверно заполненным полем Подсистемы и применяем к ним определённый стиль
rowsSubsystem <- getRows(sheet, rowIndex = (which(!incorrect_df$isSubsystemCorrect) + 1))
cellsSubsystem <- getCells(rowsSubsystem, colIndex = which( colnames(incorrect_df_filtered) == "Subsystem" )) 
lapply(names(cellsSubsystem), function(x) setCellStyle(cellsSubsystem[[x]], cs2))
# Аналогично по Категории
rowsCategory <- getRows(sheet, rowIndex = (which(!incorrect_df$isCategoryCorrect) + 1))
cellsCategory <- getCells(rowsCategory, colIndex = which( colnames(incorrect_df_filtered) == "Category" )) 
lapply(names(cellsCategory), function(x) setCellStyle(cellsCategory[[x]], cs2))
# Создатель
rowsCreator <- getRows(sheet, rowIndex = (which(!incorrect_df$isCreatorCorrect) + 1))
cellsCreator <- getCells(rowsCreator, colIndex = which( colnames(incorrect_df_filtered) == "Creator" )) 
lapply(names(cellsCreator), function(x) setCellStyle(cellsCreator[[x]], cs2))
# Сохраняем файл
saveWorkbook(wb, filenameIncorrect)

გამომავალი ასე გამოიყურება:

ყოველდღიური ანგარიშები ვირტუალური მანქანების ჯანმრთელობის შესახებ R და PowerShell-ის გამოყენებით

ასევე იყო საინტერესო ნიუანსი Windows-ის გრაფიკის დაყენების შესახებ. შეუძლებელი იყო სწორი უფლებებისა და პარამეტრების პოვნა, რათა ყველაფერი ისე დაწყებულიყო, როგორც უნდა. შედეგად, ნაპოვნი იქნა R ბიბლიოთეკა, რომელიც თავად ქმნის დავალებას R სკრიპტის გასაშვებად და არც კი ივიწყებს ჟურნალის ფაილს. შემდეგ შეგიძლიათ ხელით შეასწოროთ დავალება.

R კოდის ნაწილი ორი მაგალითით, რომელიც ქმნის დავალებას Windows Scheduler-ში

library(taskscheduleR)
myscript <- file.path(getwd(), "all_vm.R")
## запускаем скрипт через 62 секунды
taskscheduler_create(taskname = "getAllVm", rscript = myscript, 
schedule = "ONCE", starttime = format(Sys.time() + 62, "%H:%M"))
## запускаем скрипт каждый день в 09:10
taskscheduler_create(taskname = "getAllVmDaily", rscript = myscript, 
schedule = "WEEKLY", 
days = c("MON", "TUE", "WED", "THU", "FRI"),
starttime = "02:00")
## удаляем задачи
taskscheduler_delete(taskname = "getAllVm")
taskscheduler_delete(taskname = "getAllVmDaily")
# Смотрим логи (последние 4 строчки)
tail(readLines("all_vm.log"), sep ="n", n = 4)

ცალკე მონაცემთა ბაზის შესახებ

სკრიპტის დაყენების შემდეგ, სხვა პრობლემები გამოჩნდა. მაგალითად, მინდოდა მეპოვა VM-ის წაშლის თარიღი, მაგრამ vCenter-ის ჟურნალები უკვე გაცვეთილი იყო. ვინაიდან სკრიპტი ყოველდღიურად ათავსებს ფაილებს საქაღალდეში და არ ასუფთავებს მას (ჩვენ მას ხელით ვასუფთავებთ, როცა გვახსოვს), შეგიძლიათ გადახედოთ ძველ ფაილებს და იპოვოთ პირველი ფაილი, რომელშიც ეს VM არ არის. მაგრამ ეს არ არის მაგარი.

მინდოდა ისტორიული მონაცემთა ბაზის შექმნა.

სამაშველოში მოვიდა MS SQL SERVER-ის ფუნქციონალობა - სისტემური ვერსიით დროებითი ცხრილი. ის ჩვეულებრივ ითარგმნება როგორც დროებითი (არა დროებითი) ცხრილები.

დეტალურად შეგიძლიათ წაიკითხოთ აქ Microsoft-ის ოფიციალური დოკუმენტაცია.

მოკლედ, ჩვენ ვქმნით ცხრილს, ვამბობთ, რომ გვექნება ვერსიით, და SQL Server ქმნის 2 დამატებით თარიღის დროის სვეტს ამ ცხრილში (ჩანაწერის შექმნის თარიღი და ჩანაწერის ვადის გასვლის თარიღი) და დამატებით ცხრილს, რომელშიც იცვლება. დაიწერება. შედეგად, ჩვენ ვიღებთ განახლებულ ინფორმაციას და მარტივი მოთხოვნების საშუალებით, რომელთა მაგალითები მოცემულია დოკუმენტაციაში, ჩვენ შეგვიძლია დავინახოთ ან კონკრეტული ვირტუალური მანქანის სასიცოცხლო ციკლი, ან ყველა VM-ის მდგომარეობა გარკვეულ მომენტში. დროზე.

შესრულების პერსპექტივიდან, მთავარ ცხრილში ჩაწერის ტრანზაქცია არ დასრულდება მანამ, სანამ დროებით ცხრილში ჩაწერის ტრანზაქცია არ დასრულდება. იმათ. მაგიდებზე, სადაც ჩაწერის ოპერაციების დიდი რაოდენობაა, ეს ფუნქცია სიფრთხილით უნდა განხორციელდეს, მაგრამ ჩვენს შემთხვევაში ეს მართლაც მაგარი რამეა.

იმისათვის, რომ მექანიზმმა სწორად იმუშაოს, მე უნდა დავამატო კოდის პატარა ნაწილი R-ში, რომელიც შევადარებ ახალ ცხრილს ყველა VM-ის მონაცემებთან მონაცემთა ბაზაში შენახულთან და მასში მხოლოდ შეცვლილ სტრიქონებს დავწერ. კოდი არ არის განსაკუთრებით ჭკვიანი, ის იყენებს compareDF ბიბლიოთეკას, მაგრამ მას ასევე ქვემოთ წარმოგიდგენთ.

R კოდი მონაცემთა ბაზაში ჩასაწერად

# Подцепляем пакеты
library(odbc)
library(compareDF)
# Формируем коннект
con <- dbConnect(odbc(),
Driver = "ODBC Driver 13 for SQL Server",
Server = DBParams$server,
Database = DBParams$database,
UID = DBParams$UID,
PWD = DBParams$PWD,
Port = 1433)
#### Проверяем есть ли таблица. Если нет - создаём. ####
if (!dbExistsTable(con, DBParams$TblName)) {
#### Создаём таблицу ####
create <- dbSendStatement(
con,
paste0(
'CREATE TABLE ',
DBParams$TblName,
'(
[Id] [int] NOT NULL PRIMARY KEY CLUSTERED,
[VM.Name] [varchar](255) NULL,
[Cluster] [varchar](255) NULL,
[Esxi.Host] [varchar](255) NULL,
[IP.Address.1] [varchar](255) NULL,
[IP.Address.2] [varchar](255) NULL,
[IP.Address.3] [varchar](255) NULL,
[IP.Address.4] [varchar](255) NULL,
[IP.Address.5] [varchar](255) NULL,
[IP.Address.6] [varchar](255) NULL,
[vCPU] [int] NULL,
[CPU.Sockets] [int] NULL,
[Core.per.Socket] [int] NULL,
[RAM..GB.] [int] NULL,
[Total.HDD..GB.] [int] NULL,
[Power.State] [varchar](255) NULL,
[OS] [varchar](255) NULL,
[Boot.Time] [varchar](255) NULL,
[VMTools.Status] [varchar](255) NULL,
[VMTools.Version] [int] NULL,
[VMTools.Version.Status] [varchar](255) NULL,
[VMTools.Running.Status] [varchar](255) NULL,
[Creation.Date] [varchar](255) NULL,
[Creator] [varchar](255) NULL,
[Category] [varchar](255) NULL,
[Owner] [varchar](255) NULL,
[Subsystem] [varchar](255) NULL,
[IP.s] [varchar](255) NULL,
[vCenter.Name] [varchar](255) NULL,
DateFrom datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
DateTo datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (DateFrom, DateTo)
) ON [PRIMARY]
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ', DBParams$TblHistName,'));'
)
)
# Отправляем подготовленный запрос
dbClearResult(create)
} # if
#### Начало работы с таблицей ####
# Обозначаем таблицу, с которой будем работать
allVM_db_con <- tbl(con, DBParams$TblName) 
#### Сравниваем таблицы ####
# Собираем данные с таблицы (убираем служебные временные поля)
allVM_db <- allVM_db_con %>% 
select(c(-"DateTo", -"DateFrom")) %>% 
collect()
# Создаём таблицу со сравнением объектов. Сравниваем по Id
# Удалённые объекты там будут помечены через -, созданные через +, изменённые через - и +
ctable_VM <- fullXslx_df %>% 
compare_df(allVM_db, 
c("Id"))
#### Удаление строк ####
# Выдираем Id виртуалок, записи о которых надо удалить 
remove_Id <- ctable_VM$comparison_df %>% 
filter(chng_type == "-") %>%
select(Id)
# Проверяем, что есть записи (если записей нет - и удалять ничего не нужно)
if (remove_Id %>% nrow() > 0) {
# Конструируем шаблон для запроса на удаление данных
delete <- dbSendStatement(con, 
paste0('
DELETE 
FROM ',
DBParams$TblName,
' WHERE "Id"=?
') # paste
) # send
# Создаём запрос на удаление данных
dbBind(delete, remove_Id)
# Отправляем подготовленный запрос
dbClearResult(delete)
} # if
#### Добавление строк ####
# Выделяем таблицу, содержащую строки, которые нужно добавить.
allVM_add <- ctable_VM$comparison_df %>% 
filter(chng_type == "+") %>% 
select(-chng_type)
# Проверяем, есть ли строки, которые нужно добавить и добавляем (если нет - не добавляем)
if (allVM_add %>% nrow() > 0) {
# Пишем таблицу со всеми необходимыми данными
dbWriteTable(con,
DBParams$TblName,
allVM_add,
overwrite = FALSE,
append = TRUE)
} # if
#### Не забываем сделать дисконнект ####
dbDisconnect(con)

საერთო ჯამში

სცენარის განხორციელების შედეგად რამდენიმე თვეში წესრიგი აღდგა და შენარჩუნდა. ზოგჯერ ჩნდება არასწორად შევსებული VM-ები, მაგრამ სკრიპტი კარგი შეხსენების ფუნქციაა და იშვიათი VM 2 დღე ზედიზედ ხვდება სიაში.

ისტორიული მონაცემების გაანალიზებასაც საფუძველი ჩაუყარა.

გასაგებია, რომ ამის დიდი ნაწილი შეიძლება განხორციელდეს არა მუხლზე, არამედ სპეციალიზებული პროგრამული უზრუნველყოფით, მაგრამ დავალება საინტერესო იყო და, შეიძლება ითქვას, სურვილისამებრ.

R-მ კიდევ ერთხელ აჩვენა, რომ არის შესანიშნავი უნივერსალური ენა, რომელიც შესანიშნავია არა მხოლოდ სტატისტიკური პრობლემების გადასაჭრელად, არამედ ასევე მოქმედებს როგორც შესანიშნავი „ფენა“ მონაცემთა სხვა წყაროებს შორის.

ყოველდღიური ანგარიშები ვირტუალური მანქანების ჯანმრთელობის შესახებ R და PowerShell-ის გამოყენებით

წყარო: www.habr.com

ახალი კომენტარის დამატება