この記事はtech.kayac.com Advent Calendar 201320日目の記事です。
どうも、Perlに洗脳されはしましたが、最近洗脳が解けPythonを書いています@mackee_wです。
最近はVOICE DRIVERや暗殺教室 殺ジェクションマッピング、X-TRAIL X-TECH GEAR PROJECTなどをやっているクライアントワークのデバイスチームでせこせこハンダゴテを握っていたりします。
そんな中で最近良く使っているRaspberryPiとAnsibleに関するいくつかのTipsを紹介させていただきます。
RaspberryPiについて
今までデバイスを作成する場合にはArduinoなどのマイコンボードやAVR、PICといったワンチップマイコンを使う場合が多かったのですが、RaspberryPiが強力すぎてArduinoと組み合わせて使う機会が多くなってきました。
何が強力かというと普通のスクリプトが普通に動くというのがめっちゃ強力です。
クレジットカード大のボード上で普通のLinuxが動き、さらにRubyやPerlが普通に動く環境というのはC系の言語で書かなければならなかったマイコン使いとしては衝撃的でした。
ちなみに僕が作成しているデバイスに関しては言語はPythonを使っています。普通のLinuxとはいえコンパイルしなければならない言語ですとプログラムのアップロードに少し工夫が必要なので、ソースを落として実行するだけで検証可能なスクリプト言語は便利だなあと感じる日々です。
RaspberryPiのプロビジョニング
さて、RaspberryPiにはArduinoなどと決定的に違う点があります。Arduinoはスケッチと呼ばれるプログラムをアップロードするだけで使用可能になるのに対して、RaspberryPiは事前に環境構築が必要です。
そこで、プロビジョニングツールの登場です。そんな感じでRaspberryPiをAnsibleで構築しています。
ちなみにOSはRaspbianをつかっているのですが、いくつか注意点や気をつけたい点があります。
ansibleのaptモジュールが初期状態では使えない
AnsibleはPythonと同様、batteries includedと言われるようにはじめから便利なモジュールがたくさん入っています。あのコマンドちょっとやりたいな〜って思った時にAnsible Modulesを探すとたいてい出てきます。しかも冪等性を担保しつつ書くことができます。
ですが、落とし穴が一部のモジュールにあって、「ターゲット側のPythonに特定のモジュールがインストールされていないと使えない」モジュールがあります。その一つかつRaspbianで必須なのがaptモジュールです。このモジュールはpython-aptモジュールがターゲット側に入っていないと使えません。
なのでansibleに「aptモジュールが入っていない時はaptモジュールを入れてからapt-getする」的なことをplaybookに記述します。
- name: install git package from apt
apt: pkg=git force=yes # gitを入れたい
ignore_errors: True # エラーになっても終了しない
register: result # 結果を変数に保存
- name: update apt
# aptモジュールが使えないのでcommandモジュールでapt-get updateを実行
command: apt-get update
when: result|failed # 結果変数が失敗であれば実行
- name: Install setuptools
# RaspbianシステムPythonはeasy_installも使えないのでpython-setuptoolsを入れる
command: apt-get install python-dev python-setuptools -y
when: result|failed
- name: Install pip
# easy_installコマンドが使えるようになったので
# easy_installモジュールも使えるようになったのでpipを入れる
easy_install: name=pip
- name: Install python-apt
pip: name=python-apt
- name: install git package from apt
apt: pkg=git force=yes
- name: Install python-pycurl
# apt_repositoryモジュールの使用にpython-pycurlが必要なので入れる
apt: name=python-pycurl
- name: add source
apt_repository:
repo='deb-src http://mirrordirector.raspbian.org/raspbian/ wheezy main contrib non-free rpi'
state=present
ちょっとめんどくさいですがまとめるとこんなかんじです。
- とりあえずaptコマンドを使ってみる
- 失敗だったらcommandモジュールでeasy_installまで入れる
- easy_installでpipを入れる
- pipでpython-aptを入れる
- ここでようやくaptモジュールが使える
CentOSなどのRedhat系ならyumモジュールは標準で使えるので良いのですが、RaspbianのようなDebian系のOSはそうもいかない感じです。あとcommandやrawなどのモジュールはただ単純に生コマンドを発行しているだけなので、何らかの方法で冪等性を保たなければなりません。
commandモジュールでビルド
一部のプロジェクトではRaspberryPi上でOpenCVを使っていたりするのですが、Raspbian(=ほぼDebian wheezy)のaptで入るOpenCVは2.3だったりして、ドキュメントもなんかちょっと違うし最新版の2.4.1を使いたいと言うケースがあると思います。
そこでOpenCVをRaspberryPi上でビルドして入れてたりします。ただ、AWSのmicroと同じくらいのスペックであるところのRaspberryPiでOpenCVみたいな巨大なライブラリをビルドするのはめちゃくちゃ時間がかかります。
そこでこれもビルドの自動化をansibleで行っています。もちろんクロスコンパイルやdeb化という回避手段もあるのですが現状これでやっています。
- name: Install build-essential
apt: pkg=build-essential force=yes update_cache=yes
- name: get source
command: apt-get source opencv -y
- name: install deps
command: apt-get build-dep opencv -y
- name: Install Dependencies
apt: pkg=$item force=yes update_cache=yes
with_items:
- libqt4-dev
- libgtk2.0-dev
- pkg-config
- opencl-headers
- libopenjpeg-dev
- libjasper-dev
- libjasper-runtime
- libpng12-dev
- libpng++-dev
- libpng3
- libpnglite-dev
- libtiff-tools
- pngtools
- zlib1g-dev
- zlib1g-dbg
- v4l2ucp
- python
- autoconf
- libeigen2-dev
- cmake
- openexr
- libgstreamer-plugins-bad0.10-dev
- libgstreamer-plugins-base0.10-dev
- freeglut3-dev
- libglui-dev
- libavc1394-dev
- libdc1394-22-dev
- libdc1394-utils
- libxine-dev
- libxvidcore-dev
- libva-dev
- libssl-dev
- libv4l-dev
- libvo-aacenc-dev
- libvo-amrwbenc-dev
- libvorbis-dev
- libvpx-dev
- name: make directories
file:
path=/root/work/src/
state=directory
- name: pull opencv
get_url:
url=http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.6.1/opencv-2.4.6.1.tar.gz/download
dest=/root/work/src/opencv-2.4.6.1.tar.gz
- name: deflate tarball
command:
tar zxvf /root/work/src/opencv-2.4.6.1.tar.gz
chdir=/root/work/src
creates=/root/work/src/opencv-2.4.6.1
- name: create release build directory
file:
path=/root/work/src/release
state=directory
- name: cmake opencv
command:
cmake -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_PYTHON_SUPPORT=ON ../opencv-2.4.6.1
chdir=/root/work/src/release
creates=/root/work/src/release/Makefile
- name: make opencv
command:
make
creates=/root/work/src/release/bin/opencv_createsamples
chdir=/root/work/src/release
- name: make install opencv
command:
make install
chdir=/root/work/src/release
- name: ldconfig
command: ldconfig
これもまたややこしいですが、commandモジュールを駆使してなんとか自動ビルドしています。
commandモジュールはcreatesにコマンド成功後に作成されるファイルやディレクトリを指定することで作成されている場合は実行されないようにすることが出来ます。それで2回目以降は時間がかかるビルドやインストールをスキップするようにしています。
1.4からunarchiveモジュールが追加されているようなのでtarball展開部分はこれで書き直せそうですね。
管理しやすいようにBest Practicesに従って配置
よくYAMLは人間の書くものではないと言われるぐらい仕様が複雑で、さらにansibleでは以上のようにYAMLで手続きを記述したりするので煩雑になってしまいます。
ansibleはワンファイルのplaybookを書くことも出来るのですが、それだと混乱してしまうのでBest Practicesに従ってplaybookの分割を行っています。
Best PracticesではRoleという単位にplaybookを分割していくのですが、Role間で依存関係も記述できるので便利です。上記の例ですと、aptモジュールをなんとか使えるようにするplaybookをaptというRoleにして他のaptモジュールを使うRoleをapt Roleに依存させています。
依存の記述方法については以下の@hnakamurさんの記事が詳しいです。
運用方法とか何故Ansibleなのかとか
RaspberryPiはSDカードを入れ替えるだけで別のボードに移行できるので非常に便利です。物理マシンですが、VMのようにイメージの入れ替えが出来ます。ならイメージコピーや19日目で紹介されたconfigspecでいいじゃないかという感じですが、OpenCVのビルドのせいでまっさらなRaspbianの状態からplaybookを走らせると5時間ぐらいかかったりします。
冪等性を保ったplaybookを記述しておくことで構築済みSDカードに対して確実に変更を反映できるのでAnsibleを選択したという感じです。社内のソーシャルゲームチームなどではchefを使っているのですが、そもそもOSがDebian系だったりして社内のRedhat系向けに書かれているchefのレシピはそのまま流用できないためイチからレシピを書かないといけないし、何より僕がYAML大好きだったのが決め手でした。
あとGPIOというRaspberryPiに生えているピンに直接給電していると給電側の自作ボードの不具合や電源断のタイミングでSDカードやRaspberryPi自体の回路が吹っ飛んだりするので再構築を容易にしとくのは便利です。今年は一度ショートで煙が出たことがありましたがそのときにかなり助かりました。
とまあそんな感じで、実はRaspberryPiとAnsibleの組み合わせはよいという話でした。
明日はScalaでISUCON3本番アプリを書き直したScala魔神の新卒氏、@m0t0k1ch1です。