Mounting Docker volumes into Nomad tasks allocation directory

Nomad はグループ (タスクの集まり) 内で共有可能なディレクトリを提供しておりタスクからは環境変数 NOMAD_ALLOC_DIR または /alloc ディレクトリを直接参照することで使用可能です。

また Nomad のタスクは Docker が使用できます。

そこで、Docker コンテナの生成物をタスク間で共有できないか試したのですがすんなりできなかったのでメモ。

構成

以下のようなバッチジョブの構成で試します。

  • Docker ドライバーを使用したタスクで成果物を作成。
  • 後続の Exec ドライバーを使用したタスクで成果物を使用

なおこのようなタスク間に依存関係を持たせたい場合、現在 Nomad の機能としては存在しないので以下のように何かしらの工夫をする必要があります。

課題

Docker ドライバーでは以下の通り volumes オプションでホストのパスとコンテナのパスをバインドできます。

config {
  volumes = [
    # Use absolute paths to mount arbitrary paths on the host
    "/path/on/host:/path/in/container",

    # Use relative paths to rebind paths already in the allocation dir
    "relative/to/task:/also/in/container"
  ]
}

Drivers: Docker | Nomad by HashiCorp

ので、以下のように volumes オプションでホスト側のパスを NOMAD_ALLOC_DIR に指定すれば良さそうに思えます。

config {
  volumes = [
    "${NOMAD_ALLOC_DIR}:/path/in/container"
  ]
}

が、 共有したいタスクが Exec ドライバーの場合、上記の設定ではうまくいきません。 これは以下の理由によるものです。

  • Docker ドライバーで NOMAD_ALLOC_DIR をバインドすると、ホスト上の /alloc パスが共有される。
  • 一方、Exec ドライバーで実行するタスクは (クライアント実行環境が Linux の場合) cgroup と chroot によって分離された環境で実行されるため、タスク内から (分離された環境外の) /alloc パスを参照することができない。

解決方法

ということでタスクの分離された環境がどこにあるか突き止め、そのパスをホスト側のパスとしてバインドしてしまえば解決できそうです。

探してみると Nomad タスクの実行環境は以下にありました。

  • /opt/nomad/alloc/

/opt/nomad/alloc/ 以下はさらにジョブのアロケーション ID 毎にディレクトリが作成されており、その先にタスク名毎に割り当てられたディレクトリが存在します。

/opt/nomad/alloc/
...
├── 9ec969e6-4556-b728-abf6-170b541ea377
│   ├── alloc
│   ├── lego
│   └── put-certificate-to-vault
└── bc809289-2b91-3b0e-8ed5-90f0c4e211a7
    ├── alloc
    ├── test1
    └── test2

上のアロケーション ID ディレクトリ直下の alloc ディレクトリが、グループ内で共有可能なディレクトリとなっています。
このディレクトリを volumes に指定するにあたってアロケーション ID をどのように取得するかが問題になりそうですが、以下の通り環境変数 NOMAD_ALLOC_ID で取得できちゃいます。

よって、以下のように volumes を指定することで課題を解決できました。 🎉

config {
  volumes = [
    "/opt/nomad/alloc/${NOMAD_ALLOC_ID}/${NOMAD_ALLOC_DIR}:/path/in/container"
  ]
}

所感

ちょっと強引かのう…。