fluentd pluginを作ってみて

最近elasticsearchやらfluentdやらのことについて勉強していて、理解が浅いことに気づいたので、とりあえずfluentdのpluginを作ってみた。

fluent-plugin-nested-hash-filter | RubyGems.org | your community gem host

作ってみて気づいたことをメモ的にまとめておく。間違っていることもおおいにあるだろう。

テストはtest-unit

rspecも使えるらしい記事はいろいろ出ているけど、guardを併用するとなんか色々めんどくさい(というか諦めた)。

その煩わしさを味合うのは、rspecを使って気持よく開発をする目的に反するきがしたので、test-unitを使うことにした。

そもそもfluentdがベーシックにサポートしているのがtest-unitだから、まぁいいんじゃないか、と。

普通のrubygemsとは異なるディレクトリ構造

普通なら、lib/{gem_name}.rbを作ってメインでrequireさせるソースを用意して、追加のライブラリはさらにlib/{gem_name}/...につくってくと思う。

fluentd用のソースは、基本がlib/fluent/plugin/の下に作っていく。一つのプラグインでinputとoutputをそれぞれ提供するなら、lib/fluent/plugin/の下に2つ(以上)のソースファイルができることになる。

名前空間を切っておこうとか、あまり考えないほうが楽そう。

fluentdに登録するプラグインのtype

登録するプラグインの種類と、typeの名前、そのソースファイル名は連動している。

https://github.com/fluent/fluentd/blob/master/lib/fluent/plugin.rb

   def try_load_plugin(name, type)
      case name
      when 'input'
        path = "fluent/plugin/in_#{type}"
      when 'output'
        path = "fluent/plugin/out_#{type}"
      when 'filter'
        path = "fluent/plugin/filter_#{type}"
      when 'buffer'
        path = "fluent/plugin/buf_#{type}"
      else
        return
      end
   end

そこんとこ自分は、ドキュメントで読み飛ばしていたかもしれなくて、プラグインが読み込まれないと言って、時間を使ってしまった。

outputプラグインをfilter的に使う場合

タグの名前を変更できるようにするなり、何か工夫しないと、タグの名前が常に同一になるため、ループしてしまう。

今回は、タグ名にプレフィックスをつけてもらうようにしてみた。

Vagrantを使って検証環境を作る

VagrantVirtualBoxだったりEC2だったりをセットアップから起動まで簡単(?)にできるっぽいのでやってみた。

途中ハマりまくったので、徒然にメモを残しておくレベルでご勘弁。

リポジトリ

https://github.com/sugilog/vagrant.local

環境

クライアント: Mac OSX Yosemite 10.10.3

VirtualBox

Vagrant

  • v1.7.2
  • .dmgからセットアップ。
  • provision関連はドキュメントを参照。
  • Plugins
  • vagrant-berkshelfはとりあえず使わない。

Chef-DK

  • v0.4.0
  • Chefを使うのにこちらのほうが便利そうだったので。
  • Berkshelfもこみこみ。

EC2

手順

環境に描き上げたパッケージは予めインストール。

作業用のディレクトリを作成する。

metadata.rbを作成する。

  • Berkshelfをinitするときに必要。
  • 最低限必要なのはname 'NAME'のみ。

berks initする。

Berksfileに必要なcookbooksを追加してインストール。

  • berks vendor DIRNAME
  • chef.ioでレシピを探す。
  • 個人で使用するくらいなら、バージョンの指定は省略して良さそう。

Vagrantfileに設定を書き込む。

  • VirtualBox向けの設定
  • EC2向けの設定。
    • dummy boxの指定。
      • urlはhttps://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
    • アクセスキーなどのEC2へのアクセスデータ
    • リージョンやサブネットなど設定周りのID。
    • Chefの設定
    • Provisioningするときのシェルスクリプト的な。
  • VirtualBoxとEC2向けの設定はうまく設定すれば共通化できる。
    • VirtualBoxでセットアップをテストしておけば、EC2でのセットアップを必要最小限にできる。
  • Provisioningについて
    • rootユーザーでなく実際に作業するときのユーザー向けの設定をする場合は、privileged: falseにするのを忘れずに。
  • EC2向けのアクセスキーなどの管理。
    • バージョン管理の対象から外したファイルに設定を書き込んでおいた。
    • 本当はアクセスキーのファイル管理は危ないので、環境変数からの読み込みがいいか。
  • うまく行かなかったレシピ。
    • vim
    • mysql
    • redis
    • あとで手動で入れればいいのでスルー。

vagrant up

  • まずはVirtualBox向けに。
    • Chefでうまくいかないものはどんどん外したほうが良さそう。
  • AWSはマネジメントコンソールを開きっぱなしにして、変なリージョンにインスタンスが立ち上がってないかとか、確認しながらが吉。

Vagrantの後

個人利用のみなので、いろいろ手動ですがご容赦を。

参考

先に、参考にした記事とかを。

Heroku via Dropbox; Part 2

HerokuのDropbox Syncが終了するとのこと。おせわになりました。

devcenter.heroku.com

Support for Dropbox Sync will end on 24 July 2018.


前回からの続き

Heroku via Dropbox - sugilogのブログ

やること

手順

好きなテンプレートを探す。

たとえば、Start Bootstrapで探してみる。

今回は、ここのGrayscaleなテンプレートを使います。(Download!)

ファイル設置

ダウンロードして展開したファイルの中身をDropboxディレクトリ(Herokuのサイト向けのディレクトリ)につっこむ。

lessとか本当はいらないけど、とりあえずは細かいことは気にしない。

f:id:sugilog:20150211232022p:plain

デプロイ

Herokuの管理画面からデプロイ!

f:id:sugilog:20150211232100p:plain

確認してみます。

f:id:sugilog:20150211232125p:plain

↓↓↓

f:id:sugilog:20150211232445p:plain

うまくいかない場合

デプロイして、失敗する時がある。

  • 今やっている方法だと、Herokuにphpで書かれたアプリケーションとして認識されているのだけど、phpファイルが無いと思われる(らしい)。
  • そういうときは、phpファイルに空文字を足すとかして編集保存して、わざと変更があった状態にしておくと、うまくいくかもしれない。

f:id:sugilog:20150211232353p:plain

Heroku via Dropbox

HerokuのDropbox Syncが終了するとのこと。おせわになりました。

devcenter.heroku.com

Support for Dropbox Sync will end on 24 July 2018.


Herokuのデプロイ方法に、Dropbox Syncが追加されていたらしいので、試してみる。

とりあえず、できるだけ簡単に、HTMLを設置して表示することを目指す。

前提

  • Dropboxアカウントを持っている。
    • なければ作ってください。
  • Herokuアカウントを持っている。
    • なければ作ってください。
  • なんか、シングルページのHTMLで、何かしら公開したい人。
    • プログラムを組む気はないし、Facebookとかで公開してればいいし、ぐらいな人。
  • HTMLを少しいじれる人。

Gitを使わなくて良くなるので、簡単なことはHerokuで、無料でできちゃう、とか思ってます。

手順

Herokuで新しくAppを作成する。

App = 新しいサイト、ということで、まずは設置場所をつくる。

ログインをして

f:id:sugilog:20150211192538p:plain

右上の「+」のリンクをクリックして

f:id:sugilog:20150211192548p:plain

Appの名前(サイト名っぽいのん)を入力する。

ここで入力した内容は、デフォルトでのサブドメインになる。

Dropboxと連携する。

予め連携したいDropboxのウェブサイトにログインしておくと良いです。

「Deploy」タブで「Dropbox」を選んで

f:id:sugilog:20150211192554p:plain

「Connect to Dropbox」な感じで進んで

f:id:sugilog:20150211192546p:plain

連携を承認して

f:id:sugilog:20150211192555p:plain

おわりです。

Dropboxをみると、Dropbox > Apps > Heroku > (入力したApp名)ディレクトリが作られる。

f:id:sugilog:20150211192545p:plain

ファイル設置

ここだけ、自分のパソコンで作業。

f:id:sugilog:20150211192551p:plain

ここでは、index.htmlindex.phpの2つのファイルを設定してみます。 Dropbox > Apps > Heroku > (入力したApp名)ディレクトリの下に置く感じ。

HerokuにDropbox経由でStaticなファイルを設置してみる

デプロイ

HerokuのDropbox Syncは自動的にデプロイされるわけではないので、Herokuの管理画面から作業が必要です。

  • おかげで、Dropboxにファイルを設置したまま、作業ができて便利。

「Deploy」タブの「Dropbox」から

f:id:sugilog:20150211192549p:plain

「Deploy Changes」のメッセージに変更内容の要約を入力して

f:id:sugilog:20150211192552p:plain

「Deploy」押して、少し待つと完了。

f:id:sugilog:20150211192543p:plain

表示されました。

Go言語:Channelsのselect

select の話

  • selectステートメントで、複数のChannelsを条件分岐できる。
    • 受信、送信、どちらも可能。
    • Channelsの送信があるのに、受け取りを行ってなかったら、selectをしていない場合と同じで例外になるので注意。
    • 複数条件に一致する場合は、ランダムに選択される。
  • selectの中のいずれにも該当しない場合の条件をdefaultで指定可能。
    • Channelsに限定した話ではない。注意。

試してみた

Channelsのselect

実行結果

start? 1
0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
11 89
12 144
13 233
14 377
15 610
16 987
17 1597
18 2584
19 4181
quit
0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
11 89
12 144
13 233
14 377
15 610
16 987
17 1597
18 2584
19 4181
quit

コード中のコメントアウトしているdefault内の行を解くと、演算実行の間(繰り返されている間)に、何度もdefaultが呼び出されていることがわかる。

Go言語:数値型の幅

フィボナッチなサンプルを写経して動かしていた時にやっと理解した。

Go言語の数値型(の整数)

  • intはジェネリックであり、実行環境に依存したサイズ(値の扱える範囲)を持つ。
  • 範囲を超えた場合は、オーバーフローを起こす。
    • 数値演算を行った結果、扱える範囲の最小または最大を超える。
  • オーバーフローが発生しても例外は発生しない。
    • ただし、リテラルの場合は、例外が発生する。
    • わかりきったオーバーフローのためと推測。
  • オーバーフローしたら破棄され、ラップアラウンドが発生する。
    • 扱える範囲の最大値に達した後に、最小値に戻る。
    • 扱える範囲の最小値に達した後に、最大値に戻る。
  • ラップアラウンドが発生するため、x < x + 1が常に成り立つとは限らない。

  • ラップアラウンドを発生させる使用であるため、暗黙的な型変換は発生しない。

なぜGoには暗黙的な数値変換がないのですか?

C言語における数値型での自動型変換の利便性は過剰であり、混乱を招く原因となっています。式が符号無しなのはいつか? 値の大きさはどの位? オーバーフローする? 型変換の結果はポータブルで実行されるマシンに非依存? これはコンパイラも複雑にします。「通常の算術変換」の実装は簡単ではなく、アーキテクチャ間で一貫性を保つことも簡単ではありません。私たちは移植性の理由から、コード中で明示的に型変換をする労力を払うことによって、明確で解り易くする決断をしました。それでも、Go言語での定数の定義(符号およびサイズのアノテーションの要らない任意精度の値)は問題をかなり改善しています。

これと関連しますが、C言語とは違い、仮にintが64ビットだったとしても、intとint64は明確に別の型となります。int型はジェネリックです。整数が何ビットあるのかを気にしたい場合、Go言語では明示的に指定することが推奨されます。

引用:プログラミング言語Goドキュメント 言語設計

試してみる

実行環境は64bitマシン

整数のオーバーフローとラップアラウンド

実行結果

overflowIntWithLiteral
9223372036854775807 + 0 => 9223372036854775807
overflowInt
9223372036854775807 + 0 => 9223372036854775807
9223372036854775807 + 1 => 9223372036854775806
overflowUint8Max
255 + 0 => 255
255 + 1 => 254
overflowUint8Min
0 - 0 => 0
0 - 1 => 255

コード中のコメントアウトを解いて実行すると、

# command-line-arguments
./overflow.go:21: constant 9223372036854775808 overflows int

Rubyで慣れきってて、めちゃくちゃに大きい数値を扱うことが少ないと、下手にラップアラウンドを罠として踏みそう。

大事なのは、明示的にプログラミングすることと、そのための想定。

Go言語:Channesの制限

Channelsの話

  • Channelsはバッファにすることができる。
    • 容量を指定することが可能。
    • make関数の第2引数に指定。
  • caplenの実行が可能。
    • 容量cap以上にはバッファ(値の送信)できない。ブロックする。
    • バッファサイズlen以上には取り出し(値の受信)できない。
  • 送信する値がなくなったら、Channelsを閉じることができる。
    • 受信側で閉じない。
    • 閉じることは必須ではない。
  • Channelsの受け取り側で、閉じられているかどうかがわかる。
    • 受信のチャネルオペレータの2番目のパラメータ(boolean)がcloseかどうかがわかる。
    • 閉じられている場合、バッファサイズ以上の取り出し(値の受信)が可能。
    • 受信される値は型のデフォルト値(だと思う)。intなら0。
  • rangeで受信すると、閉じられるまで繰り返される。
    • rangeを使う場合は、必ず閉じなければいけない。
    • 実行時エラーになる。

書いてみた1

バッファ

出力

before send
0
2
after send
2
2
received value
1
2
after receive
0
2

コメントアウトを解いてみると、fatal errorが発生する。

  • 容量以上に送信しようとした時。
  • バッファサイズ以上に受信しようとした時。
  • コンパイル時にエラーにはならないため、実行時エラーとなる。
  • エラーの内容から、送信か受信かがわかる。
all goroutines are asleep - deadlock!

goroutine N [chan send]:
fatal error: all goroutines are asleep - deadlock!

goroutine N [chan receive]:

書いてみた2

Channelsのcloseとrange

range

0
1
1
2
3
5
8
13
21
34

rangeじゃない

0 true
1 true
1 true
2 true
3 true
5 true
8 true
13 true
21 true
34 true
0 false

rangeでcloseしなかった場合。

  • 実行時エラーで以下のようなエラーが出る。
  • エラーするのは、rangeの行。
fatal error: all goroutines are asleep - deadlock!

goroutine N [chan receive]:

rangeを使わない受信でcloseしなかった場合。

  • 書いてみた1で記述したとおり、実行時エラーする。