読者です 読者をやめる 読者になる 読者になる

#20 RaspberryPiを仕事で使うためにAnsibleを使う話

この記事はtech.kayac.com Advent Calendar 201320日目の記事です。

どうも、Perlに洗脳されはしましたが、最近洗脳が解けPythonを書いています@mackee_wです。

最近はVOICE DRIVER暗殺教室 殺ジェクションマッピングX-TRAIL X-TECH GEAR PROJECTなどをやっているクライアントワークのデバイスチームでせこせこハンダゴテを握っていたりします。

そんな中で最近良く使っているRaspberryPiAnsibleに関するいくつかの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

ちょっとめんどくさいですがまとめるとこんなかんじです。

  1. とりあえずaptコマンドを使ってみる
  2. 失敗だったらcommandモジュールでeasy_installまで入れる
  3. easy_installでpipを入れる
  4. pipでpython-aptを入れる
  5. ここでようやく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展開部分はこれで書き直せそうですね。

Ansible 最近の発見 | 1Q77

管理しやすいように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のroleを使いこなす Qiita

運用方法とか何故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です。