Yubikey終章 YubikeyでLinuxのディスク暗号化を開錠する

最近、8年ぶりぐらいに社用のノートPCを新調しました。 主に緊急時のオンコールとかカンファレンス時のTwitter用とかなので、そんなに大したデータを入れるつもりではないが、持ち歩くことは持ち歩くのでちゃんとディスクの暗号化ぐらいはしておかねばならない。

で、ちょうどいい所に最近買ったYubikeyがあるので、こいつで開錠可能にしておけばキーファイルも必要無いし、打つのがダルいほど長いパスフレーズも必要無くなる、という訳で調べて設定してみました。

Linuxでディスク暗号化をやるならブロックデバイスを暗号化する標準的な仕組みであるdm-crypt + LUKSに則ってやるのが良さそうです。

この辺りを参考にしました。

保存データ暗号化 - ArchWiki

dm-crypt/システム全体の暗号化 - ArchWiki

YubiKey - ArchWiki

今回はルートパーティションを普通に一個作ってシンプルにそれをLUKSで暗号化する形にします。この辺は最近のLinuxだと暗号化にチェック付けるだけでインストーラーが勝手にやってくれます。

LUKSの開錠にはいくつかのパターンがあるのですが、arch系のディストリビューションではmkinitcpioというツールによって作成されたinitramfsに入っているカーネルモジュールとHookスクリプトによって実施されます。

initramfsは、カーネルが起動して本物のルートファイルシステムをマウントする時に、事前に必要なものを準備しておく仮のファイルシステムです。

Yubikeyで暗号化を開錠するにはいくつかやり方がありますが、今ならsystemdの仕組みの中でFIDO2を使うのが一番楽そうだったのでその方法を選択。

systemd-cryptenroll --fido2-device=auto /dev/nvme0n1p5

こんな感じでインストール時に暗号化されたルートパーティションでfido2デバイスを利用する様に指定できます。

バイスの登録が出来たら、起動時にもfido2デバイスを利用する様にカーネルの起動オプションを設定します。ブートローダーによって設定のやり方は違いますが、systemd-bootだと以下の様になります。

title Linux Cachyos
options root=UUID=<root filesystem uuid> rw rootflags=subvol=/@ rd.luks.name=<crypted block device uuid>=cryptroot rd.luks.options=<crypted block device uuid>=fido2-device=auto nowatchdog splash
linux /vmlinuz-linux-cachyos
initrd /initramfs-linux-cachyos.img

重要なのはrd.luks.optionsにfido2-device=autoを追加すること。後はcrypttabにも同じ設定を追加しておきます。(起動時に既に開錠してるので、もしかしたらこっちは要らんかも)

cryptroot UUID=<crypted block device uuid>     fido2-device=auto

mkinitcpioのデフォルト設定でsd-encryptフックが有効になるはずなので、多分これだけでいいと思いますが環境によってはsd-encryptフックを明示的に組み込んでinitramfsを再生成しないと駄目かもしれません。

この状態で起動すると以下の様な案内が出るので、YubikeyのFIDO2のPINを入力してキーにタッチすればデバイスの復号化が出来て起動するはずです。

(既に埃が大分ww)

ここまで出来たら、パスフレーズは削除するかいざって時のために非常に長いものに変更するか別途バックアップ用のキーファイルを準備すれば良さそうです。

入力の手間やキーファイルのために別途USBメモリを持ち歩いたりしなくても、もっとコンパクトなデバイスで復号化が可能になったので、セキュリティと利便性のバランスが良い配分になったかなーと思います。

Yubikey続編 GPG鍵もYubikeyに入れたがその時にハマったgnupg内蔵CCIDの非互換問題

昔、Gitのコミット署名用に作ったGPG鍵があったんだけど、大分古くなってたしRSAだしってことで作り直すついでにYubikeyに入れることにした。

管理方針は以下の記事の内容に納得がいったので、そのまま採用。主鍵と暗号化鍵をYubikeyに入れてsignature用の副鍵を必要に応じて作る形に。 fuwa.dev

作業自体は、gpgコマンドで鍵作ってバックアップ取ったらkeytocardやるだけなので難しいことは無かったのだが、一つハマるポイントがあった。

ググれば出てくるんだけど、gpg-agentが素の状態だとYubikeyと通信できない問題が発生する。 gnupgはscdaemonというプロセスを通じてスマートカードとやり取りするらしいのだが、それに組込まれてるCCIDライブラリがYubikeyと互換性が無いらしい。(2.3辺りからそうなったとのこと)

なので、~/.gnupg/scdaemon.confを作成しdisable-ccidと記述して組込みのccidを無効化し、pcscdを入れてそっちのccidライブラリを使う様に切り替える必要があった。

公式サイトの案内もある。https://support.yubico.com/s/article/Resolving-GPGs-CCID-conflicts

地味に分かりにくい問題なので、備忘録を残しておく。

諸々設定できたので、GitHubの古い鍵を廃止したし、ついでにKeybaseのアカウントを取って公開鍵をちゃんと検証に使える様にuploadした。まあ、あんまり使うことないとは思うけど。

keybase.io

Yubikeyを買ったのでLinuxのログインや1passwordのロック解除が出来るようにしてみた

最近、認証情報を整理してファイルにベタ書きしているデータを撲滅し1passwordに寄せたら、やたら1passwordの認証が要求される様になってしまったのでYubikeyをポチった。

予定通りLinuxでも無事設定できたのでまとめておく。

ちなみに、今回買ったのはこれ。

www.yubico.com

国内代理店は完売してるので直接yubicoのサイトから買った。

Type-CにしたのはノートPCとかタブレットとかでも利用するなら将来的にこっちの方がいいかなと。自宅で利用する場合はAの方がいいんだけどアダプタ噛ませば済むので。

Yubikeyの管理

yubikey-managerをインストールする。

ykman infoファームウェアバージョンなどを確認できて、ykman configで不要な機能を無効化したりできる。

重要なのはPINの設定でYubikey NFCは指紋認識とかはできないのでPINを設定しておかないと鍵を持っていかれた時にヤバイことになる。FIDOで利用するPINの設定は以下のコマンドで行う。

ykman fido access change-pin --new-pin $PINCODE

初期時点ではPINが設定されていないのでこれでPINが登録される。ちなみにPINの変更回数は上限があるらしい。

Linuxの認証

Linuxの認証はPAMというモジュール群によって行われている。 Yubikeyの販売元であるYubicoがOSSとして開発しているpam-u2fを利用すればFIDO2認証がPAMで利用できる様になる。

pam-u2fをインストールして以下のコマンドで接続しているYubikeyに対応した鍵情報ファイルを生成してくれる。 デフォルトのファイル配置場所は~/.config/Yubico/u2f_keysになるのでそこにファイルを出力する。

pamu2fcfg -o pam://$HOSTNAME -i pam://$HOSTNAME > ~/.config/Yubico/u2f_keys

後はPAMの設定ファイルを弄るだけで良い。しかしPAMの設定を間違えて設定を壊すと一切ログインできなくなったりするので、設定が正しいかはしっかり検証した方がいい。

自分は以下の様に設定した。

ログイン

/etc/pam.d/system-local-login

auth     sufficient      pam_u2f.so origin=pam://<hostname> appid=pam://<hostname> cue openasuser pinverification=1
auth        include     system-login
account     include     system-login
password    include     system-login
session     include     system-login

sufficientでpam_u2fを登録することで、Yubikeyの認証が終わった時点でログインを可能にしている。2要素認証をしたい場合はrequriedにすれば良い。 cueを有効にすることでYubikeyへのタッチを要求するプロンプトを出す。pinverificationを有効にすることで認証時にPINを必須にする。 hostnameは各自の環境に読み替えて欲しい。

この設定で起動時のログインもhyprlockなどのスクリーンロックも同じ設定を参照するのでYubikeyだけで認証可能になる。パスワード入力の所にPINを入力してからデバイスに触れれば良い。

sudo

/etc/pam.d/sudo

auth sufficient      pam_u2f.so origin=pam://<hostname> appid=pam://<hostname> cue openasuser
auth    substack        system-auth
account substack        system-auth
session substack        system-auth

こっちは既にログイン後なのでPIN認証もなしでYubikeyが接続されているなら触れるだけで認証可能にしている。

1password (polkit)

1passwordのロック解除はシステム認証サービスを使ってロック解除を有効にすると、polkitを経由してロック解除を行う様になる。

これも、一旦マスターパスワードでロック解除した後しか使えないのでPIN認証は不要にした。

/etc/pam.d/polkit-1

auth     sufficient      pam_u2f.so origin=pam://<hostname> appid=pam://<hostname> cue openasuser
auth         include            system-auth
account     include         system-auth
password     include            system-auth
session     include         system-auth

ちなみに、polkit向けのpam設定のデフォルトは/usr/lib/pam.d/polkit-1にあるので、そこからコピーしてから変更した。

1password-cliで必要になる際にYubikeyに触れるだけで済む様になったので大分楽になった。

SSHの鍵をFIDO対応させることもできるのだが、一旦後回しでいいかなと今は変更していない。

という訳で、LinuxでもYubikeyは簡単に使えるし便利なので安心してポチって良い。

実際のところFIDO以外の認証は余り利用しない気はするが、Yubikey自体は結構色々できるので気になったらArchWikiを参照してみると良い。 GPG鍵の管理をYubikeyに任せるのは良さそうだと思ったが、今のところコミット署名にしか使ってないので別にそんなに気を遣わなくてもいいかなーという感じ。

Omarchyの裏側にあるuwsmについての解説

(自分はOmarchyに全く興味ないので、タイトルに入れてるのはこう書いといた方が見てくれる人居るかなーという雑な理由によるもので、Omarchyの話はほぼありません。)

最近のモダンなWaylandデスクトップの裏側にはuwsm - Universal Wayland Session Managerというものが居ることが多いんですが、これが何なのかとても分かりにくいし、日本語で説明されてる文書がほとんど無かったのでブログにまとめてみることにしました。 多分、需要はほぼ無いと思いますが、自分の理解を一回整理しておくメモでもあります。

