以前、オープンソースのカラムストア機能付き全文検索エンジンであるGroongaをDebianに入れるために必要な作業について、最初のとっかかりであるWNPPへのバグ登録やDebianらしいパッケージかどうかチェックするためのLintianについて紹介記事を書きました。
今回は、現在進行中であるGroongaのDebianリポジトリ入りを目指す作業の中から、Debianパッケージのアップロード先となるmentors.debian.netの使いかたについて紹介します。
WNPPにバグ登録をしたのち、debパッケージを用意するところまでできたら、次はパッケージを公開します。これはスポンサーに作ったパッケージをレビューしてもらう必要があるためです。
Groongaの場合には、Debian開発者であるやまねさんにスポンサーしてもらえることになっていたので、スポンサー探しに苦労することはありませんでした。
パッケージのレビューをしてもらうときに、パッケージのアップロード先として利用するのがmentors.debian.netです。パッケージをアップロードするには次のような手順を踏みます。
まずはmentors.debian.netにアカウントを作成します。そして、パッケージの署名に使うGPGの鍵を登録します。登録する際は次のコマンドを実行した結果をアカウント詳細ページにてアップロードします。
$ gpg --export --export-options export-minimal --armor (鍵のID)
アカウントの登録が済んだら、次はアップロードに必要な初期設定をします。
パッケージをmentors.debian.netへとアップロードするにはdputを使います。アップロード先の設定は、~/.dput.cfに行います。
[mentors] fqdn = mentors.debian.net incoming = /upload method = http allow_unsigned_uploads = 0 progress_indicator = 2 # Allow uploads for UNRELEASED packages allowed_distributions = .*
これで、パッケージをアップロードするための準備が整いました。次はパッケージをアップロードします。
.dput.cfの設定ができたらパッケージをアップロードします。その際には、引数としてアップロード先であるmentorsと.changesファイルを指定します。
$ dput mentors ../groonga_4.0.0-1_amd64.changes Checking signature on .changes gpg: Signature made Mon 10 Feb 2014 08:59:20 AM UTC using RSA key ID 3455D448 gpg: Good signature from "HAYASHI Kentaro <hayashi@clear-code.com>" Good signature on ../groonga_4.0.0-1_amd64.changes. Checking signature on .dsc gpg: Signature made Mon 10 Feb 2014 08:59:13 AM UTC using RSA key ID 3455D448 gpg: Good signature from "HAYASHI Kentaro <hayashi@clear-code.com>" Good signature on ../groonga_4.0.0-1.dsc. Uploading to mentors (via ftp to mentors.debian.net): Uploading groonga_4.0.0-1.dsc: done. Uploading groonga_4.0.0.orig.tar.gz: done. Uploading groonga_4.0.0-1.debian.tar.xz: done. Uploading groonga_4.0.0-1_amd64.deb: done. Uploading groonga-server-common_4.0.0-1_amd64.deb: done. Uploading groonga-server-http_4.0.0-1_amd64.deb: done. Uploading groonga-server-gqtp_4.0.0-1_amd64.deb: done. Uploading libgroonga-dev_4.0.0-1_amd64.deb: done. Uploading libgroonga0_4.0.0-1_amd64.deb: done. Uploading groonga-tokenizer-mecab_4.0.0-1_amd64.deb: done. Uploading groonga-plugin-suggest_4.0.0-1_amd64.deb: done. Uploading groonga-bin_4.0.0-1_amd64.deb: done. Uploading groonga-httpd_4.0.0-1_amd64.deb: done. Uploading groonga-doc_4.0.0-1_all.deb: done. Uploading groonga-examples_4.0.0-1_all.deb: done. Uploading groonga-munin-plugins_4.0.0-1_amd64.deb: done. Uploading groonga_4.0.0-1_amd64.changes: done. Successfully uploaded packages.
アップロードに成功してしばらくすると、パッケージごとの個別ページが参照できます。パッケージのページではLintianによるチェック結果を参照できます。 具体的な結果については Groongaの例 が参考になるでしょう。
もしdputしたときに、アップロード先としてmentorsの指定を忘れるとどうなるでしょうか。そのときはdputのデフォルトのアップロード先にアップロードされてしまいます。/etc/dput.cfの[DEFAULT]欄が何も指定していなかったときのアップロード先です。
その場合には、dcutコマンドを使って取り消します。dcutコマンドを使うと削除に必要なコマンドファイルをアップロードします。アップロード先では、定期的にコマンドファイルを処理しているので、しばらくするとコマンドファイルに従ってファイルを削除してくれます。
例えば、間違ってデフォルトの設定でアップロードしてしまった場合、次のようにしてdcutで取り消します*1。
$ dcut -m hayashi@clear-code.com -i groonga_4.0.0-1_amd64.changes signfile /tmp/dcut.jNuDdW/dcut.hayashi_clear_code_com.1392865192.3377.commands hayashi@clear-code.com You need a passphrase to unlock the secret key for user: "HAYASHI Kentaro <hayashi@clear-code.com>" 4096-bit RSA key, ID 3455D448, created 2012-04-24 gpg: gpg-agent is not available in this session Successfully signed commands file Uploading dcut.hayashi_clear_code_com.1392865192.3377.commands: done.
ただし、上記ができるのは、間違ってアップロードしてしまったときの.changesが残っている場合です。うっかり.changesを削除してしまった場合には使えません。デフォルトのアップロード先がDebian公式のUploadQueueだったため、このミスをしたときはftp-masterに削除を依頼する必要がありました。
今回はdebパッケージをスポンサーにレビューしてもらうときに利用するmentors.debian.netの使いかたを紹介しました。パッケージをmentors.debian.netにアップロードしたら、スポンサーにレビューしてもらう必要があります。スポンサーによる指摘事項があれば都度修正して、きちんとしたパッケージに仕上げていく作業が必要です。それらについては、またの機会に記事にします。
*1 -mオプションにはGPGの鍵にひもづいたメールアドレスを指定します。
2014/07/06にプログラミングが好きな学生のためのリーダブルコード勉強会の2回目を開催しました。内容を知りたい方は資料やこれまでの記事を読んでください。
今回も9割以上の参加者が満足してくれたようです*1。よかったです。
これで今年のSEゼミでのリーダブルコード勉強会は終了です。今後もあるかどうかはわかりません*2。しかし、今回の勉強会の資料はCC BY-SA 4.0*3のライセンスで自由に利用できる*4ので、内容に興味がある人は自由に資料を使って自分たちで開催してみてください。
自分たちで開催するときのヒントになるように、実際に開催してわかったことや工夫したことを説明します。参考にしてください。
わかったことは次の通りです。
工夫したことは次の通りです。
課題について補足します。
課題は徐々に改良していく課題になることを重視しました。リーダブルコードの必要性を感じることが多いのは既存のコードを改良していくフェーズだからです。既存のコードを改良していくためには既存のコードを読まなければいけません。このときにリーダブルコードかどうかが効いてきます。
そのため、技術的な難易度は下げました。具体的にいうと、言語の基本的な機能だけを使えば十分な課題にしました。例えば、データは文字列だけにして、さらに文字列操作はなるべくしなくても済むようにしました。C言語で動的にメモリーを割り当てないで済むように、文字列長の最大サイズなども決めました。引数がとても多い実行例になっているのも同じ理由からです*6。
技術的な難易度は下げたのですが、「プログラムの抽象度」を上げていかないと読みにくいプログラムになるような内容になっています。もう少し言うと、「1個だけ扱っていたものをn個扱うようになる」というのを繰り返すようになっています。例えば次の通りです。
プログラムの抽象度を上げていくには既存のコードを改良しないといけません。そうするとコードを読む必要がでてきて、リーダブルコードを意識せざるを得ない、ということを狙っています。
課題をやってみた人は気づいていましたか?
主催のSEプラスさん、スポンサーの楽天さん、クックパッドさん、DeNAさん、メンターの川原さん、たなべさん、参加した学生のみなさん、ありがとうございました。いい経験になりました。参加者のみなさんがこれからリーダブルコード力を上げる助けになれたならやった甲斐があるというものです。
勉強会の内容を考え、実際にやってみた側としては、メンターの川原さんとたなべさんが「楽しかった」と言ってくれて非常にうれしかったです。
勉強会中で出てきた話題に関連するURLを紹介します。
*1 アンケート結果より。「大変不満だった」「不満だった」「ふつう」「満足した」「大変満足した」の5段階評価で回答してもらいました。前回は「満足した」の人数が一番多かったのが、今回は「大変満足した」の人数が一番多かったことに驚きました。
*2 企業研修などでやってみたいという方はお問い合わせください。
*3 著作者は「株式会社クリアコード」。
*4 著作者を表示すればコピーしたり再配布したりできる。商用利用もできる。変更後も同じライセンスにするなら変更してもよい。
*5 たとえば、参加者数が少なくてチェンジできる選択肢が少ない時。
*6 「例」なので、実行例通りに実装しなくてもよいのですが、多くの人は実行例通り実装していたようです。
*7 横山さんはメンターでも参加者でもありません。Web上に公開されている情報を見て趣味で実装したものです。たぶん。
昨年11月29日に開催したイベント「全文検索エンジンGroongaを囲む夕べ 4」 において、Groonga族の新たな一員としてDroongaが加わった事をお知らせしました。その後現在に至るまでにリリースを着実に重ね、現在はバージョン1.0.4となっているのですが、その間に方針や開発の優先度が変わってきている事についてのきちんとしたアナウンスができておらず、今改めてDroongaの事を知ろうとした時に「で、結局これって何なのよ?」ということが分かりにくくなってしまっています。
この記事は、そんなDroongaの現時点での情報を一旦整理して、特にGroongaを実運用されている方にとって「Droongaって何なの? どう便利になるの?」ということが一目で分かるようにする物です。Droongaの現状まとめとしてご参照下さい。
Droongaは端的に言うと、「Distributedな(分散型の)Groonga」ということになります。詳しくご説明しましょう。
Groongaは全文検索エンジンであり、カラム指向のデータベースでもあります。アプリケーション組み込みのライブラリやHTTPサーバとして動作して、ユーザのリクエストに応じてデータの読み書きと検索を処理することができます。
簡単に利用し始められるのが魅力のGroongaですが、大規模なサービスのバックエンドとしてGroongaを安定運用するには若干の工夫が必要です。具体的には、Groonga自体にはレプリケーションなどの機能は含まれないため、耐障害性を高めるためやアクセス数の増加に対応するためなどの目的でデータベースを冗長化するには、複数のGroongaサーバを何らかの方法で協調動作させる必要があります。
Droongaは現在の所、Groongaの上記の問題点が解決された移行先となることを目指して開発が進められています。そのため、現時点で既に以下のような特徴を備えています。
Droongaは、GroongaのHTTPインターフェースと互換性があります。そのため、フロントエンドとなるWebアプリケーションの改修の必要はありません。
ただし、現時点では以下の制限事項があります。
これらの未対応項目については、将来的には互換性を改善していく予定です。とはいえ、現在Groongaを運用している中で使っている機能がDroongaが対応している機能の範囲内に収まっているのであれば、すぐにでもそのままDroongaへ移行することができます。GroongaからDroongaへの移行を検討する際には、まずこの点をチェックしてみて下さい。
Droongaは複数ノードによるクラスタとして動作しますので、もちろん、基本機能としてレプリケーションにも対応しています。レプリケーション数を増やすことで、耐障害性の高い運用体制をとれます。
また、ノードの追加・削除を簡単に行えるため、急なアクセス数の増減にも対応しやすいという利点もあります。アクセス数の増大に応じてノードを追加することにより、安定したスループットを維持できます。
ここまでの説明を見て、「発表当初のDroongaと目的が変わっているのではないか?」と思われた方もいるかもしれません。この点についても説明しておきましょう。
発表当初、Droongaは「内部的にGroongaを使用した、汎用の分散データ処理システム」という位置付けで、Groongaとの互換性についてはそれほど重視はしていませんでした。もちろん、汎用システムの一つの応用形態としてGroonga互換の検索システムとしても利用できるようになるということは想定していましたが、そのための作業の優先度はそれほど高くはありませんでした。それよりも、汎用システムとしての基盤部分を固めていくことを優先しようというのが、当初の目標設定でした。
しかし、実際に開発を進めていくうちに、ノード構成の管理やレプリケーションなどの基盤部分の開発を進めていく上では、具体的な利用シーンを設定した方がそれらの開発を進めやすい(たくさんあるやるべき事の中から「まずここから実装していった方がよい」という判断を行いやすい)という事に気がついてきました。また、実運用に即した機能が早めに出揃うことで、実運用を開始してみないと気がつかない種類の不具合を見つけやすくなるというメリットもあります。
そこで、Droongaプロジェクトでは当面のところ、「Groonga互換の検索システム」を構築するという場面を想定して、開発やドキュメントの整備を進めることにしました。「汎用の分散型データ処理システム」としての開発自体を諦めたというわけでは決してありませんので、その点についてはご安心下さい。
以上、Droongaの現状について簡単に紹介しました。ここからは、実際にDroongaを利用するための手順を解説します。DroongaがGroonga互換の検索システムとして動作することを、まずはお手元の環境で実際に試してみて下さい。
ここでの手順は、大まかに言って以下の通りです。
Droongaクラスタは、レプリケーション機能を利用するためにサーバを2台使うことにします。以下の説明では、サーバとしてUbuntu 14.04LTSがインストールされたサーバが2台あり、それぞれのIPアドレスが192.168.0.10と192.168.0.11であると仮定します。IPアドレスなどは、お手元の環境に合わせて説明を適宜読み替えて下さい。
まず、Groongaをバックエンドに使って、TODOリストの作成と検索を行う簡単なWebアプリケーションを作成してみましょう。Groongaは複数台のサーバでの動作に対応していませんので、ここでは192.168.0.10の1台だけを使うことにします。
最初に、インストール手順の説明に従ってGroongaをインストールします。ここではPPAを使ってインストールする例を示します。
% sudo apt-get -y install software-properties-common % sudo add-apt-repository -y universe % sudo add-apt-repository -y ppa:groonga/ppa % sudo apt-get update % sudo apt-get -y install groonga
次に、データベースを作成します。 ~/groonga/db/ 以下にデータベースを置くことにします。
% mkdir -p $HOME/groonga/db/ % groonga -n $HOME/groonga/db/db quit
各テーブルも定義します。
% groonga $HOME/groonga/db/db table_create --name Topic --flags TABLE_PAT_KEY --key_type ShortText % groonga $HOME/groonga/db/db column_create --table Topic --name title --flags COLUMN_SCALAR --type ShortText % groonga $HOME/groonga/db/db table_create --name Term --flags TABLE_PAT_KEY --key_type ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto % groonga $HOME/groonga/db/db column_create --table Term --name topic_title --flags "COLUMN_INDEX|WITH_POSITION" --type Topic --source title
データベースができたら、GroongaをHTTPサーバとして起動します。
% groonga -p 10041 -d --protocol http $HOME/groonga/db/db
これでバックエンドの準備ができました。続いて、フロントエンドとなるWebアプリケーションを作成します。説明を簡単にするため、アプリケーションの機能はWebページ中に埋め込んだJavaScriptだけで実装することにします。
まずWebページを作成します。
% mkdir ~/groonga/public/ % vi ~/groonga/public/index.html
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 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 |
<!DOCTYPE html> <meta charset="UTF-8"> <title>TODO List</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <p><input type="text" size="30" id="title-field"><button id="add-button">登録</button></p> <p><input type="text" size="20" id="search-field"> <button id="search-button">検索</button></p> <div id="result"></div> <script type="text/javascript"><!-- var base = 'http://' + location.hostname + ':10041'; // レコードを追加する。 $('#add-button').click(function() { var title = $('#title-field').val(); if (!title) return; $.ajax({ url: base + '/d/load', data: { table: 'Topic', values: JSON.stringify([{ _key: title, title: title }]) }, dataType: 'jsonp', success: function() { $('#title-field').val(''); } }); }); // レコードを検索する。 $('#search-button').click(function() { var data = { table: 'Topic', output_columns: 'title', limit: 10 }; var term = $('#search-field').val(); if (term) data.filter = 'title @ ' + JSON.stringify(term); $.ajax({ url: base + '/d/select', data: data, dataType: 'jsonp', success: function(result) { if (!result) return; var body = result[1]; var searchResult = body[0]; var count = searchResult[0][0]; var topics = searchResult.slice(2); $('#result') .empty() .append($('<p>').text(count + '件見つかりました。')) .append($('<ul>') .append(topics.map(function(topic) { var title = topic[0]; return $('<li>').text(title); }))); } }); }); // --></script> |
Webページの準備ができたら、Webサーバを起動します。設定ファイルなどを用意しなくてもよく簡単に起動できるため、Rubyの標準機能を使ったWebサーバを使います。
% ruby -run -e httpd -- --port 8080 groonga/public &
Webブラウザを起動し、早速Webアプリケーションにアクセスしてみましょう。URLは 「http://192.168.0.10:8080/index.html」です。
試しにTODOタスクを追加してみましょう。「追加」ボタンの左の入力欄に「バナナを買う」と入力して「追加」ボタンをクリックします。すると、レコードが追加されて入力欄が空になります。続けて「リンゴを買う」「牛乳を買う」も追加しましょう。
タスクの追加が終わったら、検索してみましょう。まずは、何も入力せずに「検索」ボタンをクリックします。すると、登録されているすべてのレコードのうち先頭10件が表示されます。(現在は3件しかレコードがないので、3件すべてのレコードが表示されます。)
続いて、全文検索もしてみましょう。「検索」ボタンの左の入力欄に「牛乳」と入力してから「検索」ボタンをクリックします。すると、登録されているレコードのうち、内容に「牛乳」を含んでいるレコードの先頭10件が表示されます。(現在は1件しか該当するレコードがないので、レコードが1件だけ表示されます。)
ということで、無事にTODOリストの管理アプリケーションが実装されました。
それでは、先のGroongaと互換性があるDroongaクラスタを構築してみましょう。
192.168.0.10と192.168.0.11の両方に、Droongaを構成するパッケージをインストールします。詳細はチュートリアルを参照してください。
(on 192.168.0.10, 192.168.0.11) % sudo apt-get update % sudo apt-get -y upgrade % sudo apt-get install -y libgroonga-dev ruby ruby-dev build-essential nodejs nodejs-legacy npm % sudo gem install droonga-engine % sudo npm install -g droonga-http-server % mkdir ~/droonga
パッケージのインストールが完了したら、catalog.jsonを作成します。これは、Droongaクラスタのノード構成の設定が書かれたファイルです。droonga-engineパッケージに含まれているdroonga-engine-catalog-generateコマンドを使って簡単に作成することができます。 --hostsオプションには、クラスタを構成するすべてのノードのIPアドレス(またはホスト名)をカンマ区切りで指定します。この操作も、2台のサーバ両方で行ってください。
(on 192.168.0.10, 192.168.0.11) % droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.11 --output=~/droonga/catalog.json
catalog.jsonができたら、それぞれのサーバ上でDroongaのサービスを起動します。Droongaはdroonga-engineとdroonga-http-serverという2つのサービスに別れており、それぞれ個別に起動する必要があります。以下に、192.168.0.10で実行するコマンドを示します。
(on 192.168.0.10) % host=192.168.0.10 % export DROONGA_BASE_DIR=$HOME/droonga % droonga-engine --host=$host \ --log-file=$DROONGA_BASE_DIR/droonga-engine.log \ --daemon \ --pid-file=$DROONGA_BASE_DIR/droonga-engine.pid % env NODE_ENV=production \ droonga-http-server --port=10042 \ --receive-host-name=$host \ --droonga-engine-host-name=$host \ --daemon \ --pid-file=$DROONGA_BASE_DIR/droonga-http-server.pid
起動オプションでそのノード自身のIPアドレスを指定していることにも注意して下さい。192.168.0.11では、起動オプションに含めるホストのIPアドレスを変える必要があります。
(on 192.168.0.11) % host=192.168.0.11 % export DROONGA_BASE_DIR=$HOME/droonga % droonga-engine --host=$host \ --log-file=$DROONGA_BASE_DIR/droonga-engine.log \ --daemon \ --pid-file=$DROONGA_BASE_DIR/droonga-engine.pid % env NODE_ENV=production \ droonga-http-server --port=10042 \ --receive-host-name=$host \ --droonga-engine-host-name=$host \ --daemon \ --pid-file=$DROONGA_BASE_DIR/droonga-http-server.pid
これで、Droongaクラスタが動作し始めました。DroongaのHTTP APIにアクセスしてみて以下のような結果を得られれば、Droongaクラスタは正常に動作しています。
% curl "http://192.168.0.10:10042/droonga/system/status" { "nodes": { "192.168.0.10:10031/droonga": { "live": true }, "192.168.0.11:10031/droonga": { "live": true } } }
Droongaクラスタを構築できたので、次は、Groongaデータベース内に定義済みのテーブルや投入済みのデータをDroongaクラスタに引き継ぎます。
まず、データの引き継ぎに必要なツールをインストールします。Groongaのダンプ形式をDroongaのリクエスト形式に変換するコマンドgrn2drnを含む同名パッケージと、DroongaのリクエストをDroongaクラスタに送信するコマンドであるdroonga-requestを含んでいるdroonga-clientパッケージの、2つのGemパッケージをインストールしましょう。
% sudo gem install grn2drn droonga-client
ツールがインストールされたら、Groongaのデータベースの内容をダンプ出力し、Droongaクラスタへ流し込みます。
% grndump ~/groonga/db/db | grn2drn | droonga-request --host=192.168.0.10
以上で、データの引き継ぎは完了です。ダンプ出力を通じて、テーブルの定義も含めてGroongaのデータベース内のすべての情報がDroongaクラスタに引き継がれました。
それではいよいよ、WebアプリケーションのバックエンドをDroongaに切り替えてみましょう。~/groonga/public/index.html でエンドポイントとして参照しているGroongaのHTTPサーバの接続先を、以下のようにしてDroongaの物に書き換えて下さい。
1 2 |
- var base = 'http://' + location.hostname + ':10041'; + var base = 'http://' + location.hostname + ':10042'; |
ファイルを編集したら、Webアプリケーションのページ( http://192.168.0.10:8080/index.html )をブラウザ上で再読み込みします。
これで、WebアプリケーションのバックエンドがGroongaからDroongaに切り替わりました。
それでは動作を試してみましょう。まずはデータが正常に引き継がれたことを確認するために、何も入力せずに「検索」ボタンをクリックします。すると、Groongaのデータベースから引き継いだレコード3つが検索結果として表示されます。
次はレコードの追加です。「追加」ボタンの左の入力欄に「ぶどうを買う」「パイナップルを買う」「コーヒー牛乳を買う」とそれぞれ入力し、レコードを追加して下さい。
タスクの追加が終わったら、検索してみましょう。何も入力せずに「検索」ボタンをクリックすると、登録されているすべてのレコードのうち先頭10件が表示されます。ここでは、Groongaのデータベースから引き継いだレコード3つと、今追加したレコード3つを合わせて、計6つのレコードが表示されます。
続いて、全文検索もしてみましょう。「検索」ボタンの左の入力欄に「牛乳」と入力してから「検索」ボタンをクリックすると、登録されているレコードのうち、内容に「牛乳」を含んでいるレコードの先頭10件が表示されます。ここでは、Groongaのデータベースから引き継いだレコード1つと、今追加したレコードのうちの1つの、計2つのレコードが検索結果として表示されます。
ということで、以上の一連の操作を通じて、DroongaのHTTPインターフェースはGroongaと互換性があるという事と、GroongaからDroongaへはデータを容易に引き継げるという事を確認できました。
なお、この状態ですでに2台構成のレプリケーションが実現されているため、仮にDroongaノードの片方が停止してもWebアプリケーションは正常に動作し続けますし、アクセス数が増大した場合でも2台のノードで処理を分担することができます。
以上、Droongaの概要の紹介と、Droongaクラスタを構築してGroongaサーバをDroongaクラスタに移行する手順を簡単に解説しました。Droongaを少しでも身近に感じていただけたら、また、Groongaからの移行先としてDroongaを検討していただけたら幸いです。
最後に、イベントの告知もしておきます。
この記事で述べたような情報に加えて、Droongaの現状や今後の展望についてより詳細な紹介を行うイベント「Droonga Meetup」を7月30日水曜日 夜8時から開催します(参加無料)。 質疑応答の時間を長めに取る予定ですので、Groongaユーザの方々の生の声をぜひお聞かせ下さい。 イベントの詳細情報やお申し込みは、Doorkeeperのイベント案内ページをご覧下さい。
オープンソースのマルチメディアフレームワークとしてGStreamerがあります。音声・動画の再生、フォーマットの変換、録音・録画など基本的なことはもちろん、RTSPなどを用いたネットワーク通信を行うこともできます。
以前、あしたのオープンソース研究所: GStreamerという記事でGStreamerの概要や仕組みについて紹介しました。 今回はGStreamerで音声・動画の再生等を複数のエレメントを組合せて使うときに必要なパイプラインの組み立てかたについて紹介します。
あしたのオープンソース研究所: GStreamerでも触れたように、GStreamerの概要を理解するために大事な概念は以下の4つです*1。
ただ、GStreamerで動画を再生するだけならplaybinを使うことで上記の概念を意識することなく簡単に再生できます。その際は、GStreamerに付属しているgst-launch-1.0を使います。Ubuntuのexample-contentパッケージにOggのサンプル動画が含まれているのでそれを再生してみましょう。次のコマンドを実行するとOgg動画を再生できます。
% gst-launch-1.0 playbin uri="file:///usr/share/example-content/Ubuntu_Free_Culture_Showcase/How fast.ogg"
playbinを使う場合には、動画を引数とするだけで簡単に再生できることがわかりました。では、個々のエレメントを組みあわせて動画再生したいときにはどのようにすればいいのでしょうか。
基本的には次の手順でエレメントをつないでパイプラインを組み立てることができます。
まずは、動画の情報を確認しましょう。mediainfoコマンドを使うと動画の情報を知ることができます。
% mediainfo /usr/share/example-content/Ubuntu_Free_Culture_Showcase/How\ fast.ogg General Complete name : /usr/share/example-content/Ubuntu_Free_Culture_Showcase/How fast.ogg Format : OGG File size : 1.83 MiB Duration : 30s 584ms Overall bit rate mode : Variable Overall bit rate : 501 Kbps Writing application : VLC media player Video ID : 314165387 (0x12B9C88B) Format : Theora Duration : 30s 597ms Bit rate : 344 Kbps Nominal bit rate : 800 Kbps Width : 1 280 pixels Height : 720 pixels Display aspect ratio : 16:9 Frame rate : 29.970 fps Compression mode : Lossy Bits/(Pixel*Frame) : 0.012 Stream size : 1.26 MiB (69%) Writing library : Xiph.Org libtheora 1.1 20090822 (Thusnelda) Audio ID : 314165386 (0x12B9C88A) Format : Vorbis Format settings, Floor : 1 Duration : 30s 584ms Bit rate mode : Variable Bit rate : 128 Kbps Channel(s) : 2 channels Sampling rate : 44.1 KHz Compression mode : Lossy Stream size : 478 KiB (26%) Writing library : libVorbis (Everywhere) (20100325 (Everywhere))
Ogg動画であることと、映像のフォーマットがTheora、音声のコーデックはVorbisであることがわかります。
動画の音声と映像を両方というのはひとまずおいて、映像だけ再生することをまず考えてみましょう。エレメントの詳細を調べるのには、GStreamerに付属しているgst-inspect-1.0を使います。
まずは、Oggに関係のあるエレメントをgst-inspect-1.0を使って探してみます。動画から映像を抜き出すには通常demuxerを使います。なので、oggdemuxというエレメントを使うとよさそうです。
% gst-inspect-1.0 | grep ogg typefindfunctions: application/x-id3v2: mp3, mp2, mp1, mpga, ogg, flac, tta typefindfunctions: application/x-id3v1: mp3, mp2, mp1, mpga, ogg, flac, tta typefindfunctions: application/ogg: ogg, oga, ogv, ogm, ogx, spx, anx, axa, axv typefindfunctions: application/x-ogg-skeleton: no extensions libav: avmux_ogg: libav Ogg muxer (not recommended, use oggmux instead) ogg: oggdemux: Ogg demuxer ogg: oggmux: Ogg muxer ogg: ogmaudioparse: OGM audio stream parser ogg: ogmvideoparse: OGM video stream parser ogg: ogmtextparse: OGM text stream parser ogg: oggparse: Ogg parser ogg: oggaviparse: Ogg AVI parser
では、oggdemuxがどんな機能を持っているかを確認してみましょう。それにはシンクとソースのCapabilities欄を見るとわかります。シンクではaudio/oggやvideo/oggに対応していることがわかります。また、ソースには制限がないことがわかります。
% gst-inspect-1.0 oggdemux (途中略) Pad Templates: SINK template: 'sink' Availability: Always Capabilities: application/ogg audio/ogg video/ogg application/kate SRC template: 'src_%08x' Availability: Sometimes Capabilities: ANY
次はoggdemuxで抜きだした映像をデコードしてやる必要があります。映像のフォーマットはTheoraでした。gst-inspect-1.0で同じように探してみましょう。
% gst-inspect-1.0 | grep theora theora: theoradec: Theora video decoder theora: theoraenc: Theora video encoder theora: theoraparse: Theora video parser typefindfunctions: video/x-theora: no extensions rtp: rtptheoradepay: RTP Theora depayloader rtp: rtptheorapay: RTP Theora payloader
theoradecというdecoderがあるのがわかりました。theoradecのCapabilitiesをgst-inspect-1.0で確認してみましょう。
% gst-inspect-1.0 theoradec (途中略) Pad Templates: SRC template: 'src' Availability: Always Capabilities: video/x-raw format: { I420, Y42B, Y444 } framerate: [ 0/1, 2147483647/1 ] width: [ 1, 2147483647 ] height: [ 1, 2147483647 ] SINK template: 'sink' Availability: Always Capabilities: video/x-theora
シンクがvideo/x-theoraに対応していて、ソースがvideo/x-rawに対応していることがわかります。
次は、video/x-rawに対応した表示のためのシンクエレメントを探せばよいことになります。先に答えを言うとxvimagesinkがvideo/x-rawに対応したシンクエレメントです*2。
% gst-inspect-1.0 xvimagesink (途中略) Pad Templates: SINK template: 'sink' Availability: Always Capabilities: video/x-raw framerate: [ 0/1, 2147483647/1 ] width: [ 1, 2147483647 ] height: [ 1, 2147483647 ]
というわけで、上記の情報から次のようなパイプラインを組み立てれば良いことがわかります。
% gst-launch-1.0 filesrc location="/usr/share/example-content/Ubuntu_Free_Culture_Showcase/How fast.ogg" ! oggdemux ! theoradec ! xvimagesink
実際に上記コマンドを実行すると動画がきちんと再生できました。
今回はGStreamerのエレメントをつないだパイプラインの組み立てかたを紹介しました。gst-inspect-1.0を使うことで、エレメントの持つ機能を詳しく知り、どのエレメントとつなげることができるかがわかります。エレメント名にautoがついているエレメントはそのあたりを気にせずに使えて便利ですが、パイプラインがどこまできちんと動作しているかを知りたいときなど、明示的に特定のエレメントを使って確認する必要にせまられることもあります。そんなときに、どうやって組みたてたら良いか知っていると問題解決に役立つのではないでしょうか。
LXCコンテナを複数作って、開発に使用しているとIPアドレスではなく名前で各コンテナにアクセスしたくなります。
もちろんlxc-consoleを使えば名前でアクセスできるのですがlxc-consoleではC-a
が取られてしまって不便です。
そこで、SSHを使って名前で各コンテナにアクセスできるととても便利です。
dnsmasqを使うと簡単にそのような環境を構築することができるので、その手順を紹介します。 なお、環境はDebian sidを想定しています。
Ubuntuだとlxcを入れるだけでやってくれる内容なのでUbuntu12.04でやっていることを調べてDebian向けにアレンジしています。
$ sudo apt-get install -y lxc dnsmasq-base bridge-utils
$ sudo brctl addbr lxcbr0
/etc/network/interfaces:
... auto lxcbr0 iface lxcbr0 inet static address 192.168.30.1 netmask 255.255.255.0 pre-up /path/to/lxcbr0-pre-up post-up /path/to/lxcbr0-post-up post-down /path/to/lxcbr0-post-down
pre-up, post-up, post-downでそれぞれスクリプトを指定して必要な処理を実行しています。
lxcbr0-pre-up:
1 2 3 4 5 6 |
#! /bin/bash brctl show | grep -q lxcbr0 if test $? -ne 0; then brctl addbr lxcbr0 fi |
lxcbr0が存在しなかったら作成するスクリプトです。
lxcbr0-post-up:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/bin/sh # This is the address we assigned to our bridge in /etc/network/interfaces braddr=192.168.30.1 # ip address range for containers brrange=192.168.30.2,192.168.30.254 iptables -A FORWARD -i lxcbr0 -s ${braddr}/24 -m conntrack --ctstate NEW -j ACCEPT iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A POSTROUTING -t nat -j MASQUERADE dnsmasq --bind-interfaces --conf-file= \ --bogus-priv \ --domain-needed \ --expand-hosts \ --domain=lxc.localdomain \ --listen-address $braddr \ --except-interface lo \ --dhcp-range $brrange \ --dhcp-lease-max=253 \ --dhcp-no-override \ --pid-file=/tmp/dnsmasq-lxcbr0.pid |
このスクリプトが今回の最も重要な部分です。 iptablesでNATの設定をしてからdnsmasqを起動してDHCPサーバとDNSサーバの機能を有効にしています。
同等の設定をデーモンとして起動するdnsmasqに行っても同じことができます。 ただし、その場合iptablesの設定は別の方法で実行する必要があります。
lxcbr0-post-down:
1 2 3 |
#!/bin/bash kill $(cat /tmp/dnsmasq-lxcbr0.pid) rm -f /tmp/dnsmasq-lxcbr0.pid |
lxcbr0-post-upで作ったPIDファイルを見てdnsmasqを終了させるだけのスクリプトです。
$ sudo ifup lxcbr0
dnsmasqが起動していることを確認します。
$ ps aux | grep dnsmasq nobody 13187 0.0 0.0 35104 1216 ? S 15:05 0:00 dnsmasq --bind-interfaces --conf-file= --bogus-priv --domain-needed --expand-hosts --domain=lxc.localdomain --listen-address 192.168.30.1 --except-interface lo --dhcp-range 192.168.30.2,192.168.30.254 --dhcp-lease-max=253 --dhcp-no-override
$ sudo lxc-create -t ubuntu -n ubuntu1204 -- --release precise --bind $USER
$ dig @192.168.30.1 ubuntu1204 ; <<>> DiG 9.9.5-3-Debian <<>> @192.168.30.1 ubuntu1204 ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56257 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;ubuntu1204. IN A ;; ANSWER SECTION: ubuntu1204. 0 IN A 192.168.30.201 ;; Query time: 0 msec ;; SERVER: 192.168.30.1#53(192.168.30.1) ;; WHEN: Mon Apr 28 15:57:44 JST 2014 ;; MSG SIZE rcvd: 55
このままだとSSHなどを使えないので/etc/resolve.confにnameserverを追加します。
NetworkManagerを使用していない場合は単純に追加しても問題ありませんが、NetworkManagerを使用している場合はNetworkManagerが自動的に/etc/resolve.confを書き換えてしまうのでnamesererの設定が消えてしまうことがあります。 /etc/dhcp/dhclient.confに以下の内容を追加してNetworkManagerを再起動すると、自動的に/etc/resolv.confに反映されます。
/etc/dhcp/dhclient.conf:
... prepend domain-name-servers 192.168.30.1; ...
$ ssh ubuntu1204
LXCコンテナに名前でアクセスする方法を紹介しました。