Thanks Driven Life

日々是感謝

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 はできてるかも

Dozens の設定を Terraform でやってみた (terraform-provider-dozens)

経緯

職場の Ops チームが AWS の各種設定を Terraform で行うようになっており、 自分も Terraform 触っていかねばという気持ちになりました。

まずは個人的に使っている環境で試してみようと思い、 現在 Terraform が扱えるプロバイダ とかいろいろ調べていたところ Terraform built-in ではないのですが、こういうものを見つけました。

github.com

Dozens とは

dozens.jp

このサービス、私も使わせてもらっており、これまでは画面から設定していました。これを Terraform で設定してみよう!!

インストール

README にも書いてあるとおり

$ go get -u github.com/takebayashi/terraform-provider-dozens

知っている人は知っている諸注意なのですが、Terraform のカスタムプロバイダを使用する時は、 そのバイナリが terraform と同じディレクトリ内に配置されている必要があります。

例えば今回の場合、↑ の go get とすると $GOPATH/bin/terraform-provider-dozens が作成されると思いますが、 これを terraform と同じディレクトリにコピーだったり symlink を張っておきましょう。

そうでないと、terraform 実行時に

* Provider 'dozens' not found
* 1 error(s) occurred:

みたいなエラーが出ると思います。

やってみる

1. .tf ファイル作成

provider "dozens" {
        user = "user"
        key = "key"
}

resource "dozens_domain" "gongoexample_org" {
  name = "gongoexample.org"
  mail = "gongo@example.com"
}

resource "dozens_record" "foo_gongoexample_org" {
        depends_on = "dozens_domain.gongoexample_org"
        domain     = "gongoexample.org"
        name       = "foo"
        address    = "192.0.2.123"
        type       = "A"
        ttl        = "7200"
        priority   = ""
}

resource "dozens_record" "gongoexample_org" {
        depends_on = "dozens_domain.gongoexample_org"
        domain     = "gongoexample.org"
        name       = ""
        address    = "gongo.github.io"
        type       = "CNAME"
        ttl        = "7200"
        priority   = ""
}

こんな感じのを作ってみました。

2. apply

$ terraform apply
dozens_domain.gongoexample_org: Creating...
  mail: "" => "gongo@example.com"
  name: "" => "gongoexample.org"
dozens_domain.gongoexample_org: Creation complete
dozens_record.gongoexample_org: Creating...
  address: "" => "gongo.github.io"
  domain:  "" => "gongoexample.org"
  ttl:     "" => "7200"
  type:    "" => "CNAME"
dozens_record.foo_gongoexample_org: Creating...
  address: "" => "192.0.2.123"
  domain:  "" => "gongoexample.org"
  name:    "" => "foo"
  ttl:     "" => "7200"
  type:    "" => "A"
dozens_record.foo_gongoexample_org: Creation complete
dozens_record.gongoexample_org: Creation complete

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

できたっぽい。Dozens のコントロールパネルで確認してみると

f:id:gongoZ:20150705220653p:plain

できてるっぽい!!

.tf ファイルから resource を削除してから terraform apply すると、ちゃんとレコードやドメインが削除されるのも確認しました

まとめ

Terraform が標準でサポートしている各種サービスで充分まかなえるとは思いますが、 今回のようなカスタムプロバイダを作成したり、それを利用するのも結構手軽にできるのは良いですね。

補足参考資料

Turnip 1.3.1 リリースしました (RSpec 3.3.0 対応)

Release Version 1.3.1 · jnicklas/turnip

テスト用に gemfiles/ 以下に RSpec 毎のファイルが増えてくると不安になる…

主な内容

掲題のとおり、RSpec 3.3.0 対応となります。

RSpec 3.3 has been released!

RSpec 3.3.0 の変更で Turnip に影響があったのは、テスト実行の際に行数が指定された時の挙動です。

どういうこと

つまり

$ rspec /path/to/foo.feature:12

みたいなやつです。RSpec 3.3.0 からは ↑ のような行数だけではなく

$ rspec /path/to/foo.feature[1:4]

このように書くことで

