ラベル Ruby の投稿を表示しています。 すべての投稿を表示
ラベル Ruby の投稿を表示しています。 すべての投稿を表示

2020/03/29

pry 0.13.0 で後方互換性を保ちつつ history file の path を設定する

pry が 0.13.0 になった際、実行履歴ファイルの指定方法が変更になった事への対応。加えて後方互換もつけたログ。


pry の警告(ruby 2.7.0 + pry 0.12.2)

ruby 2.7.0 で pry 0.12.2 を起動すると、以下のような警告が出ていました。
私は怠惰なエンジニアなので、問題が発生するまでは様子見をする事にしました。


pry 0.13.0 Released

そして、2020/03/21 に pry の version 0.13.0 がリリースされました。
素晴らしい事に起動時に例の警告が出ません。 pry の開発陣に感謝ですね。
しかし、これでハッピーエンド、という訳にはいきませんでした。


Pry.config の変更

私は pry の history file の path を指定していました。具体的な設定は以下。
0.12.2 はこの設定方法で問題ありませんでした。
しかし、 0.13.0 では設定方法が変わったようで、以下のように NoMethodError が発生します。
という事で設定方法を調べてみます。


設定方法探し

さて、指針も無く彷徨っても仕方が無いので、まずはそれらしい場所にあたりをつけます。
設定時、 Pry.config と書くので、 Config class か module あたりが無いか探します。
  • $ git grep Config
すると lib/pry/config.rb に Config class がありました。
流し読みしつつ、 history で検索します。そうすると history に関連するコードの集まりを見付けました。
さらに言えば history_file なる attribute がありますね。とてもとても怪しい。

Pry.config.history_file の値を pry で確認すると、今書き込まれている history の path でした。
変数が特定できたので、まずは path を代入してみます。 弾かれないか心配でしたが特に問題無し。
pryrc で Pry.config.history_file を設定すると、指定した path に history が書き込まれました。これで解決。

なお、history_file 辺りを blame して commit を確認すると
`Pry.config.history.file` becomes `Pry.config.history_file`
とありました。なので調べた結論で正しそうです。


0.13.0 以前の history との互換性

さて、 pry 0.13.0 で history file の path を指定できました。

しかし bundler で gem を管理していると、バージョンが異なる pry が複数、なんてケースもあると思います。
幸い history file の中身は plain text なので、バージョン毎の違いは恐らくありません。
各 pry で同じ history の path を指定する事で、今まで通りバージョンに関わらず共通の history を参照できるはずです。
ということでバージョンが低い pry も考慮に入れた pryrc が以下。

  • respond_to? でバージョン確認
  • path は instance variable で一時的に定義して最後は消す
といった感じにしました。今の所は問題無く動作しています。

2020/02/09

neovim + deoplete で kebab-case の補完をする

kebab-case(単語をハイフンで区切る) は snake_case や camelCase に比べ、比較的使われていないと思います。
ですが、 apt-get や docker-compose を打っていると少し利点を感じました。
  • 「Shift Key を押さなくて良い」
怠惰な私にとって採用理由として十分。なので zsh で使う function は kebab-case で記述しています。

さて、ここで小さな問題が発生しました。
neovim で利用している補完プラグインの deoplete が、default の設定では kebab-case を補完してくれない。
と、いうことで kebab-case も補完対象にするように設定したログです。


環境

  • OS: macOS Mojave 10.14.6
  • zsh: 5.7.1 (x86_64-apple-darwin18.2.0)
  • neovim: v0.4.3
  • deoplete: master(記事執筆時 6e01000280)
  • Python: 3.7.6
  • pynvim: 0.4.1


補完の設定

まず、補完の対象を設定する変数などが無いか、:help deoplete で探します。
ありました。keyword_patterns で filetype 別に正規表現を指定すれば良さそうです。
というか ruby の補完サンプルもありますね。末尾の ! や ? に対応している。便利そう。

話が逸れました。まず、kebab-case にマッチしそうな正規表現として
  • 最初の文字は alphabet と '_'
  • それ以降は alpabet と '_' と '-' が0回以上繰り返される
とします。これを具体的な正規表現にすると
  • [a-zA-Z_][a-zA-Z_-]*
で良さそうです。これを filetype zsh の補完に設定します。
何故 sh でなく zsh かと言うと、 sh では kebab-case のfunction が定義できない為です。

設定後、きちんと補完されるようになりました。良し良し。




おまけ(他の定義とか)

これで deoplete の補完対象の設定ができるようになりました。
zsh 以外の普段良く使う filetype の補完設定もしておきます。
  • ruby: 先程発見した ruby の補完設定で、先頭に '@' を加えたもの
  • text: 英数字も含めて補完対象を広めにしたもの
  • _: help にあった定義をそのまま利用。なお、この設定は全 filetype に適用されます
これらを鑑みて設定した、私用の具体的な keyword_patterns の設定はこんな感じ。以下抜粋。

2018/12/07

ReFe + ref.vim で Neovim から Ruby の document を読む

この記事は Okinawa.rb Advent Calendar 2018 の 7 日目の記事です。
昨日の記事は @hanachin_ さんの 『refineをしてるモジュール取り出せるrefinements_robbery gem』 でした。


なにができるの

カーソルが指している(クラス|メソッド)名のドキュメントが一発で引けます。
これが
こうじゃ

的な感じ。


環境

OS: macOS Sierra 10.12.6
Neovim: 0.3.1
ReFe: 2
Ruby: 2.5.3p105 (2018-10-18 revision 65156)
vim-ref: b45d10f6d0c82e3afd17774626e952fde03274a1



NeoVim + vim-ref

私の環境では NeoVim を使っていますが、Vim でもおそらく動くはずです。
vim-ref というリファレンスを読むのに便利なツールがあり、2014年あたりから私は活用させてもらっています。


ReFe 2

クラス名かメソッド名を指定すると該当するドキュメントを表示してくれるツールです。


ReFe + vim-ref

さて。この二つを組み合せていきます。

  • $ bitclust setup
で refe を使えるようにします。
この時点で
  • $ refe Integer
とするだけで

のように表示できます。便利。

ちなみに vim-ref は plugin を入れると refe に関しては設定無しでも動きます。
normal mode で K を押すだけ。便利便利。


まとめ

ということで vim-ref を使って ReFe 2 を引くお話でした。
ちなみに vim-ref で開いた buffer 内のワードでも refe を引けるので、色々と探検できたりします。
後、地味な利点としてオフラインでもドキュメントが見られる点があります。
快適な Ruby ライフをー

2018/09/13

ISUCON7の予選問題を解いて、スコアを27万点まで上げた

こんにちは。フリーランスのエンジニアのあっとんです。
仕事が一段落したのもあってプチ夏休みという名の仕事が無い期間、ISUCON7 の予選問題をやったりしました。
それを改めてログにまとめる次第です。

結論から言えば、予選突破のボーダーの21万点を越えられました。
ちなみに累計作業時間は58時間くらいで、最終スコアは 270957 でした。
あとリモートワークのお仕事を募集中(2018/09/13現在)です。


どうして ISUCON の問題を触ってみようと思ったのか

  • Twitter でたまに見た単語で気になっていたので
  • ミドルウェアをインストールする事はあってもガリガリにチューニングする経験が無かったので
  • とりあえず数字で善し悪しが分かるので


自分レギュレーションと目標とか

  • スコア的な目標は予選突破ボーダー(21万点くらい)
  • とりあえず最初(ISUCON7予選)はブログとか見て良いのでノウハウを積む
  • ノウハウを積んだ上で本戦問題を時間制限+解説見ずに解く


環境構築とベンチマーカを動かす

