Կարող եք ամեն օր ստանալ վիրտուալ մեքենաների վիճակի զեկույցներ R եւ PowerShell-ի միջոցով

Կարող եք ամեն օր ստանալ վիրտուալ մեքենաների վիճակի զեկույցներ R եւ PowerShell-ի միջոցով

Ներածություն

Բարի օր։ Հինգերորդ ամիսն է, ինչ մեզ մոտ գործում է սկրիպտ (նշանակում է հավաքածու սկրիպտների), որը ստեղծում է վիրտուալ մեքենաների (եւ ոչ միայն) վիճակի զեկույցներ։ Սրա մասին փորձՈւնենալն եմ ցանկանում կիսել, ինչպես նաեւ ինքնուրույն կոդը։ Հույս ունեմ, որ այս նյութը կլինի օգտակար:

Պահանջի ձևավորում

Մենք ունենք շատ վիրտուալ մեքենաներ (մոտ 1500 ՎՄ, որը բաժանված է 3 vCenter-ում)։ Նորերը վերածվում են, իսկ հիները թվարկվում են շատ հաճախ։ Ինպես որ, կարգը պահպանելու համար vCenter-ում մի քանի կարգային դաշտեր ավելացվել են, որպեսզի բաժանեն ՎՄ-ները ենթահամակարգերի, նշեն՝ արդյոք դրանք փորձնական են, ինչպես նաեւ ով եւ երբ է դրանք ստեղծել։ Մարդու գործոնը հանգեցրել է նրան, որ ավելի քան կեսը մեքենաների մնացել են անծանոթ դաշտերով, ինչը դժվարացրել է աշխատանքը։ Հինգ ամիսը մեկ ինչ-որ մեկը հուզվում էր եւ սկսում էր այս տվյալների արդիականացման աշխատանքին, բայց արդյունքը դառնում էր անօգտակար արդեն երկու շաբաթից հետո։
Ավելի վաղ կասեմ, որ բոլորը հասկանում են, որ պետք է լինեն հայտեր մեքենաների ստեղծման համար, գործընթացը դրանց ստեղծման եւ այլն ու այդպիսին է։ Եվ հենց այդ ժամանակ բոլորը օժտված են այս գործընթացին հետեւելու եւ ամեն ինչում կարգ ունենալ։ Ահա, ցավոք, մեզ մոտ այդպես չէ, բայց սա հոդվածի նյութ չէ 🙂

Ընդհանուր առմամբ, որոշել ենք ավտոմատացնել դաշտերի ճիշտ լրացման ստուգումը։
Սահմանեցինք, որ ամենօրյա նամակը սխալ լրացված մեքենաների ցուցակով բոլոր պատասխանատու ինժեներներին եւ նրանց ղեկավարներին կլինի լավ սկիզբ։

Այս պահին մի կոլեգա արդեն ներմուծել էր PowerShell-ի սկրիպտ, որը ամեն օր ժամանակացույցով հավաքում էր իրազեկումները բոլոր մեքենաների մասին բոլոր vCenter-ներում եւ ստեղծում էր 3 csv փաստաթուղթ (յուրաքանչյուրն իր vCenter-ի համար), որոնք տեղադրվում էին ընդհանուր սկավառակի վրա։ Որոշվել է, որ այս սկրիպտը վերցնենք հիմք եւ լրացնենք ստուգումներով R ծրագրի միջոցով, որի հետ որոշակի փորձ ունեինք։

Լրացման գործընթացում որոշումը ապագա հաղորդագրություններն արեց էլեկտրոնային նամակով, տվյալների բազային հիմնական եւ պատմական աղյուսակների (երիտասարդ հետագայում), ինչպես նաեւ vSphere-ի օրագրերի վերլուծության միջոցով vm-ների իրական ստեղծողներին եւ նրանց ստեղծման ժամանակը մանավանջելու համար։

Ավարտման համար օգտագործվել են RStudio Desktop IDE եւ PowerShell ISE։

Սկրիպտը запускается с обычной виндовой виртуальной машины。

Ընդհանուր տրամաբանության նկարագրություն։

