Setting up the lab in Ravello – Part 1 : the jumphost

This entry is part 1 of 1 in the series Ravello Cloud Lab 1.0

In these series we will create a lab with multiple components, a jumphost, vcsa, esxi, a vsan enabled cluster, nsx and maybe more. The aim of the series is to learn about deploying all components onto the Ravello cloud.

Part 1: Creating the Jumphost

Part one of the series will be about creating the jumphost. I’m looking at a linux system as we do not need any license to run it and it is already available in Ravello

Creating the Ravello Application

The first step is to create an application. We will create a 0.1 version of the LAB:

Creating the Jumphost VM in the Application

Drag a ‘Xubuntu Desktop 14.04.1 with qemu-kvm pre-installed’ onto the Canvas. Once the VM has been dragged onto the Canvas, there will be an error: ‘Key pair must be supplied’

You can see that the error has its source on the General tab. To correct this a Key Pair must be created.

On the General tab – Cloud Init Configuration – Key Pair

Select the Option: Create a Key Pair

In the following screenshot you can see that I already created a Key Pair

Once created the private key will be available for download. To be able to use the private key with a ssh session from putty, you will need to convert the key.pem to key.ppk. Open puttygen and load the key.pem file and save the file as key.ppk.

Now that we have created our key pair we can save the VM and the error should disappear.

On the System tab, change the # CPU to 2 and the memory to 3 GB.

On the Disks and NICs tab we leave everything as is.

On the Services tab, Add Supplied Service. We will use this Service to connect to the VM via RDP.

A second service will be added. I changed the name to RDP and chose protocol RDP which sets the Port to 3389.

We are ready to publish the application:

Change the ‘Schedule application to stop in:’ countdown timer to ‘04:00hr’. This will give us the time to update and change the VM to our needs.

Publish will power on the VM. When Powered on we will have access to the Console. Powering on the VM takes a couple of minutes.

Customizing the Jumphost VM

Upgrades

The Console will open in a new tab. The initial password for this VM is ‘ravelloCloud’.

The first thing we will do is upgrade the VM to the latest release available. Open the ‘Byobu Terminal’.

Run the command ‘sudo apt-get update && sudo apt-get upgrade’ and confirm you want to upgrade all proposed packages. I tried do-release-upgrade first, which failed because of an apt dependency.

sudo apt-get update && sudo apt-get upgrade

Now we are ready to upgrade to the lastest release. Confirm to all new version configuration files from the package maintainer. In the end all obsolete packages can be removed and reboot when finished.

Run the command ‘sudo apt-get dist-upgrade’ and confirm you want to upgrade all proposed packages. Now your system will be fully up-to-date.

XRDP 0.9.x

Install xrdp 0.9.x so that we can connect via RDP. This will be a more pleasant way of working.

We will add a PPA (Personal Package Archive) to add the package source location to the /etc/apt/sources.list file. This will enable updates through the apt update process. We will install the latest version of xrpd from this location. At the time of writing the version integrated is in the ubuntu sources is 0.6.x. The latest stable version has quite some enhancements like shared clipboard support.

sudo add-apt-repository ppa:hermlnx/xrdp
sudo apt-get update
sudo apt-get install
xrdp xrdp -v

The version installed at the time of writing is 0.9.4

Create xsession file with contents xfce4-session. The latest xrdp version should be detecting the desktop environment by default but in my case it did't and wouldn't work without the following xsession file.

cd $HOME
echo xfce4-session > ~/.xsession

Generate new certificate and key

openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365

Update XRDP to use the new certificates

cd /etc/xrdp sudo vi xrdp.ini

Change the following lines to use the certificate and key generated

certificate=/home/ubuntu/cert.pem
key_file=/home/ubuntu/key.pem
cd /etc/X11/
sudo vi wrapper.config

Change the following line

allowed_users=anybody

Reboot the VM Now you can access the VM through RDP. You will need to confirm the self-signed cert as it has not been signed by a trusted root CA.

Powershell Core

Import the public repository GPG keys

curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -

Register the Microsoft Ubuntu repository

curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list

Update the list of products

sudo apt-get update

Install PowerShell

sudo apt-get install -y powershell

Start PowerShell

pwsh

PowerCLI 10

Install the PowerCLI module from the PowerShell Gallery

Install-Module -Name VMware.PowerCLI -scope CurrentUser

Verify PowerCLI version

Get-PowerCLIVersion

OPTIONAL: Opt-out from the Customer Experience Improvement Program (CEIP)

Set-PowerCLIConfiguration -scope user -ParticipateCeip $false

OPTIONAL: Do not display the warning about using self-signed certificates

Set-PowerCLIConfiguration -InvalidCertificateAction Ignore

OPTIONAL: Visual Studio Code

Installing Microsoft Visual Studio Code can be usefull for creating scripts that will/could be used within the environment.

curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt-get update
sudo apt-get install code # or code-insiders

The next part will be setting up the ESXi machines and VCSA.

Many thanks to:

List registered SPNs in Active Directory: pimped

List all registered SPNs in Active Directory: pimped

This will go and poll all your registered SPNs in Active Directory and write them to a file. It accepts Debug, Log_Dir and Log_FileName as parameters.

Source: http://social.technet.microsoft.com/wiki/contents/articles/18996.list-all-spns-used-in-your-active-directory.aspx

<#
	.Synopsis
	Go and poll all your SPNs registered in Active Directory and write them to a file

	.Description
	Go and poll all your SPNs registered in Active Directory and write them to a file.

	.Author
	Harold Preyers

	.Parameter Debug
	The Debug parameter shows output to the screen.

	.Parameter Log_Dir
	Supply your own LogFile location.

	.Parameter Log_FileName
	Supply your own LogFile location. Don't add a file extension, the script will construct a filename based on the time of launch and add a .log extension

	.Example
	# Start SPN_List.ps1 without debug output based on the default log file location
	./SPN_List.ps1

	.Example
	# Start storage vmotions with debug output based on the default log file location
	./SPN_List.ps1 -Debug $true 

	.Example
	# Start storage vmotions with debug output based on custom log file location
	./SPN_List.ps1 -Debug $true -Log_Dir "C:\Scripts\SPN_List\Logs" -Log_FileName SPN_list 

#>

# Accept parameters
[cmdletBinding()]
	Param (
		[Bool]$Debug = $false				# Write output to console
		[string]$Log_Dir,					# Log Directory location
		[string]$Log_FileName,				# Log Filename
	)

#Variables
$Break = "-------------------------"

# Logging
$Log_Dir="C:\Scripts\Storage vMotion\Logs"
$Log_FileName="svMotion_log"
$Log_Breaker="##########################"
$global:File=""

# Create the necessary directories if necessary
If(!(Test-Path -Path $Log_Dir )){
	New-Item -ItemType directory -Path $Log_Dir
}

Function Initialize {
	# Logging parameters
	$Date = (get-date).tostring("yyyyMMdd_HHmmss")
	$global:File = $LogDir + "\" + $Log_FileName + "_" + $Date + ".log"
	If ($Debug) {Write-Host "The filename is: $global:File"}

	# Initialize log
	$Log_Breaker | Out-File "$global:File" -Append
	" LogFile:  $global:File" | Out-File "$global:File" -Append
	" LogDate:  $Date" | Out-File "$global:File" -Append
	" CSV File: $CSV_File" | Out-File "$global:File" -Append
	$Log_Breaker | Out-File "$global:File" -Append
	Add-Content -Path c:\temp\SPN_List.txt -Value "`n"
}

#Set Search
cls
$search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$search.filter = “(servicePrincipalName=*)”
$Results = $search.Findall()

