最近、Dracutを使ってinitrdをカスタマイズしたのでその時のことをまとめます。
DracutはRed Hat系のディストリビューションで使われているinitrdをカスタマイズするためのツールです。
バージョンは041を対象とします。
本題ではないのですが、簡単に説明しておきます。
ここ5年くらいのLinuxだと、こんな感じです。 initrdを作るツールがDracutだったり、initramfs-toolsだったり、本物のinitがsystemdだったり、シェルスクリプトだったりします。
まずはinitrdの中身を確認する方法を紹介します。自分が作ったinitrdが意図通りできているかどうか確認したり、他の人が作ったinitrdを展開して確認したりするために必要です。
ほとんどの場合はgzipで圧縮されたcpioアーカイブなので以下のコマンドでカレントディレクトリに展開することができます。
$ mkdir -p /tmp/initrd
$ cd /tmp/initrd
$ zcat /boot/initrd.img | cpio -idmv
展開すると以下のようなディレクトリ構成になっています。 initrdは仮のrootfsなので、見覚えのある感じではないでしょうか。
$ tree -F -L 1
.
├── bin -> usr/bin/
├── dev/
├── etc/
├── init -> /usr/lib/systemd/systemd
├── lib -> usr/lib/
├── lib64 -> usr/lib64/
├── proc/
├── root/
├── run/
├── sbin -> usr/sbin/
├── shutdown*
├── sys/
├── sysroot/
├── tmp/
├── usr/
└── var/
14 directories, 2 files
Red Hat系ではinitにsystemdを採用しているのでinitが/usr/lib/systemd/systemdへのシンボリックリンクになっています。
このうちDracutに関係するファイルはusr/lib/dracut/以下にまとまっています。
initrdをカスタマイズする目的は、いくつかあります。
Dracutは、シェルスクリプトをモジュール化して管理しています。 initrdの中身では、Dracut関連のファイルはusr/lib/dracut/以下にあり、フックごとにまとまっているためどのモジュールのファイルなのかわかり辛くなっています。
フックの種類は以下の通りです。
Dracutのモジュールを自作すれば、上記のフックを利用して目的に合うrootfsを作ることができます。
Dracutのモジュールは作るだけなら簡単です。
dracutコマンドを実行するホスト上の/usr/lib/dracut/以下に所定のファイルを置くだけです。 例えば、LiveISOを起動するためのモジュールは以下のようになっています。
$ tree /usr/lib/dracut/modules.d/90dmsquash-live/
/usr/lib/dracut/modules.d/90dmsquash-live/
├── apply-live-updates.sh
├── checkisomd5@.service
├── dmsquash-live-genrules.sh
├── dmsquash-live-root.sh
├── dmsquash-liveiso-genrules.sh
├── iso-scan.sh
├── module-setup.sh
├── parse-dmsquash-live.sh
└── parse-iso-scan.sh
このうち必須のファイルは module-setup.sh のみです。 module-setup.sh にモジュールをどのようにインストールするかをルールに従って記述します。
以下の4つの関数を定義すると、dracutコマンドから自動的に実行されます。
詳細はREADME.modulesを参照してください。
モジュールを所定のパスに置いたら、設定ファイルを/etc/dracut.conf.d/以下に置きます。
filesystems+="vfat msdos isofs ext4 xfs btrfs "
add_dracutmodules+=" my-module"
add_drivers+=" sr_mod sd_mod ide-cd cdrom ehci_hcd uhci_hcd ohci_hcd usb_storage usbhid uas "
hostonly="no"
dracut_rescue_image="no"
設定ファイルを用意したらdracutコマンドでinitrdを生成します。
# dracut /boot/initramfs-$(uname -r).img
Dracutの使い方を簡単に紹介しました。
Dracutのモジュールを自作すれば、目的に合ったinitrdを作ることができますが、実際に作るためには様々な知識が必要になります。 例えば、FedoraのLiveISOを起動できるようなDracutのモジュールを自作することを考えると、次のような知識が必要となります。
Dracutのリポジトリに含まれているドキュメントやモジュールを読むだけでも、かなり色々なことがわかるのでDracutのモジュールを自作する必要があるときは、Dracutのソースコードを読むことをおすすめします。
インプットメソッドフレームワークの一つに Fcitx
*1 があります。
複数の入力メソッドをあらかじめ設定しておくことができますが、入力メソッド自体を切り替えるのに比べて、入力メソッドの順番を変更するのはちょっと面倒です。
今回は Fcitx
で「直接入力」する入力メソッドを簡単に切り替える方法を紹介します。
そもそも、「直接入力」って切り替えたりするものなのでしょうか。 通常、一度キーボードレイアウトとそれに対応する日本語のための入力メソッドを設定してしまえば、その後めったに変更することはありません。
切り替えが必要になるのは、レイアウトの異なるキーボードを接続して使う場合です。 典型的なのは、ノートPCにUSBキーボードを接続して使うといったケースでしょう。
例えば、ノートPCは日本語キーボードで、それに接続して使うUSBキーボードが英語キーボードだと、そのままではレイアウトの違いから打てない文字がでてしまい問題になります。 *2
そのため、fcitx-configtool
を使って「直接入力」に用いるレイアウトを日本語から英語に変更します。
あるいは、Fcitx
のアドオンとして提供されている Input method selector
を利用して、グローバル入力メソッドを英語に変更するという回避策もあります。
ただし、その方法には直接入力を再設定する場合と違って落し穴があります。
直接入力を再設定する場合
fcitx-configtool
による変更が必要グローバル入力メソッドを変更する場合
上記の日本語入力(Mozc)に影響するとはどういうことでしょうか。 これは、Mozcがグローバル入力メソッド関係なしに、「既定の入力メソッド」つまり「直接入力」として設定されている入力メソッドを利用することによります。
したがって、グローバル入力メソッドを変更する場合だと、英語キーボードを使っているのに、日本語入力にはなぜか日本語キーボードレイアウトで入力しているという中途半端なことになります。 また、その際には、ローカル入力メソッドをMozcに設定しておかないと、入力メソッドのオンオフが正しく機能しません。 *3
英語キーボードの接続のたびに、fcitx-configtool
でいちいち設定を変更するのは面倒です。
そこで次に紹介するのが、fcitx-imlist
です。
fcitx-configtool
と異なり、コマンドラインで入力メソッドの設定を変更するためのツールです。
パッケージは提供していないので、ソースコードをビルドする必要があります。 ビルドのために、Fcitxの開発用のヘッダファイルが必要なので、そのためのパッケージを事前にインストールします。 *4
% sudo apt-get install build-essential fcitx-libs-dev
インストールできたら、次に fcitx-imlist
をビルドします。
% git clone https://github.com/kenhys/fcitx-imlist
% cd fcitx-imlist
% ./autogen.sh
% ./configure
% make
% sudo make install
これで、 /usr/local/bin/fcitx-imlist
としてコマンドをインストールできました。
fcitx-imlist
はキーボードのユニーク名をもとに、入力メソッドを指定順に変更できます。
キーボードごとのユニーク名については -l
オプションを指定することで取得できます。
% fcitx-imlist -l
fcitx-keyboard-jp (キーボード - 日本語) [enabled]
mozc (Mozc) [enabled]
fcitx-keyboard-us (キーボード - 英語 (US)) [enabled]
入力メソッド | ユニーク名 |
---|---|
キーボード - 日本語 | fcitx-keyboard-jp |
Mozc | mozc |
キーボード - 英語(US) | fcitx-keyboard-us |
日本語キーボードのかわりに、英語キーボードを直接入力のために使うには、 -s
オプションにユニーク名を指定します。 *5
% fcitx-imlist -s us
fcitx-keyboard-us (キーボード - 英語 (US)) [enabled]
fcitx-keyboard-jp (キーボード - 日本語) [enabled]
mozc (Mozc) [enabled]
これで、キーボードに対応したレイアウトを「直接入力」として正しく設定しなおすことができました。
ちなみに、Fcitxの次のメジャーバージョンである Fcitx
5.x *6 では、このあたりが改善されて入力メソッドのグループ化がサポートされる見込み *7 です。
そのため、使用するキーボードに応じてあらかじめ設定したグループを切り替えて使うことになるでしょう。また、グループ自体の切り替えもハードウェアを認識して自動的に行えるようになるらしいです。つまり以下のように、英語用キーボード用のグループと日本語キーボード用の設定をしておけば、あとはいい感じに自動で切り替えてくれるということですね。
グループ | インプットメソッド |
---|---|
英語キーボード用 | fcitx-keyboard-us,mozc |
日本語キーボード用 | fcitx-keyboard-jp,mozc |
Fcitx
で「直接入力」に使用する入力メソッドを簡単に切り替える方法を紹介しました。Fcitx
でレイアウトの異なるキーボードを切り替えて使っている人は参考にしてみてください。
2015-06-06にプログラミングが好きな学生のためのリーダブルコード勉強会を開催しました。この勉強会について、内容を作った立場からどうしてこのような内容にしたのかについて紹介します。また、今回の内容の課題と今後の解決案についてもまとめます。
基本的な流れは昨年開催したリーダブルコード勉強会と同じで次の通りです。
コードをチェンジ(交換)して実装を継続することで「強制的に他の人の書いたコードを読む機会を作る」ことがポイントです。
詳細な流れに興味のある方はGitHubのclear-code/sezemi-2015の中に資料があるので、自由に活用してください。当日は6種類のスライドを使いましたが、それらへのリンクも含んでいます。資料もスライドもライセンスはCreative Commons BY-SA 4.0で、著作者は「株式会社クリアコード」です。
今年と昨年で基本的な部分は変わっていませんが、大きく変わったことがあります。それは次のことです。
昨年はメンター5名で学生30名くらいでやっていましたが、今年はメンター20名超で学生50名くらいでやりました。メンターは単に数が増えただけではありません。優秀度合いは去年同様高いままです。メンターのリストを見れば「学生うらやましい!社会人だけど参加したかった!」と思うことでしょう。
メンターが増えたため、メンターそれぞれにどう振る舞って欲しいかを具体的に伝えることができなくなりました。そのため、今年は方針を共有して後はいい感じに振る舞ってもらうことにしました。共有した方針は「学生さんに新しい視点を与えること」です。答えを教えるのではなく、一緒に考えて、新しい視点を与えて欲しい、とお願いしました。
たとえば、ほとんどの学生さんは「他の人のコードからリーダブルなコードを探して自分のコードに取り入れる」ということを初めて経験します。新しいことに拒否反応を示したり、やることはわかってもうまくできないかもしれません。そんなときは、学生さんから今の気持ちを聞いて考えを整理することを手伝ったり、見方をこう変えてみるとなにか見えてこないか示唆を与えたりして欲しいということです。
難しいところもいろいろあったでしょうが、メンターのフォローにはとても助けられました。
たとえば、コードを読む習慣がない人の場合、プログラミングがうまければうまいほど、他の人のコードからうまくリーダブルなコードを見つけることができません。これは、「自分が書いている書き方がよい」という意識が強くなっているからです。こうなっていると、よほど自分よりうまく書かれていない限りリーダブルなコードを見つけられません。リーダブルなコードだと認識できない、と表現したほうが近いかもしれません。
このようなリーダブルなコードを見つけにくい状態になっている場合のフォローは難しいものです。「このコード、自分もやっているリーダブルな書き方じゃない?」とか、「違う見方で考えるとリーダブルだといえない?」というように、「すぐそばにある野生のリーダブルコード」に気づくきっかけを与えてくれたことでしょう。
リーダブルコードは読む人にとってわかりやすいコードなので、「キラキラしたリーダブルコード」を探そうと思うとかえって見つけられません。リーダブルコードはキラキラしていません。地味です。特別に意識せずともわかってしまうようなコードですから。そのようなコードを見つけて自分のプログラミングに取り入れるためには、「すぐそばにある野生のリーダブルコード」に気づける視点が必要なのです。
今回の勉強会では、学生さんには「すぐそばにある野生のリーダブルコード」に気づけるようになって欲しいと期待していました。そのため、メンターのみなさんにも「新しい視点を与える」という振る舞いをお願いしました。
昨年のSEゼミのテーマは「インターンシップの準備」というテーマ(たぶん)で、リーダブルコード勉強会は「リーダブルコードを押さえておこう」ということで開催しました。
一方、今年のSEゼミのテーマは「OSSの開発に参加しよう!」で、リーダブルコード勉強会は「OSSの開発にリーダブルコードの知識があるとよい」ということで開催しました。
これを受けて、導入での説明も変えました。「他の人のコードを読んでよいところを取り込んで自分のコードに活かす」というのはOSSの開発では当たり前のこと。リーダブルコードを書けるようになるためにも同じやり方が使えるんだから、OSSの開発に参加するならこのやり方でいこう。という方向にしました。
これからOSSの開発に参加する中で、「他の人のコードから学ぶ」というやり方を当たり前の習慣として身につけてくれることを期待して説明を変えました。
今年から「メビュー」を始めました。「レビュー」と似ていますが違います。
「メビュー」は「Mentor's View」の略で「メンターの視点」という気持ちです。
レビューは「問題がないか」という視点でコードを読みますが、メビューは「メンターの視点を共有する」という視点でコードを読みます。
自分だとよいと思っていなかったコードでも、メンターから見ればよいコードだと教えてもらえるかもしれません。リーダブルコードの解説にも次のように書いていますが、最初はヒントなしでよいコードを見つけることは難しいです。熟練したメンターの視点をもらって自分の「よいコードを見つける視点」を養います。それがメビューです。
実際にやるとぶつかること
まず、内容を活かす場所がぜんぜんないって思うだろう。でも、それはあなたの書いたコードがすでにリーダブルだからじゃない。単に「気づかない」からだ。本書を読んでいるときは次々と出てくる小さなコードを読みながら「たしかにここはそう変えた方がいいな。ふむふむ。」なんて読んでいただろう。読みながらあなたが「ふむふむ」言っていられたのは著者がわかりやすく書いてくれていたからだ。でも、実際のコードにはそういうヒントはない。著者のヒントがないんだから、実際のコードを読んでいるときは見逃してしまうだろう。それが自分が書いている真っ最中のコードならなおさらだ。読む側の視点じゃなくて書く側の視点でコードを見ているんだから。
学生さんにはアンケートを書いてもらい、メンターの何人かには直接感想を聞きました。
アンケートを見る限り多くの学生さんにはよい機会となったようです。この勉強会をきっかけにOSSのコードを読んでいこうという気になった人もたくさんいました。
メンターの人の何人かには「楽しかった」と言ってもらえました。楽しくフォローしていたのなら、学生さんにもいろいろ伝わりやすかったことでしょう。
一方、次の点では課題がありました。
昨年もそうでしたが、何人かは開発を始める前のところでつまづきます。不安な人は30分くらい早めに会場に来てもらって、勉強会前に開発を始める準備のところを手伝ってあげるのがよいかもしれません。
この勉強会で実装する課題はファイルの読み込み処理があります。開いたファイルの管理方法、ファイルをどこに配置するか、などで工夫ポイントがでるかもしれないと考えて入れた処理です。しかし、SwiftやWebブラウザー上で動作するJavaScriptなどファイル操作をすることがまれなプログラミング言語では実装の難易度をあげてしまいます。これは意図したことではないのでファイルを使うこと自体やめた方がよさそうです。
途中でコードを交換することはこの勉強会での大事なところですが、交換する相手の実装がほとんど進んでいない場合は読む機会が少なくなってしまいます。その場合はあまり進んでいない人のコードは交換対象とせずに、ある程度進んでいる人のコードを2人以上で使うなどの交換の仕方の方がよさそうです。
他にも、課題から自由度を減らして実装に集中しやすくする、メモのまとめ場所をGitHubのIssueにしてメモをまとめるときの敷居をさげる、といったことも検討しています。
昨年に引き続き開催したSEゼミのリーダブルコード勉強会の内容について、昨年の内容と比べながら紹介しました。また、実際に開催したわかった課題とその解決策もまとめました。
今年のSEゼミのイベントはまだ3つあります。引き続き優秀なメンターが多数参加するので、OSSの開発に参加したそうな学生さんに教えてあげてください。
今回の勉強会に参加した学生さん・メンターの感想に興味のある方は次のWebページを見てみてください。
今回の勉強会での成果に興味のある方は次のWebページを見てみてください。
Droongaプロジェクトの結城です。
Droongaプロジェクトは現在の所「レプリケーション機能があるGroonga互換の全文検索システム」を当座の目標としていますが、目標達成のためには不足している機能の実装だけでなく、「安定して動作する」という運用実績を作る必要もあります。そこで、デモンストレーションも兼ねて、GroongaのパッケージサーバがあるConoHaのクラウド上でDroongaクラスタを運用してみています。
このDroongaクラスタの全体像は、以下の図の通りです。
実際にhttp://157.7.124.247:80/にアクセスすると、Groonga AdminによるUIを経由して、動作中のDroongaクラスタのデータベースの内容を見られます。
このクラスタには現在の所、以下のデータが継続的に格納されています。
図ではパッケージサーバのログも収集するように描かれていますが、実は今の所はまだ、DroongaクラスタのHTTPサーバ自身のログのみ収集しています。
Droongaは複数ノード構成での運用が前提なので、当然ながら何台ものサーバが必要になります。 そんな時、物理的なサーバよりも簡単に調達でき、自前の仮想マシンよりもはるかに実用的なのが、ConoHaのようなクラウド形式のVPSサービスです。
このエントリでは、上記の運用実験用クラスタの構築手順を振り返りながら、ConoHa上にDroongaクラスタを構築する具体的な手順をご紹介します。同様の構成でDroongaを試してみる際の参考にしてください。
今回やりたかったこととやりたくなかったことは、以下の通りです。
これらを実現するために、次のような方針を定めました。
実際の作業は、以下の要領で進めます。
順番に見ていきましょう。
ConoHaのVPSは最初からグローバルIPアドレスが割り当てられており、そのままWebサービスを公開できるようになっています。 しかし、GroongaもDroongaもHTTPのGETメソッドでデータベースの内容を変更できてしまうので、グローバルIPでの運用はお薦めできません。
幸い、ConoHaでは無料でVPSをローカルネットワークに所属させられますので、DroongaのサービスはローカルネットワークのIPアドレスで起動しておいて、サーバ同士はこのローカルネットワーク内で通信するようにすれば、Droongaクラスタを安全に運用できます。 また、ConoHaではローカルネットワークの方が高速なので、ノード間の通信が多いDroongaでは性能的なメリットも大きいです。
今回の構成では、手順に従って192.168.0.0/24
のプライベートネットワークを作成しました。
同時に既存のパッケージサーバも、ネットワークインターフェースを追加してこのプライベートネットワークに参加させるようにしました。
今後新たにVPSを作成した場合も、Droongaクラスタと連携する必要があればこのネットワークに参加させることになります。
ConoHaでVPSを作成する時は、テンプレートイメージとして「汎用のCentOS 6.5」と「CentOS 6.5にWordPressとnginxをインストールした状態」のどちらかを選べます。 しかしDroongaは現在の所、CentOSについてはCentOS 7へのインストールにのみ対応しており、CentOS 6.5にインストールするのは骨が折れます。
なので、今回はVPSを作成した後で、OSを自分で入れ替えて使うことにしました。
このような使い方をする場合、VPSの作成時のテンプレートイメージはどちらを選んでも構いません。 また、VPS作成時に入力するrootパスワードは最終的には使わないので、パスワード欄には適当な文字列を入力しておいて大丈夫です。
VPSが作成されたら、すぐにシャットダウンします。
その後、手順に従ってネットワークインターフェースを追加して192.168.0.0/24
のローカルネットワークに参加させます。
テンプレートイメージをそのまま使う場合はこの後VPSを起動しますが、OSを入れ替える場合はここから先の手順が違います。 今回は、ディスクイメージからのインストールでOSをUbuntu 14.04LTSにしてみました。手順は以下の通りです。
eth0
(インターネットに繋がっているインターフェース)を選択する。droonga0
とする。Guided - use entire disk
を選択して、/dev/vdb
を選ぶ。(容量が大きい方)Basic Ubuntu server
OpenSSH server
……という手順でUbuntuをインストールしたのですが、その後になってから、インストールイメージを使う方法があるということに気が付きました。 使いたいディストリビューションのバージョンのインストールイメージが提供されているのなら、そちらの方を使ったほうが圧倒的に簡単なのは間違いないので、これはあくまで、インストールイメージが無い場合の進め方という事にしておきます。
Droongaノード用VPSの1台目ができたら、2台目も作成します。
設定やOS入れ替えの手順はほぼ同じですが、今度はホスト名をdroonga1
にします。
VPSを2台用意できたら、分かりやすいようにConoHaの管理コンソール上での表示ラベルもdroonga0
とdroonga1
に変更しておきます。
OS入れ換え直後のVPSはSSHを使ってパスワード認証でリモートログインできる状態になっていますが、これはセキュリティ的に脆弱ですので、より安全な公開鍵認証を強制するように設定しておきます。
公開鍵の登録にはssh-copy-id
コマンドを使います。
手元のPC(Ubuntu)で、以下の要領で入力します。
$ ssh-copy-id -i 公開鍵へのパス ノードのユーザ名@ノードのIPアドレス
今回のDroongaノード用VPS(droonga0
)なら、以下のようになります。
piro@localpc$ ssh-copy-id -i .ssh/id_rsa.pub piro@157.7.124.247
これで鍵認証でSSH接続できるようになったので、早速手元のPCのGNOME端末からVPSに接続してみます。
piro@localpc$ ssh piro@157.7.124.247
piro@droonga0$
以後の操作は手元のPCから行うということで、ConoHaの仮想コンソールは閉じてしまいます。
droonga0
とdroonga1
の/etc/ssh/sshd_config
を変更して、SSHを安全に運用するための定番の設定を行う事にします。
Port 22
をPort (適当な空きポート番号)
に書き換えて、SSH接続に使うポート番号を変更します。
これはセキュリティ的な対策というよりも、外部から22番ポート決め打ちでアタックされた時にログが大量に記録されてしまってウザくないように、という意味合いが主です。PermitRootLogin without-password
をPermitRootLogin no
に書き換えて、リモートからの直接のrootログインを禁止します。
Ubuntuの場合はそもそも普通に操作しているとrootログインする場面はありませんが、念のためです。PasswordAuthentication no
を追加します。
これにより、パスワード認証でのリモートログインが禁止されます。設定を編集したら、sshdを再起動します。
$ sudo service ssh restart
次に、新しいポート番号で鍵認証によって接続できる事を確認します。
GNOME端末上の現在の接続はそのままにして、GNOME端末の新しいタブを開いてからそちらで接続します。
例えば変更後のポート番号が2222
なら、以下の要領です。
piro@localpc$ ssh piro@157.7.124.247 -p 2222
piro@droonga0$
設定を間違えてしまっていてうまくいかない場合は、元のタブの方の接続で設定を修正します。 このように、リモートからのログインに関係する重要な設定を変更する時は、元のセッションを保持したまま作業するとトラブル対応を楽にできます。 元のセッションが切断されてしまった場合は、面倒ですがConoHaの仮想コンソールから操作するほかありません。
この時点では、ローカルネットワーク用のインターフェースであるeth1
にはIPアドレスが割り当てられていません。
ConoHaのローカルネットワークには初期状態ではDHCPサーバがいないからなのですが、そもそもDroongaクラスタのような物を運用する場合はIPアドレスがコロコロ変わる方が面倒なので、ここはDHCPサーバを立てずに固定IPを自分で設定することにしました。
droonga0
とdroonga1
の/etc/network/interfaces
に、以下の要領でeth1
の設定を追加します。
auto eth1
iface eth1 inet static
address 192.168.0.50
netmask 255.255.255.0
network 192.168.0.0
broadcast 192.168.0.255
address
の値はノードごとに変えます。
droonga0
は192.168.0.50
、droonga1
は192.168.0.51
としました。
ちなみに、ConoHaの仕様により、IPアドレスは11
~254
の範囲で設定する必要があります。ネットワークインターフェースの設定ができたら、インターフェースeth1
を有効化します。
$ sudo ifup eth1
Ubuntuならsudo service networking restart
なんじゃないの……?と思われるかも知れませんが、Ubuntu 14.04LTSでは既知の不具合のため、これはエラーになってしまいます。
なので、ifup
コマンドを直接使っています。
ともかくこれで、Droongaノード用のVPS同士は互いに192.168.0.50
と192.168.0.51
というIPアドレスで通信できるようになりました。
便利なように、それぞれの/etc/hosts
に以下の行を追加(および、127.0.0.1
への自身のホスト名の割り当てを削除)して、互いに名前で参照できるようにしておきます。
192.168.0.50 droonga0
192.168.0.51 droonga1
ここまで来たら、後はDroongaのチュートリアルにある通りの手順でDroongaノードとしてVPSを設定するだけです。
droonga0$ curl https://raw.githubusercontent.com/droonga/droonga-engine/master/install.sh | \
sudo HOST=droonga0 bash
HOST
の指定はノードごとに変えます。
Droonga Engineがインストールされたら、片方をもう片方にjoin
させて、クラスタを構築します。
droonga1
をdroonga0
のクラスタに参加させるなら、以下の通りです。
droonga0$ droonga-engine-join --host droonga1 --replica-source-host droonga0
Start to join a new node droonga1
to the cluster of droonga0
via droonga0 (this host)
port = 10031
tag = droonga
dataset = Default
Source Cluster ID: 88260971556bc9203087d476c0566c9da0114695
Changing role of the joining node...
Configuring the joining node as a new replica for the cluster...
Registering new node to existing nodes...
Changing role of the source node...
Getting the timestamp of the last processed message in the source node...
The timestamp of the last processed message at the source node: 2015-05-13T05:04:20.317923Z
Setting new node to ignore messages older than the timestamp...
Copying data from the source node...
0% done (maybe 00:00:00 remaining)
Restoring role of the source node...
Restoring role of the joining node...
Done.
これで、2ノードからなるDroongaクラスタができました。
このDroongaクラスタが本当に外部から接続できない安全なクラスタかどうかを、念のため確認しておきます。
チュートリアルの通りにスキーマを定義した状態から、以下のコマンドを実行して、ローカルネットワークのIPアドレス宛にレコードの追加と検索のリクエストを送ってみます。
droonga0$ echo '{"type":"add","body":{"table":"Store","key":"dummy-store0","values":{"name":"Dummy Store 0"}}}' | \
droonga-send --server droonga:192.168.0.50:10031/droonga
droonga0$ droonga-groonga select --table Store --limit -1 --output_columns name --pretty
[
[
0,
1431500987.197475,
1.1444091796875e-05
],
[
[
[
1
],
[
[
"name",
"ShortText"
]
],
[
"Dummy Store 0"
]
]
]
]
select
の結果を見ると、ちゃんとレコードが追加されている事が分かります。
今度は、ローカルネットワーク以外のIPアドレス宛にレコードの追加と検索のリクエストを送ってみます。
droonga0$ echo '{"type":"add","body":{"table":"Store","key":"dummy-store2","values":{"name":"Dummy Store 1"}}}' | \
droonga-send --server droonga:localhost:10031/droonga
E, [2015-05-13T14:24:01.896664 #14333] ERROR -- : Failed to connect fluentd: Connection refused - connect(2)
E, [2015-05-13T14:24:01.896899 #14333] ERROR -- : Connection will be retried.
E, [2015-05-13T14:24:01.908777 #14333] ERROR -- : FluentLogger: Can't send logs to localhost:10031: Connection refused - connect(2)
droonga0$ echo '{"type":"add","body":{"table":"Store","key":"dummy-store2","values":{"name":"Dummy Store 1"}}}' | \
droonga-send --server droonga:127.0.0.1:10031/droonga
E, [2015-05-13T14:24:01.896664 #14333] ERROR -- : Failed to connect fluentd: Connection refused - connect(2)
E, [2015-05-13T14:24:01.896899 #14333] ERROR -- : Connection will be retried.
E, [2015-05-13T14:24:01.908777 #14333] ERROR -- : FluentLogger: Can't send logs to 127.0.0.1:10031: Connection refused - connect(2)
droonga0$ echo '{"type":"add","body":{"table":"Store","key":"dummy-store2","values":{"name":"Dummy Store 1"}}}' | \
droonga-send --server droonga:157.7.124.247:10031/droonga
E, [2015-05-13T14:24:01.896664 #14333] ERROR -- : Failed to connect fluentd: Connection refused - connect(2)
E, [2015-05-13T14:24:01.896899 #14333] ERROR -- : Connection will be retried.
E, [2015-05-13T14:24:01.908777 #14333] ERROR -- : FluentLogger: Can't send logs to 157.7.124.247:10031: Connection refused - connect(2)
$ droonga-groonga select --table Store --limit -1 --output_columns name --pretty
127.0.0.1
(ループバックアドレス)も157.7.124.247
(グローバルIPアドレス)も、指定のポートに接続できないというエラーになっています。
select
による検索はエラーが出ていませんが、これは接続エラーが内部でハンドルされているためで、結果はやはり得られていません。
もう1度192.168.0.50
を指定して検索してみると、当然ながら結果に変化は無く、レコード追加のリクエストは処理されていないことが分かります。
(このような結果になるのは、Droonga Engineのサービスが初期化時に指定されたホスト名およびそれに紐付けられたIPアドレスにのみバインドされているからです。)
ということで、Droongaクラスタがローカルネットワーク向けにのみサービスを提供していることを確認できました。 ちなみに、Droonga Engine同士が互いの死活状態を把握するために使っているSerfのサービスも、同様にローカルネットワーク内でのみ利用できるようになっています。
droonga0$ sudo -u droonga-engine -H ~droonga-engine/droonga/serf -rpc-addr 192.168.0.50:7373
droonga0:10031/droonga 192.168.0.50:7946 alive role=service- provider,type=engine,cluster_id=5531f182f96b4699b55f0aa7cb100a118c73945a,internal-name=droonga0:46944/droonga
droonga0$ sudo -u droonga-engine -H ~droonga-engine/droonga/serf members -rpc-addr localhost:7373
Error connecting to Serf agent: dial tcp 127.0.0.1:7373: connection refused
droonga0$ sudo -u droonga-engine -H ~droonga-engine/droonga/serf members -rpc-addr 127.0.0.1:7373
Error connecting to Serf agent: dial tcp 127.0.0.1:7373: connection refused
droonga0$ sudo -u droonga-engine -H ~droonga-engine/droonga/serf members -rpc-addr 157.7.124.248:7373
Error connecting to Serf agent: dial tcp 157.7.124.248:7373: connection refused
Droonga EngineだけではHTTP接続できないので、Droonga HTTP serverもインストールしておきます。
droonga0$ curl https://raw.githubusercontent.com/droonga/droonga-http-server/master/install.sh | \
sudo HOST=droonga0 ENGINE_HOST=droonga0 bash
HOST
、ENGINE_HOST
の指定はノードごとに変える必要があります。
Droonga HTTP serverをインストールできたら、設定を変えて、サービスを0.0.0.0
ではなくローカルネットワークのIPアドレスにバインドするようにします。
droonga0$ sudo droonga-http-server-configure
Do you want the configuration file "droonga-http-server.yaml" to be regenerated? (y/N): y
IP address to accept requests from clients (0.0.0.0 means "any IP address") [0.0.0.0]: 192.168.0.50
...
enable "trust proxy" configuration (y/N): y
...
上記以外の箇所はすべて既定値のままにします。 これで、Droonga HTTP serverもDroonga Engine同様に外部からのアクセスを受け付けなくなります。
droonga0$ curl http://localhost:10041/engines
curl: (7) Failed to connect to localhost port 10041: Connection refused
droonga0$ curl http://157.7.124.247:10041/droonga/system/status
curl: (7) Failed to connect to 157.7.124.247 port 10041: 接続を拒否されました
ここまでの結果を図にすると、以下のような構成になっているという事になります。
サーバを安全に運用する、という話になるとファイアウォールの設置やiptablesといった話題が出てきがちですが、Droongaノードはこの種の技術との相性が悪いです。 というのも、Droongaはランダムに決まったポート番号でメッセージを受け付ける部分があるため、どのポートを開放するかということを事前に完全には決めきれないのです。 なので、今回の例のように、外界から隔離されたネットワーク内で互いに自由に通信できるDroongaノード同士でクラスタを構成しておき、セキュリティ対策は別のレイヤで講じるのが、Droongaクラスタの推奨運用スタイルということになります。 (言い換えると、複数拠点間をインターネット越しに繋いだDroongaクラスタの構築は非常に面倒という事になります。速度的なデメリットもありますので、そのような構成は非推奨というのが正直な所です。)
さて、安全なDroongaクラスタを構築できたわけですが、このままだとConoHa上のローカルネットワークに属しているVPSからでないとDroongaクラスタに接続できません。 インターネット越しにDroongaクラスタに接続するには、SSHでポートフォワードするなどの工夫が必要になります。 このクラスタはDroongaの運用デモンストレーションに使いたいので、これでは困ります。
そこで、リバースプロキシとしてnginxを設定しました。
リバースプロキシとして設定されたnginxは、グローバルIPアドレスの80番ポートで接続を待ち受けて、受け付けたリクエストのうち安全な物だけをDroonga HTTP serverに引き渡すという動作をします。 これで、Droongaクラスタを意図しない変更から守りつつ、内容をインターネットで公開することができます。
nginxの導入は、Ubuntuであればapt
でインストールして設定を作成するだけで済みます。
Droonga HTTP server用の設定は、Ningx本体の一般的な設定とは別のファイルに分けておくと管理が容易です。
droonga0$ sudo apt-get install nginx
droonga0$ sudo vi /etc/nginx/conf.d/droonga-http-server.conf
/etc/nginx/conf.d/droonga-http-server.conf
の内容は以下の通りです。
upstream droonga-http-server {
server 192.168.0.50:10041;
}
server {
listen 80;
server_name 157.7.124.247;
location ~ ^/($|favicon\.|(js|css|images|scripts|styles|views|[^\.]+\.(html|txt))/|engines|connections|cache|d/(select|table_list|column_list|status|suggest)|droonga/(search|system/status|system/statistics)) {
proxy_pass http://droonga-http-server;
}
}
location
に定義しているのは、この正規表現にマッチするパスのリクエストだけを192.168.0.50
に転送するという指定です。
機能のうち極限られた部分だけが安全で、それ以外は全て危険、という場合にはこのようにホワイトリスト形式でリバースプロキシを設定するのが定石です。
ファイルを編集し終えたら、nginxを再起動します。
droonga0$ sudo service nginx restart
これで、安全なリードオンリーのリクエストのみ、インターネット上からnginxを経由してDroonga HTTP serverに送れるようになりました。
同様の手順でdroonga1
にもnginxを設定しておけば、droonga0
とdroonga1
のどちらもDroongaクラスタへの公開エンドポイントとして利用できるようになります。
ちなみに、この記事の冒頭に記載したリンクも、このリバースプロキシ経由でDroonga HTTP serverに接続するための物です。
これだけだと本当にただ単にDroongaクラスタがあるだけなので、デモンストレーションとしては役に立ちません。
また、継続的にリクエストがある状態にしておかないと、「ちゃんと安定して動作しているかどうか」という検証にもなりません。
そこで、手始めにFluentdを使ってdroonga0
自体のnginxのアクセスログを収集するようにしてみました。
Fluentdでは、いくつかのプラグインを組み合わせて「情報ソースからデータを取得してくる」→「データを加工する」→「データを出力する」という事を行います。 この時、データの出力先として他のノードを指定したり、データの情報ソースとして他のノードからの流入を受け付けたりすることで、複数ノードからの情報を一箇所に簡単に集められます。
今回の事例では、droonga0
とdroonga1
のそれぞれについてnginxのログを収集して、droonga0
のDroonga HTTP serverを使ってログをデータベースに格納するように設定しました。
図にすると以下の要領です。
何はともあれ、Fluentdを各ノードにインストールする必要があります。
droonga0
とdroonga1
の両方に、以下の手順でFluentdをインストールします。
$ sudo apt-get install ntp
$ sudo service ntp restart
$ curl -L http://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh | sudo sh
続けて、nginxのログを監視したいすべてのノード(ここではdroonga0
とdroonga1
)で、必要な設定を行います。
まず、nginxのログをFluentdから読めるようにグループとパーミッションを設定します。
$ sudo chmod -R g+r /var/log/nginx
$ sudo chgrp -R adm /var/log/nginx
$ sudo usermod -a -G adm td-agent
td-agent
ユーザやグループに対して読み取りの権限を与えるのではなく、adm
グループに読み取りの権限を与えた上でtd-agent
をadm
グループに所属させる、という操作をしているのがポイントです。
adm
というのは端的に言うと「ログを見る権限のあるユーザ」を示すグループ名です。
ログファイルのグループをtd-agent
に変更する方法だと、ログローテーションが発生すると新しいログファイルのグループは自動的にadm
に設定されるため、ログローテーションの度にファイルのアクセス権を設定し直さないといけないということになってしまいます。
次に、必要なFluentdプラグインを導入します。
多くのFluentdプラグインはGemパッケージとして提供されていますが、gem
ではなくtd-agent-gem
コマンドを使うことで、Fluentd専用にそれらのパッケージをインストールできます。
$ sudo td-agent-gem install fluent-plugin-config-expander
$ sudo td-agent-gem install fluent-plugin-parser
$ sudo td-agent-gem install fluent-plugin-anonymizer
各プラグインの役割は以下の通りです。
fluent-plugin-config-expander
:設定の中で変数を使えるようにします。fluent-plugin-parser
:nginxのログファイルの各行を、扱いやすいように各フィールドの名前をキーとしたハッシュに変換します。fluent-plugin-anonymizer
:特定のフィールドの値をハッシュ化し、プライバシーを保ちつつ、元は別々の値であったことを分かるようにします。nginxのログにはプライバシーに関わる情報が含まれるため、これを使って匿名化しておきます。必要なプラグインが揃ったら、Fluentdの設定ファイル(/etc/td-agent/td-agent.conf
)に以下の設定を書き加えます。
$ sudo vi /etc/td-agent/td-agent.conf
# nginxのアクセスログを監視する
<source>
type config_expander
<config>
type tail
path /var/log/nginx/access.log
pos_file /var/log/td-agent/nginx.pos
# 後で分かりやすいように、タグにホスト名を含める。
tag nginx.log.${hostname}
format nginx
</config>
</source>
# プライバシーに関わる情報を匿名化する
<match nginx.log.*.**>
type anonymizer
# 公開するログサーバなので、refererも匿名化する
sha1_keys remote, user, referer
# 値をハッシュ化する際のsaltなので、ここは任意の文字列に変えておく
# (各ノードで共通の値にする)
hash_salt droonga-log-cluster
add_tag_prefix anonymized.
</match>
droonga0
以外のノードでは、以下の設定も付け足して、ログをdroonga0
に送るようにします。
# ログをdroonga0宛に転送する
<match anonymized.*.log.*.**>
type forward
<server>
host droonga0
</server>
</match>
他のノードから送られてきたログを受け取ってDroongaクラスタに格納するためのエンドポイントとなるdroonga0
では、さらに追加の設定が必要です。
まず、必要なFluentdプラグインを導入します。
droonga0$ sudo td-agent-gem install fluent-plugin-record-reformer
droonga0$ sudo td-agent-gem install fluent-plugin-groonga
各プラグインの役割は以下の通りです。
fluent-plugin-record-reformer
:タグ名から取り出した値などを使って、フィールドの値を再設定します。fluent-plugin-groonga
:ログをGroongaのレコードとしてloadします。GroongaサーバにはHTTPでリクエストを送る事ができるため、Droongaにもそのまま利用できます。fluent-plugin-groonga
のバッファ保存先に使うために、Fluentdが読み書きできるディレクトリを作成します。
droonga0$ sudo mkdir -p /var/spool/td-agent/buffer/
droonga0$ sudo chown -R td-agent:td-agent /var/spool/td-agent/
Fluentdの設定ファイルに、ログをDroongaクラスタに格納するための設定を書き加えます。
droonga0$ sudo vi /etc/td-agent/td-agent.conf
# 他ノードからforwardされてきたログを取り込む
<source>
type forward
</source>
# 各ホストから流入してきたログに、ホストを識別するための情報を付与する
<match anonymized.*.log.*.**>
type record_reformer
enable_ruby false
tag ${tag_parts[2]}
<record>
host ${tag_suffix[3]}
type ${tag_parts[1]}
timestamp ${time}
</record>
</match>
# ここまでの加工が全て終わったログのみを記録する
<match log>
type groonga
store_table Logs
protocol http
host droonga0
buffer_type file
buffer_path /var/spool/td-agent/buffer/groonga
flush_interval 1s
<table>
name Terms
flags TABLE_PAT_KEY
key_type ShortText
default_tokenizer TokenBigram
normalizer NormalizerAuto
</table>
<table>
name Hosts
flags TABLE_PAT_KEY
key_type ShortText
</table>
<table>
name Timestamps
flags TABLE_PAT_KEY
key_type Time
</table>
<mapping>
name host
type Hosts
<index>
table Hosts
name logs_index
</index>
</mapping>
<mapping>
name timestamp
type Time
<index>
table Timestamps
name logs_index
</index>
</mapping>
<mapping>
name path
type Text
<index>
table Terms
name logs_message_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name referer
type Text
<index>
table Terms
name logs_message_index
flags WITH_POSITION
</index>
</mapping>
</match>
設定が終わったら、各ノードのFluentdを再起動します。
$ sudo service td-agent restart
これで、Fluentdがnginxのログを読み始めて、結果がfluent-plugin-groonga
を経由してDroongaクラスタに格納されるようになります。
以上、ConoHa上にDroongaクラスタを構築する手順と、そのDroongaクラスタに継続的に情報を格納する例としてnginxのログをFluentdで収集する手順を紹介しました。
Droongaはまだまだ開発途上のプロジェクトのため、Groongaに対して実装が追いついていない部分や、Droonga独自の部分で不具合が残っている部分があります。 お恥ずかしい話ですが、今回の公開用クラスタの構築中にもいくつかの不具合を見つけて修正しました。 継続的な運用を通じてこういった不安要素を排除していき、安心して誰でも使えるようなクオリティの物にDroongaを育てていきたいと思っています。 また、実際にDroongaを試してみて不具合に遭遇したりドキュメントの不備を見つけられた方は、GitHubのイシュートラッカーに問題を報告したり、修正内容をプルリクエストで送ったりして頂ければ幸いです。
先日、fluent-plugin-github-activitiesとSharetaryという2つのソフトウェアをリリースしました。 これらを組み合わせると、GitHub上の個々人の活動をウォッチして簡単に共有することができます。
実際にどのように動作するのか、ClearCode Inc. Organizationに所属しているアカウントの公開アクティビティを収集するSharetaryの運用サンプルをご覧下さい。 (ちなみに、この運用サンプルは別のエントリで紹介したデモンストレーション用のDroongaクラスタをデータの格納先として利用しています。)
この仕組みは、SEゼミの2015年度の取り組みを支援するために開発しました(実は、先日のリーダブルコード勉強会でも会場の片隅で試験運用していました)。 今年のSEゼミではOSS Hack Weekendと題して、学生の皆さんに実際のOSSプロジェクトに参加してもらう予定ですが、Sharetaryの画面を会場内のスクリーンに出しておくことで、イベントの中で実際にどのような開発が行われているかを会場内で目に見える形で共有できますし、プルリクエストが採用されればその様子が一目で分かるというわけです。
fluent-plugin-github-activitiesは、GitHub上の公開アクティビティをクローリングすることに特化したFluentdプラグインです。
以下のような<source>
を定義しておくと、設定で列挙したユーザの公開アクティビティを一定間隔で取得して、新規に取得したアクティビティの情報をFluentdのストリームに流すようになります。
<source>
type github-activities
access_token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
users ashie,co-me,cosmo0920,hayamiz,hhatto,kenhys,kou,min-shin,monkey-mas,mooz,okkez,piroor,ridec,s-yata,t2y
clients 4
interval 1
base_tag github-activity.
pos_file /var/log/td-agent/github-activities.json
</source>
この時レコードとして流れるのは、Events APIの個々のEventです。 どんな種類のアクティビティがあってどんな情報を取得できるのかは、GitHubのAPIドキュメントを参照して下さい。
このプラグインの働きとしてはただそれだけの物なので、Sharetary以外にも様々な用途に流用できます。 例えば社内チャットにアクティビティを自動投稿するというのも、1つの使い方でしょう。
使用の際の注意点として、OAuthのアクセストークンを取得して設定ファイルに書いておく必要があるという点が挙げられます。 GitHub APIは手順に則って取得したアクセストークンを使わない場合、クロールの頻度が最大で60リクエスト毎時までに制限されてしまいます(アクセストークンがあれば5000リクエスト毎時までリクエストできます)。 fluent-plugin-github-activitiesはpushに対応するアクティビティを見付けたときはそれに含まれる各commitの詳細も個別に取得するようになっているため、アクセストークン無しの状態だとすぐにリクエスト過多と判断されてしまいます。
Sharetaryというのは「Share(共有)」と「Secretary(書記、秘書)」の組み合わせによる造語です。 バックエンドとしてはGroongaまたはDroongaのHTTPサーバを利用する想定になっており、所定の形式に則って定義された「イベント情報」のテーブルに対して検索を行って、新たに増えたイベントのレコードをTwitterなどのタイムライン風にリアルタイム表示することができます。
また、タイムライン表示とは別に、アーカイブ表示というモードもあります。 このモードでは、その時点までに取得済みのイベントのレコードをTogetterのように時系列順で表示することができます。 あるイベントに関連付けられたイベント群はスレッド風に表示されるので、タイムライン表示では追いにくい議論やコメントのやり取りなども、このビューであれば俯瞰して見る事ができます。
なお、Sharetaryで表示するための個々のイベントのレコードには「返信先URI」にあたるフィールドを持たせることができ、例えばGitHub上での「コミット」に対応するイベントに対して「コミットに対するコメントの入力フォーム」のURIを返信先として紐付けておけば、SharetaryのUI上で「このイベントに返信する」というような操作を行った際にそのコメント入力フォームへ遷移させることができます。 そうして追加されたコメントはfluent-plugin-github-activitiesによってクロールされ、Sharetaryのタイムライン上に表れることになります。 このように、Sharetaryはそれ自体に固有の情報をなるべく保持せずに、公開の情報だけをソースとして運用できる設計となっています。
Sharetary自体はデータをクロールする仕組みを持っていないため、fluent-plugin-github-activitiesのように何らかのクローラと併用することが前提になります。 冒頭でも紹介した実際の運用例は、以下の図のような構成になっています。
この図を見ると、Sharetaryとfluent-plugin-github-activities(Fluentd)は、共通のデータベースとしてDroongaクラスタにアクセスしてはいますが、互いに独立して動作している事が見て取れるでしょう。
fluent-plugin-github-activitiesは収集したアクティビティの情報をそのままレコードとして流すだけなので、それだけではSharetaryの情報ソースには使えません。 実際には、それらのアクティビティをSharetary用の適切なイベント形式のレコードに変換し、fluent-plugin-groongaを使ってSharetary用のデータベースにloadさせる必要があります。
Fluentdでのレコードの形式変換に使えるプラグインはいくつかありますが、元レコードの情報を使って全く新しいレコードを組み立てるという用途には、fluent-plugin-mapが適しているようです。 例えば、「Issueを新たに作成した」というアクティビティをfluent-plugin-github-activitiesがクロールしてきた際に、それを使ってSharetaryのイベントとなるレコードを組み立てる(GitHubのアクティビティをSharetaryのイベントに変換する)ルールは以下のように書けます。
<match github-activity.issue-open>
type map
tag "\"sharetary.\" + tag"
time time
record (require("time") || true) && { "_key" => record["payload"]["issue"]["html_url"], "type" => "issue-open", "source_icon" => "https://github.com/favicon.ico", "class" => "major", "scope" => record["repo"]["name"], "scope_icon" => record["$github-activities-related-organization-logo"], "title" => "Open", "description" => "Opened new issue: " + "#" + record["payload"]["issue"]["number"].to_s + " " + record["payload"]["issue"]["title"] + ": " + (record["payload"]["issue"]["body"] || ""), "actor" => record["actor"]["login"], "actor_uri" => "https://github.com/#{record["actor"]["login"]}/", "actor_icon" => record["$github-activities-related-avatar"], "actor_class" => "major", "uri" => record["payload"]["issue"]["html_url"], "reply_uri" => "#{record["payload"]["issue"]["html_url"].split("#").first}#new_comment_field", "created_at" => Time.now.to_i, "timestamp" => Time.parse(record["created_at"]).to_i, "parent" => nil }
</match>
fluent-plugin-mapでは変換ルールを文字列として定義しますが、Fluentdの設定ファイルでは文字列型の設定値を改行できないため、このように横長の記述になってしまっています。
基本的にはこの要領でSharetaryに表示させたいアクティビティ用の変換ルールを定義していくのですが、type
がタグと同じ内容だったり、各ルールでactor_class
やsource_icon
が同じだったりする場合、この段階でフィールドを定義するよりも、後でまとめて設定した方がスッキリしますし、今後の変更も容易になります。
レコードのタグなどを使って追加のフィールドを定義するfluent-plugin-record-reformarプラグインを使うと、これらの共通フィールドは以下のようにして一気に設定できます。
<match github-activity.issue-open>
type map
tag "\"sharetary.\" + tag"
time time
# type, actor_class, source_iconはこの段階では埋めていない
record (require("time") || true) && { "_key" => record["payload"]["issue"]["html_url"], "class" => "major", "scope" => record["repo"]["name"], "scope_icon" => record["$github-activities-related-organization-logo"], "title" => "Open", "description" => "Opened new issue: " + "#" + record["payload"]["issue"]["number"].to_s + " " + record["payload"]["issue"]["title"] + ": " + (record["payload"]["issue"]["body"] || ""), "actor" => record["actor"]["login"], "actor_uri" => "https://github.com/#{record["actor"]["login"]}/", "actor_icon" => record["$github-activities-related-avatar"], "uri" => record["payload"]["issue"]["html_url"], "reply_uri" => "#{record["payload"]["issue"]["html_url"].split("#").first}#new_comment_field", "created_at" => Time.now.to_i, "timestamp" => Time.parse(record["created_at"]).to_i, "parent" => nil }
</match>
<match sharetary.github-activity.**>
type record_reformer
enable_ruby false
tag completed.${tag}
<record>
type ${tag_parts[2]}
actor_class major
source_icon https://github.com/favicon.ico
</record>
</match>
こうして組み立てたレコードは、fluent-plugin-groongaを使ってGroongaまたはDroongaに格納します。 以下はfluent-plugin-groonga用の設定例です。
<match completed.sharetary.**>
type groonga
store_table Events
protocol http
host droonga0
buffer_type file
buffer_path /var/spool/td-agent/buffer/groonga
flush_interval 1s
<table>
name Events
flags TABLE_PAT_KEY
key_type ShortText
</table>
<table>
name Timestamps
flags TABLE_PAT_KEY
key_type Time
</table>
<table>
name Terms
flags TABLE_PAT_KEY
key_type ShortText
default_tokenizer TokenBigram
normalizer NormalizerAuto
</table>
<mapping>
name type
type ShortText
</mapping>
<mapping>
name class
type ShortText
</mapping>
<mapping>
name title
type ShortText
<index>
table Terms
name Events_title_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name description
type Text
<index>
table Terms
name Events_description_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name extra_description
type Text
<index>
table Terms
name Events_extra_description_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name scope
type ShortText
<index>
table Terms
name Events_scope_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name scope_icon
type ShortText
</mapping>
<mapping>
name uri
type ShortText
<index>
table Terms
name Events_uri_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name source_icon
type ShortText
</mapping>
<mapping>
name reply_uri
type ShortText
</mapping>
<mapping>
name actor
type ShortText
<index>
table Terms
name Events_actor_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name actor_uri
type ShortText
</mapping>
<mapping>
name actor_icon
type ShortText
</mapping>
<mapping>
name actor_class
type ShortText
</mapping>
<mapping>
name timestamp
type Time
<index>
table Timestamps
name Events_timestamp_index
</index>
</mapping>
<mapping>
name created_at
type Time
<index>
table Timestamps
name Events_created_at_index
</index>
</mapping>
<mapping>
name parent
type ShortText
<index>
table Terms
name Events_parent_index
flags WITH_POSITION
</index>
</mapping>
</match>
この構成においてはdroonga0
とdroonga1
のそれぞれでFluentdが動作していますが、Sharetary関係の物はすべてdroonga1
に置いておくという方針で、今回はdroonga1
にもfluent-plugin-groongaをインストールして、そこからSharetary用のイベントレコードをloadするようにしています。
Issueの作成以外の各種アクティビティも含めた変換ルールの設定例の完全版は、Sharetaryのリポジトリに含まれていますので、参考にしてみて下さい。 ……というか、認証のための情報やfluent-plugin-groongaの接続先ホスト以外の全ての設定は、上記運用例でもこれをそのまま使っています。 皆さんも、試してみる時はこの設定をスタート地点として、ご自分のニーズに合うように微調整していくとよいでしょう。
ところで、上記の説明からも分かる通り、適切なイベント形式のレコードに変換さえできるのなら、Sharetaryの情報ソースはGitHubのアクティビティでなくても構いません。 この設定例は、イベント同士を関連付けたり、イベントの種類に応じてSharetaryの画面上での目立たせ方を変えたりといった事のサンプルにもなっていますので、他の情報ソースを使う場合の参考になるのではないでしょうか。
以上、最近開発・公開したfluent-plugin-github-activitiesとSharetaryという2つのソフトウェアの概要と使い方について簡単にご紹介しました。
この両者の組み合わせは利用形態の一例に過ぎないとはいえ、現時点では最も代表的な利用形態でもあります。 GitHubを開発の場所として利用するハッカソン形式のイベントを開催される際は、会場内での賑やかしとして、またイベント期間中の出来事を後から振り返る際のログとして、是非利用してみてください。
また、Sharetaryの運用例としてクリアコード関係者の公開アクティビティの定点観測所についてもご案内しました。 クリアコードが日常の業務の中でOSSやフリーソフトウェアを開発・公開したり既存プロジェクトに協力したりしている様子を俯瞰してご覧頂けますので、弊社オフィスに遊びに来られるような感覚でフラッと覗いてみて頂ければ幸いです。
Firefox 29において、古くから存在していたセキュリティポリシー設定のための機能(Configurable Security Policies、またはCapabilities、略してCAPS)の大部分が削除されました。 このエントリでは、CAPSで行っていた設定を、サイト別設定機能とクリアコードが開発・提供しているアドオンの組み合わせで代用する方法について解説します。
Firefoxには古くから、Webサイトごとに詳細な権限設定を行うためのCAPSと呼ばれるセキュリティポリシー機能が存在していました。 これは以下のような形で利用する物でした。
// ポリシー一覧を定義
pref("capability.policy.policynames", "trusted");
// 既定のポリシーを定義
// JavaScriptの実行を禁止
pref("capability.policy.default.javascript.enabled", "noAccess");
// file:// で始まるURLの読み込みを禁止
pref("capability.policy.default.checkloaduri.enabled", "noAccess");
// クリップボードへの書き込み操作を禁止
pref("capability.policy.default.Clipboard.cutcopy", "noAccess");
// クリップボードの内容の読み取り操作を禁止
pref("capability.policy.default.Clipboard.paste", "noAccess");
// ウィンドウを開く事を禁止
pref("capability.policy.default.Window.open", "noAccess");
...
// 信頼済みサイト用のポリシーを定義
// ポリシーを適用するサイトの一覧を定義
pref("capability.policy.trusted.sites", "http://www.mozilla.org www.mozilla.com");
// 既定のポリシーで禁止した操作を許可
pref("capability.policy.trusted.javascript.enabled", "allAccess");
pref("capability.policy.trusted.checkloaduri.enabled", "allAccess");
pref("capability.policy.trusted.Clipboard.cutcopy", "allAccess");
pref("capability.policy.trusted.Clipboard.paste", "allAccess");
pref("capability.policy.trusted.Window.open", "allAccess");
...
この機能は設定のためのUIが存在しておらず、アドオンNoScriptが内部的に利用する以外の場面では、一般にはほとんど使われていませんでした。 そのため、Firefox 29において大部分の機能が削除されました。 (参考:Firefox 29の互換性情報における記述) 現在は以下の機能だけが残されています。
これら以外の機能は、Firefox 29以降では全く利用できません。
一方で、現在のFirefoxには「サイト別設定」と呼ばれる機能が備わっており、ポップアップブロック機能やパスワードの保存などについて、ホスト名単位で許可または禁止の権限を設定する事ができます。 これらのサイト別設定は、初期状態の権限は「不明」となっており、ポップアップブロック機能や、パスワードマネージャによるパスワードの保存機能などが発動した際に、ユーザにその後の操作が委ねられます。 その際に「今後は確認しない」などの選択を取ると、選択の結果がサイト別設定として保存され、以後は保存された設定が暗黙的に利用されるようになります。
実際に現在どのようなサイト別設定が保存されているかは、ロケーションバーに「about:permissions」と入力する事で確認できます。また、ポップアップブロック機能の許可サイト一覧やパスワードマネージャの例外サイト一覧も、本質的にはこの仕組みに基づいて提供されています。
以上のように、サイト別設定とCAPSは挙動もコンセプトも全く異なっています。 しかしながら、「プライバシー情報へのアクセスやユーザの利便性を損ない得る特別な操作は、ユーザが明示的に許可されない限りは禁止する」「特定のWebサイトについて、特別な操作を実行する許可を与える事ができる」という点に着目すると、用途によってはある程度はCAPSの代用として利用できそうです。
サイト別設定をCAPSの代用に使うには、以下のような事ができる必要があります。
具体的な方法はいくつか考えられますが、ここではクリアコードが開発・提供しているアドオンを使った場合の手順をご紹介します。
FirefoxにはMission Control Desktop(MCD)と呼ばれる集中管理のための仕組みが備わっており、これを使うと、「プリファレンス」と呼ばれる仕組みに基づいて管理されている様々な設定を、管理者が一括して管理することができます。
しかしながら、サイト別設定はプリファレンスとは別の仕組みに基づいて管理されているため、MCDで集中管理する事ができません。 また、MCD以外の方法でサイト別設定を集中管理する事もできません。
クリアコードで開発しているPermissions Auto Registererというアドオンを使用すると、サイト別設定をMCDで集中管理できるようになります。 具体的には、MCDで定義したサイト別設定を各クライアントに配布し、各クライアント上で当該アドオンによってその設定を実際のサイト別設定として反映する、ということができます。
Permission Auto Registerer用の設定をCAPS風に定義した例が、以下のコードです。 「sites」以外の設定の値は、「0」が「ユーザに尋ねる(初期状態)」、「1」が「許可」、「2」が「禁止」です。
// 信頼済みサイトの一覧(スペース区切り)
pref("extensions.autopermission.policy.trusted.sites",
"www.example.com www.example.jp");
// Cookieの保存の可否
pref("extensions.autopermission.policy.trusted.cookie", 1);
// デスクトップ通知の可否
pref("extensions.autopermission.policy.trusted.desktop-notification", 1);
// DOMフルスクリーンの利用の可否
pref("extensions.autopermission.policy.trusted.fullscreen", 1);
// 位置情報APIへのアクセスの可否
pref("extensions.autopermission.policy.trusted.geo", 1);
// 画像の読み込みの可否
pref("extensions.autopermission.policy.trusted.image", 1);
// オフラインストレージの利用の可否
pref("extensions.autopermission.policy.trusted.indexedDB", 1);
// アドオンのインストールの可否
pref("extensions.autopermission.policy.trusted.install", 1);
// Webアプリケーションのオフラインキャッシュの利用の可否
pref("extensions.autopermission.policy.trusted.offline-app", 1);
// パスワードマネージャの利用の可否
pref("extensions.autopermission.policy.trusted.password", 1);
// マウスポインタを隠す事の可否
pref("extensions.autopermission.policy.trusted.pointerLock", 1);
// 広告などのポップアップウィンドウを開く事の可否
pref("extensions.autopermission.policy.trusted.popup", 1);
//// 以下はCAPS連携機能で、「sites」の設定に基づいて自動的に
//// 相当するCAPSの設定に変換されます。
// capability.policy.*.javascript.enabled に対応
pref("extensions.autopermission.policy.trusted.javascript", 1);
// capability.policy.*.checkloaduri.enabled に対応
pref("extensions.autopermission.policy.trusted.localfilelinks", 1);
見ての通り、CAPSの設定とほぼ同じ要領で設定できるようになっています。
Permission Auto Registererによってサイト別設定を集中管理できるようになっただけでは、CAPSの代用としては不完全です。 というのも、このままでは、サイト別設定をユーザが任意に変更したり削除したり追加したりできてしまうからです。 サイト別設定をCAPSの代用として使うためには、管理者以外はサイト別設定を変更できないように制限する必要があります。
サイト別設定をユーザが変更できないようにする最も単純な方法は、スタイルシートを使ってUI要素を非表示にするというものです。 クリアコードで開発しているglobalChrome.cssというアドオンを使うと、ユーザースタイルシート相当のカスタマイズを管理者が行う事ができます。
サイト別設定に関する設定UIは各所に分散しています。 すべてのサイト別設定に関するUIを非表示にするスタイルシートの例は、以下の通りです。 (※見落としがある場合には、訂正しますのでご指摘下さい)
/* Cookie */
:root#BrowserPreferences #cookiesBox,
:root#BrowserPreferences #acceptThirdPartyRow,
:root#BrowserPreferences #keepRow,
:root[windowtype="Browser:page-info"]
#perm-cookie-row,
/* デスクトップ通知 */
:root[windowtype="Browser:page-info"]
#perm-desktop-notification-row,
/* DOMフルスクリーン */
:root[windowtype="navigator:browser"]
#full-screen-remember-decision,
:root[windowtype="Browser:page-info"]
#perm-fullscreen-row,
/* 位置情報API */
:root[windowtype="navigator:browser"]
#geolocation-notification menuitem[label="常に許可"],
:root[windowtype="navigator:browser"]
#geolocation-notification menuitem[label="常に許可しない"],
:root[windowtype="Browser:page-info"]
#perm-geo-row,
/* 画像の読み込み */
:root[windowtype="Browser:page-info"]
#blockImage,
:root[windowtype="Browser:page-info"]
#perm-image-row,
/* オフラインストレージ */
:root#BrowserPreferences #offlineNotify,
:root#BrowserPreferences #offlineNotifyExceptions,
:root[windowtype="Browser:page-info"]
#perm-indexedDB-row,
/* アドオンのインストール許可 */
:root#BrowserPreferences #addonInstallBox,
:root[windowtype="navigator:browser"]
#addon-install-blocked-notification menuitem[label="常に許可"],
:root[windowtype="navigator:browser"]
#addon-install-blocked-notification menuitem[label="常に許可しない"],
:root[windowtype="Browser:page-info"]
#perm-install-row,
/* パスワード保存 */
:root#BrowserPreferences #savePasswordsBox,
/* マウスポインタを隠す */
:root[windowtype="Browser:page-info"]
#perm-pointerLock-row,
/* ポップアップブロック */
:root#BrowserPreferences #popupPolicyRow,
:root[windowtype="navigator:browser"]
menuitem[observes="blockedPopupAllowSite"],
:root[windowtype="navigator:browser"]
menuitem[observes="blockedPopupEditSettings"],
:root[windowtype="navigator:browser"]
menuitem[observes="blockedPopupDontShowMessage"],
:root[windowtype="navigator:browser"]
menuseparator[observes="blockedPopupsSeparator"],
:root[windowtype="Browser:page-info"]
#perm-popup-row
{
display: none !important;
visibility: collapse !important;
}
Unionファイルシステムと呼ばれる、ファイルシステムを重ねあわせて透過的に扱うための仕組みがあります。この仕組みを実現する実装は複数あり、その一つがAuFS *1 です。
AuFSはDebian標準のカーネルではデフォルトで有効になっていたのですが、最近のカーネルでは削除され *2 ました。
この記事を書いている時点では、以下の2つのlinux-imageが64bit向けに提供されており、AuFSが使えるのは、3.16.0-4のほうのみ *3 です。
AuFSがDebianの提供している最新のカーネルから削除されたとはいえ、まったく使えないわけではありません。依然としてAuFS自体はカーネル 4.x向けにメンテナンスされています。しかしパッチをあてて自分でビルドするのはしんどいということもあるでしょう。
AuFSの代替案 *4 はいくつかあります。
前者のOverlayFSはカーネル 3.18以降でなら標準で使えます。ただし、モジュールを追加でロードする必要があります。
sudo modprobe overlay
正常にロードできていれば、/proc/filesystems
にoverlayがいるはずです。
ここで、何故overlayfsじゃないのかと不思議に思った人はLXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術 第18回 Linuxカーネルのコンテナ機能 [7] ─ overlayfsを参照するとよいでしょう。そのあたりの経緯も詳しい説明があります。
後者の unionfs-fuse
はパッケージを追加でインストールする必要があります。Debianではパッケージも提供されているので、導入も容易です。
sudo apt-get install unionfs-fuse
準備ができたところで、ディレクトリAとディレクトリBを重ねあわせて使う例を示します。
ディレクトリを重ねあわせる条件は次の通りとします。
ディレクトリBに書きこんだら、その内容はディレクトリAにすべて反映される構成です *5。
sudo mount -t aufs -o br:(ディレクトリA):(ディレクトリB)=ro none (ディレクトリB)
sudo mount -t overlay -o lowerdir=(ディレクトリB),upperdir=(ディレクトリA),workdir=(作業用のディレクトリ) overlay (ディレクトリB)
workdirは空のディレクトリである必要があります。
unionfs-fuse (ディレクトリA)=rw (ディレクトリB)
AuFSやOverlayFSの場合とくらべると、オプション指定も少なくすっきりしています。 ただし、ユーザーランドでの実装のため、AuFSやOverlayFSに比べると速度の面で劣ります*6 。
AuFSの代替として、OverlayFSや unionfs-fuse
コマンドでディレクトリを重ねあわせて使う方法を紹介しました。
そうそう頻繁に使う機会はないかも知れませんが、知っていると便利なコマンドです。
機会があれば、活用してみてください。
横山です。この週末にオープンソースカンファレンス2015 Hokkaidoに参加してきたのですが、その際に交通手段としてLCC(格安航空会社)を利用したので、感想と注意点についてレポートしたいと思います(OSC本編のレポートは後日書く予定です)。
LCCは主に羽田空港ではなく成田空港を利用しています。例えば、JR山手線大塚駅からだと、山手線日暮里駅まで約10分、日暮里駅から京成電鉄のスカイライナーに乗り換えて約40分、乗り換え時間を含めても合計1時間ほどで国内線の発着駅である空港第2ビル駅に着きます。日暮里から空港第2ビルまでは無停車で直通なので快適です。そのため、意外なことに羽田空港までと比べて所要時間はほとんど変わりませんでした(出発駅と乗り換え時間によってはむしろ早く着く)。
ただし、スカイライナーは20〜40分間隔で運行しているのと、スカイライナーはIC運賃1,235円(きっぷ運賃1,240円)の他に特急券1,230円が必要です。スカイライナーを使わない場合、京成本線のアクセス特急や快速特急の成田空港行に乗れば特急券が不要なので安く済みます(停車駅が多い分、30分ほど到着が遅くなります)。
行きの航空会社はジェットスターを使いました。Webで出発の10日ほど前に予約したのですが、曜日や時間帯によって運賃にばらつきがあるのは他の航空会社と変わらないようです。金曜日の午後は比較的安かったので、14:55発の便にしました。
ジェットスターなど多くのLCCには、基本運賃に上乗せすることでオプションを付けることができるシステムがあります。例えば、日時変更手数料が安くなったり、座席指定ができたり、大きな荷物を預けやすくなったりします。今回はそういったオプションは利用せず、基本運賃のみで片道7,300円でした。
また、ジェットスターを含むLCCの発着場は成田第2ターミナルではなく第3ターミナルにあるので、電車や車で行く場合はターミナル間を約500メートル歩かなければなりません。バスやタクシーであれば直接第3ターミナルに行くことも可能です。なお、第3ターミナルにも売店や食事コーナーはあります。
私が乗った便は特に座席が狭かったり運行が遅れたりといったことはなく、無事に新千歳空港へ到着しました。
帰りの航空会社はバニラエアを使いました。ジェットスターと同じくLCCです。前の便の到着が遅れたため、30分ほど遅れての出発になりました。ただ、この日は羽田空港でも1時間ほど遅れた便があったようなので、LCCだから遅れたというわけではないのかもしれません。運賃は安い最終便(定刻20:10発)を選んだので6,870円でした。注意点としては、あまり遅い時間になると成田空港から東京への脱出が難しくなります。具体的には、23:00頃までなら電車やバスで脱出できますが、それ以降はタクシーくらいしか選択肢がないようです。最終便は安いので選んでしまいがちですが、そういったリスクがあるのを考慮しておく必要があります。
東京から札幌の勉強会に参加したり、札幌から東京の勉強会に参加する際に、LCCを使って安価に移動する際の注意点をまとめました。手続きや乗り心地などはほとんど気にならなかったのですが、夜の便で成田空港到着が遅れた場合のリスクが一番の注意点かなと感じました。スケジュールに余裕があってその点が問題にならない場合にはLCCも有力な選択肢になりそうです。
要約:6月27日(土)にOSS開発参加未経験の学生向けに、OSSの開発に参加する方法をワークショップ形式で教えるイベントを開催します。「OSSの開発に参加してみたいけど漠然とした不安があり手を出せていない…」という学生向けに「具体的にこういう方法で始めるといいよ」という方法を伝えます。ワークショップ形式で実際に手を動かしながら学べるうえに、現役の超優秀エンジニアが多数メンターとしてサポートするので、OSS開発への漠然とした不安を払拭できるはずです。OSSの開発に参加してみたいけど手を出せていなかった学生の人はイベントページから応募してください。背中を押してあげたい学生を知っている人は教えてあげてください。締め切りは6月22日(月)です。
2015年のSEゼミの第3弾のイベントです。2015年のSEゼミは「OSS開発」をテーマにしています。第1弾のリーダブルコード勉強会(開催済み)ではコードを書いてOSSの開発に参加するときに必要となる(開発者にとって)読みやすいコードの書き方を学びました。第2弾のGitHub勉強会(6/20なので今週末開催)ではGitHubを使っているOSSの開発に参加する時に必要となるpull requestの仕方を学びます。
それらに続く第3弾のOSS Hack 4 BeginnersではOSSの開発に参加したことのない学生を対象に、OSSの開発への参加方法を伝えます。このイベントの目的は「OSSの開発に参加してみたいけど漠然とした不安があり手を出せていない…」という学生の「不安」を取りのぞくことです。
不安を取りのぞいた後に控える第4弾のイベントOSS Hack Weekend(7月11日(土)・12日(日)開催)ではのびのびと楽しくOSSの開発に参加しましょう!
OSS Hack 4 Beginnersは学生の不安を取りのぞくために、次のことを大事にした内容になっています。
これは次の点が「不安」の原因になっていると予想したからです。
これらの不安の原因を次の方法で解消する内容になっています。
それぞれ説明します。
まず、最初にOSS開発への参加の仕方を説明します。参加の仕方は無数にありますが、「好きなようにやっていいんだよ」と言うと不安が増す原因になってしまいます。そこで、トレーナーがオススメの方法を1つだけ説明し、このイベントではその方法を使って「やってみます」。あえて1つに限定することにより迷いポイントが減り、不安を軽減できるのではないかという考えです。
具体的な方法は次の通りです。
「まず動かす」というのは非常に重要な手順です。重要なことなのに言及している情報を見かけません。一方、pull requestの方法などはよく見かけます。当たり前すぎて誰も言及しないだけなのかもしれませんが、OSSの開発に参加する時に最初にやる一番大事なことは「動かす」ことです。
動かすためにはインストール手順を確認する必要があります。ドキュメントがあればそれを読んで、その手順に従ってインストールし、動かします。なかったら…、開発に参加するチャンスです!
OSSの開発に関わっていない人は、「ドキュメントが足りなくてわかんねぇよ!」とグチって終わりになる場面でしょう。しかし、OSSの開発に参加しようと思っている人にはチャンスです。グチっている時間で直せばよいのです。そうすれば、次にインストールしようとした人は「ドキュメントにちゃんと書いてあってスムーズにインストールできた」と思うことでしょう。直接あなたに感謝の言葉が届くことは稀かもしれませんが、「世界を(ちょっとかもしれないけど)変える」(@_ko1さんからのこのイベント参加者へのメッセージより)ことをしたのです。少しかもしれませんが、その少しが積み重なって大きな変化になっていきます。そうやってOSSはよくなっていくのです。
(なお、このイベントではすぐに対応せずにメモに残しておいて後で対応する、という進め方をします。いきなり「動かす」と「直す」を一緒にやると大変なので、まずは「動かす」に集中します。未経験者向けのイベントなので敷居を下げています。)
「OSSの開発に参加」というと、「コードを読んでバリバリコードを書かないと!」というイメージがあるかもしれませんが、そんなことばかりではありません。コードを書く前の段階でも、ユーザーの目線でフィードバックできることはたくさんあるのです。OSSの開発に参加したことがない人はそれを知らずに敷居が高いと感じてしまっていることでしょう。このイベントを通じて、「そんなことはないんだ、開発に参加したいならできることはいろいろあるんだ」ということをわかってもらいたいです。
「動かす」ことができたら「開発用にインストール」します。「動かす」ときはパッケージを使ってインストールするといったように、ユーザーとしてインストールします。一方、「開発用にインストール」するときはソースコードを取得して、そこから使える状態にもっていきます。
一般的に、「開発用にインストール」するときの方が手間がかかります。しかし、ソースコードを変えて手元で変更を確認できる環境が手に入るというメリットがあります。開発に参加するときはこれは非常に大きなメリットです。
なお、「開発用にインストール」するときもドキュメントがあればそれを参考に作業します。なかったら…、自分がやった手順をメモして、まとめたものを開発者に報告しましょう。開発に参加するチャンスですね。
「開発用にインストール」できたら「テスト」を実行します。多くのOSSではテストが用意されていて、開発者が手元で正しい挙動を確認できるようになっています。テストが用意されていたら実行して、自分の手元でも正しい挙動になっているか確認しましょう。
以上がオススメのOSS開発参加方法です。
イベントではこの内容を説明した上で、実際にトレーナーが前でやってみせます。実際にやっている様子を見ることで理解が進むことを期待しています。
オススメのOSS開発参加方法を知った後は実際にオススメの方法を実践します。
実践する際はグループを作って、グループごとに進めます。1つのグループは多くても4人から6人です。同じグループでは同じOSSを開発対象にします。これは、グループ内で相談できるようにするためです。
OSSの開発では困ったことがあったら相談しながら進めることは当たり前です。多くの場合、時間的・距離的に離れていることが多いので相談するときはインターネット越しになります。ただし、いきなりインターネット越しに相談しながらやってもいいんだよ、といっても敷居が高いでしょうから、今回は目の前の人たちと相談しやすいように同じグループで同じOSSを開発対象にしました。相談することに慣れてきたらインターネット越しでも相談できるようになることを期待しています。
なお、イベントでは現役の超優秀エンジニアが多数メンターとしてサポートします。メンターはグループ内での相談を促したり、困っていることに対してヒントを与えてくれたりします。メンターとも協力してオススメのOSS開発参加方法を実践し、できるという感触を自分のものにしましょう。
今回開発対象にするOSSはメンターが開発に関わっているOSSにします。当初は学生からの希望も考慮して決めようかと検討していたのですが、学生の「不安」を取り除くことを考えた結果、メンターが開発に関わっているOSSにすることにしました。
理由は次の通りです。
6月27日(土)に開催するOSS開発参加未経験の学生向けに、OSSの開発に参加する方法をワークショップ形式で教えるイベントを紹介しました。「OSSの開発に参加してみたいけど漠然とした不安があり手を出せていない…」という学生の人はイベントページから応募してください。背中を押してあげたい学生を知っている人は教えてあげてください。あるいは、現役の超優秀エンジニアメンターへのサポートを受けさせてあげたい学生を知っている人はこのイベントへの参加を勧めてください。
なお、締め切りは6月22日(月)です。
すでにOSSの開発に参加していて、現役の超優秀エンジニアメンターのやり方を参考にしたいという学生の人は7月11日(土)・12日(日)に開催するOSS Hack Weekendに応募してください。こちらは学生が希望したOSSを開発対象にしますし、メンターからいろいろ学べる時間がたっぷりあるので、充実した週末になるはずです。
先日、「GitHub上のアクティビティを定点観測してみませんか?」と題してfluent-plugin-github-activitiesとSharetaryという2つのソフトウェアを紹介しましたが、Sharetaryの改善に伴っていくつか設定の仕方に変化がありましたので、改めて、最新バージョンでの推奨設定について解説します。 ClearCode Inc. Organizationに所属しているアカウントの公開アクティビティを収集するSharetaryの運用サンプルも、この解説と同じ手順でSharetary 0.5.0に移行しました。
なお、この記事はSharetaryの利用のための技術情報が主ですが、Fluentdのストリームにおける1つの入力を元にして、fluent-plugin-groongaで複数テーブルにレコードを追加する方法の解説にもなっています。 fluent-plugin-groongaを使ったGroongaサーバーのレプリケーションに対して、in_groongaを使わずに直接レコードを登録するやり方に興味がある方も、参考にしてみて下さい。
バージョン0.4.0以降のバージョンには様々な変更点がありますが、最大の変更点はスキーマ定義の変更です。
バージョン0.3.0までではほとんどの情報がEventsテーブルに集中していましたが、これにはデータの重複が増えてしまうという欠点がありました。 また、Eventsテーブルのカラム数が増えれば増えるほど管理が煩雑になってしまうという問題もあります。 そこでバージョン0.4.0からは、イベントに直接関係ない情報は別のテーブルに分けて管理するように改めました。
また、それに伴ってイベントに複数のタグを紐付けられるようにし、イベントの属するscope
(GitHubのアクティビティに対応するイベントであれば、リポジトリの情報など)はタグの1つとして管理するようにしました。
これにより、「このイベントはfooというリポジトリに関連付けられており、barというハッカソンの期間中に行われた」というような複数の切り口での分類が可能になりました。
このスキーマ変更によって、これまでにクロール済みのイベント情報はそのままでは使えなくなっています。 後述の手順に則って、データを移行する必要があります。
既に設置済みのSharetaryのバージョンを0.3.0から0.4.0以上に更新する手順は以下の通りです。
migrate-0.3.0-to-0.4.0
を、Bashスクリプトとして実行し、データを移行する。sudo npm install -g sharetary
で最新バージョンのSharetaryをインストールする。データ移行用のスクリプトは、以下のオプションを指定できます。
-h <hostname>
: Groongaサーバ(またはDroongaクラスタ)のホスト名。省略時はlocalhost
。-p <port>
: Groongaサーバ(またはDroongaクラスタ)のHTTPサーバがlistenしているポート番号。省略時は10041
。-t <追加で設定するタグ>
: 移行された全てのイベントに固定で付与するタグ。-d
: 指定するとdry-runモードとなり、実際にはデータベースに変更を行わないで、どのような変更が行われるのかをシミュレートする。例えばGroongaサーバがgroonga
というホストの10041番ポートで動作していて、収集済みのイベントに「2015-sezemi-readable-code」というタグを付与して移行する場合、コマンドラインは以下のようになります。
$ wget https://raw.githubusercontent.com/clear-code/sharetary/master/bin/migrate-0.3.0-to-0.4.0
$ chmod +x ./migrate-0.3.0-to-0.4.0
$ ./migrate-0.3.0-to-0.4.0 -h groonga -t 2015-sezemi-readable-code -d
意図しないデータ破壊を避けるためは、まず1回dry-runして、適用される変更を先に把握しておくとよいでしょう。
追加するタグの指定は必須ではありませんが、指定しておくのがお薦めです。 過去の運用で何かのハッカソンに関係するイベントを収集済みなのであれば、そのハッカソンの名前をタグとして追加しておくと、それ以後収集するイベントを古いイベントと区別しやすくなります。
クローラの設定をどのように更新するかは、どんなクローラを使っているかによって変わります。 以下は、先日のエントリで紹介したfluent-plugin-github-activitiesの場合の設定(Fluentdの設定)の解説となります。 設定の参考にしてみて下さい。
先のエントリでも解説していますが、fluent-plugin-github-activitiesは本質的にはSharetaryとは全く独立した存在です。 fluent-plugin-github-activitiesはGitHubのアクティビティをクロールして取得してきてFluentdのストリームに取り込むだけの物で、それを加工してSharetary用のイベント情報としてデータベースに格納するのは、他のプラグインの仕事という事になります。
ここでは、Sharetaryのリポジトリに含まれている設定例の内容を引き合いに出しながら、fluent-plugin-github-activitiesの出力をどのように加工すればSharetary 0.4.0以降のイベント情報として使えるのかを解説します。
まず最初にfluent-plugin-github-activities用の<source>
の設定ですが、ここは先のエントリで紹介した通りで変化はありません。
<source>
type github-activities
access_token access-token-of-github
users ashie,cosmo0920,kenhys,kou,min-shin,myokoym,okkez,piroor
clients 4
interval 1
base_tag github-activity.
pos_file /var/log/td-agent/github-activities.json
</source>
これによってレコードとして取り込まれたアクティビティ情報を加工する指定が、この後に続く事になります。
FluentdでSharetary用のデータベースにレコードを格納するにはfluent-plugin-groongaを使いますが、fluent-plugin-groonga、特にその中でFluentdのストリームからGroongaサーバへの書き込みを行うout_groongaは、流入してきたレコードを特定のテーブル1つのレコードとして格納するという物です。
しかしSharetary 0.4.0以降用には、1つのイベントにつき、イベント情報そのものを格納するEvents
テーブル、人物のメタ情報を格納するActors
テーブル、タグのメタ情報を格納するTags
テーブルという具合に、最大で3つのテーブルに同時にレコードを追加する必要があります。
そこで、ストリームを「イベント用」「人物用」「タグ用」の3つに分岐して、それぞれ別々の設定のfluent-plugin-groongaによってレコードとして各テーブルに格納することになります。
このようなストリームの分岐には、Fluentdに標準添付されているcopyというプラグインを使います。
<match github-activity.**>
type copy
# Eventsテーブル用のレコード
<store>
type record_reformer
enable_ruby false
tag event.${tag}
</store>
# Actorsテーブル用のレコード
<store>
type record_reformer
enable_ruby false
tag actor.${tag}
</store>
# Tagsテーブル用のレコード
<store>
type record_reformer
enable_ruby false
tag repository-tag.${tag}
</store>
</match>
copyプラグインは、<store>
に書かれた別のプラグイン用の指定に基づいて加工を行ったレコードを、Fluentdのストリームに出力します。
この例では、レコードを加工するプラグインであるfluent-plugin-record-reformerを使って、それぞれのタグ名を「何に使うためのレコードか?」という事を示す物に変えてから出力しています。
3つの<store>
が書かれているため、これでストリームは3つに分かれることになります。
Actors
テーブルに格納する話を単純にするために、外部テーブルの方から解説していきます。
まずはActors
テーブルです。
このテーブルのレコードは_key
が人物の名前で、その人物のプロフィールページのURIとなるuri
、アイコン画像のicon
、人物の重要度を示すclass
の3つのフィールドを持ちます。
copyプラグインによって分岐された3つのストリームのうち、Actors
テーブル用に出力しているレコードについて、fluent-plugin-record-reformerの変換ルールを以下のように更新し、上記のフィールドを持ったレコードを出力するようにします。
<match github-activity.**>
type copy
...
# Actorsテーブル用のレコード
<store>
type record_reformer
enable_ruby true
renew_record true
tag sharetary.actor
<record>
_key ${(actor || committer)["login"]}
uri https://github.com/${(actor || committer)["login"]}/
icon ${self["$github-activities-related-avatar"]}
class major
</record>
</store>
...
</match>
この時の設定のポイントは以下の通りです。
Acotrs
テーブル用のレコードはゼロから組み立てるので、renew_record true
として、加工元レコードのフィールドを引き継がないようにしています。enable_ruby true
は、セキュリティや速度などの点でデメリットがありますが、今回の用途では必要な設定です。
というのも、fluent-plugin-record-reformerは、プレースホルダーを使って加工元のレコードのフィールドの値を参照できますが、fluent-plugin-github-activitiesが出力するレコード(GitHubのAPIが返すJSONそのままの形式)のようにそれぞれのフィールドが階層化されている場合、欲しい情報を取り出す事ができません。
ですので、必要な情報を参照できるようにするために、この設定を有効化しています。Actors
テーブル用のレコードにはアクティビティの種類を示す情報は必要なくなるので、タグはsharetary.actor
に統一しています。_key
とuri
は、どちらも元のレコードに含まれているユーザアカウント名を使いたいのですが、個々のコミットに対応するレコードと、それ以外のアクティビティのレコードとでは、持っている情報の構成が若干異なるため、参照先を決め打ちできません。
ですので、ここでは||
を使って、レコードに存在する方のフィールドを取り出しています。$github-activities-related-avatar
というフィールドを参照していますが、値を参照するために${$github-activities-related-avatar}
と書くとエラーになってしまいます。これは、フィールド名の先頭の$
が特別な意味を持ってしまうためです。ここでは、各フィールドの値を保持している名前空間オブジェクトのOpenStruct
自身の[]
メソッドを明示的に呼び出す事で、フィールドの値を参照しています。class
フィールドの値は、簡単のためmajor
に決め打ちしています。元レコードの情報に基づいてclass
の値を適宜切り替えると、「受講者」と「メンター」のように、人物によってイベントの表示の仕方に強弱を付ける事もできます。次は、できあがったレコードをfluent-plugin-groongaを使ってActors
テーブルに格納するための設定です。
<match sharetary.actor>
type groonga
store_table Actors
protocol http
host localhost
buffer_type file
buffer_path /var/spool/td-agent/buffer/groonga-sharetary-actor
flush_interval 1s
<table>
name Actors
flags TABLE_HASH_KEY
key_type ShortText
</table>
<mapping>
name uri
type ShortText
</mapping>
<mapping>
name icon
type ShortText
</mapping>
<mapping>
name class
type ShortText
</mapping>
</match>
buffer_path
に指定するパスは、この設定のための固有のパスにする必要があります。
他のテーブル用のfluent-plugin-groongaの設定との間でこの設定の値が衝突すると、どちらか片方の設定しか有効化されませんので、注意して下さい。これで、人物の情報がアイコン画像のURIなどのメタ情報を伴って保存されるようになります。
Tags
テーブルに格納する人物の次は、イベントのタグ付けのためのタグそのものを格納するTags
テーブルです。
fluent-plugin-github-activitiesで取得したアクティビティから取り出せる情報を使って適切なタグを自動設定すれば、収集したイベントを効率よく分類できるようになります。 最もベタな例は、「そのアクティビティに関連しているリポジトリ」という切り口でのタグ付けでしょう。
このテーブルのレコードは_key
がタグの名前で、タグ固有のアイコン画像を示すicon
というフィールドを持ちます。
copyプラグインによって分岐された3つのストリームのうち、Tags
テーブル用に出力しているレコードについて、fluent-plugin-record-reformerの変換ルールを以下のように更新し、上記のフィールドを持った「そのアクティビティに関連しているリポジトリ」のタグにあたるレコードを出力するようにします。
<match github-activity.**>
type copy
...
# Tagsテーブル用のレコード
<store>
type record_reformer
enable_ruby true
renew_record true
tag sharetary.tag
<record>
_key ${tag.end_with?(".commit") ? url.match(/repos\/([^\/]+\/[^\/]+)\/commits/)[1] : tag.end_with?(".fork") ? payload["forkee"]["full_name"] : repo["name"]}
icon ${self["$github-activities-related-organization-logo"]}
</record>
</store>
</match>
Acotrs
テーブル用のレコードと同様に、Tags
用のレコードもゼロから組み立てるので、renew_record true
としています。enable_ruby true
としています。Tags
テーブル用のレコードにもアクティビティの種類を示す情報は不要なので、タグはsharetary.tag
に統一しています。_key
の値は、ほとんどのアクティビティではrepo["name"]
で参照できますが、コミットに対応するレコードとForkに対応するレコードだけは違う値を使う必要があります。ここでは、3項演算子を使って、元レコードのどのフィールドの値を使うかを振り分けています。次は、できあがったレコードをfluent-plugin-groongaを使ってTags
テーブルに格納するための設定です。
<match sharetary.tag>
type groonga
store_table Tags
protocol http
host localhost
buffer_type file
buffer_path /var/spool/td-agent/buffer/groonga-sharetary-tag
flush_interval 1s
<table>
name Tags
flags TABLE_HASH_KEY
key_type ShortText
</table>
<mapping>
name icon
type ShortText
</mapping>
</match>
buffer_path
に指定するパスはこの設定のための固有のパスにする必要があります。これで、タグの情報も保存できるようになりました。
Events
テーブルに格納する最後に、イベントそのものの情報を格納するEvents
テーブルです。
GitHubのアクティビティは様々な種類があるため、Sharetary用のイベント情報に変換するためのルールはアクティビティの種類分だけ定義する必要があります。 全てのアクティビティについて完全な変換ルールを定義するのは煩雑なので、共通化できる所はなるべく共通化する方向で設定を組み立てていく事にします。
まず最初に、多くのアクティビティに共通している情報を使って共通のフィールドを埋めます。
copyプラグインによって分岐された3つのストリームのうち、Events
テーブル用に出力しているレコードについて、fluent-plugin-record-reformerの変換ルールを以下のように更新し、共通のフィールドを埋めた状態でレコードを出力するようにします。
<match github-activity.**>
type copy
# Eventsテーブル用のレコード
<store>
type record_reformer
enable_ruby true
tag event.${tag}
<record>
type ${tag_suffix[-1]}
actor ${actor && actor["login"]}
source_icon https://github.com/favicon.ico
timestamp ${created_at}
created_at ${time}
_repository ${repo && repo["name"]}
</record>
</store>
...
</match>
設定のポイントは以下の通りです。
renew_record true
は指定していません。
<record>
で定義した各フィールドは、元のレコードへのフィールド追加、または同名フィールドの値の置き換えになります。type
フィールドの値は、fluent-plugin-github-activitiesが出力したレコードがgithub-activity.push
のようなタグになっている事を利用して、タグの最後のパートをそのまま使います。
tag_suffix[-1]
というプレースホルダを使うと、タグ名の最後のパートを簡単に参照できます。actor
フィールドの値はActors
テーブルに格納したレコードの_key
と同じ値を設定する必要があります。
大多数のアクティビティではこれはactor["login"]
の値ですが、個々のコミットに対応するレコードでだけは、違うフィールドを参照しなくてはなりません。
そのような特例については、個々のコミットに対応するレコードの変換ルールを定義するときにまとめて処理する事にして、ここではシンプルに「フィールドを参照できるならその値を使い、参照できなければnil
にしておく」という書き方にしています。source_icon
の値は、GitHubのfaviconのURIで決め打ちにしています。
他のサービス由来のイベントも収集する場合は、適切な値をその都度指定しましょう。timestamp
フィールドの値は、元レコードのcreated_at
の値があればそれを使い、無ければnil
にするという書き方にしています。
これも、例外となるアクティビティについては別途値を設定します。created_at
フィールドの値は、現在時刻の代替として、Fluentdのレコードに紐付けられている日時情報を参照しています。
(※元レコードのcreated_at
と名前が同じですが、意味合いが異なる物で、こちらはあくまでSharetary用のEvents
テーブルのカラム名です。)Tags
テーブルに格納したレコードの_key
と同じ値をtags
フィールドに設定する必要があります。
これもactor
同様に、アクティビティの種類によって参照先のフィールドを変えなくてはなりません。
ただ、tags
フィールドの値は後から少し加工をしたいので、ここでは一旦_repository
という仮のフィールドに値を設定するに留めています。
(後加工の段階で、これを使ってtags
フィールドの値を埋める事になります。)次に、個々のアクティビティ用の変換ルールを定義します。
例えば、git push
に対応するイベントの変換ルールは、fluent-plugin-record-reformerを使って以下のように書けます。
<match event.github-activity.push>
type record_reformer
enable_ruby true
renew_record true
keep_keys type,actor,source_icon,timestamp,created_at,_repository
tag sharetary.event
<record>
_key https://github.com/${repo["name"]}/compare/${payload["before"]}...${payload["head"]}
class minor
title Push
description Pushed ${payload["size"].to_s} commits to ${repo["name"]}
uri https://github.com/${repo["name"]}/compare/${payload["before"]}...${payload["head"]}
reply_uri https://github.com/${repo["name"]}/compare/${payload["before"]}...${payload["head"]}#commits_bucket
</record>
</match>
push
に固有の情報を埋めるだけの変換ルールになっています。Events
テーブル用のレコードはfluent-plugin-github-activitiesが出力するレコードとは全く異なる形式になるため、renew_record true
として、ゼロからレコードを組み立てるようにしています。
ただし、前の段階ですでに加工を終えた共通のフィールドについてだけは、keep_keys type,actor,source_icon,timestamp,created_at,_repository
として値を引き継いでいます。
このようにする事で、「前の段階で共通のフィールドを埋めておいて、この段階では固有のフィールドだけを埋める」という効率の良い設定が可能となります。Events
テーブル用のレコードとして形式を変換済みということで、タグはsharetary.event
に統一しています。push
以外にも、Issueの投稿、コメントの投稿など、多くのアクティビティの変換ルールは同様の要領で定義する事ができます。別の例として、git commit
に対応するイベントの変換ルールも示します。
<match event.github-activity.commit>
type record_reformer
enable_ruby true
renew_record true
keep_keys type,source_icon,created_at
tag sharetary.event
<record>
_key ${html_url}
class normal
title Commit ${stats["total"].to_s} changes
description ${commit["message"]}
extra_description ${files.collect{|file| "#{file["filename"]} (#{file["status"]})" }.join("\n, ")}\n\n${files.collect{|file| file["patch"] }.join("\n\n")}
actor ${committer["login"]}
uri ${html_url}
reply_uri ${html_url}#new_commit_comment_field
timestamp ${commit["committer"]["date"]}
parent ${self["$github-activities-related-event"] && "https://github.com/#{self["$github-activities-related-event"]["repo"]["name"]}/compare/#{self["$github-activities-related-event"]["payload"]["before"]}...#{self["$github-activities-related-event"]["payload"]["head"]}" || "" }
_repository ${url.match(/repos\/([^\/]+\/[^\/]+)\/commits/)[1]}
</record>
</match>
push
の場合と同じなのですが、keep_keys
でそのまま流用している共通フィールドが、type,source_icon,created_at
だけになっています。
これは、commit
のイベントの元となるGitHub APIの戻り値が、ここまでで参照してきたアクティビティの共通フォーマットとは異なる形式を取っているからです。
actor
, timestamp
, _repository
の各フィールドの値は、ここで改めて、commit
専用のフォーマットになっているレコードから適切な値を参照するようにしています。Sharetaryリポジトリにある完全版の設定ファイルのサンプルには、fluent-plugin-github-activitiesが対応している他のアクティビティも含めた全ての変換ルールが記述されています。 そちらも併せてご参照下さい。
アクティビティごとの変換を終えたら、今度はレコードの内容の微調整のための後処理を行います
まず、仮フィールドの_repository
をtags
フィールドに変換して、イベントをタグ付けします。
これは、Fluentdに標準添付のrecord_transformerプラグインを使って、以下のような<filter>
で実現できます。
<filter sharetary.event>
type record_transformer
enable_ruby false
remove_keys _repository
<record>
tags ["${_repository}"]
</record>
</filter>
record_transformerの設定は、元になったfluent-plugin-record-reformerと同じような書式で記述します。
tags
フィールドの値を、_repository
フィールドの値が単一の要素になっている配列として定義しています。_repository
を、remove_keys _repository
でレコードから削除しています。これだけだと、この段階で変換を行う意味はあまり無いように見えます。
実際、共通フィールドを埋める段階や、各アクティビティ固有のフィールドを埋める段階でも、tags
フィールドを配列として定義することは可能です。
しかし、このようにtags
フィールドの変換を最後の段階に分けておくと、元の情報に含まれていなかった情報を使った、固定のタグの追加が可能になります。
例えば下のようにすれば、各イベントには関連するリポジトリ名のタグだけでなく、sezemi-2015-readable-code
というタグも固定で付与されるようになります。
<filter sharetary.event>
type record_transformer
enable_ruby false
remove_keys _repository
<record>
tags ["${_repository}", "sezemi-2015-readable-code"]
</record>
</filter>
SEゼミの2015年度の取り組みでは4つのサブイベントを開催しますが、サブイベントの名前のタグをこのようにして自動付与すれば、後からサブイベントごとの情報を効率よく検索できるようになります。 また、1つのサブイベントが終わったら上記の後加工段階のフィルタだけを設定し直す事で、参加メンバーが同じだとしても、別のサブイベントの情報を簡単に識別できます。
(最初からtags
フィールドを配列型にしておいて、後処理の段階でtags
の配列に要素を追加する、という形にできればそれが最もスマートなのですが、残念ながら、そのような変換を行えるFluentdプラグインは現時点では無い模様です……そこで、次善の策としてこのようなやり方を考えてみた次第です。)
タグ付けが終わったら、最後の仕上げとして、日時を示すフィールドの値をunixtimeを表す整数値に変換します。 これは、fluent-plugin-filter_typecastプラグインを使って以下のように実現できます。
<filter sharetary.event>
type typecast
types timestamp:time,created_at:time
</filter>
これによって、timestamp
とcreated_at
のフィールドの値が、2015-06-15T00:00:00
のような文字列から、対応するUTCのunixtimeに変換されます。
Groongaでは日時はunixtimeの整数で表現されますので、この変換は必ず行わなくてはなりません。
(でないと、日付の範囲を指定した絞り込みができません。)
Events
テーブルに格納する後処理を終えて全てのフィールドが揃ったら、後はEvents
テーブルにレコードを格納するだけです。
<match sharetary.event>
type groonga
store_table Events
protocol http
host localhost
buffer_type file
buffer_path /var/spool/td-agent/buffer/groonga-sharetary-event
flush_interval 1s
<table>
name Events
flags TABLE_HASH_KEY
key_type ShortText
</table>
<table>
name Timestamps
flags TABLE_PAT_KEY
key_type Time
</table>
<table>
name Tags
flags TABLE_HASH_KEY
key_type ShortText
</table>
<table>
name Actors
flags TABLE_HASH_KEY
key_type ShortText
</table>
<table>
name Terms
flags TABLE_PAT_KEY
key_type ShortText
default_tokenizer TokenBigram
normalizer NormalizerAuto
</table>
<mapping>
name type
type ShortText
</mapping>
<mapping>
name class
type ShortText
</mapping>
<mapping>
name title
type ShortText
<index>
table Terms
name Events_title_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name description
type Text
<index>
table Terms
name Events_description_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name extra_description
type Text
<index>
table Terms
name Events_extra_description_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name tags
type Tags
<index>
table Tags
name Events_tags_index
</index>
</mapping>
<mapping>
name uri
type ShortText
<index>
table Terms
name Events_uri_index
flags WITH_POSITION
</index>
</mapping>
<mapping>
name source_icon
type ShortText
</mapping>
<mapping>
name reply_uri
type ShortText
</mapping>
<mapping>
name actor
type Actors
<index>
table Actors
name Events_actor_index
</index>
</mapping>
<mapping>
name timestamp
type Time
<index>
table Timestamps
name Events_timestamp_index
</index>
</mapping>
<mapping>
name created_at
type Time
<index>
table Timestamps
name Events_created_at_index
</index>
</mapping>
<mapping>
name parent
type ShortText
<index>
table Terms
name Events_parent_index
flags WITH_POSITION
</index>
</mapping>
</match>
buffer_path
に指定するパスをこの設定のための固有のパスにしている点に注意して下さい。Actors
とTags
の両テーブルの定義も含めています。ここまでで設定したすべてのプラグインの関係を図にすると、以下のようになります。
以上、Sharetary 0.3.0をSharetary 0.4.0以降にバージョンアップする際の手順と、Sharetary 0.4.0以降向けにGitHubのアクティビティをクロールするためのFluentdの設定の仕方を解説しました。 その中で、Fluentdのストリームから1つの入力を分岐して複数テーブルにレコードを追加するfluent-plugin-groonga向けの設定の仕方も解説しました。
先日のリーダブルコード勉強会ではまだお試し運用でしたが、今月27日のOSS Hack 4 Beginnersや来月に予定されているOSS Hack Weekendでは、会場内で行われる開発の様子を横断して眺められることでしょう。 これらのイベントに参加を予定されている方は、会場内のサブスクリーンにもご注目ください。
また、クリアコード関係者の公開アクティビティを収集しているSharetary運用サンプルも引き続き運用しております。 クリアコードの普段の開発の様子も是非ご覧になってみて下さい。
Groongaにはcache_limitというコマンドがあります。
cache_limit
を使うと、最新のN件の select
コマンドの結果をキャッシュさせることができます。
しかし、実行した select
コマンドをキャッシュしたのかどうかや、今どんな内容がキャッシュされているかについてはわかりません。
そこで、今回はプラグインを導入することで、簡単にGroongaのキャッシュの中身を覗く方法を紹介します。
/usr/local
以下にGroongaをインストールしてあるという前提で説明します。
% git clone https://github.com/kenhys/groonga-plugin-grncache.git
% cd groonga-plugin-grncache.git
% ./autogen.sh
% ./configure --with-groonga-source-dir=PATH_TO_GROONGA_SOURCE_DIRECTORY
% make
% sudo make install
PATH_TO_GROONGA_SOURCE_DIRECTORY
にはソースアーカイブのパスを指定します。*1
この記事を書いている時点では、Groonga 5.0.4が最新ですので、もし /tmp
以下にソースアーカイブを展開しているなら、configure
の指定は次のようにします。
% ./configure --with-groonga-source-dir=/tmp/groonga-5.0.4
インストールまで完了すると、/usr/local/lib/groonga/plugins/grncache/grncache.so
が存在するはずです。
古いバージョンのGroongaと一緒に試す場合、Groonga 3.0.8*2以降であればこのプラグインが動作するはずです。この制限はGrncacheが使用しているAPIが3.0.8以降に追加されたためです。
実際にキャッシュの中身を覗いてみるまえに、プラグインを登録しておきましょう。
プラグインの登録には plugin_register
コマンド *3 を実行します。 Groongaを対話的に起動している場合は次のようにします。
> plugin_register grncache/grncache
HTTPサーバーとして起動している状態なら、次のようにします。
% curl http://localhost:10041/d/plugin_register?name=grncache/grncache
動作を確認するのに、サンプルとなるデータを登録しておきましょう。次のコマンドをGroongaの対話モードで実行します。
> table_create Site TABLE_HASH_KEY ShortText
> column_create --table Site --name title --type ShortText
> load --table Site
[
{"_key":"http://example.org/","title":"This is test record 1!"},
{"_key":"http://example.net/","title":"test record 2."},
{"_key":"http://example.com/","title":"test test record three."},
{"_key":"http://example.net/afr","title":"test record four."},
{"_key":"http://example.org/aba","title":"test test test record five."},
{"_key":"http://example.com/rab","title":"test test test test record six."},
{"_key":"http://example.net/atv","title":"test test test record seven."},
{"_key":"http://example.org/gat","title":"test test record eight."},
{"_key":"http://example.com/vdw","title":"test test record nine."},
]
キャッシュの状態を確認するには、grncache status
を実行します。
> grncache status
[[0,1435303725.13405,0.000181674957275391],{"cache_entries":0,"max_cache_entries":100,"cache_fetched":0,"cache_hit":0,"cache_hit_rate":0.0}]
それぞれの項目の内容は次の通りです。
項目 | 説明 |
---|---|
cache_entries |
キャッシュされた検索結果の数 |
max_cache_entries |
キャッシュ可能な検索結果の最大数。cache_limit で変更できる |
cache_fetched |
キャッシュを参照した回数。select を実行するたびに増加する |
cache_hit |
キャッシュにヒットした回数 |
cache_hit_rate |
キャッシュにヒットした割合 (cache_hit /cache_fetched で算出される) |
初期状態なので max_cache_entries
を除いてすべて0です。
キャッシュの中身をダンプすることもできます。それには、grncache dump
を実行します。初期状態なのでこちらも0件です。
> grncache dump
[[0,1435303923.9083,8.46385955810547e-05],[[0]]]
では、select
コマンドを実行してみましょう。
> select Site
[[0,1435303997.02583,0.000574111938476562],[[[9],[["_id","UInt32"],["_key","ShortText"],["title","ShortText"]],[1,"http://example.org/","This is test record 1!"],[2,"http://example.net/","test record 2."],[3,"http://example.com/","test test record three."],[4,"http://example.net/afr","test record four."],[5,"http://example.org/aba","test test test record five."],[6,"http://example.com/rab","test test test test record six."],[7,"http://example.net/atv","test test test record seven."],[8,"http://example.org/gat","test test record eight."],[9,"http://example.com/vdw","test test record nine."]]]]
grncache status
を実行してみると、cache_entries
が 1に増え、select Site
の結果がキャッシュされたことがわかります。
> grncache status
[[0,1435304001.04143,0.000191450119018555],{"cache_entries":1,"max_cache_entries":100,"cache_fetched":1,"cache_hit":0,"cache_hit_rate":0.0}]
キャッシュの中身をダンプしてみましょう。
> grncache dump
[[0,1435304009.74493,0.000130653381347656],[[1],[{"grn_id":1,"nref":0,"timeval":"2015-06-26 16:33:17.025827","value":"[[[9],[[\"_id\",\"UInt32\"],[\"_key\",\"ShortText\"],[\"title\",\"ShortText\"]],[1,\"http://example.org/\",\"This is test record 1!\"],[2,\"http://example.net/\",\"test record 2.\"],[3,\"http://example.com/\",\"test test record three.\"],[4,\"http://example.net/afr\",\"test record four.\"],[5,\"http://example.org/aba\",\"test test test record five.\"],[6,\"http://example.com/rab\",\"test test test test record six.\"],[7,\"http://example.net/atv\",\"test test test record seven.\"],[8,\"http://example.org/gat\",\"test test record eight.\"],[9,\"http://example.com/vdw\",\"test test record nine.\"]]]"}]]
[[1], [{"grn_id":1,"nref":0,"timeval":...,"value":...
という箇所があるのがわかります。
[1]
というのがキャッシュエントリの数です。 "value":...
というのがキャッシュされた検索結果です。先程実行した select Site
と結果が一致していることがわかります。
もう一度同じ検索をした後だとどうなるでしょうか。
> grncache status
[[0,1435304396.55938,0.000149011611938477],,{"cache_entries":1,"max_cache_entries":100,"cache_fetched":2,"cache_hit":1,"cache_hit_rate":50.0}]
予想通り、cache_fetched
と cache_hit
が増え、キャッシュヒット率が 50% となりました。同じ検索なので、キャッシュ済みの内容をそのまま返してきたことがわかります。
Groongaがキャッシュしている内容をプラグインを使って確認する方法を紹介しました。 今どんな内容がGroongaによってキャッシュされているのかを確認したいときには参考にしてみてください。
*1 GrncacheはGroongaの内部構造に強く依存しているため、ソースコードを参照する必要があります。
*2 ちなみにGroonga 3.0.8がリリースされたのは、2013年9月29日のこと。
*3 Groonga 5.0.0以前の場合には register コマンド