Proof of Concept: Portierung von Nomad auf NetBSD

Proof of Concept: Portierung von Nomad auf NetBSD
By Matthias Petermann / on 18.10.2025

Hintergrund und Motivation

HashiCorp Nomad ist ein leichtgewichtiger Orchestrator, der auf bemerkenswert einfache Weise verschiedenste Workloads unter einer gemeinsamen Oberfläche verwaltet – vom klassischen Prozess über Container bis hin zu virtuellen Maschinen. Seine Stärke liegt in der Modularität: dank unterschiedlicher Driver (exec, raw_exec, qemu, docker u. v. m.) lassen sich sehr verschiedene Umgebungen und Technologien unter einem einheitlichen Scheduler betreiben.

Im Kern folgt Nomad einem klaren und kompakten Architekturprinzip: Ein Agent-Programm läuft auf jeder Maschine. Im Server-Modus übernimmt es Planung, Cluster-Management und API-Zugriff, im Client-Modus führt es Aufgaben aus. Die Server-Knoten bilden gemeinsam einen Raft-basierten Konsensus-Cluster, der den Zustand repliziert und Scheduling-Entscheidungen trifft – ohne dass eine Vielzahl separater Steuerungskomponenten erforderlich ist.

Damit erfüllt Nomad zwar die wesentlichen Steuerfunktionen einer Control Plane, kommt aber ohne eine klassische, vielschichtige Control-Plane-Architektur wie Kubernetes aus. Die Clients arbeiten weitgehend autark, führen laufende Tasks auch bei kurzzeitiger Server-Nichtverfügbarkeit weiter und reduzieren so systemische Abhängigkeiten.

Dieses Design macht Nomad besonders attraktiv für kritische oder verteilte Umgebungen, in denen Stabilität, Übersicht und Determinismus wichtiger sind als maximale Feature-Dichte.


Vergleich: Kubernetes vs. Nomad (Control-Plane-Architektur)

Aspekt Kubernetes HashiCorp Nomad
Architektur Mehrere dedizierte Control-Plane-Komponenten (kube-apiserver, Scheduler, Controller, etcd) Ein einzelner Server-Agent-Typ übernimmt alle Steuerungsaufgaben (API, Scheduler, State)
State-Store etcd (externer Key-Value-Store) Integriert (Raft-basiertes Konsensus-Cluster)
Kommunikationsmodell Stark zentralisiert (API-Server als Vermittler für alle Interaktionen) Dezentral unter Servern mit Leader-Election, Clients kommunizieren direkt mit Servern
Abhängigkeiten Hoch – viele separate Prozesse und Services Gering – ein Binary für Server & Client, keine externen Datenbanken
Autonomie der Worker Eingeschränkt – Worker benötigen durchgehend Verbindung zur Control Plane Hoch – laufende Tasks bleiben aktiv, auch wenn keine Server erreichbar sind (keine neuen Jobs ohne Server)
Zielbild Hochskalierte, dynamische Cloud-Infrastruktur mit granularer Steuerung Schlanke, robuste und kontrollierbare Clusterumgebung mit minimalem Overhead

Warum NetBSD?

Die Idee dieses Proof of Concept entstand aus der Kombination zweier Wertewelten: Nomads Einfachheit und NetBSDs Klarheit. NetBSD steht seit Jahrzehnten für Portabilität, Konsistenz und Transparenz. Es ist ein System, das man versteht – bis in den Kernel hinein. Kein unnötiger Ballast, kein intransparenter Automatismus.

Genau das macht es zu einer spannenden Basis für Nomad:

  • hohe Portabilität und sauberes Systemdesign
  • stabile und vorhersehbare Laufzeitumgebung
  • gut nachvollziehbarer Aufbau

Besonders interessant für den Port sind die folgenden Treiber:

  • exec – klassisches Prozessmodell
  • raw_exec – direkter Prozessstart ohne zusätzliche Isolation
  • qemu – Virtualisierung als Workload
  • perspektivisch: ein eigener xen-Treiber, um native Hypervisor-Integration zu ermöglichen

Nomad und NetBSD teilen denselben architektonischen Grundgedanken: Klarheit über Komplexität.


Der Weg zum Prototypen

Nomad ist in Go geschrieben – ein Glücksfall, denn Go wird unter NetBSD hervorragend unterstützt. Das Ökosystem funktioniert reibungslos, pkgsrc liefert aktuelle Compiler und Bibliotheken, und der Cross-Platform-Gedanke von Go harmoniert mit NetBSDs Designphilosophie.

Schritt 1: Vorbereitung

Wie bei Drittsoftware auf NetBSD üblich, wäre der Weg über pkgsrc ideal. Da es dort jedoch (noch) keinen Port für Nomad gibt, beginnen wir manuell:

git clone https://github.com/hashicorp/nomad.git
pkgin install gmake go124

NetBSD bringt von Haus aus BSD Make mit, doch Nomad setzt auf ein GNU Makefile – also installieren wir gmake und starten den ersten Versuch.


Erster Build-Versuch

Ein gmake release führt zwar zum Start, aber der Build bricht mit einer Fehlermeldung ab. Ursache: das Makefile erkennt NetBSD als Plattform (noch) nicht.

Ein kurzer Blick ins GNUmakefile zeigt, dass die Variable THIS_OS geprüft wird. Ein kleiner Patch genügt:

ifeq (NetBSD,$(THIS_OS))
ALL_TARGETS = netbsd_amd64
endif

