Linux things 🐧

un blog sur les technologies des logiciels libres et autres digressions


Une infra avec Nomad, Consul, Vault, Tailscale et Tricot

Une infra avec Nomad, Consul, Vault, Tailscale et Tricot : S02E01

Thu, 27 Feb 2025 22:05:12 +0100
# nomad   # consul   # vault   # tailscale   # tricot   # auto-hebergement  

Il y a un peu plus de 1 an je dĂ©marrais une suite d’articles sur une infra architecturĂ©e autour de Nomad, Consul et Tailscale
S’en suivi des mises Ă  jour Ă  mesure des amĂ©liorations jusqu’au remplacement de Caddy par Tricot

Il est temps de commencer la saison 2 en ajoutant Vault

Introduction

Il y a quelques semaines j’ai migrĂ© mon infra cloud (2 VMs) vers Hetzner. Scaleway est un trĂšs bon opĂ©rateur mais pour un usage personnel j’ai 3 VMs chez Hetzner avec plus de CPU/RAM et pour moins cher (17,26€/mois).

Cette fois-ci j’ai :

  • 1 VM front (CX32) : 4 CPU / 8 Go RAM / 80 Go disk
  • 1 VM Nomad (CX22) : 2 CPU / 4 Go RAM / 40 Go disk
  • 1 VM Consul / Vault (CX22) : 2 CPU / 4 Go RAM / 40 Go disk

La VM front contient le reverse proxy Tricot, un client Nomad, Consul, Tailscale et des conteneurs Docker.
La VM nomad contient 1 serveur Nomad, un client Consul et Tailscale
La VM consul contient 1 serveur Consul, un serveur Vault et un client Tailscale

Seul front possĂšde une IP public, les 3 sont reliĂ©es par un rĂ©seau interne crĂ©Ă© par l’interface web Hetzner. Il suffit d’attacher les VMs Ă  ce rĂ©seau pour qu’elles obtiennent une IP interne :

  • front : IP_PUBLIC, 10.0.0.2
  • consul : 10.0.0.3
  • nomad : 10.0.0.4

SSH

pour me connecter sur les 2 VMs privĂ©es j’utilise un rebond ssh par le front :

~/.ssh/config

Host consul
HostName consul
User root
  # variable utile avec le terminal Ghostty
  SetEnv TERM=xterm-256color
ProxyCommand ssh -i .ssh/id_ed25519-sk root@IP_PUBLIC nc 10.0.0.3 %p

Host nomad
HostName nomad
User root
  # variable utile avec le terminal Ghostty
  SetEnv TERM=xterm-256color
ProxyCommand ssh -i .ssh/id_ed25519-sk root@IP_PUBLIC nc 10.0.0.4 %p

Squid

j’ai installĂ© le proxy Squid sur front pour permettre aux 2 autres VMs d’accĂ©der Ă  Internet.

apt install squid

Dans /etc/squid/conf.d/debian.conf ajouter

acl localnet src 10.0.0.0/8     # RFC1918 possible internal network
acl localnet src 172.16.0.0/12  # RFC1918 possible internal network
acl localnet src 192.168.0.0/16 # RFC1918 possible internal network
acl localnet src 100.64.0.0/10 # RFC1918 possible internal network
acl localnet src fc00::/7       # RFC 4193 local private network range
acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines

auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/users
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated

http_access allow localnet
http_access allow localhost

# And finally deny all other access to this proxy
http_access deny all

Créer un utilisateur

htpasswd -c /etc/squid/users un_user

relancer

systemctl restart squid

Sur nomad et consul ajouter:

/etc/environment

export http_proxy=http://un_user:PASS@10.0.0.2:3128
export https_proxy=http://un_user:PASS@10.0.0.2:3128

/etc/apt/apt.conf.d/proxy

Acquire::http::Proxy "http://un_user:PASS@10.0.0.2:3128";

Cela permet Ă  APT de sortir pour mettre Ă  jour le systĂšme et installer des paquets.

