In version 10 of Veeam Backup & Replication a lot of Linux love has been put in to the product. To me, one of the most interesting things is the new functionality where you can assign a Linux VM the role of a backup proxy. Historically the proxy functionality has been restricted to Windows OS only. The linux proxy does not come as a prebuilt Veeam appliance, just as with Windows proxies you still need to secure and patch the operating system. The rational behind it is easy to understand, most organizations already have some sort of patch management system in place to leverage for their production workload and you would want to have the same level of patching and security on your proxies as you have for all other workloads. I thought it would interesting to see if you could automate the deployment of proxy servers a bit. Sure enough it is a fairly simple process since Veeam Backup & Replication has a great PowerShell extension which will let you automate almost all of the tasks you can do in the GUI. As far as automating proxy servers go, my colleague Anthony Spiteri has a comprehensive project called “Project Ōtosukēru” based on Terraform which may be of interest to you as well.
As a side note, Veeam Backup & Replication version 10 contains a wide variety of new features and updates, the Veeam Vanguards made a list of their top features.
Mind you, the code below should serve as an indication of what you can do – as an example if you will. However, you use at at your own risk. I’ve put in an option to deploy to a test/lab environment (if you are lucky enough to have a lab environment) so you can start testing it out in a safe environment.
So why not combine this small project with the small Linux operating system VMware provides called PhotonOS? PhotonOS is a stripped down linux operating system we can use. Now you can certainly download an iso image and do a traditional install and set it up just the way you’d like and then convert it to a template in vSphere and make use of it that way. However I thought it would be more interesting to see how much you can automate. So instead I’m just downloading the prebuilt Linux appliance and using that as the source for my proxies.
The script performs 4 different tasks:
- It starts by importing the settings you specified in the configuration file (config.json)
- Download required components if they’re not available. Two components are needed: The PhotonOS OVA and a powershell script by William Lam called VMKeystrokes.ps1. VMKeystrokes is needed to change the initial password of the appliance.
- Let’s you choose where to deploy the Linux appliance, you can pick a single host, all hosts in a specific cluster or all hosts in a specific datacenter. It will also let you choose if you’d like to deploy to a test/lab environment or to a production environment (settings fetched from config.json). The script will try to ping the IP range configured in config.json to verify that they are not being used. It then deploys the appliance to the selected target(s). Once deployed the appliance will be configured (Connecting to the specified network portgroup in vSphere, Set vCPU and vRAM, set Static IP address, firewall ports opened, configuring timezone).
- The Linux server will then be added to Veeam Backup & Replication as a managed server. The managed server will then have the role Backup Proxy assigned to it, concurrent tasks will be set to whatever amount of vCPU’s you’ve assigned to the VM (from the configuration file).
Config.json explained
“location”: – Where files be downloaded and working directory
“ovasource”: – URL to the OVA
“ovaname”: – Name of the OVA
“ProxyBaseName”: – Base name of Proxy VM to be deployed
“ProxyvCPU”: – vCPUs to assign the Proxy VM
“ProxyvRAM”: – vRAM to assign the Proxy VM
“sshuser”: “root” – Logon account for the Proxy VM
“ovainitialpassword”: – Default password of the OVA is “changeme”
“newsshpassword”: – New password to apply for the root user on the Proxy VM
“Prod”: {
“vbrserver”: – IP adress to Veeam Backup & Replication server
“vcserver”: – IP adress to VMware vCenter Server
“network”: – Portgroup the Proxy VM will be connected to
“startipaddress”: – Start of IP pool the Proxy VMs will use
“endipaddress”: – End of IP pool the Proxy VMs till use
“vbruser”: – User to connect to Veeam Backup & Replication
“vcuser”: – User to connect to VMware vCenter Server
“vcdatacenter”: – Datacenter in vCenter to use
“gateway”: – IP address of gateway for the Proxy VM
“dns”: – IP address of DNS for the Proxy VM
“domains”: – DNS Search Domains for the Proxy VM
“ntpserver”: – NTP Server for the Proxy VM to use
The Proxy VM will be named with ProxyBaseName + IP address of the target host to where the VM is deployed with all dots replaced by dashes, i.e “VeeamProxy_10-10-50-38”.
Save the two files to c:\temp and then you’re good to go!
No more bits needed to be downloaded, the script will download what it’s required. The only thing you need to change is the settings in config.json to reflect your environment.
Download Deploy_Veeam_Linux_Proxy.ps1
Download config.json
The script will add proxies based on IP addresses to Veeam Backup & Replication in this version, but per Veeam recommendation FQDNs should be used.
Deploy_Veeam_Linux_Proxy.ps1
# Deploy_Veeam_Linux_Proxy.ps1
# Version: 1.0
# Author: Niclas Borgstrom, Veeam Cloud Solution Architect EMEA
#
# Synopsis:
# This script will download a linux appliance (based on VMware PhotonOS)
# and deploy the appliance to a host, cluster or datacenter of your choice.
# It will also download VMKeystrokes.ps1 by William Lam, VMware in order to
# do the initial configuration of PhotonOS.
# When the OVA is deployed it will be configured with a static IP address,
# firewall will be opened for Veeam Backup Proxy specific ports, vCPU and
# vRAM will be changed and the VM will be added to Veeam Backup & Replication
# as a managed server and then be assigned the proxy role. Concurrent tasks
# will be set according to the vCPUs assigned to the VM.
#
# You can choose to deploy either to a production- or a test environment, if
# available. The OVA will only be deployed to powered on hosts.
#
# Disclaimer:
# The script is for demonstration purpose only, run it at your own risk
#
# Credits
# 'My-Logger' function by William Lam, VMware
#
# Prereqs:
# - VMware environment managed by vCenter
# - Veeam Backup & Replication already installed on a server.
# - Internet access to download PhotonOS and VMKeystrokes.ps1 to change the default
# password on the PhotonOS appliance.
#
# Limitations:
# - Linux proxy can't be used as a 'Guest Interaction Proxy'
# - Linux proxy can't be used as a 'File Backup Proxy'
# - Linux Proxy only supports Hot-Add/Virtual appliance mode
#
# Changelog:
# v 1.0: Initial release
$global:BaseDirectory = "C:\temp\"
$global:BaseConfig = "config.json"
$verboseLogFile = $global:BaseDirectory + "Veeam_Linux_Proxy_Deployment_" + $(Get-Date -Format "yyyy-MM-dd_HH-mm") + ".log"
Function My-Logger {
param(
[Parameter(Mandatory=$true)]
[String]$message
)
$timeStamp = Get-Date -Format "yyyy-MM-dd_HH:mm:ss"
Write-Host -NoNewline -ForegroundColor White "[$timestamp]"
Write-Host -ForegroundColor Green " $message"
$logMessage = "[$timeStamp] $message"
$logMessage | Out-File -Append -LiteralPath $verboseLogFile
}
# Load and parse the JSON configuration file
My-Logger "Loading JSON configuration file $BaseDirectory$BaseConfig"
try {
$global:Config = Get-Content "$BaseDirectory$BaseConfig" -RAW -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
} catch {
My-Logger "[ERROR] The configuration files is missing!"
Break
}
# Check the configuration
if (!($Config)) {
My-Logger "[ERROR] The configuration file is missing!"
Break
}
$location = $Config.General.location
$ovasource = $Config.General.ovasource
$ovaname = $Config.General.ovaname
$proxybasename = $Config.General.ProxyBaseName
$proxyvcpu = $Config.General.ProxyvCPU
$proxyvram = $Config.General.ProxyvRAM
$sshuser = $Config.General.sshuser
$ovainitialpassword = $Config.General.ovainitialpassword
$newsshpassword = $Config.General.newsshpassword
# Set temp location
Set-Location $location
try {
Add-PSSnapin -Name VeeamPSSnapin -ErrorAction Stop
Import-Module -Name VMware.VimAutomation.Core -ErrorAction Stop
} catch {
My-Logger "[ERROR] PowerCLI or VeeamPSSnapin not installed."
Break
}
# Verify that VMKeystrokes.ps1 is available
My-Logger "Checking VMkeystrokes.ps1..."
Clear-Host
try {
# Dot source VMKeystrokes.ps1 if it's available
. .\VMKeystrokes.ps1
} catch {
# If VMKeystrokes.ps1 isn't available, ask if it should be downloaded
My-Logger " [WARNING] Unable to find 'VMKeystrokes.ps1' in $location"
$downloadvmkeystrokes = if(($downloadvmkeystrokes = Read-Host "`nWould you like to download it (Y/N) or press enter to accept default value [Y]") -eq '') {"Y"} else {$downloadvmkeystrokes}
if ($downloadvmkeystrokes.ToLower() -eq "y") {
My-Logger " Downloading 'VMKeystrokes.ps1'"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/lamw/vghetto-scripts/master/powershell/VMKeystrokes.ps1" -OutFile ".\VMKeystrokes.ps1"
. .\VMKeystrokes.ps1
} else {
My-Logger " [ERROR] Can't continue without VMkeystrokes.ps1, exiting..."
Break
}
}
$targethosts = $null
# Verify that PhotonOS it available
My-Logger "Checking PhotonOS OVA..."
Clear-Host
if(!(Test-Path ($location + "\" + $ovaname))) {
My-Logger " [WARNING] Unable to find PhotonOS OVA: $($location + "\" + $ovaname)"
$downloadova = if(($downloadova = Read-Host "`nWould you like to download it (Y/N) or press enter to accept default value [Y]") -eq '') {"Y"} else {$downloadova}
if ($downloadova.ToLower() -eq "y") {
My-logger " Downloading $($location + "\" + $ovaname)"
Invoke-WebRequest -Uri $ovasource -OutFile $($BaseDirectory + "\" + $ovaname)
} else {
My-Logger " [ERROR] PhotonOS OVA not available!"
Break
}
}
# Select where to deploy Linux Proxies
Clear-Host
Write-Output "`nWould you like to deploy to prod or test:`n"
Write-Output "1. Production environment - $($Config.Prod.vcserver)"
Write-Output "2. Test environment - $($Config.Test.vcserver)"
$selectenvironment = if(($selectenvironment = Read-Host "`nSelect item or press enter to accept default value [1]") -eq '') {"1"} else {$selectenvironment}
# Populate strings based on selected deployment type
if ($selectenvironment -eq "1") {
My-Logger "Deploying to Production environment $($Config.Prod.vcserver)"
$vbrserver = $Config.Prod.vbrserver
$vcserver = $Config.Prod.vcserver
$network = $Config.Prod.network
$startipaddress = $Config.Prod.startipaddress
$endipaddress = $Config.Prod.endipaddress
$vbruser = $Config.Prod.vbruser
$vcuser = $Config.Prod.vcuser
$vcdatacenter = $Config.Prod.vcdatacenter
$gateway = $Config.Prod.gateway
$dns = $Config.Prod.dns
$domains = $Config.Prod.domains
$ntpserver = $Config.Prod.ntpserver
} elseif ($selectenvironment -eq "2"){
My-Logger "Deploying to Test environment $($Config.Test.vcserver)"
$vbrserver = $Config.Test.vbrserver
$vcserver = $Config.Test.vcserver
$network = $Config.Test.network
$startipaddress = $Config.Test.startipaddress
$endipaddress = $Config.Test.endipaddress
$vbruser = $Config.Test.vbruser
$vcuser = $Config.Test.vcuser
$vcdatacenter = $Config.Test.vcdatacenter
$gateway = $Config.Test.gateway
$dns = $Config.Test.dns
$domains = $Config.Test.domains
$ntpserver = $Config.Test.ntpserver
} else {
My-Logger "[ERROR] Wrong selection..."
}
# Function to build a dynamic menu for infrastructure components (Datacenter, Cluster or Host)
Function Get-HostList {
Param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
$ListItem,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
$GetItem
)
try {
$menu = @{}
For ($i=1;$i -le $ListItem.count; $i++) {
Write-Host "$i. $($ListItem[$i-1].Name)"
$menu.Add($i,($ListItem[$i-1].Name))
}
[int]$ans = if (($ans = Read-Host "`nSelect item or press enter to accept default value [1]") -eq '') {"1"} else {$ans}
$Selection = $menu.Item($ans); Invoke-Expression $($GetItem)
} catch {
Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..."
}
}
# Connect to VMware vCenter Server and Veeam Backup & Replication
#Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
My-Logger "Connecting to VMware vCenter Server $vcserver"
$vicredential = Get-Credential -UserName $vcuser -Message "Enter password for VMware vCenter server: $vcserver"
Connect-VIServer $vcserver -Credential $vicredential -WarningAction SilentlyContinue
My-Logger "Connecting to Veeam Backup & Replication Server $vbrserver"
$vbrcredential = Get-Credential -UserName $vbruser -Message "Enter password for Veeam Backup & Replication server: $vbrserver"
Connect-VBRServer -Server $vbrserver -Credential $vbrcredential -WarningAction SilentlyContinue
# Select if scope of deployment
Clear-Host
Write-Output "`nWould you like to deploy a proxy to:`n"
Write-Output "1. A Single Host"
Write-Output "2. A Cluster (to all hosts in a specific cluster)"
Write-Output "3. A Datacenter (to all hosts in a specific datacenter)"
[int]$option = if(($option = Read-Host "`nSelect item or press enter to accept default value [2]") -eq '') {"2"} else {$option}
if ($option -eq "1") {
$MyQuery = (Get-Datacenter $vcdatacenter | Get-VMHost)
$MyList = "Get-VMHost `$Selection | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }"
My-Logger "Deploying to Host"
} elseif ($option -eq "2"){
$MyQuery = (Get-Datacenter $vcdatacenter | Get-Cluster)
$MyList = "Get-Cluster `$Selection | Get-VMHost | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }"
My-Logger "Deploying to Cluster"
} elseif ($option -eq "3"){
$MyQuery = (Get-Datacenter)
$MyList = "Get-Datacenter `$Selection | Get-Cluster | Get-VMHost | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }"
My-Logger "Deploying to Datacenter"
} else {
My-Logger "Wrong selection..."
}
Clear-Host
Write-Output "Select where to deploy Linux Proxies:`n"
$targethosts = (Get-HostList -ListItem $MyQuery -GetItem $MyList)
# Verify the selected object actually contains powered on hosts
if ($null -eq $targethosts) {
My-Logger "No powered on hosts found..."
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
Break
}
# Check and verify assigned IP range from config.json and verify correct IP format
$deployedproxies = [ordered]@{}
$iplistunverified = @()
$iplistverified = @()
try {
[IPADDRESS]$startip = $startipaddress
[IPADDRESS]$endip = $endipaddress
$iplistunverified += $($startip.GetAddressBytes()[3])..$($endip.GetAddressBytes()[3]) | ForEach-Object {$($startip.GetAddressBytes()[0],$startip.GetAddressBytes()[1],$startip.GetAddressBytes()[2], $_ -join '.')}
# Verify if IP addresses are available
My-Logger "Available IP addresses in range $startip to $endip :"
Clear-Host
Write-Output "Checking available IP addresses..."
foreach ($checkedip in $iplistunverified) {
if (!($(Test-Connection -BufferSize 2 -TTL 5 -ComputerName $checkedip -quiet -count 1))) {
$iplistverified += $checkedip
My-Logger " $checkedip - available"
} else {
My-Logger " $checkedip - in use"
}
}
if ($iplistverified.Count -lt $targethosts.count) {
My-Logger "[ERROR] Not enough IP addresses assigned, $($targethosts.count) IP addresses are needed. Please update config.json"
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
Break
}
} catch {
#Clear-Host
My-Logger "[ERROR] IP address assignment is incorrect, verify in config.json"
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
Break
}
# Deploy PhotonOS, change root password and assign IP settings, add VM as Managed Server and assign Proxy Role
$ipincrement = 0
foreach ($targethost in $targethosts) {
My-Logger "Processing host: $targethost"
$sw = [Diagnostics.Stopwatch]::StartNew()
# Verify if a proxy is already present on host or deploy proxy if it's missing
if (Get-VMHost $targethost | Get-VM | Where-Object { $_.Name -like $($ProxyBaseName + "_*") }) {
My-Logger " A Veeam Proxy is already deployed to host: $targethost"
} else {
$ProxyName = $ProxyBaseName + "_" + ($targethost.name).replace(".","-")
My-Logger " Importing PhotonOS OVA $ProxyName"
$vm = Import-VApp -Source $ovaname -Name $ProxyName -VMHost $targethost -DiskStorageFormat thin
# "VM Netork" will be used to attach NIC, must be present on host
My-Logger " Assigning resources to $ProxyName"
$vm | Set-VM -NumCpu $proxyvcpu -MemoryGB $proxyvram -Confirm:$false
$vm | Get-NetworkAdapter -Name "Network adapter 1" | Set-NetworkAdapter -Portgroup $network -Confirm:$false
$vm | New-AdvancedSetting -Name disk.enableUUID -Value TRUE -Confirm:$false -Force:$true
Start-VM $vm
Wait-Tools -VM $vm
Start-Sleep 10
My-Logger " VMKeystrokes: Sending user account to $vm"
Set-VMKeystrokes -VMName $VM -StringInput $sshuser -ReturnCarriage $true
Start-Sleep 2
My-Logger " VMKeystrokes: Sending inital OVA password to $vm"
Set-VMKeystrokes -VMName $VM -StringInput $ovainitialpassword -ReturnCarriage $true
Start-Sleep 2
My-Logger " VMKeystrokes: Sending initial OVA password to $vm"
Set-VMKeystrokes -VMName $VM -StringInput $ovainitialpassword -ReturnCarriage $true
Start-Sleep 2
My-Logger " VMKeystrokes: Sending new password to $vm"
Set-VMKeystrokes -VMName $VM -StringInput $newsshpassword -ReturnCarriage $true
Start-Sleep 2
My-Logger " VMKeystrokes: Sending new password to $vm"
Set-VMKeystrokes -VMName $VM -StringInput $newsshpassword -ReturnCarriage $true
# Install and update PhotonOS
# https://github.com/Vanarga/vmware/wiki/2a.-Configuring-Photon-OS
# insert 'yum update -y' last in the $cmd section if an automatic update should be performed
Start-Sleep 10
$ipofvm = $iplistverified[$ipincrement]
My-Logger " Configuring $ProxyName with IP: $ipofvm"
$cmd = @"
yum install nano perl mlocate -y
hostnamectl set-hostname $ProxyName
iptables -A INPUT -p tcp --match multiport --dports 2500:3300 -j ACCEPT
iptables -A OUTPUT -p icmp -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables-save >/etc/systemd/scripts/ip4save
timedatectl set-timezone Europe/Stockholm
echo "[Match]" > /etc/systemd/network/10-static-en.network
echo "Name=eth0" >> /etc/systemd/network/10-static-en.network
echo " " >> /etc/systemd/network/10-static-en.network
echo "[Network]" >> /etc/systemd/network/10-static-en.network
echo "Address=$ipofvm/24" >> /etc/systemd/network/10-static-en.network
echo "Gateway=$gateway" >> /etc/systemd/network/10-static-en.network
echo "DNS=$dns" >> /etc/systemd/network/10-static-en.network
echo "Domains=$domains" >> /etc/systemd/network/10-static-en.network
echo "NTP=$ntpserver" >> /etc/systemd/network/10-static-en.network
echo "LinkLocalAddressing=no" >> /etc/systemd/network/10-static-en.network
echo "IPv6AcceptRA=no" >> /etc/systemd/network/10-static-en.network
chmod 644 /etc/systemd/network/10-static-en.network
systemctl restart systemd-networkd.service
systemctl daemon-reload
"@
My-Logger " Invoking script on $ProxyName"
Invoke-VMScript -VM $VM -ScriptText $cmd -GuestUser $sshuser -GuestPassword $newsshpassword -ScriptType Bash
# Add Proxy VM to VBR as managed server and add proxy role
if ((Get-PSSnapin VeeamPSSnapin).Version.Major -ge 10) {
My-Logger " Adding $ipofvm as a Veeam Managed Server"
Add-VBRLinux -Name $ipofvm -SSHUser $sshuser -SSHPassword $newsshpassword -Description "Linux Server based on PhotonOS 3.0 Revision 2"
My-Logger " Adding Backup Proxy Role"
Add-VBRViLinuxProxy -Server (Get-VBRServer -Name $ipofvm -Type Linux) -Description "Linux Backup Proxy based on PhotonOS 3.0 Revision 2" -MaxTasks $proxyvcpu -Force
My-Logger " Veeam Proxy was deployed to host: $targethost"
} else {
My-Logger " [ERROR] You need at least Veeam Backup & Replication version 10 to add a Linux Proxy"
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
Break
}
# Collect information on Proxies in hashtable for future use
$deployedproxies.add($proxyname,$ipofvm)
$ipincrement++
$ipofvm = $null
My-Logger " Deployment of proxy took: $($sw.Elapsed.Minutes) minutes and $($sw.Elapsed.Seconds) seconds"
}
}
Clear-Host
if (($deployedproxies).Count -eq 0) {
My-Logger "[ERROR] Something went wrong, no proxies were deployed!"
My-Logger "Check log file $verboseLogFile"
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
} else {
My-Logger "The following proxies were deployed:"
My-Logger $deployedproxies
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
}
Config.json
{
"General": {
"location": "C:\\temp",
"ovasource": "http://dl.bintray.com/vmware/photon/3.0/Rev2/ova/photon-hw13_uefi-3.0-9355405.ova",
"ovaname": "photon-hw13_uefi-3.0-9355405.ova",
"ProxyBaseName": "VeeamProxy",
"ProxyvCPU": "4",
"ProxyvRAM": "8",
"sshuser": "root",
"ovainitialpassword": "changeme",
"newsshpassword": "newsecretpassword"
},
"Test": {
"vbrserver": "10.10.50.211",
"vcserver": "10.10.50.214",
"network": "VM Network",
"startipaddress": "10.10.50.150",
"endipaddress": "10.10.50.160",
"vbruser": "Administrator",
"vcuser": "administrator@vsphere.local",
"vcdatacenter": "Datacenter",
"gateway": "10.10.50.1",
"dns": "10.10.0.2",
"domains": "mydomain.local",
"ntpserver": "se.pool.ntp.org"
},
"Prod": {
"vbrserver": "10.10.10.30",
"vcserver": "10.10.10.90",
"network": "Prod",
"startipaddress": "10.10.10.150",
"endipaddress": "10.10.10.160",
"vbruser": "Administrator",
"vcuser": "administrator@vsphere.local",
"vcdatacenter": "Datacenter",
"gateway": "10.10.10.1",
"dns": "10.10.0.2",
"domains": "mydomain.local",
"ntpserver": "se.pool.ntp.org"
}
}