Job Specifications in HCL¶
Job specs are Nomad's declarative configuration language. This domain is the largest on the exam (roughly 25%). You must be able to read and author jobspecs comfortably, know each block's purpose, and understand the interactions between blocks.
Top-Level Structure¶
job "<name>" {
region = "global"
datacenters = ["dc1"]
namespace = "default"
type = "service"
priority = 50
meta {
owner = "platform"
}
group "<name>" {
count = 3
network { /* ... */ }
service { /* ... */ }
volume "<name>" { /* ... */ }
task "<name>" {
driver = "docker"
config { /* ... */ }
resources { /* ... */ }
env { /* ... */ }
template { /* ... */ }
artifact { /* ... */ }
volume_mount { /* ... */ }
}
update { /* ... */ }
restart { /* ... */ }
reschedule { /* ... */ }
}
constraint { /* ... */ }
affinity { /* ... */ }
spread { /* ... */ }
}
Job Types¶
type = "service" # long-running, default
type = "batch" # run to completion
type = "system" # one per node in scope
type = "sysbatch" # run to completion on every node
| Type | Restart | count honored | Typical Use |
|---|---|---|---|
| service | yes, auto | yes | web servers, APIs |
| batch | no | yes | data processing, one-off |
| system | yes | no (one per node) | agents, exporters |
| sysbatch | no | no (one per node) | cluster-wide tasks |
Periodic Jobs¶
Cron-style scheduling for batch jobs:
job "cleanup" {
type = "batch"
periodic {
cron = "0 2 * * *" # daily at 2 AM
time_zone = "UTC"
prohibit_overlap = true
}
}
prohibit_overlap = true prevents a new execution if the previous is still running.
Parameterized Jobs¶
Dispatch-style jobs:
job "data-processor" {
type = "batch"
parameterized {
payload = "optional"
meta_required = ["file"]
meta_optional = ["format"]
}
}
Register once, dispatch many times:
nomad job dispatch -meta file=data.csv data-processor
Each dispatch creates a new instance of the job.
Group Block¶
A group is a collection of tasks placed on the same client, sharing network and volumes.
group "web" {
count = 5
ephemeral_disk {
sticky = true
migrate = true
size = 500
}
migrate {
max_parallel = 2
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
}
}
Task Block¶
task "nginx" {
driver = "docker"
config {
image = "nginx:1.25"
ports = ["http"]
volumes = [
"local/nginx.conf:/etc/nginx/conf.d/default.conf"
]
}
env {
LOG_LEVEL = "info"
}
resources {
cpu = 500
memory = 256
}
kill_timeout = "30s"
kill_signal = "SIGTERM"
shutdown_delay = "10s"
}
Task Drivers¶
Docker¶
driver = "docker"
config {
image = "redis:7"
args = ["--maxmemory", "256mb"]
ports = ["redis"]
hostname = "redis-${NOMAD_ALLOC_INDEX}"
command = "redis-server"
work_dir = "/data"
volumes = ["local/config:/etc/redis"]
}
Exec (isolated)¶
driver = "exec"
config {
command = "/usr/local/bin/myapp"
args = ["--port", "8080"]
}
Uses cgroups and chroot for isolation. Linux only.
Raw Exec¶
driver = "raw_exec"
No isolation; process runs as the Nomad agent user. Disabled by default; enable per-client. Use only for trusted workloads.
Java¶
driver = "java"
config {
class_path = "/opt/app/*"
jar_path = "/opt/app/myapp.jar"
jvm_options = ["-Xmx512m"]
}
Others¶
qemu: KVM VMspodman: Podman containerscontainerd: containerdnspawn: systemd-nspawn containers
Resources¶
resources {
cpu = 1000 # MHz (1 GHz)
memory = 512 # MiB
memory_max = 1024 # Enterprise: allow burst
disk = 200 # MiB ephemeral
}
CPU is MHz, not cores. A 2.4 GHz core = 2400 MHz. Scheduler bin-packs based on these.
Env Block¶
env {
NODE_ENV = "production"
DB_HOST = "db.service.consul"
APP_VERSION = "${NOMAD_JOB_VERSION}"
}
Nomad runtime variables available:
NOMAD_ALLOC_IDNOMAD_ALLOC_NAMENOMAD_ALLOC_INDEX(0, 1, 2, ... for count)NOMAD_TASK_NAMENOMAD_JOB_NAMENOMAD_DCNOMAD_REGIONNOMAD_IP_<port>,NOMAD_PORT_<port>,NOMAD_ADDR_<port>NOMAD_META_<key>
Template Block¶
Rendered on the client using consul-template syntax. Supports Consul, Vault, env vars.
template {
data = <<EOH
{{ range service "api" }}
SERVER {{ .Address }}:{{ .Port }}
{{ end }}
EOH
destination = "local/upstream.conf"
change_mode = "restart"
change_signal = "SIGHUP"
splay = "1m"
perms = "644"
}
destination: relative path.local/persists,secrets/is tmpfs.env = true: loads file as environment variableschange_mode:restart,signal,noopsplay: random delay to avoid thundering herd
Artifact Block¶
Download files before the task starts:
artifact {
source = "https://example.com/app.tar.gz"
destination = "local/"
mode = "file" # or "any", "dir"
options {
checksum = "sha256:abc123..."
}
}
Supports http, https, git, s3, gcs protocols.
Update Block (Deployments)¶
update {
max_parallel = 2
canary = 1
min_healthy_time = "30s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
auto_promote = false
stagger = "30s"
}
canary: number of new-version allocs to run alongside old before promoteauto_promote: promote canaries when healthyauto_revert: rollback on failed healthstagger: only for system jobs, delay between node updates
Migrate Block¶
Governs allocation migration during node drains (service jobs only):
migrate {
max_parallel = 2
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
}
Restart Block¶
restart {
attempts = 3
interval = "5m"
delay = "15s"
mode = "fail" # or "delay"
}
Mode: - fail: after attempts exhausted, mark task failed - delay: wait interval then retry
Reschedule Block¶
reschedule {
attempts = 5
interval = "1h"
delay = "30s"
delay_function = "exponential"
max_delay = "10m"
unlimited = false
}
Reschedule moves an alloc to a new node; restart keeps it on the same.
Constraint¶
Hard requirements:
constraint {
attribute = "${attr.kernel.name}"
value = "linux"
}
constraint {
attribute = "${node.class}"
operator = "!="
value = "gpu"
}
constraint {
operator = "distinct_hosts"
value = "true"
}
Operators: =, !=, >, >=, <, <=, regexp, set_contains, set_contains_all, distinct_hosts, distinct_property, is_set, is_not_set, semver, version.
Affinity¶
Soft preferences:
affinity {
attribute = "${meta.rack}"
value = "rack-1"
weight = 100 # -100 to 100
}
Spread¶
Encourage distribution across an attribute:
spread {
attribute = "${node.datacenter}"
weight = 100
target "dc1" { percent = 50 }
target "dc2" { percent = 50 }
}
Service Block (covered in networking note)¶
Briefly:
service {
name = "api"
port = "http"
tags = ["v1"]
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "2s"
}
}
Vault Block (Enterprise or with config)¶
vault {
policies = ["db-read"]
change_mode = "restart"
change_signal = "SIGHUP"
env = true
}
Nomad obtains a Vault token for the task, renews it, revokes on stop.
CSI / Host Volumes (covered in storage note)¶
HCL vs JSON¶
Nomad accepts both HCL and JSON jobspecs. HCL is more readable. Convert: nomad job run -json job.json or -output flag during plan.
Variables (HCL2)¶
Nomad supports HCL2 variable interpolation in jobspecs:
variable "image_tag" {
type = string
default = "latest"
}
job "web" {
group "frontend" {
task "nginx" {
config {
image = "nginx:${var.image_tag}"
}
}
}
}
Pass via -var='image_tag=v2' or -var-file=prod.vars.hcl.
Exam-Ready Checklist¶
- Can author a full jobspec from scratch
- Know the 4 job types and when to use each
- Know major task drivers
- Can use
template,artifact,vault,env - Understand update/canary flow
- Know constraint operators
- Know the difference between restart and reschedule