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 のドキュメントを参考にすると良いかなと思います

参考サイト

2020年10月8日木曜日

Mac で grafana 超入門

概要

Grafana を Mac にインストールして管理画面などの操作を簡単に確認してみました

環境

  • macOS 10.15.7
  • grafana 7.2.0

インストール

  • brew install grafana

起動

  • brew services start grafana

これで localhost:3000 にアクセスするとログイン画面が確認できます

パスワードは admin/admin になります
初回ログイン時にパスワードを変更するように促されるので変更すればログインできます

ダッシュボードを作成してみる

まずはダッシュボードを作成してみましょう
左メニューのプラスから「Dashboard」を選択します

ダッシュボードにはパネルを最低 1 つ以上追加する必要があります
「Add new panel」でパネルを追加しましょう

デフォルトではサンプル用のクエリが設定されています
クエリを元にグラフを生成しているのでグラフを追加したい場合はクエリを追加します
クエリは画面の下の部分から追加できます

ダッシュボードが作成できたら右上の「Save」からダッシュボードを保存します
あとからクエリやパネルは編集可能です 

保存が完了するとホーム画面や検索の一覧にダッシュボードが表示されます
選択すると作成したダッシュボードの内容が確認できます

データソースを追加する

上記はサンプル用に準備されたデータを使っています
実際にはデータソースを追加してそこからクエリを発行してグラフを作成します
左メニューの「Configuration」->「Data sources」から追加できます

デフォルトでは ElasticSearch や Prometheus などメジャーなデータリソースが選択可能なようです

今回はデータソースの連携方法は紹介しませんが基本はデータソースを追加してそこにあるデータに対してクエリを発行することでデータを可視化する感じです

最後に

Grafana を Mac にインストールして超基本の操作を確認してみました
他にもアラートやプレイリストなど機能がたくさんあるので試す機会があれば別記事で紹介したいと思います

参考サイト

2020年10月7日水曜日

Ruby の lambda 使い方メモ

概要

lambda や Proc の使い方をメモがてら紹介します

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83

lambda を使って Proc オブジェクトを作成する

まずは基本中の基本です
lambda メソッドにブロックを渡すと Proc オブジェクトが返ってきます
それを変数に格納してあとから call メソッドを呼び出すことでブロックに定義した処理を実行することができます

my_lambda = lambda do
  puts 'ok'
end

puts my_lambda1.class
my_lambda.call

引数付きの lambda

lambda には引数を与えることもできます
ブロックを定義する際にブロック引数を与えることで call 時に引数を与えることができます

my_lambda = lambda do |val|
  puts val
end

my_lambda.call('hello')

作成した Proc オブジェクトをメソッドに与える

Proc オブジェクトを引数としてメソッドに渡すこともできます
受け取るメソッドは &block という感じで Proc オブジェクトを受け取れるようにするだけです

my_lambda = lambda do |val|
  puts val
end

def test_lambda_arg(val, &block)
  block.call(val)
  # yield(val)
end

test_lambda_arg('hello', &my_lambda)

メソッドに Proc オブジェクトを渡す場合は & を付与してブロックとして渡します
メソッドは受け取った Proc オブジェクトに対して call メソッドを呼び出すだけです
また別の呼び方として受け取った Proc オブジェクトを yield とするだけでも呼び出すことができます (更に言うと引数の &block も省略できます、ここでは紹介しないので詳しくはこちらなどを参照してください)

クラスのインスタンス変数として Proc オブジェクトを管理する

Proc オブジェクトは当然インスタンス変数としても管理できます
先程紹介したように yield を使うこともできます

class Test
  def initialize(&block)
    @block = block
  end

  def my_call(val)
    @block.call(val)
  end
end

my_lambda = lambda do |val|
  puts val
end

t = Test.new(&my_lambda)
t.my_call('hello')

initialize に Proc オブジェクトを渡す

応用テクニックの一つとして initialize に Proc オブジェクトを渡すことでインスタンス変数の初期化などを行うこともできます

class Test
  attr_accessor :val1, :val2

  def initialize(&block)
    block.call(self)
  end

  def print()
    puts @val1
    puts @val2
  end
end

t = Test.new do |obj|
  obj.val1 = 'val1'
  obj.val2 = 'val2'
end
t.print

クラス側ではブロックで初期化されるであろうインスタンス変数を attr_accessor で事前に定義しておく必要がります
また initialize 内では引数として受け取った Proc オブジェクトに self を与えることで自信のクラスのインスタンス変数を参照できるようにします

initialize に引数をわざわざ追加する必要がなくなるのでコードの修正が小さくなるのがメリットかなと思います

最後に

lambda と Proc オブジェクトの基本的な使い方を紹介しました
これ以外にもスコープの話などのあるので詳しくは参考サイトにある公式のドキュメントが参考になると思います

参考サイト

2020年10月6日火曜日

Sinatra の CustomLogger を使ってロギングに fluent-logger を使ってみる

