blog.tkeo.info

prometheus, blackbox_exporter, komachi_heartbeat

最近Prometheusの導入に向けて検証中。 今日は外形監視の設定を試した。

外形監視にはblackbox_exporterを使う。 komachi_heartbeatを導入しているので、レスポンスとしてheartbeat:okが返ってくるかチェックできればよい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# prometheus.yml
scrape_configs:
  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [http_komachi_heartbeat]
      path: ['/path/to/heartbeat']
    static_configs:
      - targets:
        - example.com
    relabel_configs:
      - source_labels: [__address__, __param_path]
        regex: (.*);(.*)
        target_label: __param_target
        replacement: http://${1}${2}
      - source_labels: []
        regex: .*
        target_label: __address__
        replacement: 127.0.0.1:9115  # blackbox_exporterが動いているIPアドレスにする
1
2
3
4
5
6
7
8
# blackbox.yml
modules:
  http_komachi_heartbeat:
    prober: http
    timeout: 5s
    http:
      fail_if_not_matches_regexp:
      - "heartbeat:ok"

複数あるwebサーバーそれぞれに対してリクエストを投げるのは以下のような感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# prometheus.yml
scrape_configs:
  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [http_komachi_heartbeat]
      path: ['/path/to/heartbeat']
    ec2_sd_configs:
      - region: ap-northeast-1
        port: 9100
    relabel_configs:
      - source_labels: [__meta_ec2_tag_role]
        regex: web
        action: keep
      - source_labels: [__meta_ec2_tag_Name]
        regex: (.*)
        target_label: name
        replacement: ${1}
      - source_labels: [__param_target]
        regex: (.*)
        target_label: instance
        replacement: ${1}
      - source_labels: [__meta_ec2_public_ip, __param_path]
        regex: (.*);(.*)
        target_label: __param_target
        replacement: http://${1}${2}
      - source_labels: []
        regex: .*
        target_label: __address__
        replacement: 127.0.0.1:9115  # blackbox_exporterが動いているIPアドレスにする
1
2
3
4
5
6
7
8
9
10
# blackbox.yml
modules:
  http_komachi_heartbeat:
    prober: http
    timeout: 5s
    http:
      headers:
        Host: example.com
      fail_if_not_matches_regexp:
      - "heartbeat:ok"

監視したいドメインが複数ある場合、ドメインごとにblackbox.ymlの設定を増やしていかないといけないのが微妙かもしれない。

rubyのrefinementsを使ってみたかった

今やってるプロジェクトでrefinementsを使うのが適していそうな場面が出てきたので初めて使ってみることにしたがなんだか微妙な感じになったという日記。

試したバージョンは2.3.3と2.4.0で、このあたりを参考にした。

同じmoduleをincludeしているクラスに対して共通のrefinementsを適用したかったが、2.3ではmoduleに対してrefineは使えないのでrefineブロック内でincludeしてみたが…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module C
end
class A
  include C
end
class B
  include C
end

module Foo
  def foo
    "foo"
  end

  def foobar
    foo + "bar"
  end
end

module R
  refine A do
    include Foo
  end

  refine B do
    include Foo
  end
end

using R
puts A.new.foo    # => foo
puts B.new.foo    # => foo
puts A.new.foobar # => undefined local variable or method `foo' for #<A:0x007fc71782dcb0> (NameError)

どうもそう単純にはいかないらしい。ぐぬぬ。

いろいろこねくり回した結果、思った通りの動きをするコードは一応何通りか書けた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
module C
end
class A
  include C
end
class B
  include C
end

# module_evalで無理やり突っ込むパターン
FooMethods = -> {
  def foo
    "foo"
  end

  def foobar
    foo + "bar"
  end
}

module R
  refine A do
    module_eval do
      FooMethods.call
    end
  end

  refine B do
    module_eval do
      FooMethods.call
    end
  end
end

# module_eval内でrefineしちゃうパターン
module R2
  %w(A B).each do |klass|
    module_eval <<-CODE
      refine #{klass} do
        def foo
          "foo"
        end

        def foobar
          foo + "bar"
        end
      end
    CODE
  end
end

