このスライドは Rust LT #2 〜いま使う!Rust〜 で発表した内容です
自己紹介
某社でサーバサイド Rust 書いてる
いま使う!Rust
どこで使う?Rust
Rust は IoT と相性が良い
- ネイティブバイナリなので省リソース
- クロスコンパイル可能
- 言語機能がセキュア
最近の IoT 実行環境
- コンテナでデプロイ (Azure IoTEdge)
- Kubernetes で管理 (virtual-kubelet)
- 省リソースなコンテナ実行環境 (resion.io)
今回の話題
- RaspberryPi で IoT
- Rust でクロスコンパイル
- コンテナで DevOps
もくじ
- クロスコンパイル入門
- Dockernize 入門
- RaspberryPi Zero で動かす
1. クロスコンパイル入門
クロスコンパイルで考えること
- ターゲットトリプル
- クロスコンパイラ
- 標準 C ライブラリ
- Rust の対応状況
1. ターゲットトリプル
ターゲットとは
- ホスト: プログラムをコンパイルする環境
- ターゲット: プログラムを実行する環境
トリプル(triplet)とは
- コンパイルで必要なシステムを表す
{arch}-{vendor}-{sys}-{abi}
- ABI は省略してトリプル
ターゲットトリプルの例
- x86_64-pc-windows-msvc
- x86_64-apple-darwin
- x86_64-unknown-linux-gnu
- (gcc と rust では一致しない場合もある)
2. クロスコンパイラ
クロスコンパイラとは
- ターゲット向けのバイナリを作るコンパイラ、リンカ...etc
-
rustc
はリンカにgcc
を使っている - 対応したリンカが必要
リンカの例
- debian の cross-toolchain
- armhf(ARMv6) 向け: raspberrypi/tools
- コンパイル済みツールチェーン全部盛り: crosstool-ng
- japaric/cross: docker + qemu (仮想マシン) を使った環境全部載せ
リンカ等の指定方法
-
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
4. Rust の対応状況
rust ツールチェーンの対応状況
- ターゲット向けのコンパイル済み
std
crate はあるか - platform-support に一覧がある
-
rustup target add [target_triple]
などでターゲットを指定できる
依存クレートの対応状況
- libc だけでなく依存クレート毎の共有ライブラにも注意
- SQLite, openssl, etc...
例: RaspberryPi Zero W の場合
ハードウェア
- SoC: BCM2835(BCM2708)
- CPU: ARM1176JZF-S
- 命令セット: ARMv6
- メモリ: 512MB
- Vector Floating Point: ハードウェア浮動小数演算)
OS とトリプル
- OS: Raspbian Stretch Lite
- Raspbian はハードウェア浮動小数点演算(HF) に対応した debian
- トリプル:
arm-unknown-linux-gnueabihf
- gnueabihf: glibc + HF EABI
- トリプル は
arm-unknown-linux-gnueabihf
(ARMv6 HF) - リンカは raspberrypi/tools
- 標準 C ライブラリは raspbian の glibc でよさそう
- 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
って?
-
arm-unknown-linux-gnueabihf
またはarmv7-unknown-linux-gnueabihf
の debian 上での呼称 - debian の armhf は ARMv7向け
- raspbian の armhf は ARMv6 向け
2. Dockernize
Docker + Raspberry Pi
- Raspberry Pi でも Docker が動くようになった
- 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 - 2MB
- arm32v7/debian:stretch - 42MB
- arm32v7/debian:stretch - 42MB
- arm32v7/debian:stretch-slim - 19MB
- resin/rpi-raspbian:stretch - 53MB
arm32v6/alpine:latest
arm32v7/debian:stretch-slim
- 比較的軽い (19MB)
- ARMv6 ダメ
resin/rpi-raspbian:stretch
- やや重い (53MB)
- ARMv6 + HF が使える
例: RaspberryPi Zero W の場合
- ARMv6 なので
arm32v6/alpine
かresin/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 して実行
クロスコンパイルテクニック
- docker multistage-build を使う
- SQLite を静的リンクする
-
.cargo/config
で linker を指定する -
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 側でリンクしてしまいたい
- raspbian の
libsqlite3-dev:armhf
を静的リンクする - deisel を使う側の
Cargo.toml
でlibsqlite3-sys
クレートの bundled feature を使う
libsqlite3-sys の bundled を使う
Cargo.toml
[dependencies]
diesel = { version = "1.3", features = ["sqlite"] }
libsqlite3-sys = { version = "0.9", features = ["bundled"] }
3. .cargo/config
でリンカを指定する
-
raspberrypi/tools
のarm-linux-gnueabihf-gcc
にパスを通しておく -
/usr/lib/arm-linux-gnueabihf
にlibsqlite3
があるので 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
...
原因を探る
-
Tag_CPU_arch: v6
になるまでソースコードをコメントアウトする -
Tag_CPU_arch: v7
の原因となるライブラリの Cargo.toml の features を調べる
原因の特定
- actix-web のコードをコメントアウトすると
Tag_CPU_arch: v6
に戻ることが判明 -
actix-web
が依存していたcookie-rs
のsecure
featrue がring
crate に依存していたのが原因だと分かる ring
crate が c コンパイラに依存していたのが原因
感想
感想
- クロスコンパイルは難しい
- alpine linux は不便なのでそのうち廃れそう
- ビルド&パッケージ化を Dockerfile で管理できて便利
クロスコンパイルは難しい
- Linux の基礎知識が必要
- オススメの情報源