ChefでRaspberry Piをセットアップする

仕事で複数台のRaspberry Piをセットアップすることになったので、Chefを使ってセットアップを自動化することにした。Chef、Vagrant、Serverspecなどいろいろな周辺ツールの全体像を整理したり、それらを使ったワークフローを体験できてよかったので、ブログとして残しておく。

また、セットアップに使ったChefのレポジトリはgithubにホストしてあるので参考にどうぞ。

https://github.com/naoty/chef-repo

今回、Chefで自動化したのは以下の通り。

  • apt-getの更新
  • gitのインストール
  • rbenvを使ってRuby 2.0.0-p247をインストール
  • nodebrewを使って最新安定版のnode.jsをインストール
  • Wiringpi(GPIOを簡単に操作するためのライブラリ)のインストール
  • mjpg-streamer(Webカメラを使ったストリーミングのためのライブラリ)のインストール

1. Vagrantで仮想環境を用意する

いきなりRaspberry PiにChefを使って環境構築を行うのは失敗したときにやり直すのが大変。なので、Raspberry Piに近い仮想環境を用意して、そこでChefを使ったセットアップを試行錯誤したい。そういうときに便利なのがVagrant。Vagrantを使えば簡単に仮想環境を作ったり壊したりできるので、失敗してもすぐにやり直せる。

今回、重要だったのがRaspberry Piに近い仮想環境を用意することだった。Vagrantにはboxという仕組みがあって、CentOSとかUbuntuとかいろんなOS、CPUに合わせたひな形がたくさん用意されている。通常はここにあるboxを使えばいいんだろうけど、Raspberry Piに近いboxがなかった。Raspberry Piに近いboxを探したところ、これがよさそうだったので使うことにした。

$ git clone https://github.com/nickhutchinson/raspberry-devbox raspberry_pi
$ cd rasbperry_pi
$ vagrant up

以上、これだけでRaspberry Piに近い仮想環境を用意することができた。

2. Chefのセットアップ

ここはいろんなところで解説されてる通りに行っただけ。

$ vagrant ssh-config --host vm-raspberry_pi >> ~/.ssh/config
$ knife solo init chef-repo
$ knife solo prepare vm-raspberry_pi

3. クックブックの作成とテスト

ここから環境構築の手順をコードとして記述していく。クックブックの書き方については「入門ChefSolo」やOpscodeの公式ドキュメントを参考にした。このときの注意点としては、Raspberry PiはRubyやnode.jsのインストールに非常に時間がかかるため、timeoutをとても長くする必要がある。数時間はかかると考えた方がいい。

書いたクックブックを実行する前にVagrantをサンドボックスモードにしておく。こうすると、失敗したときに実行した部分だけやり直すこと(ロールバック)ができる。サンドボックスモードにするためにはsaharaというVagrantのプラグインが必要なのでインストールしておく。

サンドボックスモードをオンにしてクックブックを実行したあと、本当に期待した通りに環境構築できたかどうかをServerspecを使ってテストする。Serverspecにはいくつかテストを実行する方法があるようだけど、今回はSSHでログインしてテストを実行する形式を採った。テストを通らなかった場合は、saharaを使ってロールバックしてやり直す。テストが通った場合は、saharaを使って変更を確定させる(コミット)。

これをサイクルさせながら、どんどんクックブックを追加していく。以上をコマンドで表すとこんな感じ。

$ knife cookbook create ruby -o site-cookbooks
$ vi site-cookbooks/ruby/recipes/default.rb
$ vi nodes/vm-raspberry_pi.json
$ vi spec/vm-raspberry_pi/ruby_spec.rb
$ vagrant sandbox on
$ knife solo cook vm-raspberry_pi
$ rspec
$ vagrant sandbox commit

4. Raspberry PiをChefで環境構築する

仮想環境での環境構築が完了したら、いよいよ本物のRaspberry Piにクックブックを適用する。そのためにはnodes以下に本物用の設定を追加するだけでいい。