Waylandデスクトップ

モダンなLinuxデスクトップは大抵Waylandというディスプレイサーバーを利用する様になっていて、これは今迄Linuxで使われていたディスプレイサーバーのXを置き換えるものです。 パフォーマンスの向上、GPU活用、セキュリティ上の懸念の解消、メンテナンス性を高める、などの目的で置き換えが進んでいます。

ただ、通信プロトコルの策定はそんなにスムーズにいってない様で、結構unstableという名前がついたプロトコルに依存して現状動いているといった感じです。

Waylandのデスクトップは今迄のXの仕組みとは関係が無いところで動くので、uwsmを利用しない場合は、startxみたいなコマンドは使わず直接ウインドウマネージャーのコマンドを実行して起動します。

GnomeとかPlasmaみたいなフルスタックのデスクトップ環境を利用している場合は、そこに任せれば良いんですが、世の中にはミニマルなタイル方ウインドウマネージャーを利用したい派閥がそれなりに居ます。

自分はHyprlandユーザーだし、OmarchyもHyprlandを利用する様になってる様です。i3の設定とかなり互換性があるswayも結構人気があります。

Wayland起動時の問題とそれを解決するuwsm

startxとそれに付随するX関係のシェルスクリプト読み込みが発生しないので、普通にコマンドを使って直接起動すると、今迄Xで動作していた自動起動とかGUI向けの環境変数の設定(XMODIFIERS=@im=fcitxみたいなIMの利用設定とか)を読み込んでくれません。

素朴な解決策としては、ラッパーのシェルスクリプトを書いてそこで環境変数を設定してからコマンドを起動すれば良いんですが、それだと管理しづらいし単純に面倒臭い。特に自動起動はダルい。

という訳で、uwsmの出番になります。

uwsmはWaylandデスクトップの起動コマンドをAdhocなsystemdのunitにラップして起動することによって、GUIのための環境変数を登録し自動起動して欲しいものを全てsystemdで管理可能にします。

uwsmの設定方法はUniversal Wayland Session Manager - ArchWikiを参考にしてみてください。 自分はReGreetというgreetdGUIフロントエンドから起動する様に設定してみました。

uwsmの動作

uwsmはざっくり以下のことを実施する。

  1. ウインドウマネージャーのコマンドを実行するためのsystemdのunitファイルを生成する
  2. unitファイルの依存関係を利用して、prehook posthookを仕込めるsystemdのtargetに対する依存関係を設定する
    • systemdの依存関係によってwayland-session@%i.targetがアクティブになり、それを経由してgraphical-session.targetがアクティブになる様に依存関係が構成される
  3. XDG Autostartの管理対象のディレクトリにあるdesktopエントリを起動するunitファイルを動的に生成する
    • このunitファイルはgraphical-session.targetに依存しており、graphical-session.targetがアクティブになった後に起動される
  4. uwsm用のemv設定ファイルに書かれた環境変数を設定した上でウインドウマネージャーのsystemd unitが起動し、環境変数dbusAPIを経由してsystemdに登録される

細かい順番は違うかもしれない。

こうすることで、systemdのunitの依存関係にgraphical-session.targetを設定しておけば、uwsmによってウインドウマネージャーが起動した時に必要なコマンドをsystemdが自動的に起動してくれる様になる。

これでXDG Autostartに登録される旧来のアプリも自動起動が効くし、新しいwayland向けのアプリであればgraphical-session.targetに依存したsystemdのunitファイルをセットで提供してくれるものも多くあるので、systemctl --user enable自動起動を有効にできる。

例えば、Wayland環境でよく利用されているタスクバーのwaybarで提供されているsystemdのunitファイルは以下の様になり、利用しているウインドウマネージャーの設定方法に関わらずsystemctl --user enable waybar.service自動起動が有効になる。

# /usr/lib/systemd/user/waybar.service
[Unit]
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors
Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=graphical-session.target
After=graphical-session.target
Requisite=graphical-session.target

[Service]
ExecStart=/usr/bin/waybar
ExecReload=kill -SIGUSR2 $MAINPID
Restart=on-failure

[Install]
WantedBy=graphical-session.target

ウインドウマネージャーを終了する時もsystemdが依存関係を見て自動的にプロセスを落としてくれる様になる。

uwsm利用下でのアプリ起動

もう一つの重要な機能として、手動で何らかのアプリケーションを起動する時にそれをsystemdの管理下に置く機能がある。

uwsm app -- <command or desktop entry>としてコマンドを実行すると、そのコマンドをsystemdで管理しているslice下に紐付けてくれる。

systemdはデフォルトでapp.slice, background.slice, session.sliceという3つのsliceを提供していて、これはcgroupのリソース管理と紐付けられている。デフォルトではbackground.sliceだけCPUWeightが30に設定されており、CPUリソースを食い合った時の優先度が下がる様になっている。

uwsmはこれを拡張してapp-graphical.slice, background-graphical.slice, session-graphical.sliceというsliceを提供している。sliceは-区切りで階層構造が表現できるらしい。

これらのsliceはこんな感じの中身になっている。

# /usr/lib/systemd/user/app-graphical.slice
[Unit]
Description=User Graphical Application Slice
Documentation=man:uwsm(1) man:systemd.special(7)
PartOf=graphical-session.target
After=graphical-session.target
Conflicts=wayland-session-shutdown.target
Before=wayland-session-shutdown.target

このsliceに紐付いているプロセスはgraphical-session.targetの一部で、wayland-session-shutdownのtargetがアクティブになったら終了しますよって感じの内容になる。ウインドウマネージャー起動後にランチャーなどで起動したアプリをsliceに紐付けることで、cgroupの設定を上書きしたり、wayland sessionのshutdown時に自動的にプロセスをちゃんと終了する、といった動作が可能になる。

uwsmはこんな感じで、コマンドをラップしてsystemdのunitやscopeに変換し、targetに対する依存関係を利用することでデスクトップ環境起動時の自動起動や終了時のプロセス停止を安全に行う方法を提供してくれる。

最初、何故こういうものが必要になるのか良く分かっていなかったのですが、ちゃんと設定すると環境変数自動起動の管理が大分楽になることが分かったし、ウインドウマネージャーが落ちた時にゴミプロセスを残さずに済むことが分かりました。(まあ、後者の状況になったら大抵はrebootかけますが……)

Kaigi on Rails 2025で登壇してきました

今回のKaigi on Rails 2025で登壇の機会を頂いたので、話をしてきました。

トーク内容は今改めてServiceクラスについて考える 〜あるRails開発者の10年〜です。

speakerdeck.com

ちなみに真面目にインデックス作るの面倒臭いのでURL直じゃないと参照できないんですが、Github PagesにHTML版があります。 資料中のリンクが直接クリックできたりするので、その辺り触りたい人はこちらもどうぞ。

http://joker1007.github.io/slides/kaigi_on_rails_2025/slides.html

使い古されたネタなんですが、ある程度バックグラウンドがある自分がベテランポジションとして話すことには意味があるかなあと思って改めて話をさせてもらいました。

実際言いたかったこととしては、Serviceクラスをフックにしつつ、チーム開発における共通認識の重要性とプログラマーが向き合うべき命名と設計について言いたいことを言わせてもらう、みたいな時間になりました。

諸先輩方には戦ってきた道程への共感を、業界の新人の皆さんには心の内に留めておいて欲しい「真実に向かおうとする意志」を伝えられればと思って話を作っていきましたが、懇親会で頂いたフィードバックから概ね狙った通りに話せたかなと感じています。

タイムテーブル解説回の時に某選考委員の方から、1日目のトリに配置したので実質初日のクロージングキーノートぐらいの良い話を期待してますとプレッシャーをかけられていたので、その日のトークで関係がありそうなセッションに出席してスライドの中に散りばめたり、話の中で言及できる様にしていました。おかげで直前まで資料弄る羽目になりましたが、真剣に考える時間が増えたので結果的に良い方向に消化できたと思います。

今回、久しぶりにRailsコミュニティで長めのトークを話しましたが、やっぱり気合入れて登壇してフィードバックが貰えるというのは良いものですね。特にプレッシャーがかかるタイムテーブルのおかげで熱が残ってる間に懇親会に移動できたので、本当に多くの人が自分を見つけては感想を伝えてくれました。本当にありがとうございます。まあ、めちゃくちゃ色々な人と話しをした分、後から気付いた疲労も凄かったですが……w

一参加者として

moroさんのキーノートについては、後で話すとして、その他のセッションについて。

今回、Day2のIntroducing ReActionView: A ActionView-Compatible ERB Engine / Marco Roth | Kaigi on Rails 2025がお気に入りのトークでした。 ActionViewって今迄余り省みられてなかったというか、皆JSの方に意識が向いてしまってた中でHotwireとTurboシリーズのエコシステム改善に目を向けて、HTML-awareなERBパーサーから作り込んであんな映えるデモまで持ってきた実装力が本当にクールでした。Rails Worldなどに行く余裕が無い人も一杯居ると思うので、日本に来て話をしてくれてありがとうという気持ちです。

後、高橋会長のRailsアプリケーション開発者のためのブックガイド / Masayoshi Takahashi | Kaigi on Rails 2025は、情報量が凄過ぎるし自分がまだまだ勉強しなければいかんなーと思うことが余りに一杯思い起こされて圧倒される感じでした。高橋メソッドのオリジナルはやっぱ勢いが違うw

実は、moznionさんの話は聞きたかったんだけど、裏番組になっちゃったので資料・動画待ちですね。

Samuelさんのクロージングキーノートも、作ってるものの幅の広さに圧倒される話だった。Fiberの改善から始めてあそこまでエコシステムを作り上げてるのは尋常じゃない。RubyKaigiで登壇されてた時に絶対Kaigi on Rails向きだよなーと思ってたんで、それが叶って良かった。自分もRails方面で手動かせる余裕があればTrilogy入れてFalcon化とかに突っ込んでみたいんだけどなあ……。(まあ言い訳ですね)

CFPの裏側