Docker

Docker n’est installĂ© que sur la VM front. Nomad, Consul et Vault sont installĂ©s directement dans les VMs.
Doc d’installation sur Ubuntu : Install Docker Engine on Ubuntu

Tailscale

Tailscale est le tuyau de cette infra en reliant toutes les VMs cloud et auto-hébergés.
Ainsi les services Nomad/Consul pourront communiquer entre eux que cela soit sur des VMs hĂ©bergĂ©es que vers des PC/raspberry Ă  la maison. Rien de compliquĂ© pour l’installation il y a tout ici : https://tailscale.com/download/

Attention pour pouvoir lancer le service il faut ajouter le proxy

Proxy Squid

Sur les 2 VMs nomad et consul il faut ajouter le proxy dans le fichier

/etc/default/tailscaled
https_proxy=http://un_user:PASS@10.0.0.2:3128

sinon le tailscale up ne fonctionnera pas depuis ces VMs privés. Ensuite affichez le status de vos noeuds Tailscale : tailscale status
Si votre infra est uniquement en cloud, Tailscale est bien entendu facultatif.

Certificat

Avant d’aller plus loin on va devoir gĂ©nĂ©rer un certificat pour Vault. Pour cela on utilise le programme mkcert

# installation sur son PC Manjaro/Arch
sudo pacman -S mkcert
mkcert -cert-file IP_TAILSCALE_CONSUL.crt -key-file IP_TAILSCALE_CONSUL.key IP_TAILSCALE_CONSUL

Comme je n’utilise pas un nom de domaine je gĂ©nĂšre le certificat en utilisant l’IP Tailscale de consul. Si vous utilisez un nom remplacez l’IP par le FQDN du serveur Consul/Vault. Il faudra copier ces 2 fichiers dans le rĂ©pertoire /opt/vault/tls mais aussi sur les serveurs et clients Nomad dans /opt/nomad/certs/.
Déployer aussi le certificat Root ~/.local/share/mkcert/rootCA.pemdans /usr/local/share/ca-certificates/vault-local.crt sur les serveurs Ubuntu et dans /etc/ca-certificates/trust-source/anchors/vault-local.crt sur Manjaro. Puis mettre à jour la base de données : update-ca-trust

Vault

Vault permet de stocker les secrets (mots de passe, token, etc). Ainsi ils ne sont plus exposés dans les fichiers HCL.
Pour l’installation il suffit d’ajouter les dĂ©pĂŽts d’Hashicorp sur la VM consul et d’installer le paquet vault : apt install vault

La configuration

/etc/vault.d/vault.hcl

ui = true
api_addr = "https://TAILSCALE_IP_CONSUL:8200"

# le storage peut ĂȘtre aussi Consul
storage "file" {
  path = "/opt/vault/data"
}

# HTTPS listener
listener "tcp" {
  address       = "TAILSCALE_IP_CONSUL:8200"
  
  tls_cert_file = "/opt/vault/tls/IP_TAILSCALE_CONSUL.crt"
  tls_key_file  = "/opt/vault/tls/IP_TAILSCALE_CONSUL.key"
}

systemctl start vault

PC

Sur son PC il faut ajouter la variable d’environnement VAULT_ADDR=https://TAILSCALE_IP_VAULT:8200, ensuite on peut initialiser Vault

# ici on génÚre 5 clés de descellement et il en faudra 3 pour desceller
vault operator init -key-shares=5 -key-threshold=3
# on stocke les tokens générées dans un gestionnaire de mot de passe

# on descelle vault 3 fois avec 3 des 5 clés
vault operator unseal

# on se connecte avec la root token
vault login

# on check
vault status

# on active les secrets en version 2
vault secrets enable -version '2' 'kv'

# ou si vous préférez un path qui indique la version
# vault secrets enable -path=kv2 -version=2 kv

