最近 Nomad をチマチマ触っています。

Nomad のジョブとしてバッチジョブを実行できるのですが、ジョブ内に複数タスクがある場合、タスクは並列で実行されます。
このためタスク間に依存関係がある場合、下流のタスクが失敗してしまうことに気付きました…。また、現在 Nomad にタスク間の依存関係を表現する機能はありません。

既存の議論

タスク間の依存関係を表現できるようにしたいという機能要望は数年前からあり、以下の Issue で議論されています。

今後の予定としては依存関係の機能を追加予定とのことですがまだ目立った動きはなさそう。
Issue でワークアラウンドとして以下が紹介されていますが、よくわからない…。AST is 何?抽象構文木?Consul でタスクのヘルスチェックを行い依存関係を表現しているようですがちょっとよくわからなかった…。

We needed this enough that we implemented it ourselves. We have an AST for nomad jobs and interpret it to figure out which consul health checks to watch, wait for their success/fail timeout, and add the unblocked jobs to the work queue.

Provide for dependencies between tasks in a group · Issue #419 · hashicorp/nomad · GitHub

Issue で他には Apache Airflow を使用して依存関係を持たせる方法も紹介されていました。
また、依存関係を記述できるようにプラグインを作成されている方もいました。

他に参考になったのがファイルを使用して依存関係を表現できるんじゃね?という議論でした。

Nomad はグループ (タスクの集まり) 内で共有可能なディレクトリを提供しています。ディレクトリは環境変数 NOMAD_ALLOC_DIR で参照するか、もしくは直接 /alloc パスで直接参照できます。

したがって /alloc 以下にファイルを作ってそのファイルの有無で後続のタスクを待機させれば良さそうじゃね?という話です (多分)。

どうやるか

nomad-dtree を使わせてもらうのが一番簡単そうですが、なるべく Nomad の機能だけでできないかなーと思い共有ディレクトリを使用する方法を試してみます。

が、当の Google グループでは具体的な方法までは公開されておらず…。

実現方法

だいぶ強引な感じですが while コマンドで特定のファイルが作成されるまで sleep し、作成されたら処理に移る形にしてみました。
以下のジョブで挙動を確認できます。

  • task1 では 10 秒 sleep した後 (たまたま task 2 の実行より早かった場合を防ぐため) にファイルを作成。
  • task2 ではファイルの作成を待ち、作成を確認できたら cat コマンドで中身を表示する。
job "testjob" {
  datacenters = ["test"]

  type = "batch"

  group "testgroup" {
    task "test1" {
      driver = "exec"
      config {
        command = "/bin/bash"
        args = [
          "-c",
          "sleep 10 && echo complete > ${NOMAD_ALLOC_DIR}/data/test1_task_file"
        ]
      }
      resources {
        memory = 50
      }
    }

    task "test2" {
      driver = "exec"
      config {
        command = "/bin/bash"
        args = [
          "-c",
          "while [ ! -f ${NOMAD_ALLOC_DIR}/data/test1_task_file ]; do ls -l ${NOMAD_ALLOC_DIR}/data/ && sleep 1; done && cat ${NOMAD_ALLOC_DIR}/data/test1_task_file"
        ]
      }
      resources {
        memory = 50
      }
    }
  }
}

注意点として command に /bin/bash を使用する必要があります。
exec ドライバーはコマンドを実行するドライバーですが、 && 等のシェルの演算子は使えません。したがって bash コマンドの引数として別のコマンドを渡し、bash シェルの演算子も使えるようにする必要があります。
この辺の話は exec ドライバーのドキュメントには書いてなくてハマりました。service 節のドキュメントに地味に書いてあります。

Caveat: The command must be the path to the command on disk, and no shell exists by default. That means operators like || or && are not available. Additionally, all arguments must be supplied via the args parameter. To achieve the behavior of shell operators, specify the command as a shell, like /bin/bash and then use args to run the check.

service Stanza - Job Specification | Nomad by HashiCorp

ちなみに exec ドライバーと似た名前で row_exec があります。違いは以下。

  • exec ドライバー: 分離された環境で実行される。タスクがタスク外のリソースへアクセスすることは制限されている。リソースの分離はクライアントの OS が Linux の場合 cgroup と chroot で実現されている。
  • row_exec ドライバー: Nomad プロセスと同じユーザーとして実行され、リソースへのアクセスが制限されていない。

タスクの独立性を保つためにも基本的には exec ドライバーを使うのが良いと思います。
詳細は以下。

実行結果

ジョブを実行。

$ nomad job run /vagrant/nomad-job/test.nomad

無事ファイル作成を待機してから test2 が実行されていることを確認できました! 🎉🎉

$ nomad alloc logs 5d4e6db0 test2
合計 0
合計 0
合計 0
合計 0
合計 0
合計 0
合計 0
合計 0
合計 0
合計 0
合計 0
complete

感想

正直もっときれいなやり方がある気がしてならない… (長大なワンライナーになってしまい辛い)。が、とりあえずこれでいいか…。
Nomad 使ってる人こういうタスク間の依存関係どう実現してるんだろう…。