LoginSignup
38
32

More than 5 years have passed since last update.

Rust でクロスコンパイルして Raspberry Pi Zero W で動かす

Last updated at Posted at 2018-08-01
1 / 67

このスライドは Rust LT #2 〜いま使う!Rust〜 で発表した内容です


自己紹介

某社でサーバサイド Rust 書いてる


いま使う!Rust


どこで使う?Rust


Rust は IoT と相性が良い

  • ネイティブバイナリなので省リソース
  • クロスコンパイル可能
  • 言語機能がセキュア

最近の IoT 実行環境


今回の話題

  • RaspberryPi で IoT
  • Rust でクロスコンパイル
  • コンテナで DevOps

もくじ

  1. クロスコンパイル入門
  2. Dockernize 入門
  3. RaspberryPi Zero で動かす

1. クロスコンパイル入門


クロスコンパイルで考えること

  1. ターゲットトリプル
  2. クロスコンパイラ
  3. 標準 C ライブラリ
  4. Rust の対応状況

1. ターゲットトリプル


ターゲットとは

  • ホスト: プログラムをコンパイルする環境
  • ターゲット: プログラムを実行する環境

トリプル(triplet)とは


ターゲットトリプルの例


2. クロスコンパイラ


クロスコンパイラとは

  • ターゲット向けのバイナリを作るコンパイラ、リンカ...etc
  • rustc はリンカに gcc を使っている
  • 対応したリンカが必要

リンカの例


リンカ等の指定方法

  • build.rs を使ってコンパイラに渡す文字列を構築する
  • .cargo/config を使って特定のターゲット向けにコンパイラ設定を書く

build.rs
fn main() {
    println!("cargo:rustc-linker=arm-linux-gnueabihf-gcc");
    println!("cargo:rustc-flags=static=link-arg=-march=armv6");
}

.cargo/config
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
rustflags = [
  "-C", "link-arg=-march=armv6",
]

3. 標準 C ライブラリ


標準 C ライブラリとは

  • システムコールを C 言語でラップしたもの
  • いわわゆる libc
  • POSIX: libc の API など OS が満たすべき仕様

glibc と musl

  • glibc: GNU が作った Linux(POSIX) 向けの libc
  • musl: 静的リンクされることにに特化した POSIX 互換 libc

4. Rust の対応状況


rust ツールチェーンの対応状況


依存クレートの対応状況

  • libc だけでなく依存クレート毎の共有ライブラにも注意
  • SQLite, openssl, etc...

例: RaspberryPi Zero W の場合


ハードウェア


OS とトリプル

  • OS: Raspbian Stretch Lite
    • Raspbian はハードウェア浮動小数点演算(HF) に対応した debian
  • トリプル: arm-unknown-linux-gnueabihf
    • gnueabihf: glibc + HF EABI

  1. トリプル は arm-unknown-linux-gnueabihf (ARMv6 HF)
  2. リンカは raspberrypi/tools
  3. 標準 C ライブラリは raspbian の glibc でよさそう
  4. sqlite とか使う場合は debian の マルチアーキテクチャ でを使ってクロスコンパイル用の依存ライブラリを raspbian のリポジトリとかから用意する

依存ライブラリをもってくる

echo "deb [arch=armhf] http://archive.raspbian.org/raspbian jessie main contrib non-free"
  | tee -a /etc/apt/sources.list
wget https://archive.raspbian.org/raspbian.public.key -O - | apt-key add -
dpkg --add-architecture armhf
apt-get update
apt-get install libsqlite3-dev:armhf

arm-unknown-linux-gnueabihf って?

  • arm-unknown-linux-gnueabihf: ARMv6+VFP (Pi,PiZ)
  • armv7-unknown-linux-gnueabihf: ARMv7+NEON (Pi2,Pi3)
  • ARNv7 は ARMv6 互換

armhf って?


2. Dockernize


Docker + Raspberry Pi

* ARM 向け Docker イメージをコンテナのベースに使う必要あり

インストール方法

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
echo "deb [arch=armhf] https://download.docker.com/linux/debian \
     $(lsb_release -cs) stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install docker-ce
sudo docker run armhf/hello-world

ARM 向け Docker リポジトリとイメージ


理想の Docker イメージ

  • デプロイ時間の短縮のためにコンテナは薄く軽くしたい
  • Image がでかいとダウンロード時間かかる
  • 非力な CPU だと解凍時間も長くなる

イメージサイズ


arm32v6/alpine:latest


