Adicionar automaticamente o espaço do servidor virtual

Olá pessoal!


Neste artigo, falaremos sobre como automatizamos a tarefa de expandir o espaço em disco em um de nossos servidores. E o que é difícil em uma tarefa tão simples que eu tive que automatizar - você pergunta? Nada se você não usar montagens em cascata mescladas. Eu sinto que há mais perguntas!? Bem, então, vamos embaixo do gato.

Primeiro, vou explicar por que usamos a montagem integrada em cascata.

Temos um sistema que precisa de armazenamento para arquivos pequenos (digitalizações de documentos, etc.). O tamanho médio do arquivo é de 200kb a 1 megabyte, os dados são estáticos, não mudam. Existem bilhões de arquivos e o número está crescendo todos os dias. Uma vez, quando o volume já era superior a 6 TB, percebemos que os problemas começariam em breve, um dos quais era o tempo de backup e recuperação. Decidimos dividir os dados em discos, e o UnionFS foi chamado para nos ajudar com isso.

O algoritmo foi determinado da seguinte forma: os dados são gravados no disco com mais de 2 TB, quando termina adicionamos um novo disco à máquina virtual, marcamos, adicionamos ao UnionFS, transferimos o antigo para o ReadOnly, removemos uma cópia dele, escrevemos em fita, removemos do backup online.

Como você já entendeu, esse algoritmo é bastante exigente à atenção do administrador - qualquer movimento e armazenamento desajeitados não estão disponíveis. Portanto, decidimos excluir completamente o fator humano e lembramos que possuímos o ZABBIX, que pode lidar muito bem com isso se adicionarmos um pouco de mágica do PowerShell e Bash ao algoritmo.

Agora, sobre como isso é feito.

No Zabbix, um gatilho para espaço livre é configurado e um botão para o modo manual é feito:

imagem

Quando o gatilho é acionado, uma tarefa é formada no servidor-robô sheduler no qual todos os nossos scripts de automação estão localizados:

Powershell.exe "Enable-ScheduledTask \PROD_TASKS\Add_HDD_OS0226” 

No horário marcado, um script é iniciado no servidor que:

Adiciona o disco à VM desejada:
(ele escolhe o volume mais livre)

 $vm = Get-VM -Name $vmName New-HardDisk -VM $vm -CapacityGB $newHDDCapacity -Datastore $datastoreName –ThinProvisioned 

Procura detalhes de acesso ao servidor:
OFFTop
Utilizamos um repositório personalizado de detalhes de acesso baseado no TeamPass, para que o script encontre o servidor desejado no sistema e receba seus detalhes automaticamente. Isso é feito porque todos os meses alteramos automaticamente todas as senhas, mas este é o tópico de um artigo separado

 #Generate TeamPass API request string $vmTPReq = "   TeamPass" #Send request to TeamPass $vmCreds = Invoke-WebRequest($vmTPReq) -UseBasicParsing | ConvertFrom-Json #Convert credentials $credential = New-Object System.Management.Automation.PSCredential($vmCreds.login,(ConvertTo-SecureString $vmCreds.pw -asPlainText -Force)) 

Vem em SSH:
 #Create partition & FS, mount disk to directory, edit fstab...etc. New-SSHSession -ComputerName $vmCreds.url -Credential $credential -Verbose -AcceptKey:$true $results = Invoke-SSHCommand -SessionId 0 -Command "/mnt/autodoit.sh" Remove-SSHSession -Index 0 -Verbose 