概要

Sinatra アプリのロギングに fluent-logger を使ってみました
CustomLogger を使うだけなので簡単にできます 

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • fluent-logger 0.9.0
    • sinatra 2.0.7

通常のロギング

まず通常のロギング方法です
logger を require して使うだけなので簡単です

require 'sinatra/base'
require 'logger'

class MyApp < Sinatra::Base

  configure do
    logger = Logger.new(STDOUT)
    logger.level = Logger::DEBUG
    set :logger, logger
  end

  get '/test' do
    logger.info 'ok'
    'ok'
  end

end

CustomLogger を使う

次に CustomLogger を使って fluent-logger を設定してみます
level と info というメソッドが fluent-logger にはありません
level は削除し info は post というメソッドに置き換えます
また post メソッドはタグ情報+ハッシュでロギングするようにします

require 'sinatra/base'
require 'logger'
require 'fluent-logger'
require 'sinatra/custom_logger'

class MyApp < Sinatra::Base
  helpers Sinatra::CustomLogger

  configure do
    logger = Fluent::Logger::FluentLogger.new(nil, :host => 'localhost', :port => 24224)
    set :logger, logger
  end

  get '/test' do
    logger.post('myapp.access', {'msg' => 'ok'})
    'ok'
  end

end

動作確認

受け取り側の fluentd を起動します
今回は docker で起動します

  • vim fluent.conf
<source>
  @type forward
</source>

<match *.*>
  @type stdout
</match>
  • docker run --name fluentd -d -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf -p 24224:24224 fluentd -c /fluentd/etc/fluent.conf

ログが表示されるか準備しておきます

  • docker logs -f fluentd

あとはアプリを起動してエンドポイントにアクセスします

  • bundle exec rackup config.ru
  • curl localhost:9292/test

ちゃんとアプリのログが fluentd 側に転送されているのが確認できると思います

最後に

Sinatra の Custom::Logger を使ってロガーを fluent-logger にしてみました
特定のログだけ直接 fluentd に飛ばすこともできるかなと思います

ちなみに Sinatra のデフォルトの logger はすべて標準エラーに吐かれるので logger.info を標準出力に出したい場合は CustomLogger を使うのが必須になります

参考サイト

2020年10月5日月曜日

fluent-logger を使って Ruby のログを直接ログサーバに転送する

概要

通常はログファイルに出力してそれを fluentd で処理して転送します
それでもいいのですが Ruby でアプリを書いている場合は fluent-logger を使うと普通のログを出力する感じでログを直接転送することができます
今回は簡単な使い方を紹介します

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • fluent-logger 0.9.0

インストール

  • bundle init
  • vim Gemfile
gem "fluent-logger"
  • bundle install

簡単なサンプル

とりあえずログを簡単なログを送るサンプルを作成します

  • vim app.rb
require 'fluent-logger'

log = Fluent::Logger::FluentLogger.new(nil, :host => 'localhost', :port => 24224)
unless log.post("myapp.access", {"agent" => "foo"})
  p log.last_error
end

myapp.access というタグで {"agent" => "foo"} という JSON のログを送ります
もしエラーになった場合は標準出力にエラーの内容を表示させます

動作確認: ログを受ける fluentd を立ち上げる

app.rb から送信されたログを受け取る fluentd を立ち上げます
今回は単純に受け取ってそれを標準出力に書き出すだけにします
すでにある場合はそれを使っても OK です
その場合は app.rb にある host や port の情報を書き換えましょう

  • vim fluent.conf
<source>
  @type forward
</source>

<match *.*>
  @type stdout
</match>
  • docker run --name fluentd -d -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf -p 24224:24224 fluentd -c /fluentd/etc/fluent.conf

これで起動します
以下のコマンドでログが来るか待機しておきましょう

  • docker logs -f fluentd

動作確認: 転送されているか確認する

あとはアプリを起動してログが表示されるか確認します

  • bundle exec ruby app.rb
2020-10-05 00:34:38.000000000 +0000 myapp.access: {"agent":"foo"}

こんな感じでログが表示されれば OK です

Tips: 転送先の fluentd が止まっている場合は

以下のようなログがアプリ側の標準出力に表示されると思います