Maintenant que l’on a un Vault fonctionnel il faut le configurer pour que Nomad puisse y accĂ©der. On active les Workload identities qui utilisent les jwt (l’authentification avec une token est dĂ©prĂ©ciĂ©e).

Il faut préparer 3 fichiers

vault-auth-method-jwt-nomad.json

{
  "jwks_url": "http://IP_TAILSCALE_NOMAD_SERVER:4646/.well-known/jwks.json",
  "jwt_supported_algs": ["RS256", "EdDSA"],
  "default_role": "nomad-workloads"
}

vault-role-nomad-workloads.json

{
  "role_type": "jwt",
  "bound_audiences": ["vault.io"],
  "user_claim": "/nomad_job_id",
  "user_claim_json_pointer": true,
  "claim_mappings": {
    "nomad_namespace": "nomad_namespace",
    "nomad_job_id": "nomad_job_id",
    "nomad_task": "nomad_task"
  },
  "token_type": "service",
  "token_policies": ["nomad-workloads"],
  "token_period": "30m",
  "token_explicit_max_ttl": 0
}

Pour le dernier fichier il faut avant lister les mĂ©thodes d’auth

vault auth list
Path          Type     Accessor               Description                Version
----          ----     --------               -----------                -------
jwt-nomad/    jwt      auth_jwt_d34481ad      n/a                        n/a
token/        token    auth_token_510d42ca    token based credentials    n/a

RĂ©cupĂ©rer l’accessor du jwt-nomad et le mettre dans le hcl

vault-policy-nomad-workloads.hcl

path "kv/data/{{identity.entity.aliases.auth_jwt_d34481ad.metadata.nomad_namespace}}/{{identity.entity.aliases.auth_jwt_d34481ad.metadata.nomad_job_id}}/*" {
  capabilities = ["read"]
}

path "kv/data/{{identity.entity.aliases.auth_jwt_d34481ad.metadata.nomad_namespace}}/{{identity.entity.aliases.auth_jwt_d34481ad.metadata.nomad_job_id}}" {
  capabilities = ["read"]
}

path "kv/metadata/{{identity.entity.aliases.auth_jwt_d34481ad.metadata.nomad_namespace}}/*" {
  capabilities = ["list"]
}

path "kv/metadata/*" {
  capabilities = ["list"]
}

Enfin ajoute la mĂ©thode d’auth jwt et on applique les 3 fichiers dans le Vault

vault auth enable -path 'jwt-nomad' 'jwt'

vault write auth/jwt-nomad/config '@vault-auth-method-jwt-nomad.json'

vault write auth/jwt-nomad/role/nomad-workloads '@vault-role-nomad-workloads.json'

vault policy write 'nomad-workloads' 'vault-policy-nomad-workloads.hcl'

Consul

Consul d’Hashicorp est un service qui permet entre autres d’enregistrer un nom de service (comme un DNS interne) et stocker des donnĂ©es. Sur chaque VM qui contiendra des conteneurs Docker il faut installer un client Consul et un client Nomad.

On installe le serveur Consul sur la VM consul apt install consul, voici la configuration :

consul: /etc/consul.d/consul.hcl

datacenter = "hetzner"
data_dir = "/opt/consul"

client_addr = "IP_TAILSCALE_CONSUL"

ui_config{
  enabled = true
}

server = true
bind_addr = "IP_TAILSCALE_CONSUL"
advertise_addr = "IP_TAILSCALE_CONSUL"

bootstrap_expect=1
 
retry_join = ["IP_TAILSCALE_CONSUL"]

ports {
  grpc = 8502
}

connect {
  enabled = true
}

La configuration sur front

front: /etc/consul.d/consul.hcl

datacenter = "hetzner"
data_dir = "/opt/consul"
bind_addr = "IP_TAILSCALE_FRONT"
retry_join = ["IP_TAILSCALE_CONSUL"]

nomad: /etc/consul.d/consul.hcl