Marca e adiciona ao suporte do UnionFS:
(autodoit.sh)
 #!/bin/bash fstab="/etc/fstab" newdisk=$(( ( parted -lm >&1 ) 1>/tmp/gethddlog ) 2>&1) newdisk=$(echo $newdisk | cut -d ':' -f 2) if [[ $newdisk == "" ]] ; then printf "New disk not found! Exit\n". exit fi printf "New disk found: $newdisk\n" echo #Create new partition echo Create new partition ... parted $newdisk mklabel gpt unit TB mkpart primary 0.00TB 2.00TB print sleep 10 #Create filesystem echo Create filesystem xfs ... newdisk="$newdisk$((1))" mkfs.xfs $newdisk #Create new DATA directory lastdata=$(ls /mnt/ | grep 'data[0-9]\+$' | cut -c5- | sort -n | tail -n1) lastdatamount="/mnt/data$((lastdata))" newdata="/mnt/data$((lastdata+1))" printf "Create new direcory: $newdata\n" mkdir $newdata chown -R nobody:nobody $newdata chmod -R 755 $newdata #Mount new partition to new directory printf "Mount new partition to $newdata...\n" mount -t xfs ${newdisk} ${newdata} #Get UUID of new partition uuid=$(blkid $newdisk -o value -s UUID) printf "New disk UUID: $uuid\n" #Add mountpoint for new partition printf "Add mountpoint for new disk to fstab...\n" lastdatamount=$(cat $fstab | grep "$lastdatamount\s") newdatamount="UUID=$uuid $newdata xfs defaults,nofail 0 0" ldm=$(echo $lastdatamount | sed -r 's/[\/]/\\\//g') ndm=$(echo $newdatamount | sed -r 's/[\/]/\\\//g') sed -i "/$ldm/a $ndm" $fstab #Change UnionFS mountpoint string printf "Modify mountpoint for UnionFS in fstab...\n" prevunion=$(cat $fstab | grep fuse.unionfs) newunion=$(echo $prevunion | sed -e "s/=rw/=ro/") newunion=$(echo $newdata=rw:$newunion) sed -i "s|$prevunion|$newunion|" $fstab #Remount UnionFS printf "Remount UnionFS...\n" service smb stop sleep 0.6 umount /mnt/unionfs mount /mnt/unionfs service smb start printf "Done!\n\n" rm /tmp/gethddlog 


Infelizmente, no momento da gravação, não resolvemos vários problemas relacionados à criação automática de tarefas no VEEAM para arquivar o disco antigo e gravá-lo em fita, portanto, isso ainda é feito manualmente. Definitivamente, atualizaremos o script assim que resolvermos alguns problemas.

Postado por Vitaliy Rosman ( PBCVIT ).

Um pedaço de código para colar matrizes foi honestamente emprestado, os links para o autor foram salvos no código.