Սկրիպտների ընդհանուր տրամաբանությունը հետևյալն է։

  • PowerShell-ի սկրիպտի միջոցով հավաքում ենք տվյալներ վիրտուալ մեքենաների մասին, որը մենք դնում ենք R-ում, արդյունքը միացնում ենք մեկ csv-ում։ Երկկողմ փոխգործակցությւոնն իրականացվում է նմանությամբ։ (Նկարելով տվյալները անմիջապես R-ից PowerShell-ում որպես փոփոխականներ, բայց սա դժվար է, իսկ միջանկյալ csv-ները հեշտացնում են դեբագը եւ միջանկյալ արդյունքներ բաժանելու համար ավելի հեշտ է)
  • R-ի միջոցով ձևավորում ենք թույլատրելի պարամետրեր այն կետերի համար, որոնց արժեքները ստուգում ենք։ — Բառացի փաստաթուղթ ենք կազմում, որն ունենալու է այդ կետերի արժեքները՝ ավելացնելու համար տեղեկատվական նամակում, որը պատասխան է գործընկերների հարցերին «Բայց ես սա ինչպես պետք է լրացնեմ?»։
  • Բեռնում ենք բոլոր ՎՄ կայքերի տվյալները csv–ից R-ի միջոցով, ձևավորում dataframe, հեռացնում ավելորդ կետերը և ձևավորում տեղեկատվական xlsx փաստաթուղթ, որը պարունակում է բոլոր ՎՄ-ի ամփոփ տեղեկատվությունը, որը հրապարակում ենք ընդհանուր ռեսուրսում։
  • Այդ dataframe-ի նկատմամբ կիրառվում են բոլոր ստուգումները՝ կետերի ճիշտ լրացման համար, և ձևավորվում է աղյուսակ, որը պարունակում է միայն դրանք ՎՄ-ները, որոնցում զգուշացվում են լրացված կետերը (և միայն այդ կետերը)։
  • Ստացված ՎՄ ցուցակը ուղարկում ենք մեկ այլ PowerShell սցենարով, որը ստուգում է vCenter-ի լոգերում ՎՄ ստեղծման իրադարձությունները, ինչը թույլ է տալիս նշել հնարավոր ստեղծման ժամանակն ու հնարավոր ստեղծողին։ Իսկ այսպես, երբ ոչ մեկը համարձակվում է խոստովանել, թե ում մեքենան է։ Այս սցենարն աշխատում է ռեալ ժամանակում, հատկապես, եթե շատ է լոգերը, այդ իսկ պատճառով դիտում ենք միայն վերջին 2 շաբաթների ընթացքում՝ օգտագործելով workflow, որը թույլ է տալիս տեղեկատվություն որոնել մի քանի ՎՄ-ների համար։ Սցենարի օրինակն ունի մանրամասները այս մեխանիզմի վերաբերյալ։ Արդյունքը վկայականում ձևավորում ենք csv, որն էլի բեռնում ենք R-ի մեջ։
  • Մասնագիտական ձևավորումով xlsx փաստաթուղթ ենք կազմում, որտեղ կարմիր գույնով առանձնացված են սխալ լրացված կետերը, որոշ սյունակների համար կիրառված են զտիչներ, ինչպես նաև նշված են լրացուցիչ սյունակներ, որոնք պարունակում են հնարավոր ստեղծողներին և ՎՄ ստեղծման ժամանակը։
  • Փաստաթղթի էլեկտրոնային նամակ ենք ձևավորում, ուր կցում ենք փաստաթուղթ, որը նկարագրում է թույլատրելի արժեքների կետերը, ինչպես նաև աղյուսակ սխալ լրացված ՎՄ-ներին։ Տեքստում նշում ենք սխալ ստեղծված ՎՄ-ի ընդհանուր քանակը, հղումը ընդհանուր ռեսուրսին և իրագործվող կահավորված պատկեր։ Եթե սխալ լրացված ՎՄ-ներ չկան, ուղարկում ենք այլ նամակ՝ ավելի ուրախ մոնիթորային պատկերով։
  • ՎՄ-ների տվյալները записываем в БД 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 %&gt;% 
      gsub("[[:space:]]", "", .) %&gt;% 
      str_split(., ",") %&gt;% 
      map(function(x) (all(x %in% AllowedValues$Subsystem))) %&gt;%
      as.logical(),
    isOwnerCorrect = Owner %in% AllowedValues$Owner,
    isCategoryCorrect = Category %in% AllowedValues$Category,
    isCreatorCorrect = (!is.na(Creator) &amp; Creator != ''),
    isCreation.DateCorrect = map(Creation.Date, IsDate)
  )

# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameAll)) {file.remove(filenameAll)}

#### Формируем xslx файл с отчётом ####
# Общие данные на отдельный лист
full_df %&gt;% 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) %&gt;%
  filter(isSubsystemCorrect == F | 
           isOwnerCorrect == F |
           isCategoryCorrect == F |
           isCreatorCorrect == F)

# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}

# Сохраняем список VM с незаполненными полями в csv
incorrect_df %&gt;%
  select(VM.Name) %&gt;%
  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
    ) %&gt;% 
    left_join(creators_df, by = "VM.Name") %&gt;% 
    rename(`Предполагаемый создатель` = CreatedBy, 
           `Предполагаемая дата создания` = CreatedOn)  

  # Формируем тело письма
  emailBody &lt;- paste0(
    &#039;<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>'
  )

  # Проверяем файл առկայություն
  եթե (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 &lt;- paste0(
    &#039;<html>
    <h3>Բարև, հարգելի գործընկերներ</h3>
   <p>Վիրտուալ մեքենաների ամբողջական և արդիական տեղեկատվությունը կարող եք տեսնել H: դիսկի վրա, այստեղ:<p>
    <p>\server.ruVM', sourceFileFormat, '</p>
    <p>Իրատեսություն, այս պահին բոլոր ՎՄ դաշտերը կորպուսացված են</p>
    <p><img src="data/meme_correct.jpg"></p>
    </html>'
  )

  #### Ստեղծում ենք նամակ առանց վատ լրացված ՎՄ-ների ####
  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)
}

