Nomad

HashiCorp Nomad 完全ガイド — ワークロードオーケストレーションの全容

1. はじめに

1.1 Nomad とは何か

HashiCorp Nomad は、コンテナ化されたアプリケーションだけでなく、レガシーなバイナリ、Java アプリケーション、仮想マシンなど、あらゆる種類のワークロードをデプロイ・管理するための汎用ワークロードオーケストレーターである。Kubernetes がコンテナオーケストレーションに特化しているのに対し、Nomad は「あらゆるワークロードをあらゆるインフラストラクチャ上で実行する」という設計哲学を持つ。

Nomad は単一バイナリで動作し、軽量でありながらも大規模環境(数万ノード規模)に対応できるスケーラビリティを備えている。HashiCorp のエコシステム(Consul、Vault、Terraform)との緊密な統合により、サービスディスカバリ、シークレット管理、インフラストラクチャプロビジョニングをシームレスに実現できる。

1.2 Nomad が解決する課題

現代のインフラストラクチャ運用において、以下のような課題が存在する。

  1. 多様なワークロードの統一管理: コンテナだけでなく、レガシーアプリケーション、バッチジョブ、システムデーモンなどを単一のプラットフォームで管理する必要性
  2. マルチリージョン・マルチクラウド対応: 複数のデータセンターやクラウドプロバイダーにまたがるデプロイメント
  3. 運用の複雑さの軽減: Kubernetes のような重厚なオーケストレーターの学習コストと運用負荷
  4. 段階的な導入: 既存インフラへの影響を最小限に抑えた段階的なオーケストレーション導入

1.3 Nomad の主要な特徴

特徴説明
単一バイナリ外部依存なし、軽量デプロイ
マルチワークロードコンテナ、VM、バイナリ、Java など
マルチリージョンネイティブなマルチリージョンフェデレーション
宣言的ジョブ仕様HCL によるジョブ定義
Bin Packing効率的なリソース配置
ローリングアップデートゼロダウンタイムデプロイ
サービスディスカバリConsul との統合
シークレット管理Vault との統合
ACLきめ細かなアクセス制御
Web UI組み込みダッシュボード

1.4 Nomad と Kubernetes の比較

Nomad と Kubernetes はしばしば比較されるが、それぞれ異なる設計思想に基づいている。

観点NomadKubernetes
アーキテクチャ単一バイナリ、軽量多数のコンポーネント、複雑
対応ワークロードコンテナ、VM、バイナリ等主にコンテナ
学習コスト比較的低い高い
スケーラビリティ数万ノード(単一クラスタ)数千ノード(単一クラスタ)
エコシステムHashiCorp スタック中心CNCF エコシステム、巨大
サービスメッシュConsul ConnectIstio, Linkerd 等
パッケージ管理Nomad PackHelm
セットアップ難易度低い高い
コミュニティ規模中程度非常に大きい

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 は以下のポートを使用する。

ポートプロトコル用途
4646HTTPAPI、UI
4647RPCサーバー間、クライアント-サーバー間通信
4648Serf (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)
  1. 評価(Evaluation): ジョブの状態変更(登録、更新、ノード障害など)をトリガーに生成される
  2. プランニング: スケジューラがノードの選定、リソースの割り当てを計算
  3. アロケーション: 実際にタスクをノードに割り当てて実行
┌──────────┐    ┌────────────┐    ┌──────────┐    ┌────────────┐
│ Job      │───►│ Evaluation │───►│ Plan     │───►│ Allocation │
│ Submit   │    │ Queue      │    │ Queue    │    │ Execute    │
└──────────┘    └────────────┘    └──────────┘    └────────────┘
                      │                                  │
                      ▼                                  ▼
               ┌──────────────┐                  ┌──────────────┐
               │ Scheduler    │                  │ Task Driver  │
               │ (bin-pack /  │                  │ (docker,     │
               │  spread)     │                  │  exec, etc.) │
               └──────────────┘                  └──────────────┘

4.2 スケジューラの種類

スケジューラジョブタイプ説明
ServiceserviceBin packing とスプレッドアルゴリズムを使用
Batchbatch未使用リソースを効率的に活用
Systemsystem, 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 は複数のタスクドライバーを組み込みで提供する。

ドライバー説明ユースケース
dockerDocker コンテナコンテナ化されたアプリケーション
execネイティブバイナリLinux バイナリの直接実行
javaJava アプリケーションJAR ファイルの実行
raw_exec権限なしバイナリ実行分離不要なスクリプト
qemuQEMU 仮想マシン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 外部(コミュニティ)タスクドライバー

プラグインシステムにより、以下のような外部ドライバーも利用可能。

ドライバー説明
podmanPodman コンテナ
containerdcontainerd ランタイム
firecrackerFirecracker microVM
nspawnsystemd-nspawn コンテナ
windows_iisWindows IIS
lxcLXC コンテナ
potFreeBSD jail (pot)
rookoutRookout デバッグ
SingularityHPC コンテナ
ECSAWS 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 VolumeContainer 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_killedOOM キルされたアロケーション> 0
nomad.raft.commitTimeRaft コミット時間p99 > 500ms
nomad.raft.leader.lastContactリーダーとの最後のコンタクト> 500ms
nomad.runtime.num_goroutinesGoroutine 数> 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 ダッシュボード

主要なパネル構成例:

  1. クラスタ概要: サーバー/クライアントの数、リーダーの情報
  2. リソース使用率: CPU/メモリの割り当て率と使用率
  3. ジョブステータス: Running/Pending/Dead の内訳
  4. スケジューリング: 評価キュー、ブロック数
  5. Raft: コミット時間、リーダーコンタクト
  6. ネットワーク: 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 セキュリティチェックリスト

  1. TLS の有効化: サーバー間、クライアント-サーバー間の全通信を暗号化
  2. ACL の有効化: 最小権限の原則に基づくアクセス制御
  3. Gossip 暗号化: Serf 通信の暗号化
  4. Vault 連携: シークレットの外部管理
  5. 名前空間の分離: ワークロードの論理的な分離
  6. Docker の制限: privileged モードの制限、raw_exec ドライバーの無効化
  7. ネットワークポリシー: Consul Connect によるサービス間のアクセス制御
  8. 監査ログ: Enterprise 機能による操作ログの記録
  9. Sentinel ポリシー: Enterprise 機能によるポリシーの強制
  10. 定期的な証明書ローテーション: 自動化された証明書更新

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 の強み

  1. シンプルさ: 単一バイナリで動作し、学習コストが低い
  2. 柔軟性: コンテナだけでなく、あらゆるワークロードに対応
  3. スケーラビリティ: 数万ノード規模のクラスタをサポート
  4. HashiCorp エコシステム: Consul、Vault、Terraform との緊密な連携
  5. マルチリージョン: ネイティブなマルチリージョンフェデレーション
  6. 運用の容易さ: アップグレード、バックアップ、トラブルシューティングが容易

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-peersRaft ピア一覧
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 bootstrapACL ブートストラップ
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_LIMITCPU 制限(MHz)
NOMAD_MEMORY_LIMITメモリ制限(MB)
NOMAD_UPSTREAM_ADDR_<name>Connect Upstream のアドレス
NOMAD_META_<key>メタデータの値

付録C: 参考リソース