1番目の describe の中にある、4番目の it だけ(もしくは4番目の describe の中にあるやつ全部)を実行しなさい」

という指定が可能になります。

おそらくこれは手動で指定するものではなく、同じく RSpec 3.3.0 から導入された --only-failures--next-failure のための機能追加じゃないかなって思います。多分。

んでもって

この機能追加によって、Turnip 側から行数指定による Scenario の指定ができなくなっていたので

github.com

こんな感じで修正を行いました。instance_eval 万歳。

おまけ

今回のような、テストケースの指定の仕方は、Pure RSpec なテストケースであれば公式サイトを見ればわかりますが、 Turnip の場合はどのように使えるんだ?みたいな疑問がわくと思います。行数指定は前からありましたが、[1:4] みたいな ID 指定を feature ファイルに置き換えるとどうなるんだ?みたいな。

そんなわけで、せっかくなので wiki にまとめておきました。

How to identify scenario using Location (or IDs) · jnicklas/turnip Wiki · GitHub

参考になればと思います。

まとめ

RSpec どれかサポート切りたい。「RSpec 3.x + RSpec 3.(x-1) + RSpec 2.14」とかが落とし所かなー

YAPC::Asia Tokyo 2015 に参加します

そんなわけで私も応募してましたが、あえなく reject

残念ではありますが、代わりに見る聞くに専念できるようになりました。

yapcasia.org

採択倍率4倍という凄まじい応募数から選ばれたトーク軍。どれも面白そう。
当日を楽しみにしています。ビッグサイトで僕と握手!!


ちなみに応募したトークにも書いてるとおり、チームで行ってきたことを
イベント駆動でまとめていこうかなと思いましたが、それも無くなったので
あとはほそぼそとどこかに書いていけたらなと思います。


それでは当日よろしくお願い致します! 沖縄から上京して疲れきった人が居たら僕です。

vue.js 0.12-{beta,rc} から v-repeat="文字列 | filter" すると filter に辿り着く前に文字列が配列になってる

うまく説明できないタイトルになった。

例題

<html>
    <head>
        <meta charset="UTF-8">
        <script src="vue.0.11.min.js"></script>
        <!-- <script src="vue.0.12-rc.min.js"></script> -->
        <!-- <script src="vue.0.12-rc2.min.js"></script> -->
    </head>
    <body>
        <div id="demo">
            <ul>
                <li v-repeat="foo | split">
                    {{$value}}
                </li>
            </ul>
        </div>
        <script>
         var demo = new Vue({
             el: '#demo',
             data: {
                 foo: '11111'
             },
             filters: {
                 split: function(n) {
                     console.log(n); // <= ここをチェック!!!
                     return [1,2,3];
                 }
             }
         });
        </script>
    </body>
</html>

こういうのがあったとして

1) 0.11 の頃は

f:id:gongoZ:20150611223738p:plain

2) 0.12 からは

f:id:gongoZ:20150611223820p:plain

まとめ

filter で文字列受けとってよしなに分割して変換して返すってやってたところが動かなくてなんでだろうって思ってたら、こういう変更があったんだということに気づけました。前の状態でもいいのにーと思ったけど、まあ自分がやってたのは filtering じゃなくて transform だったんだなと思うと、特に問題は無かった。

そんなわけで pizza-figlet が無事 vue.0.12.-rc2 で動いた

適当な日本人の名前や住所をランダムに作れる gimei を emacs lisp に port した

経緯

mattn.kaoriya.net

ビッグウェーブに乗った

過去

人名をランダムに生成する emacs lisp fake-full-name.el - Thanks Driven Life

こんな記事を書いたこともありました。この elisp はちょくちょく仕事でも使っていて、ちゃんとリポジトリ作成しようかなーでもなーって悩んでたところ、gimei の話題が見えましたので、あらためてこちらの方を作成しました。

現在

github.com

Usage

https://github.com/gongo/emacs-gimei#usage

まずは人名:

(require 'gimei)

(setq name (gimei/new-name))   ;; [cl-struct-gimei:name]

(gimei/kanji-of name)          ;; "浅野 深桜"
(gimei/hiragana-of name)       ;; "あさの みお"
(gimei/katakana-of name)       ;; "アサノ ミオ"

(gimei/last:kanji-of name)     ;; "浅野"
(gimei/last:hiragana-of name)  ;; "あさの"
(gimei/last:katakana-of name)  ;; "アサノ"
(gimei/first:kanji-of name)    ;; "深桜"
(gimei/first:hiragana-of name) ;; "みお"
(gimei/first:katakana-of name) ;; "ミオ"

;; 男性名/女性名 を明示

(gimei/kanji-of (gimei:new-male))   ;; "篠田 繁夫"
(gimei/kanji-of (gimei:new-female)) ;; "稲葉 澪"

住所はこんな感じ:

(setq address (gimei/new-address))  ;; [cl-struct-gimei:address]

(gimei/address:kanji-of address)    ;; "山梨県杵築市宮浦"
(gimei/address:hiragana-of address) ;; "やまなしけんきつきしみやのうら"
(gimei/address:katakana-of address) ;; "ヤマナシケンキツキシミヤノウラ"

(gimei/address:prefecture:kanji-of address) ;; "山梨県"
(gimei/address:city:hiragana-of address)    ;;"きつきし"
(gimei/address:town:katakana-of address)    ;; "ミヤノウラ"

変数に受けずに直接呼び出すことも可能:

(gimei/kanji)    ;; "宮原敏也"
(gimei/hiragana) ;; "あだちてつ"
(gimei/katakana) ;; "キシモトコタロウ"

(gimei/address:kanji)    ;; "神奈川県河内長野市藤助新田"
(gimei/address:hiragana) ;; "みえけんわたりぐんやまもとちょうきたじゅう"
(gimei/address:katakana) ;; "オオサカフコユグンシントミチョウナカノバタチョウ"

使用例

(require 'cl-lib)

(cl-loop for i from 1 to 10
         do (let ((name (gimei/new-name))
                  (address (gimei/new-address)))
              (insert "\"" (gimei/kanji-of name) "\""
                  "," "\"" (gimei/katakana-of name) "\""
                  "," "\"" (gimei/address:kanji-of address) "\""
                  "\n")))

;; => "村井 建喜","ムライ タテヨシ","宮崎県稚内市平成"
;;    "樋口 一","ヒグチ ハジメ","岩手県遠賀郡遠賀町下川上"
;;    "森 寿英","モリ トシヒデ","静岡県八丈島八丈町上磯分内"
;;    "柏木 桃世","カシワギ モモヨ","山梨県比企郡嵐山町新町"
;;    "金田 充扇","カネダ ミオ","青森県吾妻郡草津町木島"
;;    "井口 静江","イグチ シズエ","長野県相楽郡精華町清尾"
;;    "多田 征蘭","タダ セイラ","岡山県雨竜郡沼田町金山沢"
;;    "松本 範人","マツモト ノリヒト","熊本県羽咋市西恋ケ窪"
;;    "渡辺 生夏","ワタナベ ミナツ","山口県霧島市引佐町栃窪"
;;    "川島 健郎","カワシマ タケオ","岐阜県東白川郡矢祭町猫田"

即席テストユーザ CSV ファイル

おまけ

Emacs Lisp って YAML parser 無いんですね。無かったので オリジナルデータ を alist に変換して gz で配置するなどして対処しました。

まとめ

これでテストデータもばっちりですね!テストしていこう!!(この elispちゃんとテスト書きました)

TravisCI で composer install のキャッシュを取る

目的

早く速くビルドしてくれ頼む 🙏

手順

  1. container-based でビルドが走るように設定する
  2. composer install がキャッシュ置く場所を .travis.yaml に書いとく

Use container-based stack and caching of TravisCI · gongo/merciful-polluter@91bffba

sudo: false
cache:
  directories:
    - $HOME/.composer/cache

簡単

結果

Job #28.1 - gongo/merciful-polluter - Travis CI

f:id:gongoZ:20150604131832p:plain

しっかりキャッシュ効いてるっぽい 💪

まとめ

そんなわけで PHP7 でも register_globals が再現できるようになった ので、安心して PHP7 にアップデートしましょう!!!

参考