Nomad premiers pas

Configuration et déploiement de Nomad pour orchestrer vos workloads

Cet article fait suite à celui parlant de Nomad et pourquoi aujourd’hui j’essaie de me tenir loin de K8. Pour plus de context aller le lire ici.

Dans la suite, on va voir comment déployer Nomad pour la première fois, configurer de la télémétrie et voir quelques command (cli) pouvant être utiles.

Dans la suite nous allons parler de l’architecture cible. Elle sera simple mais nous discuterons quels sont les prochaines étapes.

Cet article n’a pas pour objet de l’intégrer dans un outil d’IaC, comme Terraform. Mais il tout à fait possible de faire les même étapes, ce soit seul avec Cloud-Init ou en conjonction avec Ansible (Puppet ou Chef).

Nomad est fait d’un seul binaire pouvant travailler en mode Server ou Client; le “ou” n’est pas exclusif.

Le Server a pour rôle d’Orchestrer les tâches. faisant plus de la chorégraphie que de d’orchestration, mais c’est un détail. C’est à lui qu’on doit envoyer la définition des Jobs pour les exécuter.

Le Client est en charge d’exécuté la tâche. Plusieurs drivers sont disposibles, mais nous utiliserons majoritairement les containers (Podman ou Docker).

Simple, basique

Avoir 4 machines disposibles pour installer Nomad. Avoir CoreDNS déployé avec toute les machines enregistrées, exposant les DNS server1.vm, client1.vm, client2.vm et client3.vm.

Toutes les machines peuvent communiquer sur le port 4646/4647/4648 et sur les ports allons de 20000 à 32000.server1.vm est accessible depuis l’exterieur sur le DNS server.nomad.dns.com, et client1.vm est accessible de l’extérieur sur traefik.dns.vm.

Les 3 machines clients ont installé le moteur conteneur choisi (podman ou docker).

Sur Ubuntu:

0$apt-get update && apt-get install -y nomad

Voila, c’est installé.

Nomad a besoin de certificats pour communiquer en mtls avec les autres clients. Ainsi on va créer les certificats serveur et client, en passant le dns interne et externe sur lesquels on peut joindre le serveur Nomad. On va en profiter pour créer les certificats pour que Traefik puisse utilise le Service Discovery de Nomad.

On va créer un dossier /opt/nomad/certs ou l’on va enregistrer les certificats.

0$docker run --rm -it -v /opt/nomad/certs:/home -w /home  docker.io/hashicorp/nomad:1.9.5 tls ca create
1$docker run --rm -it -v /opt/nomad/certs:/home -w /home  docker.io/hashicorp/nomad:1.9.5 tls cert create -server additional-dnsname server1.vm -additional-dnsname server.nomad.dns.com -days 90
2$docker run --rm -it -v /opt/nomad/certs:/home -w /home  docker.io/hashicorp/nomad:1.9.5 tls cert create -client additional-dnsname server1.vm -additional-dnsname server.nomad.dns.com -days 90
3$mv global-client-nomad.pem traefik.pem
4$mv global-client-nomad-key.pem traefik-key.pem
5$docker run --rm -it -v /opt/nomad/certs:/home -w /home  docker.io/hashicorp/nomad:1.9.5 tls cert create -client additional-dnsname server1.vm -additional-dnsname server.nomad.dns.com -days 90

Si vous avez pas accès à un dns interne/externe vous pouvez aussi utiliser les ips fixes, il faudra alors utiliser l’option -additional-ipaddress. Le flag -days 90 permet de définir le nombre de jours de validiter du certificat.

