Octopus Deploy and Powershell
Needed to deploy a pretty cool dynamic proxy called traefik, which is built on golang and can be deployed as a single binary and along with some simply configs can take a lot of difficulties of say an nginx reverse proxy out of the way.
Key points
- Service is running on Windows so it needs to be a Windows Service (I would have preferred to use Linux and Systemd, but that option is not available at this time)
- The golang binary for Traefik is not a service but a command line program, but you can servicify this with the Non-Sucking Service Manager (nssm). The name is completely appropriate as writing Windows Services is not great.
Stop service and delete
$Service = Get-WmiObject -Class Win32_Service -Filter "Name='Traefik'"
if ( $Service ) {
$Service.StopService()
}
Get—Process "traefik windows—amd64" -ErrorAction SilentlyContinue | Stop-Process -PassThru -Force
Get—Process "traefik" -ErrorAction SilentlyContinue | Stop-Process -PassThru -Force
# Delete the service entry
$Service = Get-WmiObject -Class Win32_Service -Filter "Name='Traefik'"
if ( $Service ) {
$Service.Delete()
}
Install NSSM
When you install a program to be servicified by NSSM it will add a registry key that sets the NSSM path as a source for the Windows Event Log. This means that you can stop the service, but to delete the nssm.exe
file you will need to restart the EventLog
service, once this is done the event log source is no longer locked and the nssm.exe
file can be deleted.
$Folder = $TraefikInstallPath
if (Test-Path -Path $Folder) {
Set-Location $Folder
if (Test-Path -Path "nssm") {
Start-Process "nssm" -Wait -ArgumentList "stop Traefik"
Start-Process "nssm" —Wait -ArgumentList "remove Traefik confirm"
}
Set-Location 'C:\'
Restart-Service —Name "EventLog" -Force
Remove-Item —Path $Folder -Force —Recurse -Verbose
}
Write out configuration files from templates
Here the configuration files are stored in Octopus variables {TLS.Certificate, TLS.Key}
configured to for each enviroment etc and then we will write the variable out to a file on the machines
$hostName = [Environment]::MachineName
$certFileName = '{0}.cer' —f $hostName
$keyFileName = '{0}.key' -f $hostName
Set-Location $TraefikInstallPath
$OctopusParameters["TLS.Certificate"] | Out-File —FilePath $certFileName —Force
$OctopusParameters["ILS.Key"] | Out-File —FilePath $keyFileName -Force
Write config
Again using the technique above and writing Octopus variables as templates to be written out as configuration files for Traefik
to use.
Be extremely careful to check what PowerShell writes characters out here as in pre Powershell 6 the default -f
output is ucs-2 encoding
which is going to make standard golang toml processing blow up. Then you may think it is ok to specify the encoding to utf8
instead with a parameter like so -encoding utf8
which will write the output in what golang should work with ok, but now again Powershell is going to be weird because that utf8
is going to have BOM
or byte order markings in the bytes and again golang blows up. A way around all this chaos at least in Windows (arrgggh) is to specify the encoding as ascii
and be done. Now the golang toml readers of Traefik will no longer get byte confused and blow up.
$hostFullName = [System.Net.DNS]::GetHostByName($Null).HostName
$hostShortName = [Environment]::MachineName
Set-Location $TraefikInstallPath
$OctopusParameters["Traefik.StartFile"] -replace "`n`", "`r`n" | Out-File -FilePath 'traefik.toml' -Force -encoding ascii
$OctopusParameters["Traefik.ConfigFile] -f $hostFullName, $hostShortName -replace "`n`", "`r`n" | Out-File -FilePath 'config.toml' -Force -encoding ascii
Traefik example file - traefik.toml
[log]
# level = "DEBUG"
[api]
dashboard = true
[entryPoints]
[entryPoints.websecure]
address = ":17443"
[entryPoints.websecure.http.tls]
[providers.file]
filename = "config.toml"
watch = true
Traefik provider config example file - config.toml
[http.routers]
[http.routers.Router-1]
# By default, routers listen to every entry points
entryPoints = ["websecure"]
rule = "PathPrefix(`/<WebAppPath>`)"
service = "TheService"
[http.serversTransports.ServersTransport1]
disableHTTP2 = true
[http.middlewares]
[http.services.TheService.loadBalancer]
serversTransport = "ServersTransport1"
[[http.services.TheService.loadBalancer.servers]]
url = "https://fully-qualified-domain-name:1234"
[[tls.certificates]]
certFile = "{1}.cer"
keyFile = "{1}.key"
[[tls.stores]]
[tls.stores.default]
[tls.stores.default.defaultCertificate]
certFile = "{1}.cer"
keyFile = "{1}.key"
Here need to disable HTTP2 for Windows Authentication to flow through the traefik proxy
Setup Service installation
To properly get nssm
to recognize and register the program as a service you should provide the full path to exe, otherwise it will register a blank service point for nssm
which means it appears as though the service is running without error, but nothing is actually running other than the nssm
service with no end point
Set-Location $TraefikInstallPath
$installPath = Join-Path -Path $TraefikInstallPath -ChildPath "traefik.exe"
$installArgs = "install Traefik {0}" -f $installPath
Start—Process "nssm" —-Wait -Argumenthist $installArgs
Start—Process "nssm" -Wait —ArgumentList "start Traefik"
Set-Service -Name "Traefik" -StartupType Automatic