まずは環境構築から。
とりあえず GCP の上に 4core 8GB の instance を上げる。
ISUCON7 の予選問題の README.md に従って環境構築。
初期スコアは5000点くらいだったと思います。
ベンチマークをかける側も 4core 8GB で環境構築。


なんとなくの知識で高速化をする

とりあえず知ってる範囲で適当に進めていく。
  • nginx の proxy を経由せずに直接 puma で 80番を bind するように
    • スコアが 3000 くらいになる。 却下。
  • rackup する時に environment を production にする
  • logger を無しにして log を disk に書かないようにする
    • 一応スコアが 5980 くらいにはなる
  • css と js を minify する
    • yui-compressor なるものを使う
    • $ yui-compressor main.css -o minimain.css # とか
    • Validation に失敗するのでこの手は使えない
  • gzip で圧縮してみる
    • .gz を置いて配る
    • そんなにスコアが変わんなかった
  • N+1 を解決したいが SET IN ... に書き換えるのが面倒
    • 普段 ActiveRecord を使っていたので includes で解決して欲しい……
    • (後から結局 sinatra-activerecord を入れることになる)
  • SELECT * の結果に対して first していた部分に LIMIT 1 を付ける
    • 1万点くらいになる
  • メモリの上に DB の全データを載せる
    • tmpfs + rsync で載せてしまう
    • 19340 点になる。倍。
  • mysql の log の WARN を取り除く
    • 22511 点になる。10%増し。


予測するな計測せよ

結局何が一番のネックなのかが分からないとどうしようもない。
ということで計測していく。
profiling には rack-mini-profiler を使ってみた。
さてこれでどこがネックなのか探るか、と思ったら作成されたファイルが読めない。
結論から言えばファイルに書く前に Marshal.dump されているせいで Marshal.load しないと読めなかった。
Marshal.dump を JSON.dump に monkey patch して無理に plain text にして jq で読む。
しかしファイルが多すぎて(200個とか)まともに読めない。
profiler を使うのは諦めて top だけで行くというゴリ押しをする。
(rack-mini-profiler を rack application で動かす、というのも記事にできそうなので後から書くかもしれない)
なお、初期の段階でベンチマークをかけると CPU を mysql が 55% 、puma が 35% くらい食っていて DB ネックだった。


参考文献を参照開始


1Core 1GB にスペックダウン



11万あたりの伸び悩み

その後もチマチマと変更を加えるものの、12万点の壁は越えられず。
CPU使用率も合計100%にならなくなった。
気付くのに結構かかってしまったけれど、オチとしてはメモリ不足。
具体的には残り10k とかになっていた。
というわけでここからは複数台構成にする。


複数台構成



lsyncd を使って画像の同期

解決すべき問題は icons を3台の間でどう整合性を取って返すか。
業務だったら S3 に置くなり CDN を使うのが良さそう。
しかし ISUCON なので、手元にあるサーバでどうにかする必要がある。
画像ファイル自体は1MB以下なのが保証されている。
なので書き込み時に memcached じゃなくて全サーバに書き込むことに。
そうすれば /icons を nginx が Cache-Control 付きで返してくれて良さそう。
(nfs で mount しても良かったのですが、結局 network access が発生するので早くならなそうだったので断念。)
(nfs の cache の config でどうにかなった可能性もゼロではないが……)
最初は inotify + rsync でゴリ押ししようと思ったのですが、 lsyncd なるものを発見。
(rdiff-backup とか osync とか csync2 とか unison とか mirror とかあるがどれも古い)
サーバA は更新があったらサーバB に rsync。 B は C へ。 C は A へ。という形に。
これでファイルが upload されたら全サーバが共通のファイルを返せる。
結果、1台だけの時にスコアが 69770 に。3倍にできれば目標達成かな。


最終的な構成

3台 nginx にするより、2台 nginx + DB のがスコアが高かった。
あと mysqltuner というスクリプトを見つけて、それで値を調整。
最終的には score 270957 を達成したので脳内予選突破。



まとめ

  • ISUCON7 の予選問題のスコアを 27 万点くらいまで上げました。
  • 作業ノートによると58時間くらいかかっているらしい。
  • これで夏休みの自由研究2018としましょうか。



参考

2017/12/03

Ruby を使って Kobo の情報を取得する #okinawarb

この記事は Okinawa.rb Advent Calendar 2017 3日目の記事です。
2日目は @_atton さんの「amazon-ecs を使って Kindle マンガの情報を取得する」でした。
というか私ですね。連投です。


経緯

しかし Amazon Advertising API の利用は却下されてしまいました。
取ってしまったドメインを何とか活用しようと思ったログです。


どうして kobo なのか

当初は BookWalker か honto あたりで同じことをする予定でした。
しかし BookWalker ではそれらしきものは無し。
honto は完全に更地でした(これが本家かは不明)。
ということで両方とも無理。

困っていたら本関連のAPI まとめを発見。
軽く見ていると、リンクは辿れませんが楽天のAPIがあります。
楽天と言えば kobo があることを思いだす。

なので kobo の API が無いかなー、と思って探すとありました。
ドメイン取っちゃったし、何らかのコンテンツを提供するために使うことにしました。


Rakuten Developers 登録

Amazon Advertising API と同様にまずは token を手に入れるところから。
developer 向けページでアカウントを作成。
New App で Application を作ります。なんだか Twitter みたいですね。
そうしたら
  • (Application|Developer) ID
  • application secret
  • Affiliate ID
が発行されます。
これらのパラメタを使ってリクエストを組み立てることになります。


厚い無料本の山

API endpoint は https://app.rakuten.co.jp/services/api/Kobo/EbookSearch/20170426 です。
これに対して post すれば情報が手に入る。
日付が入ってるのが気になる。リリース日とかなのだろうけれど。
詳細は楽天Kobo電子書籍検索APIにありますが、いくつかの条件を付与可能。

無料本は sort='+itemPrice' とかすると出てくるので問題は無い。
しかし、 minimum_price 相当が無いっぽい。これは痛い。
加えて、kobo には大量の本が登録されているので、sort='+itemPrice' とかしてると有料の本まで取得できない。
具体的には 30 * 10 とかしても無料のものばかり。
分冊版とかおためし版とか青空文庫があるのが原因な気もします。

その辺をフィルタすると50冊くらいにになる。が、無料のものしか無いのは変わらず。
頭が痛い例としては、ユーザアンケート調査が本として登録されているとか。

無料本の何が問題かと言うと、報酬面。
アフィリエイトは有料本を買ってもらわないと報酬が発生しないので、無料本を買って貰っても報酬ゼロ。
なのでマネタイズができない。困った。
結局 ebook-comic.info のドメインは取ったものの、どうしようかは考え中です。
何かアイデアあればください。


ドキュメントについて

ちなみに、ドキュメントは Kobo の方が断然良い。
実際、誰かのブログを見ずに公式ドキュメントだけでリクエストを作れた。
Amazon Advertising API は微妙で、まずドキュメントはログインしないと見られない。
その上、ドキュメントの記述とレスポンスが違ったりする。つらい。
大抵 body にエラーが書いてあるので読めば問題は対処は可能ですが。


リクエストのサンプルコード

なお、koboのリクエスト例はこんな感じ。
1リクエストで30個(Amazonは10個)取れたりと都合が良いところも多いのです。
しかし、私の周辺では kobo を使っている人を見たことがない……


まとめ

とりあえず無料本を取得するところまではできました。
しかし affiliate の収入が絶望的なので使わない気もする。

ということで Okinawa.rb Advent Calendar 3日目の記事でした。
なお、クリスマスプレゼントはこちらで募集しています。よろしくね。

明日の担当は @_simanman さんです。


環境

  • OS: Ubuntu 16.04.3 LTS 
  • ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]


参考

2017/12/02

