はてなには、開発合宿というイベントがあります。有志で集ったメンバーが普段の開発から離れ、テーマを決めて集中して取り組むイベントです。
今回、私(id:dekokun)、id:Krouton、id:ne-sachirouの3人からなるチームで、はてなが提供するサーバー管理・監視サービスMackerelに必須であるmackerel-agentというプログラムを題材に、Rustでの再実装を試みました。
mackerel-agentは、監視対象のホストにインストールし、ホストの情報(メトリックやメタデータ)を集めてMackerelに送るプログラムで、Go言語で実装されています。なお、今回はあくまでRustの知見を得る材料として選択したもので、今後の実装を置き換えようとしているわけではありません。
Rustでmackerel-agentを再実装する狙い
Rustでmackerel-agentを再実装したのには、以下のような目的がありました。
なぜRustなのか
そもそも、はてなにはRustによる開発の知見が存在していなかったのですが、今後はRustの活躍分野が広がっていくことも想定されるため、実際の開発を通してRustの知見を得ておきたいと考えました。
なぜmackerel-agentなのか
mackerel-agentは、ユーザのサーバ1台1台で動くことが想定されているので、動作時のフットプリントが小さいことや、バイナリサイズが小さいことなどが求められます。このためRustが活躍できる可能性があります。
また、mackerel-agentの実装に使えそうなRustのライブラリが既にいくつか存在し、3日間の開発合宿である程度まで実装できる可能性が高かったこともあります。
- OS system statistics library for Rust (Krout0n/os-stat-rs)
- An API client library for Mackerel written in Rust (itchyny/mackerel-client-rs)
なお、いろいろと書きましたが、実際にはRustを愛するKroutonが開発合宿に関係なく個人的にRustでmackerel-agentの実装を既に始めており、Rustを学びたいne-sachirouとdekokunがそれに乗っかったというのが、合宿のチーム結成に至った最も重要な流れではあります。
システムメトリック取得の準備と3日間の成果
チーム結成から合宿まで、1ヶ月弱ありました。その間、週に1度集まっては、次の1週間でやりたいことを決めて、実行するというサイクルで事前準備を進めました。 3人それぞれが、主に以下を準備しました。
Rustをあまり触ったことがないne-sachirouは、インタラクティブなオンラインチュートリアルのTour of RustでRustを学習しました。
Kroutonは、mackerel-agentのシステムメトリックを取得する実装を進めました。システムメトリックとは、mackerel-agentがデフォルトで取得するメトリックのことです。詳しくはヘルプの「メトリック仕様」を参照してください。
dekokunは、Kroutonの作業を手伝うとともに、開発環境のVMを準備しました。VMよりDockerで開発する方がメンバーも慣れていますが、Dockerにするとmackerel-agentがOSのメトリックを取得する際に何かにハマる可能性もゼロではなく、限られた合宿の期間中にそのような罠があるのは避けたいということでVMを選択しました。
なお、合宿終了後にDockerでの開発環境が整備されました。
合宿1日目にはライブラリの改善など
合宿1日目には次の実装を進めました。
- os-stat-rsで提供するシステムメトリックの拡充
- mackerel-client-rsのasync/await対応
- ホストの登録機能追加
なお、mackerel-client-rsの改善はフォークしたリポジトリで進めており、まだ上記のリポジトリには反映できていません
合宿2日目でメトリックをMackerelに登録
続いて合宿2日目には、以下を実施しました。
- os-stat-rsで取得したシステムメトリックをMackerelに登録する機能追加
- ホストメタデータ(一部のみ)をMackerelに登録する機能追加
- RustのCI環境構築
CI環境は、cargo check
すら通ってないプログラムがマスターにマージされる事件が起きたため、整備しました。
合宿3日目で定期的に送信できるように
そして合宿3日目までに以下を実施できました。
- すべてのシステムメトリックのMackerelへの登録が可能に
- メトリックの取得を非同期で実行するようにする改修
- ホストのサービス・ロール情報の登録機能追加
- バイナリサイズ削減し、Go言語版のとのサイズ比較
これによりRust版のmackerel-agentが起動すると、mackerel-agent.confを読み込み、ホスト情報をMackerelに登録し、それから定期的にシステムメトリックをMackerelに送信することができるようになりました。
成果物は次のリポジトリで公開しています。合宿後も開発が進んでいます。
まだGo言語版の機能のほんの一部しか実装できていないため、これからも開発を続けていきたいと考えています。
Rust版mackerel-agentの実装で学べたこと
開発合宿を通して次のような学びがありました。
1. Rust実行ファイルのバイナリサイズを削減する手法
デバッグビルドではバイナリサイズが80MBほどありましたが、最適化の設定を施しつつ、strip
コマンドでシンボル情報を削除することで、容量を4.1MBまで抑えることができました。
こういったバイナリサイズを小さくする手法は書籍『実践Rustプログラミング入門』の第10章に書かれており、たいへん参考になりました。
参考までに、Go言語版のバイナリサイズは7.8MBでした。もちろん、実装している機能の量が違いすぎますし、スタティックリンクしているライブラリにも差があるため、単純な比較にあまり意味はありません。
2. channelとthread::spawnを使ったハマり
スレッドを使った並行処理は、mackerel-agentでは次のように欠かせない要素です。
- CPUのメトリックを取ってくる
- loadavgのメトリックを取ってくる
- memoryのメトリックを取ってくる ...
そのため、N個の計算があるときにそれぞれスレッドを割り当てて、値をchannelで受け渡しするように実装したのですが、軽くハマりどころがありました。
その内容についてはKroutonが個人ブログでエントリを上げているので、こちらを読んでいただけると本人が喜びます。
3. RustのCI作成
Github Actionsを使ってCI環境を作りました。Rust用のActionであるrust-toolchainやcargoを使うことで、非常に簡単に整備できました。
このCIの中ではcargo fmt --check
、cargo clippy
、cargo test
を実行しています。最初はclippyが複数のwarningを吐いていたため、warningではfailしない設定にしていました。その後、clippyのwarningを全て直してwarningでもfailするようにできたのは、非常に気持が良かったです。
実際にclippyのチェックに助けられたこともあり、改めてclippyの有能さを知りました。
4. NaNとの遭遇
f64を使った計算を行っていたところ、計算の途中にNaN(非数)が紛れ込みました。
Kroutonが「NaNだこれは!?」と言っていて、たいへん楽しそうで良かったです。
シングルバイナリ化にも挑戦したがうまくいかず
SSLをダイナミックリンクしている状態からシングルバイナリ化にも取り組みました。Go言語版とバイナリサイズを比較するため、Go言語ではダイナミックリンクしていないという知識をもとに、Rust版でも共有ライブラリのダイナミックリンクをできる限り避けたかったのです。
rust-musl-builderを使ってシングルバイナリを生成しようとしたのですが、cfg-ifのコンパイル中にE0463エラーが出て通りませんでした。時間的な制約で、この問題は合宿中に追いきれませんでした。
そうこうしているうちに、Go言語版のmackerel-agentをldd
コマンドで調査したところ、やはりlibcなどをダイナミックリンクをしていることが分かり、Rust版をシングルバイナリ化しても厳密なバイナリサイズの比較にはならないため、対応を諦めました。
まとめ ─ これをRustの第一歩として
最後に3人それぞれの感想をまとめます。
- id:Krouton
- ただひたすらにコーディング関係に集中しながら作業できる環境がとても良かったです。自分が舵を切って複数人で開発する経験はなかったので、その経験が積めたのも大きい。社内にRustを普及するためにこれからもやっていきたいと思います!!
- id:ne-sachirou
- 私はRustで何かを作ったのは初めてだったので、こういふ感じになるんだ…、と勉強以前でした。だいぶん以前にRustを触った時と比べcompilerが大変親切に成ってをり、呼吸の仕方から教えてくれる。これも勉強にならない(ぉ。mackerel-agent-rsの開発は継続してるのでこれは慣れてゆきたいですね。個人的に作りたいものはClojure->Rust transpilerです。
- id:dekokun
- これまで、競技プログラミングやISUCON(Webアプリパフォーマンスチューニングコンテスト)でRustは書いていましたが、今回合宿で行ったようなdaemon実装はまた全然別の知識が必要なこともあり、楽しく学びが多かったです。Kroutonがやった並列実行実装なんかは学びの宝庫でした。これからどんどん勢いが増していくRust言語をはてなが取り入れる(かどうかの検討の)ための第一歩が踏み出せたのではないでしょうか。
id:dekokun
はてなブログチーム SRE。2015年8月にオペレーションエンジニアとして入社し、2018年11月より現職。本職以外では東海道・中山道徒歩評論家、準公務員(消防団員)。
Twitter: @dekokun
GitHub: dekokun
blog: でこてっくろぐ ねお