# あきらめたパターン
module R3
  refine Object do
    def foo
      if kind_of?(C)
        "foo"
      else
        raise NoMethodError
      end
    end

    def foobar
      if kind_of?(C)
        foo + "bar"
      else
        raise NoMethodError
      end
    end
  end
end

using R
puts A.new.foo    # => foo
puts B.new.foo    # => foo
puts A.new.foobar # => foobar

うーん…どれもなんかやだな。 もうちょっとうまいやり方はないものか。

rdbtoolsを使ってredisのデータの内訳を調べた

最近redisのデータ量が増えすぎて、スワップを使い始めている状態になってしまった。 メモリ使用量は7.3GBでキーの数は1400万ぐらい。 スケールアップを検討しつつも、余計なデータがないか軽く調査。 以前にもやったことがあるけど忘れるので後のためにメモとして残しておく。

まずrdbファイルを取得する。バックアップのエクスポートで今日のバックアップをS3にエクスポートした。1.8GBぐらい。

ファイルがでかいのでAWS上で作業する。 本番ウェブサーバーのコピーを使って適当に立てたインスタンスにrdbtoolsをインストール。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sudo pip install rdbtools
Downloading/unpacking rdbtools
  Downloading rdbtools-0.1.9.tar.gz
  Running setup.py (path:/tmp/pip_build_root/rdbtools/setup.py) egg_info for package rdbtools
    warning: no files found matching 'README.textile'
Downloading/unpacking redis (from rdbtools)
  Downloading redis-2.10.5-py2.py3-none-any.whl (60kB): 60kB downloaded
Installing collected packages: rdbtools, redis
  Running setup.py install for rdbtools
    warning: no files found matching 'README.textile'
    Installing redis-memory-for-key script to /usr/bin
    Installing redis-profiler script to /usr/bin
    Installing rdb script to /usr/bin
Successfully installed rdbtools redis
Cleaning up…

これでOKなはずだったが・・・

1
2
3
4
5
6
7
8
9
10
11
$ rdb -c memory dump-2017-03-06-0001.rdb -f redis-memory-2017-03-06.csv
Traceback (most recent call last):
  File "/usr/bin/rdb", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 2655, in <module>
    working_set.require(__requires__)
  File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 648, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 546, in resolve
    raise DistributionNotFound(req)
pkg_resources.DistributionNotFound: redis

あれ?

1
2
$ pip list | grep redis
redis (2.10.5)

??

以前やったときはこんなことにはならなかったが???

よく見るとどうやら最新は9日前にリリースされたバージョンで、前回使ったのは古いやつだったらしい。

1
2
$ sudo pip uninstall rdbtools
$ sudo pip install rdbtools==0.1.8

これでとりあえず動いた。pythonのバージョンの問題な気がするが、よくわからんので放置。

CSVの生成には20分ぐらいかかった。 次は出力されたCSVを加工していく。

1
2
3
4
5
6
7
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element
0,string,"user:6665644:ip_address",104,string,13,13
0,string,"user:5568100:logged_in_time",96,string,8,8
0,string,"user:7540191:ip_address",104,string,13,13
0,string,"user:3428265:ip_address",104,string,12,12
0,string,"user:2176673:logged_in_time",96,string,8,8
...

出力されたCSVの中身はこんな感じ。 redis-objects gem使ってるとこういうキーのやつがいっぱいできちゃうよなー。 まとめやすいようにidっぽい部分を雑に置換する。

1
$ cat redis-memory-2017-03-06.csv | ruby -ne '_,_,key,size = $_.split(","); puts [key.gsub(/:\d+/, ":*"), size].join(",")' > tmp.csv
1
2
3
4
5
6
7
key,size_in_bytes
"user:*:ip_address",104
"user:*:logged_in_time",96
"user:*:ip_address",104
"user:*:ip_address",104
"user:*:logged_in_time",96
...

こいつを合計するスクリプトをざっと書いた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat redis-summary.rb
#!/usr/bin/env ruby

count = Hash.new {|h,k| h[k] = 0 }
sum = Hash.new {|h,k| h[k] = 0 }

while line = $stdin.gets
  key,size = line.split(",")
  count[key] += 1
  sum[key] += size.to_i
