Hashicorp Nomad Cluster Autoscaling mit OpenStack nutzen

Verwenden Sie Nomad Cluster Autoscaling OpenStack

Überblick

Hashicorps Workload Orchestrierer - Nomad - bietet verschiedene Autoscaling-Funktionen, die mit Hilfe des Nomad Autoscalers implementiert werden. Wie man in einer OpenStack-Umgebung das sogenannte “Horizontal Cluster Autoscaling” (also das dynamische Hinzufügen bzw. Wegnehmen von Cluster-Knoten/Nomad-Clients) umsetzen kann, wird in diesem Tutorial erklärt.

Nomad Autoscaler Binary herunterladen

Um überhaupt Autoscaling in einem Nomad Cluster verwenden zu können, muß zunächst der Nomad Autoscaler konfiguriert und gestartet werden. Es ist sinnvoll dazu den Nomad Autoscaler auch als Nomad Job unter Kontrolle von Nomad laufen zu lassen. Dazu lädt man einfach die passende Binärdatei von releases.hashicorp.com herunter und legt sie dann z. B. unter /usr/local/bin auf einem Nomad-Client ab.

Nomad Autoscaler Job-Datei erstellen

Um den Autoscaler als Job unter Nomad-Kontrolle laufen zu lassen, muß eine Job-Datei erstellt werden. Sie enthält alle relevanten Konfigurationsparameter. Wir gehen hier schrittweise ein Beispiel durch.

Wie bei allen Job-Dateien für Nomad, legen wir fest in welcher Region und in welchen Datacentern der Job laufen soll. Da es sich um eine “systemnahe” Workload handelt, wird der Job in einen eigenen Namespace verbannt. Das macht es erforderlich, den Namespace und den Job mit einer Policy zu versehen, die die erforderlichen Rechte enthält. Mit dieser Policy wird dann das Token erzeugt, welches im weiteren Verlauf unten dann verwendet wird.

Der Autoscaler bindet sich mit seinem http-Endpunkt an einen Port, wir lassen diesen hier per Zufall von Nomad bestimmen.

Als nächstes folgen die Aufrufparameter für den Autoscaler. Es wird das Verzeichnis für Erweiterungen (Plugins) festgelegt sowie die Verzeichnisse für die allgemeine Konfiguration und die Scaling-Policies bestimmt.

Um auf den lokalen Nomad-Client zugreifen zu können, benötigt der Autoscaler passende SSL-Zertifikate und ein Nomad-Token, mit dem die Rechte verknüpft sind, Cluster-Knoten zu erzeugen (siehe oben).

Der Anfang der Job-Datei für das Deployment einer Instanz des Autoscalers könnte also z. B. so aussehen:

job "autoscaler-prod4" {

  region      = "de-west"
  datacenters = ["prod4"]
  namespace   = "autoscalerprod4"

  group "autoscaler" {

    network {
      port "http" {}
    }

    task "autoscaler_agent" {
      driver = "exec"
      config {
            command = "/usr/local/bin/nomad-autoscaler"
            args = [
              "agent",
              "-plugin-dir=local/nomad-autoscaler/plugins",
              "-config=local/nomad-autoscaler/etc",
              "-policy-dir=local/nomad-autoscaler/etc/policies",
              "-nomad-address=https://127.0.0.1:4646",
              "-http-bind-address=${NOMAD_IP_http}",
              "-http-bind-port=${NOMAD_PORT_http}",
              "-nomad-ca-cert=local/nomad-autoscaler/etc/certificates/ca.pem",
              "-nomad-client-cert=local/nomad-autoscaler/etc/certificates/cert.pem",
              "-nomad-client-key=local/nomad-autoscaler/etc/certificates/private_key.pem",
              "-nomad-region=de-west"
            ]
          }
    [...]

Nomad Nova Autoscaler Plugin

Wie schon angedeutet, kann der Autoscaler mit Plugins erweitert und z. B. an verschiedene Cloud-Provider angepaßt werden. Das Nova Plugin für den Nomad Autoscaler ist (neben Anderen) in der Dokumentation zum Nomad Autoscaler verlinkt. Es ist praktisch, sich das Plugin von zentraler Stelle herunterzuladen, wenn der Nomad-Job ausgeführt wird:

    [...] 
      artifact {
        source = "https://github.com/jorgemarey/nomad-nova-autoscaler/releases/download/v0.6.0/nomad-nova-autoscaler-v0.6.0-linux-amd64.tar.gz"
        destination = "local/nomad-autoscaler/plugins"
        options {
            checksum = "md5:fec29af8625842b154d30be8b8db305f"
        }
      }
    [...]

Nomad Variablen verwenden

Um sensible Informationen wie SSL-Zertifikate oder Token nicht direkt in der Job-Datei speichern zu müssen, ist es sinnvoll sie in Nomad-Variablen oder in einem Hashicorp Vault zu speichern. In diesem Teil der Job-Datei kann man sehen, dass das Nomad-Token, die SSL-Zertifikate und die Zugangsdaten zur OpenStack-Umgebung, die das Nova Autoscaler Plugin benötigt, in Nomad Variablen abgespeichert worden sind. Weiterhin wird für das Applikation Performance Management (APM) in diesem Fall das Nomad APM Plugin verwendet, welches Daten über CPU- und Memory-Auslastung liefern kann, da diese Daten von jedem Nomad-Client erhoben werden. Für ausgefeiltere Skalierungsparameter (wie z. B. Verbindungen pro Sekunde) sollte das Prometheus APM Plugin verwendet werden welches dann aber eine Prometheus-Installation mit entsprechenden Exportern erforderlich macht.

    [...]
      template {
        destination = "${NOMAD_SECRETS_DIR}/env.txt"
        env         = true
        data        = <<EOT  
NOMAD_TOKEN={{ with nomadVar "nomad/jobs/autoscaler-prod4" }}{{ .token }}{{ end }}
EOT
      }
      template {
         data = <<EOH
{{ with nomadVar "nomad/jobs/autoscaler-prod4" }}{{ .cacert }}{{ end }}
         EOH
         destination = "local/nomad-autoscaler/etc/certificates/ca.pem"
      }
      template {
         data = <<EOH
{{ with nomadVar "nomad/jobs/autoscaler-prod4" }}{{ .clientcert }}{{ end }}
         EOH
         destination = "local/nomad-autoscaler/etc/certificates/cert.pem"
      }
      template {
         data = <<EOH
{{ with nomadVar "nomad/jobs/autoscaler-prod4" }}{{ .clientkey }}{{ end }}
         EOH
         destination = "local/nomad-autoscaler/etc/certificates/private_key.pem"
      }

      template {
         data = <<EOH
apm "nomad-apm" {
  driver = "nomad-apm"
}

target "os-nova" {
  driver = "os-nova"
  config = {
    auth_url    = {{- with nomadVar "nomad/jobs/autoscaler-prod4" }} "{{ .osauthurl }}" {{- end }}
    username    = {{- with nomadVar "nomad/jobs/autoscaler-prod4" }} "{{ .osusername }}" {{- end }}
    password    = {{- with nomadVar "nomad/jobs/autoscaler-prod4" }} "{{ .ospassword }}" {{- end }}
    domain_name = {{- with nomadVar "nomad/jobs/autoscaler-prod4" }} "{{ .osdomainname }}" {{- end }}
    project_id  = {{- with nomadVar "nomad/jobs/autoscaler-prod4" }} "{{ .osprojectid }}" {{- end }}
    region_name = {{- with nomadVar "nomad/jobs/autoscaler-prod4" }} "{{ .osregion }}" {{- end }}
  }
}
 
  [...]

Scaling Strategie festlegen

Zuletzt wird dann noch eine Scaling Strategie festgelegt. Diese besteht aus einem “Check”, der Strategie selbst und dem “Target”, für welches die Strategie angewendet werden soll. Der Check verwendet die Daten des APM-Plugins, um die Strategie (hier den “Prozentsatz der allozierten CPU bei 70 zu halten”) beim “Target” (hier mit Hilfe des “os-nova” Target-Plugins realisiert) umzusetzen.

Als Strategie wurde hier “target-value” ausgewählt, um die Auslastung pro Nomad-Client ungefähr bei 70% CPU-Auslastung zu halten. In der Policy ist zusätzlich die Dauer der “cooldown” Periode festgelegt, in der der Autoscaler “abwartet”, nachdem er eine Skalierung durchgeführt hat. Außerdem wird bestimmt, wie oft (“evaluation_interval”) der Autoscaler bewerten soll, ob die Anzahl der Nomad-Clients skaliert werden muß. Weiterhin werden die “min” und “max” Werte konfiguriert, die die Grenzen festlegen, zwischen denen der Autoscaler Nomad-Clients erzeugen bzw. löschen soll.

Die Dokumentation zum OpenStack Nova Autoscaler Plugin enthält eine detaillierte Erklärung der verschiedenen Parameter. Die meisten erklären sich jedoch von selbst. Damit der Autoscaler die oben formulierte Strategie umsetzen kann, ist es erforderlich, dass ein eigener Node-Pool verwendet wird und das den erzeugten Nomad-Clients eine gemeinsame“node_class” zugwiesen wird. Wenn gewünscht können die erzeugten Nomad-Clients auch server-groups und security-groups zugewiesen werden.

Zuletzt werden dem Job noch CPU- und Memory-Ressourcen zugewiesen.

  [...]
    strategy "target-value" { 
      driver = "target-value"
    }
             EOH
             destination = "local/nomad-autoscaler/etc/nom>
          }

          template {
             data = <<EOH

    scaling "worker_pool_policy" {
      enabled = true
      min     = 1
      max     = 2

      policy {
        cooldown            = "2m"
        evaluation_interval = "1m"

        check "cpu_allocated_percentage" {
          source = "nomad-apm"
          query  = "percentage-allocated_cpu"
          strategy "target-value" {
            target = 70
          }
        }

        target "os-nova" {
          dry-run = false

          stop_first         = true
          image_id           = "0c453c2c-cdc2-416a-95f7-c1>
          flavor_name        = "SCS-2V-2-20"
          pool_name          = "nom-pool"
          name_prefix        = "nom-"
          network_id         = "275b130d-c650-4f20-a25c-1f>
          security_groups    = "default"
          availability_zones = "az1"
          tags               = "nom-pool,ubuntu-minimal"
          server_group_id    = "373265a7-5856-4e5c-a371-43>
  
          node_class                    = "dynamic"
          node_drain_deadline           = "1h"
          node_drain_ignore_system_jobs = false
          node_purge                    = true
          node_selector_strategy        = "least_busy"
        }
      } 
    }

             EOH
             destination = "local/nomad-autoscaler/etc/pol>
          }

          resources {  
            cpu    = 50
            memory = 128
          }

        } 
      }
    }