amazon-ecs を使って Kindle マンガの情報を取得する #okinawarb

この記事は Okinawa.rb Advent Calendar 2017 2日目の記事です。
1日目は @hanachin さんの「Rubyで国民健康保険料を計算する」でした。

Amazon は Product Advertising API というものを提供していて、商品を検索したりできます。
利用するためには affiliate.amazon.co.jp への登録が必要です。
加えて、 Advertising API を使うサイトなども申請する必要があります。
ちなみに登録に関してはにはこちらの記事が有用です。

Product Advertising API の token を取得したので、いざ商品検索。
具体的にはこちらを参考にしつつ書き始めましたが、この記事では商品検索リクエスト(item_search)を使っています。
私は検索ではなく「特定の値段以下のKindle本」が欲しいので、send_request で直接リクエストを作ることにしました。

とは言え実行するアクションは「ItemSearch」なので検索に相当します。
なお、 Advertising API は「特定のノード以下の情報を検索」ができます。
browse_node: 2293143051 というマジックナンバーがそれです。
2293143051 は Kindle book store のノードのようです。
Amazon本家のサイトの「Kindle ストア」にカーソルを合わせたりすると URL に含まれていて、他にも大量にあります。

さて、リクエストの目星が付いたので実行してみましょう。
あれ。取れていませんね。何故だろう。

デバッグのために request を inspect したりして request の URI を取得。
その上参考記事の URI と比較すると、どうやら amazon.com に request を送っているようです。
(ここはエラーの詳細を書きたかったのですが、BANされたので割愛)
ということで marketplace: 'ja' を追加すると amazon.co.jp にリクエストを送るようになりました。
これで無料の Kindle 本を取得できました。


まとめ

amazon-ecs を使って無料の Kindle 本を集めることができました。
私はやってないのですが、これを購入する部分まで書くと素敵なことになりそうですね。

ということで Okinawa.rb Advent Calendar 2日目の記事でした。
なお、クリスマスプレゼントはこちらで募集しています。
明日の担当は @_atton さんです。というか私です。そして3日目の記事はこちら。


環境

  • OS: Ubuntu 16.04.3 LTS 
  • ruby: 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]
  • rubygems: 2.6.13
  • amazon-ecs: 2.5.0


参考

2017/11/13

Ubuntu 16.04 LTS で NeoVim を使う

AWS の EC2 インスタンスに Ubuntu 16.04 があったので使ってみる。
環境構築の中で NeoVim のインストールと設定をしたログ。


環境

  • OS: Ubuntu 16.04.3 LTS
  • apt: 1.2.24
  • NeoVim: 0.2.0
  • Ruby: 2.3.1p112
  • neovim gem: 0.6.1
  • Python2: 2.7.12
  • pip2: 8.1.1
  • python2-neovim: 0.2.0
  • Python3: 3.5.2
  • pip3: 8.1.1
  • python3-neovim: 0.2.0


NeoVim 本体をインストール

  • # apt-get install -y software-properties-common python-software-properties
  • # add-apt-repository ppa:neovim-ppa/stable
    • Enter 押す必要があったけれど -y とか付けたら良かったのかな
  • # apt-get update
  • # apt-get install neovim
で neovim 本体は入ります。楽で良い。


dot_files を設定する

私は dot_files を公開しているので
  • git clone https://github.com/atton/dot_files
  • cd dot_files
  • ./install.sh
で設定終了。備えあれば何とやら。


NeoVim の環境を整える

私が使ってるプラグインが python3 必須だったりするのでその辺も整備。
  • # apt-get install -s ruby bundler
  • # gem install neovim
  • # apt-get install -y python3 python3-dev python3-pip python python-dev python-pip
  • # pip3 install neovim
  • # pip install neovim
で python2 + python3 + ruby の環境が整った。
:CheckHealth すると clipboard が無い以外は問題無い、とのことでOK。
自己診断機能便利ですよね。 brew doctor とか。


おまけ

ec2 の IP 覚えるとか domain 振るのとか面倒なので私の ~/.ssh/config はこんな感じ。
というかOSごとに default user name 違うってなんでやねん。

2017/10/07

Akatsuki を OSS 化した

Akatsuki を OSS 化して GitHub で公開しました。



Akatsuki とは琉球大学情報工学科のシステムです。具体的には
  • 学生のIP申請
    • 対応するドメインを内向き DNS へと登録
    • その IP を DHCP で配れるように
  • 学生のVM新規作成/起動
  • 学生のパスワード変更
    • unix login
    • 802.11X で無線に繋ぐ際の RADIUS を提供
といったことができます。


つまり今まで記事で部分部分を解説していたものの本体です。
関連している記事は以下。


公開にあたって

もちろん大学の内部情報をそのまま GitHub に投げるわけにはいきません。
なので、こいつは OSS 化用にいじってあります。
具体的には漏れたらまずい情報を消してあります。
あとライセンスは MIT だし気にせず自分でいじって公開していく。

その手順として
  • $ git rm very-important-information
    • で公開してはいけない情報を消して
  • $ git commit
    • コミットします。
  • $ git rebase -i --root
    • その後に rebase します。
    • 全部 fixup して一つのコミットにまとめてしまいます
    • fixup しないと復元ができてしまうので、消しても意味がない。
    • ということで、逆に言えばこの手順を踏めばアカンやつを隠蔽できます。


まとめ

所謂実績の一つとして見てもらえると良いかなー、と思って OSS 化しました。
ということで、大学生/大学院生時代にしていた仕事の1つが OSS になりました。

2017/10/06

Heroku の Hobby プランの PostgreSQL が一杯になったので Standard にアップグレードした

Rails を使った Web application を heroku の hobby プラン(無料枠) で運用中、DBへの書き込みができなくなる。

  • $ heroku logs

を漁ると