#list results
Foreach($Result in $Results)
{
	$userEntry = $result.GetDirectoryEntry()

	$Output = "Object Name = " + $userEntry.name
	If ($Debug) {Write-Host $Output -backgroundcolor "yellow" -foregroundcolor "black"}
	$Output | Out-File c:\temp\SPN_List.txt -Append
	$Break | Out-File c:\temp\SPN_List.txt -Append

	$Output = "DN = " + $userEntry.distinguishedName
	If ($Debug) {Write-host $Output}
	$Output | Out-File c:\temp\SPN_List.txt -Append

	$Output = "Object Cat. = " + $userEntry.objectCategory
	If ($Debug) {Write-host $Output}
	$Output | Out-File c:\temp\SPN_List.txt -Append
	$Break | Out-File c:\temp\SPN_List.txt -Append

	$Output = "servicePrincipalNames"
	If ($Debug) {Write-host $Output}
	$Output | Out-File c:\temp\SPN_List.txt -Append
	$Break | Out-File c:\temp\SPN_List.txt -Append

	$i=1

	foreach($SPN in $userEntry.servicePrincipalName) {
		If (($i).tostring().length -le 1) {
			$preZero = "0"
		}
		Else {
			$preZero = ""
		}
		$Output = "SPN(" + $preZero + $i + ") = " + $SPN
		If ($Debug) {Write-host "SPN(" $preZero$i ") = " $SPN}
		$Output | Out-File c:\temp\SPN_List.txt -Append
		$i+=1
	}
	If ($Debug) {Write-host ""}
	$Break | Out-File c:\temp\SPN_List.txt -Append
	Add-Content -Path c:\temp\SPN_List.txt -Value "`n"
}

 

Powershell Test AlwaysOn

Powershell Test AlwaysOn

This script checks which node is the primary in the SQL AlwaysOn cluster. A test database has been added to see if that database is writable. All actions are logged to a log file.

This script is not to be run in a production environment. The purpose of this script is to see if the SQL AlwaysOn Availability Group has done a failover. It also checks if a database within this AG is writable and if not how long it has been offline.
The part to check which node is the primary has been found on a blog, which I would have given the credits if I remembered where I found it.

<#
	.Synopsis
	Monitors an SQL AlwaysOn cluster and tries to write to a database on this AlwaysOn cluster.
 
 	.Description
	Monitors an SQL AlwaysOn cluster and tries to write to a database on this AlwaysOn cluster.
 
	.Example
	# No Examples available
#>

# Import SQL module
Import-Module sqlps

# Connection parameters
$ServerInstance="database_server" # if you are running SQL on a custom port it should be "database_server,port"
$Database="TEST_AlwaysOn"

# Logging
$FileName="PrimaryLOG"
$LogDir="E:\SQL_script"
$LogBreaker="##########################"
$global:File=""
$MaxLogEntries = 1000
$LogEntries = 1
$Failed = 0
$Sleep = 900

Function Initialize {
	# Logging parameters
	$LogFileDate = Get-Date -Format FileDateTime
	$global:File = $LogDir + "\" + $FileName + "_" + $LogFileDate + ".log"
	If ($Debug) {write "The filename is: $global:File"}
 
	# Initialization (empty table)
	$SQLQuery=$("TRUNCATE TABLE Test_AlwaysOn.dbo.Test_Table")
	invoke-sqlcmd -query $SQLquery -serverinstance $ServerInstance -database $Database
	#Start-Sleep -Seconds 3
 
	# Initialize log
	$LogBreaker | Out-File "$global:File" -Append
	" LogFile: $global:File" | Out-File "$global:File" -Append
	" LogDate: $Date" | Out-File "$global:File" -Append
	" Server: $ServerInstance" | Out-File "$global:File" -Append
	" Database: $Database was truncated" | Out-File "$global:File" -Append
	$LogBreaker | Out-File "$global:File" -Append
}

Initialize