$ vi nodes/raspberry_pi.json
$ knife solo cook raspberry_pi

RubyのWebSocketサーバー「pingpong」を作った

最近、「Working with TCP Sockets」って本を読んだ。Rubyでソケットと戯れつつ、7つくらいのWebサーバーのアーキテクチャを概観できるいい本だった。で、その中にイベント駆動モデルの実装とかノンブロッキングIOの実装について紹介されてて面白かったので、練習がてらWebSocketサーバーを作ることにした。

PingPong

f:id:naoty_k:20131011013521g:plain

https://github.com/naoty/pingpong

卓球ハウスっぽい名前にした。数日で作ったので、他のクライアントへpush通知を行うことしかできない。たぶん大きいデータも送れない気がする。

WebSocketサーバーの実装とは

まずはRFC 6455のサーバーに関する部分を読んだ。最低限必要な部分をRubyで実装していった。例えば、以下のコードはHandshake(websocket接続の確立)の際にサーバーがクライアントに返すレスポンスヘッダーを作っている。

def response_headers
  [
    ["Upgrade", "websocket"],
    ["Connection", "Upgrade"],
    ["Sec-WebSocket-Accept", signature]
  ]
end

def signature
  value = @header["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  hash = Digest::SHA1.digest(value)
  Base64.strict_encode64(hash)
end

ご覧のとおりハードコーディングがたくさん出てくる。RFCを読むと、このヘッダーにはこの値を入れなさいって書いてあることが多い。なので、それぞれの値の意味はわからないけどとりあえずRFCに従ってハードコーディングしている。signatureというメソッドはあるヘッダーの値をRFCで以下のように定められた形式で生成している。(余談だけど、ここでBase64.encode64を使って小1時間ハマった。これは改行コードを入れるためここでは使えない。)

A |Sec-WebSocket-Accept| header field. The value of this header field is constructed by concatenating /key/, defined above in step 4 in Section 4.2.2, with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this concatenated value to obtain a 20-byte value and base64-encoding (see Section 4 of [RFC4648]) this 20-byte hash.

イベント駆動モデルとノンブロッキングIO

push通知はイベント駆動モデルというアーキテクチャを使って実装した。イベント駆動モデルはマルチプロセスやマルチスレッドとは違ってシングルスレッドで多数のリクエストを並行処理する。具体的には、websocket接続の確立に成功したソケットを配列に入れておき、ループ内でそれらのソケットにread/writeしていく。このとき、read/writeがブロッキングしてしまうとすべての処理がそこで止まってしまうので、read/writeの前にselect(2)等を使ってread/write可能なソケットだけ選択してread/writeを行う。これがノンブロッキングIOだと思う(だよね…?)。

実際のコードは以下の通り。

def start
  @sockets = {}
  @message_queue = []

  loop do
    to_read = @sockets.values << @server
    to_write = @sockets.values
    readables, writables, _ = IO.select(to_read, to_write)

    readables.each do |socket|
      if socket == @server
        establish_connection
      else
        begin
          request = socket.read_nonblock(CHUNK_SIZE)
          message = Frame::Request.new(request).message
          # the message may be passed to a web application.
          @message_queue << Message.new(socket.fileno, message)
        rescue EOFError
          @sockets.delete(socket.fileno)
        end
      end
    end

    message = @message_queue.shift
    next if message.nil? || message.empty?

    writables.each do |socket|
      if socket.fileno != message.from
        data = Frame::Response.new(message.body).data
        socket.write_nonblock(data)
      end
    end
  end
end

感想

WebSocket、イベント駆動モデル、ノンブロッキングIO…という言葉はよく耳にしてきたけど理解したとは言えなかった。実際にWebSocketサーバーを書いてみると、コードに基づいて何が行われているのか正確に理解することができた。push通知も何やら凄そうな響きがするけど、実際に実装してみると特に難しいことはしていなかった。また、websocketの弱点と言われている、CPUヘビーな処理がなぜ弱点なのかも合点がいった。シングルスレッドで処理しているので、例えばレンダリングのような重い処理がひとつでも走ると、全体に悪影響が出るということだと理解した。

iOS用グラフ描画ライブラリを書き始めた

卓球ハウスに来てから楽しすぎてコード書いてないことに気づいたので、なんか書こうと思った。最近はセンサーのデータをどうにかiOSに転送したくていろいろ試しているのだけど、送られたデータを表示するときに何かとグラフを描画したくなる。

iOSでグラフを描画したいとき、まずCorePlotを試してみる。だけど、ドキュメントがあんまりないし、見た目がダサい。githubじゃないのもなんかなぁと思ってすぐにやめてしまう。で、CorePlotはやめてwebviewでJSのグラフ描画ライブラリを使うことにする。highchartsが便利なのでよく使う。でも、やっぱりいちいちHTMLとJSのファイルを用意しなくちゃいけないのがダルいとは思っていた。

そこでせっかくだしグラフ描画ライブラリをObj-Cで実装してみることにした。

naoty/NTChartView

f:id:naoty_k:20130806230903p:plain

まだぜんぜんできてない。かろうじて上のような折れ線グラフを表示できるレベル。負値すらうまく表示できない。もちろんテストもない。地味に頭を使う実装が多い気がする。

将来的には折れ線グラフだけじゃなくて円グラフと棒グラフも実装したい。けっこう時間かかりそうなので、ヒマなときにゆっくり実装したい。

半年経ちました・引っ越しました

2013年もあっという間に半年が経ちました。上半期の半分はiOSアプリやAndroidアプリを書いたり、プライベートでgemを書いたりしてました。上半期のもう半分は新しい会社でハードウェアの企画・開発に携わりました。この期間は自分のなかで失敗の連続で落ち込む日が多かったように思います。そしてけっこう太りました。技術面のみならずいろんなところで自分の至らなさを突きつけられました。プライベートでの時間の使い方が散漫だったのが最大の反省点です。もっとハードウェアという未知の領域の理解に時間をかけるべきでした。まだまだwebへの未練が捨てられず、ついついハードウェアよりweb的なものやアプリを作ったりvimいじりをしてしまうのがよくなかったです。ただ、昨年の大晦日にたてた目標、「ハードウェアを開発すること」は少しずつ実現していっているように思います。

そして、おととい引っ越しました。シェアハウスです。数年前に一度トライして失敗に終わりましたが、念願かなって実現できました。自分史上最高にいい家です。足が伸ばせるお風呂は初めてでした。住民は同年代のプログラマーで、僕よりはるかに優秀な人たちです。あと、彼らと親交のあるインターネッツ勢が来客されるので、僕は末席でじーっとしてます。

半年経った反省を基に、新たな環境で、なんとか2013年の目標を達成できるよう努力したいです。あと、かなりの出費で瀕死の状態がしばらく続きそうなので、アプリで一発あてたいです。よろしくお願いいたします。

konashi make-a-thonに参加してきた

6/1と昨日6/22の2日間、konashi make-a-thonに参加してきた。konashiを使ってiOSと連携するハードウェアをmakeするイベントだった。数人のチームに分かれてアイデアのブレーンストーミングから実際の開発まで行った。

一日目はワークショップ形式でたくさんのアイデアを出しまくった。その中から実装するアイデアを決めた。二日目までの3週間、僕はiOSアプリ担当ということで90%くらい完成させた。で、二日目にはハードウェアと筐体とkonashiを結合して、アプリから操作するところまでを確認し、微調整をした。で、できたのがこれ。

f:id:naoty_k:20130622160819j:plain

なにこの完成度ww

このコースターはグラスのビールが空になるとiOSにバイブで通知する。コースターには感圧センサーが入っていて、重さをkonashiを使ってbluetoothでiOSアプリに送信する。で、アプリ側で重さが一定値を超えるとバイブするようになっている。上司のグラスが空になったらアプリが教えてくれるので、飲み会の席でうまく立ち回れる。アプリのソースコードはGithubで公開しているので、konashiのサンプルコードとして参考にしてもらえるとうれしい。

Github - naoty/Konastar

感想としては、自分では思いもよらない面白いアイデアがどんどん出てきて面白かった。もっとこういう話をいろんな人としてみたいと思った。makeのための環境や技術はどんどん進歩していく一方で、相対的にmakeするもののアイデアが不足しているように感じていたところ、こういう機会に参加できたことはいい刺激になった。

あと、不特定多数が利用するデバイスを利用者それぞれにパーソナライズして利用できるようにするための手段としてスマホを捉えるのは、すごく興味深い応用範囲のひろい考え方だと思った。スマホには利用履歴や設定が保存されており、それを共用デバイスと通信することで共用デバイスをパーソナライズすることが可能、というのは考えたことなかった。コンビニで買い物するときいちいち「TSUTAYAカード持ってません」って言うの誰の得にもなってない気がする。TSUTAYA_CARD = falseみたいな設定をスマホに保存し、なんらかの形でコンビニと通信することでこの問題を解決したい。

ちょっと話が脱線したけど、どのチームも完成度が高くて面白かった。イベント終了後の懇親会では、言い知れぬ充実感とものづくりの喜びと今後のmakerムーブメントへの期待とでいい気分になった。よかった。

slide_template改めglideの今後について

LTのスライドつくるのだるい - naoty.to_sの続き。

"slide_template"という名前はあまりに味気なかったので"glide"という名前をつけた。"slide"に近い単語で、スムーズにスライドを作成できるイメージから名付けてみた。あと、ギタフリで好きな曲の名前でもある。

naoty/glide · GitHub

slide_template改めglideの今後の方向性と課題について考えてみた。glideが目指している方向性を端的に言うと「slide版のtwitter bootstrap」だと思う。すごく凝ったスライドをつくる人には向いてないけど、時間をかけずにそれなりにいい感じのスライドを作りたい人に向けたプロダクトにしていきたい。プログラマーがプログラミング以外のところで時間を奪われるのは社会的な損失だと思う。一方で、プログラマーによる勉強会は増えてる気がする。いちおう僕もEbisu.rbを月1で開催してたりする。勉強会が増えるとスライドを作成する時間も増える。勉強会自体はいいことだと思うけど、スライド作成に時間が奪われるのはよくないことだと思う。そこで、スライドを簡単に作成するフレームワークが必要だと思った。ここらへんがglideをつくるきっかけとなってる。

で、この方向性に沿って今後やっていくことは、以下のように考えてる。

  1. デフォルトのテーマを豊富にそろえる。今のところほとんどCSSがない状態なので、bootstrapみたいな感じでクラスを指定するだけでかっこいい感じのスライドになるようにしたい。
  2. スライドをPDF形式で出力できるようにする。htmlをアップするサーバーを持たない人はspeakerdeckとか使うと思うので、PDF化が必要。
  3. bowerに依存せずにセットアップできるようにする。デザイナーさんなどに協力をお願いするにあたって一番のネックは環境構築だと思う。今のところJSとCSSのセットアップにbowerを使っているけど、bowerはnodeとnpmが必要なので慣れてないときついかもしれない。なので、bower以外のセットアップ方法も用意する必要がある。

とりあえず3.を片付けてデザイナーさんと協力できる体制を整えて1.に取り掛かりたいと思ってる。2.についてはPDF用のCSSが必要っぽくて未知の領域なので、ここもやはりデザイナーさんに教えてもらいたいところ。

一人でできるレベルを超えてきた感じがしてきたので、まずはこうやってブログを書いてみることが大事だと思った。