ActiveRecord::StatementInvalid (PG::InsufficientPrivilege: ERROR:  permission denied

とある。何事だろう。
調べたら、レコードが多すぎる時に発生するらしい。

  • $ heroku pg:info

すると無料枠の 10k レコードを越えていました。
というかぴったり 10k 制限じゃないんだ。優しさなのかなんなのか。

参照した記事の場合、レコードを消して対応していました。
私の場合、レコードを消すわけにはいかないので、 hobby からの upgrade をすることにしました。


コマンド

正直なことを言えば公式のドキュメントが最強です。
指示通りでそのまま問題なくアップグレードできます。
なので、コメント付きで公式ドキュメントをなぞるくらいの気持ちでログを残します。
  • $ heroku maintenance:on
    • Application を止めます。メンテナンスモードですね。
    • おお、これは所謂「停止メンテ」だ。何が起こるか分からないのが良いですね。
  • $ heroku addons:create heroku-postgresql:standard-0
    • DB を新規に作成。プランは standard にしました。
  • $ heroku pg:wait
    • 新規作成したDBを使えるようにします。
  • $ heroku pg:info
    • を見ると DB が増えています。
  • $ heroku pg:copy DATABASE_URL HEROKU_POSTGRESQL_BLACK_URL
    • 古いDBから新しいDBにコピー。
    • DATABASE_URL は置換するとかではなく、この文字列で正しい。
    • なので postgresql-lively-12345 とかに書き換える必要は無いです。
    • 公式ドキュメントだと PINK とかだったかな。どうやらネーミングは色らしい。
  • $ heroku pg:promote HEROKU_POSTGRESQL_BLACK_URL
    • 新しい standard の方をメインのDBにセットします
    • これで DB の移行は終わり
  • $ heroku maintenance:off
    • メンテナンスモード終了
    • これで DB がアップグレードされた状態で Web Application が上がります。
流石 PaaS 。サクサクメンテができるのでインフラに手間がかからない。
ということで淡々とアップグレードができたのでした。ぱちぱち。



参考

2017/09/23

RGhost に PR を送ってマージされた

現プロジェクトで画像を扱う機会があって RGhost を使っていました。
その時以下のようなエラーが。パッと見て何が起きたか何も分からない。

具体的にその時は Tempfile を渡していたのですが、nil 関係無くても出てくる。

ちょっと困ったなー、ということで調べてみる。
具体的にエラーが発生しているのは shellescape というメソッド。
これを呼んでるのはこことかこことかここ。
RGhost は ImageMagick を使って画像を変換するのでそれ用に引数を shellescape しているようです。
(書いてて思ったけれど ImageMagick 側は何もAPIとか提供してないのかな)

それで、よくよく見てみると file_in は case を使って @document のクラスで確定するらしい。
あー、それなら Tempfile は扱えないな。
ということで File にして一時の問題は解決。
他の人にも同じ轍を踏んで欲しくないので、対応してないのならメッセージが出るとかした方が良いな、と思う。

ということで PR にしてみました。
やったことは単純で、case に else 節を足して例外を投げるように変更。
RGhost::Document と File と String しか対応してないよー、と例外経由でメッセージが出るようになった。
RSpec ももちろん追加。
gemspec に RSpec のバージョンが指定されていないのが若干ひやひやものである。
3.0 未満の RSpec だと raise_error とか無かった気がする。
あと浮動小数の値がちょっと違う。なんか最近YARVに変更入ったっけ。分からない。
そのあたりも含めて最新に合わせつつ、バージョン情報とかを PR に書いて投げた。
そしたら一晩でマージしてくれた。対応早くてうれしい。

という訳で Rubykaigi の熱がさめないうちに OSS 活動をしたのであった。まる。

2017/09/22

Rubykaigi 2017 に行ってきた #rubykaigi

2017/09/18-20 にあった Rubykaigi 2017 へと行ってきました。


所感

完全にお祭り。インターネットでよく見掛ける人がその辺にごろごろいるし、毎晩ご飯とお酒が振る舞われるし。
ちょっと所用のせいで二日目からの参加になりましたが、みんな毎晩呑んで寝不足ー、みたいなお祭り騒ぎ状態でした。


真面目な感想

全体的に発表のクオリティが高い。
個人的には海外から来ている方々の発表がジョーク含め、センス良い感じがあった。
日本人の発表は良くも悪くも淡々としたので文化を感じる。
淡々と恐しいことをさらっとやってのける感じは流石だが。
この辺は文字通り国際会議的な感じはあった。文化のごった煮だ。
と言いつつ一番印象に残ったのは@syouhei さんの「Glitching ruby script」。
American Fuzzy Lop という Glitch するアルゴリズムをコードに適用して、「ちょっと変更されたけれど、実行結果は変わらない」みたいなコードを作成できる。
んでそれがコーナーケースとして刺さったりするらしい。面白い。
あと American Fuzzy Lop の画像検索結果が良いね。
感想としては、視野の広さと応用力を感じた。いやーそうくるか、みたいな。

一日目に参加できなったので笹田さんの発表が気になっている。
二日目以降の発表で「前に笹田さんが発表していたのでスキップ」が数度あった。何を話されていたのだろう。
あとは vs The World が聞けなかったのも痛いか。

実は LT に応募してたのだが、「僕らからすると別に特別目新しい内容でも無いので reject 」と言われてしまった。
メールが来た時はちょっと残念だったが、実際のLTを見るとレベル高かった。
仮に採用されていた時には確かに場違い的な内容になりかねなかった。危ない。


個人用な雑な感想とちょっとしたイベントメモとか

  • 『二つ名を得よ。』
  • 翻訳業界ではその辺で神が降臨したりしてビビる
  • 背後に隠れているコンテキストの方が話す内容に影響するというか単語の意味を決める
  • お前の単価は(高すぎる|適切|低すぎる)
  • 「我々は賢いので」
  • 「ぬるかるさん遅いですね」「あの子は最近帰ってくるの遅いから」「言い方が不良息子の母」
  • 名刺を渡そうとして名刺入れをズボンのポケットから取ったら勢い良く渡す相手のご飯の皿にダイブ。
  • 「Why did you think this idea?」
  • 「なんで髪伸ばしてるんですか」「地毛で女装するためです」
  • 「女装するとメイクスキルが上がって、顔を見るとどのくらい加工しているかが分かるようになる」
  • 超ハイテンションで帰ってきたぬるかるさん、10m歩いてクールダウンして真顔。
  • 「スピノザは良いぞ」
  • 「良く分かんねーことが発生すると神なんだよ」
  • 「聖書は自己言及もするし矛盾もしてる」「クソリプ対策みたいですね」
  • MessagePack の作者と遭遇して名刺交換。 Alice で採用していると伝える。
  • OC さんを知る者と遭遇する。世は狭い
  • 雑談していると思ったら妻帯者からの高度なのろけが飛んでくる
  • 「マイノリティに立っていきたい」「実際面倒が多い」
  • 「家って何?」
  • 君は(雑魚|普通の人|ヤバいやつ)
  • 「社交性発揮してきます」
  • 荷造り5分で東京駅に行ったら広島駅につける。4時間と2万円かかるけれど。飛行機か。
  • でも足元はゆったりで快適で読書が捗る
  • 「いやー酒が呑めるようになっちゃって」
  • 「若いっていつまで名乗って良いのですかね」「若くても老害はいる。逆もしかり。そういうことだ」
  • 唐突の「デカ外人クイーズ」
  • アメリカだと公園で飲酒は違法だったりする(ので日本で呑みに行くぜ)
  • 「Github という小さい会社で働いています」
  • データの可視化は重要だ……(メモリのフラグメンテーションを見ながら)
  • 「Type? Compile Time が無いなら Run Time で取れば良いじゃん(テスト動かしながら)」
  • 「あー草Tシャツの人ね」
  • 「今日一日で一生分の「うぇーい」を使った」
  • 超高速で便器の蓋が開くレゴ
  • 「やんちゃハウスは治安が悪い」「前は川沿いが治安悪かった」
  • 「論理やんちゃハウスと物理やんちゃハウスがある」
  • 「Typhoon season is very risky」
  • 寝不足だと眠い


まとめ

という感じで完全に祭り気分でした。実際そんな感じで過ごしてる人のが多かった印象。
体力がゼロだったので観光はしなかったが、広島限定茶葉も買ってお好み焼きも食べて広島もちょっとエンジョイした。
わいわいしてたなー。運営の皆様ありがとうございました。

2017/08/31

Rails 5.1 の form_with に remote: false を指定する時に気をつけること

Rails で form を書いていて、「ここは非同期に ajax にしよう」となった時は form_for などに remote: true を渡すと実現できます。
逆に、同期の POST にしようと思ったら明示的に remote: false とする訳ですが、 Rails 5.1 で form_with に remote: false を書いても POST にならない。
調べてみると、local オプションというものになったようです。
加えてどうやらデフォルトが ajax の様子。ちょっと引っかかりました。

とはいえ実態は data-remote に false を設定しているだけなので

のどちらで書いても同期のPOSTリクエストになります。


環境

  • macOS Sierra 10.12.6
  • ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]
  • Rails 5.1.3


参考

2017/07/18

ActiveLDAP が Timeout Module を使っていた

かなり前の記事になりますが、ActiveLDAP が Process.fork を使っているせいで PostgreSQL との相性が悪いバグがありました。
具体的には Timeout を指定すると fork したプロセスを使って Timeout を確認しているせいで pg の connection を使いはたしてしまう、というのが原因でした。

