手間がかからずおいしい朝ごはんを目指して

この記事はぴょこりんクラスタ:Re Advent Calendar 2016 - Adventarのために書いたものです。

サマリ

  • おいしい朝ごはんを楽して食べたい
  • 冷凍チャーハンのコスパがよいのでおすすめ
  • 栄養面で改善の余地が大いにあるのでなんとかしたい

はじめに

ひとり暮らしの僕にとって、朝ごはんは頭の痛い問題である。具体的には以下の3つ。

  • 問題点1:できるだけ長い時間うとうとしていたいのに朝ちょっと早起きしなきゃいけない
  • 問題点2:自分ひとりのために作るのが面倒*1
  • 問題点3:コンビニなりパン屋なりで買うと結構高くつく(コンビニで買うものは味が濃いので毎日食べられない問題もある)

いろいろ問題はあるにせよ、朝ごはんを食べたいことには変わりないので、 何とかしてこの辺を解決する方法を見つけたい。

朝ごはんの要件

問題点から、朝ごはんの満たす要件は以下の2つになる。以降、以下の要件を満たす朝ごはん案について、実際に試したことがあるものについて述べる。

  • 要件1:手早く、簡単に、おいしいものが用意できる(問題点1、2に対応)
  • 要件2:コンビニなりパン屋で買うよりもお金がかからない(問題点3に対応)

案1:シリアル

シリアルのよいところ

今回言うところのシリアルは、フルーツグラノーラもしくはオールブランとする*2。 牛乳をかければ即食べられるという、手軽さの面で他の追随を許さないところがよい。 甘みがあり食べやすいのもポイント。 栄養強化もされてるっぽいので栄養面でも死角はないのではないかと思うけども、実際はよくわからない。

価格の面でも有利であり、1食換算すると30円くらいになるんじゃなかろうか(1袋500円で、2週間くらい持ったような気がする)。

シリアルの悪いところ

いつかフルーツグラノーラの甘みに耐えられなくなる。何度かフルーツグラノーラ生活をしているが、 僕はだいたい3か月〜半年くらいで耐えられなくなってくる。 オールブランとか甘くないでしょというのは確かにそうだが、それでも多少は甘みがある。 甘みがないと食べられないが、甘いだけだと辛くなるジレンマは解決の見通しが立たない。

案2:手作りおにぎり

手作りおにぎりのよいところ

米を炊いたものを冷凍しておけば、翌朝解凍して、具を混ぜるなりふりかけをかければ立派なおにぎりのできあがりである。 解凍までは3分ほどだし、具を混ぜたりふりかけをかけるだけだが作る喜びも多少は味わえるし、 何より米を食べられるのがよい*3。水気もあるので単体でもいける。

手作りおにぎりを試行するにあたり、試しに鯖フレークを買ってみたが、これが大変おいしく、 単体でむしゃむしゃ食べてしまったことは大いに反省している。

価格の面では、まあそこそこいいんじゃないですかね。コンビニでおにぎりを買うよりは相当安いよ多分。

手作りおにぎりの悪いところ

米を炊くときに増える洗い物がダメ。 1回炊くと7個おにぎりが作れるので、1回作るとしばらく洗わなくてもいいんだけど、 洗い物が増えるという心理的負担に耐え切れずに挫折。 おにぎりが悪いわけではなくて、何か洗い物があんまり好きじゃない僕のせい。 もちろんやるときはやるし、洗い物を無限にためたりも絶対しないけど、 炊飯釜とか炊飯器蓋とか、大きめのものはできれば洗いたくないなという気持ちでいっぱいになるので ダメだった。

あと、あえて言うならもっとおかずを追加して栄養バランスを取ったほうがよい。あえて言うなら。

案3:お茶漬け

お茶漬けのよいところ

ごはんを解凍して、お湯をかけるだけなので楽ちんだし、お茶漬けの素もそんなに高くないのでコスパもよい。

お茶漬けの悪いところ

手作りおにぎりの問題に加えて、飽きてしまうのが問題。 味を変えながら2週間くらい食べ続けたところで、お茶漬けに飽きてしまった。はて、何でだろう。

案4:冷凍焼きおにぎり

冷凍焼きおにぎりのよいところ