資料にもちょっと記載してますが、今回の登壇の一番直接の切っ掛けは福岡Rubyist会議04の懇親会です。

実際のところ、こういう話や設計についての考え方なんかは飲み会でしばしば発生してまして、もう10年以上もRubyコミュニティに居ると何度もやったよねー、という話ではあります。

なので、自分としては改めて自分が話さなくても別に良いかなーと思ってました。しかし、サブタイトルとして入れた様に10年も仕事してると立場も変わって人を監督したりアドバイスしたりということがどんどん増えてきました。そういう中で、大事なことは何度も繰り返し伝えないと、そんな一発でどうにかなる様なことばっかりではないよなーということが分かってきました。という訳で、飲み会であの時は良い話が出来たなーで終わらせるのは流石に勿体無いだろうと思い直し、CFPに応募しようと決めました。 ただ、去年のタイミングだとCFPの時期が噛み合わなくて、1年ズレたという感じですね。締切直前の関西Ruby会議でも結構力を貰った感じです。

今回の自分の話は、去年の島田さんのキーノートや今年のmoroさんのキーノートと大体同じことを言ってるつもりです。着目している箇所は違うし伝え方も違うけど。

koicさんと懇親会で話してた時に、皆繋がってて同じことを伝えようとしてるよね、と感想を貰ったけど全くその通りで、伝わる人にはすぐ伝わるもんだなーと思った次第です。

昔は、Web業界の仕事自体が少なかったのもあって職場に先輩とか居なかった一方で、各地で勉強会がバンバン開催されており自分もRails勉強会Tokyoなどでmoroさんに何度も相談させてもらったことを覚えています。自分でネタを持ってきて自分の考えをまとめて発表してみて議論するっていう機会があの時に何度も経験できたのは、幸運だったなーと今になって思います。やっぱ言語化して誰かと議論するって結構大事なんですよね。更に懇親会でお酒があると勢いがついて更に話し易くなることもあるし、人間一緒に飯食えばなんか仲良くなれる感じがするので、ああいう時間は振り返ってみるととても大事だったなと思います。

今は職場に大勢仲間が居る環境も珍しくないし、相談に乗ってくれる上司も居るので、今回のKaigi on Railsで受け取ったことを色々な人と話をして言葉にしていってもらえると、きっと役に立つ時が来るはずです。そういう飲み会で発生するイイ話みたいなものをカンファレンスの場に引っ張り出すというのが裏テーマの一つと言えたかもしれません。(別に酒を飲めという話ではないことに注意。酒は個人の趣味・嗜好なので)

自分も所謂アーキテクトとして仕事をする様になってきて、システム全体をデザインし、成長の方向性を定め、修復と再構築を繰り返して継続的にソフトウェアを育てる、ということの難しさがやっと分かってきたかなあという感じで、まだまだ入口に立ったばかりです。学ばなければいけないことが山ほどあって大変ではありますが、一緒に悩んでいきましょう。

来年は渋谷ということで、また次のKaigi on Railsで!

RubyKaigi 2025 参加記録

愛媛県松山市で開催されたRubyKaigi 2025に参加してきました。

今回は、近くに道後温泉があったことやカンファレンス自体が水・木・金開催だったこともあり、月曜日に道後温泉に一泊したりしたので、余裕をもって観光が出来たかなと思います。

沖縄の時はなんだかんだで車が無いと色々厳しいなーという感じだったので、自動車免許持ってない自分にはちょっとやり辛いところがあったんですよね。

来年は函館らしいですが、北海道は土地が何もかも広いので、見て回るには結構大変かもしれないなーとその辺りの点で若干の不安がありますね。

という訳で、今回のRubyKaigiを振り返っていきます。

とりあえず、Day-1からの簡単な記録から。

(人が写ってる写真は基本的にTwitterにツイート済みのものか大丈夫なものを選んでるはず)

Day -1

つまり、月曜日。お昼ぐらいに松山の大街道に到着、荷物を何とか預けて宇和島鯛めしを食べる。 関西人として瀬戸内の魚で育っているので、鯛の刺身が好きな自分としては、この鯛めしはとても素晴らしい。

大街道を散歩して、夏目漱石推しが凄い喫茶店に入ってコーヒー飲んで一息

そのまま、DD4D Brewingに行きビールを飲む。DD4Dは東京近郊のクラフトビアフェスでもよく見る醸造所で信頼感抜群、安定の美味いビールだった。昼から通し営業やってるのも嬉しい。

荷物を回収して道後温泉街にある温泉旅館へ。ビジホじゃない所に泊まるのはめちゃくちゃ久しぶりだった。旅館の内湯に入った後、s01さんの黒暗森林信号に合流しようと思ってたんですが、近場の寿司屋がちょうど空いてたので、軽く寿司をつまんで合流しようと思ってたら、めちゃくちゃなサイズのカワハギが出てきたり、隣の席の人に日本酒奢ってもらったりでタイミングを逃した。

肝の量が尋常ではないカワハギ。延々酒が飲めてしまう。

結果的に、松田さん達(確か武者さんとnobuさんとtomogさんだったかな?)と居酒屋で合流できて、焼き鳥の基本水準が高いとか話していた。

最後は松田さんと、瓶でクラフトビールが入っていて、柑橘やフルーツを使ったカクテルが美味しいバーで飲んでいた。帰ろうとしたぐらいでsorahさん達が入ってきて、会が微妙に延長されたりなどして、RubyKaigiが始まってきたなーと感じた。

Day0

最後のバーで飲み過ぎたせいか、RubyKaigiが始まってもいないのに二日酔いになってしまい、チェックアウト延長して昼から活動開始。道後温泉商店街を散策して、お土産を物色したり、シックな喫茶店でコーヒー飲んだり。そういえば、ペンさん夫婦とStanに会った。

荷物を上手く預けることができなかったので、飛鳥乃湯の休憩室を借りて風呂に入った後お茶菓子飲んでリラックス。こういう時間が得られるのはとても良い。

ホテルのチェックイン時間が来たのでいつも通りビジホにチェックインして、ANDPADさん主催のPre Partyへ。

その後は今回の松山において非常に重要な場所となったThe Tapへ。地域のクラフトビールを提供してくれて夜遅くまでやっている素晴らしいクラフトビアバーである。松山にまた行くことがあれば是非行きたい。

最初、3人ぐらいで入ってたのだが、いつの間にかすごい人口密度になった。流石に申し訳ねえとは思ったが、ここしか無いのでお世話になるしかない。お金は落とすんでよろしくお願いします、という気持ちだった。

Day1

さて、ここからカンファレンス本編なので、セッションの内容を中心に書いていきたいと思う。ちなみに自分の記憶がベースになってるので、内容の紹介は間違ってる可能性がある。ここは違うよって気付いた人が居たら教えてください!

キーノート (ima1zumiさん)

地元開催、コミッタ就任披露、キーノートスピーカーと、今回のRubyKaigiの主役の一人と言って間違いないima1zumiさんによるキーノートは、とても素晴らしかった。

情報の符号化から文字コードの歴史に繋げつつ、文字というものの奥深さとそれがRubyでどの様に扱われているかをとても分かりやすく発表されていた。EBCDICとかはSIer時代にちょっとだけ見たことがあるし、自分がSIerで仕事をしていた時でもサーバーの大半がEUC-JPだったり、何故かUnixのシステム文字コードSJISに変更して運用するとかよく分からないシステム(Windowsシステムとの相互作用のためだと思う)が存在しており、自分は単に面倒だなーと思ってただけなのだが、こういう事をちゃんとどうなってんだこれ?と突っ込んでいけると、こんな風に突き抜けることが出来るんだなーカッコいいなーと思いながら聞かせてもらった。

話の流れがとても洗練されていて、後から聞いた発表までの裏話なんかも踏まえると、大変な労力がかかっているんだろうなということが想像できる。

自分が一番好きだったのは「文字コードにのめり込むことになってしまった」という表現で、この「事前にそうなる未来なんて何も想像していなかったのに、唐突な出会いの結果自分でもよく分からん反応をしてしまって、人生に大きな影響を与えてしまった状態」というのが伝わってくるというか、こういう出会いが今の自分を形成してるんだよな、としみじみ思ってしまった。勝手に滅茶苦茶共感してるけど、多分合ってると思う。

Make Parsers Compatible Using Automata Learning (@makenowjustさん)

正確な名前は忘れましたが、教師あり学習によってオートマトンを生成するアルゴリズムを利用して、LramaとPrismのそれぞれのパーサーのプッシュダウンオートマトンを入手し、オートマトンが等価であるかどうかを比較することで、パーサー間の互換性の向上に繋げようというアプローチについての発表だった。

既存研究によって知られているアルゴリズムを応用して、人間では容易発見できない様な現実の問題の改善に繋げている点が素晴らしい。個別のアルゴリズムについては自分が不勉強なためほとんど分からなかったが、オートマトンというモデルが持つ応用力が伝わってくる話で、複雑な状態遷移を伴う現実の問題に対処する時に、何かしらのヒントになるかもしれない。記憶に残しておきたい話だった。

Continuation is to be continued (@fetburnerさん)

継続という非常にマニアックかつ挙動が理解しにくい機能を用いてモナド記法的な表現を作るという、なんか俺似た様なことやったことある!ってなった発表だった。

自分のアプローチはどっちかというとAST変換に近い形でブロック内で書かれている特定の記法を乗っ取り、flat_mapの入れ子に変換するという魔術だったのが、continuationの方はメソッドの途中でcontinuationを経由して行ったり来たりすることで、flat_mapの入れ子を表現していた。面白いなーと思ったが、continuationで実行されるコード、マジでどこからどこに飛ぶのか全然読めねえw 入れ子になると地獄w

サイン会

パーフェクトRails著者として参加してました。

もう5年前の書籍なのに、2冊買ってくれた方が居てちゃんとサイン出来た!本当にありがとうございます!

今でも役に立たなくはないと思うけど、読み替えないといけないところは色々あるのでその辺り申し訳ないと思う。

