Hashicorp Nomad Cluster Autoscaling mit OpenStack nutzen
8 Minuten Lesezeit
Ü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
}
}
}
}