手作りおにぎりはかなりいい線まで行ったが、洗い物をしたくないという心理的ハードルに破れた。 今回は大きな洗い物をしなくてよいというところで死角がなくなった。

価格は8個入り500円なので、手作りおにぎりよりも多少高いと思うが、 コンビニおにぎりほどではないので特に問題なし。

冷凍焼きおにぎりの悪いところ

これも手作りおにぎりと同じで、栄養バランスがよくないのがいまいち。 あとは、味が単調になるのでいつか飽きそうな点だけど、 同じ味の焼きおにぎりを2週間くらい食べ続けてみたところ、2週間くらいなら大丈夫だった。 お茶漬けとの違いは全くわからないが、何となく歯ごたえに原因がありそうな気がする。

案5:冷凍チャーハン

冷凍チャーハンのよいところ

焼きおにぎりのよいところとほぼ同じ。400gで250円で、だいたい1週間ちょいくらい持つので、冷凍焼きおにぎりよりもコスパはよい。 冷凍のくせに味もよいので、今は冷凍チャーハン生活をしている。

冷凍チャーハンの悪いところ

焼きおにぎりに同じ。栄養バランス取りたい。

まとめ

冷凍チャーハンは安くて味もよく、飽きにくいのでおすすめ。 しかし、栄養バランスが明らかにわるいので、何か野菜とたんぱく質が取れるおかずを追加したほうがよい。

[参考]突撃となりの朝ごはん

同期やら先輩やらに、朝ごはんに何を食べているかを聞いた結果を以下にまとめる。 やはりごはん食よりも手軽そうなパン食が多いっぽい。 冷奴というのは結構独創性があってよいと思ったが、まだ試せていない。

  • カロリーメイトとかそれに類するもの
  • コンビニで買ったパン
  • コンビニで買ったおにぎり
  • ホームベーカリーで手作りしたパン
  • パン屋で買ったパン
  • 冷奴
  • 栄養ドリンクとウィダーインゼリー
  • 焼いた食パンとベーコンエッグ
  • ベーコントースト(食パンの上にベーコンを載せてトースターで焼く)

[参考]僕の理想の朝ごはん

参考までに、僕の理想の朝ごはんメニューはこんな感じ*4。 そこそこ以上のホテルの朝ごはんはおおよそ条件を満たしてくれる上に、片付けをしなくていいので幸せにひたれる。

  • ふっくら炊きあがり、水気をやや多めに含んだ白米
  • だしのきいた具だくさんの味噌汁
    • 豆腐、わかめ、小口切りしたねぎが基本。にんじん、ごぼう、大根を追加した豚汁は最高。たまねぎを入れて甘くしてもよいし、溶き卵を入れてボリュームと見た目を強化するのもいい。
    • 味噌は白味噌もしくは白味噌多めの合わせ味噌がいい。赤味噌は嫌いではないけれど、甘めのものが好き。
  • 焼き魚
    • 塩鮭、鯖、アジの開き、さんまの塩焼き(蒲焼きでもいい)などなど、脂が乗ってて骨ごと食べられる魚がベスト。
    • うるめいわしやししゃもも渋くてよいが、あまり見かけない
  • 漬物
    • 例えば、ふっくらした明太子、ごりっと歯ごたえのあるごぼうの味噌漬け、山形名産のだし、ぱりっとしたたくあん、酒粕で漬けた瓜などが挙げられる
  • ふわっとしただし巻き卵(半熟の目玉焼きでもいい)
  • ベーコンと野菜の炒めもの(野菜を取るのが趣旨なのでサラダで代替してもよい)

パンも好きだが、ごはん派です。

*1:他人がいたら作るのかというとそれは別の問題

*2:他のは食べてない

*3:僕は米好き

*4:何の参考だよ

"pyokkorin feat. Kagamine Rin!" is so classic

この記事はぴょこりんクラスタ:Re Advent Calendar 2016のために書いたものです。

サマリ

pyokkorin feat. Kagamine Rin!に動画をつけました。この動画が全てで、あとの文章は全ておまけ。

www.nicovideo.jp

背景

まずはこれをみてほしい。お風呂に入りながら携帯ではてブを眺めるという あれなアクティビティに勤しんでいたときに見つけた動画だ。

www.nicovideo.jp