来年は新刊持っていきたい……と皆で話してましたが、その辺りはDay2に続く。

かわかみさんが、会場までパRailsを持ってきてくれた!重い本をありがとうございます!

Deoptimization: How YJIT Speeds Up Ruby by Slowing Down (@k0kubunさん)

JITの実装において、Deoptimizationとは何なのか何故こういうものが必要なのかということについてRubyの実例を元に解説してくれる発表だった。

Rubyは一見変化しそうに見えないコードでもユーザーランドのコードで何でも変更が加えられてしまう。あるメソッドの中のローカル変数でさえ書き換え可能だし、定数もちゃんと定数ではないw なのでJITで最適化して置き換えてしまっても前提が崩れた時には元に戻して最適化をやり直さないと結果が壊れてしまう。

という訳で、JITを作る上ではそういった挙動を理解して置き換えを元に戻す緊急ハッチをあちこちに備えておかなければいけない。このdeoptimizationがあることで最適化が出来る範囲を拡大できるが、その分メモリを食うことになる。

この話は3日目のRuby Commiters and the Worldで議論されていたStatic Barrierに繋がっている話だと認識していて、事前にk0kubunさんの発表を聞いておいて良かったと思った。

余談だが、こういうトーク間の繋がりで伏線回収されるの良いよね。

Ruby's Line Breaks (@spikeolafさん)

パーサーギャング団のボス、kaneko.yさんの発表。

Rubyの改行とは、改行を認識する・無視する状況とは何ぞやということを実際のRubyのコードとそのパースのされ方から紹介しつつ、歴史的経緯によって発生した基本規則からの例外事象をいくつか上げて、Rubyの改行の認識のされ方が実はlex_stateというlexerが持っている状態によって分岐しているという話だった。

a = 1..
2

みたいなコードは実際に直感的ではないし事故りそうな気はする。

この発表の凄いところは、改行というのは一例であってlex_stateというものに依存して状態が過剰に複雑になっていることが大きな問題の一つであり、それを観測可能に出来る様にLramaを回収したという点だと思う。なので、今後改行に限らずlex_stateに依存している例外的な挙動を補足し改善するための足がかりを確保したということだ。確実に新しい一歩を進めている偉業をサラっと発表している辺りがCoolだなと思った発表だった。

State of Namespace (@tagomorisさん)

我々もMatzも待望しているNamespaceの実装に至るまでの苦労話(絶賛継続中)。

k0kubunさんの発表に近いところがあって、Rubyってのはマジで色々なところが実行中に書き変わる言語なので、下手にメモリ構造を共有すると効率的であっても何が起きるか分からん、みたいなことが大量にあるのだなあと思った次第。Rubhという言語は使うのは自由だけどそれ自体を開発するのはすごい大変という対比がよく分かる話だった。

C拡張のdlopenの重複回避のためにdllをコピーしてる箇所の凡ミスが面白かったw

TRICK

体力が尽きてきたので最後の通常セッションはお休みして、TRICKへ。SQLiteパーサーとクエリ生成の話は聞きたかったけど……。

TRICKはもう言葉では説明できないので、動画が上がったら見てくれとしか言い様がない。

言えることとしては、今年もぺんさんとmameさんが異常な活躍を見せる中、優勝者は別の人だったのが驚いた。

個人的に一番好きだったのは、ぺんさんの標準入力で数字を打ち込むとirbのsyntax highlightで表示できるアスキーアートになり、アスキーアートを上から順番に実行すると算術演算した結果のアスキーアートが出てくるやつ。文章で書いても何言ってるか全然分からんし、何がどうなってんねんという感じ……。

何にせよ、今年も滅茶苦茶笑ったので最高だった。

Official Party

滅茶苦茶広い松山城の城山公園でOfficial Partyがあった。天気が良くて本当に良かった。DD4Dのビールが何と飲み放題であり、ここだけクラフトビアフェスか?みたいな状態になっていた。

食べ物は結構数がシビアで食べ損ねた人が結構居た様だが、日本酒とビールは豊富だった。 どうやら、終了時間の少し前ぐらいに日本酒もビールの樽も綺麗に無くなったらしく、奇跡的な見積り量だと驚いた。松田さんはやっぱ何か持ってる人だよなと思う。

The Tap

帰り際にトイレ行ってたら飲んでた面子と逸れてしまったので、The Tapに向かうことにした。SHさんが一人で飲んでたのでしばらく二人でサシ飲みみたいなことをしてたら、やはり後からぞろぞろ人がやってきたw

結構SHさんとガンダムトークが出来て楽しかった。

The Tapでydahさんの熱い思いを聞きつつトークの予習が出来たなーと思ったのだが、それがあったのはDay0だったかもしれない。大概ビール飲んでるので細かい記憶が曖昧になっている。

松山のラーメン・つけ麺、美味くない?

Day2

二日酔いはそこまで重くなかったが、睡眠の質が悪過ぎて朝からクソ眠かった。とりあえずキーノートにはギリギリ遅刻せずに済んで良かった。

キーノート (Ivo Anjoさん)

事前にdatadog gemのコードを多少読んでたんだけど、非常に洗練されており、TracePointをめちゃくちゃ使いこなしているなーと思っていたので、とても楽しみにしていたキーノートだったが、期待以上だった。思ってたよりTracePoint成分が多く、Cレベルでしか触れないイベントやpostpone_job APIなどマニアックな機能をプロファイラ用のツールキットとしてまとめて使い易い形でgem化しているという話だった。

一つ一つの機能の説明が非常に分かり易く、話し方自体もめちゃくちゃ洗練されていて、朝一の英語セッションだったけどとても聞き易かった。

自分の仕事でも結構ガチでパフォーマンスに向き合わなければいけないことが多いので、楽しい + すぐに役に立つという俺得な発表だったと思う。

Dissecting and Reconstructing Ruby Syntactic Structures (@ydah_さん)

Rubyと他の言語のメソッド引数の解釈の仕方を比較しつつ、Rubyの特徴的な文法構造の一つとしてargsとprimitive(primitiveだっけ?記憶だけだと限界が……)という階層に着目してRubyという言語の柔軟さと文法構造の関わりというものを熱く語っているトークだった。

そういった文法構造を可視化しよりメンテしやすくする仕組みとしてparameterized ruleという仕組みをLramaに導入したということ、そしてそこで得られた成果を楽しそうに話すydah_さんが印象的だった。

熱量があり過ぎて、ちょっと聴衆を置いていって感があったけど、それもRubyKaigiって感じで良い発表だった。(自分も事前にThe Tapで色々聞くタイミングがあったので着いていけたかなーぐらいw)

ZJIT: Building a Next Generation Ruby JIT (@maximecbさん)

突然表れた新たなJIT、ZJIT。スケジュールに書かれてたabstractを見る限りではコンパイル結果のシリアライズと共有を可能にするのかなーぐらいの話だと思ってたら、JITの仕組み自体を根本的に方針転換するという話だった。

2年前にMaximeさんはキーノートをやっているとは言え、正直、これがキーノートではないRubyKaigiは恐ろしいなと思った。結構な大ニュースである。

YARVに注目してLBBVというまだ研究が進んでない分野で一定の成果を出したけど、これ以上の高速化はかなり厳しいという観測結果が出てきており、メソッドベースの教科書的なJIT手法に転換するとのことだ。自分達が作って切り開いてきたものにスパっと見切りを付けて、今迄の経験と挑戦とリスクを評価して、よりスタンダードなアプローチで結果が出せると判断し活動を開始している点がとても凄いことだと思う。何より作ってきたものを捨てるのは簡単じゃない。

既にマイクロベンチではYJITと同等以上の成果が出ているらしく、年末には実用的な性能としてYJITと同様のレベルに辿り着くと計画しているらしい。開発のペースが早過ぎる。どうなってんだ本当に……。

という訳で、度肝を抜かれた発表としては今回この発表がベストだったかも。次元の違いを見せられた感がある。

そういえば、またハチロクがスライドに居た。やっぱJITチームでは頭文字Dは必読なのだろうかw

ランチタイム

2日目のランチは、会場に来ていたパーフェクトRails関係者で集まってお寿司を食べた。

まだ未来はどうなるか分からないのだが、来年には既刊ではなく新刊を持ってRubyKaigiにサークル参加したいぞ!という気持ちを新たにする会だった。

前日のサイン会でやる気も出てきたので、なんとかKaigi Effectで動き出せる様にしたいなと思っている。

MicroRuby: True Microcontroller Ruby (@hasumikin)

午後一のセッションはちょっと時間が中途半端になってしまったのでお休みして、スポンサーブースを眺めたりして、その次から再開。

はすみさんのトークは淡々と凄いことを話すのが面白いところだなと思う。mruby/cではなくmrubyのフットプリントを十分に小さくし、更に組込みでよく利用される非同期ジョブのAPIを自分で実装することで、mrubyを本当の意味でマイクロコントローラーとして使える様にしたという発表だった。

mrubyとmruby/cで互換を取るためにサラっと凄い量のコードを書いていて、本当に凄かった。mrubyという言語の活用幅を大きく広げるプロダクトを作り上げたという点でも偉業だと思う。(自分は物理的な方面では非常に不器用で面倒臭がりなので、こっちの方面のプロダクトを全然触っていないのが申し訳ないところである……。なんかMatzもそうらしいがw)

Speeding up Class#new (@tenderlove)

Class#newはinitializeを経由することでCとRubyのインターフェースを行き来することになってしまい、そこで発生しているオブジェクト変換のオーバーヘッドが無視できないので、全部Rubyにした方が早いんじゃない?という話だった。特にキーワード引数はHashオブジェクトを経由しないとCとRubyを行き来できないのでコストがかかるらしい。

仮説をちゃんとベンチマークで検証してゴールに向かう開発の仕方や、Rubyのメソッド呼び出しにおけるinline cacheについても詳しく解説されており、とても勉強になる話だった。

JITが実用的になってきた時に、k0kubunさんがRubyで書いた方が簡単でかつ早くなる未来が来るという話をしていたが、この辺りもJITによる最適化のメリットが大きそうだし、その辺りも考慮に入っているのだろうと思う。