end

count.keys.each do |key|
  puts [key, count[key], sum[key]].join(",")
end
1
2
3
4
5
6
"user:*:ip_address",5537264,583765440
"user:*:logged_in_time",5538922,531736432
"room:*:ready_members",25817,3654184
"user_deck:*:total_power_cache",548302,57306072
"room:*:status_value",175756,19190496
...

余計なものがたくさんありそうなことはわかったけど、こいつらを消してもまだ足りなさそうな気がするなあ。

追記

定期的に削除する/できるようにする前のデータが残っていたのでまとめて削除した。 実装に手を入れたタイミングで消したつもりだった…。

graph

Octopressデプロイ環境を作り直した

雑なメモ。 会社からでも更新できるように環境を作った。

とりあえずcloneしてsourceブランチに切り替える。

1
2
3
git clone git@github.com:tkeo/tkeo.github.io.git blog
cd blog
git co -t origin/source

もろもろ必要なgemをinstallする。bundle execとか付けたくないので雑にgemsetに突っ込む。

1
2
rbenv gemset init blog
bundle

デプロイするには_deployディレクトリにmasterブランチが必要なのでworktreeを作る。

1
git worktree add _deploy master

これでOK。

fish shellに移行した

学生の頃にzshを使い始めてかれこれ10年以上になるが、2ヶ月ほど前にfishに移行した。 ようやく慣れてきたのでメモを残しておく。

使用しているfishのバージョンは2.4.0で、プラグインなどは入れてない。 oh-my-fishを使うと便利っぽいけど、困ったときに入れたらいいかなと思ってまだ困ってないので使ってない。

config

とりあえず~/.zshrcをベースに~/.config/fish/config.fishを作った。 aliasとかexportとかは大体そのままコピペで動くが、なぜかPATHのexportだけはうまくいかなかったのでsetを使うように変更した。

他はifendで閉じるとか、&&のかわりに; andを使うとか、$( )( )にするとか、気をつけないといけないポイントはいくつかあるけどそのへんは適当に。

補完

サーバーにログインするときはsshrcを使っていて、sshコマンドと同じようにタブでホスト名を補完してほしい。そういうときの設定は以下。

1
complete -c sshrc -w ssh

complete - edit command specific tab-completions

一時的な環境変数

例えばzshの場合だとこんな感じのコマンドを実行することがあるけど

1
RAILS_ENV=test rake db:reset

fishだとこれではダメで、envをつける必要がある。

1
env RAILS_ENV=test rake db:reset

How do I set an environment variable for just one command?

ブレース展開

{ }の中身が展開されるため、展開を抑制したいときはエスケープが必要。

1
git stash drop stash@\{1\}

とか、

1
rails g model foo bar:references\{polymorphic\}

とか。ちょっと面倒。gitの引数の場合はタブ補完でバックスラッシュをちゃんとつけてくれる。

プロセス置換

コマンド実行結果をファイルのように扱うやつ。zshだとこんな感じ。

1
diff <(foo) <(bar)

fishの場合はpsubを使う。

1
diff (foo | psub) (bar | psub)

psub - perform process substitution

履歴

zshのshare_history相当のものが無いため最初は戸惑ったが、普段使っているコマンドのパターンはそれほど多くなかったので履歴が育てば不要だった。

それでもtmuxの別のwindow/paneで少し前に打ったコマンドがほしくなることはたまにあって、そういうときはhistory mergeを実行して履歴を再読込している。

自動でmergeするのを試してみたけどコマンド実行のたびに微妙に待たされてストレスたまるので手動運用のほうがよいと思う。

history - Show and manipulate command history

UTF-16なファイルのgit diffを見たい

gitがUTF-16のファイルをバイナリだと認識してしまって、diffが表示されずに困ったので以下の設定をした。

1
$ git config diff.toutf8.textconv 'nkf -w8'

.gitattributesファイルに以下の一行を書く。

1
*.cs diff=toutf8

これでcsファイルをUTF-8に変換してからdiffを取るようになった。

ググるとiconvを使うのが見つかるけど、UnityアセットのソースコードがUTF-8のものとUTF-16のものが混在しているので、入力をよしなに判別してくれるnkfを使うようにした。

社内LT大会で家電ChatOpsについて話した

IRKitを使って、ChatOpsやってみたよっていう話を先週の社内LT大会で話しました。 ChatOpsしたい、rubotyとhubot使いたいっていうのが目的なので、わざとまわりくどいことをやってます。

EmacsでUnity開発をする

これはドリコム Advent Calendar 2014の10日目の記事です。

9日目は@hiracyさんによるサーバが増えた時にインフラ担当者がやってきたことです。

自己紹介

  • @tkeo
  • 2007年新卒でドリコムに入社、8年目
  • メインの仕事はスマホゲームのサーバサイド(Rails)で、今年からクライアント(Unity)のほうも触り始めた
  • Rails歴 6年、Unity歴 8ヶ月
  • いまの作業比率は サーバ:クライアント = 2:1 ぐらい

今回の話

Unityを始めた当初はMonoDevelopを使っていたのですが、どうにも手になじまず腹が立ったので、普段サーバ側の開発で使っているemacsを使うことにしました。

以下はいろいろハマりながらも、インターネット上の先人の力を借りてまとめた自分の設定です。

環境

  • Mac OS X Mavericks
  • emacs 24.4.1
  • パッケージ管理はcask

csharp-mode

これがなくては始まらないC#用のメジャーモード。

1
2
; cask
(depends-on "csharp-mode")
1
2
3
4
5
6
7
8
9
; init.el
(require 'csharp-mode)
(add-hook 'csharp-mode-hook
          '(lambda ()
             (setq indent-tabs-mode nil)
             (setq c-basic-offset 4)
             (c-set-offset 'substatement-open 0)
             (flycheck-mode 1)
             (omnisharp-mode)))

自動補完

OmniSharpServer

C#のコードをパースしたりいろいろやってくれる(らしい)サーバです。 他のエディタでも使えるらしいです。

monoが必要なのでインストールして、

1
$ brew install mono

あとはREADME通りにビルド。

1
2
3
4
$ git clone https://github.com/nosami/OmniSharpServer.git
$ cd OmniSharpServer
$ git submodule update --init --recursive
$ xbuild

するとOmniSharp/bin/DebugOmniSharp.exeができているはず。 試しに起動してみる。

1
$ mono OmniSharp/bin/Debug/OmniSharp.exe -s /path/to/unity-project.sln

するとずらずらっとLoadingほげほげと出てきて、Solution has finished loadingで起動完了。無事起動が確認できたらCtrl+Cで止めておく(このあとemacsから起動するので)

omnisharp-emacs

OmniSharpServerをemacsから使うためのものです。

1
2
; cask
(depends-on "omnisharp")
1
2
3
; init.el
(require 'omnisharp)
(setq omnisharp-server-executable-path (expand-file-name "/path/to/OmniSharp/bin/Debug/OmniSharp.exe"))

csファイルを開いた時にslnファイルを選べと言われるので、選んであげるとバックグラウンドで起動してくれます。

M-x omnisharp-auto-completeで候補表示、M-x omnisharp-go-to-definitionで定義にジャンプできます。 適当なキーバインドを設定しておくと便利。

文法チェック

flycheck

omnisharp-emacsにcheckerが含まれていて、OmniSharpサーバと連携してチェックをしてくれます。

1
2
; cask
(depends-on "flycheck")
1
2
3
4
; init.el
(require 'flycheck)
(setq flycheck-check-syntax-automatically '(mode-enabled save idle-change))
(setq flycheck-idle-change-delay 2)

emacsclientアプリ化

Unity上でcsファイルをダブルクリックして開きたくなったときのための設定(あまりしないけど)

Unityの設定で外部エディタに指定できるのはMacのアプリ(*.app)だけなので、emacsclientを起動するアプリを作ります。 vim用にやっている人がいたのでその設定を参考にしました。

アプリを作るのにはAutomatorを使います。手順は以下のとおり。

  1. アプリケーションを選択。
  2. アクション>ユーティリティの中にある「AppleScriptを実行」を右にドラッグ&ドロップ。
  3. 下記コードを貼り付けて、appを適当な場所に保存。
1
2
3
on run {input, parameters}
    do shell script "/usr/local/bin/emacsclient -n " & quoted form of POSIX path of input
end run

automator

作ったappをUnity上で指定してあげると、外部エディタとしてemacsが使えるようになります。 あとはserverとして起動するのを忘れずに。

1
2
3
4
; init.el
(require 'server)
(unless (server-running-p)
  (server-start))

リファレンス検索

rubyの開発でも使っているDashを使えるようにします。

dash-at-point

1
(depends-on "dash-at-point")
1
2
3
4
; init.el
(global-set-key (kbd "C-c d") 'dash-at-point)
(global-set-key (kbd "C-c e") 'dash-at-point-with-docset)
(add-to-list 'dash-at-point-mode-alist '(csharp-mode . "cs"))

カーソル位置の単語で検索します。 上記設定ではcsharp-modeのときはキーワード “cs” が設定されたdocsetの中から検索します。

dash

Dash側で画像のようにキーワードを設定しておくと、.NET FrameworkとUnity 3Dのドキュメントを同時に検索できます。

自前docsetを作る

doxygenを使えばDash用のdocsetを作れます。 コードを含むアセットを購入した時に(たまに)やってます。

1
$ brew install doxygen
1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat doxygen.config
GENERATE_DOCSET        = YES
SEARCHENGINE           = NO
DISABLE_INDEX          = YES
GENERATE_TREEVIEW      = NO
GENERATE_LATEX         = NO
GENERATE_HTMLHELP      = YES
RECURSIVE              = YES

PROJECT_NAME           = hogehoge
OUTPUT_DIRECTORY       = output
INPUT                  = path/to/Scripts
DOCSET_BUNDLE_ID       = hogehoge

こんな感じの設定ファイルを作って、まずはHTMLドキュメントを生成。

1
$ doxygen doxygen.config

htmlディレクトリに移動してmakeをたたくとdocsetが作られます。

1
2
3
$ cd output/html
$ make
$ open hogehoge.docset

その他

日本語を含むときはBOM付きで保存しないと化けます。C-x RET futf-8-with-signatureに変更。

まとめ

以上、MonoDevelopを使わずにemacsでUnity開発するための設定を紹介しました。

明日はみっきーさんです。

参考リンク

ブログ引っ越し

hatenablogから引っ越してきた。 octopressお手軽でいい感じ。

zshだとrake new_postのときにglob展開しようとして怒られるので、noglobをつける。

1
2
3
4
5
$ rake new_post['hoge']
zsh: no matches found: new_post[hoge]
$ noglob rake new_post['hoge']
mkdir -p source/_posts
Creating new post: source/_posts/2014-12-09-hoge.markdown

面倒なのでaliasを書いてしまった。

1
alias rake='noglob rake'

日記

railsアプリ開発中にmigrationでエラーが起こって、一部だけ変更が適用された戻すのがめんどくさい中途半端な状態になることがよくあって今日もうぎゃーってなったので、トランザクションで囲んでくれるような仕組みないのかなと思って調べてみると

http://guides.rubyonrails.org/migrations.html

On databases that support transactions with statements that change the schema (such as PostgreSQL or SQLite3), migrations are wrapped in a transaction. If the database does not support this (for example MySQL) then when a migration fails the parts of it that succeeded will not be rolled back. You will have to rollback the changes that were made by hand.

なんだってー

postgresはデフォルトで対応しているが、mysqlはサポートされてなくて非対応。知らなかった…。

一応mysqlのドキュメントにもあたってみる。

http://dev.mysql.com/doc/refman/5.6/en/cannot-roll-back.html

Some statements cannot be rolled back. In general, these include data definition language (DDL) statements, such as those that create or drop databases, those that create, drop, or alter tables or stored routines.

DDLはロールバックできない、と。

開発環境だけDBサーバ変えるとかしたくないし、どうしたらいいかなあ。

という日記。

追記

change_tablebulk: trueオプションを付けてあげればいいらしい。 ひとつのALTERでまとめてカラム追加・削除されるので中途半端な状態は作られない。