Integrate Nomad and Vault

現在 NomadConsul を使用してこのブログを動かしています。

例えば DB のパスワード等の機密情報を Nomad のジョブで扱いたい場合があります。Nomad は環境変数から機密情報を取得することもできますが、Vault と連携することで Vault の暗号化された KVS から機密情報を取得することが可能になります。

今回、Nomad と Vault の統合を設定してみたのでメモします。

Nomad の設定

Nomad の設定としては以下のように vault スタンザを追加するだけです。
※今回、簡単のため Vault 側で TLS の設定を無効にしていますが TLS を有効にしている場合は以下に証明書等の設定が追加されます。

vault {
  enabled = true
  address = "http://127.0.0.1:8200"
  create_from_role = "nomad-cluster"
  token = "<Vault トークン>"
}

なお、上記は Nomad サーバー側の設定で Nomad クライアント側の設定の場合は以下の通り token 関係の設定が無くても大丈夫。

vault {
  enabled = true
  address = "http://127.0.0.1:8200"
}

設定の概要は以下。

  • token: Vault から発行したトークン。このトークンを使用して Vault に接続し、タスクに割り当てるトークンの取得等を行います。トークンは Vault 初期化時に発行される root トークンか、 vault token create で発行した任意のトークンを指定できます。なるべく root トークンは使わない方が良いと思います (root トークンは初期設定時、緊急時にのみ使用することが推奨されています)。なおトークンは Nomad 起動時に環境変数や引数で渡すこともできますが、プロビジョニングツールを使った設定の自動化と相性が良くなく、良い方法が思いつかなかったので直書きする設定にしています。
  • create_from_role: タスク用に取得するトークンを生成するトークンロールの指定。Nomad タスクから Vault へ接続する際にここで指定したトークンロールからトークンが生成されます。Nomad 設定ファイルに設定するトークンはこのトークンロールから作成してある必要はありません。タスク用に発行するトークンのためのトークンロールなので。

Vault の準備

今回は root トークンでなく非 root トークンを使用するので、Vault 側で Nomad 用にトークンを発行してあげます。Vault のトークンにはポリシーを付与することが可能で、Vault の path へのアクセスと操作を制限することができます。Nomad に設定で与えるトークンには自身のトークンを更新する権限等、トークンを管理する権限が必要なので、ポリシーでこれを定義します。

必要な権限を含むポリシーは Hashicorp がダウンロードできる形で提供しているので、今回はこれをそのまま使います。vault コマンドでポリシーを適用します。ポリシーはファイルまたは標準入力で与えます。 vault policy write <ポリシー名> <ポリシーファイルのパス> のようにして実行します。

$ curl https://nomadproject.io/data/vault/nomad-server-policy.hcl -O -s -L

$ vault policy write nomad-server nomad-server-policy.hcl

次に、トークンロールを作成します。トークンロールはトークンの作成時に特定の動作を強制するものです。今回の場合、Nomad がタスク用にトークンを作成するのですが、このタスク用のトークンに付与する権限をコントロールするためにトークンロールを作成します。トークンロールにポリシー等の情報を書くことができ、トークンロールを基に作成されたトークンはトークンロールで指定された権限を持ちます。

トークンロールも Hashicorp の提供しているサンプルがあります。内容は以下。

{
  "disallowed_policies": "nomad-server",
  "token_explicit_max_ttl": 0,
  "name": "nomad-cluster",
  "orphan": true,
  "token_period": 259200,
  "renewable": true
}
  • disallowed_policies: ポリシーのブラックリスト。今回、Nomad サーバー用のトークンに付与するポリシーとして nomad-server を作成しましたが、タスクが Nomad サーバーより与えられた自身のトークンからトークンの有効期限等を変更できては困るので、nomad-server ポリシーをブラックリストに入れています。
  • token_explicit_max_ttl: トークンの最大 TTL を指定します。トークンの有効期限には TTL と 期間 (period) があります。TTL 期間内でトークンの有効期限を period 単位で延ばすことができます。TTL が切れると、トークンを再発行する必要があります。token_explicit_max_ttl を 0 にすることで、TTL を無期限に設定できます。TTL を無期限にすることで、トークンが更新され続けている限り有効期限を無期限にできます。例えば長期間実行されるサービスの場合は TTL を無期限にしたトークンが欲しくなるので、そういう場合に TTL を無期限に設定します。
  • name: ロールの名前。
  • orphan: トークンは親子関係があり、あるトークンを使って発行されたトークンは子のトークンとなります。orphan が false の場合、親のトークンが取り消されると子のトークンも取り消されます。true の場合、親のトークンが取り消されても子のトークンは有効のままになります。
  • token_period: トークンの更新時に延長される期間を設定します。Vault への更新リクエスト負荷が大きくならない程度に短い期間を設定することが推奨されます。259200 秒は 3 日。
  • renewable: 作成されたトークンが更新可能かどうかを設定します。

トークンロールを以下のように設定します。

$ curl https://nomadproject.io/data/vault/nomad-cluster-role.json -O -s -L

$ vault write /auth/token/roles/nomad-cluster @nomad-cluster-role.json

これで Nomad サーバー向けのトークンを作成する準備と、Nomad がタスク向けに作成するトークンの準備ができました。

Nomad の設定に進む前に Nomad サーバー用のトークンを発行しておきます。

$ vault token create -policy nomad-server -period 72h -orphan

改めて Nomad の設定

Nomad サーバー用のトークンを発行できたので、これで設定が完了できます。

vault {
  enabled = true
  address = "http://127.0.0.1:8200"
  create_from_role = "nomad-cluster"
  token = "<発行した Vault トークン>"
}

Bolt でどう自動化するか

大まかに以下のステップが必要です。

  1. Nomad, Vault のインストール。
  2. Vault の設定。
  3. Vault の初期化、unseal, ポリシーとロールの設定。
  4. Vault で Nomad サーバー用のトークンを作成。
  5. Nomad の設定 (発行した Vault のトークンを設定)。

Bolt を使ってプロビジョニングを行っているのですが、上記 3, 4 を自動化するのがだいぶ面倒そうで、手動で行っています。すると、5 のステップも半分手動になります。私の Bolt 構成として、基本的に以下のコマンドで全ての設定が完了するようにしています。

  • bolt plan run provision targets=production do_token=$DIGITALOCEAN_TOKEN

が、ここから Nomad の plan を切り出して Vault 初期化、トークンの発行後別途以下のコマンドで Nomad の設定を行う形にしました。

  • bolt plan run nomad targets=production vault_token=$VAULT_TOKEN

つまりプロビジョニングの全体の流れは以下になります。

  1. bolt plan run provision targets=production do_token=$DIGITALOCEAN_TOKEN
  2. Vault の初期化、unseal, ポリシーとロールの設定。
  3. Vault で Nomad サーバー用のトークンを作成。
  4. bolt plan run nomad targets=production vault_token=$VAULT_TOKEN

妥協感ありますが煩雑すぎるわけでもないのでひとまずヨシ!

Nomad の plan はこんな感じ。

plan nomad (
  TargetSpec $targets,
  String     $vault_token
) {
  $nomad_version       = '0.11.0-beta1'
  $nomad_download_link = "https://releases.hashicorp.com/nomad/${nomad_version}/nomad_${nomad_version}_linux_amd64.zip"

  # Install nomad.
  run_command(
    "curl -s $nomad_download_link -o /tmp/nomad.zip && \
    unzip -o /tmp/nomad.zip -d /usr/local/bin && \
    mkdir -p /etc/nomad.d && \
    mkdir -p /opt/nomad",
    $targets
  )

  # Setup nomad.
  upload_file(
    'nomad/nomad.service',
    '/etc/systemd/system/nomad.service',
    $targets
  )
  $targets.apply_prep
  apply($targets) {
    file { '/etc/nomad.d/nomad.hcl':
      ensure  => file,
      content => epp('nomad/nomad.hcl.epp', {
        'targets'     => $targets,
        'vault_token' => $vault_token
      })
    }
  }
  run_command(
    'systemctl daemon-reload',
    $targets
  )

  # Enable and start nomad.
  run_command(
    'systemctl enable nomad',
    $targets
  )
  run_task(
    'service',
    $targets,
    {
      'action' => 'start',
      'name' => 'nomad'
    }
  )
  return run_command(
    'pgrep -l nomad',
    $targets
  )
}

ハマったところ

Vault を以下の通り 3 台のサーバーを使用し HA モードで使用しているのですが、全ての Vault で unseal 作業をしなければいけないのにしばらく気付かなかったw

あと Vault を再起動して seal されてしまい、それに気付かず Nomad を再起動して起動に失敗するなど。

Once a Vault is unsealed, it remains unsealed until one of two things happens:

  1. It is resealed via the API (see below).
  2. The server is restarted.

Seal/Unseal | Vault by Hashicorp

感想

これでようやく Nomad, Consul, Vault の Hashicorp マイクロサービス三銃士が揃った感!

無料の写真素材はフリー素材のぱくたそ

参考リンク