Nomad
Nomad — оркестратор рабочих нагрузок Meduza. Он планирует Docker-контейнеры, управляет проверками здоровья, обрабатывает перезапуски и интегрируется с Traefik (для маршрутизации) и Consul (для конфигурации).
Основные концепции
Задачи, группы и задачи
Nomad использует трёхуровневую иерархию:
Job (например, "hub-prod")
├── Group: "backend"
│ └── Task: backend container
└── Group: "frontend"
└── Task: frontend container- Job — единица работы верхнего уровня. Одна задача на сервис на окружение.
- Group — набор задач, размещённых на одном узле. Каждая группа масштабируется независимо.
- Task — один контейнер (драйвер Docker), работающий внутри группы.
Переменные
Файлы задач используют HCL-переменные для параметризации деплоев по окружениям:
variable "env" {
description = "Environment name: prod, stage, or dev"
type = string
}
variable "domain" {
description = "Public domain for the frontend"
type = string
}
variable "api_domain" {
description = "Public domain for the backend API"
type = string
}
variable "image_tag" {
description = "Docker image tag to deploy"
type = string
}
variable "backend_replicas" {
description = "Number of backend task instances"
type = number
default = 1
}Эти переменные передаются при деплое:
nomad job run \
-var="env=prod" \
-var="domain=app.meduza.io" \
-var="api_domain=api.meduza.io" \
-var="image_tag=v1.2.3" \
-var="backend_replicas=2" \
hub.nomad.hclСтруктура задачи: пример Hub
Группа backend
Группа backend использует bridge-сеть с динамическим портом, маппящимся на внутренний порт контейнера 3010. Nomad автоматически выбирает доступный порт хоста, а Traefik обнаруживает его через каталог сервисов.
group "backend" {
count = var.backend_replicas
network {
mode = "bridge"
port "http" {
to = 3010
}
}
service {
name = "hub-${var.env}-backend"
port = "http"
provider = "nomad"
check {
type = "http"
path = "/health/live"
interval = "10s"
timeout = "3s"
}
}
task "backend" {
driver = "docker"
config {
image = "registry.example.com/hub-backend:${var.image_tag}"
ports = ["http"]
}
# Consul template block — see Consul docs for details
template {
data = <<-TPL
{{ with consulKey "hub/${var.env}/config" }}
{{ range $key, $value := .Data | parseJSON }}
{{ $key }}={{ $value }}
{{ end }}
{{ end }}
TPL
destination = "secrets/env.env"
env = true
}
resources {
cpu = 500
memory = 512
}
}
}Динамические порты
При использовании динамических портов Nomad назначает случайный доступный порт на хосте. Директива to = 3010 указывает Nomad маппить этот порт хоста на порт 3010 внутри контейнера. Это позволяет избежать конфликтов портов при запуске нескольких экземпляров или окружений на одном хосте.
Группа frontend
Группа frontend использует статические порты, чтобы каждое окружение имело предсказуемый, фиксированный порт. Traefik маршрутизирует публичный HTTPS-трафик на эти порты на основе заголовка Host.
group "frontend" {
count = 1
network {
mode = "bridge"
port "http" {
static = var.env == "prod" ? 8888 : var.env == "stage" ? 8889 : 8890
to = 3000
}
}
service {
name = "hub-${var.env}-frontend"
port = "http"
provider = "nomad"
tags = [
"traefik.enable=true",
"traefik.http.routers.hub-${var.env}-frontend.rule=Host(`${var.domain}`)",
"traefik.http.routers.hub-${var.env}-frontend.entrypoints=websecure",
"traefik.http.routers.hub-${var.env}-frontend.tls.certresolver=letsencrypt",
]
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}
}
task "frontend" {
driver = "docker"
config {
image = "registry.example.com/hub-frontend:${var.image_tag}"
ports = ["http"]
}
resources {
cpu = 200
memory = 256
}
}
}Режимы сети
| Режим | Описание | Когда использовать |
|---|---|---|
| bridge | Контейнер получает собственное сетевое пространство имён. Порты маппятся с хоста в контейнер. | По умолчанию для всех сервисов Meduza |
| host | Контейнер разделяет сетевое пространство имён хоста напрямую. | Не используется в Meduza |
Все задачи Meduza используют режим bridge. Ключевое различие — между статическим и динамическим выделением портов:
| Тип порта | Поведение | Когда использовать |
|---|---|---|
| Динамический | Nomad выбирает доступный порт хоста при планировании | Бэкенд-сервисы (обнаруживаются через каталог сервисов) |
| Статический | Вы указываете точный порт хоста | Фронтенд-сервисы (Traefik требуется стабильный upstream) |
Проверки здоровья
Каждый блок сервиса должен содержать проверку здоровья. Nomad использует их для определения готовности задачи принимать трафик и необходимости перезапуска.
check {
type = "http"
path = "/health/live"
interval = "10s"
timeout = "3s"
}- type —
http,tcpилиscript. HTTP-проверки предпочтительны для веб-сервисов. - path — эндпоинт, на который Nomad будет отправлять запросы. Должен возвращать статус 2xx.
- interval — как часто проверять.
- timeout — время ожидания, после которого проверка считается неудачной.
WARNING
Если проверка здоровья падает многократно, Nomad перезапустит задачу. Убедитесь, что ваш health-эндпоинт лёгкий и не зависит от внешних сервисов, если вы не хотите каскадных перезапусков.
Выделение ресурсов
Каждая задача объявляет свои требования к CPU и памяти. Nomad использует их для плотной упаковки задач на доступных узлах.
| Сервис | CPU (МГц) | Память (МБ) |
|---|---|---|
| Backend | 500 | 512 |
| Frontend | 200 | 256 |
resources {
cpu = 500 # MHz
memory = 512 # MB
}TIP
Это жёсткие лимиты. Если контейнер превысит выделенную память, Nomad завершит его по OOM. Устанавливайте значения с запасом — отслеживайте реальное потребление через nomad alloc status и корректируйте.
Полезные команды
# Run or update a job
nomad job run -var-file=prod.vars.hcl hub.nomad.hcl
# Check job status
nomad job status hub-prod
# View allocations for a job
nomad job allocs hub-prod
# Stream logs for an allocation
nomad alloc logs -f <alloc-id>
# View allocation resource usage
nomad alloc status <alloc-id>
# Stop a job
nomad job stop hub-prod
# Force a periodic garbage collection
nomad system gc