datacenter = "hetzner"
data_dir = "/opt/consul"
bind_addr = "IP_TAILSCALE_NOMAD" 
retry_join = ["IP_TAILSCALE_CONSUL"]

systemctl start consul

Consul propose une interface web ici : http://IP_TAILSCALE_CONSUL:8500/ui/

Une capture qui affiche mes services en cours :

consul

Nomad

On installe Nomad avec apt install nomad (voir la doc : https://developer.hashicorp.com/nomad/install).

Serveur

nomad: /etc/nomad.d/nomad.hcl

datacenter = "hetzner"
data_dir  = "/opt/nomad/data"
bind_addr = "IP_TAILSCALE_NOMAD"

server {
  # license_path is required for Nomad Enterprise as of Nomad v1.1.1+
  #license_path = "/etc/nomad.d/license.hclic"
  enabled          = true
  bootstrap_expect = 1
}

client {
  enabled = false
  servers = ["IP_TAILSCALE_NOMAD"]
}

consul {
  address = "127.0.0.1:8500"
}

acl {
  enabled = true
}

vault {
  enabled = true
  address = "https://IP_TAILSCALE_CONSUL:8200"
  jwt_auth_backend_path = "jwt-nomad"

  cert_file = "/opt/nomad/certs/IP_TAILSCALE_CONSUL.crt"
  key_file  = "/opt/nomad/certs/IP_TAILSCALE_CONSUL.key"

  # Only needed in servers when transioning from the token-based flow to
  # workload identities.
  # create_from_role = "nomad-cluster"
  create_from_role = "nomad-workloads"

  # Provide a default workload identity configuration so jobs don't need to
  # specify one.
  default_identity {
    aud  = ["vault.io"]
    env  = true
    change_mode = "restart"
    file = true
    ttl  = "1h"
  }
}

telemetry {
 collection_interval = "5s",
 publish_allocation_metrics = true,
 publish_node_metrics = true,
 prometheus_metrics = true
}

La derniÚre section active la télémétrie si on veut monitorer le serveur Nomad avec Grafana.

systemctl start nomad

lancer ensuite le bootstrap des ACL afin de gĂ©nĂ©rer des tokens d’accĂšs au serveur Nomad

nomad acl bootstrap

cette commande sort :

Accessor ID  = e02bd587-1f96-f60f-c79b-4c5beadae911
Secret ID    = 3fffa295-5f90-acb2-3d47-8ac7add477a6
Name         = Bootstrap Token
Type         = management
Global       = true
Policies     = n/a
Create Time  = 2021-08-29 13:41:08.988749307 +0000 UTC
Create Index = 5651
Modify Index = 5651

Le secret ID est Ă  sauvegarder et Ă  ajouter en tant que variable d’environnement dans votre .bashrc ou .zshrc sur votre PC pour que la commande nomad puisse accĂ©der au serveur

NOMAD_ADDR=http://IP_TAILSCALE_NOMAD:4646
NOMAD_TOKEN=3fffa295-5f90-acb2-3d47-8ac7add477a6

Client

Nomad est à installer et configurer en tant que client sur les VMs et serveurs qui devront héberger des conteneurs Docker.

front: /etc/nomad.d/nomad.hcl

datacenter = "hetzner"
data_dir  = "/opt/nomad/data"
bind_addr = "IP_TAILSCALE_FRONT"

server {
  # license_path is required for Nomad Enterprise as of Nomad v1.1.1+
  #license_path = "/etc/nomad.d/license.hclic"
  enabled          = false
  bootstrap_expect = 1
}

client {
  enabled = true
  servers = ["IP_TAILSCALE_NOMAD"]

  host_network "public" {
    interface = "eth0"
  }

  host_network "tailscale" {
    cidr = "100.64.0.0/10"
  }
}

plugin "docker" {
  config {
    volumes {
      enabled      = true
    }
    extra_labels = ["job_name", "job_id", "task_group_name", "task_name", "namespace", "node_name", "node_id"]
  }
}

consul {
  address = "127.0.0.1:8500"
}

vault {
  enabled = true
  address = "https://IP_TAILSCALE_CONSUL:8200"
  
  cert_file = "/opt/nomad/certs/IP_TAILSCALE_CONSUL.crt"
  key_file  = "/opt/nomad/certs/IP_TAILSCALE_CONSUL.key"
}

Nomad propose une interface web qui permet de consulter les jobs en cours et l’Ă©tat des noeuds. Il suffit de se connecter sur http://IP_TAILSCALE_NOMAD:4646/ui/ et de saisir la token gĂ©nĂ©rĂ©e plus haut.

Une capture qui affiche mes conteneurs en cours

nomad

Depuis son PC on peut piloter le serveur, par exemple :

nomad node status
ID        Node Pool  DC   Name   Class   Drain  Eligibility  Status
d89a983a  default    dc1  node2  <none>  false  eligible     ready
6348bbae  default    dc1  node1  <none>  false  eligible     ready

Plugins CNI

Vous verrez dans les logs des Nomad clients une erreur car il ne trouve pas le répertoire /opt/cni/bin. En effet il a besoin de ces fichiers binaires pour déployer correctement les conteneurs notamment pour la configuration réseau. Il faut donc récupérer ici le targz selon votre plateforme https://github.com/containernetworking/plugins/releases et le décompresser dans ce répertoire sur chaque VM cliente de Nomad (inutile sur le serveur Nomad).
Plus d’infos sur la page dĂ©diĂ©e chez Hashicorp : CNI

Auto-hébergement

J’ai un mini PC de type NUC sur lequel j’hĂ©berge un grand nombre de service dans des conteneurs.
Sur le NUC j’ai installĂ© Manjaro qui diffĂšre un peu d’une Ubuntu. Les fichiers de configuration ne sont pas au mĂȘme endroit.

/etc/nomad.d/defaults.hcl

datacenter = "hetzner"
Data_dir = "/var/lib/nomad"
bind_addr = "IP_TAILCALE_NUC"

# binaries shouldn't go in /var/lib
plugin_dir = "/usr/lib/nomad/plugins"

server {
  # license_path is required for Nomad Enterprise as of Nomad v1.1.1+
  #license_path = "/etc/nomad.d/license.hclic"
  enabled          = false
  bootstrap_expect = 1
}

client {
  enabled = true
  servers = ["IP_TAILSCALE_NOMAD"]

  host_network "tailscale" {
    cidr = "IP_TAILSCALE_NUC/32"
  }
}

plugin "docker" {
  config {
    allow_privileged = true
#    allow_caps = ["all"]
    volumes {
      enabled      = true
    }
    extra_labels = ["job_name", "job_id", "task_group_name", "task_name", "namespace", "node_name", "node_id"]
  }
}


consul {
  address = "127.0.0.1:8500"
}


vault {
  enabled   = true
  address   = "https://IP_TAILSCALE_CONSUL:8200"
  cert_file = "/var/lib/nomad/certs/IP_TAILSCALE_CONSUL.crt"
  key_file  = "/var/lib/nomad/certs/IP_TAILSCALE_CONSUL.key"
}

En thĂ©orie on devrait changer la valeur de datacenter = "hetzner" et mettre par exemple datacenter = "home". Mais pour cela il faudrait un serveur ou une VM dĂ©diĂ©e et configurer ce Nomad en serveur. C’est ce que j’ai fait en environnement professionnel avec 1 serveur Nomad par site et qui forment un cluster. Pour un usage personnel pour simplfier je n’utilise qu’un datacenter pour tous mes sites et donc 1 unique serveur Nomad.

Idem pour Consul qui est configuré ici comme client

/etc/consul.d/consul.hcl

datacenter = "hetzner"
data_dir = "/var/lib/consul"
bind_addr = "IP_TAILSCALE_NUC"
retry_join = ["IP_TAILSCALE_CONSUL"]

Tricot

Avant de lancer des services il faut un serveur web sur front. Tricot est un reverse proxy qui utilise Consul pour automatiquement faire un reverse HTTPS vers un service enregistrĂ© dans Consul. Pour cela il scrute les nouveaux tags qui commencent par tricot, gĂ©nĂšre un certificat Let’s Encrypt et applique le reverse.

A savoir que Tricot ne gĂšre pas les autres certificats, achetĂ©s ou gĂ©nĂ©rĂ©s. Dans un environnement oĂč Let’s Encrypt n’est pas utilisĂ© j’utilise Caddy.

tricot.hcl

job "tricot" {
  datacenters = ["hetzner"]
  type        = "service"

  group "proxy" {
    count = 1

    network {
      mode = "bridge"

      port "internal" {
        static       = 9334
        to           = 9334
        host_network = "tailscale"
      }

      port "http-public" {
        static       = 80
        to           = 80
        host_network = "public"
      }

      port "https-public" {
        static       = 443
        to           = 443
        host_network = "public"
      }
    }

    restart {
      attempts = 2
      interval = "2m"
      delay    = "30s"
      mode     = "fail"
    }

    task "tricot" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "front"
      }

      config {
        image = "fredix/tricot"

        volumes = [
          "secrets:/etc/tricot",
        ]
        ports = ["internal", "http-public", "https-public"]
      }

      resources {
        cpu    = 1000
        memory = 2000
      }


      template {
        data        = <<EOH
        TRICOT_NODE_NAME={{ env "attr.unique.hostname" }}
        TRICOT_LETSENCRYPT_EMAIL=fredix@protonmail.com
        TRICOT_ENABLE_COMPRESSION=true
        TRICOT_CONSUL_HOST=http://IP_TAILSCALE_CONSUL:8500
        TRICOT_CONSUL_TLS_SKIP_VERIFY=true
        TRICOT_HTTP_BIND_ADDR=[::]:80
        TRICOT_HTTPS_BIND_ADDR=[::]:443
        TRICOT_METRICS_BIND_ADDR=[::]:9334
        RUST_LOG=tricot=info
        EOH
        destination = "secrets/env"
        env         = true
      }

      service {
        name     = "tricot-http"
        provider = "consul"
        port     = "http-public"
      }

      service {
        name     = "tricot-https"
        provider = "consul"
        port     = "https-public"
      }

      service {
        name     = "tricot-metrics"
        provider = "consul"
        port     = "internal"
      }

    }
  }
}

