~第一回~ NomadクラスタをGCEに構築する

こんにちは。grasys清水です。

最近Kubernetes(:以下k8s)が何かと話題ですよね。その熱の煽りを受けてか、業務でGKEを触る機会がだんだんと増えています。
ただ、今回はk8sに関する記事ではありません。
今回は選択肢を増やす、という意味で似たようなプロダクトHashiCorp社が提供する Nomad をご紹介します。

k8sを使うと何かと考えなくちゃいけないことが増えたり、運用が複雑に感じることもしばしばあって、『あれ、結果トータルとしてどれほどの恩恵を受けられているのか』と疑問に思った方もいるのではないでしょうか?

k8sのボリュームがいろんな意味で大きく、それを負担に感じてる方にはNomadは良いソリューションの一つかもしれません。

まだまだ人気的なものでいえば下火なのか、あまり日本語の記事が見つかりません。そこで、今回の記事を 第一回 として、何回かに分けてNomadの機能・使い方について色々ご紹介したいと思います。k8sとは別の選択肢が欲しい、と言う方々の参考になればと思います。

Nomad とはなんぞや

一言で言うと オーケストレーター です。

通常の(またはバッチ)アプリケーションに加え、コンテナ化されたアプリのデプロイをマネージングできます。

ただ、k8sのようなネットワークや権限の制御機能など高尚な機能はありません。薄〜くk8sを使うイメージです。

その分、複雑に抽象化された概念が少なく、オペレーションもシンプル。

使いどころを模索するアプローチとしては

  • わざわざk8sを使うほどでもないけど、docker imageを使いたい
  • ちょっとした条件をつけてバッチを走らせたい(空いたリソースで一つだけバッチを走らせたいなど)
  • クロスプラットフォームを実現したい

といった感じでしょうか。

また、同社のプロダクトでもある ConsulVault と親和性が高く、うまく機能を組み合わせることで実現できる事の幅がぐんと広がるのも魅力の一つです。

第一回である今回の記事では、Nomadの基本的な構築方法、使い方、概念等をご紹介します。

極力、細かな解説は後回し(後々の記事に)します。

インストール

環境

インスタンスは合計3台使います。今回は検証用なので下記のスペック↓です。

name: nomad-cluster01/02/03

OS: CentOS 7.6

machine-type: n1-standard-1(vCPU *1, 3.75GB mem)

インストール手順

今回はversion 0.9.1を入れます。

下準備はサイトの デプロイメントガイド に従って入れていきます。

サイトのダウンロードページから持ってきて解凍します。

mkdir -p /usr/lcoal/src
cd /usr/local/src
curl -O https://releases.hashicorp.com/nomad/0.9.1/nomad_0.9.1_linux_amd64.zip
unzip nomad_0.9.1_linux_amd64.zip
mv nomad /usr/local/bin/

できたらタブ補完を有効にします。

nomad -autocomplete-install
complete -C /usr/local/bin/nomad nomad

確認

$ nomad -v
Nomad v0.9.1-dev (8d48b77bca93a4908c83cd15519b74718d797157)

下準備〜起動まで

ひとまず、nomad agentを一通り動かせるところまでやってみましょう。

作業するのは一台のインスタンスだけです。

nomad agentの挙動にはserver モードと client モードの2種類があります。まずは動作確認のため、1台のサーバ上にそれぞれserverとclientを設置します。各モードついては後ほど解説します。

nomadのデータディレクトリを作成します。

$ sudo mkdir -p /opt/nomad

次は Unit を作成します。

$ cat << EOF > /usr/local/etc/systemd/nomad.service
[Unit]
Description=Nomad
Documentation=https://nomadproject.io/docs/
Wants=network-online.target
After=network-online.target

[Service]
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/nomad agent -config /etc/nomad.d
KillMode=process
KillSignal=SIGINT
LimitNOFILE=infinity
LimitNPROC=infinity
Restart=on-failure
RestartSec=2
StartLimitBurst=3
StartLimitIntervalSec=10
TasksMax=infinity

[Install]
WantedBy=multi-user.target
EOF

$ systemctl daemon-reload

そこからどんどん必要なconfigurationファイルを置いていきます。

↑上記のUnitの設定のままであれば /etc/nomad.d の下にファイルを置けば、すべて読み込んでくれます。

設定ファイルの作成

confを置くディレクトリを作成

$ sudo mkdir --parents /etc/nomad.d
$ sudo chmod 700 /etc/nomad.d

まずは、serverとclientに共通するnomadの基本になるconfを作成します。dataディレクトリを変えた場合は、パスを変更しておきましょう。

また、dataディレクトリには シムリンク を使わないようにしてください。

※ ちなみに、 log_json = true でjson形式のログを吐くようにしてます。

$ cat << EOF > /etc/nomad.d/nomad.hcl
datacenter = "dc1"
data_dir = "/opt/nomad"
log_json = true
EOF

シムリンクだとデプロイの際、エラーになってしまいす。

2019-05-12T01:49:10Z Driver Failure failed to launch command with executor: rpc error: code = Unknown desc = failed to create container(2e7fee0b_7008_e4c1_d0b4_a5d59c9c8741): /usr/local/var/nomad/alloc/9ae5113a-02e7-d8d1-e8de-ac84eae78b77/server is not an absolute path or is a symlink

次は、 server の挙動を設定するファイルを作成します。

server.hcl を同じディレクトリの下に置きます。

cat << EOF > /etc/nomad.d/server.hcl
server {
  enabled = true
  bootstrap_expect = 1
}
EOF

最後に、 client の設定ファイルを作成します。

client.hcl を同様に。

cat << EOF > /etc/nomad.d/client.hcl
client {
  enabled = true
}
EOF

最初の設定はシンプルですね。

起動

では起動してみましょう。

sudo systemctl enable nomad
sudo systemctl start nomad
sudo systemctl status nomad

どうでしょう?うまく起動したでしょうか?

ServerとClient

ここで一旦、ServerとClientの解説をします。

Server

Nomadに置ける Server とは、クラスタ全体のマネージングを担っている機能です。クラスタ全体の状態を記憶し、スケジューリングの意思決定を行います。

Serverは投票によってNomad leader serverを決める必要があるため、クラスタ全体で3台ないし5台 使用することが推奨されています。また、スペックは 4〜8core + 16〜32GB of memory + 40-80 GB fast diskが推奨されています。(今回は検証なので1coreで作っていますが)

先ほど、ファイルを作成する際は bootstrap_expect=1 に設定していましたが、ひとまず、単体でserverモードを動かすために変更しているものです。クラスタリングする際は、serverの台数に合わせてパラメータを変更してください。

# 3台のserverを動かす時
server {
  enabled = true
  bootstrap_expect = 3
}

Client

ServerからJobのリクエストを受け処理を行う実行部隊です。常に「俺は生きてるよ〜」とServerに対して自身の状態を報告しています。

CPUやmemoryなどの色々な閾値を設定することができ、その値を元にnodeの実行が完了した不要なjobなどを削除(garbage collection)してくれたりします。

Serverに比べると通信量が少ないのでagentとしては軽量です。

クラスタリング

Server クラスタリング

起動までうまくいけば、次は マニュアルでクラスタリング の設定をしてみましょう。

先ほど使用したインスタンスとは別に二台のインスタンスにnomadをインストールしておきます。

で、全台の server.hcl に下記のパラメータを追加してください。

  bootstrap_expect = 3 # 1→3に書き換える
  server_join {
    retry_join = ["<nomad-cluster01のIP>:4648"]
  }

そしたら nomad-cluster01 から順に起動していきます!

全台起動が完了したら、ステータスを確認してみましょう。この場合は nomad server members で確認するのが便利です。

$ nomad server members
Name                    Address      Port  Status  Leader  Protocol  Build  Datacenter  Region
nomad-cluster01.global  10.140.0.31  4648  alive   true    2         0.9.1  dc1         global
nomad-cluster02.global  10.140.0.29  4648  alive   false   2         0.9.1  dc1         global
nomad-cluster03.global  10.140.0.30  4648  alive   false   2         0.9.1  dc1         global

全台で試してみてください。

互いに認識できているのがわかると思います。

ちょっとこれだけだと味気ないので、leaderである nomad-cluster01 のnomad agentプロセスを落としてみます。

$ systemctl stop nomad

ではもう一度確認。

$ nomad server members
Name                    Address      Port  Status  Leader  Protocol  Build  Datacenter  Region
nomad-cluster01.global  10.140.0.31  4648  failed  false   2         0.9.1  dc1         global
nomad-cluster02.global  10.140.0.29  4648  alive   false   2         0.9.1  dc1         global
nomad-cluster03.global  10.140.0.30  4648  alive   true    2         0.9.1  dc1         global

おお、ちゃんとleaderが変わってますね。

プロセスをもう一度起動みたり、余裕のある方はパラメータをいじって試してみてください。

色々試したところ、 初回起動 さえうまくいけば、プロセスが立ち上がるたびに自動でクラスタへrejoinしてくれるようです。(初回起動の時点で retry_join = [IP] のnomad agentが起動していないとクラスタリングされません)

ちなみに、下記のコマンドでもクラスタリング可能です。

$ nomad server join <known-address>

Client の認識

Serverのクラスタリングが終われば、 clientを認識させなければいけません。

要領は同じで、 client.hcl に記載するかクライアントがあるサーバで nomad node config -update-servers コマンドを使うだけ。

:configファイルの場合

client {
  enabled = true
  servers = ["<known-address>:4647"]
}

:コマンドの場合

$ nomad node config -update-servers <IP>:4647

もし、この時点で認識できていない場合は、 nomad node status をしても何も起こりません。

$ nomad node status
$

ちゃんと認識できていれば下記のように出るはずです。

$ nomad node status
ID        DC   Name             Class   Drain  Eligibility  Status
48c8da6b  dc1  nomad-cluster03  <none>  false  eligible     ready
5dcf2b23  dc1  nomad-cluster02  <none>  false  eligible     ready
74e2e49c  dc1  nomad-cluster01  <none>  false  eligible     ready

Jobの実行

jobはClientにアプリケーションを実行させる機能のこと。

一連の流れ

Jobを実行させるための流れは下記です。

  1. job file を書く
  2. 登録されるJobの確認
  3. jobの登録
  4. 実行されたjobのステータス(またはlogの)確認

まずは

nodeのステータスを確認。ちゃんとServerから見えてますね。

 nomad node status
ID        DC   Name             Class   Drain  Eligibility  Status
4eba7433  dc1  nomad-cluster01  <none>  false  eligible     ready
119babd9  dc1  nomad-cluster02  <none>  false  eligible     ready
8ce4c355  dc1  nomad-cluster03  <none>  false  eligible     ready

あと、テストできるものであればなんでも良いのですが、goで書かれた http-echo をビルドしておきましょう。

ただテキストを返すシンプルなweb-serverです。

$ http-echo -listen=":5678" -text="hello world"

別のターミナルから

$ curl http://localhost:5678
hello world

1. job file を書く

jobのファイルを作ります。ファイル名は hey.nomad です(なんでもかまいません)

# ↓ 任意のjob名
job "hello" {
  datacenters = ["dc1"]

  group "example" {
    task "server" {
      driver = "exec"

      config {
        command = "/usr/bin/http-echo"
        args = [
          "-listen", ":5678",
          "-text", "hello world",
        ]
      }

      resources {
        network {
          mbits = 10
          port "http" {
            static = "5678"
          }
        }
      }
    }
  }
}
~

まず、注目したいのが下記の部分。

これが、jobをCLI上で操作するのに頻出するIDになります。

job "hello" {
}

2. 登録されるJobの確認

fileができたらひとまず nomad job plan をしてみましょう。

これから何が起こるのかを確認することができます。 Terraform を使った事のある方には既視感があるかもしれません。

$ nomad job plan hey.nomad
+ Job: "hello"
+ Task Group: "example" (1 create)
  + Task: "server" (forces create)

Scheduler dry-run:
- All tasks successfully allocated.

Job Modify Index: 0
To submit the job with version verification run:

nomad job run -check-index 0 hey.nomad

When running the job with the check-index flag, the job will only be run if the
server side version matches the job modify index returned. If the index has
changed, another user has modified the job and the plan's results are
potentially invalid.

3. jobの登録

次は nomad run 。これでデプロイを実行します。

$ nomad job run hey.nomad
==> Monitoring evaluation "4d230d5d"
    Evaluation triggered by job "hello"
    Allocation "20553d38" created: node "4eba7433", group "example"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "4d230d5d" finished with status "complete"

ほうほう。 8ce4c355 のノードにあると出てきましたね。

ステータスを確認してみます。 コマンドは nomad job status です。

$ nomad job status
ID     Type     Priority  Status          Submit Date
hello  service  50        running         2019-05-12T01:42:24Z

さて、これはちゃんと動いてるんでしょうか?

デプロイされているはず nomad-cluster01(4eba7433) のインスタンスで curl http://localhost:5678 と打ってみましょう。 hello world と返ってくればOKです。

4. 実行されたjobのステータス(またはlogの)確認

jobのステータスがどうなっているのか詳しく確認したいですね。

詳しく見たいときは nomad job status <job-name> です。

さっきのjobファイルでつけたjobの名前は hello です。

$ nomad job status hello
ID            = hello
Name          = hello
Submit Date   = 2019-05-12T02:50:35Z
Type          = service
Priority      = 50
Datacenters   = dc1
Status        = running
Periodic      = false
Parameterized = false

Summary
Task Group  Queued  Starting  Running  Failed  Complete  Lost
example     0       0         1        1       0         0

Allocations
ID        Node ID   Task Group  Version  Desired  Status   Created    Modified
5b5c529f  4eba7433  example     0        run      running  1h41m ago  1h34m ago

いいですね。動いてくれていそうです。

Allocations とはclientノードとjobのtask間のマッピングされた情報のこと。直訳すると”割り当て”。

また、nomadのjobにおける概念の中で最も小さい単位が task にあたる。

allocationsの詳しい情報を見ることでtaskのより細かな状態を知ることができます。コマンドは、 nomad alloc status <Allocation ID> です。

$ nomad alloc status 5b5c529f
ID                  = 5b5c529f
Eval ID             = 601098bd
Name                = hello.example[0]
Node ID             = 4eba7433
Job ID              = hello
Job Version         = 0
Client Status       = running
Client Description  = Tasks are running
Desired Status      = run
Desired Description = <none>
Created             = 1h56m ago
Modified            = 1h48m ago

Task "server" is "running"
Task Resources
CPU        Memory           Disk     Addresses
4/100 MHz  1.9 MiB/300 MiB  300 MiB  http: 10.140.0.31:5678

Task Events:
Started At     = 2019-05-12T03:04:43Z
Finished At    = N/A
Total Restarts = 0
Last Restart   = N/A

Recent Events:
Time                  Type        Description
2019-05-12T03:04:43Z  Started     Task started by client
2019-05-12T02:57:28Z  Task Setup  Building Task Directory
2019-05-12T02:57:28Z  Received    Task received by client

ちゃんと動いてくれていそうです。

また、ログも確認することができます。 nomad alloc logs <Allocation ID> でログを確認できます。

$ nomad alloc logs 5b5c529f-501e-11aa-bfb1-d3b7fb7ce3c5
2019/05/12 04:35:51 localhost:5678 [::1]:36054 "GET / HTTP/1.1" 200 12 "curl/7.29.0" 230.717µs
2019/05/12 05:01:41 localhost:5678 [::1]:36682 "GET / HTTP/1.1" 200 12 "curl/7.29.0" 287.317µs

jobの停止

taskの削除は nomad job stop <job ID> でできます。

$ nomad job stop hello
==> Monitoring evaluation "0e4d5128"
    Evaluation triggered by job "hello"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "0e4d5128" finished with status "complete"

さて、もう一回 job statusします。

$ nomad job status
ID     Type     Priority  Status          Submit Date
hello  service  50        dead (stopped)  2019-05-12T02:50:35Z

ステータスはstoppedになっていますね。では削除してみます。

nomadはHTTP APIを使うことができるので下記のpathへリクエストを投げます。

$ curl -X PUT http://localhost:4646/v1/system/gc

再度確認します

$ nomad job status
No running jobs

綺麗になくなってますね。

今日はここまで。

いかがでしたでしょうか?

今回の記事は入門編ということでチュートリアル的な内容ではありますが、ざっくりと概念についてはイメージできたのではないでしょうか。

次回は consul + nomd による自動クラスタリング、 Dockerプラグイン を使ったアプリケーションのデプロイなどより実践的な構築を記事にしたいと思います。

ではでは、また次回。