本当に具合が悪い時は Ruby がコアダンプを吐いて死ぬ時もあったので、直してしまってRuby会議2017のLTネタ にでもしようと思って調査を続行。
最新版では再現しないのでソースを読んでみたらそもそも Process.fork をしていない。
Timeout というものに block を渡している。
なんと Timeout なる Module があるようで、それに時間の処理を一任しているようです。

Timeout Module 知らなかった。
Timeout の処理って面倒なので自分が次に Timeout の処理を書くことがあったら使おうと思いましたまる。
ソース読むのはおもしろかったけれどネタが無くなってしまったな。どうしようかな。

2016/12/06

何故 Rails では View から Controller のインスタンス変数にアクセスできるのか

この記事は Okinawa.rb Advent Calendar 2016 6日目の記事です。


はじまり

むかしむかし、あるところでひとりのあっとんさんが Okinawa.rb Advent Calendar のネタをどうるか考えていました。
そこへ @CodeHex さんがあらわれ、ちょっと Rails について質問をしても良いですかと尋ねてきました。

CodeHex さん「Controller で @ を付けた変数は View からアクセスできるんですか?」
あっとんさん「Rails ではそうですね。ちなみに Ruby では @ が付いた変数はインスタンス変数なのです。」
CodeHex さん「なるほど。 Perl では @ があると配列なので違和感がありますね。」

さて、質問された時に Rails ではそういうものだという解答をしたのですがふと思いました。
「別にインスタンス変数だからと言って View から見えるのは関係が無いよね」
そこで調査班は実装を見付けるために Rails へと踏み込むことにしました。


View はどのようにインスタンス変数にアクセスしているのか

まずは View のクラスを確認してみます。
単に Controller を継承しているだけなのかもしれません。
それならこの記事は終わってしまいますがまず確認しないことには始まりません。

