본문 바로가기

VMware Cloud Foundation/vSphere

vmware vm tools upgrade

vmware vsphere을 운영 시, vm tools의 업그레이드는 일반적으로 하이퍼바이저 업데이트 이후 진행되는 경우가 일반적입니다.

특별한 경우가 아니면 vm tools를 업그레이드 하는 경우가 빈번하게 발생하지는 않습니다.

 

그러나, 보안패치와 같은 영향으로 이벤트성의 vm tools을 독립적으로 업그레이드가 필요한 경우가 발생할 수 있으며, 이미 운영중인 VM에 대해서 "자동 업그레이드" 설정이 되어 있는 경우에는 ESXi host가 가진 최신 vm tools 버전으로 VM reboot 하는 시점에 업그레이드 이벤트가 발생하게 됩니다.

 

이러한 VM reboot 이벤트가 오랜 시간을 기다려서 발생하거나, 모든 VM이 동시에 reboot를 할 수 있는 것이 아닌 경우가 더 많기에 독립적인 vm tools 버전 업그레이드를 해야하는 경우가 발생할 수 있습니다.

 

수 백, 수 천대의 VM에 대해서 vm tools 업그레이드를 진행한다는 것은 쉬운 작업이 아니며, 또한 vmtools 업그레이드 과정에서는 네트워크 중단이 2~3 초간 발생하기 때문에 민감한 운영 서비스에는 영향을 줄 수 있습니다.

 

이런 이유로 정해진 PM 시간에 정해진 VM 그룹에 대해서 서비스 가용성을 전환 이후 작업 진행 하거나 혹은 전환 없이 vm tools 업그레이드 작업을 진행할 수 있습니다.

 

다양한 Windows, Linux VM에서 어떤 형태로 vm tools software를 새로운 버전으로 배포할 것인지는 여러가지 방안을 생각해 볼 수 있으나, 한 가지 방안으로 다음과 같은 진행 프로세스를 이용할 수 있습니다.

 

아래 코드는 Windows powershell에서 진행되며, VMware PowerCLI 도구를 사용하게 됩니다.

이 도구를 통한 vCenter로부터 필요로 하는 Datacenter, Cluster, Host 정보를 수집한 이후, SSH 접속을 통한 각 요구되는 명령어를 실행하는 방식으로 진행됩니다.

첫번째로 vmtools 11.x 이후부터 지원되는 guest store 기능을 잘 활용할 수 있으며, guest store의 폴더에 vmtools guest store용 파일을 다운로드 받아서 압축 해제하여 업로드를 합니다.

ESXi host에서는 해당 guest store를 사용하도록 활성화가 필요하며, 아래 코드는 이 작업을 해당 경로(datastore 경로 확인 필요)를 설정하여 진행하게 됩니다.

 

예제 활성 명령 구문) esxcli system settings gueststore repository set --url ds:///vmfs/volumes/vsan:52c715888e7c668e-c97c6cd68c68b565/gueststore

 

#requires -Modules posh-ssh
#Run command on powershell window >> Install-Module posh-ssh
#requires -Modules posh-ssh
#Run command on powershell window >> Install-Module posh-ssh