Making TCPSocket.new "Happy"! (@coe401_)

RubyのSocket.tcpに実装したHappy Eyeballs V2をC実装であるTCPSocket.newにも展開するという話だった。メンターであるakrさんの指摘を素直に受け止めて、そもそものSocket.tcpの実装をやり直すところから始めており、こういう真摯さがしおいさんの凄いところだなと改めて思った。

そして、相変わらずスライドの作り込みが凄い。しかもこれをちゃんと締切に間に合わせるレベルで作ってるんだから偉過ぎる……。これは根本的に駄目人間な自分には真似できない。

しっかりとRubyKaigiまでに実装を完了させ、実際にRubyのネットワーク接続を大きく改善してコミッタに就任というのが実にカッコいい。最悪のケースだと100倍ぐらい接続までの時間が短くなるので凄い成果である。

LT

なんか今年のLT、音出る発表多かった気がする。やっぱ音はインパクトありますね。

ダブルヘッダーだったydah_さんのLTが実にテックLTって感じでとても好きだった。デモやろうとして間に合わないところとか特に良い。

今回、kakedaさんがLTの前説を務めており、LTの歴史や銅鑼の歴史について話をしてくれたのがめちゃくちゃエモくて最高だった。自分はコミュニティに顔を出し始めたのは、その黎明期よりも少し後のことで活動時期が被ってないので、kakedaさんのことは名前だけ存じ上げているという感じだったが、今回語ってくれたLTの文化はまだ強く残っていたというか、わちゃわちゃやって熱量先行で全然間に合わねー!みたいな感じをとても楽しんでいた世代だ。この人時間内にちゃんと言いたいこと話し切れるかな、というプリミティブな所で登壇者を応援したくなる、そういうところがLTの好きな点だ。時代の移り変わりで登壇する人も昔より大分多様になったので、こういう昔ながらのLTというものを意識するかしないかは個人の自由だと思うが、自分はそういうLTが好きだし今回はそういう話がいくつかあってとても楽しいLTだったと思う。

kakedaさんのブログでそういった歴史についてもう少し詳しく説明している記事が上がっていたので、是非読んで欲しい。自分ぐらいの世代だとエモさ爆発だった。

note.com

晩御飯とRuby Karaoke

2日目はドリンクアップからあぶれていたというか、こじんまりと飲みに行くのもいいなと思っていたので無理に補欠をどうにかしようと思わなかった。という訳で久しぶりに会った@shishiさん、ペパボの@deowさん、弊社若者の@_anji7さんとで地元っぽい居酒屋で晩御飯を食べた。

やっぱこういう地元感のあるお店の刺身のコスパが異常というか、いやこの美味さとこの量で1800円ですか?となる。後やっぱ鶏肉が美味い。

小さい集まりだったが今回RubyKaigiに初参加した若者をコミュニティとの接点に紹介できたのは良かったかなと思う。

食事の後は自分は離脱してRuby Karaokeに移動。ちなみに酒飲んでカラオケすると大概酷いことになるので、2日目はほとんど飲んでいない。

mishさんのリクエストで一人ヒプマイやったり、大部屋でCreepy Nutsを歌ったりしていたが、個人的なハイライトはima1zumiさんとSOUL'd OUTを歌う実績が解除できたことだ。最近スティール・ボール・ランのアニメ化が発表された結果SOUL'd OUT熱が高まってきているが、結構昔のグループだし知らん人は全然知らんみたいなグループなので、着いてきてくれる人が少なくて普段寂しい思いをしていたが、今回キーノートスピーカーと一緒に盛り上がれたのは最高だった。COZMIC TRAVELをカラオケで歌う人とか高校時代の同級生以外でほとんど見たことがないw

自分は割とガチめでカラオケが趣味なので、普通よりそれなりにカラオケが上手いらしく結構人に褒められるのだが、好きなことを褒められると正直嬉しいw 人間調子に乗ってはいかんと思うがこの辺りは素直に喜んでおきたい。

Day3

Ruby Karaokeのおかげで酒はほとんど飲んでなかったので二日酔いは一旦抜けたのだが、HPが足りてない感じは変わらず。とりあえず遅刻しなくて済んだのでセーフ。

Ruby Committers and the World

and the Worldは非常にライブ感が強いセッションなのだが、今年はnobuさんのパッチ袋が火を吹いていて面白かった。やっぱパッチモンスターが猛威を奮っている回は面白い。k0kubunさんのセッションの感想でも書いたがStatic Barrierの議論が中々興味深かった。deoptimizeの話を聞いた後だとその意義というものがよく分かるし、斎藤さんがコミュニティの分断を招くからそう簡単に有効にできないと言ってたのもよく分かる。後、inline commentは結構欲しいですね。rbs-inline以外の文脈でもここに軽くコメントで注釈書きたいんだけど上の行に書くかーみたいな時はある。まあ、改行してしまえば書けるとかもあるんだが。

ランチ & 道後温泉

くっ、ガッツが足りない!という状態だったので、ちょっとでもスッキリさせようと思って午前のセッションはお休みして早めに抜けてモリスさんと道後温泉に向かった。早めに移動したのでスムーズに御飯が食べれたし、そのまま道後温泉本館で体を休める。温泉の体に染み入る感じと重力から開放される感じが良いよね。温泉街が近いRubyKaigi最高では。

温泉から出たら、道後温泉の湯上がりIPAを一杯。ってかフルサイズがでけえ。UKパイントでもなくて大ジョッキだよこれは。

天気が良くて、建物が映える

Road to Go gem (@sue445さん)

Goでrubygemsを作る基盤を整備したという発表。事前に聞いていたのだが、自分が関西RubyKaigiで話しをしたRustでrubygemsを作る話が参考になったらしく、これは聞いておかねばという感じで着席していた。

自分は一人のユーザーとして紹介しただけではあるのだが、それがRubyKaigiでの発表に繋がってたのかと思うと、発表して良かったなと思うしとても嬉しい。

実際、Rustの方でソースコードを読んでいたので構造についても想像が付くのだが、それを自分の手でしっかりやり切って形にしているのは凄いことだと思う。正直、かなり面倒臭いことが一杯あったはず。

Bundlerの方にもPRを出してるらしいので、近い内に皆の手元でGo製のrubygemsを書く雛形が簡単に手に入る様になるだろう。

Analyzing Ruby Code in IRB (@tompngさん)

スポンサーブースを眺めてたら、ちょっと遅れてしまったので最初が聞けなかった。IRBをPrismのAPIに寄せて、そのfault-tolerantな性質を利用してIRBがカバーできない変なコードもちゃんと扱える様にしよう、という発表だったと思う。

印象に残っているのが、IRBが受け取るとヤバイ変なコードがマジでおかしなコードで、この辺が如何にもぺんさんっぽいというか、今回も楽しませてもらった。

去年のレポートでも書いたけど、本当に想像力の幅が凄い。想定範囲の広さってエンジニアの能力としても重要な要素なので、リスペクトしかない。そして真似できる気がしないw

力尽きてホテルへ

眠気が限界だったので、短時間でも仮眠しようとホテルに戻った。40分ぐらいベッドで横になってキーノートへ向かう。

Matz Keynote

登場の仕方がズルいww あんなん笑うやろw

奈落からスポットライトを浴びながらせり上がってきたMatz

AI時代のプログラミング言語とは、というテーマでのキーノートだった。「AIのための」ではなく「AI時代」に則したプログラミング言語の話であるのがポイント。「AIのための」言語?だったらPython使っとけばいいんじゃない?みたいなノリが黒Matz感あって面白かった。

実際のところ、短い記述量にコンテキストを多く含められ、DSLを構成するのに非常に親和性のあるRubyという言語は、LLMとの相性は悪くないだろう。型によるコードの意味の詳細化は、一定以上複雑なシステムと人間の認知力の関係性においては有効だと思っているが、LLMをベースにした今のAIの能力にそこまで有効かどうかはまだよく分かっていないと思う。人間に親和性のあるインターフェースとAIに対するそれとで、どういうバランスを取るのが良いのかはこれからより明らかになっていくだろうし、自然言語と今のプログラミング言語の間の存在としてAIとのコミュニケート言語が主流になってくるかもしれない。そういう時代においてRubyは割と戦い様がある言語なんじゃないかというのは自分も同意するところだ。まあ世の中どうなるか分からんというのは毎年のことなので、信条を守って進む以外にやれることはないのかもしれない。

Closing

書きそびれてたので追記。 s01さんが本当にやり切ったという感じで壇上で挨拶していたのにグッときた。Offical Partyは実際天候のリスクとかもあって自分の想像より遥かに大変だったろうと思う。

終わった後にスタッフに感謝の拍手が出来るあの瞬間はとても好きなので、スタッフの皆は堂々と壇上に上がってくれ!といつも思っている。

After Party

カンファレンス後は、さくらインターネットさんのUchiageへ。めっちゃ結婚式会場だったw

入口でウェルカムしているtagomorisさんの図

色々話したんだけど、自分ももういいオッサンになってる訳で、久々に会った友人と話す仕事の苦労話みたいなのが段々重くなっているのを実感するw

tricknotesさんと話してる時に、これこれこんな感じの仕事があってこういう時にマジで大変ですよねー、みたいなことを話してたら「自分の仕事見てました?」とか言われたのが面白かった。一緒にmirakuiさんと小川さんが居て、CTOと元CTOみたいな感じの人間が集まってたので、皆の分かりみが深いというやつだったのが印象深い。

uchiage後は、そこそこの人数と合流して「生」というビアバーに入った。ここも妙に地元感があるというか、えらく家庭的なビアバーだった。パーサーギャングやkoicさんが居たので登壇の話を色々と聞いたりしていた。

そして、最終的に松山の本拠地とも言えるThe Tapに移動。入ったタイミングではそんなに居なかったのだが、なんだかんだでじわじわと人が増えて最終的に黄色いシャツの一団が出来ていた。豪華メンバーが揃ってくるのRubyKaigiって感じでとても良い。

