Dotnet API Projekt Deployment auf einen Ubuntu Server – Teil 2
In diesem Teil wird das Deployment einer Dotnet Applikation auf einen Ubuntu Server automatisiert. Nach dem Builden in Visual Studio soll voll automatisch die aktuelle Version des Programms am Server laufen.
Dotnet API Projekt Deployment auf einen Ubuntu Server – Teil 2
Im letzten Teil habe ich einen lokalen Ubuntu Server aufgesetzt über den ich während der Entwicklung mein .NET Programm hosten werden. Nun möchte ich den Arbeitsaufwand mit dem deployen und starten der Applikation so niedrig wie möglich halten. Dazu gibt es Software wie den Supervisor.
Supervisor
Für das Deployment verwende ich Supervisor. Supervisor ist eine Monitoring Applikation mit der man .NET Programme starten und überwachen kann. Installiert wird das Tool wie folgt:
sudo apt-get install supervisor
Als nächstes erstelle ich eine Konfiguration für mein Api Projekt. Zur Erinnerung: im letzten Beitrag habe ich dieses manuell in den Ordner /home/werner/api kopiert und von dort gestartet. Eine Supervisor Konfiguration für dieses Szenario sieht wie folgt aus:
sudo nano /etc/supervisor/conf.d/api.conf
Für jede Applikation erstelle ich eine eigene *.conf Datei. Das Api Projekt wird folglich unter api.conf abgelegt:
[program:api] command=/usr/bin/dotnet /home/werner/api/Api.dll directory=/home/werner/api/ autostart=true autorestart=true stderr_logfile=/var/log/api.err.log stdout_logfile=/var/log/api.out.log environment=HOME=/var/www/,ASPNETCORE_ENVIRONMENT=Production user=werner stopsignal=INT stopasgroup=true killasgroup=true
Ich muss die Applikation nun nicht mehr wie früher manuell mit dotnet /home/werner/api/Api.dll starten, sondern kann dafür den Supervisor verwenden. Der Supervisor wird mit den üblichen System-Kommandos gestartet, gestoppt oder neu gestartet:
sudo service supervisor start sudo service supervisor stop sudo service supervisor restart
Der eine oder andere wird sich nun fragen: gut, was bringt mir das, es bleibt trotzdem ein Kommando zum Ausführen? Bei einer einzigen Applikation macht diese Umstellung noch nicht den großen Unterschied. Hostet man mehrere Programme, dann lassen sich diese dann einzeln wie folgt neu starten:
sudo supervisorctl restart api
Aus einem Deployment Skript kann man so (einen laufenden Supervisor vorausgesetzt) einzelne Programme aktualisieren.
Automatisierung
Manuell kann ich nun eine veröffentlichte Applikation auf den Server kopieren und den Supervisor mit einem Befehl auffordern diese neu zu starten. Das wäre auf Dauer recht langweilig, wir brauchen eine Automatisierung. Die Idee ist folgende: in einem Post-Build Kommando soll Visual Studio nach dem Bauen der Version automatisch die Dateien auf den Server kopieren und die laufende Applikation aktualisieren. Ich möchte auf Knopfdruck ein aktuelles Testsystem.
PowerShell Remote Zugriff auf Ubuntu
In meinem Setup übernimmt ein PowerShell Skript die Automatisierung. Damit das klappt muss ich über die PowerShell remote auf den Ubuntu Server zugreifen. Dort habe ich bereits im ersten Teil PowerShell installiert. Am Windows System benötigt man als Voraussetzung SSH. Das kann man wie folgt prüfen:
Ich habe mir nun folgendes PowerShell Script gebaut:
$CurrentDirName=Split-Path -Path (Get-Location) -Leaf $OutputFolder="$env:TEMP\$CurrentDirName" $OutputZipFile="$env:TEMP\$CurrentDirName.zip" $RemoteHost="BENUTZERNAME@IPADRESSE" $RemoteLocation="/home/BENUTZERNAME/" "CurrentDirName: " + $CurrentDirName "OutputFolder: " + $OutputFolder "OutputZipFile: " + $OutputZipFile "RemoteHost: " + $RemoteHost "RemoteLocation: " + $RemoteLocation
Zuerste definierte ich einige Variablen und gebe diese zu Debugzwecken aus. Der zweite Block kann, sollte das Skript mal laufen, entfernt werden. Platzhalter habe ich groß geschrieben zum Beispiel BENUTZERNAME. Diese solltest du mit deinen eigenen Werte vom Ubuntu System ersetzen. Ideal wäre, wenn diese Secrets noch außerhalb vom Script gespeichert werden würden.
# publish project and generate zip dotnet publish -o $OutputFolder Compress-Archive -Path $OutputFolder -Update -DestinationPath $OutputZipFile Remove-Item $OutputFolder -recurse # copy local files to server scp $OutputZipFile $RemoteHost":"$RemoteLocation
In diesen beiden Blöcken wird zuerst die .NET Applikation gepublished, der Ordner gezippt und der gepublishted Ordner im Temp Verzeichnis gelöscht. Im zweiten Block wird die erstellte Zip Datei auf den Ubuntu Server kopiert.
# unzip and restart supervisor $username = "BENUTZERNAME" $password = ConvertTo-SecureString "PASSWORT" -AsPlainText -Force $keyfile = "C:\Users\WINDOWSBENUTZER\.ssh\openssh" $cred = New-Object System.Management.Automation.PSCredential($username, $password) $ssh = (New-SSHSession -ComputerName IPADRESSE -Credential $cred -Keyfile $keyfile -AcceptKey -ErrorAction SilentlyContinue).SessionId $stream = New-SSHShellStream -SessionId $ssh "stop supervisor" Invoke-SSHStreamExpectSecureAction -Command "sudo service supervisor stop" -ShellStream $stream -ExpectString "[sudo] Password" -SecureAction $password | Out-Null "remove dir $CurrentDirName" Invoke-SSHStreamExpectSecureAction -Command "sudo rm -rf $CurrentDirName*" -ShellStream $stream -ExpectString "[sudo] Password" -SecureAction $password | Out-Null "unzip $CurrentDirName.zip" Invoke-SSHCommand -Command "unzip -q $CurrentDirName.zip" -SessionId $ssh | Out-Null "start supervisor" Invoke-SSHStreamExpectSecureAction -Command "sudo service supervisor start" -ShellStream $stream -ExpectString "[sudo] Password" -SecureAction $password | Out-Null "done"
Nun kommt der Teil, der auf dem Ubuntu Server ausgeführt wird. Im ersten Block werden einige Variablen definiert und eine SSH Session aufgebaut. Im zweiten Block werden Shell Kommandos direkt auf dem Ubuntu System ausgeführt. Der Supervisordienst wird gestoppt, das Verzeichnis mit der alten Version vom .NET Programm gelöscht, dann das zuvor kopierte ZIP entpackt und zuletzt wird der Supervisordienst neu gestartet.
Probleme
Sollte man auf folgendes Problem stoßen, dann hilft diese Anleitung von Microsoft:
New-PSSession: [192.168.0.190] The background process reported an error with the following message: The SSH client session has ended with error message: subsystem request failed on channel 0.
Apps -> Optionale Features -> Optionales Feature hinzufügen
OpenSSH-Client und OpenSSH-Server installieren, danach mit einer Admin PowerShell Konsole das sshd Service neu starten:
Restart-Service -Name sshd
Im %ProgramData%\ssh Verzeichnis findet man nun die Konfigurationsdatei für ssh.
Visual Studio
Mein Deployment Script wird nun immer nach einem erfolgreichen Build Vorgang automatisch ausgeführt. Das kann man in Visual Studio bei den Projektsettings unter dem Punkt Events (Pos-Build event) definieren. In meinem Fall wird die Powershell (pwsh) mit dem Script als Argument gestartet. Im Ausgabefenster von Visual Studio sieht man nun auch die Ausgabe des Scripts.
Tipp! Würde man nun bauen, dann hätte man eine Endlosschleife von Builds gestartet. Warum? Das Build Script Published das Projekt, dabei wird es gebaut und wiederrum das Script ausgeführt. Das verhindert man mit folgender Änderung am Projektfile:
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(BuildingInsideVisualStudio)' == 'true'"> <Exec Command="pwsh .\deployment.ps1" /> </Target>
Die Condition verhindert, dass das Event von der Kommandozeile ausgeführt wird. Es funktioniert nun nur noch über Visual Studio.
Fazit
Automatisierung ist bei repetitiven Tasks sehr wichtig. Als Entwickler möchte ich mich um den Code kümmern und nicht darum, wie dieser jedes Mal am Entwicklungssystem aktualisiert wird. Powershell und isual Studio sind zwei wichtige Tools um jeden beliebigen Task zu automatisieren. Mit meinem Script wird der externe Ubuntu Server nun nach jedem Build aktualisiert.