nomad job run tricot.hcl

Tricot est lancĂ© sur la VM front et utilise l’interface publique et l’interface de Tailscale ce qui lui permet d’accĂ©der aux services dans le rĂ©seau Tailscale.

Exemples

Si tout va bien votre infra est prĂȘte (sinon postez vos commentaires). Voici 4 exemples de services et les 2 derniers qui utilisent Vault.

Un premier exemple avec un service auto-hébergé : Navidrome

navidrome.hcl

job "navidrome" {
  datacenters = ["hetzner"]
  type        = "service"
  group "home" {
    count = 1

    network {
      port "http" {
        to = 4533 # container port the app runs on
        # static = 4533   # host port to expose
        host_network = "tailscale"
      }
    }

    task "navidrome" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "nuc"
      }

      env {
        ND_SCANSCHEDULE   = "1h"
        ND_LOGLEVEL       = "info"
        ND_SESSIONTIMEOUT = "24h"
        ND_BASEURL        = ""
      }

      config {
        image = "deluan/navidrome:latest"
        volumes = [
          "/data/volumes/navidrome/:/data",
          "/data/musiques:/music"
        ]
        ports = [
          "http"
        ]
      }

      resources {
        cpu    = 200
        memory = 500
      }

      service {
        name     = "navidrome"
        provider = "consul"
        port     = "http"

        tags = ["tricot navi.fredix.xyz"]
      }
    }

  }
}

