Nomad
HashiCorp Nomad 完全ガイド — ワークロードオーケストレーションの全容
1. はじめに
1.1 Nomad とは何か
HashiCorp Nomad は、コンテナ化されたアプリケーションだけでなく、レガシーなバイナリ、Java アプリケーション、仮想マシンなど、あらゆる種類のワークロードをデプロイ・管理するための汎用ワークロードオーケストレーターである。Kubernetes がコンテナオーケストレーションに特化しているのに対し、Nomad は「あらゆるワークロードをあらゆるインフラストラクチャ上で実行する」という設計哲学を持つ。
Nomad は単一バイナリで動作し、軽量でありながらも大規模環境(数万ノード規模)に対応できるスケーラビリティを備えている。HashiCorp のエコシステム(Consul、Vault、Terraform)との緊密な統合により、サービスディスカバリ、シークレット管理、インフラストラクチャプロビジョニングをシームレスに実現できる。
1.2 Nomad が解決する課題
現代のインフラストラクチャ運用において、以下のような課題が存在する。
- 多様なワークロードの統一管理: コンテナだけでなく、レガシーアプリケーション、バッチジョブ、システムデーモンなどを単一のプラットフォームで管理する必要性
- マルチリージョン・マルチクラウド対応: 複数のデータセンターやクラウドプロバイダーにまたがるデプロイメント
- 運用の複雑さの軽減: Kubernetes のような重厚なオーケストレーターの学習コストと運用負荷
- 段階的な導入: 既存インフラへの影響を最小限に抑えた段階的なオーケストレーション導入
1.3 Nomad の主要な特徴
| 特徴 | 説明 |
|---|---|
| 単一バイナリ | 外部依存なし、軽量デプロイ |
| マルチワークロード | コンテナ、VM、バイナリ、Java など |
| マルチリージョン | ネイティブなマルチリージョンフェデレーション |
| 宣言的ジョブ仕様 | HCL によるジョブ定義 |
| Bin Packing | 効率的なリソース配置 |
| ローリングアップデート | ゼロダウンタイムデプロイ |
| サービスディスカバリ | Consul との統合 |
| シークレット管理 | Vault との統合 |
| ACL | きめ細かなアクセス制御 |
| Web UI | 組み込みダッシュボード |
1.4 Nomad と Kubernetes の比較
Nomad と Kubernetes はしばしば比較されるが、それぞれ異なる設計思想に基づいている。
| 観点 | Nomad | Kubernetes |
|---|---|---|
| アーキテクチャ | 単一バイナリ、軽量 | 多数のコンポーネント、複雑 |
| 対応ワークロード | コンテナ、VM、バイナリ等 | 主にコンテナ |
| 学習コスト | 比較的低い | 高い |
| スケーラビリティ | 数万ノード(単一クラスタ) | 数千ノード(単一クラスタ) |
| エコシステム | HashiCorp スタック中心 | CNCF エコシステム、巨大 |
| サービスメッシュ | Consul Connect | Istio, Linkerd 等 |
| パッケージ管理 | Nomad Pack | Helm |
| セットアップ難易度 | 低い | 高い |
| コミュニティ規模 | 中程度 | 非常に大きい |
Kubernetes は事実上の業界標準であるが、Nomad は以下のようなケースで特に有効である。
- レガシーアプリケーションのオーケストレーションが必要な場合
- 運用チームが小規模で、シンプルなソリューションが求められる場合
- HashiCorp スタックを既に利用している場合
- マルチリージョンデプロイが主要な要件である場合
2. アーキテクチャ
2.1 全体構成
Nomad のアーキテクチャは、**サーバー(Server)とクライアント(Client)**の2つの主要コンポーネントで構成される。
┌─────────────────────────────────────────────────────┐
│ Nomad Cluster │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Server │──│ Server │──│ Server │ (Raft) │
│ │ (Leader)│ │(Follower)│ │(Follower)│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ┌────┴────────────┴────────────┴────┐ │
│ │ RPC / gRPC │ │
│ └────┬────────────┬────────────┬────┘ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │ Client │ │ Client │ │ Client │ │
│ │ (Node) │ │ (Node) │ │ (Node) │ │
│ │┌───────┐│ │┌───────┐│ │┌───────┐│ │
│ ││ Alloc ││ ││ Alloc ││ ││ Alloc ││ │
│ ││ Alloc ││ ││ Alloc ││ ││ Alloc ││ │
│ │└───────┘│ │└───────┘│ │└───────┘│ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────┘
2.2 サーバー(Server)
サーバーはクラスタの「頭脳」であり、以下の責務を担う。
- ジョブの評価とスケジューリング: ジョブ仕様を受け取り、適切なノードへのワークロード配置を決定
- 状態管理: クラスタの全状態を Raft コンセンサスプロトコルで管理
- リーダー選出: Raft によるリーダー選出とフェイルオーバー
- API エンドポイント: CLI、UI、外部システムからのリクエストを処理
Raft コンセンサス
サーバーは Raft コンセンサスプロトコルを使用して、クラスタ状態の一貫性を保証する。推奨構成は3台または5台のサーバーで、過半数(クォーラム)が動作していれば、クラスタは正常に機能する。
# サーバー設定例
server {
enabled = true
bootstrap_expect = 3
# Raft のパフォーマンスチューニング
raft_protocol = 3
# サーバー間の暗号化
encrypt = "cg8StVXbQJ0gPvMd9o7yrg=="
# データディレクトリ
data_dir = "/opt/nomad/data"
# サーバージョイン
server_join {
retry_join = [
"nomad-server-1.example.com",
"nomad-server-2.example.com",
"nomad-server-3.example.com"
]
}
}
2.3 クライアント(Client)
クライアントは実際にワークロードを実行するノードであり、以下の役割を果たす。
- タスクの実行: サーバーから割り当てられたタスクをタスクドライバーを通じて実行
- リソース管理: CPU、メモリ、ディスク、ネットワークのリソースをサーバーに報告
- ヘルスチェック: 実行中のタスクの健全性を監視
- フィンガープリンティング: ノードの特性(OS、アーキテクチャ、利用可能なドライバーなど)を自動検出
# クライアント設定例
client {
enabled = true
# サーバーへの接続
servers = [
"nomad-server-1.example.com:4647",
"nomad-server-2.example.com:4647",
"nomad-server-3.example.com:4647"
]
# ノードクラス(スケジューリング制約に使用)
node_class = "compute-optimized"
# メタデータ(スケジューリング制約に使用)
meta {
rack = "rack-1"
zone = "us-east-1a"
team = "platform"
}
# ホストボリューム
host_volume "data" {
path = "/opt/data"
read_only = false
}
# リソース予約(OS 用)
reserved {
cpu = 500
memory = 512
disk = 1024
}
}
2.4 リージョンとデータセンター
Nomad はネイティブにマルチリージョン・マルチデータセンターをサポートする。
┌──────────────────────┐ ┌──────────────────────┐
│ Region: us-east │ │ Region: eu-west │
│ │ │ │
│ ┌────────────────┐ │ │ ┌────────────────┐ │
│ │ DC: us-east-1 │ │ │ │ DC: eu-west-1 │ │
│ │ Server × 3 │◄─┼─────┼─►│ Server × 3 │ │
│ │ Client × N │ │ │ │ Client × N │ │
│ └────────────────┘ │ │ └────────────────┘ │
│ │ │ │
│ ┌────────────────┐ │ │ ┌────────────────┐ │
│ │ DC: us-east-2 │ │ │ │ DC: eu-west-2 │ │
│ │ Client × N │ │ │ │ Client × N │ │
│ └────────────────┘ │ │ └────────────────┘ │
└──────────────────────┘ └──────────────────────┘
# リージョンとデータセンターの設定
datacenter = "us-east-1"
region = "us-east"
# 他リージョンとのフェデレーション
server {
enabled = true
bootstrap_expect = 3
# リージョン間通信
server_join {
retry_join = ["provider=aws tag_key=nomad-server tag_value=true"]
}
}
2.5 通信とポート
Nomad は以下のポートを使用する。
| ポート | プロトコル | 用途 |
|---|---|---|
| 4646 | HTTP | API、UI |
| 4647 | RPC | サーバー間、クライアント-サーバー間通信 |
| 4648 | Serf (TCP/UDP) | ゴシッププロトコル(WAN/LAN) |
# ポートとアドレスの設定
addresses {
http = "0.0.0.0"
rpc = "0.0.0.0"
serf = "0.0.0.0"
}
ports {
http = 4646
rpc = 4647
serf = 4648
}
# TLS 設定
tls {
http = true
rpc = true
ca_file = "/opt/nomad/tls/ca.pem"
cert_file = "/opt/nomad/tls/server.pem"
key_file = "/opt/nomad/tls/server-key.pem"
verify_server_hostname = true
verify_https_client = true
}
3. ジョブ仕様(Job Specification)
3.1 ジョブの階層構造
Nomad のジョブ仕様は階層的な構造を持ち、以下のように整理される。
Job
├── Group (Task Group)
│ ├── Task
│ │ ├── Driver (docker, exec, java, etc.)
│ │ ├── Resources (CPU, Memory, Network)
│ │ ├── Artifacts
│ │ ├── Templates
│ │ └── Services
│ ├── Task
│ ├── Network
│ ├── Volume
│ ├── Scaling
│ └── Service
├── Group
│ └── ...
├── Constraint
├── Affinity
├── Spread
└── Update
3.2 ジョブタイプ
Nomad は4種類のジョブタイプを提供する。
Service ジョブ
長時間実行されるサービス。デフォルトのジョブタイプ。
job "web-api" {
datacenters = ["us-east-1", "us-east-2"]
type = "service"
# アップデート戦略
update {
max_parallel = 2
min_healthy_time = "30s"
healthy_deadline = "5m"
auto_revert = true
canary = 1
}
# マイグレーション戦略
migrate {
max_parallel = 1
health_check = "checks"
min_healthy_time = "15s"
healthy_deadline = "5m"
}
group "api" {
count = 6
# ネットワーク設定
network {
port "http" {
to = 8080
}
port "metrics" {
to = 9090
}
}
# サービス登録(Consul 連携)
service {
name = "web-api"
port = "http"
tags = ["urlprefix-/api"]
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}
# Consul Connect (サービスメッシュ)
connect {
sidecar_service {
proxy {
upstreams {
destination_name = "database"
local_bind_port = 5432
}
}
}
}
}
# ボリューム
volume "data" {
type = "host"
source = "data"
read_only = false
}
# リスケジュール戦略
reschedule {
attempts = 10
interval = "24h"
delay = "30s"
delay_function = "exponential"
max_delay = "1h"
unlimited = false
}
task "api-server" {
driver = "docker"
config {
image = "registry.example.com/web-api:v2.1.0"
ports = ["http", "metrics"]
# Docker 固有の設定
labels {
service = "web-api"
env = "production"
}
# ログ設定
logging {
type = "json-file"
config {
max-size = "10m"
max-file = "3"
}
}
# ulimit 設定
ulimit {
nofile = "65536:65536"
}
}
# ボリュームマウント
volume_mount {
volume = "data"
destination = "/app/data"
}
# リソース制約
resources {
cpu = 1000 # MHz
memory = 512 # MB
}
# 環境変数
env {
APP_ENV = "production"
LOG_LEVEL = "info"
DB_HOST = "${NOMAD_UPSTREAM_ADDR_database}"
}
# テンプレート(Consul / Vault 連携)
template {
data = <<-EOF
{{- with secret "secret/data/web-api" }}
DB_PASSWORD={{ .Data.data.db_password }}
API_KEY={{ .Data.data.api_key }}
{{- end }}
{{- range service "redis" }}
REDIS_ADDR={{ .Address }}:{{ .Port }}
{{- end }}
EOF
destination = "secrets/env.txt"
env = true
change_mode = "restart"
}
# Vault 連携
vault {
policies = ["web-api"]
}
# ログローテーション
logs {
max_files = 10
max_file_size = 50
}
# シグナル設定
kill_timeout = "30s"
kill_signal = "SIGTERM"
# リスタートポリシー
restart {
attempts = 3
interval = "5m"
delay = "15s"
mode = "delay"
}
}
# サイドカータスク
task "log-shipper" {
driver = "docker"
config {
image = "fluent/fluent-bit:latest"
}
resources {
cpu = 100
memory = 128
}
lifecycle {
hook = "poststart"
sidecar = true
}
}
}
}
Batch ジョブ
短期間のタスクやバッチ処理に使用する。
job "data-pipeline" {
datacenters = ["us-east-1"]
type = "batch"
# 定期実行(cron)
periodic {
cron = "0 */6 * * *"
prohibit_overlap = true
time_zone = "Asia/Tokyo"
}
group "etl" {
count = 1
# エフェメラルディスク
ephemeral_disk {
size = 5000 # MB
migrate = false
sticky = false
}
task "extract-transform-load" {
driver = "docker"
config {
image = "registry.example.com/etl-pipeline:v1.3.0"
command = "/app/run-etl.sh"
args = ["--date", "${NOMAD_META_run_date}"]
}
resources {
cpu = 4000
memory = 4096
}
# アーティファクト(外部ファイルのダウンロード)
artifact {
source = "s3://my-bucket/config/etl-config.yaml"
destination = "local/config/"
options {
aws_access_key_id = "ACCESS_KEY"
aws_access_key_secret = "SECRET_KEY"
}
}
template {
data = <<-EOF
{{- with secret "secret/data/etl" }}
SOURCE_DB_URL={{ .Data.data.source_db_url }}
TARGET_DB_URL={{ .Data.data.target_db_url }}
{{- end }}
EOF
destination = "secrets/db.env"
env = true
}
vault {
policies = ["etl-pipeline"]
}
}
}
}
System ジョブ
全ノード(または制約に一致するノード)で1つずつ実行されるデーモン型ジョブ。
job "node-exporter" {
datacenters = ["us-east-1", "us-east-2"]
type = "system"
# システムジョブのアップデート戦略
update {
max_parallel = 1
min_healthy_time = "10s"
healthy_deadline = "3m"
stagger = "30s"
}
group "monitoring" {
network {
port "metrics" {
static = 9100
}
}
service {
name = "node-exporter"
port = "metrics"
tags = ["monitoring", "prometheus"]
check {
type = "http"
path = "/metrics"
interval = "30s"
timeout = "5s"
}
}
task "node-exporter" {
driver = "docker"
config {
image = "prom/node-exporter:v1.6.1"
network_mode = "host"
pid_mode = "host"
ports = ["metrics"]
volumes = [
"/proc:/host/proc:ro",
"/sys:/host/sys:ro",
"/:/rootfs:ro",
]
args = [
"--path.procfs=/host/proc",
"--path.sysfs=/host/sys",
"--path.rootfs=/rootfs",
"--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($|/)",
]
}
resources {
cpu = 100
memory = 64
}
}
}
}
Sysbatch ジョブ
全ノードで一度だけ実行されるバッチジョブ。
job "security-scan" {
datacenters = ["us-east-1"]
type = "sysbatch"
group "scanner" {
task "cis-benchmark" {
driver = "exec"
config {
command = "/usr/local/bin/cis-scanner"
args = ["--output", "/alloc/data/results.json"]
}
resources {
cpu = 500
memory = 256
}
}
}
}
3.3 制約(Constraint)、アフィニティ(Affinity)、スプレッド(Spread)
Constraint(ハード制約)
条件を満たすノードにのみ配置する。
job "gpu-training" {
# カーネルバージョンの制約
constraint {
attribute = "${attr.kernel.version}"
operator = "version"
value = ">= 5.4.0"
}
group "training" {
# GPU が利用可能なノードのみ
constraint {
attribute = "${attr.driver.docker.volumes.enabled}"
value = "true"
}
constraint {
attribute = "${node.class}"
value = "gpu-enabled"
}
# Distinct Hosts: 各アロケーションを異なるホストに配置
constraint {
operator = "distinct_hosts"
value = "true"
}
# Distinct Property: 異なるラックに配置
constraint {
operator = "distinct_property"
attribute = "${meta.rack}"
}
task "train" {
driver = "docker"
config {
image = "registry.example.com/ml-training:latest"
# GPU デバイスのマウント
devices = ["/dev/nvidia0"]
privileged = true
}
resources {
cpu = 8000
memory = 16384
device "nvidia/gpu" {
count = 1
constraint {
attribute = "${device.attr.memory}"
operator = ">="
value = "8 GiB"
}
affinity {
attribute = "${device.model}"
value = "Tesla V100"
weight = 75
}
}
}
}
}
}
Affinity(ソフト制約)
優先的に配置するが、必須ではない。
group "api" {
# SSD ストレージを持つノードを優先
affinity {
attribute = "${meta.storage_type}"
value = "ssd"
weight = 80 # -100 〜 100
}
# 特定のゾーンを優先
affinity {
attribute = "${meta.zone}"
value = "us-east-1a"
weight = 50
}
}
Spread(分散配置)
アロケーションを特定の属性に基づいて分散させる。
group "api" {
count = 12
# アベイラビリティゾーンに均等に分散
spread {
attribute = "${meta.zone}"
weight = 100
target "us-east-1a" { percent = 34 }
target "us-east-1b" { percent = 33 }
target "us-east-1c" { percent = 33 }
}
# ラックに均等に分散
spread {
attribute = "${meta.rack}"
weight = 50
}
}
4. スケジューリング
4.1 スケジューリングの流れ
Nomad のスケジューリングは以下の段階で行われる。
ジョブ登録 → 評価(Evaluation)→ プラン(Plan)→ 割り当て(Allocation)
- 評価(Evaluation): ジョブの状態変更(登録、更新、ノード障害など)をトリガーに生成される
- プランニング: スケジューラがノードの選定、リソースの割り当てを計算
- アロケーション: 実際にタスクをノードに割り当てて実行
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────────┐
│ Job │───►│ Evaluation │───►│ Plan │───►│ Allocation │
│ Submit │ │ Queue │ │ Queue │ │ Execute │
└──────────┘ └────────────┘ └──────────┘ └────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Scheduler │ │ Task Driver │
│ (bin-pack / │ │ (docker, │
│ spread) │ │ exec, etc.) │
└──────────────┘ └──────────────┘
4.2 スケジューラの種類
| スケジューラ | ジョブタイプ | 説明 |
|---|---|---|
| Service | service | Bin packing とスプレッドアルゴリズムを使用 |
| Batch | batch | 未使用リソースを効率的に活用 |
| System | system, sysbatch | 全対象ノードでの実行を保証 |
4.3 Bin Packing vs Spread
Nomad は2つのスケジューリング戦略を提供する。
Bin Packing(デフォルト): ノードを可能な限り詰めて使い、未使用ノードを最小化する。コスト最適化に有効。
Spread: ノード間で均等にワークロードを分散する。可用性の向上に有効。
# スケジューラ設定(サーバー設定)
server {
default_scheduler_config {
# スケジューラアルゴリズム
scheduler_algorithm = "spread" # or "binpack"
# プリエンプション(優先度ベースの追い出し)
preemption_config {
batch_scheduler_enabled = true
system_scheduler_enabled = true
service_scheduler_enabled = true
sysbatch_scheduler_enabled = true
}
# メモリオーバーサブスクリプション
memory_oversubscription_enabled = true
}
}
4.4 プリエンプション(Preemption)
優先度の高いジョブが、優先度の低いジョブを追い出してリソースを確保する仕組み。
# 高優先度ジョブ
job "critical-service" {
priority = 90 # 1-100(デフォルト: 50)
group "api" {
task "server" {
resources {
cpu = 4000
memory = 8192
}
}
}
}
# 低優先度ジョブ(プリエンプションされる可能性がある)
job "background-worker" {
priority = 20
group "worker" {
task "processor" {
resources {
cpu = 2000
memory = 4096
}
}
}
}
4.5 メモリオーバーサブスクリプション
Nomad 1.1 以降、メモリのオーバーサブスクリプションが可能。memory_max で実際の上限を設定し、memory でスケジューリング時の予約量を指定する。
task "api" {
resources {
cpu = 1000
memory = 256 # スケジューリング時の予約量
memory_max = 1024 # 実際のメモリ上限
}
}
5. タスクドライバー
5.1 組み込みタスクドライバー
Nomad は複数のタスクドライバーを組み込みで提供する。
| ドライバー | 説明 | ユースケース |
|---|---|---|
| docker | Docker コンテナ | コンテナ化されたアプリケーション |
| exec | ネイティブバイナリ | Linux バイナリの直接実行 |
| java | Java アプリケーション | JAR ファイルの実行 |
| raw_exec | 権限なしバイナリ実行 | 分離不要なスクリプト |
| qemu | QEMU 仮想マシン | VM ワークロード |
5.2 Docker ドライバー
最も広く使用されるドライバー。
task "web" {
driver = "docker"
config {
image = "nginx:1.25"
# ポートマッピング
ports = ["http", "https"]
# ボリュームマウント
volumes = [
"local/nginx.conf:/etc/nginx/nginx.conf:ro",
"secrets/tls:/etc/nginx/tls:ro",
]
# Docker ネットワーク
network_mode = "bridge"
# DNS 設定
dns_servers = ["10.0.0.2"]
dns_search_domains = ["service.consul"]
# ヘルスチェック
healthchecks {
disable = false
}
# Docker Auth
auth {
username = "user"
password = "pass"
}
# sysctl 設定
sysctl = {
"net.core.somaxconn" = "65535"
}
# capabilities
cap_add = ["NET_BIND_SERVICE"]
cap_drop = ["ALL"]
# セキュリティ
readonly_rootfs = true
pids_limit = 100
}
}
5.3 Exec ドライバー
ネイティブバイナリを chroot/cgroups で分離して実行する。
task "app" {
driver = "exec"
config {
command = "/usr/local/bin/my-app"
args = [
"--config", "${NOMAD_TASK_DIR}/config.yaml",
"--port", "${NOMAD_PORT_http}",
]
# pid/ipc 名前空間の分離
pid_mode = "private"
ipc_mode = "private"
}
# バイナリのダウンロード
artifact {
source = "https://releases.example.com/my-app/v1.2.0/my-app-linux-amd64"
destination = "local/my-app"
mode = "file"
headers {
Authorization = "Bearer ${TOKEN}"
}
}
}
5.4 Java ドライバー
task "spring-app" {
driver = "java"
config {
jar_path = "local/app.jar"
jvm_options = [
"-Xms512m",
"-Xmx2g",
"-XX:+UseG1GC",
"-Dspring.profiles.active=production",
"-Dserver.port=${NOMAD_PORT_http}",
]
}
artifact {
source = "https://artifactory.example.com/libs-release/my-app/1.0.0/app.jar"
destination = "local/"
}
resources {
cpu = 2000
memory = 2560
}
}
5.5 外部(コミュニティ)タスクドライバー
プラグインシステムにより、以下のような外部ドライバーも利用可能。
| ドライバー | 説明 |
|---|---|
| podman | Podman コンテナ |
| containerd | containerd ランタイム |
| firecracker | Firecracker microVM |
| nspawn | systemd-nspawn コンテナ |
| windows_iis | Windows IIS |
| lxc | LXC コンテナ |
| pot | FreeBSD jail (pot) |
| rookout | Rookout デバッグ |
| Singularity | HPC コンテナ |
| ECS | AWS ECS リモート実行 |
# プラグインディレクトリの設定
plugin_dir = "/opt/nomad/plugins"
# Podman ドライバーの設定例
plugin "nomad-driver-podman" {
config {
socket_path = "unix:///run/podman/podman.sock"
volumes {
enabled = true
}
}
}
6. ネットワーキング
6.1 ネットワークモード
Nomad は複数のネットワークモードをサポートする。
group "api" {
network {
# モード選択
mode = "bridge" # none, host, bridge, cni/<name>
# 動的ポート割り当て
port "http" {
to = 8080 # コンテナ内ポート
}
# 静的ポート割り当て
port "metrics" {
static = 9090
to = 9090
}
# DNS 設定
dns {
servers = ["10.0.0.2"]
searches = ["service.consul"]
options = ["ndots:2"]
}
}
}
6.2 CNI プラグイン
Container Network Interface (CNI) プラグインとの統合により、高度なネットワーク設定が可能。
// /opt/cni/config/mynet.conflist
{
"cniVersion": "0.4.0",
"name": "mynet",
"plugins": [
{
"type": "bridge",
"bridge": "nomad-br0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"ranges": [
[{ "subnet": "172.20.0.0/16" }]
],
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "portmap",
"capabilities": { "portMappings": true }
},
{
"type": "firewall"
}
]
}
# CNI ネットワークの使用
group "api" {
network {
mode = "cni/mynet"
port "http" {
to = 8080
}
}
}
6.3 Consul Connect(サービスメッシュ)
Consul Connect との統合により、mTLS ベースのサービスメッシュを実現する。
job "web-app" {
group "frontend" {
network {
mode = "bridge"
port "http" {
to = 3000
}
}
service {
name = "frontend"
port = "3000"
connect {
sidecar_service {
proxy {
# バックエンド API への接続
upstreams {
destination_name = "backend-api"
local_bind_port = 8080
}
# Redis への接続
upstreams {
destination_name = "redis"
local_bind_port = 6379
}
# プロキシ設定
config {
protocol = "http"
local_connect_timeout_ms = 5000
handshake_timeout_ms = 10000
}
# Envoy の公開設定
expose {
path {
path = "/health"
protocol = "http"
local_path_port = 3000
listener_port = "http"
}
}
}
}
# サイドカータスクのリソース
sidecar_task {
resources {
cpu = 100
memory = 128
}
}
}
}
task "frontend" {
driver = "docker"
config {
image = "registry.example.com/frontend:v2.0"
}
env {
BACKEND_URL = "http://${NOMAD_UPSTREAM_ADDR_backend_api}"
REDIS_URL = "redis://${NOMAD_UPSTREAM_ADDR_redis}"
}
resources {
cpu = 500
memory = 256
}
}
}
}
6.4 Consul Connect の Intention(アクセス制御)
# Consul の Intention 設定(Terraform で管理)
resource "consul_config_entry" "frontend_to_backend" {
kind = "service-intentions"
name = "backend-api"
config_json = jsonencode({
Sources = [
{
Name = "frontend"
Action = "allow"
Precedence = 6
Type = "consul"
}
]
})
}
7. ストレージ
7.1 ストレージの種類
Nomad は複数のストレージ方式をサポートする。
| 種類 | 説明 | 永続性 |
|---|---|---|
| Ephemeral Disk | アロケーションのライフサイクルに紐づく一時ディスク | △(migrate 可能) |
| Host Volume | ホストマシン上のディレクトリ | ○ |
| CSI Volume | Container Storage Interface プラグインによるボリューム | ○ |
7.2 エフェメラルディスク
group "cache" {
ephemeral_disk {
size = 1000 # MB
migrate = true # ノード移動時にデータを移行
sticky = true # 同じノードへの再配置を優先
}
task "redis" {
driver = "docker"
config {
image = "redis:7"
args = ["--dir", "/alloc/data"]
}
}
}
7.3 ホストボリューム
# クライアント設定(nomad.hcl)
client {
host_volume "mysql-data" {
path = "/opt/mysql/data"
read_only = false
}
host_volume "certs" {
path = "/etc/ssl/certs"
read_only = true
}
}
# ジョブでの使用
group "database" {
volume "db-data" {
type = "host"
source = "mysql-data"
read_only = false
}
task "mysql" {
driver = "docker"
config {
image = "mysql:8.0"
}
volume_mount {
volume = "db-data"
destination = "/var/lib/mysql"
}
}
}
7.4 CSI ボリューム
Container Storage Interface(CSI)による動的ボリュームプロビジョニング。
# CSI プラグインのデプロイ(コントローラー)
job "ebs-csi-controller" {
datacenters = ["us-east-1"]
type = "service"
group "controller" {
task "plugin" {
driver = "docker"
config {
image = "amazon/aws-ebs-csi-driver:v1.20.0"
args = [
"--endpoint=unix:///csi/csi.sock",
"--logtostderr",
"--v=5",
]
}
csi_plugin {
id = "aws-ebs"
type = "controller"
mount_dir = "/csi"
}
resources {
cpu = 500
memory = 256
}
}
}
}
# CSI プラグインのデプロイ(ノード)
job "ebs-csi-node" {
datacenters = ["us-east-1"]
type = "system"
group "node" {
task "plugin" {
driver = "docker"
config {
image = "amazon/aws-ebs-csi-driver:v1.20.0"
privileged = true
args = [
"--endpoint=unix:///csi/csi.sock",
"--logtostderr",
"--v=5",
]
}
csi_plugin {
id = "aws-ebs"
type = "node"
mount_dir = "/csi"
}
resources {
cpu = 100
memory = 128
}
}
}
}
# CSI ボリューム登録
resource "nomad_volume" "postgres_data" {
type = "csi"
plugin_id = "aws-ebs"
volume_id = "postgres-data"
name = "postgres-data"
external_id = "vol-0abcdef1234567890"
capability {
access_mode = "single-node-writer"
attachment_mode = "file-system"
}
mount_options {
fs_type = "ext4"
mount_flags = ["noatime"]
}
}
# CSI ボリュームの使用
group "database" {
volume "postgres-data" {
type = "csi"
source = "postgres-data"
read_only = false
attachment_mode = "file-system"
access_mode = "single-node-writer"
}
task "postgres" {
driver = "docker"
config {
image = "postgres:15"
}
volume_mount {
volume = "postgres-data"
destination = "/var/lib/postgresql/data"
}
resources {
cpu = 2000
memory = 4096
}
}
}
8. サービスディスカバリと Consul 連携
8.1 ネイティブサービスディスカバリ
Nomad 1.3 以降、Consul なしでのネイティブサービスディスカバリが可能。
group "api" {
service {
name = "web-api"
provider = "nomad" # Nomad ネイティブ
port = "http"
tags = ["v2", "production"]
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}
}
task "api" {
# テンプレートで他のサービスを参照
template {
data = <<-EOF
{{- range nomadService "redis" }}
REDIS_ADDR={{ .Address }}:{{ .Port }}
{{- end }}
EOF
destination = "local/env.txt"
env = true
}
}
}
8.2 Consul サービス登録
group "api" {
service {
name = "web-api"
provider = "consul" # Consul 連携
port = "http"
tags = ["v2", "production", "urlprefix-/api"]
# タグ付きアドレス
tagged_addresses {
public_wan = "203.0.113.10"
}
# メタデータ
meta {
version = "2.1.0"
team = "platform"
}
# ヘルスチェック
check {
name = "HTTP Health"
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
check_restart {
limit = 3
grace = "60s"
ignore_warnings = false
}
}
check {
name = "TCP Check"
type = "tcp"
interval = "5s"
timeout = "2s"
}
check {
name = "gRPC Health"
type = "grpc"
interval = "10s"
timeout = "3s"
grpc_service = "my.service.v1"
grpc_use_tls = true
tls_server_name = "web-api.service.consul"
}
}
}
8.3 テンプレートによるサービスディスカバリ
Nomad のテンプレートエンジン(consul-template ベース)を活用して、動的なサービスディスカバリが可能。
task "nginx" {
driver = "docker"
config {
image = "nginx:1.25"
volumes = [
"local/nginx.conf:/etc/nginx/nginx.conf:ro",
]
}
# Nginx のアップストリーム設定を動的生成
template {
data = <<-EOF
upstream backend {
{{- range service "backend-api" }}
server {{ .Address }}:{{ .Port }} weight=1;
{{- end }}
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /health {
return 200 "OK";
}
}
EOF
destination = "local/nginx.conf"
change_mode = "signal"
change_signal = "SIGHUP"
splay = "30s"
}
}
9. Vault 連携とシークレット管理
9.1 Vault 連携の設定
# Nomad サーバー/クライアントの Vault 設定
vault {
enabled = true
address = "https://vault.service.consul:8200"
# Vault トークン(サーバーのみ)
token = "s.XXXXXXXXXXXXXXXXXXXXXX"
# TLS 設定
tls_ca_file = "/opt/nomad/tls/vault-ca.pem"
tls_cert_file = "/opt/nomad/tls/vault-client.pem"
tls_key_file = "/opt/nomad/tls/vault-client-key.pem"
# トークン作成の設定
create_from_role = "nomad-cluster"
# 名前空間(Enterprise)
namespace = "admin"
}
9.2 Vault ポリシーとロール
# Vault ポリシー(vault-policy.hcl)
path "secret/data/{{identity.entity.aliases.auth_token_xxxx.metadata.nomad_namespace}}/{{identity.entity.aliases.auth_token_xxxx.metadata.nomad_job_id}}/*" {
capabilities = ["read"]
}
path "database/creds/{{identity.entity.aliases.auth_token_xxxx.metadata.nomad_job_id}}" {
capabilities = ["read"]
}
path "pki/issue/{{identity.entity.aliases.auth_token_xxxx.metadata.nomad_job_id}}" {
capabilities = ["create", "update"]
}
# Vault ロールの作成
vault write auth/token/roles/nomad-cluster \
disallowed_policies="nomad-server" \
token_explicit_max_ttl=0 \
orphan=true \
token_period="72h" \
renewable=true
9.3 ジョブでの Vault 利用
job "web-api" {
group "api" {
task "server" {
vault {
policies = ["web-api-policy"]
change_mode = "restart"
env = true
# Vault 名前空間(Enterprise)
namespace = "engineering"
}
# KV シークレットの取得
template {
data = <<-EOF
{{- with secret "secret/data/web-api/config" }}
DB_PASSWORD={{ .Data.data.password }}
API_SECRET={{ .Data.data.api_secret }}
ENCRYPTION_KEY={{ .Data.data.encryption_key }}
{{- end }}
EOF
destination = "secrets/app.env"
env = true
change_mode = "restart"
}
# 動的データベースクレデンシャル
template {
data = <<-EOF
{{- with secret "database/creds/web-api-role" }}
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
{{- end }}
EOF
destination = "secrets/db.env"
env = true
change_mode = "restart"
}
# PKI 証明書の自動発行
template {
data = <<-EOF
{{- with pkiCert "pki/issue/web-api" "common_name=web-api.service.consul" "ttl=24h" }}
{{ .Cert }}
{{- end }}
EOF
destination = "secrets/tls/cert.pem"
change_mode = "restart"
}
template {
data = <<-EOF
{{- with pkiCert "pki/issue/web-api" "common_name=web-api.service.consul" "ttl=24h" }}
{{ .Key }}
{{- end }}
EOF
destination = "secrets/tls/key.pem"
change_mode = "restart"
}
}
}
}
10. デプロイメント戦略
10.1 ローリングアップデート
job "web-api" {
update {
# 同時にアップデートするアロケーション数
max_parallel = 2
# ヘルシーとみなすまでの最小時間
min_healthy_time = "30s"
# ヘルシー判定のデッドライン
healthy_deadline = "5m"
# 進捗がない場合のデッドライン
progress_deadline = "10m"
# 失敗時の自動ロールバック
auto_revert = true
# アップデート間の待機時間
stagger = "10s"
}
group "api" {
count = 6
task "server" {
driver = "docker"
config {
image = "registry.example.com/web-api:v2.2.0" # 新バージョン
}
}
}
}
10.2 ブルー/グリーンデプロイ
カナリアデプロイを活用したブルー/グリーン戦略。
job "web-api" {
update {
max_parallel = 0 # 手動プロモーションまで待機
canary = 6 # count と同じ数のカナリア = ブルー/グリーン
# 手動プロモーション
auto_promote = false
auto_revert = true
}
group "api" {
count = 6
service {
name = "web-api"
port = "http"
# カナリアインスタンスのタグ
canary_tags = ["canary", "v2.2.0"]
# 本番インスタンスのタグ
tags = ["production", "v2.1.0"]
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}
}
task "server" {
driver = "docker"
config {
image = "registry.example.com/web-api:v2.2.0"
}
}
}
}
# デプロイの状態確認
nomad job deployments -latest web-api
# カナリアのプロモーション(ブルーからグリーンへ切り替え)
nomad deployment promote <deployment-id>
# ロールバック
nomad deployment fail <deployment-id>
10.3 カナリアデプロイ
job "web-api" {
update {
max_parallel = 2
canary = 2 # 2つのカナリアインスタンス
auto_promote = true # ヘルスチェック通過後に自動プロモート
# プロモーションまでの待機時間(ヘルスチェック)
min_healthy_time = "60s"
healthy_deadline = "5m"
auto_revert = true
}
group "api" {
count = 6
# カナリアが先にデプロイされ、ヘルスチェック通過後に残りがアップデートされる
}
}
10.4 マルチリージョンデプロイ
job "global-api" {
type = "service"
# マルチリージョン設定
multiregion {
strategy {
max_parallel = 1 # 一度に1リージョンずつ
on_failure = "fail_all" # 1リージョンで失敗したら全停止
}
region "us-east" {
count = 3
datacenters = ["us-east-1", "us-east-2"]
meta {
region_name = "US East"
}
}
region "eu-west" {
count = 3
datacenters = ["eu-west-1"]
meta {
region_name = "EU West"
}
}
region "ap-northeast" {
count = 2
datacenters = ["ap-northeast-1"]
meta {
region_name = "AP Northeast"
}
}
}
group "api" {
task "server" {
driver = "docker"
config {
image = "registry.example.com/global-api:v3.0.0"
}
resources {
cpu = 1000
memory = 512
}
}
}
}
11. オートスケーリング
11.1 Nomad Autoscaler
Nomad Autoscaler は水平スケーリングとクラスターのスケーリングを提供する。
# Autoscaler の設定ファイル(autoscaler.hcl)
nomad {
address = "http://nomad.service.consul:4646"
}
apm "prometheus" {
driver = "prometheus"
config = {
address = "http://prometheus.service.consul:9090"
}
}
target "aws-asg" {
driver = "aws-asg"
config = {
aws_region = "us-east-1"
}
}
strategy "target-value" {
driver = "target-value"
}
strategy "threshold" {
driver = "threshold"
}
11.2 水平アプリケーションスケーリング
job "web-api" {
group "api" {
count = 3
scaling {
enabled = true
min = 2
max = 20
policy {
# Prometheus メトリクスに基づくスケーリング
evaluation_interval = "30s"
cooldown = "3m"
check "cpu_usage" {
source = "prometheus"
query = "avg(nomad_client_allocs_cpu_total_percent{task='api-server'})"
strategy "target-value" {
target = 70 # CPU 使用率 70% を目標
}
}
check "request_rate" {
source = "prometheus"
query = "sum(rate(http_requests_total{service='web-api'}[5m]))"
strategy "target-value" {
target = 1000 # 1000 req/s を目標
}
}
# Datadog メトリクスに基づくスケーリング
check "queue_depth" {
source = "datadog"
query = "avg:queue.depth{service:web-api}"
strategy "threshold" {
upper_bound = 100
lower_bound = 10
delta = 2
}
}
}
}
task "api-server" {
driver = "docker"
config {
image = "registry.example.com/web-api:v2.1.0"
}
resources {
cpu = 1000
memory = 512
}
}
}
}
11.3 クラスターオートスケーリング
# クラスタースケーリングポリシー
scaling "cluster_policy" {
enabled = true
min = 3
max = 50
policy {
evaluation_interval = "1m"
cooldown = "5m"
check "cpu_allocated" {
source = "nomad-apm"
query = "percentage-allocated_cpu"
strategy "target-value" {
target = 80
}
}
check "memory_allocated" {
source = "nomad-apm"
query = "percentage-allocated_memory"
strategy "target-value" {
target = 80
}
}
target "aws-asg" {
aws_asg_name = "nomad-client-asg"
node_class = "compute-optimized"
node_drain_deadline = "5m"
}
}
}
12. 変数とテンプレート
12.1 Nomad 変数(Variables)
Nomad 1.4 以降、Nomad ネイティブの変数管理機能が利用可能。
# 変数の設定
nomad var put nomad/jobs/web-api/config \
db_host="db.example.com" \
db_port="5432" \
log_level="info"
# 名前空間付き変数
nomad var put -namespace production \
nomad/jobs/web-api/secrets \
api_key="sk-abc123" \
db_password="secret"
# 変数の確認
nomad var get nomad/jobs/web-api/config
# 変数の一覧
nomad var list
# ジョブでの変数参照
task "api" {
template {
data = <<-EOF
{{- with nomadVar "nomad/jobs/web-api/config" }}
DB_HOST={{ .db_host }}
DB_PORT={{ .db_port }}
LOG_LEVEL={{ .log_level }}
{{- end }}
{{- with nomadVar "nomad/jobs/web-api/secrets" }}
API_KEY={{ .api_key }}
DB_PASSWORD={{ .db_password }}
{{- end }}
EOF
destination = "secrets/env.txt"
env = true
}
}
12.2 ランタイム変数
Nomad は多数のランタイム変数を提供する。
task "app" {
env {
# ノード情報
NODE_ID = "${node.unique.id}"
NODE_NAME = "${node.unique.name}"
NODE_DC = "${node.datacenter}"
NODE_CLASS = "${node.class}"
# アロケーション情報
ALLOC_ID = "${NOMAD_ALLOC_ID}"
ALLOC_NAME = "${NOMAD_ALLOC_NAME}"
ALLOC_INDEX = "${NOMAD_ALLOC_INDEX}"
# ジョブ情報
JOB_NAME = "${NOMAD_JOB_NAME}"
GROUP_NAME = "${NOMAD_GROUP_NAME}"
TASK_NAME = "${NOMAD_TASK_NAME}"
NAMESPACE = "${NOMAD_NAMESPACE}"
REGION = "${NOMAD_REGION}"
DC = "${NOMAD_DC}"
# ネットワーク情報
HOST_IP = "${NOMAD_IP_http}"
HOST_PORT = "${NOMAD_PORT_http}"
ADDR = "${NOMAD_ADDR_http}"
# ディレクトリ
ALLOC_DIR = "${NOMAD_ALLOC_DIR}"
TASK_DIR = "${NOMAD_TASK_DIR}"
SECRET_DIR = "${NOMAD_SECRETS_DIR}"
# リソース情報
CPU_LIMIT = "${NOMAD_CPU_LIMIT}"
MEMORY_LIMIT = "${NOMAD_MEMORY_LIMIT}"
}
}
12.3 テンプレート機能
task "app" {
# 設定ファイルの動的生成
template {
data = <<-EOF
# アプリケーション設定
server:
port: {{ env "NOMAD_PORT_http" }}
host: {{ env "NOMAD_IP_http" }}
# サービスディスカバリ
upstream_services:
{{- range service "backend-api" }}
- host: {{ .Address }}
port: {{ .Port }}
tags: {{ .Tags | join "," }}
{{- end }}
# Consul KV からの設定取得
database:
{{- with key "config/web-api/database" }}
{{ . }}
{{- end }}
# 条件分岐
{{- if eq (env "NOMAD_DC") "us-east-1" }}
region: east
{{- else }}
region: west
{{- end }}
# ループ
allowed_origins:
{{- range $key, $pairs := tree "config/web-api/cors" }}
- {{ .Value }}
{{- end }}
# タイムスタンプ
generated_at: {{ timestamp }}
EOF
destination = "local/config.yaml"
change_mode = "signal"
change_signal = "SIGHUP"
# テンプレートのレンダリング間隔
splay = "30s"
# テンプレートエラー時の動作
error_on_missing_key = true
# パーミッション
perms = "0644"
# 左右のデリミタ変更(テンプレートエンジンの衝突回避)
left_delimiter = "[["
right_delimiter = "]]"
}
# バイナリファイルのテンプレート
template {
source = "local/tls/ca-bundle.pem.tpl"
destination = "secrets/tls/ca-bundle.pem"
change_mode = "restart"
}
}
13. ACL(アクセス制御リスト)
13.1 ACL の基本概念
Nomad の ACL システムは、Consul の ACL システムに似た設計で、ポリシーベースのアクセス制御を提供する。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Token │───►│ Policy │───►│ Rules │
│ │ │ │ │ │
│ (認証) │ │ (権限) │ │ (操作) │
└──────────┘ └──────────┘ └──────────┘
13.2 ACL の有効化
# サーバー設定
acl {
enabled = true
# トークンの TTL
token_ttl = "30s"
policy_ttl = "60s"
# ロール/ポリシーの TTL
role_ttl = "60s"
}
# ACL ブートストラップ(最初のマネジメントトークン生成)
nomad acl bootstrap
# 出力例:
# Accessor ID = a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Secret ID = s3cr3t-t0k3n-xxxx-xxxx-xxxxxxxxxxxx
# Name = Bootstrap Token
# Type = management
# Global = true
# Create Time = 2025-01-01T00:00:00Z
# Expiry Time = <none>
# Policies = n/a
13.3 ACL ポリシー
# 開発者用ポリシー(developer-policy.hcl)
namespace "default" {
policy = "read"
capabilities = [
"submit-job",
"read-job",
"list-jobs",
"read-logs",
"read-fs",
]
}
namespace "development" {
policy = "write"
capabilities = [
"submit-job",
"dispatch-job",
"read-job",
"list-jobs",
"read-logs",
"read-fs",
"alloc-exec",
"alloc-lifecycle",
]
}
# ノード操作(読み取りのみ)
node {
policy = "read"
}
# Quota の読み取り
quota {
policy = "read"
}
# ホストボリュームの読み取り
host_volume "*" {
policy = "read"
}
# 管理者用ポリシー(admin-policy.hcl)
namespace "*" {
policy = "write"
capabilities = [
"submit-job",
"dispatch-job",
"read-job",
"list-jobs",
"read-logs",
"read-fs",
"alloc-exec",
"alloc-lifecycle",
"alloc-node-exec",
"csi-register-plugin",
"csi-write-volume",
"csi-read-volume",
"csi-list-volume",
"csi-mount-volume",
"list-scaling-policies",
"read-scaling-policy",
"scale-job",
"sentinel-override",
]
}
node {
policy = "write"
}
agent {
policy = "write"
}
operator {
policy = "write"
}
quota {
policy = "write"
}
host_volume "*" {
policy = "write"
}
plugin {
policy = "read"
}
# ポリシーの作成
nomad acl policy apply developer-policy developer-policy.hcl
nomad acl policy apply admin-policy admin-policy.hcl
# トークンの作成
nomad acl token create \
-name="Developer Token" \
-policy=developer-policy \
-type=client
# ロールの作成
nomad acl role create \
-name="developer-role" \
-policy=developer-policy \
-description="Role for developers"
# トークンにロールを割り当て
nomad acl token create \
-name="Developer Token with Role" \
-role=developer-role \
-type=client
13.4 OIDC 認証
# OIDC 認証メソッドの設定
acl {
enabled = true
}
# OIDC 認証メソッドの作成
nomad acl auth-method create \
-name="okta" \
-type="OIDC" \
-max-token-ttl="8h" \
-config @- <<EOF
{
"OIDCDiscoveryURL": "https://mycompany.okta.com",
"OIDCClientID": "client-id",
"OIDCClientSecret": "client-secret",
"BoundAudiences": ["client-id"],
"AllowedRedirectURIs": [
"http://localhost:4649/oidc/callback",
"https://nomad.example.com:4646/ui/settings/tokens"
],
"ClaimMappings": {
"email": "email"
},
"ListClaimMappings": {
"groups": "groups"
}
}
EOF
# バインディングルールの作成
nomad acl binding-rule create \
-auth-method="okta" \
-bind-type="role" \
-bind-name="developer-role" \
-selector='list.groups contains "Engineering"'
nomad acl binding-rule create \
-auth-method="okta" \
-bind-type="role" \
-bind-name="admin-role" \
-selector='list.groups contains "SRE"'
14. 名前空間とリソースクォータ
14.1 名前空間
# 名前空間の作成
nomad namespace apply -description "Production environment" production
nomad namespace apply -description "Staging environment" staging
nomad namespace apply -description "Development environment" development
# 名前空間の一覧
nomad namespace list
# 名前空間を指定したジョブ操作
nomad job run -namespace=production web-api.nomad
nomad job status -namespace=production web-api
# ジョブ内での名前空間指定
job "web-api" {
namespace = "production"
datacenters = ["us-east-1"]
type = "service"
# ...
}
14.2 リソースクォータ(Enterprise)
# クォータ仕様の定義
quota "production-quota" {
description = "Production environment quota"
limit {
region = "us-east"
region_limit {
cpu = 100000 # 100 GHz
memory = 204800 # 200 GB
}
}
limit {
region = "eu-west"
region_limit {
cpu = 50000 # 50 GHz
memory = 102400 # 100 GB
}
}
}
# クォータの適用
nomad quota apply production-quota.hcl
# 名前空間にクォータを割り当て
nomad namespace apply \
-description "Production environment" \
-quota "production-quota" \
production
# クォータの使用状況確認
nomad quota inspect production-quota
15. 監視とオブザーバビリティ
15.1 Prometheus メトリクス
# Nomad のテレメトリ設定
telemetry {
publish_allocation_metrics = true
publish_node_metrics = true
# Prometheus エンドポイント
prometheus_metrics = true
# StatsD
statsd_address = "statsd.service.consul:8125"
# Datadog
datadog_address = "localhost:8125"
datadog_tags = ["env:production", "service:nomad"]
# メトリクス収集間隔
collection_interval = "10s"
# メトリクスプレフィックス
disable_hostname = true
}
15.2 Prometheus スクレイプ設定
# prometheus.yml
scrape_configs:
# Nomad サーバーメトリクス
- job_name: 'nomad-server'
metrics_path: '/v1/metrics'
params:
format: ['prometheus']
consul_sd_configs:
- server: 'consul.service.consul:8500'
services: ['nomad']
tags: ['server']
relabel_configs:
- source_labels: ['__meta_consul_tags']
regex: '.*,server,.*'
action: keep
# Nomad クライアントメトリクス
- job_name: 'nomad-client'
metrics_path: '/v1/metrics'
params:
format: ['prometheus']
consul_sd_configs:
- server: 'consul.service.consul:8500'
services: ['nomad-client']
relabel_configs:
- source_labels: ['__meta_consul_service']
target_label: 'instance'
15.3 主要メトリクス
| メトリクス | 説明 | アラート閾値例 |
|---|---|---|
nomad.nomad.broker.total_blocked | ブロックされた評価の数 | > 10 |
nomad.nomad.plan.submit | プランの処理時間 | p99 > 5s |
nomad.nomad.worker.invoke_scheduler | スケジューラの呼び出し時間 | p99 > 10s |
nomad.client.allocs.cpu.total_percent | アロケーションの CPU 使用率 | > 90% |
nomad.client.allocs.memory.usage | アロケーションのメモリ使用量 | > 90% |
nomad.client.allocs.oom_killed | OOM キルされたアロケーション | > 0 |
nomad.raft.commitTime | Raft コミット時間 | p99 > 500ms |
nomad.raft.leader.lastContact | リーダーとの最後のコンタクト | > 500ms |
nomad.runtime.num_goroutines | Goroutine 数 | > 10000 |
15.4 ログ管理
# Nomad エージェントのログ設定
log_level = "INFO"
log_file = "/var/log/nomad/nomad.log"
log_rotate_bytes = 104857600 # 100MB
log_rotate_duration = "24h"
log_rotate_max_files = 10
log_json = true
# syslog 出力
enable_syslog = true
syslog_facility = "LOCAL0"
# ジョブのログ確認
nomad alloc logs <alloc-id>
nomad alloc logs -stderr <alloc-id>
nomad alloc logs -f <alloc-id> # ストリーミング
nomad alloc logs -tail -n 100 <alloc-id> # 末尾100行
# 特定のタスクのログ
nomad alloc logs -task web-server <alloc-id>
15.5 Grafana ダッシュボード
主要なパネル構成例:
- クラスタ概要: サーバー/クライアントの数、リーダーの情報
- リソース使用率: CPU/メモリの割り当て率と使用率
- ジョブステータス: Running/Pending/Dead の内訳
- スケジューリング: 評価キュー、ブロック数
- Raft: コミット時間、リーダーコンタクト
- ネットワーク: RPC リクエスト数、レイテンシ
16. 運用
16.1 クラスタの構築
systemd ユニットファイル
# /etc/systemd/system/nomad.service
[Unit]
Description=Nomad
Documentation=https://www.nomadproject.io/docs
Wants=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/nomad.d/nomad.hcl
[Service]
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/nomad agent -config /etc/nomad.d/
KillMode=process
KillSignal=SIGINT
LimitNOFILE=65536
LimitNPROC=infinity
Restart=on-failure
RestartSec=2
StartLimitBurst=3
StartLimitIntervalSec=10
TasksMax=infinity
OOMScoreAdjust=-1000
[Install]
WantedBy=multi-user.target
基本設定ファイル
# /etc/nomad.d/nomad.hcl(共通設定)
datacenter = "us-east-1"
data_dir = "/opt/nomad/data"
log_level = "INFO"
log_json = true
bind_addr = "0.0.0.0"
addresses {
http = "0.0.0.0"
rpc = "{{ GetPrivateInterfaces | attr \"address\" }}"
serf = "{{ GetPrivateInterfaces | attr \"address\" }}"
}
advertise {
http = "{{ GetPrivateInterfaces | attr \"address\" }}"
rpc = "{{ GetPrivateInterfaces | attr \"address\" }}"
serf = "{{ GetPrivateInterfaces | attr \"address\" }}"
}
# Consul 連携
consul {
address = "127.0.0.1:8500"
server_service_name = "nomad"
client_service_name = "nomad-client"
auto_advertise = true
server_auto_join = true
client_auto_join = true
# Consul トークン
token = "consul-token-for-nomad"
tags = ["production"]
}
# Vault 連携
vault {
enabled = true
address = "https://vault.service.consul:8200"
}
# TLS
tls {
http = true
rpc = true
ca_file = "/opt/nomad/tls/ca.pem"
cert_file = "/opt/nomad/tls/nomad.pem"
key_file = "/opt/nomad/tls/nomad-key.pem"
verify_server_hostname = true
verify_https_client = false
}
# ACL
acl {
enabled = true
}
# テレメトリ
telemetry {
publish_allocation_metrics = true
publish_node_metrics = true
prometheus_metrics = true
}
16.2 アップグレード手順
# 1. 新バージョンのダウンロード
curl -o nomad_new.zip https://releases.hashicorp.com/nomad/1.7.0/nomad_1.7.0_linux_amd64.zip
unzip nomad_new.zip
# 2. クライアントのドレイン(計画的な退避)
nomad node drain -enable -deadline 5m <node-id>
# 3. サービスの停止
sudo systemctl stop nomad
# 4. バイナリの置き換え
sudo mv nomad /usr/local/bin/nomad
# 5. サービスの再起動
sudo systemctl start nomad
# 6. ドレインの解除
nomad node drain -disable <node-id>
# 7. ヘルスチェック
nomad server members
nomad node status
16.3 バックアップとリストア
# スナップショットの取得
nomad operator snapshot save backup.snap
# 自動バックアップ設定(Enterprise)
nomad operator snapshot agent \
-interval 1h \
-retain 24 \
-path /opt/nomad/snapshots/
# スナップショットのリストア
nomad operator snapshot restore backup.snap
16.4 トラブルシューティング
# サーバーメンバーの確認
nomad server members
# ノードステータスの確認
nomad node status
nomad node status -verbose <node-id>
# ジョブの評価確認
nomad eval status <eval-id>
# アロケーションの詳細確認
nomad alloc status <alloc-id>
# アロケーション内のファイルシステム確認
nomad alloc fs <alloc-id> /
# アロケーションへのシェルアクセス
nomad alloc exec -task web-server <alloc-id> /bin/sh
# デバッグバンドルの生成
nomad operator debug -duration 5m -interval 30s
# Raft ピアの確認
nomad operator raft list-peers
# ガベージコレクション
nomad system gc
# 強制的な評価
nomad eval trigger -job web-api
17. Terraform による Nomad 管理
17.1 Nomad プロバイダー
# provider.tf
terraform {
required_providers {
nomad = {
source = "hashicorp/nomad"
version = "~> 2.0"
}
}
}
provider "nomad" {
address = "https://nomad.example.com:4646"
region = "us-east"
secret_id = var.nomad_token
# TLS 設定
ca_file = "/path/to/ca.pem"
cert_file = "/path/to/client.pem"
key_file = "/path/to/client-key.pem"
}
17.2 ジョブの管理
# ジョブの登録
resource "nomad_job" "web_api" {
jobspec = file("${path.module}/jobs/web-api.nomad.hcl")
hcl2 {
enabled = true
vars = {
image_tag = var.web_api_image_tag
replicas = var.web_api_replicas
environment = var.environment
}
}
# デタッチモード(デプロイ完了を待たない)
detach = false
}
# 名前空間の管理
resource "nomad_namespace" "production" {
name = "production"
description = "Production workloads"
quota = nomad_quota_specification.production.name
capabilities {
enabled_task_drivers = ["docker", "exec"]
disabled_task_drivers = ["raw_exec"]
}
meta = {
owner = "platform-team"
env = "production"
}
}
# ACL ポリシー
resource "nomad_acl_policy" "developer" {
name = "developer"
description = "Developer access policy"
rules_hcl = <<-EOF
namespace "development" {
policy = "write"
}
namespace "production" {
policy = "read"
}
node {
policy = "read"
}
EOF
}
# ACL トークン
resource "nomad_acl_token" "developer" {
name = "developer-token"
type = "client"
policies = [nomad_acl_policy.developer.name]
}
# CSI ボリューム
resource "nomad_csi_volume" "postgres" {
plugin_id = "aws-ebs"
volume_id = "postgres-data"
name = "postgres-data"
external_id = aws_ebs_volume.postgres.id
capability {
access_mode = "single-node-writer"
attachment_mode = "file-system"
}
mount_options {
fs_type = "ext4"
mount_flags = ["noatime"]
}
}
# スケジューラ設定
resource "nomad_scheduler_config" "config" {
scheduler_algorithm = "spread"
memory_oversubscription_enabled = true
preemption_config {
batch_scheduler_enabled = true
system_scheduler_enabled = true
service_scheduler_enabled = true
sysbatch_scheduler_enabled = true
}
}
18. CI/CD パイプラインとの統合
18.1 GitHub Actions によるデプロイ
# .github/workflows/deploy.yml
name: Deploy to Nomad
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and Push Docker Image
run: |
docker build -t registry.example.com/web-api:${{ github.sha }} .
docker push registry.example.com/web-api:${{ github.sha }}
- name: Setup Nomad
uses: hashicorp/setup-nomad@main
with:
version: '1.7.0'
- name: Deploy to Nomad
env:
NOMAD_ADDR: ${{ secrets.NOMAD_ADDR }}
NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}
NOMAD_CACERT: ${{ secrets.NOMAD_CACERT }}
run: |
nomad job run \
-var="image_tag=${{ github.sha }}" \
-var="replicas=6" \
jobs/web-api.nomad.hcl
- name: Wait for Deployment
env:
NOMAD_ADDR: ${{ secrets.NOMAD_ADDR }}
NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}
run: |
DEPLOY_ID=$(nomad job deployments -latest -json web-api | jq -r '.[0].ID')
nomad deployment status -monitor $DEPLOY_ID
18.2 Nomad Pack
Nomad Pack は、Nomad ジョブのパッケージ管理ツール(Helm に相当)。
# Nomad Pack のインストール
brew install hashicorp/tap/nomad-pack
# レジストリの追加
nomad-pack registry add community \
github.com/hashicorp/nomad-pack-community-registry
# 利用可能なパックの一覧
nomad-pack registry list
# パックの実行
nomad-pack run traefik \
--var="traefik_count=3" \
--var="resources_cpu=500"
# パックの情報確認
nomad-pack info traefik --registry=community
カスタム Nomad Pack の作成
my-app-pack/
├── README.md
├── metadata.hcl
├── variables.hcl
├── templates/
│ ├── my-app.nomad.tpl
│ └── _helpers.tpl
└── outputs.tpl
# metadata.hcl
app {
url = "https://github.com/example/my-app"
author = "Platform Team"
}
pack {
name = "my-app"
description = "My Application deployment pack"
version = "1.0.0"
}
# variables.hcl
variable "job_name" {
description = "The name of the Nomad job"
type = string
default = "my-app"
}
variable "image" {
description = "Docker image"
type = string
default = "registry.example.com/my-app:latest"
}
variable "count" {
description = "Number of instances"
type = number
default = 3
}
variable "resources" {
description = "Resource requirements"
type = object({
cpu = number
memory = number
})
default = {
cpu = 500
memory = 256
}
}
19. セキュリティベストプラクティス
19.1 TLS の完全な設定
# CA 証明書の生成(cfssl を使用)
cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"nomad": {
"usages": [
"signing", "key encipherment",
"server auth", "client auth"
],
"expiry": "87600h"
}
}
}
}
EOF
cat > ca-csr.json <<EOF
{
"CN": "Nomad CA",
"key": { "algo": "ecdsa", "size": 256 },
"names": [
{
"O": "HashiCorp",
"OU": "Nomad"
}
]
}
EOF
cfssl geninitca ca-csr.json | cfssljson -bare ca
# サーバー証明書の生成
cat > server-csr.json <<EOF
{
"CN": "server.us-east.nomad",
"hosts": [
"server.us-east.nomad",
"localhost",
"127.0.0.1",
"*.us-east.nomad"
],
"key": { "algo": "ecdsa", "size": 256 },
"names": [
{
"O": "HashiCorp",
"OU": "Nomad"
}
]
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem \
-config=ca-config.json -profile=nomad \
server-csr.json | cfssljson -bare server
19.2 Gossip 暗号化
# 暗号化キーの生成
nomad operator gossip keyring generate
# 設定ファイルに追加
server {
encrypt = "cg8StVXbQJ0gPvMd9o7yrg=="
}
19.3 セキュリティチェックリスト
- TLS の有効化: サーバー間、クライアント-サーバー間の全通信を暗号化
- ACL の有効化: 最小権限の原則に基づくアクセス制御
- Gossip 暗号化: Serf 通信の暗号化
- Vault 連携: シークレットの外部管理
- 名前空間の分離: ワークロードの論理的な分離
- Docker の制限:
privilegedモードの制限、raw_execドライバーの無効化 - ネットワークポリシー: Consul Connect によるサービス間のアクセス制御
- 監査ログ: Enterprise 機能による操作ログの記録
- Sentinel ポリシー: Enterprise 機能によるポリシーの強制
- 定期的な証明書ローテーション: 自動化された証明書更新
20. Enterprise 機能
20.1 主要な Enterprise 機能
| 機能 | 説明 |
|---|---|
| 名前空間 | ワークロードの論理的な分離(OSS でも利用可能、ただし機能制限あり) |
| リソースクォータ | 名前空間ごとのリソース制限 |
| Sentinel ポリシー | Policy as Code |
| マルチリージョンデプロイ | リージョン間のジョブ管理 |
| 監査ログ | 操作の監査証跡 |
| 自動スナップショット | 定期的なクラスタバックアップ |
| SSO / OIDC | シングルサインオン |
| ライセンス管理 | 自動ライセンスリロード |
| レプリケーション | クロスリージョンの ACL レプリケーション |
20.2 Sentinel ポリシー(Enterprise)
# 本番環境では privileged コンテナを禁止
import "tfplan"
main = rule {
all job.task_groups as tg {
all tg.tasks as task {
task.config.privileged is not true
}
}
}
# 最小リソース要件の強制
main = rule {
all job.task_groups as tg {
all tg.tasks as task {
task.resources.cpu >= 100 and
task.resources.memory >= 64
}
}
}
21. 実践的な構成例
21.1 マイクロサービスアプリケーション
# フロントエンド + バックエンド + データベースの構成例
job "ecommerce-platform" {
datacenters = ["us-east-1", "us-east-2"]
namespace = "production"
type = "service"
priority = 70
# アップデート戦略
update {
max_parallel = 2
canary = 1
min_healthy_time = "30s"
healthy_deadline = "5m"
auto_revert = true
auto_promote = true
}
# フロントエンド
group "frontend" {
count = 4
spread {
attribute = "${meta.zone}"
}
network {
mode = "bridge"
port "http" { to = 3000 }
}
service {
name = "ecommerce-frontend"
port = "3000"
connect {
sidecar_service {
proxy {
upstreams {
destination_name = "ecommerce-api"
local_bind_port = 8080
}
}
}
}
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}
}
task "frontend" {
driver = "docker"
config {
image = "registry.example.com/ecommerce-frontend:v3.2.1"
}
env {
API_URL = "http://localhost:8080"
NODE_ENV = "production"
}
resources {
cpu = 500
memory = 256
}
}
}
# バックエンド API
group "api" {
count = 6
spread {
attribute = "${meta.zone}"
}
network {
mode = "bridge"
port "http" { to = 8080 }
port "grpc" { to = 9090 }
}
service {
name = "ecommerce-api"
port = "8080"
connect {
sidecar_service {
proxy {
upstreams {
destination_name = "postgres"
local_bind_port = 5432
}
upstreams {
destination_name = "redis"
local_bind_port = 6379
}
upstreams {
destination_name = "elasticsearch"
local_bind_port = 9200
}
}
}
}
check {
type = "http"
path = "/api/health"
interval = "10s"
timeout = "3s"
}
}
task "api" {
driver = "docker"
config {
image = "registry.example.com/ecommerce-api:v3.2.1"
}
vault {
policies = ["ecommerce-api"]
}
template {
data = <<-EOF
{{- with secret "secret/data/ecommerce/api" }}
JWT_SECRET={{ .Data.data.jwt_secret }}
STRIPE_KEY={{ .Data.data.stripe_key }}
{{- end }}
{{- with secret "database/creds/ecommerce-api" }}
DB_URL=postgresql://{{ .Data.username }}:{{ .Data.password }}@localhost:5432/ecommerce
{{- end }}
REDIS_URL=redis://localhost:6379
ELASTICSEARCH_URL=http://localhost:9200
EOF
destination = "secrets/env.txt"
env = true
}
resources {
cpu = 1000
memory = 512
}
# オートスケーリング
scaling "cpu" {
enabled = true
min = 4
max = 20
policy {
check "cpu" {
source = "prometheus"
query = "avg(nomad_client_allocs_cpu_total_percent{task='api'})"
strategy "target-value" {
target = 70
}
}
}
}
}
}
}
21.2 バッチ処理パイプライン
job "data-pipeline" {
datacenters = ["us-east-1"]
type = "batch"
# パラメータ化されたジョブ
parameterized {
payload = "optional"
meta_required = ["source_table", "target_table"]
meta_optional = ["batch_size", "parallelism"]
}
group "etl" {
count = 1
# リスタートポリシー
restart {
attempts = 3
interval = "30m"
delay = "15s"
mode = "fail"
}
ephemeral_disk {
size = 10000
}
task "extract" {
driver = "docker"
lifecycle {
hook = "prestart"
sidecar = false
}
config {
image = "registry.example.com/etl-extract:v1.0"
command = "/app/extract"
args = [
"--source", "${NOMAD_META_source_table}",
"--output", "${NOMAD_ALLOC_DIR}/data/extracted.parquet",
]
}
resources {
cpu = 2000
memory = 4096
}
}
task "transform-load" {
driver = "docker"
config {
image = "registry.example.com/etl-transform:v1.0"
command = "/app/transform-load"
args = [
"--input", "${NOMAD_ALLOC_DIR}/data/extracted.parquet",
"--target", "${NOMAD_META_target_table}",
"--batch-size", "${NOMAD_META_batch_size}",
]
}
resources {
cpu = 4000
memory = 8192
}
vault {
policies = ["etl-pipeline"]
}
template {
data = <<-EOF
{{- with secret "database/creds/etl-role" }}
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
{{- end }}
EOF
destination = "secrets/db.env"
env = true
}
}
}
}
# パラメータ化ジョブのディスパッチ
nomad job dispatch \
-meta source_table="raw_events" \
-meta target_table="processed_events" \
-meta batch_size="10000" \
data-pipeline
22. まとめ
22.1 Nomad の強み
- シンプルさ: 単一バイナリで動作し、学習コストが低い
- 柔軟性: コンテナだけでなく、あらゆるワークロードに対応
- スケーラビリティ: 数万ノード規模のクラスタをサポート
- HashiCorp エコシステム: Consul、Vault、Terraform との緊密な連携
- マルチリージョン: ネイティブなマルチリージョンフェデレーション
- 運用の容易さ: アップグレード、バックアップ、トラブルシューティングが容易
22.2 Nomad の適用が適しているケース
- HashiCorp スタックを既に利用している組織
- コンテナとレガシーアプリケーションの混在環境
- 小〜中規模の運用チーム
- マルチリージョン・マルチクラウドの要件がある場合
- Kubernetes の複雑さを避けたい場合
- 段階的にオーケストレーションを導入したい場合
22.3 今後の展望
Nomad は継続的に進化を続けており、以下のような方向性が期待される。
- ネイティブサービスメッシュの強化: Consul Connect のさらなる統合深化
- エッジコンピューティング: 軽量なアーキテクチャを活かしたエッジデプロイ
- AI/ML ワークロード: GPU スケジューリングの強化
- Kubernetes との相互運用: ハイブリッドオーケストレーション
- セキュリティの強化: ゼロトラストアーキテクチャへの対応
付録A: 主要 CLI コマンドリファレンス
| コマンド | 説明 |
|---|---|
nomad agent | エージェントの起動 |
nomad job run <file> | ジョブの登録/更新 |
nomad job plan <file> | ジョブの変更プレビュー |
nomad job stop <name> | ジョブの停止 |
nomad job status <name> | ジョブのステータス確認 |
nomad job deployments <name> | デプロイメント一覧 |
nomad job history <name> | ジョブの変更履歴 |
nomad job dispatch <name> | パラメータ化ジョブのディスパッチ |
nomad job scale <name> <group> <count> | スケーリング |
nomad alloc status <id> | アロケーションのステータス |
nomad alloc logs <id> | アロケーションのログ |
nomad alloc exec <id> <cmd> | アロケーション内でコマンド実行 |
nomad alloc fs <id> <path> | ファイルシステムの確認 |
nomad node status | ノード一覧 |
nomad node drain -enable <id> | ノードのドレイン |
nomad server members | サーバーメンバー一覧 |
nomad operator raft list-peers | Raft ピア一覧 |
nomad operator snapshot save <file> | スナップショット取得 |
nomad system gc | ガベージコレクション |
nomad deployment promote <id> | カナリアプロモーション |
nomad deployment fail <id> | デプロイメント失敗(ロールバック) |
nomad var put <path> <k=v> | 変数の設定 |
nomad var get <path> | 変数の取得 |
nomad acl bootstrap | ACL ブートストラップ |
nomad acl token create | トークン作成 |
nomad acl policy apply <name> <file> | ポリシー適用 |
付録B: 環境変数リファレンス
| 変数 | 説明 |
|---|---|
NOMAD_ALLOC_ID | アロケーション ID |
NOMAD_ALLOC_NAME | アロケーション名 |
NOMAD_ALLOC_INDEX | アロケーションインデックス |
NOMAD_ALLOC_DIR | アロケーションディレクトリ |
NOMAD_TASK_DIR | タスクディレクトリ |
NOMAD_SECRETS_DIR | シークレットディレクトリ |
NOMAD_JOB_NAME | ジョブ名 |
NOMAD_JOB_ID | ジョブ ID |
NOMAD_GROUP_NAME | グループ名 |
NOMAD_TASK_NAME | タスク名 |
NOMAD_NAMESPACE | 名前空間 |
NOMAD_REGION | リージョン |
NOMAD_DC | データセンター |
NOMAD_IP_<label> | ポートラベルの IP |
NOMAD_PORT_<label> | ポートラベルのポート番号 |
NOMAD_ADDR_<label> | ポートラベルの IP:ポート |
NOMAD_HOST_PORT_<label> | ホスト側のポート番号 |
NOMAD_CPU_LIMIT | CPU 制限(MHz) |
NOMAD_MEMORY_LIMIT | メモリ制限(MB) |
NOMAD_UPSTREAM_ADDR_<name> | Connect Upstream のアドレス |
NOMAD_META_<key> | メタデータの値 |