Debian armhfなDocker imageを作ってみた
組込みLinuxを使っていると、ライブラリなどをソースコードからビルドしたいことが度々あるのですが、通常はクロスコンパイルをするか、ターゲットマシンにDebianなどのOSをインストールしてビルドするといった方法を取ります。
しかし、クロスコンパイルする時には大抵クロスコンパイル用のオプションで嵌りますし、ターゲットマシンでビルドするにも遅すぎで、いつも困っていました。
そんな折、QEMUのユーザーモードエミュレーションとbinfmtを使うと、x86マシンの上でarmバイナリを動かすことができることを知りました。
KMC Staff Blog:QEMUのもうひとつの使い方: ユーザーモードエミュレーションとbinfmtとchrootの組み合わせ
このテクニックを使うと、chrootした後の環境で動くプログラムは、あたかもARMマシン上で動いているように錯覚します。*1そのため、x86マシンの強力なCPUパワーを使い、arm用のバイナリをビルドすることができるようになります。
しばらくはこの環境のお世話になっていたのですが、自分一人だけならまだしも、他の人に同じ環境を作らせようとすると、手順を教えるのがなかなか大変なです。
Web業界では、アプリケーションを実行するサーバーの環境を同一に保ち、保守をしやすくするために、仮想化やコンテナと呼ばれる技術を使うようです。これを使えばarm用のバイナリをビルドする環境を簡単に構築できるんじゃないか?と思い、試してみました。
Docker
コンテナとは、ホストOS上に*2名前空間を分離した環境を構築する技術です。Linuxのコンテナとしてはlxcなどもありますが、今回はDocker Hubを使って作成したイメージの配布が容易にできるDockerを使ってみます。
Dockerのインストール
Debian では、jessieやsidにはDocker 1.0のパッケージが追加されていますが、wheezyにはまだ入っていないようです。そのため、get.docker.io からパッケージを取得してインストールします。
$ wget https://get.docker.io/ -O - | sh
$ sudo gpasswd -a user docker
Docker imageの作成
Dockerでは、Docker imageと呼ばれるもので環境を管理します。DebianやUbuntu, Redhat, CentOSなど、大抵のOS用のイメージは最初から用意されているので、通常はそのイメージを使い、自分用の環境を構築します。しかし、さすがにarmhfバイナリを動かすような環境はないでしょうから*3、イメージを自前で作成します。
Docker imageの作成方法は、公式ドキュメントのCreating a Base Imageで解説されています。これを見ると、debootstrapを使ってルートファイルシステムを作り、それをDocker imageに変換するようです。
Docker imageを作成するスクリプトは、dockerの公式リポジトリのcontribディレクトリに含まれています。これを利用して、QEMUのユーザーモードエミュレーションとbinfmtを使ってdebootstrapでルートファイルシステムを作り、Docker imageを作成できるようにしてみました。*4
KoyoTakenoshita/docker at debootstrap-emul · GitHub
下記の手順で、Debian wheezy armhfなDocker imageを作成できます。
$ sudo apt-get install qemu-user-static
$ git clone https://github.com/KoyoTakenoshita/docker.git -b debootstrap-emul
$ cd docker/contib
$ sudo ./mkimage.sh -t USER/REPOSITORY debootstrap-emul --arch=armhf --interpreter-path=/usr/bin/qemu-arm-static wheezy
作成したDocker imageは、Docker Hubに上げてあります。
kytknst/debian-wheezy-armhf Repository | Docker Hub Registry - Repositories of Docker Images
動かしてみる
実際に作成したDocker imageを動かしてみると、下記のようになり、armアーキテクチャと錯覚していることが分かります。
$ docker pull kytknst/debian-wheezy-armhf
$ docker run -i -t kytknst/debian-wheezy-armhf uname -a
Linux ed64794e1dba 3.2.0-4-amd64 #1 SMP Debian 3.2.57-3+deb7u2 armv7l GNU/Linux
Debianでtestingやunstableからパッケージを借りる時の手順
普段はDebian stableを使っているけど、たまにtestingやunstableにしか入っていないパッケージを使いたくなる時がある。そんな時の対応方法*1
1. /etc/apt/apt.conf.d/ 99target を(普通はないので)作成
下記行を追加
APT::Default-Release "stable";
2. /etc/apt/source.list
下記行を追加
deb http://ftp.jp.debian.org/debian testing main contrib non-free
deb-src http://ftp.jp.debian.org/debian testing main contrib non-free
unstableのパッケージをインストールしたい場合は、testingの部分を置き換える。
3. apt-get update
※この時点でapt-get upgradeしても、testingやunstableのパッケージは入らない。
4. パッケージのインストール
$ sudo apt-get install (パッケージ名)/testing
$ sudo apt-get install (パッケージ名)/jessie
でtesting(jessie)のパッケージをインストールできる。
※ apt-get sourceでも/testingの指定は使える。
より詳しくはこちら。
D: AptGet - Debian GNU/Linux スレッドテンプレ
http://debian.fam.cx/index.php?AptGet#f2d52afb
*1:一時的に/etc/apt/source.listを書き換えるという面倒な方法をしていたら、会社の先輩にもっといいやり方があると教えてもらった。
Cloud9 IDEをローカルマシンにインストールする
Cloud9 IDE は、Webブラウザで動作するIDE。開発元がWebサービスとして提供しているが、IDE自体のソースを公開しているので、自前でサービスを立てることもできる。
いつものごとく、Debian 7 Wheezyにインストールしてみる。基本的には、READMEの記述に従うだけ。
Node.jsのインストール
事前に、Node.jsとnpmをインストールしておく必要がある。過去の記事を参照。
依存するパッケージのインストール
$ sudo apt-get install libxml2-dev
Cloud9 IDEのインストール
$ git clone https://github.com/ajaxorg/cloud9.git
$ cd cloud9
$ npm install
※ git cloneの後に、git submodule update --init --recursive しておく必要があるかもしれない。
Mercurialのインストール
Mercurial(hg)をインストールしていない環境で実行すると、ワークスペースを開くときに エラーで落ちてしまうので、インストールしておく。参考: cloud9 + node 0.10 · Issue #2741 · ajaxorg/cloud9 · GitHub
$ sudo apt-get install mercurial
サービスの起動
$ mkdir ~/workspace/hoge
$ bin/cloud9.sh local -l 0.0.0.0 -w ~/workspace/hoge
おまけ: C/C++のコンパイル
個人的に、Cloud 9 IDEはNode.jsやRubyなどのプログラムを作るのが主流らしいが、個人的にはC/C++言語で書いたプログラムをコンパイルしたい。そういった場合でも、特に困ることもなく、普通にC言語でソースを書き、IDE下部の Command Line で
とかしてやれば良い。
デバッグするには、どうしたら良いのかな?
Debian wheezyでNode.jsをdebパッケージからインストール
Debian 7 WheezyにはNode.jsのパッケージは含まれていなかったのだけれど、backportsにnodejsパッケージが追加された。そのため、ソースからビルドしなくても、apt-get installコマンドで簡単にNode.jsをインストールできるようになった。
インストール
/etc/apt/sources.list.d/backports.list あたりに、下記を追記しておく。
deb http://ftp.jp.debian.org/debian/ wheezy-backports main contrib non-free
deb-src http://ftp.jp.debian.org/debian/ wheezy-backports main contrib non-free
nodejsパッケージと、nodejs-legacyパッケージ*1をインストール。
$ sudo apt-get update
$ sudo apt-get install nodejs nodejs-legacy
nodeパッケージにはnpmは含まれていないので、下記コマンドでインストールしておく。
# curl -L https://npmjs.org/install.sh | sh
ARMマシンでNode.js
Node.jsを使ってのアプリの書き方に少し慣れてきたので、最終的なターゲットであるARMマシンでも動かしてみようかと思ったら、少しハマッた。
nvmを使ってインストール
x86マシンと同様に、単純に nvm installすればインストールはされる。一応、安定版と呼ばれているv0.10.xの最新版を入れることにする。
# nvm install v0.10.16
"バイナリが無いからソースからインストールするよ"的なメッセージが表示され、Node.js自体のコンパイルが始まる。
コンパイル自身は特に問題なく終了し、nodeコマンドがインストールされるが、実行しようとすると、Segmentation fault で落ちてしまう。どうも、v0.10.xはARMマシンではうまく動かないらしい。ちなみに、試した環境は Debian wheezy armhf 環境。
pcDuino Segmentation fault · Issue #4946 · joyent/node · GitHub
↑のリンク先では、v.0.11.2-preだと動くよと書いてある気がするので、v0.11.xの最新版v0.11.6で試してみたら、うまく動いてくれた。
ちょっと気になったRaspberry Pi対応
nvmでは、Raspberry Pi用のバイナリが用意されているらしく、Ras Piの場合はそれをインストールしようとする。ただ、その判定処理がちょっとイケてない。
~/nvm/nvm.shの一部
# Try to figure out the os and arch for binary fetching
local os=
local arch="$(uname -m)"
case "$uname" in
esac
case "$uname" in
*i*86*) arch=x86 ;;
*armv6l*) arch=arm-pi ;;
esac
この処理だと、ARM11等armv6アーキテクチャのCPUを積んでるボードはRas Piだと誤認識されるんじゃないかな。せめて、uname -nで判別すればいいのに。と思ったが、v0.10.xやv0.11.xはそもそもRas Pi用のバイナリが用意されてないようなので、結局ソースから入れることになるので、あんまり関係ないなと思ったり。