koicさんとydah_さんとで、最後まで飲んでる俺らみたいな人間ってのはつまり寂しがり屋なんですよ、っていう話をして意気投合していた。その時、松田さんは居なかったけど同じだよなーと思っている。

で、3日目でHPが限界だった所で最後まで飲んでたのもあり、店を出るタイミングでかなりベロベロになってしまっていて、koicさんとydah_さんとpastakさんに助けてもらった。記憶が曖昧だがコンビニでポカリと水を買ってきてもらって、水分ガンガン取って休んでたら多少なんとかなったらしい。

気付いたら着替えもせずにベッドでぶっ倒れてたが、幸い何も失くしていないし事前にチェックアウト時間を延長してたおかげで普通にチェックアウトできた。本当お世話になりました、ありがとうございます!

Day4

二日酔いではあったが、一応それなりに寝れたので普通に観光するぐらいなら問題なさそうってことで、お昼過ぎに松山城に向かう。リフトでお城近くまで上がれるのだが、下のフェンスまでそれなりの高さがあり大人になってこういうの乗ると結構怖いなと思う。自分はそこまででもなかったが、harukasanのツイートを見ると顔が割とマジだったので人によってはかなり怖かっただろうと思う

松山城は今迄見たことがある中でも、最高レベルに戦闘的な城で、道の作り、門の数、城の窓の配置と角度、全てが殺意に溢れており見てて滅茶苦茶楽しい城だった。そして、マジでRubyistだらけだったのであちこちで立ち話が出来てお得な場所だったw

中の展示も色々見所があったし、天守閣からの眺めが滅茶苦茶良くて瀬戸内海まで見えた。松本城の時は自分のタイミングが悪くて曇ってたので、その無念が晴らせた感じ。

その後、鯛ラーメンと鯛めしのセットを食べた。観光地っぽい店だったが普通に美味かったし東京のラーメン屋より全然安い。物価の違いを感じる。

実質RubyKaigiの廊下と化していた大街道をブラブラ散歩してすれ違ったRubyistに挨拶をしつつ、生みかんジュースを飲む。今回何度か飲んだ中でお気に入りだった「せとか」のジュース。味が濃いんだけど結構スルスルと飲める感じで上手い。完全に生物なのでお土産にできないのが残念な感じだった。

そして、Day-1とOfficial Partyでお世話になったDD4Dで今回の旅最後のビールを飲む。お店の人に最高でしたと感謝を伝えたら、綺麗に飲み切ってくれてこちらこそありがとうと感謝が返ってきた。イベントで持ち込むと大抵余ってしまうらしいが、そこはRubyistの酒飲みパワーでしっかり飲み切ることができた。後から聞いた話では向こうとしても大分良いイベントにできたらしく、何だったら全国に遠征しますよ、ぐらい言ってくれたらしいw

最初は一人だったんだけど、ここでもじわじわ人数が増えてきて最終的にはこんな感じだった。

面白かったのがioquatixさんがDDRプレイヤーだったらしく、やっぱコミッタ界(一部)ではDDRが流行ってるのか?とか思ったりした。せっかく日本に来たから日本でDDRをプレイしたいとのことで、日本のコナミのアミュパスとアカウントを紐付ける登録作業を手伝ったりしていた。実に光栄なことだw

この後は荷物を回収して空港へ。バスで炬燵さんに会って「ここ座れよー」って言われたりしつつ、空港でお土産を探していた。

実家の母親向けに一六タルト、後は酒飲みセット。

腹ごなしをしようと寿司屋に入ったら黒曜さんとばったり遭遇し、ここぞとばかりにモバイルバッテリーの電力を借りたりしていた。あの時は助かりました。ありがとうございます!自分のはDay3で飲み過ぎてぶっ倒れてたせいで充電しそこねていてモバイルバッテリーのHPが0だった。

帰りの飛行機がogijunさんと同じでヤノさんを紹介してもらったりした。最後までRubyistとの遭遇が楽しめるのがRubyKaigiの良いところだ。

まとめ

色々とあったことをサクっと思い出せる範囲でまとめて書いてみましたが、今年は観光もカンファレンスもバランスよく楽しめたかなーという感じです。沖縄の時は直前まで体調崩していて参加自体が微妙だったのと、移動に自動車が必要そうな箇所も多かった感じがあって、全力で楽しめたかというとちょっと悔いが残るところがあったので、今回その分は取り戻せた感じがありますね。

実は高校生の頃に一度道後温泉に行ったことがあって縁のある町ではあったんですが、今回改めて松山が好きになった。繰り返しになるけど松山城は本当に良かった。地元の人(キーノートスピーカー)がオススメするだけのことはある。

最近、RubyKaigiのCFPに応募できるレベルでRubyと関われていない自分ではあるのだが、やはりRubyKaigiはとても楽しいし刺激も受けるし勉強にもなる。自分にとって最高のカンファレンスがこれからも続いてくれることを祈っております。

来年の函館もとても楽しみですね。Rubyistの皆様、また函館で会いましょう。RubyKaigi 2025、お疲れ様でした!

オブザーバビリティ入門: OpenTelemetryについて知っておくべきこと

自分が在籍している会社でKafkaを利用したマイクロサービスが増えてきているので、昔からオブザーバビリティの向上というものにちゃんと着手したかったのだが、最近になってやっと手を動かせる所まで優先度を上げられた。

という訳で、ここしばらくは社内にあるマイクロサービス群にOpenTelemetryによる計装を入れまくっている訳だが、大分可視化が進んできたので、これを社内のメンバーに周知しなければならない。

とは言え、説明したい内容が余りに一般的な知識なので、社内向けのクローズドなドキュメントとして書くのは勿体無いので、オープンなブログの方にまとめることにする。

(会社のテックブログに書いた方がいいのではという話はあるが、仕事っぽくなると面倒臭い……)

基本的にはOpenTelemetryの公式ドキュメントを自分なりに解釈して要点を絞る、という形で解説していくつもりなので、公式ドキュメントは一通り読んで理解したよ、という人は読む必要はない。

オブザーバビリティとは

オブザーバビリティという言葉が昨今広く言及される様になったが、そもそもオブザーバビリティとは何が出来ることを指してるのか。

それはシステムの内部がどの様に動いているかを知らなかったとしても、外形的な監視によってそのシステムが想定した形で動作していることが観測できることを指している。

そして観測内容を通じて、システムに起きている問題や未知の事象に対する疑問に解答できる様なヒントを与えることを目的としている。

昨今の成長したシステムは非常に複雑で、全てのコンポーネントでその内部の挙動を詳しく理解している人、というのは大抵の組織でほとんど存在しない。内部の挙動に関する知識に依存しない形でシステムの動作に対する疑問にある程度答えられる状況を作らなければ、とてもじゃないが運用していく人間が足りなくなってしまう。

そういう訳で、昨今オブザーバビリティという言葉が指すシステム特性が非常に重要になっている。

OpenTelemetryはこの様なオブザーバビリティ実現のために必要な構成要素を定義した仕様であり、それを実装したコンポーネントやライブラリなども含んでいる。

何を観測するのか

OpenTelemetryにおいては、こういったことを可能にするための観測対象を大きく3つに区分している。

  • トレース: ある一連の処理を示すSpanとそのSpan同士の関連性を繋げたもの。後述する。
  • メトリクス: 一定期間内のアプリケーションやインフラにおける動作情報を数値化したもの。CPU利用率とかメモリ消費とかI/O統計とかが代表的。
  • ログ: ある瞬間に起きたことを説明するタイムスタンプ付のメッセージ。アクセスログ、エラーログ、重要な処理が完了したことを示すログ、デバッグログなど。

メトリクスとログに関しては、昔から収集していることが多かったし、その二つを活用した自動アラートの仕組みを構築しているところも多いだろう。新しく理解することが多いのはトレースである。OpenTelemetryもトレースを取得するための規格策定と実装から始まっていて、今はメトリクスやログも含めて総合的に扱えるオブザーバビリティの基盤という形に発展してきた。

という訳で、メトリクスとログについては改めてここでは解説せず、トレースに絞ってより詳しく見ていく。

トレース(分散トレース)とは

ある一つの処理の開始から終了までをトラックしたものをSpanと呼ぶ。そのSpan同士を親子構造もしくはリンクによって繋げて複数の処理の関係性を表現したものがトレースである。

トレースにおいて重要なのは、複数のサービス間を跨ぐ様な処理の関係性も表現できることだ。この表現力のおかげで、あるサービスが受け取ったリクエストが更に内側のマイクロサービスにリクエストを投げてその結果をクライアントに返すといった処理の関係性を示すことができて、一連の処理の中でどういったサービスが関係していて、それぞれにどれぐらいの処理時間がかかっているかを記録することができる。処理の関係性は同期的なリクエストに限った話ではなく、RailsにおけるActiveJobを利用するケースや、SQSなどのキューを介してワーカーに処理を移譲するケースなどの、非同期処理も含まれる。

もしトレースの可視化が無ければ、実際にエンドユーザーが体験するリクエストから結果までの時間の中で、どの処理がボトルネックになっているか、どういったサービスが関係しているかを把握するのがかなり難しくなる。特に非同期処理の場合は、ある一連の処理が終端に到達するまでに実際にどれぐらいの時間がかかっているかを把握するだけでも、それなりの困難を伴う。

マイクロサービスアーキテクチャやサーバーレスコンポーネントの組み合わせでシステムを構成する際には、こういった課題は大きな問題だったが、トレースの取得が一般化したことで大分把握しやすい世界になってきた。

可視化されたトレースの例: https://opentelemetry.io/img/waterfall-trace.svg

メトリクスやログはある単体のサービスやノードで完結している情報なので、取得して可視化するまでの流れが分かり易いが、トレースにおいては複数のノード間で関係性を伝達する必要があるため、それをどうやって実現しているかを理解しておいた方が良いだろう。

ここからはトレースを実現するための構成要素をより詳しく見ていく。

Span