Ergebnis

Mit Hilfe des OpenStack Nova Autoscaler Plugins kann Nomad dynamisch Nomad clients im Nomad Cluster erzeugen und auch wieder löschen. Wenn wir ein Image erzeugt haben (z. B. mit Hilfe von Packer oder Terraform), mit dem wir Nomad clients dynamisch starten wollen und den Autoscaler-Job mit der Job-Datei gestartet haben, sollten sich automatisch erzeugte Nomad Clients - ähnlich wie hier - zeigen:

root@nomad1:~# nomad node status -allocs -os  |grep -i dynamic
d838cb47  nom-pool    prod4    nom-1a607061-1f5c      dynamic     debian  false  eligible     ready   1
7f10377b  nom-pool    prod4    nom-d1d8e976-bc4f      dynamic     debian  false  eligible     ready   1
Komplette Nomad Job-Datei
    job "autoscaler-ha-prod4" {

      region      = "de-west"
      datacenters = ["prod4"]
      namespace   = "autoscalerprod4"

      group "autoscaler" {

        network {
          port "http" {}
        }

        task "autoscaler_agent" {
          driver = "exec"
          config {
                command = "/usr/local/bin/nomad-autoscaler"
                args = [
                  "agent",
                  "-plugin-dir=local/nomad-autoscaler/plugins",
                  "-config=local/nomad-autoscaler/etc",
                  "-policy-dir=local/nomad-autoscaler/etc/policies",
                  "-nomad-address=https://127.0.0.1:4646",
                  "-http-bind-address=${NOMAD_IP_http}",
                  "-http-bind-port=${NOMAD_PORT_http}",
                  "-nomad-ca-cert=local/nomad-autoscaler/etc/certificates/ca.pem",
                  "-nomad-client-cert=local/nomad-autoscaler/etc/certificates/cert.pem",
                  "-nomad-client-key=local/nomad-autoscaler/etc/certificates/private_key.pem",
                  "-nomad-region=de-west"
                ]
              }

          template {
            destination = "${NOMAD_SECRETS_DIR}/env.txt"
            env         = true
            data        = <<EOT
    NOMAD_TOKEN={{ with nomadVar "nomad/jobs/autoscaler-ha-prod4" }}{{ .token }}{{ end }}
    EOT
          }
          artifact {
            source = "https://github.com/jorgemarey/nomad-nova-autoscaler/releases/download/v0.6.0/nomad-nova-autoscaler-v0.6.0-linux-amd64.tar.gz"
            destination = "local/nomad-autoscaler/plugins"
            options {
                checksum = "md5:fec29af8625842b154d30be8b8db305f"
            }
          }

          template {
            data = <<EOH
    {{ with nomadVar "nomad/jobs/autoscaler-ha-prod4" }}{{ .cacert }}{{ end }}
            EOH
            destination = "local/nomad-autoscaler/etc/certificates/ca.pem"
          }

          template {
            data = <<EOH
    {{ with nomadVar "nomad/jobs/autoscaler-ha-prod4" }}{{ .clientcert }}{{ end }}
            EOH
            destination = "local/nomad-autoscaler/etc/certificates/cert.pem"
          }

          template {
            data = <<EOH
    {{ with nomadVar "nomad/jobs/autoscaler-ha-prod4" }}{{ .clientkey }}{{ end }}
            EOH
            destination = "local/nomad-autoscaler/etc/certificates/private_key.pem"
          }

          template {
            data = <<EOH

    apm "nomad-apm" {
      driver = "nomad-apm"
    }

    target "os-nova" {
      driver = "os-nova"
      config = {
        auth_url    = {{- with nomadVar "nomad/jobs/autoscaler-ha-prod4" }} "{{ .osauthurl }}" {{- end }}
        username    = {{- with nomadVar "nomad/jobs/autoscaler-ha-prod4" }} "{{ .osusername }}" {{- end }}
        password    = {{- with nomadVar "nomad/jobs/autoscaler-ha-prod4" }} "{{ .ospassword }}" {{- end }}
        domain_name = {{- with nomadVar "nomad/jobs/autoscaler-ha-prod4" }} "{{ .osdomainname }}" {{- end }}
        project_id  = {{- with nomadVar "nomad/jobs/autoscaler-ha-prod4" }} "{{ .osprojectid }}" {{- end }}
        region_name = {{- with nomadVar "nomad/jobs/autoscaler-ha-prod4" }} "{{ .osregion }}" {{- end }}
      }
    }

    strategy "target-value" {
      driver = "target-value"
    }
            EOH
            destination = "local/nomad-autoscaler/etc/nomad-autoscaler.hcl"
          }

          template {
            data = <<EOH

    scaling "worker_pool_policy" {
      enabled = true
      min     = 1
      max     = 2

      policy {
        cooldown            = "2m"
        evaluation_interval = "1m"

        check "cpu_allocated_percentage" {
          source = "nomad-apm"
          query  = "percentage-allocated_cpu"
          strategy "target-value" {
            target = 70
          }
        }

        target "os-nova" {
          dry-run = false

          stop_first         = true
          image_id           = "0c453c2c-cdc2-416a-95f7-c1779ed2fc54"
          flavor_name        = "SCS-2V-2-20"
          pool_name          = "nom-pool"
          name_prefix        = "nom-"
          network_id         = "275b130d-c650-4f20-a25c-1f6568f520dc"
          security_groups    = "default"
          availability_zones = "az1"
          tags               = "nom-pool,ubuntu-minimal"
          server_group_id    = "373265a7-5856-4e5c-a371-43b923c4a3d0"
          
          node_class                    = "dynamic"
          node_drain_deadline           = "1h"
          node_drain_ignore_system_jobs = false
          node_purge                    = true
          node_selector_strategy        = "least_busy"
        }
      }
    }

            EOH
            destination = "local/nomad-autoscaler/etc/policies/scaling-policy.hcl"
          }

          resources {
            cpu    = 50
            memory = 128
          }

        }
      }
    }
Zuletzt geändert 03.06.2026: additions I (93489553)