E, [2020-10-05T09:36:01.091357 #11569] ERROR – : Failed to connect fluentd: Connection refused - connect(2) for "localhost" port 24224 E, [2020-10-05T09:36:01.091423 #11569] ERROR – : Connection will be retried. #<Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 24224> E, [2020-10-05T09:36:01.092207 #11569] ERROR – : FluentLogger: Can't send logs to localhost:24224: Connection refused - connect(2) for "localhost" port 24224

最後に

サクッと fluent-logger を試してみました
とりあえず使う分には簡単に使えそうです
エラーの場合にはログファイルに出力するなども簡単にできると思います
気になるのは負荷的な面かなと思います
ログを出力するたびに通信が発生しそうなのでログを大量に出力するアプリだとアプリがハングしないか気になりました

また Sinatra などのカスタムロガーにこれを使えば Web アプリのログも簡単に転送にできるようになりそうです
次回はそのあたりを検証してみようかなと思います

参考サイト

2020年10月2日金曜日

Sinatra で Markdown テンプレートを使う方法

概要

rdiscount という Markdown プロセッサを使います
Sinatra に sinatra-rdiscount というエクステンションがあるのでこれを使います

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • sinatra 2.0.7

準備

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "sinatra-rdiscount"
  • bundle install

サンプルアプリ

テンプレートを表示するのに markdown という命令を使います
views/index.md はあとで紹介します

  • vim app.rb
require 'sinatra/base'
require 'sinatra/rdiscount'

class MyApp < Sinatra::Base
  get '/test' do
    markdown :index
  end
end
  • vim config.ru
require './app'
run MyApp

Markdown ファイル

普通の Markdown ファイルです
テーブルも使えました
基本的には discount が対応しているフォーマットは使えるので詳しくは discount のドキュメント を御覧ください

  • vim views/index.md
# Markdown Tests

## List
* Name
  * Hawksnowlog
* Age
  * 10

## Link
[blog](https://hawksnowlog.blogspot.com/)

## Table

| A | B |
|---|---|
| C | D |

## Horizen

Top
***
Bottom

あとは起動して確認すれば OK です

  • bundle exec rackup config.ru

スタイルを当てるには

layout_engine を指定します

  • vim app.rb
require 'sinatra/base'
require 'sinatra/rdiscount'

class MyApp < Sinatra::Base
  get '/test' do
    markdown :index, :layout_engine => :erb
  end
end

今回はレイアウトに erb を使ってコンテンツの本体は Markdown を想定しています
コンテンツ本体を呼び出す場合には yield をコールします

  • view views/layout.erb
<html>
<head>
  <title>test</title>
</head>
<body>
<%= yield %>
</body>
</html>

あとはここで CSS や JS などの読み込みをすれば OK です

最後に

Sinatra で Markdown テンプレートを使って Markdown に記載された情報を HTML で返却する方法を紹介しました
Jekyll のような感じのことができるようになると思います

変換部分の本体は rdiscount -> discount というネイティブエクステンションを使っているので一応念頭に入れておきましょう

参考サイト

2020年10月1日木曜日

Sinatra で Rack::Lint の挙動を確認してみる

概要

Sinatra では Rack::Lint がデフォルトで使われています
今回はどのような感じで Rack::Lint が使われているか確認してみました

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • sinatra 2.0.7

そもそも Rack::Lint とは

レスポンスを返却する際に正しい形式でレスポンスが作成されているかチェックしてくれるミドルウェアです
例えばステータスコードがおかしな数字になっている場合などに自動でチェックしてくれます

config.ru

require './app'
run MyApp

status コードは 100 以上でないといけない

一番簡単に Rack::Lint のエラーを起動するのはステータスコードかなと思います
例えばステータスコードを 1 のように存在してはいけないコードを返そうとするとエラーが発生します

require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/test' do
    status 1
  end
end

「Rack::Lint::LintError at /test Status must be >=100 seen as integer」こんな感じのエラーが表示されると思います

ステータスコードが 204 のときには Content-Type と Content-Length を自動で削除する

Sinatra は Rack::Lint の check_content_type と check_content_length にひっかからないようにステータスコードを 204 or 304 で返却する場合には自動で削除してくれています

参考: https://www.rubydoc.info/gems/rack/Rack/Lint#check_content_type-instance_method

例えば以下のようなコードを書いてみましょう

require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/test' do
    headers 'Content-Type' => 'text/xml'
    status 204
    body 'ok'
  end
end

これを起動するとわかりますが Content-Type と Content-Length がレスポンスヘッダに含まれていないことが確認できると思います

  • curl -v localhost:9292/test
< HTTP/1.1 204 No Content
< X-Content-Type-Options: nosniff
< Connection: close
< Server: thin

check_env は行っていない?

Rack::Lint には check_env というメソッドがありますがこれを内部で使っている感じはありませんでした
もし自分でチェックで使う場合には以下のように使えます

  • vim config.ru
require './app'

app = MyApp.new
env = nil
Rack::Lint.new(app).check_env(env)

run app

アプリを起動しようとすると「Rack::Lint::LintError: env nil is not a Hash, but NilClass」でエラーになるのが確認できると思います
自分でミドルウェアを作成してそこでチェックしても OK かなと思います

最後に

Sinatra 内での Rack::Lint 挙動について確認してみました
デフォルトで使えるようになっていますが Sinatra 内部で Rack::Lint の機能を使っている部分は少ないようです
Rack::Lint にはいくつかのチェック用のメソッドがあるのでそれらを使って自分でミドルウェアを作成してチェックするのを想定している感じかなと思います