#$VerbosePreference = "Continue"
$ErrorActionPreference = "SilentlyContinue"
$ActionPreference = "SilentlyContinue"
Clear-Host
	##############################
	# Check the required modules #
	############################## 

	function vmware-check-Module ($m) {
		# If module is imported say that and do nothing
		if (Get-Module | Where-Object {$_.Name -eq $m}) {
			write-host "Module $m "  -f Magenta -NoNewLine  
			write-host "is already imported." -f Green
		} else {
			# If module is not imported, but available on disk then import
			Write-Warning "Module $m is NOT imported (must be installed before starting)."
			if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
				Import-Module 'VMware.PowerCLI' -Verbose
			} else {
				# If module is not imported, not available on disk, but is in online gallery then install and import
				if (Find-Module -Name 'VMware.PowerCLI' | Where-Object {$_.Name -eq 'VMware.PowerCLI'}) {
					Install-Module -Name 'VMware.PowerCLI' -Force -Verbose -Scope CurrentUser
					Import-Module 'VMware.PowerCLI' -Verbose
				} else {
					# If module is not imported, not available and not in online gallery then abort
					Write-Warning "Module $m not imported, not available and not in online gallery, exiting."
					EXIT 1
				}
			}
		}
	}

	function Check_PS_Version
	{
		$version = (Get-Host).Version.Major
		if($version -lt 3)
		{
			#--- Windows Form to alert user the detected powershell version is not sufficient ---#
			[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
			$ans = [System.Windows.Forms.MessageBox]::Show(     
				"This script requires Windows Management Framework 3.0 or higher`n`nClick Yes to be directed to the download page", `
				"Attention", `
				[System.Windows.Forms.MessageBoxButtons]::YesNo, `
				[System.Windows.Forms.MessageBoxIcon]::Exclamation)
			if($ans -eq "Yes")
			{
			 start 'http://www.microsoft.com/en-us/download/details.aspx?id=34595'
			}
			exit
		}
	}

function vCenter_connect {
    #################################
    #   vSphere Targeting Variables # 
    #################################  
    write-host;
    write-host;
    $vCenter = Read-Host 'Enter the vCenter domain or IP Address-'
    write-host;
    $Script:username = Read-Host 'Enter The your vCenter Username(Default: administrator@vsphere.local)-'    
    If($Script:username -eq ""){
	    $Script:username='administrator@vsphere.local'
	}
    write-host;
    $Script:password = Read-Host 'Enter The vCenter Password-' -AsSecureString
    $Script:plainpw = [System.Net.NetworkCredential]::new("", $Script:password).Password
    $Script:vccredential = New-Object System.Management.Automation.PSCredential ($Script:username, $Script:password)
    Disconnect-VIServer  -Force -confirm:$false  -ErrorAction SilentlyContinue -WarningAction 0 | Out-Null

    Write-Host "Connecting to $vCenter..." -Foregroundcolor "Yellow" -NoNewLine
    $connection = Connect-VIServer -Server $vCenter -Cred $Script:vccredential -ErrorAction SilentlyContinue -WarningAction 0 | Out-Null
    #$connection = Connect-VIServer -Server vmk-vcsa01a.vmk.local -user administrator@vsphere.local -password VMware1!

		If($? -Eq $True){
			Write-Host "Connected" -Foregroundcolor "Green" 	
		}
		Else{
			Write-Host "`nError in Connecting to $vCenter; Try Again with correct user name & password!" -Foregroundcolor "Red" 
			sleep 5
				break
		}
	}

# Check Powershell version
Check_PS_Version
# Check PowerCLI Module
vmware-check-Module 'VMware.Vim'
# Connect vCenter Server
vCenter_connect
#Get-Variable true | Out-Default; Clear-Host;

$clusterName = Read-host "Input cluster name"
$user = 'root'
$pswdsec = Read-host "Input root password" -AsSecureString
$pswd = [System.Net.NetworkCredential]::new("", $pswdsec).Password
$code = {
        param(
            [string]$EsxName,
            [string]$User,
            [string]$Password
        )

        $pswdSec = ConvertTo-SecureString -String $Password -AsPlainText -Force
        $cred = New-Object System.Management.Automation.PSCredential($User,$pswdSec)
        $ssh = New-SSHSession -ComputerName $EsxName -Credential $cred -AcceptKey -KeepAliveInterval 5

        # Test
        $cmd = 'esxcli system settings gueststore repository set --url ds:///vmfs/volumes/vsan:52c715888e7c668e-c97c6cd68c68b565/gueststore'
	    #$cmd = 'date'

#       1..2 | %{
#            "Loop $_"
#            Invoke-SSHCommand -SessionId $ssh.SessionId -Command $cmd -TimeOut 30 | select -ExpandProperty Output
#            sleep 10
#       }

		write-host "command push to " $EsxName
		Invoke-SSHCommand -SessionId $ssh.SessionId -Command $cmd -TimeOut 30 | select -ExpandProperty Output
		sleep 5

        Remove-SSHSession -SessionId $ssh.SessionId | Out-Null
}

$jobs = @()
Get-Cluster -Name $clusterName | Get-VMHost -PipelineVariable esx |

ForEach-Object -Process {
    Write-Host -ForegroundColor Blue -NoNewline "$($esx.Name)"
    if((Get-VMHostService -VMHost $esx).where({$_.Key -eq 'TSM-SSH'}).Running){
        Write-Host -ForegroundColor Green " SSH running"
        $jobs += Start-Job -ScriptBlock $code -Name "SSH Job" -ArgumentList $esx.Name,$user,$pswd
    }
}

Wait-Job -Job $jobs

Receive-Job -Job $jobs

#--- Cleanly exit program ---#
disconnect-viserver -force -confirm:$false

 

그 다음 어떤 VM을 대상으로 vmtools 업그레이드를 진행할 것인지를 선택할 수 있을 것입니다. vCenter UI에서 우리는 VM을 선택하여, vmtools upgrade를 진행할 수 있습니다.

그러나, 특정한 VM 목록을 만들고서 해당 목록의 VM들을 대상으로 특정 PM 시간대에 진행할 수 있는 것을 고려해볼 수 있습니다.

 

이를 위해서 우선 우리가 원하는 vCenter에서 전체 VM 리스트를 export한 이후, 대상 리스트만을 편집하여 만들어볼 수 있습니다.

이러한 VM 리스트를 export 하는 것은 몇 가지 구문을 통해서 출력할 수 있을 것이며, vmware TAM 엔지니어가 준비한 코드를 좀 더 수정하여 아래와 같이 로그인과 함께 하는 과정을 추가해 봤습니다.(function getvms 섹션을 살펴볼 수 있습니다.)

 

#$VerbosePreference = "Continue"
$ErrorActionPreference = "SilentlyContinue"
$ActionPreference = "SilentlyContinue"
Clear-Host
	##############################
	# Check the required modules #
	############################## 

	function vmware-check-Module ($m) {
		# If module is imported say that and do nothing
		if (Get-Module | Where-Object {$_.Name -eq $m}) {
			write-host "Module $m "  -f Magenta -NoNewLine  
			write-host "is already imported." -f Green
		} else {
			# If module is not imported, but available on disk then import
			Write-Warning "Module $m is NOT imported (must be installed before starting)."
			if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
				Import-Module 'VMware.PowerCLI' -Verbose
			} else {
				# If module is not imported, not available on disk, but is in online gallery then install and import
				if (Find-Module -Name 'VMware.PowerCLI' | Where-Object {$_.Name -eq 'VMware.PowerCLI'}) {
					Install-Module -Name 'VMware.PowerCLI' -Force -Verbose -Scope CurrentUser
					Import-Module 'VMware.PowerCLI' -Verbose
				} else {
					# If module is not imported, not available and not in online gallery then abort
					Write-Warning "Module $m not imported, not available and not in online gallery, exiting."
					EXIT 1
				}
			}
		}
	}

	function Check_PS_Version
	{
		$version = (Get-Host).Version.Major
		if($version -lt 3)
		{
			#--- Windows Form to alert user the detected powershell version is not sufficient ---#
			[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
			$ans = [System.Windows.Forms.MessageBox]::Show(     
				"This script requires Windows Management Framework 3.0 or higher`n`nClick Yes to be directed to the download page", `
				"Attention", `
				[System.Windows.Forms.MessageBoxButtons]::YesNo, `
				[System.Windows.Forms.MessageBoxIcon]::Exclamation)
			if($ans -eq "Yes")
			{
			 start 'http://www.microsoft.com/en-us/download/details.aspx?id=34595'
			}
			exit
		}
	}

	function vCenter_connect {
    #################################
    #   vSphere Targeting Variables # 
    #################################  
    write-host;
    write-host;
    $vCenter = Read-Host 'Enter the vCenter domain or IP Address'
    #$vCenter = 'vCenter domain URL or IP Address'
    write-host;
    $username = Read-Host 'Enter The your vCenter Username(default: administrator@vsphere.local)'
    if ($username -eq "") {
	    $username = 'administrator@vsphere.local'
    }
    #$username = 'administrator@vsphere.local'
    write-host;
    $password = Read-Host 'Enter The vCenter Password' -AsSecureString    
    $vccredential = New-Object System.Management.Automation.PSCredential ($username, $password)

    Disconnect-VIServer  -Force -confirm:$false  -ErrorAction SilentlyContinue -WarningAction 0 | Out-Null

    Write-Host "Connecting to $vCenter..." -Foregroundcolor "Yellow" -NoNewLine
    $connection = Connect-VIServer -Server $vCenter -Cred $vccredential -ErrorAction SilentlyContinue -WarningAction 0 | Out-Null

		If($? -Eq $True){
			Write-Host "Connected" -Foregroundcolor "Green" 	
		}
		Else{
				Write-Host "`nError in Connecting to $vCenter; Try Again with correct user name & password!" -Foregroundcolor "Red" 
				sleep 5
				break
		}
	}

function getvms {

  Get-VM | Get-VMguest |
  where-object {$_.State -eq 'Running' -and $_.ExtensionData.ToolsversionStatus -eq 'GuestToolsNeedUpgrade' -and $_.ExtensionData.GuestFullname -match "win"} |
  Select VmName,
      @{N="Cluster";E={Get-Cluster -VM $_.VMname}},
      OsFullName,
      State,
      @{N="Toolsversion";E={$_.ExtensionData.Toolsversion}},
      @{N="ToolsStatus";E={$_.ExtensionData.ToolsversionStatus}} |
      Export-Csv winvm-export.csv -NoTypeInformation -UseCulture
	cat winvm-export.csv
	write-host
	write-host "Saved winvm-export.csv to current folder"
}

# Check Powershell version
Check_PS_Version
# Check PowerCLI Module
vmware-check-Module 'VMware.Vim'
# Connect vCenter Server
vCenter_connect
getvms

 

세번째로 해당 VM 리스트 .csv 파일을 memory에 import를 한 다음, guest OS의 vmtools를 업데이트 하는 과정을 진행해 볼 수 있습니다.

 

Update-Tools -NoReboot 라는 powercli 명령문을 이용해서 VM별로 진행할 수 있습니다. 다들 아는 바와 같이 powershell 스크립트 대화형 언어입니다. 한 개의 작업이 종료되어야지만, 다음 작업이 실행이 되는 방식이기에 vmtools 업그레이드가 1개 VM씩 순서대로 리스트에 맞춰서 진행이 되기에 1개 VM당 최소 30초에 가까운 시간을 소비해야 하며, 이는 VM수가 많을 경우 상당한 시간을 소비해야 한다는 것을 의미 합니다.

 

이를 신속하게 처리하기 위해서 powercli에 있는 start job 명령문을 이용하여, 각각의 background job 형태로 연속으로 job을 생성하여 처리하는 것으로 고려해볼 수 있습니다.

 

#$VerbosePreference = "Continue"
$ErrorActionPreference = "SilentlyContinue"
$ActionPreference = "SilentlyContinue"
Clear-Host
	##############################
	# Check the required modules #
	############################## 

	function vmware-check-Module ($m) {
		# If module is imported say that and do nothing
		if (Get-Module | Where-Object {$_.Name -eq $m}) {
			write-host "Module $m "  -f Magenta -NoNewLine  
			write-host "is already imported." -f Green
		} else {
			# If module is not imported, but available on disk then import
			Write-Warning "Module $m is NOT imported (must be installed before starting)."
			if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
				Import-Module 'VMware.PowerCLI' -Verbose
			} else {
				# If module is not imported, not available on disk, but is in online gallery then install and import
				if (Find-Module -Name 'VMware.PowerCLI' | Where-Object {$_.Name -eq 'VMware.PowerCLI'}) {
					Install-Module -Name 'VMware.PowerCLI' -Force -Verbose -Scope CurrentUser
					Import-Module 'VMware.PowerCLI' -Verbose
				} else {
					# If module is not imported, not available and not in online gallery then abort
					Write-Warning "Module $m not imported, not available and not in online gallery, exiting."
					EXIT 1
				}
			}
		}
	}

	function Check_PS_Version
	{
		$version = (Get-Host).Version.Major
		if($version -lt 3)
		{
			#--- Windows Form to alert user the detected powershell version is not sufficient ---#
			[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
			$ans = [System.Windows.Forms.MessageBox]::Show(     
				"This script requires Windows Management Framework 3.0 or higher`n`nClick Yes to be directed to the download page", `
				"Attention", `
				[System.Windows.Forms.MessageBoxButtons]::YesNo, `
				[System.Windows.Forms.MessageBoxIcon]::Exclamation)
			if($ans -eq "Yes")
			{
			 start 'http://www.microsoft.com/en-us/download/details.aspx?id=34595'
			}
			exit
		}
	}

function vCenter_connect {
    #################################
    #   vSphere Targeting Variables # 
    #################################  
    write-host;
    write-host;
    $vCenter = Read-Host 'Enter the vCenter domain or IP Address-'
    write-host;
    $Script:username = Read-Host 'Enter The your vCenter Username(Default: administrator@vsphere.local)-'    
    If($Script:username -eq ""){
	    $Script:username='administrator@vsphere.local'
	}
    write-host;
    $Script:password = Read-Host 'Enter The vCenter Password-' -AsSecureString
    $Script:plainpw = [System.Net.NetworkCredential]::new("", $Script:password).Password
    $Script:vccredential = New-Object System.Management.Automation.PSCredential ($Script:username, $Script:password)
    Disconnect-VIServer  -Force -confirm:$false  -ErrorAction SilentlyContinue -WarningAction 0 | Out-Null

    Write-Host "Connecting to $vCenter..." -Foregroundcolor "Yellow" -NoNewLine
    $connection = Connect-VIServer -Server $vCenter -Cred $Script:vccredential -ErrorAction SilentlyContinue -WarningAction 0 | Out-Null
    #$connection = Connect-VIServer -Server vmk-vcsa01a.vmk.local -user administrator@vsphere.local -password VMware1!

		If($? -Eq $True){
			Write-Host "Connected" -Foregroundcolor "Green" 	
		}
		Else{
			Write-Host "`nError in Connecting to $vCenter; Try Again with correct user name & password!" -Foregroundcolor "Red" 
			sleep 5
				break
		}
	}

    function upgradetools {
        begin {
			Get-Variable true | Out-Default; Clear-Host;
			Write-Host "Upgrade vmtools software . . ."  
        }
        process{
            try{
                  $Script:vmlist_file=Read-Host "input imported file name: "
                  $Script:vmlists = Import-Csv $vmlist_file
                  #$Script:vmlists = Import-Csv 'winvm-export.csv'
			[int]$runcnt = 100
			$textbook=@()
		      foreach ($vmlist in $Script:vmlists) {
                        $clsname = $vmlist.cluster
                        $strNewVMName = $vmlist.vmname
                        #get-cluster $Global:clsname | get-vm $Global:strNewVMName | Update-Tools -NoReboot
				$textbook += "start-job -Name toolsupgrade -scriptblock {connect-viserver vmk-vcsa01a.vmk.local -user $Script:username -password $Script:plainpw; get-cluster $clsname | get-vm $strNewVMName | Update-Tools -NoReboot; disconnect-server -Force -ConFirm:$false}"
				invoke-expression $textbook[$VMCount]
			  	$Script:VMcount++
				if ($Script:VMcount -eq $runcnt){
					sleep 30
					$runcnt=$runcnt*2
				}
                  }
				sleep 3
				#$textbook | Out-host | Export-Csv temp-runcommand.csv -NoTypeInformation -UseCulture
            } #try
            catch {
                    "Error: You must input correct filename.`n" | Out-host
                    break
            } #catch
        } #process
        End{}
    }

    function checktoolversion {
        begin {
			Get-Variable true | Out-Default; Clear-Host;
			Write-Host "Check vmtools version . . ."  
        }
        process {
            try{
                $upgrade_num = 0
                $VM_pnum = 0
		    $StartMs = (Get-Date)
                while ($upgrade_num -ne $Script:VMcount) {
                    Get-Variable true | Out-Default; Clear-Host;
			  $upgrade_num = 0
			  foreach ($vmlist in $Script:vmlists) {
                        $VMinfo = Get-Cluster $vmlist.cluster | Get-VM $vmlist.vmname | Get-VMguest |
                        where-object {$_.State -eq 'Running' -and $_.ExtensionData.GuestFullname -match "win"} |
                        Select vmname,
                        @{N="Cluster";E={Get-Cluster -VM $_.vmname}},
                        OsFullName,
                        State,
                        @{N="Toolsversion";E={$_.ExtensionData.Toolsversion}},
                        @{N="ToolsStatus";E={$_.ExtensionData.ToolsversionStatus}}

                        if ($VMinfo.ToolsStatus -eq 'guestToolsCurrent') {
                            write-host $VMinfo.vmname " was upgraded as " $VMinfo.Toolsversion "version."
                            $upgrade_num++
                        } else {
                            write-host $VMinfo.vmname " is not upgraded as new version."
                        }#if end

                        $VM_pnum=$upgrade_num/$VMcount*100 #Percentage
                        Write-Progress -Activity "Upgrading vmtools in Progress" -Status "$VM_pnum% Complete:" -PercentComplete $VM_pnum
				$vm_inum++
				if ($upgrade_num -eq $vmlists.count){
					break
				}
                    }#foreach end
                        $EndMs = (Get-Date)
                        $timeSpan = $EndMs - $StartMs
                       if ($timeSpan.TotalSeconds -gt 1800) {
                        "Error: Time period went over 30 mins.`n" | Out-host
                        break
                    }
			  sleep 5
                } #while end
			write-host $timeSpan
            }#try end
            catch {
                    "Error: `n" | Out-host
                    break
            }#catch end
        }#process
        End{}
    }



#Import vm name from csv file
#$Script:vmlist_file=Read-Host "input imported file name"
#$Script:vmlists = Import-Csv $vmlist_file
$Global:strNewVMName = $null
$Global:clsname = $null
$Script:vmlists = $null
$Script:VMcount = 0


# Check Powershell version
Check_PS_Version
# Check PowerCLI Module
vmware-check-Module 'VMware.Vim'
# Connect vCenter Server
vCenter_connect
Get-Variable true | Out-Default; Clear-Host;
upgradetools
checktoolversion

#--- Cleanly exit program ---#
disconnect-viserver -force -confirm:$false

 

이를 통해서 vmtools 업그레이드를 동일한 시간대에 신속하게 정해진 목록에 대해서만 수행을 하고, 진행율(완료된 VM)을 표시할 수 있는 powercli 스크립트를 작성할 수 있습니다.

 

물론 이러한 업데이트는 vSphere life cycle manager를 통해서 기준점을 정의하여, vmtools 업그레이드를 진행할 수 있습니다. 그러나, 우리가 정확히 원하는 방향성, 특히 vmtools 이후 VM이 즉시 업데이트를 방지하고, 향후 maintanance 작업 시점에 reboot을 시행하기를 원하는 경우 이용해 볼 수 있습니다.

 

vmtools의 경우 reboot 요소로 몇 가지 장치에 대한 드라이버 업데이트에 따라서 영향을 받습니다. 이미 설치된 vmtools에 업데이트의 경우, mounse, keyboard에 대한 변경 버전 드라이버에 따라 업데이트 메시지를 출력하게 됩니다. 그러나, 이는 직접적인 서비스에 영향을 주는 요소는 아니기에 reboot 없이 우선 vmtools 업그레이드를 시행할 수 있으며, 이러한 방지를 위해서 최초 vmtools 설치 과정에서 이러한 장치를 설치 대상에서 제외하는 것도 하나의 선택지가 될 수 있습니다.

'VMware Cloud Foundation > vSphere' 카테고리의 다른 글

vSphere+, vSAN+ 로 전환  (0) 2023.05.25
vSphere Replication PowerCLI 활용  (0) 2022.03.24