NomadでPrometheusを起動する

[前回](https://qiita.com/takayoshi-shiraki/items/b32b6e6abf2a70e3ce78)、[前々回](https://qiita.com/takayoshi-shiraki/items/46fc6a1e4dbc7f5fd500)でConsulとNomadのHTTP APIのTLSを有効化しました。
今回はNomad上にPrometheusを配置しつつ、ConsulとNomadのメトリクスを収集してみたいと思います。

## 概要

PrometheusをNomadのジョブとして投入します。
Prometheusの設定ファイルはあえてdockerイメージの内部に配置せず、Consul KVに登録してNomadジョブ投入と同時にコンテナ内に配置していきます。

さらにPrometheusでConsulとNomadのメトリクスを収集しますが、前回まででConsulとNomadをTLS化したため、PrometheusからもHTTPSでメトリクス取得のリクエストを発行します。
そのあたりの設定ファイルの書き方を見ていきましょう。

ちなみに、Nomadのジョブはなるべくステートレス（状態を持たない、永続化もしない）に作るべきものですが、Prometheusのメトリクス情報については何かしら永続化の方法を考えたいところです。
これについては次回以降で扱っていく予定です。

　

では進めていきましょう。


## Prometheusの設定ファイル

まず、ConsulとNomadのメトリクスを収集するためのPromethuesの設定ファイルを作ります。

PrometheusはPull型でデータを収集するため、監視エンドポイントをPrometheusに設定する必要があります。
今回は、ConsulのService Discoveryを使ってConsulとNomadが起動しているIPアドレスとポートを取得します。

「consul_sd_configs」がConsul経由で監視エンドポイントのIPアドレスとポートを取得する設定です。
HTTPSでConsulにアクセスするため、「scheme: https」を指定し、「tls_config」で各種証明書・鍵を指定しています。
ここではDockerコンテナ内の/opt/certに証明書・鍵を配置することにしています。

```yml:promethus.yml
global:
  scrape_interval:     5s
  evaluation_interval: 5s
scrape_configs:
  - job_name: 'consul_metrics'
    scheme: https
    tls_config:
      ca_file: /opt/cert/consul-agent-ca.pem
      cert_file: /opt/cert/dc1-cli-consul-0.pem
      key_file:  /opt/cert/dc1-cli-consul-0-key.pem
      insecure_skip_verify: true
    consul_sd_configs:
    - server: 'consul.service.consul:8501'
      services: ['consul']
      scheme: https
      tls_config:
        ca_file: /opt/cert/consul-agent-ca.pem
        cert_file: /opt/cert/dc1-cli-consul-0.pem
        key_file:  /opt/cert/dc1-cli-consul-0-key.pem
        insecure_skip_verify: false
    relabel_configs:
    - source_labels: [__address__]
      replacement: ${1}:8501
      regex: ([^:]+):(\d+)
      target_label: __address__ 
    - source_labels: [__meta_consul_node]
      target_label: node_name
    scrape_interval: 5s
    metrics_path: /v1/agent/metrics
    params:
      format: ['prometheus']

  - job_name: 'nomad_metrics'
    scheme: https
    tls_config:
      ca_file: /opt/cert/nomad-ca.pem
      cert_file: /opt/cert/nomad-cli.pem
      key_file:  /opt/cert/nomad-cli-key.pem
      insecure_skip_verify: true
    consul_sd_configs:
    - server: 'consul.service.consul:8501'
      services: ['nomad-client', 'nomad']
      scheme: https
      tls_config:
        ca_file: /opt/cert/consul-agent-ca.pem
        cert_file: /opt/cert/dc1-cli-consul-0.pem
        key_file:  /opt/cert/dc1-cli-consul-0-key.pem
        insecure_skip_verify: false
    relabel_configs:
    - source_labels: ['__meta_consul_tags']
      regex: '(.*)http(.*)'
      action: keep
    - source_labels: [__meta_consul_node]
      target_label: node_name
    scrape_interval: 5s
    metrics_path: /v1/metrics
    params:
      format: ['prometheus']
```

前提として、PrometheusのDockerコンテナから「consul.service.consul」の名前でConsulにアクセスできるように、DNSの調整をする必要があります。
私の環境では、Nomadクライアントを起動しているホストでdnsmasqを使ってConsul DNSを参照できるようにし、その上でDockerコンテナからhost_network経由でホスト上のDNSを参照しています。
[Nomadの公式ドキュメント](https://learn.hashicorp.com/tutorials/consul/dns-forwarding#dnsmasq-setup)を参考にしてください。

Consulから得られるNomadのIPアドレスがNomadのサーバ証明書内のCNやSANと一致しないため、サーバホスト名の検証をスキップしてTLSエラーを回避しています。
（nomad_metricsのinsecure_skip_verify:true）
consul_metricsの insecure_skip_verify: trueも同様です。


## Nomadの設定ファイル

Nomadの設定フィルにメトリクス公開の設定を追加します。
項目については[ドキュメント](https://www.nomadproject.io/docs/configuration/telemetry)を参照ください。

```nomad_server.hcl、nomad_client.hcl
...
telemetry {
  publish_allocation_metrics = true
  publish_node_metrics       = true
  prometheus_metrics         = true
}
...
```


## Nomadジョブファイル

Prometheus向けのNomadジョブファイルを作ります。

再下部のtemplateブロックでConsul KVからprometheus.yml、証明書・鍵を読み出し、Nomadジョブ用のlocalディレクトリに配置します。
mountブロックでlocalディレクトリをコンテナ内にマウントしています。

証明書・鍵ファイルをばらばらと置いていますが、このあたりはzip化してWebサーバやS3に上げるほうがすっきりするかと思います。

update,migrate,resourcesあたりは仮の設定ですので、運用設計や環境に合わせて設定が必要です。

```prometheus.nomad
job "prometheus" {
  datacenters = ["dc1"]
  type = "service"
  update {
    max_parallel = 1
    min_healthy_time = "10s"
    healthy_deadline = "3m"
    progress_deadline = "10m"
    auto_revert = false
    canary = 0
  }
  migrate {
    max_parallel = 1
    health_check = "checks"
    min_healthy_time = "10s"
    healthy_deadline = "5m"
  }
  group "prometheus" {
    count = 1
    restart {
      attempts = 2
      interval = "30m"
      delay = "15s"
      mode = "fail"
    }
    network { 
      mode = "host"
      port "prometheus" {
        static = 9090
      }
    }
    service {
      name = "prometheus"
      port = "prometheus"
      tags = ["opt"]
      check {
        type     = "http"
        protocol = "http"
        path     = "/-/healthy"
        interval = "30s"
        timeout  = "2s"
      }
    }
    task "prometheus" {
      driver = "docker"
      config {
        logging {
          type = "journald"
        }
        image = "prom/prometheus:v2.24.1"
        ports = ["prometheus"]
        network_mode = "host"
        args = [
          "--config.file=/srv/prometheus/prometheus.yml",
          "--storage.tsdb.path=/prometheus",
          "--web.enable-lifecycle"
        ]
        mounts = [
          {
            type = "bind"
            source = "local/prometheus.yml"
            target = "/srv/prometheus/prometheus.yml"
          },
          {
            type = "bind"
            source = "local/cert"
            target = "/opt/cert"
          }
        ]
      }
      env {
      }
      template {
        change_mode = "restart"
        destination = "local/prometheus.yml"
        data = "{{ key \"prometheus/config/prometheus.yml\" }}"
      }
      template {
        change_mode = "noop"
        destination = "local/cert/consul-agent-ca.pem"
        data = "{{ key \"cert/consul/consul-agent-ca.pem\" }}"
      }
      template {
        change_mode = "noop"
        destination = "local/cert/dc1-cli-consul-0.pem"
        data = "{{ key \"cert/consul/dc1-cli-consul-0.pem\" }}"
      }
      template {
        change_mode = "noop"
        destination = "local/cert/dc1-cli-consul-0-key.pem"
        data = "{{ key \"cert/consul/dc1-cli-consul-0-key.pem\" }}"
      }
      template {
        change_mode = "noop"
        destination = "local/cert/nomad-ca.pem"
        data = "{{ key \"cert/nomad/nomad-ca.pem\" }}"
      }
      template {
        change_mode = "noop"
        destination = "local/cert/nomad-cli.pem"
        data = "{{ key \"cert/nomad/nomad-cli.pem\" }}"
      }
      template {
        change_mode = "noop"
        destination = "local/cert/nomad-cli-key.pem"
        data = "{{ key \"cert/nomad/nomad-cli-key.pem\" }}"
      }
      resources {
        cpu    = 500  # MHz
        memory = 1024 # MB
      }
    }
  }
}
```

## Consul KVとNomadジョブファイルの投入

それではいよいよローカルにあるファイルをConsul KVやNomadに投入していきましょう。

以下のファイルを用意します。ドメイン名やパスは調整してください。

```env.sh
export CONSUL_HTTP_ADDR=https://consul.mydomain.tk:8501
export CONSUL_CACERT=./consul-agent-ca.pem
export CONSUL_CLIENT_CERT=./dc1-cli-consul-0.pem
export CONSUL_CLIENT_KEY=./dc1-cli-consul-0-key.pem

export NOMAD_ADDR=https://nomad.mydomain.tk:4646
export NOMAD_CACERT=./nomad-ca.pem
export NOMAD_CLIENT_CERT=./nomad-cli.pem
export NOMAD_CLIENT_KEY=./nomad-cli-key.pem
```

```register_setting.sh
#!/bin/bash
source ./env.sh

consul kv put prometheus/config/prometheus.yml @./prometheus.yml
consul kv put cert/consul/consul-agent-ca.pem @./consul-agent-ca.pem
consul kv put cert/consul/dc1-cli-consul-0.pem @./dc1-cli-consul-0.pem
consul kv put cert/consul/dc1-cli-consul-0-key.pem @./dc1-cli-consul-0-key.pem
consul kv put cert/nomad/nomad-ca.pem @./nomad-ca.pem
consul kv put cert/nomad/nomad-cli.pem @./nomad-cli.pem
consul kv put cert/nomad/nomad-cli-key.pem @./nomad-cli-key.pem
```

```register_job.sh
#!/bin/bash
source ./env.sh

nomad job run prometheus.nomad
```

シェルスクリプトを実行して投入します。

```console
$ chmod +x *.sh
$ ./register_setting.sh
Success! Data written to: prometheus/config/prometheus.yml
Success! Data written to: cert/consul/consul-agent-ca.pem
Success! Data written to: cert/consul/dc1-cli-consul-0.pem
Success! Data written to: cert/consul/dc1-cli-consul-0-key.pem
Success! Data written to: cert/nomad/nomad-ca.pem
Success! Data written to: cert/nomad/nomad-cli.pem
Success! Data written to: cert/nomad/nomad-cli-key.pem
$
$ ./register_job.sh
==> Monitoring evaluation "8e6e815c"
    Evaluation triggered by job "prometheus"
    Evaluation within deployment: "679353d4"
    Allocation "50ef43a1" created: node "ef1cba89", group "prometheus"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "8e6e815c" finished with status "complete"
```

ConsulやNomadの管理UI画面でPrometheusのジョブが起動しているのを確認します。

以下のようにNomad CLIでNomadジョブのステータスを確認することもできます。

```console
$ source ./env.sh
$ nomad job status prometheus
ID            = prometheus
Name          = prometheus
Submit Date   = 2021-03-31T02:26:49Z
Type          = service
Priority      = 50
Datacenters   = dc1
Namespace     = default
Status        = running
Periodic      = false
Parameterized = false

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

Latest Deployment
ID          = 679353d4
Status      = successful
Description = Deployment completed successfully

Deployed
Task Group  Desired  Placed  Healthy  Unhealthy  Progress Deadline
prometheus  1        1       1        0          2021-03-31T02:39:09Z

Allocations
ID        Node ID   Task Group  Version  Desired  Status   Created    Modified
50ef43a1  ef1cba89  prometheus  0        run      running  2m29s ago  10s ago
```

Statusがrunningになり、Healthyが1になっています。

　

## まとめ

今回はPrometheusをNomadジョブで起動してみました。
また、ConsulのService Discoveryから監視エンドポイントを取得する設定も確認しました。

今回の設定では、Prometheusが収集したメトリクス情報は、Nomadジョブ内の起動オプション「--storage.tsdb.path」でしたとおり、Dockerコンテナ内の/prometheusに保存されます。
これではNomadジョブが停止した際にDockerコンテナと同時にロストしてしますので、次回は適切に永続化するように構成を変更してみましょう。


＜参考＞

* https://learn.hashicorp.com/tutorials/nomad/prometheus-metrics
* https://www.nomadproject.io/docs/configuration/telemetry
* https://prometheus.io/docs/prometheus/latest/configuration/configuration/