nomad job run navidrome.hcl : le job est lancé et le service est aussitÎt en prod.

Uptime Kuma

Autre exemple avec Uptime Kuma qui est lui lancé dans la VM front

uptimekuma.hcl

job "uptimekuma" {
  datacenters = ["hetzner"]
  type        = "service"
  group "app" {
    count = 1

    network {
      port "http" {
        to = 3001 # container port the app runs on
      }
    }

    task "uptimekuma" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "front"
      }

      config {
        image = "louislam/uptime-kuma"
        volumes = [
          "/data/nomad/uptimekuma:/app/data"
        ]
        ports = [
          "http"
        ]
      }

      resources {
        cpu    = 500
        memory = 512
      }

      service {
        name     = "uptimekuma"
        provider = "consul"
        port     = "http"
        tags     = ["tricot uptimekuma.fredix.xyz"]

        check {
          type     = "http"
          name     = "app_health"
          path     = "/"
          interval = "20s"
          timeout  = "10s"
        }
      }
    }
  }
}

Radicle

Premier exemple Vault avec Radicle (forge logicielle en P2P).

job "radicle" {
  datacenters = ["hetzner"]
  type        = "service"
  group "app" {
    count = 1

    network {
      port "node" {
        to     = 8776 # container port the app runs on
        static = 8776 # host port to expose
      }
      port "http" {
        to = 8080 # container port the app runs on
        # static = 8181   # host port to expose
      }
    }

    task "node" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "front"
      }

      vault {}

      template {
        data        = <<EOF
              {{ with secret "kv/data/default/radicle" }}
                RAD_PASSPHRASE = "{{ .Data.data.RAD_PASSPHRASE }}"
              {{ end }}
              RUST_LOG         = "info"
              RUST_BACKTRACE   = 1
              GIT_TRACE        = 1
              GIT_TRACE_PACKET = 1
            EOF
        destination = "secrets/file.env"
        env         = true
      }

      config {
        image = "fredix/radicle-node:1.1.0"
        volumes = [
          "/data/nomad/radicle/node:/root"
        ]
        ports = [
          "node"
        ]
      }

      resources {
        cpu    = 500
        memory = 500
      }

      service {
        name     = "radicle-node"
        tags     = ["global", "app"]
        provider = "consul"
        port     = "node"
      }
    }

    task "httpd" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "front"
      }

      env {
        RUST_LOG       = "info"
        RUST_BACKTRACE = 1
      }
      config {
        image = "fredix/radicle-httpd:0.18.0"
        mounts = [
          {
            type     = "bind"
            target   = "/root"
            source   = "/data/nomad/radicle/node"
            readonly = true
            bind_options = {
              propagation = "rshared"
            }
          }
        ]

        ports = [
          "http"
        ]
      }

      resources {
        cpu    = 256
        memory = 64
      }

      service {
        name     = "radicle"
        provider = "consul"
        port     = "http"
        tags     = ["tricot seed.fredix.xyz"]

        check {
          type     = "http"
          name     = "app_health"
          path     = "/api/v1"
          interval = "20s"
          timeout  = "10s"
        }
      }
    }
  }
}

Mon noeud Radicle a besoin d’une passphrase pour dĂ©marrer. J’ai d’abord ajoutĂ© le passe en ligne de commande dans Vault

vault kv put -mount 'kv' 'default/radicle' 'RAD_PASSPHRASE=PASS'
vault kv get kv/default/radicle

nomad job run radicle.hcl

Dans le template j’extrait le mot de passe de Vault et le stocke dans la variable d’environnement RAD_PASSPHRASE

LND

LND est un service basĂ© sur Bitcoin (pour simplifier). Peu importe si cela ne vous intĂ©resse pas, c’est un 2Ăšme exemple avec Vault.
Au démarrage LND a besoin de mot de passe pour ouvrir son coffre sinon le service reste scellé et ne peut pas effecturer de transaction.

lnd.hcl

job "lnd" {
  datacenters = ["hetzner"]
  type        = "service"
  group "app" {
    count = 1

    network {
      port "tcp" {
        to     = 9735 # container port the app runs on
        static = 9735 # host port to expose
      }
      port "rest" {
        to           = 8081 # container port the app runs on
        static       = 8081 # host port to expose
        host_network = "tailscale"
      }
      port "grpc" {
        to           = 10009 # container port the app runs on
        static       = 10009 # host port to expose
        host_network = "tailscale"
      }

      dns {
        servers = ["172.17.0.1", "8.8.8.8", "8.8.4.4"]
      }
    }


    task "lnd" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "front"
      }

      vault {}

      template {
        data        = <<EOH
	   #!/bin/bash
	   chmod 400 /secrets/file.env
	   CONTAINER_IP=$(ip route get 1.2.3.4 | awk '{print $7}')
	   lnd --bitcoin.mainnet --bitcoin.node=neutrino --neutrino.addpeer=btcd0.lightning.engineering 
	   --fee.url=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json --restlisten=${CONTAINER_IP}:8081 
	   --rpclisten=${CONTAINER_IP}:10009 --tlsextraip=IP_TAILSCALE_FRONT --tlsextradomain=lnd --externalip=IP_PUBLIC_VM 
	   --alias=fredix.xyz --protocol.zero-conf --protocol.option-scid-alias --wallet-unlock-password-file=/secrets/file.env
           EOH
        destination = "local/file.sh"
      }

      template {
        data        = <<EOF
{{ with secret "kv/data/default/lnd" }}{{ .Data.data.unlock }}{{ end }}
EOF
        destination = "secrets/file.env"
        change_mode = "restart"
      }

      config {
        image = "lightninglabs/lnd:v0.18.5-beta"
        entrypoint = [
          "bash",
          "-c",
          "chmod 777 /local/file.sh && ./local/file.sh",
        ]

        volumes = [
          "/data/nomad/lnd:/root/.lnd"
        ]
        ports = [
          "tcp", "rest", "grpc"
        ]
      }

      resources {
        cpu    = 500
        memory = 600
      }

      service {
        name     = "lnd"
        tags     = ["global", "app"]
        provider = "nomad"
        port     = "tcp"
      }

      service {
        name     = "lnd"
        tags     = ["global", "app"]
        provider = "nomad"
        port     = "rest"
      }

      service {
        name     = "lnd"
        tags     = ["global", "app"]
        provider = "nomad"
        port     = "grpc"
      }

    }

  }
}

Avant de lancer le service j’ajoute le mot de passe du service LND

vault kv put -mount 'kv' 'default/lnd' 'unlock=PASS
vault kv get kv/default/lnd

nomad job run lnd.hcl

GrĂące au template with secret Nomad rĂ©cupĂšre le mot de passe et l’Ă©crit dans le fichier secrets/file.env. Ensuite le premier template a accĂšs Ă  ce fichier et le fourni en paramĂštre de LND : --wallet-unlock-password-file=/secrets/file.env

DNS

Pour dĂ©ployer automatiquement n’importe quel service en sous-domaine j’ai positionnĂ© que 2 lignes dans ma configuration DNS de mon domaine chez bookmyname

*                       10800  A      IP_PUBLIC_HETZNER
@                       10800  A      IP_PUBLIC_HETZNER

Conclusion

C’Ă©tait un condensĂ© de mes prĂ©cĂ©dents Ă©pisodes de la saison 1.
Vous trouverez sur codeberg mes scripts hcl (bientĂŽt Ă  jour) : https://codeberg.org/fredix/nomad

quelques liens :

Vault de A Ă  Y

Nomad ACL

Introduction to HashiCorp Nomad

Why you should take a look at Nomad before jumping on Kubernetes

How and why to use Nomad for orchestration at your startup

Nomad vs Kubernetes without the complexity

The Two Million Container Challenge

Running Nomad for home server

Understanding Networking in Nomad

Who Uses Nomad