Running up TeamCity build Agents on HyperV

I had to run up a bucket load of build servers on a HyperV environment the other day so decide to automate the process a little.

I have also started using RAM disks for the agents too, to speed them up.

Step for the build server load were:

  1. Install OS (Win Server 2012 R2)
  2. Install SQL 2012 Express
  3. Install VS 2015
  4. Install build agent and point it at the TC Build agent on Drive E:
  5. Install RAMDisk (here)
  6. Run windows updates

If you are unsure where to get the Build Agent installer from click the agents tab in TeamCity and there is a link in the top left.

InstallTeamCityBuildAgent

At this point move the build agent to the RAM disk by:

  1. Stop Team City Build agent service
  2. Change the drive letter of E: to F:
  3. create a RAM drive as E: with image file on F and save to disk on shutdown
    1. I used 5Gb drive and it handles ok
  4. Copy the build agent folder from F to E
  5. Start Build agent service

After this i also installed IIS and disabled the firewall

Lastly sysprep the server and check OOBE/Shutdown

Then delete the VM I was using and the VHDX file  that was generated keep for using as a template, I backed it up to use on other hosts as well in future.

Then wrote the below script that run’s on the host, it does the following for me:

  1. Creates a new VHDX using a diff based on the original (makes run up really fast)
  2. Creates a VM that uses that VHDX
  3. Sets some values that I can set in the New-VM Command
  4. Changes the network config of the guest

 


param(
[string]$MachineName="MyMachine",
[string]$IPv4Address="10.0.0.20"
)
Function Set-VMNetworkConfiguration {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,
Position=1,
ParameterSetName='DHCP',
ValueFromPipeline=$true)]
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName='Static',
ValueFromPipeline=$true)]
[Microsoft.HyperV.PowerShell.VMNetworkAdapter]$NetworkAdapter,

[Parameter(Mandatory=$true,
Position=1,
ParameterSetName='Static')]
[String[]]$IPAddress=@(),

[Parameter(Mandatory=$false,
Position=2,
ParameterSetName='Static')]
[String[]]$Subnet=@(),

[Parameter(Mandatory=$false,
Position=3,
ParameterSetName='Static')]
[String[]]$DefaultGateway = @(),

[Parameter(Mandatory=$false,
Position=4,
ParameterSetName='Static')]
[String[]]$DNSServer = @(),

[Parameter(Mandatory=$false,
Position=0,
ParameterSetName='DHCP')]
[Switch]$Dhcp
)

$VM = Get-WmiObject -Namespace 'root\virtualization\v2' -Class 'Msvm_ComputerSystem' | Where-Object { $_.ElementName -eq $NetworkAdapter.VMName }
$VMSettings = $vm.GetRelated('Msvm_VirtualSystemSettingData') | Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
$VMNetAdapters = $VMSettings.GetRelated('Msvm_SyntheticEthernetPortSettingData')

$NetworkSettings = @()
foreach ($NetAdapter in $VMNetAdapters) {
if ($NetAdapter.Address -eq $NetworkAdapter.MacAddress) {
$NetworkSettings = $NetworkSettings + $NetAdapter.GetRelated("Msvm_GuestNetworkAdapterConfiguration")
}
}

$NetworkSettings[0].IPAddresses = $IPAddress
$NetworkSettings[0].Subnets = $Subnet
$NetworkSettings[0].DefaultGateways = $DefaultGateway
$NetworkSettings[0].DNSServers = $DNSServer
$NetworkSettings[0].ProtocolIFType = 4096

if ($dhcp) {
$NetworkSettings[0].DHCPEnabled = $true
} else {
$NetworkSettings[0].DHCPEnabled = $false
}

$Service = Get-WmiObject -Class "Msvm_VirtualSystemManagementService" -Namespace "root\virtualization\v2"
$setIP = $Service.SetGuestNetworkAdapterConfiguration($VM, $NetworkSettings[0].GetText(1))

if ($setip.ReturnValue -eq 4096) {
$job=[WMI]$setip.job

while ($job.JobState -eq 3 -or $job.JobState -eq 4) {
start-sleep 1
$job=[WMI]$setip.job
}

if ($job.JobState -eq 7) {
write-host "Success"
}
else {
$job.GetError()
}
} elseif($setip.ReturnValue -eq 0) {
Write-Host "Success"
}
}

Write-Host $MachineName

New-VHD –Path “D:\Hyper-V\Diff.$MachineName.vhdx” –ParentPath “D:\Hyper-V\agent_template2.VHDX” –Differencing

New-VM -Name $MachineName -MemoryStartupBytes 6024000000 -Generation 2 -BootDevice VHD -VHDPath “D:\Hyper-V\Diff.$MachineName.vhdx” -SwitchName "10.0.0.xx"

Set-VM -Name $MachineName -DynamicMemory -ProcessorCount 8
Write-Host $IPv4Address

Get-VMNetworkAdapter -VMName $MachineName -Name "Network Adapter" | Set-VMNetworkConfiguration -IPAddress $IPv4Address -Subnet 255.255.255.0 -DNSServer 10.0.0.15 -DefaultGateway 10.0.0.1

The above command takes a parameter of the new machine name and the IP you want to give the server, I have hard codded the subnet, gateway and DNS, but these should probably be made parameters too, depending on your environment.

After this i just have to login to the agents and domain join them after they are spun up. I used to use VMM that would domain join out-of-the-box, but it looked painful to do from a script on the host so have left it.

An also authorize them on the TeamCity server.

 

Connecting TeamCity to GitLab with a self-signed SSL

So I spent hours today beating my head against a wall and cursing JRE, so a pretty normally day for me.

I had to connect our TeamCity server to the GitLab server, the GitLab server uses a SSL cert that was generate from the AD Domain CA, so is trusted by all the domain machines. Our TC server is on the domain as well and when connecting to the https site it comes up as green.

However when connecting to git through TeamCity it is running inside JRE which for some reason doesn’t use the machine trusts, it has it’s own cert store you need to add the cert too.

Here’s the error i was facing:

 

List remote refs failed: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

 

To test the trust from JRE you need to run this

java SSLPoke git.mycompany.local 443

Where git.mycompany.local is your gitlab server

You can get the sslpoke class here

if its untrusted you will see an error here.

You can use your web browser to export the public key.

GitLabUntrustedSSLCertificate.PNG

Most docs tell me that you can export your root CA public cert, but this didn’t work for me, I actually had to export the specific cert for this site.

Then use this command line to import the cert into JRE and restart TeamCity.

C:\TeamCity\jre\bin>C:\TeamCity\jre\bin\keytool.exe -importcert -trustcacerts -file C:\MyGitLabSSLCert.cer -alias MyGitLabSSLCert -keystore “C:\TeamCity\jre\lib\security\cacerts”

After this we are in business!