arm32v7/debian:stretch-slim

  • 比較的軽い (19MB)
  • ARMv6 ダメ

resin/rpi-raspbian:stretch

  • やや重い (53MB)
  • ARMv6 + HF が使える

例: RaspberryPi Zero W の場合

  • ARMv6 なので arm32v6/alpineresin/rpi-raspbian
  • 共有ライブラリ使わないなら musl を静的リンクして alpine
  • 使うならとりあえず raspbian で始めて徐々に alpine

3. RaspberryPi Zero W で動かす


おひとりさまミニブログ

  • ツイ禁のために自作
  • 衝動買いした RaspberryPi Zero W を使う
  • Tor Onion Service + IoT でセキュアでグローバルIP&ドメインいらずの自宅サーバ
  • 自宅サーバは IoT か?

DevOps とか

  • actix-web + askama + diesel (sqlite)
  • 開発マシン(ubuntu) 上の debian コンテナでクロスビルド
  • rpi-raspbian でパッケージして Docker Hub に push
  • PiZ の Raspbian 上の docker で pull して実行

クロスコンパイルテクニック

  1. docker multistage-build を使う
  2. SQLite を静的リンクする
  3. .cargo/config で linker を指定する
  4. readelf でターゲットを確認する

1. docker multistage-build


Dockerfile
FROM debian:jessie as builder
WORKDIR /source
ADD . /source
RUN cargo build --target=arm-unknown-linux-gnueabihf -p server --release

FROM resin/rpi-raspbian
COPY --from=builder /source/target/arm-unknown-linux-gnueabihf/release/server /opt/server
CMD ["/opt/server"]

  • ひとつの Dockerfile だけでビルドして成果物の別のコンテナに入れる機能
  • debian でビルドしたバイナリを alpine に入れてパッケージ化とかできる
  • GoogleContainerTools/skaffold 使うとデプロイまで自動化できる

  • 今回は debian でビルドして raspbian でパッケージ化
  • alpine 使いたかったが libsqlite3 も含めて musl にするのは面倒
  • まずはとりあえず raspbian

2. SQLite を静的リンクする


静的リンクする動機

  • 開発マシンでは rpi-raspbian ステージの apt は動かない(armhf なので)
  • multi-stage build でクロスコンパイルする場合特有の問題
  • なるべく debian 側でリンクしてしまいたい


libsqlite3-sys の bundled を使う

Cargo.toml
[dependencies]
diesel = { version = "1.3", features = ["sqlite"] }
libsqlite3-sys = { version = "0.9", features = ["bundled"] }

3. .cargo/config でリンカを指定する

  • raspberrypi/toolsarm-linux-gnueabihf-gcc にパスを通しておく
  • /usr/lib/arm-linux-gnueabihflibsqlite3 があるので linker にパスを渡しておく

リンカオプションてんこもり

Cargo.toml
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
rustflags = [
  "-C", "link-arg=-march=armv6",
  "-C", "link-arg=-mfpu=vfp",
  "-C", "link-arg=-mfloat-abi=hard",
  "-C", "link-arg=-L/opt/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/arm-linux-gnueabihf/libc/usr/lib/arm-linux-gnueabihf/",\n\
  "-Z", "print-link-args",
]

4. readelf で細かくバイナリを確認する


怪奇現象


ARMv6 向け設定でビルドしたのに結果は ARMv7 になる現象

$ readelf --arch-specific ./target/arm-unknown-linux-gnueabihf/debug/server
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "7-A"
  Tag_CPU_arch: v7
...

期待する結果 (ARMv6) だとこう表示されるはず

$ readelf --arch-specific ./target/arm-unknown-linux-gnueabihf/debug/server
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "6"
  Tag_CPU_arch: v6
...

原因を探る

  1. Tag_CPU_arch: v6 になるまでソースコードをコメントアウトする
  2. Tag_CPU_arch: v7 の原因となるライブラリの Cargo.toml の features を調べる

原因の特定

  1. actix-web のコードをコメントアウトすると Tag_CPU_arch: v6 に戻ることが判明
  2. actix-web が依存していた cookie-rssecure featrue が ring crate に依存していたのが原因だと分かる
  3. ring crate が c コンパイラに依存していたのが原因

感想


感想

  • クロスコンパイルは難しい
  • alpine linux は不便なのでそのうち廃れそう
  • ビルド&パッケージ化を Dockerfile で管理できて便利

クロスコンパイルは難しい


おわり

38
32
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
32