Deployment mit Octopus: ein Musterbeispiel
Ziel:
Deployment eines Binaries mit Konfigurationsfile auf einem Linux-Server
Vorausetzungen:
Binary und Konfigfile werden nach dem Build als Zip-Datei in die Octopus Package-Verwaltung hochgeladen und es wird ein Release erzeugt, zum Beispiel mit GitHub-Actions:
- name: Create a Zip package
uses: OctopusDeploy/create-zip-package-action@v3
with:
package_id: ${{ env.OCTOPUS_PACKAGE_NAME }}
version: ${{ env.PACKAGE_VERSION }}
output_folder: './packages/'
base_path: './myApp'
files: |
*.*
- name: Push a package to Octopus Deploy
uses: OctopusDeploy/push-package-action@v3
with:
packages: |
./packages/${{ env.OCTOPUS_PACKAGE_NAME }}.${{ env.PACKAGE_VERSION }}.zip
- name: Create Release
uses: OctopusDeploy/create-release-action@v3
with:
project: ${{ env.OCTOPUS_PROJECT_NAME }}
release_number: ${{ env.PACKAGE_VERSION }}
package_version: ${{ env.PACKAGE_VERSION }}
Best practice:
PACKAGE_VERSION (und release number) entspricht der Versionsnummer der Software
Sprechender und eindeutiger Name für OCTOPUS_PACKAGE_NAME, so dass es leicht dem Projekt zugeordnet werden kann
Step 1: Environments anlegen
Environments -> Add Environment -> Environments anlegen
Best practice:
Es empfiehlt sich, die Namen nach dem im Unternehmen üblichen Sprachgebrauch anzulegen, zum Beispiel "Live" oder "Prod" oder "Production" etc.
Über das Punkte-Menü neben "Add Environment" können die Umgebungen in die übliche Ablauf-Reihenfolge (zum Beispiel "Test", "Staging", "Production") sortiert werden. Das erleichtert die Übersicht.
Step 2: Installation und Konfiguration des Ziel-Servers als Listening-Agent, sogenannte "Tentacle"
Anmerkung: Octopus erlaubt auch Polling-Tentacle bzw. SSH-Verbindungen, je nach Umgebung - Firewall-Regeln etc. - können diese Varianten die bessere Lösung sein. Beim Listening-Tentacle pushed der Octopus-Server zum Tentacle, beim Polling-Tentacle kontaktiert der Client den Server.
Deployment Targets -> "Add Deployment Target" -> Linux -> Listening Tentacle
Den Thumbprint des Octopus Servers kopieren.
Je nach Linux-Derivat die Tentacle-Software installieren, siehe https://octopus.com/docs/infrastructure/deployment-targets/tentacle/linux
Danach die Konfiguration mit /opt/octopus/tentacle/configure-tentacle.sh starten und durchführen.
Standardmäßig benutzt Octopus den Port 10933, dieser muss auf allen Firewalls zwischen den Servern und auf den Servern direkt freigegeben sein.
Auf dem Octopus-Server die Tentacle-Einrichtung fortführen:
Displayname
Environment -> welches Environment soll auf diesem Server deployed werden dürfen, können auch mehrere sein
Target Tags (auswählen oder neu vergeben) -> Tags, mit denen Server gruppiert werden können (zum Beispiel alle Server für ein Projekt). Ein Server kann mehrere Target Tags haben.
Best practice:
In den Displayname Servername und IP schreiben.
Für die Target Tags eindeutige und sprechende Namen verwenden. "webserver" ist ein eher schlechter Tag, "myProjectWebserver" schon besser.
Step 3: Lifecycle anlegen
Lifecyles -> Add Lifecyle
Lifecycle nach den im Unternehmen üblich Standards mit den für die im Projekt verwendeten Environments anlegen. Eine Phase kann mehrere Environments haben. Es kann bei jedem Environment festgelegt werden, ob automatisch bei Erreichen in dieser Phase deployed werden soll, oder ob manuell. Phasen können auch als optional eingerichtet werden, das heißt sie muss nicht zwingend durchlaufen werden, um auf die nächste Phase deployen zu dürfen.
Hier können auch Retention Policies für nicht mehr benötigte Files am Server und die Releases festgelegt werden, global für den ganzen Lifecyle und/oder pro Phase.
Beispiel (vermutlich in der Praxis nur bedingt sinnvoll, aber zeigt die gebräuchlichsten Möglichkeiten):
Dieser Lifecycle besteht aus den vier Phasen "Development", "Tests", "Staging" und "Live".
In der Phase "Development" wird auf die Umgebungen CI und Develop deployed, auf CI automatisch bei Erstellung des Releases. Es muss auf beide Environments deployed werden, um auf Umgebungen in der nächsten Phase deployen zu dürfen.
In der Phase "Tests" wird auf die Umgebungen Test und QA-Test deployed. Es ist nur ein erfolgreiches Deployment auf einer Umgebung nötig, um auf die nächsten Phase deployen zu dürfen.
Die Phase "Staging" beinhaltet nur das gleichnamige Environment "Staging". Die Phase ist optional, d.h. sie muss nicht durchlaufen werden, um auf die nächste Phase deployen zu dürfen.
Die Phase "Live" hat das Environment "Production".
Step 4: Projekt anlegen
Projects -> Add Project
Best practice:
Bei vielen Projekten bietet sich an, diese noch in Project-Groups zu organisieren. Neue Project-Groups können in der Projekt-Übersicht oben rechts mit "Add Project Group" angelegt werden.
Step 5: Variablen anlegen
Projects -> Projekt auswählen -> Project Variables
Best practice:
1) Passwörter/sensible Daten immer als Sensitive Variable deklarieren, und dann wo nötig einbinden
Beispiel: DB Connection-String
Variable "db_pass": ***
Variable "db_connection": server=server1;port=1234;user=dbuser;password=#{db_pass};database=mydb
--> Passwort ist nirgends sichtbar, die nicht sensitiven Informationen des Connection-Strings können normal editiert werden
2) Variablen wiederverwenden
Variablen sollten so eingerichtet sein, dass gleiche Werte auch nur an einer Stelle gepflegt werden müssen. Wenn die gleichen Variablen in mehreren Projekten benötigt werden (um Beispiel zentraler DB-Server mit vielen Datenbanken), ist es sinnvoll, diese in einem zentralen sogenannten VariablenSet abzulegen, und das VariablenSet in die Projekte einzubinden.
Beispiel: Datenbankserver und Connectionstring
Variable "db_server": server1
Variable "db_port": 1234
Variable "connectionstring_db1": server=#{db_server};port=#{db_port};user=db1user;password=#{db1_pass};database=mydb1
Variable "connectionstring_db2": server=#{db_server};port=#{db_port};user=db2user;password=#{db2_pass};database=mydb2
Variable "connectionstring_db3": server=#{db_server};port=#{db_port};user=db3user;password=#{db3_pass};database=mydb2
--> Wenn sich der Server ändert, muss nur die Variable "db_server" geändert werden.
3) Scoping nutzen
Variablen können nach verschiedenen Parametern - meistens Environment, aber auch andere Unterscheidungen sind möglich, zum Beispiel Target Tag, oder Deployment Target - gescoped werden. Das bedeutet, je nachdem in welches Environment - oder welches Target Tag, oder welches Deployment Target - gerade deployed wird, hat die Variable unterschiedliche Werte.
Dies kann man nutzen, um bei Teilvariablen das Scoping zu setzen, womit man bei zusammengesetzen Variablen nicht mehr scopen muss.
Variable "db_server":
-> Value für Environment "Test": server1
-> Value für Environment "Production": server2
Variable db_pass:
Value für Environment "Test": abc
Value für Environment "Production": xyz
Variable "connectionstring_db1": server=#{db_server};port=#1234;user=db1user;password=#{db1_pass};database=mydb1
--> kein Scoping mehr nötig, je nach Environment werden die richtigen Werte aus den Einzelvariablen genommen
4) Wo möglich, immer Variablen für ein Structured Variable Replacement anwenden
Eine der mächtigsten Funktionen für das Variablen-Replacement von Octopus ist das Structured Variable Replacement. Hier wird in angegebenen Dateien gesucht, ob ein Variablen-Name matched, und der Wert dann ersetzt.
Octopus kann dies für JSON, YAML, XML und "Key = Value"-Paare.
Beispiele:
---> Ergebnis nach Replacement:
---> Ergebnis nach Replacement:
---> Ergebnis nach Replacement:
---> Ergebnis nach Replacement:
--> Es braucht keine Template-Files speziell zum Deployment, in den Files können zum Beispiel direkt Werte für die lokale Entwicklungsumgebung stehen.
Step 6: Deployment Steps anlegen
Octopus bietet bereits viele vorgefertigte Steps, die man nutzen kann:
Für unseren Fall brauchen wir nur Package -> Deploy a Package
Step Name: Sprechender und aussagekräftiger Name, was in diesem Step passiert
Target Tags: Das oben angelegte Target Tag für den Server (können auch mehrere sein)
Package: Das Package (ein Package pro Step)
Configure features:
Hier können die verschiedenen Features aus- oder abgewählt werden, die Octopus bei diesem Step unterstützt. Wir wollen unsere Applikation in ein fest definiertes Verzeichnis installieren, noch ein kurzes Script laufen lassen, und - am wichtigsten - die Ersetzung unserer für das structured replacement angelegten Variablen. Die standardmäßig gesetzten .NET Features wählen wir ab.
Custom Install Directory: hier das Verzeichnis angeben (es empfiehlt sich, dieses als Variable anzulegen). Es kann ausgewählt werden, ob das Verzeichnis vor dem Deployment gelöscht werden soll, und wenn ja, ob es Files/Verzeichnise gibt, die bestehen bleiben sollen.
Custom Deployment Scripts: Die Custom Scripts sind aufgeteilt in Pre-Deployment, Deployment und Post-Deployment. In den einzelnen Bereichen kann zuerst der Interpreter aufgerufen werden, unter dem das Script laufen soll (zum Beispiel Powershell, Bash, Python), und dann der Script-Code eingefügt werden. In unserem Fall nutzen wir nur ein Script in der Deployment-Phase:
Structured Configuration Variables: hier die Files eintragen, über die die Ersetzung laufen soll.
Die restlichen Einstellungen können nach Bedarf angepasst werden, in den meisten Fällen wird man die Default-Einstellungen so lassen.
Best practice:
Es empfiehlt sich, die Aufgaben der einzelnen Steps möglichst klein zu halten bzw. nur logisch zusammenzufassen. Beispiel: Ein chown/chmod auf Projekt-Files würde man in einem Step machen, ein Tool auf dem Server aktualisieren und dann chown/chmod auf Projektfiles macht man besser in zwei Steps, auch wenn beides "Run a Script" (auf dem Deployment-Target) ist.
Und damit ist das erste Projekt in Octopus erstellt. Happy Deploying! :-)