####### Ներմուծում ենք տվյալները DB #####

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)

Արևելքի արդյունքն է մոտավոր así:

Կարող եք ամեն օր ստանալ վիրտուալ մեքենաների վիճակի զեկույցներ 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)

Վերահսկիր տվյալների բազայի մասին

Սկрипտի կարգավորելուց հետո սկսեցին երևալ այլ հարցեր: Իմաստավորել էր ստանալ օրը, երբ VM-ն հեռացվել է, իսկ vCenter-ում օրագրերը դիմում են: Քանի որ սկрипտը ֆայլերը հավաքում է մարմնամարզական օրը և չի մաքրում (մաքրում ենք ձեռքով, երբ հիշում ենք), կարելի է նայել հին ֆայլերը և գտնել առաջին ֆայլը, որտեղ տվյալ VM-ը չկա: Բայց սա լավ չէ:

Ուղղակի ցանկացա ստեղծել պատմական տվյալների բազա:

Օգնության եկավ MS SQL SERVER-ի գործառույթը` system-versioned temporal table: Այս բանաձևը սովորաբար թարգմանվում է որպես ժամանակային (ոչ ժամանակային) աղյուսակ:

Կարող եք մանրամասն կարդալ Microsoft-ի պաշտոնական փաստաթղթում.

Եթե պակաս, մենք ստեղծում ենք աղյուսակ, ասում ենք, որ لدينا verjiness-ով և SQL Server-ը ստեղծում է 2 հավելյալ datetime շարքեր այս աղյուսակում (գրանցման ստեղծման ամսաթիվ և գրառման կյանքի ժամկետի վերջ ամսաթիվ) և հավելյալ աղյուսակ, որտեղ կլինեն գրառումները: Արդյունքում մենք ստանում ենք ժամանակակից տեղեկատվություն և, պարզ հարցումների միջոցով, որոնց օրինակները ներկայացված են փաստաթղթում, կարող ենք տեսնել թե կոնկրետ վիրտուալ մեքենայի կյանքի ցիկլը, թե բոլոր VM-ների վիճակը տվյալ ժամանակահատվածում:

Կատարելագործման տեսանկյունից՝ հիմնական աղյուսակում գրանցման գործարքը չի լրանալու, մինչեւ ժամանակային աղյուսակում գրանցման գործարքը չի ավարտվում: Այսինքն՝ մեծ թվով գրառումների գործողությամբ աղյուսակները պետք է զգուշությամբ գործարկվեն, բայց մեր դեպքում դա իսկապես շատ հետաքրքիր բան է:

Այս մեխանիզմի ճիշտ աշխատանքի համար հարկավոր էր R-ում մի փոքր կոդ գրել, որը համեմատեր նոր աղյուսակը բոլոր VM-ների տվյալների հետ, որոնք պահվում են տվյալների բազայում, և գրանցեր միայն փոփոխված շարքերը: Կոդը այնքան բարդ չէ, օգտագործում է compareDF գրադարանը, բայց կներկայացնեմ այն էլ ներքևում:

Նախարարի R կոդ gegevens in de базу данных

# Подцепляем пакеты
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)

Ընդհանուր առմամբ

Սկրիպտի կիրառման արդյունքում, մի քանի ամիսների ընթացքում կարգի համագործակցություն էր ցուցաբերվում ու պահվում: Ամեն ինչ պարզ չէ, որ լրիվ սխալ լրացված ՎՄ-ներ հայտնվում են, բայց սկրիպտը լավ հիշեցում է ու հազվադեպ ՎՄ-ը հայտնվում է ցուցակում 2 օր անընդմեջ։

Ինպես նաև իրականացվել է պատմական տվյալների վերլուծության պատրաստություն։

Թարմ է, որ շատ բաներ կարելի է իրականացնել ոչ "թեթեւ" ձևով, այլ պրոֆեսիոնալ ծրագրակազմի միջոցով, բայց խնդիրը հետաքրքիր էր և կարելի է ասել, որ факультատիվ էր։

R-սահմանագրում կրկին ցույց տվեց, որ այն հրաշալի բազմակողմանի լեզու է, որը հրաշալիորեն հարմար է ոչ միայն վիճակագրական խնդիրների լուծման համար, այլև գերազանց միջավայր է գործում այլ տվյալների աղբյուրների միջև։

Կարող եք ամեն օր ստանալ վիրտուալ մեքենաների վիճակի զեկույցներ R եւ PowerShell-ի միջոցով

Ընտանիք: habr.com

Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով 🔥 Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster