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
info

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 des root 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.

info

Ein Polycrate Workspace kann Artifacts verwalten. Artefakte sind beliebige Dateien, die bei der Ausführung von Actions erzeugt wurden und im Workspace persistiert werden sollen. Zwei Beispiele für häufig genutzte Artefakte sind das Ansible Inventory und die Kubeconfig.

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.

info

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
info

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).

info

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.

info

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:

  1. Wir haben einen Block packages definiert, der vom Block install-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 Blockes install-packages mit der Konfiguration des Blockes packages gemerged (wobei der Kind-Block (packages) die Konfiguration des Eltern-Blockes (install-packages) überschreibt).
  2. Wir haben den Block packages mit dem Inventory des Blockes inventory verknüpft. Dadurch steht das Inventory beim Ausführen in der install Action des packages Blocks Ansible zur Verfügung und der definierte Server kann wirklich erreicht und verändert werden.
info

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:

  1. Wir ändern die Liste der Pakete in der Block Konfigurations-Datei des Blocks install-packages
  2. 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.

info

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

info

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:

  1. Den Inhalt der Variable block aus Sicht von Ansible (Hinweis: enthält die vollständige Konfiguration des aktuellen Blocks als Dictionary)
  2. Den Inhalt der Variable block.name aus Sicht von Ansible (Hinweis: enthält den Namen des aktuellen Blocks)
  3. Den Inhalt der Variable action.name aus Sicht von Ansible (Hinweis: enthält den Namen der aktuellen Action)
  4. Den Inhalt der Variable block.config aus Sicht von Ansible (Hinweis: enthält die Benutzer-Konfiguration des aktuellen Blocks als Dictionary)
  5. 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ür block und action))
  6. Wir man auf die Konfiguration eines anderen (also nicht des aktuellen) Blocks im Workspace zugreifen kann
  7. 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.

Kontaktieren Sie uns

Unsere Container-Experten beraten Sie gerne und individuell.

Fleet Team
Fleet Team
Fleet Team
Fleet Team
Fleet Team
Fleet Team
Fleet Team

Wir antworten in der Regel innerhalb weniger Stunden auf Ihre Nachricht.