ということで Rails のプロジェクトを作ります。
$ rails -v # 5.0.01
$ rails new advent2016
$ cd advent2016
$ rails generate scaffold user uid:string
ここで User#index に binding.pry を仕込んで self.class を確認してみます。
[2] pry(#<#<Class:0x007ffdce310d48>>)> self.class
=> #<Class:0x007ffdce310d48>
どうやら特異クラス。さて ancestors を確認してみると
となっていました。
継承しているものに Controller らしきものはパッと見は無いようですね。
調査は続行しそうです。

そこで self を確認していると
とそのまま @_assigns というインスタンス変数があることを発見。あやしいですね。
もちろんこの段階では @users は存在しているので
$ self.instance_eval{@_assigns= nil}
としても @users は変更されません。


@_assign を追いかける

@_assign はどこで変更されるのか。
調査隊はさらに Rails の奥へと踏み込むことを決意します。
$ git grep @_assigns
するとactionview/lib/action_view/base.rb の一つしかありません。
しかもやっていることは instance_variable_set なので怪しさしかありません。
assigns は引数なのでどこかで new されているはず。
次はそれを探します。


ActionView::Base.new はどこだ

まずは素直に git grep します。test もあったので grep -v で除外。
$ git grep ActionView::Base.new | grep -v test
actionpack/lib/action_controller/metal/helpers.rb が引っかかりました。
しかしこいつは module でこれが呼ばれるのを探すのは面倒。
また pry に戻って探してみることにします。

backtrace を覗く

また User#index の binding.pry に戻ります。
User#index の段階では view は用意されてるのでどこかで既に new されているはず。
なのでまず backtrace を眺めてみます。
pry-byebug を入れて pry-backtrace と打つと backtrace が見られます。
さて覗いてみると

多い。111もあります。
折角 byebug で repl も動くことなので up していきます。
途中で String を freeze しているころを見付けます。
噂の物体を副産物で見付けた気分ですね。
どんどん up していきます。
actionview/lib/action_view/renderer/template_renderer.rb の render_with_layout くらいから view という変数がちらほら出てきますね。あやしい。
view は context という名前になったりしながら連れ回されているようです。
actionview/lib/action_view/rendering.rb の _render_template までそれっぽいのがありますが、actionpack/lib/action_controller/metal/streaming.rb の _render_template あたりから出てこなくなります。
この辺りに真相が潜んでいそうです。
というか metal は見覚えがありますね。 ActionView::Base.new しているのも metal の helper でした。


結局どこでインスタンス変数を渡しているのか

最後に view っぽいのを扱っているのは actionview/lib/action_view/rendering.rb の _render_template でした。
context に view_context を代入しています。
view_context の定義に view_assigns という怪しさ満点のものを発見。
しかし rendering.rb に view_assigns は無い。
名前が分かればとりあえず git grep します。
actionpack/lib/abstract_controller/rendering.rb に定義を発見しました。
instance_variables を取ってきてhash にしています。
slice しているのは @ を取りのぞくためですね。
ここでおもしろいのは protected_vars を reject していること。
@_formats などは controller から渡さないようにしているようです。
view_assigns は AbstractController::Rendering に定義されています。
ということは ApplicationController が AbstractController::Rendering を継承していれば、ユーザ定義のコントローラは view_assigns を経由でインスタンス変数を取得して view を生成していそうですね。
となれば気になるのは UsersController の ancestors です。
と AbstractController::Rendering がばっちりありますね。56行目です。
という訳で「何故 View は Controller のインスタンス変数へとアクセスできるのか」を発見できました。
それは「Controller が View を Rendering する時に自分のインスタンス変数を渡してやっているから」です。


view_assigns を上書きするとどうなるか

実装が分かれば遊びたくなるのが常。
自分はきちんとコードが読めたのかも含めてちょっと遊んでみます。
内容は @users という変数を無理矢理作ってやるというもの。
この状態で user#index にアクセスすると DB の中身に関係無く、定義された内容が表示されました。
やはり view に渡すためのインスタンス変数を取得するメソッドは AbstractController::Rendering#view_assign で間違いない様子。
ということで無事 Rails の中からお目当てのものを見付けられました。
めでたしめでたし。


おわり

というおはなしなのでした。
読むのに pry-byebug が便利でした。
むしろ無かったら見付けられなかったかもしれない。
結論としては Controller で view_assigns を上書きすると大変なことになりそう、って感じです。


みなさま良い年末をー。

2015/10/19

FreeRADIUS を使って DHCP サーバを立てて Rails から管理してみる

デバイスごとに静的なIPを振ってそれをDHCPで配ろう、ということに。
デバイスは多々ありそうなので静的IPの登録を Rails 経由でやってみる。
FreeRADIUS を使うと DHCP の静的IPの設定を DB 側に保存できるらしいので使ってみた。

ということでサンプル的なアプリケーションを書いてみた。(https://github.com/atton-/freeradius_dhcp_with_rails)
IP の最後のオクテットと、デバイスの MacAddress を入力すると、DHCP サーバから IP を取得できます。


環境

  • OSX Yosemite 10.10.5
  • Ruby 2.2.3
  • Rails 4.2.4
  • Docker 1.8.1
  • FreeRADIUS 3.0.4
  • PostgreSQL 9.4.5


使いかた

まず Docker で PostgreSQL と FreeRADIUS 用のコンテナを起動。
FreeRADIUS 用のコンテナに直接入って、起動しているコンテナの情報に合わせて /etc/raddb/sites-enabled/dhcp.relay を書き換えてから radiusd を起動。
Rails 側でクライアントとなるデバイスの Mac Address と IP を登録。
そうすると DHCP で特定の Mac Address 宛に Static な IP が振られます。


解説とか

まず DHCP の設定は このページを参考に FreeRADIUS の設定。
基本的な設定はデフォルトのままでもOKです。
ちょっと問題なのが mods-config/sql/main/postgresql/queries.conf の設定。
元々のサンプルは /etc/raddb/mods-config/sql/main/postgresql/setup.sql や /etc/raddb/mods-config/sql/main/postgresql/schema.sql を利用しているのですが、これで使っている attribute という column name が ActiveRecord 的にアウト。
なので適当に名前を変えます。

今回使うテーブルは radcheck と radreply 。こいつの構造を保ったまま名前を変えます。
元の schema は username, attribute, value, op の 4 column + id。
username が mac address に相当して, value, op, attribute は config の key, op, value に対応してるっぽいです。
例えば attribute = 'DHCP-Your-Address', op = '=', value = '192.168.100.1' とか。
元は DHCP-Your-Address = 192.168.100.1 みたいに書いてたんだろうなー、とか想像したり。なのでいろんな情報とか突っこめそう。
それはさておき column の name を以下に変更。 op と value は変更無し。
  • username => mac_address
  • attribute => radius_attribute
対応するように queries.conf の query を書き換えます。
あとは Rails 側で radcheck 相当のモデル radius_check_information を作って、  mods-available/sql で参照している radcheck を radius_check_informations に変更。
同じように radreply もやってしまいます。
これで DHCP の Static IP の情報を DB に突っこむのはできました。

あとは rails 側でよしなにフォーマットに合うようにレコードを追加してあげたらOK。


引っかかったところ

Docker 側で dhcp とかを扱おうとすると arp table とかをいじろうとしてコケることが。
その場合は docker run する時に --privileged してやると良いみたいです。
あと Docker だと FreeRADIUS の実行ユーザとグループを root にしておかないと dhcp の返事ができないっぽいです。


具体的なコマンドとか

Docker で起動時の IP を使って FreeRADIUS の設定とかをしなかったので、ある程度手動で設定する必要があります。
なので具体的なコマンドとかを載せておこうと思います。

  • $ git clone https://github.com/atton-/freeradius_dhcp_with_rails
  • $ cd freeradius_dhcp_with_rails
  • $ docker build -t pg94 docker/postgres
  • $ docker run -itd --name postgres-server -p 5432:5432 -e POSTGRES_PASSWORD=hogehoge pg94
  • $ docker build -t freeradius_with_rails/freeradius-dhcp docker/freeradius-dhcp
  • $ docker run --privileged --link postgres-server:postgres-server --name dhcp-server -it freeradius_with_rails/freeradius-dhcp
    • これで bash が起動します。
    • ip a してその IP Address を確認。 (ex: 172.17.0.50)
    •  ip r してクライアントの Router を確認 (ex: 172.17.42.1)
      • 今回はクライアントも Docker で上げてみます。
      • なので Router は Docker Private Network の gateway です
    • /etc/raddb/sites-enabled/dhcp.relay を編集
      • DHCP-Domain-Name-Server に DNS を設定します (ex: 8.8.8.8)
      • DHCP-DHCP-Server-Identifier に DHCP サーバの IP を設定します (ex: 172.17.0.50)
      • DHCP-Router-Address に DHCP クライアントのゲートウェイのIP を設定します (ex: 172.17.42.1)
      • DHCP-Subnet-Mask に DHCP クライアントのネットワークのサブネットマスクを設定します (ex: 255.255.0.0)
    • radiusd -X
      • で FreeRADIUS を起動します
  • $ docker run --privileged -it centos bash
    • DHCP クライアント相当の Docker container です
    • $ yum install -y dhclient
    • $ ip addr
      • とかして mac address を確認します (ex: 02:42:ac:11:00:37)
  • $ rake db:create db:migrate
  • $ rails server
    • mac address と IP の値を入れます。 (ex: 02:42:ac:11:00:37, 100)
      • 今回は 172.17.0.0/16 のIPを振ります。
  • $ dhclient -d
    • docker run --privileged -it centos bash のコンテナで実行します
    • 172.17.0.100 が振られます。やったね。

まとめ

これでデバイスごとに静的IPを触れるDHCPサーバを Rails で管理できました。
前の記事にも書いた KVM を Ruby から触る機能と登録したIPに対応するDNSとかと連携すると、IPを申請したら静的にIPが振られてドメインが登録されたり、VMを template から clone してそのIPとドメインが自動申請されるようなシステムが作れます。やったね。


参考

2015/10/14

fog-libvirt を使って Ruby から KVM を扱う

Ruby から KVM を扱ってみよう、ということで fog-libvirt を使ってみた話。

もともと fog という gem があって、OpenStack とか AWS とかを Ruby で扱うような gem みたいです。
それの libvirt 版が fog-libvirt 。
他にも ruby-libvirt とかあるのですが fog の方が扱いやすかったし、結局内部では ruby-libvirt を呼んでいるようです。


libvirt への remote access の設定

まずは KVM host 側の設定をします。
virsh の remote access の機能を使って fog-libvirt はアクセスするようなので、remote access を許可するように設定してやらないといけません。
具体的な設定方法は参考URLを参照してください。

remote access が何かと言えば、 virsh でやる時に
  • $ virsh -c 'qemu+tcp://hoge.exmaple.com/system'
とか書くやつです。

それが fog-libvirt を使うと、sasl での認証だと

  • Fog::Compute.new(provider: 'libvirt', libvirt_uri: 'qemu+tcp://user@hoge.example.com/system', libvirt_username: 'user',libvirt_password: 'password')
みたいに書きます。
public key を使った ssh だと

  • Fog::Compute.new(provider: 'libvirt',libvirt_uri: 'qemu+ssh://user@hoge.example.com/system?keyfile=/path/to/key')
みたいになります。public key の path を URI に書くような形式っぽいです。



fog-libvirt を使ったKVM操作

接続できればあとはいろいろできます。

  • connection = Fog::Compute.new(provider: 'libvirt',libvirt_uri: 'qemu+ssh://user@hoge.example.com/system?keyfile=/path/to/key')
    • KVM への接続
  • server = connection.servers.find{|s| s.name == 'hogehoge'}
    • domain name 'hogehoge' で define された VM を探してくる
  • server.active? #=> true or false
    • 電源が入ってるかチェック
  • server.start
    • VM 起動
  • server.halt
    • VM 停止
  • connection.volumes
    • イメージファイルの一覧表示とか。
    • define された domain に引っついてるディスクの一覧が手に入る。
  • volume = connection.volumes.find{|v| v.name = 'fugafuga.qcow2'}
    • 'fugafuga.qcow2' を探してくる
  • volume.clone_volume('piyopiyo.qcow2')
    • volume の clone もできます。


テンプレートVM の clone

特定の VM をテンプレートとして、そのテンプレートを clone する機能が欲しいな、ということになりました。
Fog::Libvirt::Compute::Server#create には volume_template_name を指定するオプションがあるのですが、どうも思ってたように動かない。
確認すると volume の clone には成功しているようですが、clone した後の VM の volume が template と同じ。
どうやら Fog::Libvirt::Compute::Volume#clone_volume は clone すると template の方を return する様子。
これじゃ新しいVMの volume が clone対象の方だ、ということで clone し終わった方の volume を返すように変更。
加えて、テンプレートと clone した後の volume の storage pool を分けたかったので  Fog::Libvirt::Compute::Volume#clone_volume に storage pool の指定もできるようにしてみました。
ドキュメントがほとんど無かったので割とソース読んで手探り。
gem はだいたいソースが見られるので良いですね。

例えばこんな感じで使います。

  • cloned_vol = template_vol.clone_volume('new-vm-volume-name', 'new-pool')
  • kvm.servers.create(name: 'new-vm-nameloned_vm_name' , volumes: [cloned_vol])


ストレージプールの話

説明が後になっているのでよろしく無いのですが、KVM にはストレージプールというものがあるらしいです。
特定のディスクやディレクトリを virsh 経由で操作できるようにするものみたいですね。
こいつを設定しておくと特定のディレクトリの下に clone したイメージを fog-libvirt 経由で自由に作ったり消したりできるようになります。

例えば pool を作るには

  • virsh pool-define-as vm_pool dir - - - - /path/to/dir
  • virsh pool-build vm_pool
  • virsh pool-start vm_pool
  • virsh pool-autostart vm_pool
とかします。

そうすると
  • virsh vol-list --pool vm_pool
    • volume の一覧
  • virsh vol-clone --pool vm_pool tempalte.qcow2 new.qcow2
    • volume の clone
  • virsh vol-delete --pool vm_pool garbage.qcow2
    • volume の削除
とかできるようになります。もちろん fog-libvirt 側でもOK。
あと、ストレージプールにしたディレクトリの下にディレクトリがあると、 fog-libvirt 側の list が上手く動かなかったりする様子。ディレクトリ作らない方が良さそうですね。

そんなこんなで fog-libvirt を使って KVM をいじってみたよ、というお話でした。


使った環境情報

実行OS : OSX Yosemite 10.10.5, CentOS 7.1
Ruby : 2.2.3
KVM Host : CentOS 7.1 + libvirt 1.2.8,  Fedora 21 + libvirt


参考URL


2015/10/10

Rails と bind-sdb を連携してみる

DB と連携した DNS を立てることになったので、そのDBの操作を Rails でやってしまおう、というお話。

環境

  • OS : Mac OSX 10.10.5
  • Docker : 1.8.1 (docker-machine)
  • Ruby : 2.2.3
  • Rails : 4.2.4
  • PostgreSQL : 9.4
  • Bind-SDB : 9.9.4

Bind-SDB について

通常の bind だと zone ファイルとかを更新したりする必要があるのですが、 sdb インターフェースというものを使うと zone ファイルではなくてDBのレコードを使って名前解決とかできるらしいです。
具体的には CentOS とかだと bind-sdb(bind-sdb-chroot) パッケージ。
バックエンドはデフォルトでは PostgreSQL なので PostgreSQL で。 一応 MySQL のアダプタもあります。
レコードに要求されるカラムは以下の4つ。
  • name : DNS を引く時の key。 A record ならFQDNとか。
  • rdtype : レコードの種類。 A とか AAAA とか SOA とか。
  • rdata : DNS を引く時の value。 A record ならIPとか。
  • ttl : レコードのTTL
この4つがきちんと引けていれば動くようなので、この構造を持ったモデルを作ってしまえば Rails で管理できそうだな、ということで実際に作ってみました。

サンプルアプリケーション

サンプルとしてちょこっと書いてみました。(https://github.com/atton-/bind_sdb_with_rails)
*.hoge.com を解決できるようなDNSを作れます。
一応逆引きもできるようにしてるのでアドレス帯域は 10.100.200.0/24 くらい。
rails server して、 domain を入れて IP の最後のオクテットを数値で入れると名前が解決できます。
例えば、 rake db:seed では aaa.hoge.com というレコードを入れるので
$ dig aaa.hoge.com @<your-dns-ip>
とかすると 10.100.200.1 と返します。
実行コマンドは README.md に書いてますが、コンテナのIPが環境によって違うのでちょっと書き換えが必要です。
あと DOCKER_HOST が設定されていること前提。OSX で docker-machine を使っていれば設定されているはずです。

やってること

やっていることはドメインの文字列とIPの組から A レコードと PTR レコードを作ってるだけです。
ドメインとIPが入る record モデルを作って
A レコードが入る forward_record モデルと PTR レコードが入る reverse_record モデルを作って has_one で持たせる。
record モデルの after_save くらいで forward_record と reverse_record を生成してやります。
あとは bind-sdb 側がやってくれます。

bind-sdb 側の設定として、 /var/named/chroot_sdb/etc/named.conf にDBサーバのIPを指定する必要があります。
どうやら名前で書くのはダメなようで、IPで直接指定します。
今回は docker container で DB を持っているので、 docker の host か DB の container の IP を書いてから build するようにしてます。ちょっと面倒。

bind-sdb は特定の zone を特定のテーブルから引く、という処理をするようなので、正引き用の zone は forward_records テーブルを、逆引き用のzoneは reverse_records テーブルを見るようにします。
あとは forward_records/reverse_records に SOA/NS を突っこんでやれば動きます。
ただ、ゾーンを転送する際はテーブルの中に入っているもの全てを転送してしまうようなので、複数のゾーンを1つのテーブルに入れてしまわないように注意。一応転送しないのなら動きはしますが……
ちなみに select するだけのようなので、 read only の view とかを切って複数のゾーンに対応することもできます。そうすれば Rails 側の association が多くなることも無いです。
もちろん AAAA レコードも追加できるので IPv6 も対応可能。今回はゾーンを書くのが面倒だったので v4 のみで。
あとは転送とかするのなら SOA のシリアルの管理もきちんと。 record モデルの after_update くらいに SOA のシリアルを上げる処理を書いてやる形とか。

ということで、 Rails でドメインとIPを申請すると名前が解決できるようになりました。

実行コマンド例

一応置換してね、みたいな記述をしているつもりなのですけれど、具体的な値で実行してみた時の例を載せておきます。
  • $ git clone https://github.com/atton-/bind_sdb_with_rails
  • $ cd bind_sdb_with_rails
  • $  echo $DOCKER_HOST
    • tcp://192.168.99.100:2376
    • docker のホストのIP は 192.168.99.100 のようなのでこれを使います。
  • $ docker build -t pg94 docker/postgres
  • $ docker run -itd --name postgres-server -p 5432:5432 -e POSTGRES_PASSWORD=hogehoge pg94
  • $ rake db:create db:migrate db:seed
    • ここで aaa.hoge.com, 10.100.200.1 が追加される。
  • $ rails server
    • http://localhost:3000 でレコードの追加とかができます。
  • $ sed -i -e 's/postgres-server/192.168.99.100/'
  • $ docker build -t bind_sdb_with_rails/bind-sdb docker/bind-sdb
  • $ docker run -itd --name name-server -p 53:53 -p 53:53/udp bind_sdb_with_rails/bind-sdb
    • この段階でDNSが立ち上がります
  • $ dig @192.168.99.100 aaa.hoge.com
    • 正引きチェック
  • $ dig @192.168.99.100 -x 10.100.200.1
    • 逆引きチェック

参考

2015/10/09

ActiveLdap を使っている時に PG がエラーを吐いて RSpec が落ちる

RSpec を使って Rails の並列リクエスト込みのテストを書いていると、

 PG::ConnectionBad: PQsocket() can't get socket descriptor
とか言われてテストがコケる。
テストだけじゃなくて Webrick でもエラーを吐く時は吐く。
実行する環境が Mac だとどうやっても通って Linux だとどうやっても通らない。

結論から言うと、 ActiveLDAP にタイムアウトを付けていると ActiveLDAP が fork するのでなんやかんや PG のソケットが足りなくなるから PG 側がエラーを吐いて死ぬっぽい。
fork が Process.fork してるのでたぶん OS 依存なバグになってややこしかった形。
対症療法としては activeldap の config の  timeout を 0 にして無効化すれば発生しなくなる。

環境

具体的に試した環境としては

OKな環境(Mac)

  • Rails : 4.2.4
  • Ruby : 2.2.2, 2.1.5, 2.0.0-p645
  • DB
    • PostgreSQL :  9.2.13, 9.3, 9.4 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
    • MySQL : 5.7 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • OpenLDAP : 2.4.39 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • ActiveLDAP :  4.0.4
  • pg :  0.18.2, 0.18.1, 0.18.0, 0.17.1, 0.17.0
  • RSpec 実行OS Mac OSX Yosemite
OK な環境(Linux)
  • Rails 4.2.4
  • Ruby : 2.2.2
  • MySQL : 5.7 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • OpenLDAP : 2.4.39 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • ActiveLDAP :  4.0.4
  • pg :  0.18.2, 0.18.1, 0.18.0, 0.17.1, 0.17.0
  • RSpec 実行OS : CentOS 7, Fedora 20
NG な環境(Linux)
  • Rails 4.2.4
  • Ruby : 2.2.2, 2.1.5, 2.0.0-p645
  • PostgreSQL : 9.2.13, 9.3, 9.4 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • OpenLDAP : 2.4.39 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • ActiveLDAP :  4.0.4
  • pg : 0.18.2, 0.18.1, 0.18.0, 0.17.1, 0.17.0
  • RSpec 実行OS : CentOS 7, CentOS 7 and Fedora 20 on VirtualBox, boot2docker 1.8.0
まとめると
  • Mac なら DB を PostgreSQL にしても MySQL にしても何でもOK
  • Linux だと DB を PostgreSQL にするとたまに再現
  • Linux だと DB を MySQL にすると再現せず
  • postgresql-server や postgresql-devel のバージョンを変えても問題は解決せず

原因調査の流れ

まずは gem のバージョンを変える。なるべく最新へ。解決せず。
gem のバージョンを過去のバージョンにもする。解決せず。
次に PostgreSQL も最新にする。解決せず。
クライアント側の postgresql-devel も最新に。 解決せず。
とりあえず pg を読む。
 PG::ConnectionBad: PQsocket() can't get socket descriptor
は 'pg' の gem の native extension で、PostgreSQL への socket が取れない時に投げられるらしい。
socket が取れないとのことなので pool を増やしたりもしたけれど解決せず。
MySQL にしてみる。 Linux でも動く。どうやら問題はDBだとアタリを付ける(結果的にははずれ)
動かす Linux の kernel を上げたりする。(CentOS でも Fedora でも動かず。 2.x でも 3.x でもダメ)
Linux を物理マシンに乗っけるも動かず。
試しに rails new したやつで並列にリクエストをしても捌く。どうやら原因はデフォルトの gem では無いらしい。
そうこうしてると Linux 側でコアダンプ吐いて Ruby が死ぬ。 stack trace を見ると ActiveLDAP の様子。
ということで覗いて見ると Process.fork とかしてるらしい。
fork した先で PG の connection を使いまくって PG が正しいソケット取れないのかな、と推測してとりあえず timeout を切る。
これで解決。再現せず。長かった……

とりあえず怪しいのは ActiveLDAP の fork 方法か Ruby の Linux 側の fork 。原因を追求したい気もするけれど今はちょっと放置。

2015/09/16

ActiveLDAP で DN が被ってないのに Distinguishedname is duplicated と言われる時

タイトルの通りに ActiveLDAP で DN が被ってないのに Distinguishedname is duplicated と言われる時にチェックするポイントとか。

DN を構成する時に使ってる attribute の末尾にスペースとかが入ってないかチェック。

どうやらスペースが入ってると validation で引っかかってしまう様子。
推測ですが、attributeが違うと DN が違う判定になって update ではなく create 扱いになって、create 時のDN重複チェックはスペースを無視しちゃうので重複扱いになる、といった感じ?(参考)。

見つけた方法としては、 ldapserarch で各種 attributes を確認。
DN自体はOKっぽかったのですが DN を構成する attiribute の value が英字 only のはずなのに :: が付いてたので怪しいなー、と思って確認してみると投入するデータの attribute の末尾にスペースが入ってました、というオチでした。
ちなみにスペースを消すと直りました。めでたしめでたし。

2015/08/01

Capybara で同時に複数のリクエストを受け取った時のテストをする

Capybara を使って Rails Application のテストを書いていた時に同時にリクエストを受け取った時のテストを書きたくなったのでそのログ。

環境


  • OSX Yosemite 10.10.4
  • rbenv 0.4.0
  • ruby-build 20150719
  • Ruby 2.2.2
  • Rails 4.2.2
  • Rspec 3.3.1
  • Capybara 2.4.4
  • Postgresql 9.2.13


状況と解説と解決策


想定としては

  • ユーザが resource を new する
  • resource に対して Rails 側が unique な値を追加する
  • unique な値は DB 内では重複してはいけない
といった感じです。

とりあえずアバウトなサンプルとして
  • 本の名前と価格をユーザが入れる
  • ISBNをRails側が順番に振っていく
  • もし本の登録が無くなったらそのISBNは再利用
みたいなサンプルを書いてみました。


まず、リクエストを並列に投げるには spec 内で fork しちゃうと良いみたいです。
validates_uniqueness_of をすり抜ける程度には近いタイミングで実行されるようなので、同時に受け取ったテストと考えて良いかな、と思っています。
実際、 SQLite3 + validates_uniqueness_of のみを書いてある commit では、2つのリクエストを同時に投げると unique に振らないといけない ISBN に対して同じ値でレコードが2つ追加されてしまいます。


DB内で重複を排除するには unique な index を貼ることで回避してます。
そうすることで SQLite3 でも重複された値は格納されないようになるのですが
  • /Users/atton/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/sqlite3-1.3.10/lib/sqlite3/statement.rb:108:in `step':
  •  SQLite3::BusyException: database is locked: INSERT INTO "books" ("name", "price", "created_at", "updated_at") VALUES (?, ?, ?, ?)
  •  (ActiveRecord::StatementInvalid)
と言われてしまってテストがコケちゃいます。
ActiveRecord::StatementInvalid を rescue で拾ってから再格納しようとしても BusyException が出るのでうまくいかず。


ということで DB を PostgreSQL に変えてみます。
そうすると、 validates_uniqueness_of をすり抜けて重複した値が入ってきた時には ActiveRecord::RecordNotUnique が返ってきます。
こいつを rescue してきちんとした値にしてやればどうにか目的は達成できました。
ちなみにちゃんと rescue するロジックを書いた場合だとテストで同時に80リクエストくらい捌けました。fork しまくったせいでメモリを数GBとか使うので80くらいが限界。


コマンドとか

Github に上げたサンプルをとりあえず動かすには
  • git clone https://github.com/atton-/sample_of_rspec_to_concurrent_requests_using_capybara.git
  • cd sample_of_rspec_to_concurrent_requests_using_capybara
  • git checkout sqlite3_and_validates_uniqueness_of
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • sqlite3 と validates_uniqueness_of のみ。
    • unique であって欲しい column に同じ値が入ってしまう
  • git checkout use_postgresql
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • PostgreSQL + Unique Index
    • unique であって欲しい column に同じ値は入らない
    • 同じ値を入れようとすると ActiveRecord::RecordNotUnique
  • git checkout handle_record_not_unique
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • PostgreSQL + Unique Index + ActiveRecord::RecordNotUnique を rescue
    • ActiveRecord::RecordNotUnique をきちんと処理してやれば unique にしてあげられる
とかです。
PostgreSQL の設定は環境変数に設定するか database.yml を変更してください。


まとめ

  • 特定の column の値を Unique にしたかったら unique index を貼っておくと validates_uniqueness_of をすり抜けられてもDBに入るのは止められる
  • SQLite3 だと Unique index がある table に同時に insert とかすると BusyException とか出ちゃう
  • PostgreSQL だとほぼ同時なリクエストでも捌けた
  • PostgreSQL だと返ってくる exception は ActiveRecord::RecordNotUnique に変わる


気になるところ

  • Unique Index を貼る方法で Uniqueness を確保する方法って一般的なのかな
  • 特定の column の値が [1,2,3,4, 6,7,8,9] みたいな状態で 5 を取ってくる SQL ってあったっけ(今は 1 から順に ActiveRecord#exists? で回してるので O(n) なんだよなー)
  • 並列にリクエストを投げるのを SQLite3 でできると楽なんだけれど何か方法無いかな