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 にはいくつかのチェック用のメソッドがあるのでそれらを使って自分でミドルウェアを作成してチェックするのを想定している感じかなと思います

2020年9月30日水曜日

Gitlab のバックアップデータを自動で S3 に保存する方法

概要

過去に gitlab のデータをバックアップする方法を紹介しました
今回はバックアップしたデータを自動で S3 にアップロードする方法を紹介します
また gitlab のバージョンも 13 系になったのでコマンドが変わったのでそのあたりも紹介します

環境

  • GitLab Enterprise Edition 13.3.5-ee

フルバックアップコマンド

  • gitlab-backup create

これですべてのデータベースやリポジトリのバックアップが取得できます
ビルドの成果物や git lfs のファイルも含まれるのでファイルがかなり大きくなるケースがあるので注意しましょう
デフォルトだとバックアップファイルは /var/opt/gitlab/backups/ に保存されます

ただこのコマンドにはセンシティブの設定ファイルやシークレットファイルが含まれていません

gitlab.rb と gitlab-secrets.json をバックアップする

実際に別サーバに移行するとなった場合にはこれらのファイルも必要です
先程の gitlab-backup create コマンドにはこれらは含まれません
なので別途バックアップする必要があります
gitlab 12.3 以上であれば専用のコマンドがありこれで tar ファイルを作成してくれるのでこれを使いましょう

  • gitlab-ctl backup-etc

これで /etc/gitlab/config_backup/ に tar ファイルが作成されます

本題: S3 に自動アップロードする方法

本題の自動アップロード方法です
gitlab.rb に S3 のバケット名と認証情報を設定するだけです
なので事前にバケットの作成と認証情報の確認をしておきましょう

  • vim /etc/gitlab/gitlab.rb
gitlab_rails['backup_upload_connection'] = {
  'provider' => 'AWS',
  'region' => 'us-east-2',
  'aws_access_key_id' => 'AKIxxxxxxxxxxxxxxxxxxx',
  'aws_secret_access_key' => 'xxxxxxxxxxxxxxxxxxxxx',
  # # If IAM profile use is enabled, remove aws_access_key_id and aws_secret_access_key
  'use_iam_profile' => false
}
gitlab_rails['backup_upload_remote_directory'] = 'your-bucket-name'

設定できたら reconfigure を実行します

  • gitlab-ctl reconfigure

再度バックアップを実行して動作確認

では再度バックアップを実行してどうなるか確認してみます
まずフルバックアップしてみましょう

  • gitlab-backup create

Uploading backup archive to remote storage ... というログが表示されるようになると思います

バケット側を確認するとちゃんと tar ファイルがアップロードされていることも確認できると思います

一応ローカル側にも tar ファイルは残っているようです
/var/opt/gitlab/backups/1601361216_2020_09_29_13.3.5-ee_gitlab_backup.tar
不要な場合は削除しましょう

backup_keep_time

どうやら gitlab.rb の backup_keep_time が経過したらローカル側は自動で削除してくれるようです
試しに 1 秒にしてみたらすぐにローカルの tar ファイルは削除されました

gitlab_rails['backup_keep_time'] = 1

Excon::Error::Socket: Broken pipe (Errno::EPIPE)

自分は S3 のバケットがリージョン名が違っていたために発生しました
基本的にこのエラーが出た場合は gitlab.rb を見直して認証情報やリージョン情報など S3 の設定が間違っていないか確認しましょう

設定ファイルはどうなるか

次に設定ファイルのバックコマンドを実行して S3 にアップロードされるか確認してみました

  • gitlab-ctl backup-etc

こちらは S3 にアップロードされることはなくローカルに保存されるだけでした
おそらくセキュリティ的な面と /etc/gitlab 配下のサイズが大きくならないので、わざわざ S3 にアップロードする必要がないという判断なのかなと思います

最後に

gitlab のバックアップファイルを S3 に自動にアップロードする方法を紹介しました
gitlab.rb に S3 の情報を記載するだけで OK なので簡単にできると思います
また注意する点としては設定ファイルはフルバックアップに含まれないので別途手動でバックアップする必要があるということがわかりました

参考サイト