Spanはある一つの処理単位を表現する。具体的には以下の様な情報を持っている構造体になる。

  • Name: Spanの名前。メソッド名とかリクエストURLなんかが入る
  • Parent span ID: 親(呼び出し元)になるSpanのID。一連のトレースの視点の場合は空になり、そういったSpanはRoot Spanと呼ばれる
  • 開始時刻、終了時刻: 処理の開始と終了を示すタイムスタンプ
  • Span Context: トレース全体を示すIDや自身のSpan IDなどを保持する不変のデータ構造
  • Attributes: Spanに関係するメタデータを表現するキーバリュー構造
  • Events: Spanの中で起きた一つ一つの事象を表現する。Spanの中でも特に重要な処理が開始された記録など
  • Span Links: 明確に親子関係ではないが、関連しているSpanを示す。複数の非同期処理を待ち受けている場合など、親が一つに絞れないケースなどを表現できる
  • Span Status: そのSpanが成功に終わったかエラーに終わったかを表現する。

特に重要なのは名前とタイムスタンプで、つまりSpanとはある処理の開始時間と終了時間に名前を付けてメタデータを付与したもの、ということになる。

Context Propagation

Context

分散トレーシングにおいて、ある処理Aとある処理BがAPIの境界を跨いでるとする。例えばエンドユーザーから何らかのリスト取得APIがリクエストされたとして(A)、その処理の中でユーザーの認証処理を内部の認証基盤にリクエストして認証する処理がある(B)、という場合を考える。

この時、AとBの処理が関連ある実行単位であることを示すためのデータを保持しているオブジェクトをContextと呼ぶ。実態はキーバリューの形でデータを持っているシンプルなオブジェクトだが、満たさなければいけない仕様やContext操作のために実装しておかなければいけないAPIがOpenTelemetryの仕様に規定されている。

Propagation

Contextを複数のサービスに跨って持ち回るには、それを伝播させる仕組みが必要になる。OpenTelemetry仕様ではその実現のためのPropagator APIが規定されているが、現在仕様がちゃんと定まっていて実装が存在しているのはTextMapPropagatorのみである。

簡単に言えば、HTTPのリクエストヘッダーの様なテキストで表現できるキーバリュー構造を用いてContextオブジェクトを持ち回るためのルールを規定している。

現在、OpenTelemetryで利用するヘッダー構造の仕様としてデフォルトで利用されるのはW3C TraceContextという仕様だ。詳しく知りたい場合はリンク先のW3Cの文書を読んでもらうとして、ここで簡単に説明しておくと、現在この仕様はtrace-idとparent-idとtrace-flagsの持ち方を規定している。trace-idは一連の分散トレース全体を示すユニークなIDで、parent-idは親のSpanを示すユニークなIDで、trace-flagsはトレースがサンプリングされているかとかトレースのレベルなどを表現するいくつかのフラグを表現したものだ。

非常にざっくりした言い方をすると、HTTPのリクエストヘッダやそれに類する仕組みを用いて、ルールに則ってtrace-idとparent-idを伝播させることで、各アプリケーションが自分の処理を表現するSpanを構築する時に、どのトレースに属していて、どのSpanから呼ばれたのかを判断する、ということだ。Spanには多くの情報が付与されるが、Spanは各システムの責任で外部に送信される。トレース全体で最低限持ち回らなければいけないのは2つのIDとフラグだけで、システム間で伝播している実際の情報量はとても小さいので、システム間での情報伝達のオーバーヘッドは小さい。

この様に各Span自体は親のIDを知っているだけで独立したデータ構造で、それをどう表現するかは可視化のためのアプリケーション(Grafana, Jaeger, Zipkin, etc)やWebサービス(Datadog, Splunk, etc)の責任範囲になる。

計装と収集の分離

OpenTelemetryはこういったトレースやメトリックの概念を規定するだけでなく、それらを転送するプロトコルも一緒に規定している。これをOpenTelemetry Protocol (OTLP)と呼ぶ。

現在、OTLPはgRPCとHTTPで計測データを送るための仕様を定義している。

OTLPという仕様のおかげで、各計装ライブラリがデータを可視化基盤に送る時の実装を統一できるし、後述するCollectorを利用してデータ集約してから複数の基盤に転送するといったことが可能になっている。OTLPを利用して直接可視化基盤に対して計装データを送っても良いが、転送効率やトレースメトリクスの収集などを考慮すると、Collectorを利用してデータを送信する構成を取るのが推奨される。

OpenTelemetry Collector

OpenTelemetryのコンポーネント群として用意してくれているベンダー非依存な計装データ転送のための仕組みがOpenTelemetry Collectorだ。Collectorはこれまでに解説してきたトレースだけでなく、メトリクスやログも扱うことができる。

https://opentelemetry.io/docs/collector/img/otel-collector.svg

似たコンポーネントを挙げて表現するなら、fluentdみたいなものと言って良いかと思う。

Collectorは以下の様な構成要素を持つ。それぞれの構成要素はプラガブルになっており、必要に応じて選択して利用できる。

receiver

データを受付ける責務を受け持つ。OTLPが基本になるがCollectorはメトリクスやログも扱うし、既存のサービスからのexportも受け付けることを想定しているので、公式でメンテナンスされているだけでも大量のreceiverが存在する。

例えば、snmpやsyslogやjmxなどログやメトリクスの出力としてよく利用されるものに加えて、Kafkaを利用してデータを集約するためのkafkareceiverなども存在する。

exporter

データを外部に出力する責務を受け持つ。これもOTLPが基本でOTLPを直接受付けられる可視化基盤であればこれだけで十分だが、AWSのcloudwatch logsに出力したり、syslogに送ったり、datadogやsplunkの様な外部サービスに送ったりといったことに対応できる様になっている。

processor

データを加工したり、フィルタして除外したりする時に利用する。

SpanのAttributesに含まれる情報を利用して、送信先をコントロールしたり、不要な情報を除外したり、表記を書き換えたりなどを行う。

OpenTelemetryは仕様のバージョンがよく更新されているので、ライブラリによっては同じsemanticを表すAttributeが微妙に異なっていたりする。こういったものを正規化する目的などにも利用できる。

また、一定期間のデータをメモリ上に蓄積してまとめて処理するためのbatch processorやメモリ消費を一定に抑えるためのmemorylimiterなどもprocessorに含まれている。

connector

processorに似ているが、内部的にはexporterとreceiverとして扱える様になっている。

例えば、トレースメトリクスの収集などに利用する。トレースメトリクスはSpanの秒間リクエスト数や所要時間の統計など、トレースに関連して発生するメトリクスのことだ。connectorを利用するとSpanをbatch processorで集約し、connectorに渡すことでトレース情報から生成したメトリクスをメトリクス送信のパイプラインに繋げるといったことができる。datadogやgrafana cloud向けにそういった処理を行うconnectorが提供されている。

設定例

OpenTelemetry Collectorはyamlで設定を記述する。

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: ${env:MEMORY_LIMITER_LIMIT_PERCENTAGE:-75}
    spike_limit_percentage: ${env:MEMORY_LIMITER_SPIKE_LIMIT_PERCENTAGE:-20}

  batch:


exporters:
  kafka:
    brokers:
      - ${env:KAFKA_BROKER_0}:9092
      - ${env:KAFKA_BROKER_1}:9092
      - ${env:KAFKA_BROKER_2}:9092
    protocol_version: 2.1.0
    partition_traces_by_id: true
    producer:
      compression: zstd

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [kafka]

こんな感じで、OTLPで受付けたトレースデータをKafka Brokerに転送するといった処理を記述できる。

ocb (OpenTelemetry Collector Builder)

OpenTelemetryはGoで実装されたシングルバイナリのコンポーネントなので、ビルド済みのものとして配布されているものには多くのプラグインが含まれている。

もし、必要なものが既に分かっていて、より小さいバイナリが欲しい場合は、ocbというツールを使うことで必要なプラグインだけに絞ったビルドを作成することができる。 また、自分で実装した独自のプラグインを組込むためにも利用できる。

利用方法は難しくないので、リンク先を読めば使い方は大体分かるだろう。

提供されているプラグインを知るには

GitHubリポジトリを確認するのが一番早い。 Collector本体のリポジトリと、オプショナルで提供されているcontribリポジトリを参照すると何が提供されているかが分かる。

github.com

github.com

アプリケーション上での計装方法

大きく分けて自動計装と手動計装があり、自動計装でかなりの範囲がカバーできるのでそちらを利用することが多いと思うが、登場する概念を先に説明するために手動計装から解説する。

手動計装

OpenTelemetryは各言語向けにAPISDKが用意されており、メジャーな言語は大体カバーされている。

ここではRubySDKを利用して手動でSpanを作成する処理を公式ドキュメントから引用する。

まず、SDKのgemをインストールする。

gem install opentelemetry-sdk

or

source "https://rubygems.org"

gem "opentelemetry-sdk"

次にTracerを準備するコードを書く。

# If in a Rails app, this lives in config/initializers/opentelemetry.rb
require "opentelemetry/sdk"

OpenTelemetry::SDK.configure do |c|
  c.service_name = '<YOUR_SERVICE_NAME>'
end

# 'Tracer' can be used throughout your code now
MyAppTracer = OpenTelemetry.tracer_provider.tracer('<YOUR_TRACER_NAME>')

例えば、Railsのinitializerなどで初期構成を行ってTracerオブジェクトを定数に格納しておく。Tracerはスレッドセーフに実装することが仕様で決められており、大抵の場合はアプリケーションに対してシングルトンで良い。

Tracerが準備できたら計測したい場所で新しくSpanを作成する。

require "opentelemetry/sdk"

def parent_work
  MyAppTracer.in_span("parent") do |span|
    # do some work that the 'parent' span tracks!

    child_work

    # do some more work afterwards
  end
end

def child_work
  MyAppTracer.in_span("child") do |span|
    # do some work that the 'child' span tracks!
  end
end

Rubyであれば、do-endのブロックをSpan計測区間として直感的に表現できる。OpenTelemetryのAPIは上記の様にあるSpan計測区間の中でin_spanメソッドを呼んでSpanを作成すると暗黙的に親を認識して入れ子のSpanにしてくれる。

