試行錯誤のおと

日々の試行錯誤した結果です。失敗することが多い記録、それだけでっす!

Kyoto.なんか #2 で Rust の実践的な話について発表してきました

タイムラインをみていると流れてきたので、なんか会社休みだし、なんか実家にいるので Kyoto.なんか #2 で インフラエンジニアのための Rust というタイトルで発表してきた。

atnd.org

今回、発表で話しきれなかったことやうまくいかなかったことを、ブログ記事に発表内容をまとめた。

※ 発表資料はあとでリンク貼ります。

モチベーション

趣味で Rust を書いていて、 Rust に対するモチベーションをあげたかった話。

Rust というと一般にシステムプログラミング言語で、変数や式の所有権を付与することで安全性や高速性が謳われている。 Hacker News や Reddit を眺めているとほぼ毎日のように Rust の話があって、みんな OS や DNS サーバを実装したり、知見を共有し合っていて楽しそうだなあと思っていた。 趣味のプロダクトだけではなくて、エンタープライズでも一部のコンポーネントとして使っている事例が CloudFlare や Dropbox からもあがっていて、すごくホットな雰囲気も伝わってくる。 一方で国内の Rust 事情はというと、あまり情報が出回っていなくて、そもそも Rust の実践的な話を聞かないなあと感じていたのが今回の話につながっている。

最近、業務で苦労していることを絡めて Rust で netmap を使ったネットワークパケットの作成、解析として将来を見据えた話題になればなあと思って話をしてきた。

実践的な Rust の使い方を目指して

最近、 AWS を使って運用している案件で高トラフィックな要件の案件があって、負荷試験なんかやるとボトルネックが掴みにくくなっていて困っている。 大抵は EC2 インスタンスのリソースが限界になることが多くて、インスタンスタイプごとの性能がこういった形で共有されているのだけど、帯域はともかく m3.large なんかだと PPS は限界に到達してしまう事案が発生している。

qiita.com

問題に対して、今のところは数の暴力で殴って解決していて落ち着いている。 けれども、それではコストがかさむし、AWS だからといって無限にスケールアウトできるわけでもない。 さらに、パケットの処理能力がボトルネックとなっているほどネットワークが高負荷な場合、パケットキャプチャしようとすればパケットを取りこぼす可能性がある。 パケットキャプチャで取りこぼすようだと、何かネットワークまわりで問題が発生したときに調査がしづらくなるため見過ごせない。

そうした課題は以前から社内でも懸念に上がっていて、 netmap や Intel DPDK といった技術は注目していて情報は追っていた。 Rust で netmap や Intel DPDK を利用して何かできれば実践的な話になるんじゃないかなあということで、Rust から netmap を使ってみた。

Rust で packet を作成 & 解析する

Rust には libpnet という Crate (Rust のライブラリ) があって、汎用的なプロトコルスタックを持っていてパケットを作成 & 解析するのに使える。

github.com

標準では扱うレイヤによって OS のネットワークスタックを使うようなんだけど、その気になれば自前でネットワークスタックを libpnet 上で作ることもできる。 リポジトリexamples にはパケットキャプチャのサンプルプログラムが含まれていて、 lo を指定してパケットキャプチャしてみると以下のような結果を得ることができる。

./target/debug/pnet-study-general lo
[lo]: TCP Packet: 127.0.0.1:52472 > 127.0.0.1:6600; length: 39
[lo]: TCP Packet: 127.0.0.1:6600 > 127.0.0.1:52472; length: 269
[lo]: TCP Packet: 127.0.0.1:52472 > 127.0.0.1:6600; length: 32
[lo]: TCP Packet: 127.0.0.1:52472 > 127.0.0.1:6600; length: 44

コードも 200 行程度しかなくて、簡潔な記述でかけることがわかると思う。 パケットキャプチャと逆の要領でパケットを作成することもできて、各レイヤの処理に精通していなくても必要なヘッダやパラメタが分かるところが嬉しい。

さらに libpnet では様々なデータリンク層インターフェイスがサポートされていて、 以下のリンクから見れる。

https://github.com/libpnet/libpnet/tree/master/src/datalink

libpnet がサポートするデータリンク層インターフェイスには中には netmap も含まれていて、今回はこれを利用した。

試してみたのは ICMP echo-request と ICMP echo-reply の簡単な例。 netmap の環境を用意するには netmap をサポートしたデバイスを持っていなかったため、docker を利用して veth インターフェイスを作成し環境を切り分けて、 veth 上で netmap を試してみた*1。 ICMP echo-request を送信して、受け取ったパケットを解析して標準出力に出力した後に ICMP echo-reply を返し、返したパケットも解析して標準出力に出力している。

root@6485c836a3ff:/# /netmap-ping eth0 172.18.0.1
64 bytes from 172.18.0.1: icmp_seq=0 ttl=64 time=
64 bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=
64 bytes from 172.18.0.1: icmp_seq=2 ttl=64 time=
debug # ./pnet-study-pong vethf90b4d8
[vethf90b4d8]: ICMP echo request 172.17.0.2 -> 172.18.0.1 (seq=0, id=70)
[vethf90b4d8]: ICMP echo reply 172.18.0.1 -> 172.17.0.2 (seq=0, id=70)
[vethf90b4d8]: ICMP echo request 172.17.0.2 -> 172.18.0.1 (seq=1, id=70)
[vethf90b4d8]: ICMP echo reply 172.18.0.1 -> 172.17.0.2 (seq=1, id=70)
[vethf90b4d8]: ICMP echo request 172.17.0.2 -> 172.18.0.1 (seq=2, id=70)

OS のネットワークスタックを使うと、 ICMP のヘッダとデータ部分を作成するだけでいいんだろうけれど、 netmap を使うと当然ながら OS のネットワークスタックを使えない。 そのため、今のところパケットを全部自前で作成しないといけないところが少ししんどいところ。

ちなみに、今回利用した、コードは以下のリンクから

netmap-ping の方は需要ありそうだと思っていて、これからも機能を加えていきたいなぁと思っているので、アップデートしていきたい。

まとめ

発表初めに会場で Rust を書いたことがある人を聞いたら結構、多くて驚いた。 # たぶん、若手エンジニアが多かったからだろうな〜。若手エンジニアが多いからこそ、 Rust について話そうと思ったんだけども。

Rust を使ったモチベーションとして、インフラエンジニア向けな話だったと思うのでそこは刺さりにくいのよなあと思ったので少し反省。 けれども id:y_uuki さんや id:moznion くんには刺さったようで、発表後や懇親会ではずっと喋っていてて、一定手応えはあったと感じることができたので良かった。

コードを書いていて、思ったのが libpnet のプロトコルスタックを使うと簡単にパケットを作成できるので libpnet は優秀だなあと感じた。 使っていて思ったのはパケットを作成するというよりはどちらかというと、受け取ったパケットを一部書換えてキャプチャしたり、転送するのに向いているなと思ったので、ロードバランサやホワイトボックススイッチとかの実装に向いているんじゃないかなあと思った。 そういったこれからも発展していくものに関わっているところで Rust は将来明るい技術だと思うし、学ぶモチベーションが上がるし、プロダクション投入に向けて高めていきたい。

最後になりましたが、こうした発表の機会を設けてくださり、ありがとうございました!! > id:hakobe932, id:hitode909

*1:発表した時点ではうまく疎通できなかったんだけど、 veth だから実際は一つのインタフェイスしかないわけで..普通に考えてコンテナの内側も外側も netmap じゃないとダメだよねといオチだった