Καθημερινές αναφορές για την υγεία των εικονικών μηχανών που χρησιμοποιούν R και PowerShell

Καθημερινές αναφορές για την υγεία των εικονικών μηχανών που χρησιμοποιούν R και PowerShell

Είσοδος

Καλό απόγευμα. Εδώ και μισό χρόνο τρέχουμε ένα σενάριο (ή μάλλον ένα σύνολο σεναρίων) που δημιουργεί αναφορές για την κατάσταση των εικονικών μηχανών (και όχι μόνο). Αποφάσισα να μοιραστώ την εμπειρία δημιουργίας μου και τον ίδιο τον κώδικα. Ελπίζω για κριτική και ότι αυτό το υλικό μπορεί να είναι χρήσιμο σε κάποιον.

Διαμόρφωση ανάγκης

Έχουμε πολλές εικονικές μηχανές (περίπου 1500 VM κατανεμημένες σε 3 vCenters). Δημιουργούνται καινούργια και τα παλιά διαγράφονται αρκετά συχνά. Για να διατηρηθεί η τάξη, πολλά προσαρμοσμένα πεδία προστέθηκαν στο vCenter για να χωριστούν τα VM σε Υποσυστήματα, να υποδειχθεί εάν είναι δοκιμαστικά και από ποιον και πότε δημιουργήθηκαν. Ο ανθρώπινος παράγοντας οδήγησε στο γεγονός ότι περισσότερες από τις μισές μηχανές έμειναν με κενά πεδία, γεγονός που περιέπλεξε τη δουλειά. Μία φορά κάθε έξι μήνες, κάποιος φρίκαρε και άρχισε να εργάζεται για την ενημέρωση αυτών των δεδομένων, αλλά το αποτέλεσμα έπαψε να είναι σχετικό μετά από μιάμιση εβδομάδα.
Να διευκρινίσω αμέσως ότι όλοι καταλαβαίνουν ότι πρέπει να υπάρχουν αιτήσεις για τη δημιουργία μηχανών, διαδικασία δημιουργίας τους κ.λπ. και ούτω καθεξής. Και ταυτόχρονα όλοι ακολουθούν αυστηρά αυτή τη διαδικασία και όλα είναι εντάξει. Δυστυχώς, αυτό δεν συμβαίνει εδώ, αλλά δεν είναι αυτό το θέμα του άρθρου :)

Γενικά, πάρθηκε η απόφαση να αυτοματοποιηθεί ο έλεγχος της ορθότητας της συμπλήρωσης των πεδίων.
Αποφασίσαμε ότι μια καθημερινή επιστολή με μια λίστα λανθασμένα συμπληρωμένων μηχανημάτων προς όλους τους υπεύθυνους μηχανικούς και τα αφεντικά τους θα ήταν μια καλή αρχή.

Σε αυτό το σημείο, ένας από τους συναδέλφους μου είχε ήδη εφαρμόσει ένα σενάριο στο PowerShell, το οποίο κάθε μέρα, σύμφωνα με ένα χρονοδιάγραμμα, συνέλεγε πληροφορίες για όλα τα μηχανήματα όλων των vCenters και δημιουργούσε 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 που προκύπτει σε ένα άλλο σενάριο PowerShell, το οποίο θα εξετάσει τα αρχεία καταγραφής του vCenter για συμβάντα δημιουργίας VM, τα οποία θα μας επιτρέψουν να υποδείξουμε τον εκτιμώμενο χρόνο δημιουργίας του VM και τον προβλεπόμενο δημιουργό. Αυτό ισχύει για την περίπτωση που κανείς δεν παραδέχεται ποιανού είναι το αυτοκίνητο. Αυτό το σενάριο δεν λειτουργεί γρήγορα, ειδικά αν υπάρχουν πολλά αρχεία καταγραφής, επομένως εξετάζουμε μόνο τις τελευταίες 2 εβδομάδες και χρησιμοποιούμε επίσης μια ροή εργασίας που σας επιτρέπει να αναζητάτε πληροφορίες για πολλά εικονικά μηχανήματα ταυτόχρονα. Το παράδειγμα σεναρίου περιέχει λεπτομερή σχόλια για αυτόν τον μηχανισμό. Προσθέτουμε το αποτέλεσμα σε ένα csv, το οποίο και πάλι φορτώνουμε στο R.
  • Δημιουργούμε ένα όμορφα μορφοποιημένο έγγραφο xlsx στο οποίο θα επισημαίνονται με κόκκινο χρώμα πεδία που δεν έχουν συμπληρωθεί, θα εφαρμοστούν φίλτρα σε ορισμένες στήλες και θα υποδεικνύονται πρόσθετες στήλες που περιέχουν τους προβλεπόμενους δημιουργούς και την ώρα δημιουργίας του VM.
  • Δημιουργούμε ένα email, όπου επισυνάπτουμε ένα έγγραφο που περιγράφει τις έγκυρες τιμές πεδίων, καθώς και έναν πίνακα με πεδία που έχουν συμπληρωθεί σωστά. Στο κείμενο υποδεικνύουμε τον συνολικό αριθμό των εσφαλμένα δημιουργημένων εικονικών μηχανών, έναν σύνδεσμο προς έναν κοινόχρηστο πόρο και μια εικόνα κινήτρων. Εάν δεν υπάρχουν λανθασμένα συμπληρωμένα VM, στέλνουμε άλλη μια επιστολή με μια πιο χαρούμενη κινητήρια εικόνα.
  • Καταγράφουμε δεδομένα για όλα τα 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")

Σενάριο για τη λήψη μιας λίστας vm στο PowerShell

# Данные для подключения и другие переменные
$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

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)

Ξεχωριστά για τη βάση δεδομένων

Μετά τη ρύθμιση του σεναρίου, άρχισαν να εμφανίζονται άλλα ζητήματα. Για παράδειγμα, ήθελα να βρω την ημερομηνία κατά την οποία διαγράφηκε η εικονική μηχανή, αλλά τα αρχεία καταγραφής στο 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 μπαίνει στη λίστα για 2 συνεχόμενες ημέρες.

Έγινε επίσης βάση για την ανάλυση ιστορικών δεδομένων.

Είναι σαφές ότι πολλά από αυτά μπορούν να εφαρμοστούν όχι στο γόνατο, αλλά με εξειδικευμένο λογισμικό, αλλά η εργασία ήταν ενδιαφέρουσα και, θα έλεγε κανείς, προαιρετική.

Η R αποδείχθηκε για άλλη μια φορά ότι είναι μια εξαιρετική καθολική γλώσσα, η οποία είναι τέλεια όχι μόνο για την επίλυση στατιστικών προβλημάτων, αλλά λειτουργεί και ως εξαιρετικό «στρώμα» μεταξύ άλλων πηγών δεδομένων.

Καθημερινές αναφορές για την υγεία των εικονικών μηχανών που χρησιμοποιούν R και PowerShell

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο