2020年10月19日月曜日

Python から Prometheus のメトリックを取得してみる

概要

前回 Ruby から Prometheus のメトリックを取得してみました
今回は Python から取得してみたいと思います

環境

  • macOS 10.15.7
  • Python 3.8.5
    • prometheus-api-client

インストール

  • pipenv install prometheus-api-client

すべてのメトリックのクエリを取得

取得可能なメトリックの一覧を取得してみます
配列でメトリック名が取得できます
このメトリック名と時間を使って特定の期間のメトリックを取得できます

  • vim app.py
from prometheus_api_client import PrometheusConnect

prom = PrometheusConnect(url ="http://192.168.100.10:9090", disable_ssl=True)
print(prom.all_metrics())
  • pipenv run python app.py

特定のメトリック情報を取得する

例えば redis_memory_used_bytes を取得してみます
最新の 10 分間だけ取得する場合には get_metric_range_data を使います
label_config には特定のメトリックを取得ために設定されているキーバリューのラベルを設定します
あとは metric_name にメトリック名を指定すれば OK です

import datetime
from prometheus_api_client import PrometheusConnect

prom = PrometheusConnect(url ="http://192.168.100.10:9090", disable_ssl=True)
label_config = {'instance': 'localhost:9121', 'job': 'redis'}
metric_data = prom.get_metric_range_data(metric_name='redis_memory_used_bytes', label_config=label_config)
print(metric_data)

t = metric_data[0].get('values')[-1][0]
v = metric_data[0].get('values')[-1][1]
print(datetime.datetime.fromtimestamp(t))
print(int(v) / (1000 * 1000))

values という配列にメトリックデータが入っています
-1 で参照しているのは配列の最後に最新のデータが入っているためです
時刻は unix time なので変換し値はバイト単位なのでメガバイトに変換しています

時間を指定して取得する

10 分間ではないレンジを指定したい場合は get_metric_range_data を使います
時間の指定は prometheus_api_client が用意してくれている parse_datetime が使えます
chunk_size は取得できるメトリック数をいくつかのチャンクに分けて取得してくれます
例えば以下のサンプルのように 30 分間のメトリックを取得する際に chunk_size=6mins などにすると 5 分間隔のメトリックデータに分割して取得してくれます

import datetime
from prometheus_api_client import PrometheusConnect
from prometheus_api_client.utils import parse_datetime

prom = PrometheusConnect(url ="http://192.168.100.10:9090", disable_ssl=True)
label_config = {'instance': 'localhost:9121', 'job': 'redis'}
start_time = parse_datetime("30min")
end_time = parse_datetime("now")
chunk_size = datetime.timedelta(minutes=30)

metric_data = prom.get_metric_range_data(
    'redis_memory_used_bytes{instance="localhost:9121",job="redis"}',
    start_time=start_time,
    end_time=end_time,
    chunk_size=chunk_size,
)
print(metric_data)
print(len(metric_data[0].get('values')))

t = metric_data[0].get('values')[-1][0]
v = metric_data[0].get('values')[-1][1]
print(datetime.datetime.fromtimestamp(t))
print(int(v) / (1000 * 1000))

最後に

Python から Prometheus のメトリックを取得してみました
基本は Ruby と同じでメトリック名+ラベル+時間のレンジを指定することでメトリック情報を取得することができます
時間や値の変換方法が言語で異なる他、データ取得のためのヘルパーメソッドが用意されているかいないかが違いかなと思います

参考サイト

2020年10月16日金曜日

Ruby から Prometheus のメトリックを取得してみる

概要

過去に自分のアプリにエクスポータを追加する方法を紹介しました
今回はエクスポータから取得して Prometheus に溜まったメトリックデータを Ruby から取得してみたいと思います
使用するライブラリは prometheus/prometheus_api_client_ruby になります

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • prometheus-api-client 0.6.2

インストール

  • bundle init
  • vim Gemfile
gem "prometheus-api-client"
  • bundle install

サンプルコード: メトリックの取得とデータ変換

例えば redis-exporter から取得して Prometheus に溜まっているデータを取得するには以下のようなコードになります

  • vim app.rb
require 'prometheus/api_client'

prometheus = Prometheus::ApiClient.client(url: 'http://192.168.100.10:9090')

now = Time.now
a_hour_ago = now - 60 * 60

response = prometheus.get(
  'query_range',
  query: 'redis_memory_used_bytes{instance="localhost:9121",job="redis"}',
  start: a_hour_ago.strftime("%FT%T%:z"),
  end:   now.strftime("%FT%T%:z"),
  step:  '120s',
)

puts response.status
puts response.body

result = JSON.parse(response.body)
t = result["data"]["result"].first["values"].first[0]
v = result["data"]["result"].first["values"].first[1]

puts Time.at(t)
puts v.to_i / (1024.0 * 1024.0)

クエリに関しては Prometheus の UI で確認できるクエリをそのまま入力すれば OK です

データは基本的に時間のレンジで指定するのがいいので start と end を指定しましょう
時刻はフォーマットが決まっているので指定のフォーマットに併せて変換しています (例: 2020-10-16 09:58:44 +0900)

取得できるデータは JSON になっています
時刻データと実際の値が配列になっているのでそれぞれ取得しています
あとは必要なデータ形式に変換すれば OK です
サンプルでは見やすいように時刻を変換しているのとバイトデータとメガバイト単位に変換しています

最後に

prometheus/prometheus_api_client_ruby を使って Prometheus に溜まったデータを取得してみました
これを元に独自のアラートルールや UI が作成できるようになります

参考サイト

2020年10月15日木曜日

Sinatra の time_for ヘルパーメソッドを使ってみる

概要

Sinatra の time_for は Time オブジェクトを生成するためのヘルパーメソッドです
今回はどんな感じで使えるのか試してみました

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • sinatra 2.0.7

とりあえず使ってみる

引数には Time オブジェクトを生成する文字列を指定することができます
Time.parse で指定可能な形式ならなんでも OK です

  • vim app.rb
require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/' do
    t = time_for('Oct, 15 2020')
    # t = time_for('2020/10/15 10:09:55')
    # puts t.class # -> Time
    t.to_s
  end
end

last_modified や expires ヘルパーメソッドに影響する

last_modified と expires はヘッダを自動的に付与するヘルパーメソッドです
これらのヘッダは基本的に時刻を返却するようになっています
Sinatra では last_modified や expires は内部的に time_for を呼び出しているので time_for をオーバライドすることでそれらを使いやすくすることができます

require 'sinatra/base'

class MyApp < Sinatra::Base
  helpers do
    def time_for(value)
      case value
      when :yesterday then Time.now - 24*60*60
      else super
      end
    end
  end

  get '/' do
    last_modified :yesterday
    'ok'
  end
end

デフォルトの time_for に関してはこのあたりのソースを読むとわかりやすいと思います

最後に

Sinatra の time_for ヘルパーメソッドを試してみました
他のヘルパーメソッドメソッドからも参照されているのでデフォルトの time_for をオーバライドすることでいろいろと使えそうなヘルパーメソッドにすることができそうです

2020年10月14日水曜日

Ruby の connection_pool の挙動を確認してみる

概要

Ruby の connection_pool を redis で使って挙動を確認してみました
connection_pool を使うことで意図しない大量の接続が来たときにもデータベース負荷をかけずにアプリ側でコントロールできるようになります
今回はプールのサイズを超えて接続した場合の挙動などを確認します

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • connection_pool 2.2.3

プールサイズ確認用のスクリプト

特に redis に対してコマンドは発行していませんが state_check のブロック内で cp.available で残りのプール数を表示しています
(1..3) の Range の部分でプールのサイズを調整できるのここを変更することで挙動を確認します

require 'connection_pool'
require 'redis'

def state_check(cp)
  cp.with do |redis_cli|
    puts cp.available
    sleep 5
  end
end

cp = ConnectionPool.new(size: 5, timeout: 3) {
  Redis.new
}
threads = []

(1..3).each do
  threads << Thread.new do
    state_check(cp)
  end
end

threads.each { |t| t.join }

ちなみに上記スクリプトを実行するとプール数が 4, 3, 2 と減って表示されるのが確認できます
なおコマンドが実行されていないので redis-cli の CLIENT LIST で見ても表示はされません

プールサイズを超えてみる

では実際にプールサイズを超えて実行してみます
先程の Range の (1..3) を (1..6) にするだけです

すると Waited 3 sec (ConnectionPool::TimeoutError) のエラーが発生するのが確認できると思います
これはプールサイズが 5 に対して同時に 6 接続した場合に 1 つは接続待ち状態になります
接続待ち状態になったプールは ConnectionPool.new(size: 5, timeout: 3) で指定した timeout 秒待ってプールに空きが出なければ上記のエラーを raise しているのです

なので正常に終わらせたいのであれば timeout を 10 などに変更してみましょう
すると今度は raise せずに 4, 3, 2, 1, 0, 4 と表示されて正常終了するはずです

ここまで確認すればわかるのですがプールが開放されるのは with ブロック内で実行しているコマンドなどが終了してブロックが終了した段階でプールが開放されるようです

with 内で raise された場合はどうなるのか

with 内で意図的に raise してブロックが最後まで行かなかった場合にどうなるのかも確認してみました
確認スクリプトを少し修正しています

require 'connection_pool'
require 'redis'

def state_check(cp, i)
  cp.with do |redis_cli|
    begin
      puts cp.available
      sleep 5
      raise Exception
    rescue Exception => e
      puts "error on #{i}"
    end
    redis_cli.set("result#{i}", 0)
  end
end

Thread.report_on_exception = false
cp = ConnectionPool.new(size: 5, timeout: 10) {
  Redis.new
}
threads = []

(1..6).each do |i|
  threads << Thread.new do
    state_check(cp, i)
  end
end

threads.each { |t| t.join }
puts "end"

スクリプトが悪い可能性もありますが一応これで 6 つのプールの処理がすべて正常に終了することが確認できました
with のブロックが何かしらの処理で止まったりしてもブロックを抜けさえすればプールは開放されるようです

最後に

connection_pool を使って Redis に接続できるクライアント数の制御をしてみました
プールサイズを超えた場合はタイムアウト秒待ちそれでもプールに空きがない場合はエラーになることが確認できました

2020年10月13日火曜日

Ruby で redis にあるデータをオブジェクトにバインドする方法

概要

Ruby で Redis を扱う場合にデータ構造が決まっている場合はオブジェクトにしたい場合があります
そんな場合は redis-objects というライブラリを使うと簡単にオブジェクト操作できます

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • redis-objects 1.5.0

インストール

  • bundle init
  • vim Gemfile
gem "redis-objects"
  • bundle install

接続

内部的には redis ライブラリを使っているので初期化方法などは redis ライブラリと同じように行えます

  • vim app.rb
require 'redis'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

データを保存する

データを保存する場合も一度 redis-objects のオブジェクトを作成してから保存します
redis のオブジェクトとして操作するクラスは必ず Redis::Objects を include する必要があります

今回は単純な文字列を保存してみます
Redis::Objects には様々なタイプが用意されており文字列を保存したい場合には value を使って宣言します
そしてキーを一意に特定するために必ず id というメソッドを実装する必要があります

  • vim app.rb
require 'redis'
require 'redis/objects'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

class User
  include Redis::Objects
  value :name
  def id
    1
  end
end

user = User.new
user.name = "hawk"

これで実行すると redis 側には以下のようなデータが保存されているのが確認できると思います

127.0.0.1:6379> keys *
1) "user:1:name"
127.0.0.1:6379> get user:1:name
"hawk"

データを取得する

次に保存したデータを取得してみましょう
とは言ってもやることはオブジェクトを作成するだけです
データを参照する場合は .value を使います

  • vim app.rb
require 'redis'
require 'redis/objects'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

class User
  include Redis::Objects
  value :name
  def id
    1
  end
end

user = User.new
puts user.name.value # => hawk

カウンタを使う

先程は value というタイプを使いました
次は counter というタイプを使ってみます
これは数字の情報を redis で管理する他にカウントアップするための専用のメソッドが用意されています
increment を呼び出すと値を 1 つプラスしてくれます

  • vim app.rb
require 'redis'
require 'redis/objects'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

class User
  include Redis::Objects
  counter :my_posts
  def id
    1
  end
end

user = User.new
user.my_posts.increment
user.my_posts.increment
user.my_posts.increment
puts user.my_posts.value # 3
user.my_posts.reset
puts user.my_posts.value # 0
user.my_posts.reset 5
puts user.my_posts.value # 5
127.0.0.1:6379> keys *
1) "user:1:name"
2) "user:1:my_posts"
127.0.0.1:6379> type user:1:my_posts
string
127.0.0.1:6379> get user:1:my_posts
"5"

配列を使う

配列も扱えます
list を使ってキーを宣言します

require 'redis'
require 'redis/objects'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

class User
  include Redis::Objects
  list :favorites
  def id
    1
  end
end

user = User.new
['ruby', 'swift', 'python'].each do |lang|
  user.favorites << lang
end
127.0.0.1:6379> LLEN user:1:favorites
(integer) 3
127.0.0.1:6379> LRANGE user:1:favorites 0 -1
1) "ruby"
2) "swift"
3) "python"

ハッシュを使う

ハッシュも扱えます
hask_key を使ってキーを宣言します

  • vim app.rb
require 'redis'
require 'redis/objects'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

class User
  include Redis::Objects
  hash_key :score
  def id
    1
  end
end

user = User.new
user.score['japanese'] = 10
user.score['arithmetic'] = 20
user.score['science'] = 30
127.0.0.1:6379> hgetall user:1:score
1) "japanese"
2) "10"
3) "arithmetic"
4) "20"
5) "science"
6) "30"

直接 redis オブジェクトを使う

get や set といった redis のコマンドを直接実行することもできます
ただその場合はキーの指定も直接行う必要があります
クラス名、id メソッド、キー名を使って自動生成される redis のキーを指定します

  • vim app.rb
require 'redis'
require 'redis/objects'

Redis.current = Redis.new(
  :host => '127.0.0.1',
  :port => 6379
)

class User
  include Redis::Objects
  hash_key :score
  def id
    1
  end
end

score = User.redis.hgetall('user:1:score')
puts score

=> {"japanese"=>"10", "arithmetic"=>"20", "science"=>"30"}

最後に

redis-objects を使って redis にあるデータをオブジェクトにシリアライズしてから使用する方法を紹介しました
コード上でどういったデータが redis に入っているのか一目で確認することができるのも嬉しい点かなと思います

こちらのほうがオブジェクト指向っぽく書けますがデータ構造が変わった場合はクラス側も修正必要があるので手間であります
今回は基本的なシリアライズ/デシリアライズの方法しか紹介しませんでしたがカスタムシリアライザも作成できるので複雑なデータのオブジェクト化も自作すれば可能になります

参考サイト

2020年10月12日月曜日

Ruby の hashie を使ってハッシュを使いやすくする

概要

hashie/hashie は Ruby のハッシュを拡張するためのライブラリです
ハッシュのフィールドにアクセスするときにスクエアブラケットではなくドットにしたりハッシュに対して型成約を付けたりすることができます
今回はいろいろな使い方のサンプルを紹介します

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83

Getting Started

とりあえず使ってみましょう
既存のハッシュから Hashie::Mash オブジェクトを作成すればキーにドットでアクセスできるようになります
ネストしている Array は Hashie::Array、Hash は Hashie::Mash として変換されています

require 'hashie'

profile = Hashie::Mash.new({
  name: 'hawk',
  age: 10,
  langs: [
    'ruby',
    'swift',
    'python'
  ],
  score: {
    'japanese': 10,
    'arithmetic': 20,
    'science': 30,
  }
})
puts profile.name
puts profile.age
puts profile.langs.first
puts profile.score.science

型を強制する

ハッシュには型がありませんが Coercion という機能を使うと入力するハッシュの型成約ができます
Hashie::Extensions::Coercion と Hashie::Extensions::MergeInitializer を include しましょう
そして coerce_key を使って型成約するフィールドを指定します

class Profile < Hash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
end

profile = Profile.new({
  name: 'hawk',
  age: 10
})
puts profile[:name]
puts profile[:age]

強制した上でドットでフィールドにアクセスする

先程は普通にハッシュに対して型成約しただけなのでフィールドにアクセスする場合はスクウェアブラケットを使います
それだと Hashie っぽくないのでドットでアクセスできるようにする場合は Hashie::Mash を継承しましょう

class Profile < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
end

profile = Profile.new({
  name: 'hawk',
  age: 10
})
puts profile.name
puts profile.age

強制なので強制できない場合は変換メソッドのデフォルトの値になる

例えば coerce_key :age, Integer した場合は age フィールドには数値が入ることが想定されます
しかし以下のように数値以外が来た場合には to_i メソッドの返り値がそのまま入ります

class Profile < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
end

profile = Profile.new({
  name: 'hawk',
  age: 'snowlog'
})
puts profile.name
puts profile.age # -> 0

この場合 age は 0 で初期化されます
なぜなから 'snowlog'.to_i の結果が 0 になるからです

クラスの入れ子にすると自動で初期化してくれる

例えば強制するハッシュクラス内に別のクラスがある場合にはそのクラスの initialize を自動で読んでセットしてくれます

class Score < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :japanese, Integer
  coerce_key :arithmetic, Integer
  coerce_key :science, Integer
end

class Profile < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
  coerce_key :score, Score
end

profile = Profile.new({
  name: 'hawk',
  age: 10,
  score: {
    japanese: 20,
    arithmetic: 30,
    science: 40
  }
})
puts profile.name
puts profile.age
puts profile.score.japanese

入れ子になるクラスは Hashie::Mash を継承していなくてもいい

入れ子にするクラスは既存のクラスを使いたい場合もあると思います
そんな場合はハッシュを受取る initialize を定義すればそちらを自動で読んでくれます

class Score
  attr_accessor :japanese, :arithmetic, :science

  def initialize(score)
    @japanese = score[:japanese]
    @arithmetic = score[:arithmetic]
    @science = score[:science]
  end
end

class Profile < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
  coerce_key :score, Score
end

profile = Profile.new({
  name: 'hawk',
  age: 10,
  score: {
    japanese: 20,
    arithmetic: 30,
    science: 40
  }
})
puts profile.name
puts profile.age
puts profile.score.japanese

coerce_key は lambda を受け取ることもできる

型が 1 つじゃない可能性がある場合は coerce_key を lambda で定義することもできます

class Profile < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
  coerce_key :point, lambda { |v|
    if v > 0
      v
    else
      'error'
    end
  }
end

profile = Profile.new({
  name: 'hawk',
  age: 'snowlog',
  point: -1
})
puts profile.name
puts profile.age
puts profile.point

アロー演算子を使った場合は以下のように定義できます

class Profile < Hashie::Mash
  include Hashie::Extensions::Coercion
  include Hashie::Extensions::MergeInitializer

  coerce_key :name, String
  coerce_key :age, Integer
  coerce_key :point, ->(v) do
    if v > 0
      v
    else
      'error'
    end
  end
end

最後に

Ruby の Hashie を使ってハッシュの拡張をしてみました
とりあえずハッシュのフィールドにドットでアクセスするだけでも便利かなと思います
他にもキーが存在しない場合にはエラーにしたり定義していないキーを無視したりといろいろな機能があります

ハッシュの自由度を少し制限してしまう感じもあるので型強制については使いすぎると Ruby の良さも失ってしまうかもしれません
また型強制することで本来入らないであろうデータが入ってきてエラーを握りつぶす可能性もあるのでその辺りの考慮も必要になるかなと思います
リレーショナルなデータベースなどで型が厳密に決まっている場合には使えそうな気がします

参考サイト

2020年10月9日金曜日

Gitlab の Grafana でアラート機能を試してみた

概要

過去 に Gitlab の Grarana にアクセスする方法を紹介しました
どうやらアラート機能も使えるようなので試してみました

環境

  • GitLab Enterprise Edition 13.3.5-ee
  • grafana 7.0.3

admin でログインできるようにする

アラート機能を使うには admin 権限が必要です
Gitlab の Grafana はデフォルトでは Gitlab の OAuth のみでログインするようになっており権限が readonly しかないので admin でログインできるようにする必要があります

admin ログイン用のフォームを表示させる

まずは admin がログインできるようにログインフォームを表示するようにします

  • vim /etc/gitlab/gitlab.rb
grafana['disable_login_form'] = false
  • gitlab-ctl reconfigure

これで reconfigure をかければ grafana のログイン画面に OAuth 以外のログインフォームが表示されるようになります

admin ユーザのパスワードを設定する

これは CLI で行います
grafana['admin_password'] という項目が gitlab.rb にありますがこれではなく CLI で先にパスワードを設定する必要があります

  • gitlab-ctl set-grafana-password

これで新規に admin ユーザのパスワードを設定することができます
設定できたら Grafana の UI から「admin/設定したパスワード」でログインできるか確認してみましょう

アラート機能を有効にする

次にアラート機能を有効にします
デフォルトではなぜかアラート機能が使えないので有効にする必要があります

  • vim /etc/gitlab/gitlab.rb
grafana['alerting_enabled'] = true
  • gitlab-ctl reconfigure

これでアラート機能が有効になります

Notification channel を設定する

まずは Notification channel を新規で追加する必要があります
左メニューから追加しましょう

Slack や LINE、メールなどのチャネルに通知することができます
今回は Slack に通知してみます
Type に Slack を選択しあとは Incomming Webhook URL を設定しましょう

それ以外にも細かい設定ができますが今回は Incomming Webhook URL のみ設定します
チャネルやアイコンなど必要であれば設定しましょう
以下のようにチャネルの一覧に表示されれば OK です

アラートを設定してみる

これでようやくアラート機能が使えます
admin ユーザでログインして既存のダッシュボードを適当に選択して編集画面に移動しましょう

ダッシュボードの一覧からアラートしたい項目のダッシュボードを選択します
今回は「Gitlab Omnibus - Redis」を選択します

そしてダッシュボードから「Memory Usage」パネルを選択し「Edit」を選択します

アラート用のクエリを作成する

まずはアラート用のクエリを作成します
いくつかクエリが設定されているのですがアラート用には作成されておらずアラートが作成できないので新規でクエリを作成します

Query タブから新規で追加します
今回は Metrics に「redis_memory_used_bytes」を指定します

クエリを新規で作成した「Alert」タブに移動します
そして「Create Alert」を選択しましょう

アラートを作成する

まずアラートの条件を設定します
今回は 5 分間の値がある一定以上であれば通知するようにします
条件は以下のようになります

単位が bytes なので注意しましょう
設定しているしきい値の値がリアルタイムにグラフに反映されるのでわかりやすいと思います

次に通知するチャネルを設定します
先程作成した Slack チャネルを使います
通知する際のメッセージを設定可能なので好きなメッセージを設定します
本来はここに変数を使って現在の値などを表示したいのですがやり方がわかりませんでした
調べると他の人も悩んでいるのでまだやり方がないのかもしれません (参考)

作成できたら右上の「Apply」を押してアラートを作成します

ダッシュボードにもアラートのしきい値が表示されるようになります

動作確認

あとはアラートが上がるまで待ちましょう
今回の条件であれば 5 分後に 100% アラートが上がるはずです

問題なく Grafana でアラートの監視が始まっていればダッシュボードで Pending の線が表示されるようになります
これが表示されない場合はダッシュボードにアラートのルールがうまく保存されていないので以下のトラブルシューティングなどを参考に解決してください

トラブルシューティング: Template variables are not supported in alert queries

ダッシュボードからアラート以外で使用するクエリを削除してみましょう
もしくはアラート専用のダッシュボードを作成して再度クエリとアラートの登録を行ってみてください

トラブルシューティング: Provisioning: Cannot save provisioned dashboard

Gitlab が用意している既存のダッシュボードに対して操作すると出る場合があるようです
どうやら Gitlab が用意している Grafana の監視ファイルは JSON で保存されています

  • ls -l /opt/gitlab/embedded/service/grafana-dashboards
total 176
-rw-r--r-- 1 root root 14858 Sep  4 18:25 gitaly.json
-rw-r--r-- 1 root root 12103 Sep  4 18:25 nginx.json
-rw-r--r-- 1 root root 22528 Sep  4 18:25 overview.json
-rw-r--r-- 1 root root 20974 Sep  4 18:25 postgresql.json
-rw-r--r-- 1 root root 17716 Sep  4 18:25 praefect.json
-rw-r--r-- 1 root root 12019 Sep  4 18:25 rails-app.json
-rw-r--r-- 1 root root   542 Sep  4 18:25 README.md
-rw-r--r-- 1 root root 24709 Sep  4 18:25 redis.json
-rw-r--r-- 1 root root 11682 Sep  4 18:25 registry.json
-rw-r--r-- 1 root root 22238 Sep  4 18:25 service_platform_metrics.json

これらのファイルに対して UI から更新を掛ける場合は allowUiUpdates = true なる属性の設定が必要なようで Gitlab ではそれが設定されていないためにエラーになるようです

なのでその場合は新規でダッシュボードを作成して同じクエリと同じアラートルールを追加すれば OK です

最後に

Gitlab に付属の Grafana でアラート機能を試してみました
有効にするのが少し面倒なのとアラートのルールを設定できるのは admin ユーザだけのようです

有効にできればあとは Grafana の世界の話なのでクエリの設定の仕方などは Grafana のドキュメントを参考にすると良いかなと思います

参考サイト