Erste Schritte
Installation
Die Anleitung zum Installieren der Polycrate CLI befindet sich hier.
Einen Workspace anlegen
- Legen Sie einen Ordner für Ihren Workspace an:
mkdir -p $HOME/.polycrate/workspaces/my-workspace
- Wechseln Sie zum neu erstellten Ordner:
cd $HOME/.polycrate/workspaces/my-workspace
- Initialisieren Sie die Workspace Konfiguration und SSH Keys für den Workspace:
polycrate init --with-name my-workspace
Ihr Workspace Ordner sollte jetzt folgenden Inhalt haben:
blocks/ # Ordner für Blöcke
id_rsa # SSH Private Key
id_rsa.pub # SSH Public Key
workspace.poly # Workspace Konfiguration
Die Workspace Konfigurations-Datei (workspace.poly
) sollte jetzt folgenden überschaubaren Inhalt haben:
name: my-workspace
Der Workspace Ordner kann sich an einem beliebigen Ort im Dateisystem befinden - Polycrate kann direkt aus diesem Ordner heraus ausgeführt werden und weiß dadurch automatisch, mit welchem Workspace Sie arbeiten möchten. Alternativ kann der Pfad zum Workspace auch mit dem flag --workspace
(oder kurz -w
) angegeben werden: polycrate -w $HOME/.polycrate/workspaces/my-workspace workspace inspect
Polycrate eignet sich am besten für die Arbeit mit Ansible, daher folgen nun ein paar Beispiele um die Möglichkeiten mit Polycrate und Ansible zu demonstrieren.
Für alle weiteren Schritte nehmen wir an, dass:
- ein Server erstellt wurde, der unter der IP
1.2.3.4
erreichbar ist - der öffentliche SSH-Key des Workspaces in der
authorized_keys
Datei desroot
Users auf dem Server hinterlegt wurde - Port 22 auf dem Server für SSH Verbindungen geöffnet ist
Ein Inventory erstellen
Ansible benötigt für die meisten Aufgaben ein gültiges Inventory. Wir erzeugen ein neues Inventory und speichern es unter artifacts/blocks/inventory/inventory.yml
ab.
all:
hosts:
my-host:
ansible_host: 1.2.3.4
ansible_ssh_port: 22
ansible_python_interpreter: "/usr/bin/python3"
ansible_user: root
children:
my-hosts:
hosts:
my-host
Dieses Inventory enthält einen Host namens my-host
mit der IP Adresse 1.2.3.4
sowie eine Gruppe my-hosts
mit my-host
als einzigem Mitglied.
Weitere Informationen über das Ansible Inventory finden Sie hier.
Wir speichern dieses Inventory wie eben beschrieben in artifacts/blocks/inventory/inventory.yml
:
mkdir -p artifacts/blocks/inventory
cat <<EOF > artifacts/blocks/inventory/inventory.yml
all:
hosts:
my-host:
ansible_host: 1.2.3.4
ansible_ssh_port: 22
ansible_python_interpreter: "/usr/bin/python3"
ansible_user: root
children:
my-hosts:
hosts:
my-host
EOF
Einen Inventory-Block im Workspace anlegen
In Polycrate stellen Blöcke die eigentliche Funktionalität eines Workspaces bereit. Blöcke können beliebigen Code enthalten, die beste Integration hat Polycrate allerdings mit Ansible. Ein Block könnte zum Beispiel die Installation von Docker in ein Linux Betriebssystem abbilden, ein weiterer die Installation von Traefik mittels Docker-Compose.
Blöcke funktionieren wie Klassen - sie können instanziiert werden und ihre Eigenschaften und Funktionen (sog. Actions) vererben.
Polycrate Blöcke können erstellt werden, indem unterhalb des Block Ordners (blocks/
) ein Verzeichnis mit einer gültigen Block Konfigurations-Datei (block.poly
) und ggf. Code angelegt wird. Die name
Stanza innerhalb der Block Konfigurations-Datei bestimmt den Namen des Blockes im Workspace.
Außerdem können Blöcke von anderen Blöcken in Form einer Eltern-Kind-Beziehung abgeleitet, bzw. instanziiert werden. Der Kind-Block erbt dabei vom Eltern-Block die Working-Directory (also der Ordner in dem der Code des Blocks liegt) sowie alle Eigenschaften und Actions. Im Kind-Block definierte Eigenschaften und Actions überschreiben die importierten Werte des Eltern-Blocks.
Blöcke die unter blocks/
im Workspace Ordner abgelegt werden sind automatisch zur Benutzung im Workspace verfügbar und müssen nicht separat in der Workspace Konfigurations-Datei angelegt werden. Für unser Inventory haben wir allerdings keine Notwendigkeit, einen Block unterhalb von blocks/
anzulegen - es reicht, dem Workspace bekannt zu machen, dass es einen Block namens inventory
gibt.
# workspace.poly
name: my-workspace
blocks:
- name: inventory
Polycrate verwaltet die Artefakt-Ordner für jeden Block im Workspace automatisch. Sobald Polycrate also einen Block im Workspace findet, wird ein Ordner für den Block unterhalb von artifacts/blocks
angelegt - in unserem Fall artifacts/blocks/inventory
. Der Ordner kann auch, wie in unserem Beispiel, manuell angelegt werden.
Bei jedem Aufruf von Polycrate wird der artifacts/
Ordner nach Artefakten durchsucht und gefundene Artefakte werden für die Verwendung im Workspace bereitgestellt.
Einen Funktions-Block im Workspace anlegen
Unser Inventory-Block kann aktuell nicht mehr als dem Workspace ein Ansible Inventory bereitzustellen, das wiederum von anderen Blöcken genutzt werden kann. Im nächsten Schritt wollen wir einen Block anlegen, der etwas an unserem Server verändert - nämlich einige Programme installiert.
Dafür benötigen wir Ansible und erstellen dafür einen neuen Block im Workspace.
mkdir -p blocks/install-packages
cat <<EOF > blocks/install-packages/block.poly
name: install-packages
config:
packages:
- curl
actions:
- name: install
playbook: install.yml
EOF
Diese Befehle erstellen einen Ordner (blocks/install-packages
) sowie eine Konfigurations-Datei für den Block (blocks/install-packages/block.poly
).
Die Block Konfigurations-Datei (block.poly
) ist sehr wichtig. Polycrate durchsucht beim Start den Ordner blocks/
(inklusive aller Unterordner) nach Block Konfigurations-Dateien und fügt dem Workspace für jede gefundene und valide Block Konfigurations-Datei einen Block hinzu. Der Ordner in dem sich die Datei befindet ist die sog. Block Workdir
, die neben der Konfigurations-Datei beliebige andere Dateien enthalten kann - insbesondere Code, wie z.B. Ansible Playbooks, der im Rahmen des Blocks ausgeführt werden soll.
Die im Workspace verfügbaren Blöcke können mit polycrate block list
angezeigt werden.
Die Block Konfigurations-Datei enthält ein Attribut actions
- Actions sind ein elementarer Bestandteil von Polycrate. Sie erlauben, auf Basis eines Blocks beliebige Aktionen zu definieren, die aus Anwender-Sicht ausgeführt werden können. Ein Beispiel wären install
und uninstall
Actions, um Pakete o.ä. auf einem Server zu installieren oder zu entfernen.
Die mit der install
Action verknüpfte Datei install.yml
(ein Ansible Playbook) werden wir jetzt erstellen:
cat <<EOF > blocks/install-packages/install.yml
- name: Install packages
hosts: all
tasks:
- name: Install packages
ansible.builtin.package:
name: "{{ item }}"
state: present
with_items: "{{ block.config.packages }}"
EOF
Dieses Playbook verbindet sich mit allen Hosts eines Ansible Inventory und installiert dann alle Pakete, die in block.config.packages
definiert wurden.
Polycrate erstellt vor Ausführung einer Action einen sog. Workspace Snapshot - eine YAML Datei, die alle Informationen zum Workspace sowie allen Blöcken enthält - und übergibt diesen Ansible in Form von extra vars.
Der Snapshot enthält direkt verfügbare Informationen zum aktuell ausgeführten Block in einer Top-Level Variable namens block
, sowie zum Workspace in einer Top-Level Variable namens workspace
.
Dadurch kann man innerhalb von Ansible mit Hilfe von Pfad-Angaben bequem auf Werte aus der Block Konfigurations-Datei zugreifen, indem man einfach {{ block.SUB.PATH }}
etc. referenziert (Hinweis: die doppelt geschweiften Klammern sind Jinja-Syntax. Nicht zu verwechseln mit Go Template Syntax).
Diese Action kann mit polycrate run install-packages install
ausgeführt werden.
Im aktuellen Zustand wird die Ausführung der Action allerdings fehlschlagen, da wir zwar ein Inventory definiert haben, aber den Block install-packages
noch nicht so konfiguriert haben, dass er dieses Inventory auch nutzt. Das geht, indem man in der Workspace Konfigurations-Datei dem Block install-packages
die inventory
Stanza hinzfügt:
# workspace.poly
name: my-workspace
blocks:
- name: inventory
- name: packages
from: install-packages
inventory:
from: inventory
Die obige Workspace Konfiguration enthält zwei relevante Neuerungen:
- Wir haben einen Block
packages
definiert, der vom Blockinstall-packages
ableitet. Polycrate Blöcke können wie Klassen genutzt und mehrfach instanziiert werden. Beim Ausführen einer Action eines solchen abgeleiteten Blocks wechselt Polycrate die Block Workdir zur Workdir des Eltern-Blocks (install-packages
) um den dortigen Code ausführen zu können. Gleichzeitig wird die Konfiguration (und alle Actions) des Blockesinstall-packages
mit der Konfiguration des Blockespackages
gemerged (wobei der Kind-Block (packages
) die Konfiguration des Eltern-Blockes (install-packages
) überschreibt). - Wir haben den Block
packages
mit dem Inventory des Blockesinventory
verknüpft. Dadurch steht das Inventory beim Ausführen in derinstall
Action despackages
Blocks Ansible zur Verfügung und der definierte Server kann wirklich erreicht und verändert werden.
Sofern ein SSH Key im Workspace existiert, kümmert sich Polycrate automatisch darum, dass dieser bei allen Verbindungen die Ansible macht genutzt wird.
Eine Action ausführen
Nachdem wir nun einen Block haben, der Pakete auf einem Linux-Host installieren kann (in unserem Fall curl
), sollten wir ihn ausprobieren: polycrate run packages install
INFO[0000] Running action action=install block=packages txid=2fc57485-d1b4-43f7-b82b-533126fb3690 workspace=my-workspace
INFO[0000] Starting container action=install block=packages txid=2fc57485-d1b4-43f7-b82b-533126fb3690 workspace=my-workspace
INFO[0000] Pulling image: cargo.ayedo.cloud/library/polycrate:0.18.17
PLAY [Install packages] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [my-host]
TASK [Install packages] ********************************************************
changed: [my-host] => (item=curl) => {"ansible_loop_var": "item", "cache_update_time": 1678080938, "cache_updated": false, "changed": true, "item": "curl", "stderr": "", "stderr_lines": [], "stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nThe following packages were automatically installed and are no longer required:\n libflashrom1 libftdi1-2 libllvm13 libvulkan1 libxcb-randr0\n mesa-vulkan-drivers\nUse 'sudo apt autoremove' to remove them.\nThe following NEW packages will be installed:\n curl\n0 upgraded, 1 newly installed, 0 to remove and 40 not upgraded.\nNeed to get 194 kB of archives.\nAfter this operation, 454 kB of additional disk space will be used.\nGet:1 http://de.archive.ubuntu.com/ubuntu jammy-updates/main amd64 curl amd64 7.81.0-1ubuntu1.8 [194 kB]\nFetched 194 kB in 1s (366 kB/s)\nSelecting previously unselected package curl.\r\n(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 194308 files and directories currently installed.)\r\nPreparing to unpack .../curl_7.81.0-1ubuntu1.8_amd64.deb ...\r\nUnpacking curl (7.81.0-1ubuntu1.8) ...\r\nSetting up curl (7.81.0-1ubuntu1.8) ...\r\nProcessing triggers for man-db (2.10.2-1) ...\r\nNEEDRESTART-VER: 3.5\nNEEDRESTART-KCUR: 5.15.0-60-generic\nNEEDRESTART-KEXP: 5.15.0-67-generic\nNEEDRESTART-KSTA: 3\nNEEDRESTART-UCSTA: 1\nNEEDRESTART-UCCUR: 0x00f0\nNEEDRESTART-UCEXP: 0x00f0\nNEEDRESTART-SVC: packagekit.service\nNEEDRESTART-SVC: udisks2.service\n", "stdout_lines": ["Reading package lists...", "Building dependency tree...", "Reading state information...", "The following packages were automatically installed and are no longer required:", " libflashrom1 libftdi1-2 libllvm13 libvulkan1 libxcb-randr0", " mesa-vulkan-drivers", "Use 'sudo apt autoremove' to remove them.", "The following NEW packages will be installed:", " curl", "0 upgraded, 1 newly installed, 0 to remove and 40 not upgraded.", "Need to get 194 kB of archives.", "After this operation, 454 kB of additional disk space will be used.", "Get:1 http://de.archive.ubuntu.com/ubuntu jammy-updates/main amd64 curl amd64 7.81.0-1ubuntu1.8 [194 kB]", "Fetched 194 kB in 1s (366 kB/s)", "Selecting previously unselected package curl.", "(Reading database ... ", "(Reading database ... 5%", "(Reading database ... 10%", "(Reading database ... 15%", "(Reading database ... 20%", "(Reading database ... 25%", "(Reading database ... 30%", "(Reading database ... 35%", "(Reading database ... 40%", "(Reading database ... 45%", "(Reading database ... 50%", "(Reading database ... 55%", "(Reading database ... 60%", "(Reading database ... 65%", "(Reading database ... 70%", "(Reading database ... 75%", "(Reading database ... 80%", "(Reading database ... 85%", "(Reading database ... 90%", "(Reading database ... 95%", "(Reading database ... 100%", "(Reading database ... 194308 files and directories currently installed.)", "Preparing to unpack .../curl_7.81.0-1ubuntu1.8_amd64.deb ...", "Unpacking curl (7.81.0-1ubuntu1.8) ...", "Setting up curl (7.81.0-1ubuntu1.8) ...", "Processing triggers for man-db (2.10.2-1) ...", "NEEDRESTART-VER: 3.5", "NEEDRESTART-KCUR: 5.15.0-60-generic", "NEEDRESTART-KEXP: 5.15.0-67-generic", "NEEDRESTART-KSTA: 3", "NEEDRESTART-UCSTA: 1", "NEEDRESTART-UCCUR: 0x00f0", "NEEDRESTART-UCEXP: 0x00f0", "NEEDRESTART-SVC: packagekit.service", "NEEDRESTART-SVC: udisks2.service"]}
PLAY RECAP *********************************************************************
my-host : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
INFO[0010] Removing container action=install block=packages txid=2fc57485-d1b4-43f7-b82b-533126fb3690 workspace=my-workspace
Wir haben eine Action install
definiert, die auf unserem Host my-host
aus dem vorher angelegten Inventory das Paket curl
installiert, welches wir in der Datei block.poly
als Teil der Block Konfiguration definiert haben.
Was aber, wenn wir neben curl
auch wget
installieren wollen? Hierfür haben wir zwei Optionen:
- Wir ändern die Liste der Pakete in der Block Konfigurations-Datei des Blocks
install-packages
- Wir überschreiben die Liste der Pakete in der Workspace Konfigurations-Datei
Da unser Block packages
vom Eltern-Block install-packages
abgeleitet wurde, erbt er die im Block definierte Liste von Paketen, die aktuell nur curl
enthält. Um dieser Liste auch wget
hinzuzufügen, überschreiben wir den Wert von config.packages
in der Workspace Konfiguration für den Block packages
:
# workspace.poly
name: my-workspace
blocks:
- name: inventory
- name: packages
from: install-packages
inventory:
from: inventory
config:
packages:
- curl
- wget
Führen wir die Action nun nochmals aus, werden 2 Pakete installiert: polycrate run packages install
INFO[0000] Running action action=install block=packages txid=abd5b953-f3b7-4a93-901e-84b72f73e919 workspace=my-workspace
INFO[0000] Starting container action=install block=packages txid=abd5b953-f3b7-4a93-901e-84b72f73e919 workspace=my-workspace
INFO[0000] Pulling image: cargo.ayedo.cloud/library/polycrate:0.18.17
PLAY [Install packages] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [my-host]
TASK [Install packages] ********************************************************
ok: [my-host] => (item=curl) => {"ansible_loop_var": "item", "cache_update_time": 1678080938, "cache_updated": false, "changed": false, "item": "curl"}
ok: [my-host] => (item=wget) => {"ansible_loop_var": "item", "cache_update_time": 1678080938, "cache_updated": false, "changed": false, "item": "wget"}
PLAY RECAP *********************************************************************
my-host : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
INFO[0008] Removing container action=install block=packages txid=abd5b953-f3b7-4a93-901e-84b72f73e919 workspace=my-workspace
In unserem Fall war wget
bereits auf dem Host installiert, daher wurde seitens Ansible keine Änderung durchgeführt. Wie man der Ausgabe des Befehls entnehmen kann, wurde wget
aber zusätzlich zu curl
evaluiert.
In der Workspace Konfigurations-Datei wurde die Stanza config.packages
mit curl
und wget
überschrieben. Es reicht nicht, einfach nur wget
anzufügen, da die vom Eltern-Block vererbten Werte vollständig überschrieben und nicht zu teilen gemerged werden.
Per SSH verbinden
Polycrate kann eine direkte SSH Verbindungen zu Hosts in einem Ansible Inventory innerhalb des Workspace aufbauen. Standardmäßig versucht Polycrate das Inventory des Blocks inventory
zu laden um eine SSH Verbindung aufzubauen: polycrate ssh my-host
.
Es ist möglich, mehr als ein Inventory in einem Workspace zu verwalten. Um eine SSH Verbindung zu einem bestimmten Host eines anderen Inventory aufzubauen kann folgendes Flag mitgegeben werden: polycrate ssh --block inventory2 my-host-2
Um die nötige Konfiguration für die SSH Verbindung aufzubauen muss Polycrate das Ansible Inventory interpretieren. Dafür startet Polycrate im Hintergrund einen Container auf Basis des Polycrate Images (cargo.ayedo.cloud/library/polycrate
), liest das Inventory mit dem entsprechenden Ansible-Tooling ein und übermittelt alle relevanten Settings für eine Verbindung zum Zielhost zurück an Polycrate, sofern Inventory und Zielhost existieren.
Dieser Vorgang nimmt einige Sekunden Zeit in Anspruch, weswegen des beim Aufbau der SSH Verbindung zu einer kurzen Verzögerung kommt.
Spaß mit Ansible
Um die Tiefe der Ansible-Integration besser zu verstehen, kann folgendes Playbook als Block im Workspace angelegt und ausgeführt werden. Es zeigt:
- Den Inhalt der Variable
block
aus Sicht von Ansible (Hinweis: enthält die vollständige Konfiguration des aktuellen Blocks als Dictionary) - Den Inhalt der Variable
block.name
aus Sicht von Ansible (Hinweis: enthält den Namen des aktuellen Blocks) - Den Inhalt der Variable
action.name
aus Sicht von Ansible (Hinweis: enthält den Namen der aktuellen Action) - Den Inhalt der Variable
block.config
aus Sicht von Ansible (Hinweis: enthält die Benutzer-Konfiguration des aktuellen Blocks als Dictionary) - Den Inhalt der Variable
workspace
aus Sicht von Ansible (Hinweis: enthält die gesamte Workspace Konfiguration (inklusive der ebenfalls top-level verfügbaren Objekte fürblock
undaction
)) - Wir man auf die Konfiguration eines anderen (also nicht des aktuellen) Blocks im Workspace zugreifen kann
- Wir man auf die Konfiguration mehrerer anderer (also nicht des aktuellen) Blöcke im Workspace gleichzeitig zugreifen kann
# blocks/ansible/playbook.yml
- name: "Debug workspace"
hosts: localhost
tasks:
- name: Show current block
ansible.builtin.debug:
var: block
- name: Show current block name
ansible.builtin.debug:
var: block.name
- name: Show current action name
ansible.builtin.debug:
var: action.name
- name: Show current block config
ansible.builtin.debug:
var: block.config
- name: Show workspace
ansible.builtin.debug:
var: workspace
- name: Get config from block with name 'packages'
ansible.builtin.debug:
var: (workspace.blocks | selectattr('name', 'match', 'packages') | first).config
- name: Show 'packages' user-config of all blocks that have it
ansible.builtin.debug:
var: item
loop: "{{ workspace | community.general.json_query('blocks[*].config.packages') }}"
Wrapup
Sie sollten jetzt einen grundlegend Eindruck davon haben, was Polycrate leisten kann. Jetzt liegt es an Ihnen, etwas damit zu entwickeln.