僕はこれを見て、「すごい!生きてる!!!」と 今思えばよくわからない理由で感動しすぎて文字通りぐずぐずに泣いた。 感動のあとは、自分も同じくMMDで何か作ってみたいという衝動が湧いてきたわけだけど、 MMDをダウンロードして使い方をざっと調べたところで何をしたらいいかよくわからなかった。 よくあるダウンロードしてチュートリアルしたら満足してしまう病気だ。

そんなところで今年もピョッコリンさん主催でアドベントカレンダーをやることになった。 まがりなりにも2年間続いたので、せっかくなら去年のアドベントカレンダーとの間に連続性を持たせたい。 去年のピョッコリンアドベントカレンダーのおさらいをしたところ、 ちょうどいい感じに12/24の回にピョッコリンさんが曲をリリースしていたので、その曲にMMDダンスをつけた。

練習作ということで、リンちゃん*1にはジャージで踊ってもらった。 MVにジャージはどうなのよということも考えたけれど、踊っているときに手が衣装にめり込みにくく、 ひらひらしてない分長い手足の動きが目立つ(ような気がする)という利点があり、 さらには表情の動きがとてもいい感じにつけられる初心者にやさしいモデルなので、 衣装云々に関してはあまり気にしていない。これはこれでかわいらしいと思う。

振り付けは冒頭の初月 is so Classicを作った@Somubu_choさんの作ったものから 何となくリズムがあっているLove Shakeを選んだ。 本当は初月 is so Classicのダンスを当てたかったけど、 リズムが全然合わない上に、それを強引に合わせこむ技量がなかったのでリリース優先でがんばることにした。

せっかくなので苦労した点

音楽とダンスを合わせるのが一番大変だった*2。 合わないのはわかる、でも何がずれているから合わないように見えるのかがわからなくて苦労した。 結局のところ、ダンスはステップが全てで、音楽のリズムとステップが合ってれば だいたいうまくいってるように見えるけど、ステップが合わなければ何をしてもダメ。 その辺は偉大な先人が踊っているのをひたすら眺めてたら気づいた。 Michael Jackson - Thriller - YouTubeと、 Pulp Fiction - Dance Scene (HQ) - YouTubeはどれだけ見ても飽きないですね。

あとはニコニコ動画にアップロードするときのffmpegオプション。 なかなか覚えられないし今後はHandbrakeみたいにかんたんなやつでやろう*3

*1:今までどちらがリンでどちらがレンなのか区別ついてない不届き者だったのでリンちゃんと呼ぶと怒られてしまうかもしれない。さんをつけろよデコ助野郎的なあれ。

*2:口パクは諦めてもいいやと思えるくらいに達するまでにはあまり時間がかからなかった

*3:今度はないかもしれない

kill_fasyncの引数のまぎらわしさ

tl;dr

  • kill_fasyncの第2引数は飛ばしたいシグナル番号を指定するわけではない
  • つまみ食いで勉強すると落とし穴にはまる危険があるので気をつける

詳細

kill_fasyncという関数をご存知だろうか。kill_fasyncとは、Linuxカーネルが提供する関数の1つで、 カーネルモジュールからユーザプロセスへシグナルを飛ばすものだ。

Kernel 4.8での実装はこんな感じ。至ってシンプルで、 正直これで何がわかるか、というところであるが、少なくとも何をいくつ引数に取るかわかる。

Linux/fs/fcntl.c - Linux Cross Reference - Free Electrons

724 void kill_fasync(struct fasync_struct **fp, int sig, int band)
725 {
726         /* First a quick test without locking: usually
727          * the list is empty.
728          */
729         if (*fp) {
730                 rcu_read_lock();
731                 kill_fasync_rcu(rcu_dereference(*fp), sig, band);
732                 rcu_read_unlock();
733         }
734 }
735 EXPORT_SYMBOL(kill_fasync);

さて、これを見て何を思うか。素直な心と有り余る読解力で読むと、第2引数のsigは飛ばすシグナルの番号だと思うのではないだろうか。 ところがこれは飛ばすシグナルの番号とは全く関係がない。

さて、kill_fasync_rcuを見てみよう。

699 static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
700 {
701         while (fa) {
702                 struct fown_struct *fown;
703                 unsigned long flags;
704 
705                 if (fa->magic != FASYNC_MAGIC) {
706                         printk(KERN_ERR "kill_fasync: bad magic number in "
707                                "fasync_struct!\n");
708                         return;
709                 }
710                 spin_lock_irqsave(&fa->fa_lock, flags);
711                 if (fa->fa_file) {
712                         fown = &fa->fa_file->f_owner;
713                         /* Don't send SIGURG to processes which have not set a
714                            queued signum: SIGURG has its own default signalling
715                            mechanism. */
716                         if (!(sig == SIGURG && fown->signum == 0))
717                                 send_sigio(fown, fa->fa_fd, band);
718                 }
719                 spin_unlock_irqrestore(&fa->fa_lock, flags);
720                 fa = rcu_dereference(fa->fa_next);
721         }
722 }

意外にもsigはSIGURGじゃなければOKで、実際にシグナルを飛ばしていると思しき send_sigioにはsigが全く入っていない。じゃあ一体sigって何なのか。 それに飛ばしたいシグナル番号ってどこに入ってるんだ。

順番が前後するが、飛ばしたいシグナル番号はsend_sigio関数を見ればわかるよね。 send_sigioはさらにsend_sigio_to_taskという関数を呼んでる。

491 void send_sigio(struct fown_struct *fown, int fd, int band)
492 {
493         struct task_struct *p;
494         enum pid_type type;
495         struct pid *pid;
496         int group = 1;
497         
498         read_lock(&fown->lock);
499 
500         type = fown->pid_type;
501         if (type == PIDTYPE_MAX) {
502                 group = 0;
503                 type = PIDTYPE_PID;
504         }
505 
506         pid = fown->pid;
507         if (!pid)
508                 goto out_unlock_fown;
509         
510         read_lock(&tasklist_lock);
511         do_each_pid_task(pid, type, p) {
512                 send_sigio_to_task(p, fown, fd, band, group);
513         } while_each_pid_task(pid, type, p);
514         read_unlock(&tasklist_lock);
515  out_unlock_fown:
516         read_unlock(&fown->lock);
517 }

あった。シグナル番号signumはfownに入ってる。

449 static void send_sigio_to_task(struct task_struct *p,
450                                struct fown_struct *fown,
451                                int fd, int reason, int group)
452 {
453         /*
454          * F_SETSIG can change ->signum lockless in parallel, make
455          * sure we read it once and use the same value throughout.
456          */
457         int signum = ACCESS_ONCE(fown->signum);
458 
459         if (!sigio_perm(p, fown, signum))
460                 return;
461 
462         switch (signum) {
463                 siginfo_t si;
464                 default:
465                         /* Queue a rt signal with the appropriate fd as its
466                            value.  We use SI_SIGIO as the source, not 
467                            SI_KERNEL, since kernel signals always get 
468                            delivered even if we can't queue.  Failure to
469                            queue in this case _should_ be reported; we fall
470                            back to SIGIO in that case. --sct */
471                         si.si_signo = signum;
472                         si.si_errno = 0;
473                         si.si_code  = reason;
474                         /* Make sure we are called with one of the POLL_*
475                            reasons, otherwise we could leak kernel stack into
476                            userspace.  */
477                         BUG_ON((reason & __SI_MASK) != __SI_POLL);
478                         if (reason - POLL_IN >= NSIGPOLL)
479                                 si.si_band  = ~0L;
480                         else
481                                 si.si_band = band_table[reason - POLL_IN];
482                         si.si_fd    = fd;
483                         if (!do_send_sig_info(signum, &si, p, group))
484                                 break;
485                 /* fall-through: fall back on the old plain SIGIO signal */
486                 case 0:
487                         do_send_sig_info(SIGIO, SEND_SIG_PRIV, p, group);
488         }
489 }

でもfown_structなんてどこで設定するんだ?と思ったあなたは鋭い。 実はkill_fasyncを使うには(当たり前だが)準備がいる。 具体的にはfcntlを使えばよい。以下のスライドを見てくれれば何となくわかると思う。 F_SETSIGとシグナル番号を与えてfcntlを撃ってやればOKだ。先に見るべきだった。

http://www.cs.usfca.edu/~cruse/cs635s05/lesson22.ppt

では、SIGIOは一体何なのか。 このへんを読むと、非同期のI/Oイベントを伝えるときにはSIGIOを使うらしいというのがわかる。 要はある種のお約束的なものですね。 まあ確かにカーネルモジュールというかドライバだから、ユーザプロセスに何か通知するのって、 外部デバイスの入出力イベント以外にないよなあ。

ところでSIGURGって何で特別扱いされてるの?と気になるけど、こちらもある種のお約束的なものらしい。 SIGIOよりも用途が限定されてるようなので特別扱いなのかも。

ソケットを使用したアプリケーションへの信号

SIGURG は、OOB データをサポートしているソケットでアウト・オブ・バンド (OOB) データを受信するときに送信される信号です。たとえば、AF_INET アドレス・ファミリーで SOCK_STREAM タイプのソケットは、 SIGURG 信号を送信するように条件付けることができます。
SIGIO は、あらゆるタイプのソケットで通常のデータ、OOB データ、エラー条件、その他が発生したときに送信される信号です。 

まとめ

kill_fasyncの第2引数はある種のおまじない的にSIGIOを指定すればよいっぽい。 使い方をちゃんと勉強しないと余計なことを調べなきゃいけなくなるので、 ショートカットせずに地道に調べた方がよい。

Crowiでもプラグインを使いたかったんです

サマリ

Crowiでオレオレjavascriptを実行できるようにした。

github.com

背景

CrowiはMarkdownでかけるwikiで、見た目もおしゃれで大変いい感じなんだけど、 pluginが使えないのがちょっと残念だったので作った。

予防線

  • 自分のJavascript知能指数は2くらいしかないので、ゴリラが文字を書くようなコードになっていてつらい
  • あんまりテストしていないのでバグってる可能性が多々ある
  • 本家crowiとは全く関係ない

使い方

  • public/js以下にpluginフォルダを作って、pluginのjsファイルを放り込む
  • 以下のようにmarkdownを書くと、plugin_name.jsをロードして、同名の関数にデータを入れたjsonを引数にしてコールしてくれる。
  • pluginはmarked(https://github.com/chjj/marked)をちょこっといじって実装しているので、返り値は必ずHTMLにすること。
  • 1ページにつき1つのプラグインしかサポートしていない
  • 複数使いたい欲張りはもっとちゃんとしたやつを作って僕にも使わせてください
 ```#plugin_name
 {json形式(改行なし)でデータを入れる}
 ```

どんなことができるの?

こんな感じで、markdownだけどフォームを入れ込んだりできる。要は調整さんとかdoodleとかの 出欠確認フォームみたいなのを作れるというわけですね。

f:id:nbisco:20160706231635p:plain

プラグインサンプルよこせ

くらえ!これがJavascript知能指数2のコードだ!!!

gist.github.com

どういう仕組みで実現しているの?

プラグインコール

markedのRenderer(markdownからhtmlに変換するところ)をoverrideして使っています。 ```# を見つけたらpluginだと判断して、#に続く文字列をevalして プラグインをコール。

参考URL

Tumbling Dice — [JavaScript]marked.jsを無理矢理拡張してオレオレパーサーを作る

プラグインロード

lib/route/page.jsで、記事の中から```#<plugin_name>を抜き出します。 renderVarsに、pluginというプロパティを定義し、<plugin_name>.jsを覚えさせておきます。 そして、lib/views/layout/layout.htmlで、pluginプロパティが定義されているときにscriptタグを追加するようにします。

なんでplugin1個だけサポートなのかというと、 Promise内にループを仕掛けるのが結構大変そうだったから。

今更ながらdockerメモ

目次

サマリ

dockerをさわってみたのでメモ。

参考文献

ていねいなユーザマニュアルが標準装備なのはたいへんありがたいですね。

docs.docker.com

dockerのインストール

Ubuntuだとaptで入れられるのでらくちんですね。 なお、以下のバージョンしか公式にサポートしていない模様。 さらに、カーネルは3.10以上じゃないとダメ。

  • dockerがサポートしているUbuntuのバージョン
  • dockerがサポートしているカーネルのバージョン
    • 3.10 以降

以下、手順。僕の環境はUbuntu 14.04。 ログアウトは、端末を開き直すだけじゃダメ。

sudo apt-get install linux-image-extra-$(uname -r) は、 aufsを使うためだけっぽいのでいらないと思う。

# aptがhttpsを使えるように
$ sudo aptitude update
$ sudo aptitude install apt-transport-https ca-certificates

# aptのGPG keyを追加
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

# /etc/apt/sources.list.d/docker.listをまっさらにして作りなおす
$ echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" > /etc/apt/sources.list.d/docker.list

# aptのパッケージをアップデート
$ sudo aptitude update

# lxc-dockerをすでに入れていたらpurgeしておこう
$ sudo aptitude purge lxc-docker

# aptが正しくリポジトリを認識ているか確認しよう
$ sudo apt-cache policy docker-engine

# インストール!
$ sudo apt-get install docker-engine

# Dockerグループに自分をいれておくと、sudoしなくてもdockerコマンドをたたけるようになるので
# いれておく
$ sudo gpasswd -a $USER Docker

# 一旦ログアウトしよう
$ logout

dockerでhello world

docker run hello-worldして、以下のメッセージが出てきたら成功。 最初にDocker Hubからhello-worldイメージを取ってきて、 そこから起動するみたい。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
03f4658f8b78: Pull complete 
a3ed95caeb02: Pull complete 
Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7
Status: Downloaded newer image for hello-world:latest

Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
 https://hub.docker.com

For more examples and ideas, visit:
 https://docs.docker.com/userguide/

Docker Hubからdocker imageを取得して動かす

GitHubみたいに、docker imageを置いておくことができる Docker Hubというものがある。 公式リポジトリがいくつかあるようなので、そこから試しにubuntuを取得してみる。 docker pull <リポジトリ>:<タグ>でdocker imageを取得できる。 <タグ>を省略すると、最新版を取得する。

例えば、以下の例では、ubuntuリポジトリのタグ"14.04"を取得する。

$ docker pull ubuntu:14.04

取得したイメージはdocker imagesで見られる。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              14.04               07c86167cdc4        2 days ago          188 MB
hello-world         latest              690ed74de00f        4 months ago        960 B

取得したものは、docker runで動かすことができる。 取得しなくとも、docker runを打つと、 勝手にDocker Hubに探しに行って取得してくれるので便利。 プログラムを動かしたままコンテナから脱出するにはCtrl-p Ctrl-q。 logoutしてしまうと、コンテナが停止してしまう。

# 単に動かす
$ docker run ubuntu echo "hello world"
### ubuntuイメージがない場合はDocker Hubから勝手に取ってきてくれる
hello world

# backgroundで動かす
$ docker run -d ubuntu echo "hello world"
### ubuntuイメージがない場合はDocker Hubから勝手に取ってきてくれる
hello world

# -itをつけると、対話型のプログラムも動かせる。
$ docker run -it ubuntu /bin/bash
root@44d395e6298d:/#

# コンテナに名前をつける
$ docker run -it --name bash_test ubuntu /bin/bash 

コンテナのリストを取得するのは、docker ps docker ps -aで、止まっているコンテナも見ることができる。

NAMES(コンテナの名前。以後コンテナ名とする)はDocker Engineが勝手につけてくれる(もちろん自分でつけてもよい)。 動かしたり止めたりするときに、コンテナIDを指定してもよいし、コンテナ名を指定してもよい。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                      PORTS               NAMES
44d395e6298d        ubuntu:14.04        "/bin/bash"            13 seconds ago      Exited (0) 3 seconds ago                        sharp_panini
b4a331a7280d        ubuntu:14.04        "echo 'hello world'"   26 seconds ago      Exited (0) 25 seconds ago                       mad_hugle
a627000187a6        hello-world         "/hello"               8 minutes ago       Exited (0) 8 minutes ago                        nostalgic_bhabha

お作法としては、コンテナは使い終わったら破棄。 停止しているコンテナの破棄は、docker rmでできる。

# sharp_paniniを破棄
$ docker rm sharp_panini
sharp_panini 

# いちいち指定するのが面倒なので全部破棄
$ docker rm $(docker ps -a -q)

# --rmをつけてrunすると、終了後にdocker rm してくれる
$ docker run --rm -it ubuntu /bin/bash

オレオレdocker imageを作る

直感的なやり方

いちばん直感的なのは、コンテナ起動して、必要なプログラムをインストールした後、コミットするやり方。 例えば、sysstatをインストール済みのコンテナを作るとすると、こんな感じになる。

$ docker run -it ubuntu:14.04 /bin/bash
root@cd9379115537:/# sudo aptitude install sysstat
root@cd9379115537:/# exit
# -m はコミットログ。 -a は変更作成者。
# nbisco/sysstatというイメージを、testタグをつけて作成。
$ docker commit -m "Added sysstat" -a "nbisco" \
cd9379115537 nbisco/sysstat:test

Dockerfileを使うやり方

Dockerfileを使うと、いちいちコンテナ起動しなくても docker imageを作ることができる。

何はともあれ、まずはDockerfileを作る。

$ mkdir test 
$ cd test
$ touch Dockerfile

Dockerfileを作ったら、以下を書き込む。 何のことはない、単にやってほしいことを書くだけだ。

# コメント行
FROM ubuntu:14.04 # どのイメージをベースにするか
MAINTAINER nbisco <XXXX@XXXX.XXX>  # イメージのメンテナはだれか
RUN aptitude update && aptitude install -y sysstat # コンテナ起動後に実行するコマンド

Dockerfileを作ったら、以下のコマンドでイメージ作成。

# 最後の引数はDockerfileへのパスを指定すること。dotなので見落としがち。
$ docker build -t nbisco/sysstat:dev . 

作ったimageは同じようにdocker run nbisco/sysstat:devで起動できる。 らくちん。

Dockerのネットワーク機能

コンテナにおけるネットワークは、(1)何とも繋がないnone、(2)ホストオンリーのhost、 (3)他コンテナやホストと繋げられるbridgeの3種類に大別できる。 デフォルトはbridge。

ネットワークは、以下のコマンドで確認できる。

$ docker network ls
NETWORK ID          NAME                DRIVER
18a2866682b8        none                null                
c288470c46f6        host                host                
7b369448dccb        bridge              bridge  

ネットワークの状況は、docker network inspect <network名>で調べられる。 ※特に何も繋がっていない状況なので、"Containers"が空になっている。

$docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "157fb4893818ff6e8fa7512b142b9e97df007f042d61f5bbc7bbdfbe9c9da2cb",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Containers": {},
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        }
    }
]

動かしているコンテナのネットワークを切り離すこともできる。

$ docker run -d --name networktest training/webapp python app.py
$ docker network disconnect bridge networktest

専用のbridgeネットワークを作って、コンテナ同士をつなげたりもできる。

# コンテナスタート時にネットワークを指定する場合
$ docker network create -d bridge my-bridge-network 
$ docker run -d --net=my-bridge-network --name network_test1 ubuntu
$ docker run -it --net=my-bridge-network --name network_test2 ubuntu /bin/bash
root@047745a86ac1:/# ping network_test1
PING db (172.18.0.3) 56(84) bytes of data.
64 bytes from db.my-bridge-network (172.18.0.3): icmp_seq=1 ttl=64 time=0.101 ms
64 bytes from db.my-bridge-network (172.18.0.3): icmp_seq=2 ttl=64 time=0.063 ms

# 一旦止めれば、ネットワークに接続させることもできる
$ docker run -it --name network_test3 ubuntu /bin/bash
★IPが172.18.0.0と別セグメントになっているので、届かない。
root@51057043d3ff:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:02  
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:25 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:4814 (4.8 KB)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

$ docker stop network_test3
$ docker network connect my-bridge-network network_test3
$ docker start network_test3
$ docker exec -it network_test3 /bin/bash
root@51057043d3ff:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:02  
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:26 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:4825 (4.8 KB)  TX bytes:648 (648.0 B)

# ★もう1個できて、network_test1/2とつながるようになった
eth1      Link encap:Ethernet  HWaddr 02:42:ac:12:00:04  
          inet addr:172.18.0.4  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe12:4/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:26 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:4825 (4.8 KB)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Dockerにおけるデータ管理

コンテナ起動時に-vオプションをつけると、コンテナイメージに含まれない ボリュームを作ることができる。例えばこんな感じ。

$ docker run -d -P -v /testweb --name web training/webapp python app.py
$ docker exec -it web /bin/bash
root@fc0beb95b345:/opt/webapp# ls /
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  testweb  tmp  usr  var
root@fc0beb95b345:/opt/webapp# 

ボリュームの場所は、docker inspectで調べられる。 ボリュームと言っても、単なるディレクトリに見えるので、作ったデータを取り出すのは簡単。

$ docker inspect web | less
()
        "Mounts": [
            {
                "Name": "08375e69b62c71792953d2dc83ac735c1c5cb8de63ac655557098f9446b95936",
                "Source": "/var/lib/docker/volumes/08375e69b62c71792953d2dc83ac735c1c5cb8de63ac655557098f9446b95936/_data",
                "Destination": "/testweb",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

()

$ docker exec -it web /bin/bash
docker exec -it web /bin/bash
root@fc0beb95b345:/opt/webapp# touch /testweb/test.txt
$ ls /var/lib/docker/volumes/08375e69b62c71792953d2dc83ac735c1c5cb8de63ac655557098f9446b95936/_data
test.txt

既存のホストディレクトリをマウントすることもできる。 lsするとサブディレクトリ以下まで普通に見えてしまう。

$ mkdir /home/nbisco/test_docker
$ touch /home/nbisco/test_docker/test.txt
$ mkdir /home/nbisco/test_docker/test_docker_sub
$ docker run -d -P -v /home/nbisco/test_docker:/test_docker --name web training/webapp python app.py
$ docker exec -it web /bin/bash
root@fce1df482e8d:/opt/webapp# ls /test_docker
test_docker_sub test.txt 

ディレクトリだけでなく、ファイルもマウントできちゃう。 わざわざdotfileをcloneしたりしなくて済むね。

$ docker run --rm -it -v ~/.bash_history:/root/.bash_history ubuntu /bin/bash

もちろん、他のコンテナとディレクトリを共有することもできる。 Docker Userguideには、/dbdataを共有する例が載ってる。 コンテナ間でディレクトリを共有すると、バックアップも手軽にできちゃう。

$ docker create -v /dbdata --name dbstore training/postgres /bin/true
$ docker run -d --volumes-from dbstore --name db1 training/postgres
$ docker run -d --volumes-from dbstore --name db2 training/postgres

# バックアップの例
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

# バックアップから復元したデータでコンテナを動かす
$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

以上、ざっくり基本動作のメモでした。

Node.js + Express + MongoDBでログイン機構を作る

サマリ

Node.js + Express + MongoDBでログイン機構が動くようになった。

ソースコード

ここのコミット20e71e2から2db752eの差分を取ると、express-generatorからの差分がわかる。

github.com

ぼやき

Qiitaやら個人ブログやらいろいろあってありがたいが、 Node.jsの性質なのか、APIやら関数の呼び方やらがぐにゃぐにゃと変わるせいで、 ぐぐってもぐぐっても動くサンプルに辿りつけない。つらい。

ユーザプログラムの関数トレーサを作りなおした

サマリ

ここやらここここで解説されている ユーザプログラムの関数トレーサftraceを手直しして、少し機能追加しました。 もとのftraceだと名前がまぎらわしいのと、2011年で開発が止まっているっぽかったので、 uftrace(Userspaceのu)と名前を変えて公開しました。 github.com

ちなみに、ftraceの開発者hamanoさんのレポジトリはこれ。 github.com

ビルド方法

テンプレ的な方法でいけます。autotoolsはまだ勉強途上でよくわからない・・・

$ git clone https://github.com/bisco/uftrace.git
$ cd uftrace
$ ./configure
$ make
$ sudo make install

使い方

gccのオプション-g -finstrument-functionsをつけてビルドしなおしたあと、 こんな感じで使えます。

$ uftrace <your_program> <your_program_args>

追加した機能たち

細々追加しましたが、いちばんの目玉はフィルタ機能。 大きなプログラムだといろいろ出すぎて困るので作りました。

  • ファイル名と行数を表示(ftrace-0.90から手直しして復活)
  • 関数から出た時に情報を出力(ftrace-0.90から手直しして復活)
  • フィルター機能:正規表現でパターンを指定。マッチしたパターンを出力"しない"。