# MAIN
# While statement is built to always run
While ($true) {
	$Date = (get-date).ToString("yyyy-MM-dd HH:mm:ss.fff")
	# Generate at least one entry/day to see if I'm alive
	If ((get-date -Format hhmmss) -eq 000000) {
		$AddToFile = "$Date" + " It's a new day, it's a new dawn"
		$AddToFile | Out-File "$global:File" -Append
	}
	$Run++
 
	# Find AlwaysOn Primary
	$SQLQuery=$("SELECT AGC.name, RCS.replica_server_name, ARS.role_desc, AGL.dns_name " +
	"FROM sys.availability_groups_cluster AS AGC " +
	"INNER JOIN sys.dm_hadr_availability_replica_cluster_states AS RCS " +
	"ON RCS.group_id = AGC.group_id " +
	"INNER JOIN sys.dm_hadr_availability_replica_states AS ARS " +
	"ON ARS.replica_id = RCS.replica_id " +
	"INNER JOIN sys.availability_group_listeners AS AGL " +
	"ON AGL.group_id = ARS.group_id " +
	"WHERE ARS.role_desc = 'PRIMARY' ;")
	$AGResult=invoke-sqlcmd -query $SQLquery -serverinstance $ServerInstance -database $Database
	$PrimaryReplica = $AGResult[1]
 
	If ($PrimaryReplica -ne $PrimaryReplicaLastRun) {
		# Filter first run
		If ($PrimaryReplicaLastRun -eq $null) {
			$AddToFile = "$Date" + " AG is $PrimaryReplica"
			$AddToFile | Out-File "$global:File" -Append
			$LogEntries++
		}
		Else {
			# Write to log
			$AddToFile = "$Date" + " AG changed from $PrimaryReplicaLastRun to $PrimaryReplica"
			$AddToFile | Out-File "$global:File" -Append
			$LogEntries++
		}
	}

	# Write to table to see if it is available
	$SQLQuery=$("INSERT INTO Test_AlwaysOn.dbo.Test_Table (Test_Run,AG_Primary,Test_Time) " +
	"VALUES ('$Run','$PrimaryReplica','$Date'); " )
	invoke-sqlcmd -query $SQLquery -serverinstance $serverinstance -database $database

	# Read the last insert via $Run
	$SQLQuery=$("SELECT Test_Run,AG_Primary,Test_Time FROM Test_AlwaysOn.dbo.Test_Table where Test_run = $Run")
	$SelectResult=invoke-sqlcmd -query $SQLquery -serverinstance $serverinstance -database $database
	If ($Debug) {write "SelectResult: "$SelectResult}
	$AG_Primary = $SelectResult[1]
	$Test_Time = $SelectResult[2].ToString("yyyy-MM-dd HH:mm:ss.fff")

	# Write to log
	If ($Test_Time -eq $Date) {
		If ($Failed -gt 0) {
			$AddToFile = "$Date Database was not writable between $Test_TimeLastSucceed and $Date, we tried $Failed times."
			$AddToFile | Out-File "$global:File" -Append
			$LogEntries++
			$OfflineHR = $Date.Substring(11,2) - $Test_TimeLastSucceed.Substring(11,2)
			If ($Debug) {write $OfflineHR}
			$OfflineMin = $Date.Substring(14,2) - $Test_TimeLastSucceed.Substring(14,2)
			If ($Debug) {write $OfflineMin}
			$OfflineSec = $Date.Substring(17,5) - $Test_TimeLastSucceed.Substring(17,5)
			If ($Debug) {write $OfflineSec}
			$Offline = $OfflineHR*3600 + $OfflineMin*60 + $OfflineSec
			If ($Debug) {write $Offline}
			$AddToFile = "$Date Database was not writable for $Offline seconds."
			$AddToFile | Out-File "$global:File" -Append
			$LogEntries++
			$Failed = 0
		}
		$Test_TimeLastSucceed = $Test_Time
		$Test_Time = $null
	}
	Else {
		$Failed++
	}
	Start-Sleep -Milliseconds $Sleep

	# Check if the # entries have reached the maximum entries
	If ($LogEntries % $MaxLogEntries -eq 0) {
		$LogBreaker | Out-File "$global:File" -Append
		$AddToFile = "$Date" + " Log limits have been reached. A new log file will be started."
		$AddToFile | Out-File "$global:$File" -Append
		Initialize
	}

	# Keep the values of the variables of the last run
	$PrimaryReplicaLastRun = $PrimaryReplica
}