Script completo
 #Set VM name $vmName = "OS0226" #Set TeamPass ID of linux server $vmTPId = "1161" #Set capacity of new HDD in GB $newHDDCapacity = 2048 #Set Log file $logFile = "C:\SCRIPTS\Log\NewHardDisk-OS0226.log" #Import module for SSH connections Import-Module Posh-SSH #Add VEEAM Snap-In Add-PSSnapin VeeamPSSnapin #Initialize VMWare PowerCLI & 'C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1' #Add function for array join Function Join-Object { # https://powersnippets.com/join-object/ [CmdletBinding()]Param ( # Version 02.02.00, by iRon [Object[]]$RightTable, [Alias("Using")]$On, $Merge = @{}, [Parameter(ValueFromPipeLine = $True)][Object[]]$LeftTable, [String]$Equals ) $Type = ($MyInvocation.InvocationName -Split "-")[0] $PipeLine = $Input | ForEach {$_}; If ($PipeLine) {$LeftTable = $PipeLine} If ($LeftTable -eq $Null) {If ($RightTable[0] -is [Array]) {$LeftTable = $RightTable[0]; $RightTable = $RightTable[-1]} Else {$LeftTable = $RightTable}} $DefaultMerge = If ($Merge -is [ScriptBlock]) {$Merge; $Merge = @{}} ElseIf ($Merge."") {$Merge.""} Else {{$Left.$_, $Right.$_}} If ($Equals) {$Merge.$Equals = {If ($Left.$Equals -ne $Null) {$Left.$Equals} Else {$Right.$Equals}}} ElseIf ($On -is [String] -or $On -is [Array]) {@($On) | ForEach {If (!$Merge.$_) {$Merge.$_ = {$Left.$_}}}} $LeftKeys = @($LeftTable[0].PSObject.Properties | ForEach {$_.Name}) $RightKeys = @($RightTable[0].PSObject.Properties | ForEach {$_.Name}) $Keys = $LeftKeys + $RightKeys | Select -Unique $Keys | Where {!$Merge.$_} | ForEach {$Merge.$_ = $DefaultMerge} $Properties = @{}; $LeftOut = @($True) * @($LeftTable).Length; $RightOut = @($True) * @($RightTable).Length For ($LeftIndex = 0; $LeftIndex -lt $LeftOut.Length; $LeftIndex++) {$Left = $LeftTable[$LeftIndex] For ($RightIndex = 0; $RightIndex -lt $RightOut.Length; $RightIndex++) {$Right = $RightTable[$RightIndex] $Select = If ($On -is [String]) {If ($Equals) {$Left.$On -eq $Right.$Equals} Else {$Left.$On -eq $Right.$On}} ElseIf ($On -is [Array]) {($On | Where {!($Left.$_ -eq $Right.$_)}) -eq $Null} ElseIf ($On -is [ScriptBlock]) {&$On} Else {$True} If ($Select) {$Keys | ForEach {$Properties.$_ = If ($LeftKeys -NotContains $_) {$Right.$_} ElseIf ($RightKeys -NotContains $_) {$Left.$_} Else {&$Merge.$_} }; New-Object PSObject -Property $Properties; $LeftOut[$LeftIndex], $RightOut[$RightIndex] = $Null } } } If ("LeftJoin", "FullJoin" -Contains $Type) { For ($LeftIndex = 0; $LeftIndex -lt $LeftOut.Length; $LeftIndex++) { If ($LeftOut[$LeftIndex]) {$Keys | ForEach {$Properties.$_ = $LeftTable[$LeftIndex].$_}; New-Object PSObject -Property $Properties} } } If ("RightJoin", "FullJoin" -Contains $Type) { For ($RightIndex = 0; $RightIndex -lt $RightOut.Length; $RightIndex++) { If ($RightOut[$RightIndex]) {$Keys | ForEach {$Properties.$_ = $RightTable[$RightIndex].$_}; New-Object PSObject -Property $Properties} } } }; Set-Alias Join Join-Object Set-Alias InnerJoin Join-Object; Set-Alias InnerJoin-Object Join-Object -Description "Returns records that have matching values in both tables" Set-Alias LeftJoin Join-Object; Set-Alias LeftJoin-Object Join-Object -Description "Returns all records from the left table and the matched records from the right table" Set-Alias RightJoin Join-Object; Set-Alias RightJoin-Object Join-Object -Description "Returns all records from the right table and the matched records from the left table" Set-Alias FullJoin Join-Object; Set-Alias FullJoin-Object Join-Object -Description "Returns all records when there is a match in either left or right table" #Connect to vCenter Connect-VIServer vcenter.mmc.local #Get datastore $datastores = get-datastore | where-object Name -like "*TIERED_VM_PROD*" if ($datastores.Count -gt 0) { if (($datastores | Sort -Descending {$_.FreeSpaceGB})[0].FreeSpaceGB -gt 2048) { $datastoreName = ($datastores | Sort -Descending {$_.FreeSpaceGB})[0].Name } else { Write-Host("ERROR: No enought space on datastore for new HDD!") break } } else { Write-Host("ERROR: No Datastores found!") break } #Generate TeamPass API request string $vmTPReq = "   TeamPass" #Send request to TeamPass $vmCreds = Invoke-WebRequest($vmTPReq) -UseBasicParsing | ConvertFrom-Json #Convert credentials $credential = New-Object System.Management.Automation.PSCredential($vmCreds.login,(ConvertTo-SecureString $vmCreds.pw -asPlainText -Force)) if ((Test-Connection $vmCreds.url -Count 1 -Quiet) -eq $false) { $err = $error[0].FullyQualifiedErrorId } try { # Get disks information from Linux New-SSHSession -ComputerName $vmCreds.url -Credential $credential -Verbose -AcceptKey:$true $linuxCommand1 = 'ls -dl /sys/block/sd*/device/scsi_device/*' $linuxDisksData1 = Invoke-SSHCommand -SessionId 0 -Command $linuxCommand1 $linuxCommand2 = 'lsblk -l | grep /mnt' $linuxDisksData2 = Invoke-SSHCommand -SessionId 0 -Command $linuxCommand2 Remove-SSHSession -Index 0 -Verbose $linuxMounts = $linuxDisksData2.Output -replace '\s+', ' ' | Select @{N='Partition';E={($_.split(" ")[0])}}, @{N='linuxMount';E={($_.split(" ")[6])}} $linuxDisks = $linuxDisksData1.Output -replace '\s+', ' ' | Select @{N='Partition';E={($_.split(" ")[8]).split('/')[3]+'1'}}, @{N='SCSIAddr';E={(($_.split(" ")[8]).split('/')[6]).split(':')[1]+':'+(($_.split(" ")[8]).split('/')[6]).split(':')[2]}} $linuxDisks = $linuxDisks | sort SCSIAddr } catch { $err = $error[0].FullyQualifiedErrorId } #Get disks information from vmware $vmDisks = Get-VM -Name $vmName | Get-HardDisk | Select @{N='vmwareHardDisk';E={$_.Name}}, @{N='vSCSI';E={$_.uid.split("/")[3].split("=")[1]}}, @{N='SCSIAddr';E={[string]([math]::truncate((($_.uid.split("/")[3].split("=")[1])-2000)/16))+':'+[string]((($_.uid.split("/")[3].split("=")[1])-2000)%16)}} $vmDisks = $vmDisks | sort SCSIAddr #Get total information about VM Disks $OLAYtotalEffects = $vmDisks | InnerJoin $linuxDisks SCSIAddr -eq SCSIAddr | InnerJoin $linuxMounts Partition -eq Partition| sort vmwareHardDisk #Display total information about VM Disks $OLAYtotalEffects | ft $OLAYtotalEffects | ft 2>$logFile #Get latest mount $linuxLatestDiskMount = [string](($OLAYtotalEffects | select linuxMount | where linuxMount -like "/mnt/data*" | % {[int](($_.linuxMount.Split("/")[2]).Replace("data",""))} | Measure -Maximum).Maximum) #Get latest vSCSI number $latestDiskvSCSI = ($OLAYtotalEffects | where {$_.linuxMount -eq "/mnt/data$linuxLatestDiskMount"}).vSCSI #Add HDD to VM $vm = Get-VM -Name $vmName New-HardDisk -VM $vm -CapacityGB $newHDDCapacity -Datastore $datastoreName -ThinProvisioned #Let the disk takes root Write-Host("Let the disk takes root...") sleep 10 if ((Test-Connection $vmCreds.url -Count 1 -Quiet) -eq $false) { $err = $error[0].FullyQualifiedErrorId } try { #Create partition & FS, mount disk to directory, edit fstab...etc. New-SSHSession -ComputerName $vmCreds.url -Credential $credential -Verbose -AcceptKey:$true $results = Invoke-SSHCommand -SessionId 0 -Command "/mnt/autodoit.sh" Remove-SSHSession -Index 0 -Verbose $results.Output } catch { $err = $error[0].FullyQualifiedErrorId } #After adding a new disk, some checks are just performed if ((Test-Connection $vmCreds.url -Count 1 -Quiet) -eq $false) { $err = $error[0].FullyQualifiedErrorId } try { # Get disks information from Linux New-SSHSession -ComputerName $vmCreds.url -Credential $credential -Verbose -AcceptKey:$true $linuxCommand1 = 'ls -dl /sys/block/sd*/device/scsi_device/*' $linuxDisksData1 = Invoke-SSHCommand -SessionId 0 -Command $linuxCommand1 $linuxCommand2 = 'lsblk -l | grep /mnt' $linuxDisksData2 = Invoke-SSHCommand -SessionId 0 -Command $linuxCommand2 Remove-SSHSession -Index 0 -Verbose $linuxMounts = $linuxDisksData2.Output -replace '\s+', ' ' | Select @{N='Partition';E={($_.split(" ")[0])}}, @{N='linuxMount';E={($_.split(" ")[6])}} $linuxDisks = $linuxDisksData1.Output -replace '\s+', ' ' | Select @{N='Partition';E={($_.split(" ")[8]).split('/')[3]+'1'}}, @{N='SCSIAddr';E={(($_.split(" ")[8]).split('/')[6]).split(':')[1]+':'+(($_.split(" ")[8]).split('/')[6]).split(':')[2]}} $linuxDisks = $linuxDisks | sort SCSIAddr } catch { $err = $error[0].FullyQualifiedErrorId } #Get disks information from vmware $vmDisks = Get-VM -Name $vmName | Get-HardDisk | Select @{N='vmwareHardDisk';E={$_.Name}}, @{N='vSCSI';E={$_.uid.split("/")[3].split("=")[1]}}, @{N='SCSIAddr';E={[string]([math]::truncate((($_.uid.split("/")[3].split("=")[1])-2000)/16))+':'+[string]((($_.uid.split("/")[3].split("=")[1])-2000)%16)}} $vmDisks = $vmDisks | sort SCSIAddr #Get total information about VM Disks $OLAYtotalEffects = $vmDisks | InnerJoin $linuxDisks SCSIAddr -eq SCSIAddr | InnerJoin $linuxMounts Partition -eq Partition| sort vmwareHardDisk #Display total information about VM Disks $OLAYtotalEffects | ft $OLAYtotalEffects | ft 2>$logFile Disconnect-VIServer -Confirm:$false Disable-ScheduledTask \PROD_TASKS\Add_HDD_OS0226 


Não há queixas contra o UnionFS, ele trabalha de forma estável há mais de dois anos.

A questão de por que o armazenamento é organizado em geral é geralmente retórica, apenas aceite-a como é.

Observe que diferentes tipos de mapeamento de unidades devem ser usados ​​para diferentes sistemas. portanto, tenha cuidado e a força virá com você.

Source: https://habr.com/ru/post/pt440138/


All Articles