HashiCorp Nomad:KubernetesよりシンプルなDockerコンテナ管理 — 中小規模システムに最適

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Kubernetesは重すぎ、Swarmは機能不足 — Nomadはどこに位置するのか?

2年前、e-commerceシステムをマイクロサービスへ移行し始めたとき、最初に浮かんだ質問は「コンテナのオーケストレーションに何を使うか?」でした。Kubernetesは名前こそ立派ですが、3台のVPSでのセットアップを2週間試した後、チームはetcdクラスター、コントロールプレーン、RBAC、CRDで完全に疲弊してしまいました。おまけに当時、あるコンテナでメモリリークのバグを踏み、原因特定に丸2日かかりました。十数個のPodのログが入り乱れるK8s上でのデバッグ — 経験したことがなければ、どれほど辛いかなかなか想像できないでしょう。

Docker Swarmはよりシンプルですが、スケジューリング機能が貧弱です。柔軟なヘルスチェックもなく、詳細なリソースクォータもなく、Docker以外のワークロードもサポートしていません。

そこでHashiCorp Nomadを試してみたのですが、1年以上実運用で使ってきた今、中小規模システムを運用している方にぜひ紹介したいツールとなりました。

Nomadとは何か、なぜ小規模チームに向いているのか?

NomadはHashiCorpのワークロードオーケストレーターで、Dockerに限定されません。通常のバイナリ、Javaアプリ、シェルスクリプト、コンテナなど何でも動かせ、それぞれが異なる「タスクドライバー」として扱われます。K8sと比べていくつか明確な利点があります:

  • 単一バイナリ:サーバーもエージェントも約100MBの1つのバイナリファイルで動作。独立したetcdも複数のコンポーネントも不要。
  • 15分でセットアップ:3ノードのNomadクラスターが15分以内に起動できる。K8sは最低でも数時間かかるのと対照的。
  • ConsulとVaultとのネイティブ連携:HashiCorpエコシステムのサービスディスカバリーとシークレット管理 — すでにこのスタックを使っているなら追加インストール不要。
  • シンプルなJob Spec:HCL(HashiCorp Configuration Language)を使用 — K8sのYAMLよりもはるかに読みやすい。

覚えておくべきコアコンセプト

ドキュメントを最初から読む必要はありません。この4つのコンセプトを理解すればすぐにデプロイできます:

  • Job:最大のデプロイ単位。1つのJobに複数のTask Groupを含めることができる。
  • Task Group:同じノード上で一緒に動作するタスクのグループ(K8sのPodに相当)。
  • Task:最小単位 — Dockerコンテナ1つ、スクリプト1つ、またはバイナリ1つ。
  • Allocation:Nomadスケジューラーが特定のノードで1つのTask Groupを実行することを決定したとき、それをAllocationと呼ぶ。

Nomadのインストールとシンプルなクラスターの起動

Ubuntu/DebianへのNomadインストール

# HashiCorpリポジトリを追加
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install nomad

# バージョン確認
nomad version

devモードでNomadを起動(ローカルでの素早いテスト)

# サーバーとエージェントを同じマシンで起動 — テスト専用!
sudo nomad agent -dev

# 別のターミナルを開いてノードを確認
nomad node status

本番サーバー設定(3ノード)

サーバーノード用に /etc/nomad.d/server.hcl ファイルを作成:

datacenter = "dc1"
data_dir   = "/opt/nomad/data"

server {
  enabled          = true
  bootstrap_expect = 3  # クォーラムには3つのサーバーノードが必要
}

client {
  enabled = false
}

ワーカーノード用に /etc/nomad.d/client.hcl ファイルを作成:

datacenter = "dc1"
data_dir   = "/opt/nomad/data"

client {
  enabled = true
  servers = ["192.168.1.10:4647", "192.168.1.11:4647", "192.168.1.12:4647"]
}

plugin "docker" {
  config {
    allow_privileged = false
  }
}

Job Specの作成とDockerコンテナのデプロイ

NomadがDocker Swarmよりも明らかに優れていると感じる部分です。Job Specが明確で読みやすく、バージョン管理もしやすい。

基本的なJob Spec — Nginxのデプロイ

job "nginx-web" {
  datacenters = ["dc1"]
  type        = "service"  # 継続実行、失敗時は自動再起動

  group "web" {
    count = 2  # 2インスタンス

    network {
      port "http" {
        static = 80
      }
    }

    task "nginx" {
      driver = "docker"

      config {
        image = "nginx:alpine"
        ports = ["http"]
      }

      resources {
        cpu    = 200  # MHz
        memory = 128  # MB
      }

      # 組み込みのヘルスチェック
      service {
        name = "nginx-web"
        port = "http"

        check {
          type     = "http"
          path     = "/"
          interval = "10s"
          timeout  = "2s"
        }
      }
    }
  }
}

クラスターにジョブをデプロイ:

# applyする前にplanを確認(terraform planと同様)
nomad job plan nginx.nomad

# デプロイ
nomad job run nginx.nomad

# ステータス確認
nomad job status nginx-web

# 特定のAllocationのログを確認
nomad alloc logs <alloc-id>

実践ヒント:OOMを防ぐためのリソース制約の活用

e-commerceプロジェクトでメモリリークのバグに遭遇したとき、高い代償を払って学んだ教訓があります:必ずメモリ制限を設定すること。K8sではコンテナがkillされてもログが不明瞭でした。Nomadでは、コンテナがメモリ制限を超えると:

  • Allocationログに理由を明確に記録:OOM killed
  • 設定済みのポリシーに従って自動再起動
  • アラート用のメトリクスを更新
resources {
  cpu        = 500
  memory     = 256
  memory_max = 512  # バーストを許可するが、この値を超えない
}

restart {
  attempts = 3
  interval = "5m"
  delay    = "15s"
  mode     = "fail"  # 無限再起動ではなく3回失敗したら停止
}

環境変数とシークレットを持つアプリケーションのデプロイ

task "api-server" {
  driver = "docker"

  config {
    image = "myapp/api:v1.2.0"
  }

  env {
    APP_ENV  = "production"
    LOG_LEVEL = "info"
  }

  # Vaultからシークレットを取得(HashiCorp Vaultを使用する場合)
  template {
    data = <<EOT
{{ with secret "secret/myapp/db" }}
DB_PASSWORD={{ .Data.data.password }}
{{ end }}
EOT
    destination = "secrets/env.txt"
    env         = true
  }

  resources {
    cpu    = 300
    memory = 256
  }
}

実運用から学んだ実践ヒント集

1. バッチタスクには「batch」ジョブタイプを使う

cronジョブや一度だけ実行するスクリプトが必要な場合は、type = "batch" を使います。Nomadは実行後にクリーンアップし、プロセスを保持しません。

job "db-backup" {
  type = "batch"
  # Nomadにはperiodicブロックもある — cronと同様
  periodic {
    crons = ["0 3 * * *"]  # 毎日午前3時に実行
  }
  # ... タスク設定
}

2. NomadにはCanaryデプロイメントが組み込み済み

update {
  canary           = 1       # 最初に1つの新インスタンスをデプロイ
  min_healthy_time = "30s"   # 30秒間正常であれば続行
  healthy_deadline = "5m"
  auto_promote     = false   # 手動プロモートが必要 — より安全
  auto_revert      = true    # 失敗した場合は自動ロールバック
}

3. Nomad APIのJSONレスポンスを検証する

NomadにはかなりのREST APIが充実しています。デバッグや自動化スクリプトを書く際、出力を読みやすくフォーマットするためにToolCraftのJSON Formatterをよく使います。ブラウザ上でローカル実行されるツールなので、内部データが外部に送信される心配もありません。

# JSON形式でジョブ情報を取得
curl http://localhost:4646/v1/job/nginx-web | python3 -m json.tool

# またはnomad CLIを使用
nomad job inspect nginx-web

4. 必要に応じてYAMLとHCL間で変換する

チームがYAML(K8s由来)に慣れている場合、NomadはJSON形式のジョブ仕様も受け付けます。設定を相互変換する必要があるときはToolCraftのYAML ↔ JSON Converterを使っています — 手動でスクリプトを書くよりずっと便利です。

5. Nomad UIでモニタリング

Nomadには http://<server-ip>:4646 でアクセスできるWeb UIが付属しています。K8s Dashboardほど洗練されてはいませんが、ジョブのステータス、Allocation、ログ、リソース使用率をリアルタイムで確認できます。

クイック比較:Nomad vs Docker Swarm vs Kubernetes

比較項目 Nomad Docker Swarm Kubernetes
セットアップの複雑さ 非常に低
リソースオーバーヘッド ~100MB RAM ~50MB RAM >1GB RAM
Docker以外のワークロード ✅ あり ❌ なし ✅ あり
Canaryデプロイメント ✅ 組み込み ❌ 手動 ✅ あり
エコシステム HashiCorp Docker Inc. CNCF(非常に大規模)
適した対象 2〜10人のチーム シンプルなセットアップ向け エンタープライズ

まとめ

Nomadは完璧なツールではありません。大規模なエコシステム、大きなコミュニティ、または本当に大規模なSaaSを構築する必要がある場合は、K8sの方が適しています。しかし、小規模なチームで3〜20台のサーバーを運用していて、コントロールプレーンの維持に丸一週間費やしたくない、それでも十分なオーケストレーションが必要という場合 — Nomadは真剣に試す価値があります。

個人的にNomadへ移行して1年:デプロイが速くなり、デバッグが楽になり、そして最も重要なことは、チーム全員がシステムで何が動いているかを理解できるようになったこと — もう誰もkubectlを開くのを怖がらなくなりました。

Share: