Thanks Driven Life

日々是感謝

heroku-docker を使って Emacs & Cask がインストールされた Heroku 環境 (Slug) を作成する Docker イメージ作った

成果物

経緯

Emacs を使っている人は、日頃から

「あー Emacs でも HTTP サーバ立てられるんだし Heroku で起動してーなー」

と考えていると思います。

しかし Emacs ぐらいになると Heroku が標準サポートしている環境には含まれておらず、
いわゆるサードパーティ製 buildpack の導入が必要となります。

Emacs の buildpack もあった*1 のですが

  1. 試しに使ってみたら、なんかエラー出た*2
  2. まあ直せばいいかーと思う
  3. buildpack の修正検証ってものすごいめんどくさいイメージ(ちゃんと調べてない)
  4. モチベーション消えた

みたいな人生を送っていました。

Heroku + Docker

そんなある日、もう一つの可能性である Docker を思い出しました。

Build and Deploy with Docker | Heroku Dev Center

リリースされた当時は

「好きな Docker イメージを Heroku で動かせるのかよすごい!!」

みたいな反応が多数(私も)だったのですが、実際には

  1. 「Heroku と同等の環境を Docker イメージとして配布する」
  2. 「その中で動作確認しろよ」
  3. 「(制限はあるが)そのままデプロイできるフローも作ったぜ」

的なものでした。

デプロイ方法もあるし、ドカドカデバックできるので、これで行こうと決めました。

そんなわけで作りました。

https://github.com/gongo/emacs-heroku-docker

概要は README を読んでもらうとして、つまり必要なことは

  1. 普段通り Emacs Lisp で Web アプリケーションを書く
  2. Cask ファイルを作成し、依存パッケージを書く
  3. Heroku ではお馴染の Procfile に、起動コマンドを書く
  4. コンテナビルドしたり Heroku へリリースするためのファイルを作成
    • docker build したあとのファイルを転送するため、リリースにも使われる
  5. heroku docker:release

みたいな感じです。

試しに作ったのがこちら

https://emacs-heroku-docker-sample.herokuapp.com/

いつか消します

補足

heroku-docker でデプロイする時に注意するところ、備忘録も兼ねて

1. /app 以下のファイルしか転送されない

heroku docker:release で Heroku にデプロイされるファイルは、awesome氏のポストにもある通り

/app以下をtgzで固めてcpコマンドでそれを取り出している.なのでDockerfileに独自の変更を加えるときは注意が必要で/app以下に依存をちゃんと含めるように書く必要がある.

Herokuの'docker:release'の動き | SOTA

という制限(仕様)となっています。

つまり、Dockerfile で素直に apt-get install emacs とかやっても、Heroku 側には Emacs が入っていない slug が転送されてしまう、ということです。 なので今回はソースコードからコンパイルし、 /app/emacs 以下にインストールすることで依存を /app 以下に閉じ込めました。Cask についても同様。

2. 環境変数 export は /app/.profile.d/* 以下に書いておこう

上記のとおり heroku docker:release で転送されるのは、コンテナ内の /app 以下だけ。つまり

ENV PATH /app/emacs/bin:$PATH

みたいなのを書いていても、docker run した環境では使えますが Heroku 上には反映されません

ではどうするか。/app/.profile.d/ 以下に、環境変数設定するファイルを書いておきます。

.profile.d Scripts | Heroku Dev Center

$HOME/.profile.d/ 以下に置いたやつは Dyno 起動時に読まれるので、必要な処理はここに書いておきましょう。

ちなみに heroku-ruby では Dockerfile の ENTRYPOINT として init.sh というものを仕込んでいます。

#!/bin/bash

for SCRIPT in /app/.profile.d/*;
  do source $SCRIPT;
done

exec "$@"

こうすることで

Procfile:

web: cask exec emacs -Q --batch -l app.el

docker run 時:

$ docker run `コンテナ名` cask exec emacs -Q --batch -l app.el

つまり

  • Procfile に書くコマンドと docker run のコマンドを揃えることができる
  • どちらもコンテナ起動時に /app/.profile.d/* 以下を読んでくれる

という感じで、より Heroku の環境に近づけることができる、というわけです。

まとめ

heroku-docker を使うことで、buildpack よりも破壊再構築のサイクルを早く回せると思います。 どんどん作って最高のオレオレ cedar:14 にしよう!!

参考

*1:https://github.com/technomancy/heroku-buildpack-emacs

*2:libgpmが無いとかなんとか

json-reformat.el v0.0.4 リリースしました

Release 0.0.4 · gongo/json-reformat · GitHub

修正内容

v0.0.3 までは、空のハッシュに対して json-reformat-region とかを仕掛けると、
下記のように null になってしまう という 仕様 でした。

{"foo": {}}

// ↓↓↓

{
  "foo": null
}

v0.0.4 からは、ちゃんと空のハッシュのまま整形できるようになりました。

{"foo": {}}

// ↓↓↓

{
    "foo": {
    }
}

空のハッシュであれば "foo": { } という感じで改行入らない方がいいかもしれませんね。
いつか考えます。

今回の内容を仕様としていた理由

json-reformat.el では、JSON テキストのパースを (json-read) で行っています。

;; $ cat foo.json
;; {
;;     "foo": 3,
;;     "bar": "pizza"
;; }

(dolist (type '(plist alist hash-table))
  (let ((json-object-type type))
    (with-temp-buffer
      (insert-file-contents "foo.json")
      (json-read))))

;; => (:bar "pizza" :foo 3)
;; => ((bar . "pizza") (foo . 3))
;; => #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("foo" 3 "bar" "pizza" ...))

このように alist plist hash-table の中の好きな形でパースした結果を受け取れます。

v0.0.3 までは plist で実装していたのですが、この時問題となるのが、前述の 空のハッシュ です。

;; $ cat foo.json
;; {
;;     "foo": {},
;;     "bar": null
;; }

(let ((json-object-type 'plist))
  (with-temp-buffer
    (insert-file-contents "foo.json")
    (json-read)))

;; => (:bar nil :foo nil)

このように 空のハッシュも null もパースすると nil になってしまう ということで、
受け取った側としては「どっちかわからんから {} に直すこともできねーなー」となって
最終的に「これは仕様ですわー」みたいな感じにしていました。

光明

そんなことを Tweet してみたところ、 @ さんから以下の reply をいただきました。

なるほど hash-table と思って実際に試してみたところ

;; $ cat foo.json
;; {
;;     "foo": {}
;; }

;; $ cat bar.json
;; {
;;     "bar": null
;; }

(let ((json-object-type 'hash-table))
  (dolist (filename '("foo.json" "bar.json"))
    (with-temp-buffer
      (insert-file-contents filename)
      (json-read))))

;; => #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("foo" #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ( ...)) ...))
;; => #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ("bar" nil ...))

ちゃんと nil なのか空ハッシュなのか判別できる!!

というわけで、今までの仕様はバグということにできて修正することができました。 @ さんありがとうございました!!

P.S.

Emacs 24.4 から標準実装されている M-x json-pretty-print も似たような問題が起きていますが
M-x json-pretty-print または M-x json-pretty-print-buffer 実行前に

(setq json-object-type 'hash-table)

とかしておくと、空ハッシュが壊れることはありません。

PHP バージョン毎の php.ini の差分を確認できるサービス phpini-diff

軽く作りました。

http://phpini-diff.herokuapp.com/

f:id:gongoZ:20150924162526g:plain

概要

RailsDiff みたいなやつです。

作成経緯

PHP のバージョン上げる時に php.ini の違いも一応調べないと*1。でも PHPソースコード落としてきて diff するのめんどいし。今なら GitHub にある PHP 本体のリポジトリ でタグ差分で違いみれるけどいちいちバージョン探して指定してーってめんどくさいな」

みたいなことから

Rails だと railsdiff があるよなー」

みたいな感じで php.ini 版があると多少便利になるかな、といった感じです。

ソースコード

github.com

備忘録も兼ねて簡単に内容を

PHP のバージョンは 4.0.0 以上 & (正式版 or RC 版) を対象

  • GitHub でタグ付けされてる分だけ
  • 正式版以外だと alpha とか beta とかあるけど、めんどくさいので RC だけ

php.ini の種類については

プロダクション用(所謂 php.ini-production)を比較対象としています。php.ini-development とかは除外しています。 ちなみに PHP 5.3 未満は php.ini-recommended が production 用っぽかったので、それを採用しています。

各バージョンの php.ini の収集方法

適当にスクリプト書いて 対処しました。

とりあえず JavaScript (Node.js) で

  • フロントは vue.js + webpack で作成
  • サーバ側は express
    • そこは PHP だろみたいなところもあったんですが、 PHP xdiff を heroku で使うのめんどそう
    • いい感じで差分ブロック分けてくれるやつが node にあった flitbit/diff ので、もう統一しようかなと

まとめ

ini ファイル、拡張ライブラリまで手を出そうと思ったけどめんどくさいのでやめました。 そこらへんはファイルサイズもそんなに大きくないし、ひとまずコアな php.ini の差分だけ簡単に見れたら良しとしました。

てきとうにご利用ください

*1:例えば 5.3 → 5.4 みたいな差分は http://php.net/manual/ja/migration54.ini.php とかでも確認できます

YAPC::Asia Tokyo 2015 参加ついでに会社(前)訪問したり飯食ったりしてた

YAPC::Asia Tokyo 2015 参加してきました - Thanks Driven Life

合間のお話

食事

マグロカツカレー。マグロカツ美味しかった

東印度カレー商会 築地場外店 - 築地市場/カレーライス [食べログ]


親子丼

なか卯 勝どき一丁目店 - 勝どき/牛丼 [食べログ]




朝シースー最高!!

店舗詳細 | 東京の寿司 マグロが自慢【すしざんまい】つきじ喜代村


朝ぎゅうどん

松屋 勝どき店(晴海/丼もの) - ぐるなび


ビッグサイト近くで沖縄勢と飲んだ居酒屋の茶漬。店忘れた


最高の肉

the 肉丼の店ホームページ | 高田馬場・晴海・蒲田・赤坂でステーキ丼とローストビーフ丼

会社(の前の写真を撮るための)訪問

GitHub ジャパン!!!

伝説のヒカリエのオフィス受け付け(11F)まで行って写真だけ撮ってすぐ降りました

YAPC::Asia 2015 Hackathon 会場だったというビルいって写真。
若干遠くから撮ったっぽい理由は、警備員いて怖かったからです。

まとめ

飯最高!!

YAPC::Asia Tokyo 2015 参加してきました

yapcasia.org

f:id:gongoZ:20150822233914j:plain

最高!!!

経緯

YAPC::Asia 、一昔前は名前のとおり Perl Conference ということもあり Perl まったく触っていない自分とは遠い存在でしたが、 ここ最近の「いいからテクを集中させて祭だ!!」感が良さそうだったので参加してみました。

初参加、そして最後です。

8/20 (木) 前夜祭

8/21 (金) 一日目

instagram.com

朝寿司最高

8/22 (土) 二日目

御礼

  • WiFi CONBU さんありがとうございました!!ほぼほぼ快適に使えておりました!
  • 無限コーヒーありがとうございました!! Emacs コップ最高でした!
  • 同時通訳すごかった……

    togetter.com

牧さんはじめ YAPC::Asia Tokyo スタッフの皆様、本当にありがとうございました!!!

YAPC::Asia Tokyo 2015 で、PHP の register_globals に関する LT してきました

参加しました記事はまた別で書きます。書きました → YAPC::Asia Tokyo 2015 参加してきました - Thanks Driven Life

発表直前

発表しました。

スライド

69ページ辺りで銅鑼が鳴り、LT 力不足が露見しました。最後まで行けなかったのは心残りですが、笑ってくれれば僕としては満足です。 残りのスライドで、書かれてること以外に言うことはなかったので、あとはスライドで補完していただければと思います。

ドキュメントに載ってない仕様について

ネタっぽく喋りましたが、当時この現象と直面した時は

  • php.net に載ってない
  • ググっても出てこない(みんな extract($_FILES) で余裕かましてた)

という状況で、仕方なく PHP 5.3.29 のソースコードを読んで*1、どうにか確信に近いものを手に入れました。

「載ってないなら自分から php.net にコミットするべきか?」とも思ったのですが、 今更 PHP 5.3 以下向けのドキュメント更新してもな?という気持ちがありました。単にめんどくさかったとも言います。

会場でも挙手をお願いした時、何人か PHP 5.3 以下を使用中という方々も居らっしゃったので 少しでもこのスライドがみなさまの register_globals 撲滅の助けになればいいなと思います。

merciful_polluter について

PHP 5.4 以上でも register_globals を再現するライブラリ MercifulPolluter - Qiita

作成した理由としては以下のとおりです。

  • 面白ろ4割
  • packagist でライブラリUPしてみたかった3割
  • 高まって欲しくない実用性3割

本体のコード200行、テストコード含めても500行ぐらいの軽いライブラリなので、 もし怪しいところがあっても、そこそこ手を加えやすくなってると思います。

発表について

  • みんな「採択されたトークは当日発表します!」という状況できっちり仕上げてきててすごい…
  • 国際会議場の壇上すごい緊張しました(会場キャパは1000人なので、おそらく950人ぐらいはいた)

f:id:gongoZ:20150822075643g:plain

f:id:gongoZ:20150822075933j:plain

まとめ

本セッショントークでは落選してしまいましたが、LT 採用されてある意味本セッションよりも面白い壇上(通常であればキーノートレベルしかできないところ)に立つことができたので 結果的に良かったかなと思います。みなさん PHP がんばりましょう!!

Docker Hub 公式 PostgreSQL イメージを用いて、データベースクラスタ作成済みコンテナイメージを作成する

いろいろあって Docker Hub 公式の PostgreSQL イメージ を使うことになりました。

序章: Docker Hub 公式 PostgreSQL イメージについて

FROM postgres:9.4

と書いた Dockerfile を用意して docker build すれば、あっという間に PostgreSQL 9.4 がインストールされたコンテナが作成できます。便利! ただしデータベースクラスタは未作成です。docker run した時に初めて initdb されるような感じです。

また、コンテナの /docker-entrypoint-initdb.d/ 以下に *.sh もしくは *.sql を配置しておくと、docker run した時に それらが実行されるようになっています。例えば

みたいな SQL ファイルを Dockerfile と同じ場所に配置して

FROM postgres:9.4

COPY test.sql /docker-entrypoint-initdb.d/

みたいな Dockerfile を使って docker build、しかるのち docker run をしてみると

$ docker build -t test-db .
$ docker run -d --name gongodb test-db
$ docker exec gongodb psql -U postgres gongo -c "SELECT * FROM users"
 name  | age 
-------+-----
 gongo | 999
 du    | 666
(2 rows)

こんな感じで、起動時に必要なデータが投入されてコンテナを使い始めることができる、というわけです。

本当にやりたかったこと

さきほど述べたとおり、この PostgreSQL イメージは docker run した時に初めて initdb する設計になっています。 つまり、このままでは データベースクラスタが作成された状態のコンテナdocker build では作成できません。 「docker run のタイミングでデータ投入されてもいいのでは」とかはとりあえず考えないでください。

ではどうするか。

  • PostgreSQL インストールして initdb までやり切る Dockerfile を自分で書き直すのか

    → めんどくさい

  • /docker-entrypoint-initdb.d/ 以下に配置したファイルを読み込んでくれるという仕組みはそのまま利用したい

    → つまり docker-entrypoint.sh はそのまま使いたい

というわけで、最終的には docker run した時にしか実行されない docker-entrypoint.sh を docker build の時にも実行してあげればいいんじゃないか ということになりました。

docker-entrypoint.shデータベースクラスタが作成済みであれば 、何度呼ばれても initdb は実行しない (postgresql サービスを起動するだけ) という仕様になっているので、docker build の時にやっちゃっても問題ないだろう、ぐらいです。

完成品

こうなりました

https://gist.github.com/gongo/531459a34e814b5c94bb

まずは Dockerfile

ここでは環境変数 PGDATA をセットしています。

なぜかというと、PostgreSQL イメージ作成時の Dockerfile では、$PGDATA が Data Volume のディレクトリとして指定されており、docker-entrypoint.shinitdb -D $PGDATA を行うようになっています。 この状態では、例え docker build 時に initdb を行っても、次に docker run する時には $PGDATA が空っぽになっているため、悲しいことになります。

そんなわけで、$PGDATA を Data Volume として指定されていないディレクトリ に指定し直しています。

次に restore.sh

つまり何をしているかというと initdb が終わったのを確認したら docker-entrypoint.sh に対して SIGTERM を投げつける ようにしています。

当初の Dockerfile は

このように、とりあえず docker-entrypoint.sh 実行しとけばいいんだろ、ぐらいの考えだったのですが、 docker-entrypoint.sh は、initdb だけではなく PostgreSQL サービスの起動も兼ねているため

  1. RUN /docker-entrypoint.sh
  2. initdb とかされて、test.sql も呼ばれる!!やったぜ!!
  3. initdb 終わったら postgresql サービス起動するぜ

     _人人人人人人人人人人人人人人_
     > docker build が終わらない <
      ̄YYYYYYYYYYYYYY ̄
    

となって、Ctrl-c とかしないと docker build から戻ってきません。そして Ctrl-c すると 正常に docker build が完了されなかった と視なされ、コンテナが作成されていない、という事態になります*1

ではどうするか。initdb まではやって欲しい。でもそのあとの exec gosu postgres "$@" は終了して欲しい。

そんな願いから生まれました。

  1. 最終的に止めたいプロセスは、exec gosu postgres "$@" の exec の効果により docker-entrypoint.sh と同じ PID になるため、まずはそれを取得
  2. /docker-entrypoint.sh の標準出力を監視し、initdb終わったと思われる出力が見つかった ら、監視(while read)をやめる
  3. 1 で取得した PID (つまり PostgreSQL サービス) に対して SIGTERM を発行
  4. 最終的に /docker-entrypoint.sh正常に終了した と判断され、docker build が完了

という感じになりました。

mkfifo 使ったのはまあいろいろあったんですが、結果的にこれでうまく動いてるので良しとしています。

最後の sleep 3 は、exec gosu postgres "$@"PostgreSQL が起動するのを待つためのものです。もしかしたら要らないかも。

まとめ

別に docker run のタイミングでリストアしてもいいだろ。なに?「docker run のタイミングでリストアするのに時間かかる巨大なデータがあると、コンテナが起動しても 実際にデータが作られるまで時間かかるから、すぐに使いたいと思ってもデータができてない可能性がある」だと?そんな巨大なデータは Data Volume Container に置いとけば いいじゃないか。なに?コンテナ一つだけだと足りないだと?そんな我侭(ry

みたいな自問自答は今でもあるのですが、ひとまず数行のコードで目的が達成できたので良しとします。 restore.sh の方では mkfifo つかったりしてるんですが、もしかしたらもっと簡単に書けるかも。 あと、「Data Volume で指定されてるから〜」のところもちゃんと検証はしていません。セットしなおさなくても大丈夫かも

とりあえず終わり。

経緯

そもそもなんで データベースクラスタ作成済みコンテナイメージ が欲しかったのか。本題ではないので最後に持ってきました。

あなたの安心を高速に守る Container-based CI

現在職場で用いている CI を使ったテスト環境(コンテナの構成)は、上のスライドの通り

f:id:gongoZ:20150804220614g:plain

  1. test-queue で放ったテストケースを Selenium Hub が受け止める
  2. Selenium Hub に接続している Selenium Node にテストケースが割り振られる
  3. それぞれの Selenium Node が、起動時に「こいつ使え」と教えられた App に対してアクセスする
  4. PHP (Apache) + DB が動いているコンテナ上で Selenium の命令が実行される

このようにして、並列実行でよくある 違うテストケースが同じデータを触ってテストが失敗する みたいなのを防いでいました。

今のところ順調なのですが、最近こうしたいなーということをチーム内で話し合っていました。

f:id:gongoZ:20150804221126p:plain

つまり、これまで 「App+DBコンテナ」としていたものを

  • PHP(Apache) だけが起動している Web アプリケーションコンテナ
  • PostgreSQL だけが起動している テストデータリストア済みの DB コンテナ

の2つに分割したい、というものです。

  • 「テストデータだけ差し替えたいのに App インストールも含めてコンテナ作り直すのちょっとな」
  • 「DaaS (Databaee の方) に近い環境に併せたいので、やはり App とは分離しておかないと」

とかいろいろ理由はありましたが、とりあえずやってみよう、ということになりました。

そんなわけで

  • PostgreSQL の Dockerfile を一から書くのめんどくさいし Docker Hub 公式のやつあるからこれ使おう-
  • あーでも、これテストデータリストアされた状態のコンテナが作れないなーうーん

といった悩みから最初に戻ります。

参考

*1:もしかしたら none で image はできてるかも