現在のSpanを取得したい場合は以下の様にすれば良い。

current_span = OpenTelemetry::Trace.current_span

current_span.add_attributes({
  "my.cool.attribute" => "a value",
  "my.first.name" => "Oscar"
})

取得したSpanに対してattributesを追加することもできる。

Spanの情報はContextオブジェクト保持されていて、Rubyの実装ではContextオブジェクトはスレッドローカルストレージに確保される様になっている。

      def stack
        Thread.current[STACK_KEY] ||= []
      end

なのでクラスメソッドを呼ぶと現在のスレッドの最新のContextを経由してSpanを取得することができる。

基本的にはこれだけ覚えておけばトレースを記録することができる様になるが、もう少し登場概念について説明しておく。

TracerProvider

Tracerを生成するためのファクトリで、アプリケーション単位で構成される。後述するResourceやアプリケーションからcollectorや外部サービスにデータを送信するためのexporterの情報を持っている。

Tracer

TracerProviderから生成される実際にSpanを作成するためのオブジェクト。

Span

Tracerが作成するアプリケーションの処理単位ごとの情報を保持するオブジェクト。詳しくは上記を参照。

Resource

Attributeと似ていて分かりにくいのだが、テレメトリデータを生成するエンティティを表す属性値を示す。

どういうことかと言えば、インフラ情報やプロセス名などアプリケーションの単位で不変なものを表現する。例えば以下の様なものが該当する。

  • コンテナID
  • コンテナのイメージ名
  • OS情報
  • デプロイ先の環境名(production, staging, etc)
  • プロセス名と引数
  • クラウドベンダー特有の情報
    • ECSのタスク名やcluster arn
    • GCPのCloudRunの実行ID

これらはSpanに付随する情報としても利用されるが、Spanごとに変化することは無いものとして扱われるので別の概念が割当てられている。

自動計装

OpenTelemetryの文脈ではZero-code Instrumentationとも表現される。

RubyはOpenTelemetryでの定義上はZero-code Instrumentationには含まれていないのだが、実際ほとんど同じ様なものなので、この記事では自動計装とまとめてしまう。

Rubyでは以下の様にすることで自動計装が有効になる。

# Gemfile
gem "opentelemetry-instrumentation-all"
# config/initializers/opentelemetry.rb
require 'opentelemetry/sdk'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
  c.service_name = '<YOUR SERVICE>'
  c.use_all() # enables all instrumentation!
end

この様にすることでopentelemetry-instrumentation-allに含まれる全ての計装処理が有効になる。

個別に利用する場合は以下の様になる。

OpenTelemetry::SDK.configure do |c|
  c.use 'OpenTelemetry::Instrumentation::Rack'
end

opentelemetry-instrumentation-allに含まれる計装を知りたい時はopentelemetry-ruby-contribリポジトリを参照する。

github.com

ここを見ると、HTTPのリクエストやrack、railsのエントリポイント、Active Recordの呼び出しsidekiqの呼び出しなどで自動的に計装してくれることが分かる。

Rubyではどうやって自動計装を実現しているのか

ここでは、active_recordのinstrumentation実装例を見てみよう。

# lib/opentelemetry/instrumentation/active_record/instrumentation.rb

module OpenTelemetry
  module Instrumentation
    module ActiveRecord
      # The Instrumentation class contains logic to detect and install the ActiveRecord instrumentation
      class Instrumentation < OpenTelemetry::Instrumentation::Base
        MINIMUM_VERSION = Gem::Version.new('7')

        install do |_config|
          require_dependencies
          patch_activerecord
        end

        present do
          defined?(::ActiveRecord)
        end

        compatible do
          gem_version >= MINIMUM_VERSION
        end

        private

        def gem_version
          ::ActiveRecord.version
        end

        def require_dependencies
          require 'active_support/lazy_load_hooks'
          require_relative 'patches/querying'
          require_relative 'patches/persistence'
          require_relative 'patches/persistence_class_methods'
          require_relative 'patches/persistence_insert_class_methods'
          require_relative 'patches/transactions_class_methods'
          require_relative 'patches/validations'
          require_relative 'patches/relation_persistence'
        end

        def patch_activerecord
          ::ActiveSupport.on_load(:active_record) do
            # Modules to prepend to ActiveRecord::Base are grouped by the source
            # module that they are defined in as they are included into ActiveRecord::Base
            # Example: Patches::PersistenceClassMethods refers to https://github.com/rails/rails/blob/v7.0.0/activerecord/lib/active_record/persistence.rb#L10
            #   which is included into ActiveRecord::Base in https://github.com/rails/rails/blob/914caca2d31bd753f47f9168f2a375921d9e91cc/activerecord/lib/active_record/base.rb#L283
            ::ActiveRecord::Base.prepend(Patches::Querying)
            ::ActiveRecord::Base.prepend(Patches::Persistence)
            ::ActiveRecord::Base.prepend(Patches::PersistenceClassMethods)
            ::ActiveRecord::Base.prepend(Patches::PersistenceInsertClassMethods)
            ::ActiveRecord::Base.prepend(Patches::TransactionsClassMethods)
            ::ActiveRecord::Base.prepend(Patches::Validations)

            ::ActiveRecord::Relation.prepend(Patches::RelationPersistence)
          end
        end
      end
    end
  end
end
# lib/opentelemetry/instrumentation/active_record/patches/querying.rb

module OpenTelemetry
  module Instrumentation
    module ActiveRecord
      module Patches
        # Module to prepend to ActiveRecord::Base for instrumentation
        module Querying
          def self.prepended(base)
            class << base
              prepend ClassMethods
            end
          end

          # Contains ActiveRecord::Querying to be patched
          module ClassMethods
            method_name = ::ActiveRecord.version >= Gem::Version.new('7.0.0') ? :_query_by_sql : :find_by_sql

            define_method(method_name) do |*args, **kwargs, &block|
              tracer.in_span("#{self} query") do
                super(*args, **kwargs, &block)
              end
            end

            private

            def tracer
              ActiveRecord::Instrumentation.instance.tracer
            end
          end
        end
      end
    end
  end
end

DSL的な実装になっているが、基本的にはライブラリの必要なクラスに対してモジュールをprependすることでSpan作成の処理を挟み込んでいる。 そして、その中でTracer#in_spanを呼んでspanを作成するというシンプルな実装になっている。

もし自分が利用しているライブラリ向けのinstrumentationが提供されていない場合は、この実装を参考にしてmodule prependする様なパッチを当てることで、アプリケーションエンジニアが意識しない形でトレースを取得することが出来るだろう。

Tracerの設定方法

トレースの作成自体は上記の様なAPIを利用して行えるが、アプリケーションに対して取得したデータをどこに送るのかや、共通で追加したいメタデータなどを設定しなければならない。

プログラム上で指定することは可能だが、昨今のコンフィグの入力方法としては環境変数を利用する方法を理解しておくのが良いだろう。このやり方なら言語非依存なのでどの言語で書かれたアプリケーションでも同じ様に設定できる。

設定に利用できる環境変数は以下のリンクから一覧できる。

ここで示されている環境変数を指定することで、計測したデータの送信先やトレースをサンプリングする割合などを設定できる。大量にアクセスがあるシステムだとトレースに関するデータを全て収集していると物凄いデータ量になってしまうので、サンプリングの設定は非常に大事な要素の一つだ。

トレース取得の結果得られるもの

分散トレーシングが可能になると、上記の画像の様に複数のサービスを跨いだ処理のフレームグラフが得られるだけではない。

各サービス間の関係性が分かるので、サービスやミドルウェアへの依存関係やリクエスト量などを可視化することもできる。

特にマイクロサービスアーキテクチャを採用していると、このサービスが何のサービスから入力を受けて、どのサービスに向けて出力しているのか、このサービスはRDBを利用しているのか否かといった情報がすぐに分からなくなってしまう。

コンポーネント同士で担当チームが違うことも有り得るので、自分達の扱うコンポーネントが隣接しているものが何かと、それを管理しているチームがどこかが簡単に把握できることはとても重要だ。

こういった需要に応えられる様に、多くの可視化基盤ではService GraphやService Mapと呼ばれる機能が提供されている。例えがGrafanaだと以下の画像の様に可視化される。

こういったビューがあると、自分がフォーカスしているサービスに関係しているサービスが何なのかを即座に把握できる様になるし、システムの全体像を描くシステム構成図も簡易的なものであれば自動的に作成することができる。

ある程度複雑になってしまったシステムでは、何もかもモノリスで済む様に構成するのはかなり難しいので、こういった仕組みは開発の大きな助けになるだろう。

より進んだオブザーバビリティへ

この記事ではOpenTelemetryとその中でも特にトレースを中心にした解説をしてきたが、最初に述べた様にオブザーバビリティはトレースだけに留まるものではなく、システムの動作を外から観測可能にして開発の助けになる情報が得られる状態を目指すものだ。 その可視化対象はメトリクス、ログ、運用におけるプレイブックの管理、プロファイラの計測結果、担当チームへのコンタクト情報、など多岐に渡る。

最近の可視化基盤では、これらの情報を関連付けて統合的なビューとして見られる様な機能が提供されていることも多い。

アプリケーションを開発する時に、こういった可視化しておくと後々便利になる情報を意識しておくと、未来のスムーズな開発体験に繋げることができるだろう。仮にインフラエンジニアやSREが社内に居るとしても、アプリケーション開発者自身が健全な運用に必要な知識を得ておくことはDevOpsの精神としても重要なことだと思う。

卑近な言い方をすると、ちゃんと役に立つ情報を収集してまとめておけば、他人に何度もシステム構成を説明する手間も省けるし、説明資料をメンテする工数も削減できるし、多くの人間がボトルネック調査をしやすくなる、という感じで仕事が楽になるので、そのために役に立つ情報はちゃんと学んでおこう、ということだ。

最近は色々と便利なものが出来てるので、ちゃんと活用してしっかり楽をしつつより良い開発を目指していこう。