Ein erneuter Versuch läuft etwas weiter – diesmal scheitert der Build an einer Go-Dependency: github.com/mitchellh/go-ps.

Der Fehler:

undefined: processes
undefined: findProcess

Das bedeutet: Die Bibliothek hat keine Implementierung für NetBSD.


Untersuchung der Dependency

Ein Blick auf das Repository zeigt: go-ps ist seit 2024 archiviert, der letzte Commit liegt fünf Jahre zurück. Nomad nutzt es offenbar für einfache Prozessabfragen – stabil, aber alt. Wir forken das Projekt unter github.com/MatthiasPetermann/go-ps und beginnen die Anpassung.

Ziel:

Eine NetBSD-Implementierung, die /proc nutzt – analog zur bestehenden process_unix.go, die für Linux und Solaris aktiv ist.

Dazu genügt es zunächst, den Build-Tag anzupassen:

// +build linux solaris netbsd

Beim Kompilieren taucht der Hinweis auf, dass UnixProcess.Refresh() fehlt – die Methode, die /proc/<pid>/stat parst. Wir kopieren also die Linux-Version (process_linux.go), legen sie als process_netbsd.go an und passen den Parser an.


Exkurs: /proc/<pid>/stat auf NetBSD

Ein Vergleich zeigt:

NetBSD:

1016 (xinit) S 856 1016 850 ...

Linux:

1042 (systemd-logind) S 1 1042 1042 ...

Die ersten Felder (pid, comm, state, ppid, pgrp, sid) sind identisch – genau die, die der Parser nutzt. Somit kann die Funktion fast unverändert übernommen werden.

Der resultierende Code:

// +build netbsd

package ps

import (
    "fmt"
    "io/ioutil"
    "strings"
)

func (p *UnixProcess) Refresh() error {
    statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
    dataBytes, err := ioutil.ReadFile(statPath)
    if err != nil {
        return err
    }

    data := string(dataBytes)
    binStart := strings.IndexRune(data, '(') + 1
    binEnd := strings.IndexRune(data[binStart:], ')')
    p.binary = data[binStart : binStart+binEnd]

    data = data[binStart+binEnd+2:]
    _, err = fmt.Sscanf(data, "%c %d %d %d", &p.state, &p.ppid, &p.pgrp, &p.sid)
    return err
}

Damit ist go-ps um NetBSD erweitert.


Integration in Nomad

Damit Nomad unseren Fork statt der Originalbibliothek nutzt, ergänzen wir den Replace-Block in go.mod um:

replace (
    ...
    ...
    github.com/mitchellh/go-ps => github.com/MatthiasPetermann/go-ps v1.1.1
)

Dann:

go mod tidy
gmake release

Und siehe da:

==> Building pkg/netbsd_amd64/nomad with tags ui codegen_generated release...
==> Packaging for netbsd_amd64...
  adding: LICENSE.txt (deflated 62%)
  adding: nomad (deflated 60%)
==> Results:
/h/mpeterma/Projects/hashcorp/nomad/pkg
├── netbsd_amd64
│   ├── LICENSE.txt
│   └── nomad
└── netbsd_amd64.zip

1 directory, 3 files

Ein vollständiges, komprimiertes Release-Package für NetBSD – ohne offensichtliche Fehler gebaut, und das ohne tiefgreifende Eingriffe in Nomads Kern.

Den Abschluss bildet ein Test, ob das erzeugte Binary lauffähig ist:

mkdir nomad
cd nomad
unzip ~/Projects/hashcorp/nomad/pkg/netbsd_amd64.zip
./nomad
Usage: nomad [-version] [-help] [-autocomplete-(un)install] <command> [args]

Common commands:
    run         Run a new job or update an existing job
    stop        Stop a running job
    status      Display the status output for a resource
    alloc       Interact with allocations
    job         Interact with jobs
    node        Interact with nodes
    agent       Runs a Nomad agent

Other commands:
    acl                 Interact with ACL policies and tokens
    action              Run a pre-defined command from a given context
    agent-info          Display status information about the local agent
    config              Interact with configurations
...

Fazit

Fazit: Der Proof of Concept zeigt, dass sich Nomad mit wenigen gezielten Anpassungen erfolgreich auf NetBSD bauen lässt. Der Build-Prozess läuft stabil durch, und das resultierende Release-Package für netbsd_amd64 enthält ein startbares Binary.

Die größte Hürde war eine fehlende Plattformunterstützung in der go-ps-Bibliothek, die durch eine eigene NetBSD-Implementierung gelöst wurde. Mit dieser Ergänzung und einer kleinen Erweiterung im Makefile erkennt Nomad NetBSD korrekt als Zielplattform und kompiliert ohne weitere Eingriffe.

Damit ist die Grundlage gelegt, um Nomad unter NetBSD praktisch zu erproben. In einem kommenden Beitrag folgen erste Tests zur Laufzeit, Funktionsprüfung der Task-Driver (raw_exec, exec, qemu) und eine Einschätzung, wie sich Nomad im Betriebssystemumfeld von NetBSD verhält.


Bildnachweis: Das Nomad-Logo ist eine eingetragene Marke von HashiCorp, Inc.. Verwendung im Rahmen redaktioneller Berichterstattung gemäß den Trademark- und Markenrichtlinien von HashiCorp. Das NetBSD-Logo ist eine eingetragene Marke der NetBSD Foundation, Inc. Verwendung im Rahmen redaktioneller Berichterstattung gemäß den Richtlinien zur Logonutzung.