Le dossier devrait contenir les 8 fichier:

 0total 9
 1drwxrwxr-x 1 v4rgas v4rgas    0 févr. 18 05:03 ./
 2drwxrwxr-x 1 v4rgas v4rgas    0 janv. 22 23:40 ../
 3-rw------- 1 v4rgas v4rgas  227 févr. 18 11:55 global-client-nomad-key.pem
 4-rw-r--r-- 1 v4rgas v4rgas 1001 févr. 18 11:55 global-client-nomad.pem
 5-rw------- 1 v4rgas v4rgas  227 févr. 18 11:55 global-server-nomad-key.pem
 6-rw-r--r-- 1 v4rgas v4rgas 1001 févr. 18 11:55 global-server-nomad.pem
 7-rw------- 1 v4rgas v4rgas  227 févr. 18 11:55 nomad-agent-ca-key.pem
 8-rw-r--r-- 1 v4rgas v4rgas 1115 févr. 18 11:55 nomad-agent-ca.pem
 9-rw------- 1 v4rgas v4rgas  227 févr. 18 11:55 traefik-key.pem
10-rw-r--r-- 1 v4rgas v4rgas 1001 févr. 18 11:55 traefik.pem

Sur la machine serveur, on va créer le fichier: /etc/nomad.d/nomad.hcl.

 0datacenter = "group-1"
 1data_dir = "/opt/nomad/data"
 2server {
 3    enabled = true
 4    bootstrap_expect = 1
 5}
 6advertise {
 7    http = "0.0.0.0"
 8}
 9tls {
10    http = true
11    rpc  = true
12    rpc_upgrade_mode = true
13
14    ca_file   = "/opt/nomad/certs/nomad-agent-ca.pem"
15    cert_file = "/opt/nomad/certs/global-server-nomad.pem"
16    key_file  = "/opt/nomad/certs/global-server-nomad-key.pem"
17
18    verify_server_hostname = true
19    verify_https_client    = true
20}
21telemetry {
22    collection_interval = "15s"
23    disable_hostname = true
24    prometheus_metrics = true
25    publish_allocation_metrics = true
26    publish_node_metrics = true
27}`;

Ensuite téléverser les certificats nécessaire, dans les dossier défini plus haut. Le bloc server, est nécessaire pour activer le process “server” de nomad, il n’est pas exclusif (les deux bloc serveur et client peut-être mis dans le fichier de config).

Le bloc adversite se met à écouter sur l’ip définie. Le bloc tls permet de configurer les certificats nécessaire pour faire tu mtls et exposer le client https. Enfin le bloc telemetry, permet d’exposer les metrics en format prometheus. Il faudra soit les collecter en utiliser des certificats spécifiques.

Sur les trois machines client on va aussi créer le fichier: /etc/nomad.d/nomad.hcl. Sur client1.vm on va définir le comme étant edge et les deux autre machine on utilisera services.

 0datacenter = "<DC>"
 1data_dir = "/opt/nomad"
 2client {
 3    enabled = true
 4    servers = ["server1.vm:4647"]
 5}
 6tls {
 7    http = true
 8    rpc  = true
 9
10    ca_file   = "/opt/certs/nomad/nomad-agent-ca.pem"
11    cert_file = "/opt/certs/nomad/global-client-nomad.pem"
12    key_file  = "/opt/certs/nomad/global-client-nomad-key.pem"
13
14    verify_server_hostname = true
15    verify_https_client    = true
16}
17plugin "docker" {
18    config {
19        volumes {
20            enabled = true
21        }
22        auth {
23            config = "/opt/scripts/docker-auth.json"
24        }
25        allow_privileged = true
26        extra_labels = ["job_name", "job_id", "task_group_name", "task_name", "namespace", "node_name", "node_id"]
27    }
28}
29telemetry {
30    collection_interval = "15s"
31    disable_hostname = true
32    prometheus_metrics = true
33    publish_allocation_metrics = true
34    publish_node_metrics = true
35}

Ensuite téléverser les certificats nécessaire, dans les dossier défini plus haut. On voit le client client qui permet de définir la chemin d’accès.

Le block docker, permet de configurer le client docker utilisé par Nomad. On défini ici un docker-auth pour se connected avec le Repository de Container en étant authentifié. On rajoute des labels au service (pour la télémétrie), on permet l’utilisation des volumes docker et l’utilisation priviligiée.

Il ne reste plus qu’a démarrer nos services pour qu’ils soient accèssibles. Nous allons utiliser systemd pour orchestrer les services Nomad.

 0[Unit]
 1Description=Nomad
 2Documentation=https://www.nomadproject.io/docs
 3Wants=network-online.target
 4After=network-online.target
 5[Service]
 6ExecReload=/bin/kill -HUP $MAINPID
 7ExecStart=/usr/bin/nomad agent -config /etc/nomad.d/nomad.hcl
 8KillSignal=SIGINT
 9LimitNOFILE=infinity
10LimitNPROC=infinity
11Restart=on-failure
12RestartSec=2
13StartLimitBurst=3
14StartLimitInterval=10s
15TasksMax=infinity
16[Install]
17WantedBy=multi-user.target

Il suffit maintenant d’activer le service et le démarrer:

0$sudo systemctl enable nomad
1$sudo systemctl start nomad

Voila, desormais Nomad tourne sur les machines. Si vous faites un $systemctl status nomad et vous devriez avoir la gui-cli de systemd pour nomad.

Désormais vous pouvez appeler le serveur Nomad depuis l’extérieur.

0$export ENDPOINT=NOMAD_ADDR=https://server1.vm:4646
1$export NOMAD_CLIENT_CERT=NOMAD_CLIENT_CERT=/opt/certs/nomad/global-client-nomad.pem
2$export NOMAD_CLIENT_KEY=NOMAD_CLIENT_KEY=/opt/certs/nomad/global-client-nomad-key.pem
3$export NOMAD_CACERT=NOMAD_CACERT=/opt/certs/nomad/nomad-agent-ca.pem
4$nomad status
On devrait voir la liste de services vide, car on ne fait runner en interne pour le moment.

Désormais on expose la GUI web Nomad, accessible sur server.nomad.dns.com, il faut créer le fichier p12 que vous enregistrerez dans votre navigateur.

0$echo "$(openssl rand -base64 30 | tr -d "=+/" | cut -c1-64)" > /tmp/user-gui.txt
1$openssl pkcs12 -export -inkey /opt/nomad/certs/global-client-nomad-key.pem -in /opt/nomad/certs/global-client-nomad.pem -out /opt/nomad/certs/nomad-gui.p12 -password file:/tmp/user-gui.txt
Comme nous créons le p12 depuis des certificats dont la durée de vie est de 90 jours, le p12 aura la même durée de vie.

Et désormais si vous allez sur l’url server.nomad.dns.com vous aurez accès à la GUI de Nomad, de manière sécurisée.

Pour faire du https, il vous faudra les certificats. Une fois crées déposez le dans le dossier /opt/traefik/tls/<DNS-NAME>. Il vous faudra au minimum 2 pairs de certificat un simple et un en wildcard. Pour l’exemple nous allons utiliser les dns: dns.com et *.service.dns.com, accessibles dans les dossiers /opt/traefik/tls/dns.com et /opt/traefik/tls/service.dns.com respectivement. Nous allons aussi ajouter des certificats auto-signé pour le dns *.service.local On les téléverse sur le client1 (ayant pour datacenter edge)

Enfin on téléverse les fichiers /opt/certs/nomad/nomad-agent-ca.pem, /opt/certs/nomad/traefik-key.pem et /opt/certs/nomad/traefik.pem. On s’en servira pour utilise le Service Discovery de Nomad directement.

On va créer un fichier contenant les configs tls: /opt/traefik/config.yml

0tls:
1  certificates:
2    - certFile: /opt/traefik/tls/dns.com/fullchain.pem
3      keyFile: /opt/traefik/tls/dns.com/privkey.pem
4    - certFile: /opt/traefik/tls/service.dns.com/fullchain.pem
5      keyFile: /opt/traefik/tls/service.dns.com/privkey.pem
6    - certFile: /opt/traefik/tls/service.internal/fullchain.pem
7      keyFile: /opt/traefik/tls/service.internal/privkey.pem
On peut soit le téléverser sur la machine client1, soit utilise la stanza template dans le fichier hcl de traefik.

On va créer le fichier /opt/nomad/jobs/traefik.hcl.

 0job "traefik" {
 1  datacenters = ["edge"]
 2  type        = "service"
 3
 4  group "traefik" {
 5    count = 1
 6
 7    network {
 8      port "http" {
 9        static = 80
10        to     = 80
11      }
12      port "https" {
13        static = 443
14        to     = 443
15      }
16    }
17
18    service {
19      name     = "traefik"
20      provider = "nomad"
21      port     = "http"
22      tags = [
23        "traefik.http.routers.traefik-http.service=api@internal",
24        "traefik.http.routers.traefik-http.rule=Host(`traefik.service.dns.com`)",
25        "traefik.http.routers.traefik-http.tls=true",
26      ]
27    }
28
29    task "traefik" {
30      driver = "docker"
31      config {
32        image        = "docker.io/traefik:v3.3.5"
33        network_mode = "host"
34        ports        = ["admin", "http", "internal", "https"]
35        args = [
36          "--api.dashboard",
37          "--api.insecure",
38          "--serversTransport.insecureSkipVerify",
39          "--entrypoints.web.address=:80",
40          "--entrypoints.https.address=:443",
41          "--providers.file.directory=/etc/traefik/dynamic",
42          "--providers.nomad",
43          "--providers.nomad.endpoint.address=https://server1.vm:4646",
44          "--providers.nomad.stale",
45          "--providers.nomad.defaultRule=Host(`{{ .Name }}.service.dns.com`)",
46          "--providers.nomad.endpoint.tls.ca=/opt/certs/nomad/nomad-agent-ca.pem",
47          "--providers.nomad.endpoint.tls.cert=/opt/certs/nomad/traefik.pem",
48          "--providers.nomad.endpoint.tls.key=/opt/certs/nomad/traefik-key.pem",
49
50        ]
51        mounts = [
52            {
53                type     = "bind"
54                target   = "/opt/certs/nomad/"
55                source   = "/opt/certs/nomad/"
56                readonly = true
57            }, {
58                type     = "bind"
59                target   = "/opt/traefik/tls"
60                source   = "/opt/traefik/tls"
61                readonly = true
62            },
63          ]
64      }
65      
66      resources {
67        cpu    = 500
68        memory = 512
69      }
70
71    }
72  }
73}

il manque plus qu’a appliquer le job soit en passant par la GUI soit en passant par le CLI. nomad job plan traefik.hcl vous permettra de valider le fichier. nomad job run traefik.hcl vous permettra de démarrer le service avec le fichier hcl.

Voila, désormais vous trouverez la GUI de traefik à l’url traefik.service.dns.com.

Et voila, désormais lorsque vous levez un service il suffira de mettre le provider (ligne 20) et traefik l’enregistrira automatiquement pour l’exposer sur l’url https://ServiceName.service.dns.com.

0$nomad job status traefik
0$nomad status <allocation_id>
0$nomad logs <allocation_id>

On crée le dossier /opt/nomad/variables s’il n’existe pas. On crée un script:

0#!/bin/bash
1set -eu -o pipefail
2paths=$(nomad var list -out table nomad/jobs | tail -n +2 | egrep -o '([a-z0-9]+/)([a-z0-9/]+)')
3echo "$paths" | while IFS= read -r line ; do nomad var get -out table nomad/jobs | sed '/Items/,$!d' | tail -n +2 | sed -r 's/[ ]+=[ ]+/=/g' > /opt/nomad/variables/$(echo $line | sed 's/\//./g');  done

On aura ainsi autant de fichier que de portées.

On créer un fichier /opt/nomad/variables/nomad.job

0VERSION=12

On lance le shell suivant

0$nomad var put -out json -force nomad/jobs $(cat variables/nomad.jobs | sed -r 's/\n/ /g')

La configuration et déploiement de base est comme vous le voyez très simple.

Il manque à ajouter les configurations pour l’opentélémétrie (metrics, logs et traces), déployer les services de télémétrie dans Nomad, faire un peu de hardening sur nomad, utiliser un service pour la gestion des secrets, et configurerles services pour faire du rolling updates.