2025 年振り返り

あけましておめでとうございます。2025 年もお世話になりました。

2026 年もよろしくお願いします。

私生活

  • 体調管理
    • 健康です。家族から風邪がうつって、1日だけ仕事を休んだ程度で済みました。
    • 公園で遊具から飛び降りてきた知らない子とぶつかって以来、背中を少し悪くしました。無理をしなければ日常生活に支障はないです。
  • 引っ越し
    • 2025年最大のイベントは引っ越しでした(二番目は子どもの長期入院です。今は元気です)。4月の入学・入園に合わせるため、あえて繁忙期の3月に移りました。以前の住まいにそんなに不満はなかったですが、8年以上住んでいて環境を変えたかったというのが理由です。あと、子どもが小学校に入ったら気軽に引っ越しできないですからね。住んでみて、近くに大きな公園があって通学路が安全、少し歩けば緑があり、今のところ引っ越して正解でした。
  • 旅行
    • 秋田旅行で男鹿半島を回りました。どこにいってもなまはげ推しがすごかったです。秋田市の支援で貸し切りタクシーがお得に利用できるのでおすすめです。国際タクシーの運転手さんが親切な人で名所をたくさん教えてくれました。一応熊スプレーを買って備えてましたが、熊には出会わなかったです。帰ってきた翌週くらいに訪れた場所に熊が出没してましたが。
      • OUTBACKフロンティアーズマン・ベアスプレー を大人一つずつ持参。
      • 秋田へは往路が飛行機、復路が新幹線でしたが、飛行機の方が圧倒的に早くて快適だったので、次に行く機会があれば往復とも飛行機にするつもりです。
    • なまはげ
  • 子育て
    • 第一子は7歳になりました。電車好きは残りつつ、バス好きも加わりました。一時期は週末に都バス・コミュニティバス巡りしてました。入学前の様子を踏まえて、登下校は送り迎え全部付き添う覚悟でしたが、全然一人で行けてます。小学生になると宿題や持ち物管理が必要になるので大変ですが、毎日洗濯物を畳むお手伝いをしてくれたり徐々に自立してきてます。
      • 水泳は今も続けてます。施設の都合で見学できず先生からの報告もないため進捗は全くの謎で、とりあえず先週は「でんぐり返し」をしたらしいということだけ把握しています 😇
    • 第二子は1歳になりました。徐々に歩けるようになってきたので、保育園の登下校も歩かせてます。自動車が大好きで歩いている最中よそ見してばかり、たくさん転ぶので生傷は絶えません。公園でもたくさん走らせていたら、だいぶ足速くなってきたかもしれません。とても良い保育園に巡り会えて、先生たちにかわいがってもらってます。
    • 高輪ゲートウェイシティ
  • 読書

仕事

  • 入社から五年半経ちました
  • 2025年の一番大きな変化は Coding Agent 導入の本格化ですね。Codex, Gemini CLI, Claude は自分の開発で使う、三種の神器になってきました。今だとメインで Codex にコードを生成させつつ、Gemini CLI と Claude にコードレビューや設計の手直しをさせることが多いです。あと、最近では Codex の簡潔な日本語に影響されて、0から自分で書いたissueすら「Agent製」だと思われていそうです(別にいいんですけどね)。
  • ガチャを0から作ったのは新鮮でした。拡張性を意識したデータ構造・事後検証可能な履歴管理・不整合を起こさないトランザクション管理・確率チェックツール(QA用シミュレーター/監視ジョブ)など妥協なく作り切れたのは楽しい経験でした。
  • 2025年は外部への発表機会がなかったですが、Flink は二年近く運用してきて色々学びがあったり、Vitess もマイグレーションツールとしてプロダクトを支えてくれているので、2026年はそういった技術スタックを紹介する機会を作りたいと考えてます。

ソフトウェアエンジニアリング

  • 2025 年も 会社で使用している OSS に bug fix の PR を送りました。中でも flink-cdc の mysql cdc のバグ修正は規模がでかいプロジェクトで頑張りました。Vitess マイグレーション時にジョブが復旧不能になるバグでした。ジョブの再構築は一日がかりになるため、このバグは致命的で issue起票後も注視していたのですが誰も直してくれなそうなので自分で直すことにしました。まだこのときは AI では直せなかったので自力でやりました 💪
  • 自分のプロジェクトではあまり活動ないと思っていましたが、意外とありました
    • protolint の MCP サーバー対応。Claude に作らせれば一瞬という情報を信じてやってみたら全然作りきれないので、結局自分でMCPプロトコルを読み込んで作るはめになりました。ちゃんとAIと対話して動くようになったときは感動。デモ用動画を撮ると毎回結果が変わるのでそれもAIらしいと思った記憶があります。
    • vitess-activerecord-migration に来た初コントリビューション。Time Tracking Software With Invoicing | Harvest で Vitess 移行に当たってマイグレーションツールを検証してくれて、このツールのエラーハンドリング不備(自社利用だと問題ないので適当にお茶を濁していた部分)にPRを送ってくれた。kind (k8s) を使った vitess ありの E2E 環境 (CI) を褒めてくれて、Hervest の SRE いい人だと思いました。

Pod の sysctl でエラー: net.netfilter.nf_conntrack_max を Kubernetes 環境で扱うポイント

sysctl -w net.netfilter.nf_conntrack_max=262000 を Pod の initContainer で実行したらエラーになった。

sysctl: error setting key 'net.netfilter.nf_conntrack_max': Permission denied

同じ net.* でも、net.core.somaxconn はエラーにならない。

https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster に原因が書かれているのだが、なかなか読み解くのに時間がかかったので、簡単にまとめておく。

結論

net.netfilter.nf_conntrack_maxnode-level sysctls に分類される特殊なパラメーターで、 Node 上で直接設定するか、DaemonSet を利用する必要がある。

sysctl を Kubernetes で使うには

基本ルール

  • sysctl インターフェースを使えば、カーネルパラメーターを変更できる。Kubernetes では、Pod から変更可能なパラメーターとそうでないものがある。
  • Kubernetes はこれらのカーネルパラメーターを以下の2つの軸で分類している
    1. safe / unsafe
      • Kubernetes が「安全」と判断したパラメーターのみ、Pod から変更可能
      • unsafe な sysctl パラメーターを変更したい場合、allowed-unsafe-sysctls フラグで明示的に許可を与える必要がある
    2. namespaced / unnamespaced
      • namespaced: Pod 内でスコープが閉じる(他のPodに影響しない)。
      • unnamespaced: Node 全体に影響を与えるため、node-level sysctls と呼ばれ、Pod からの変更はできない。

なぜ net.netfilter.nf_conntrack_max は変更できないのか?

  • net.netfilter.nf_conntrack_maxnode-level sysctls に分類されていて、Node 全体で共有されるパラメーターなので、Pod から変更できない。

読み解くのが難しかった記述

Kubernetes ドキュメントには以下の記述がある:

The following sysctls are known to be namespaced.

Those net.* that can be set in container networking namespace. However, there are exceptions (e.g., net.netfilter.nf_conntrack_max and net.netfilter.nf_conntrack_expect_max can be set in container networking namespace but are unnamespaced before Linux 5.12.2).

最初、ここの意味がわからなかったので変更経緯を見た。 この記述を最初に読んだとき、「namespaced に見えるけど実際は違う」 という部分が直感的に理解しにくかったので、Linux カーネルの変更履歴を確認した。

Linux 5.12.2 より前

  • net.netfilter.nf_conntrack_max にはバグがあり、Pod からは変更できるが、Node 全体に影響を与える unnamespaced な挙動をしていた。
  • この挙動を修正するために パッチ が適用された。

Linux 5.12.2 以降

  • このパッチにより、net.netfilter.nf_conntrack_max は Pod から変更できなくなり、一貫した unnamespaced パラメーターとして扱われるようになった。
  • 結果、現在の環境では、Pod から操作しようとするとエラーになる

ドキュメントが間違っているわけではないけど、もう少し言葉を足してもいいかなと思うのでした。終わり。

2024 年振り返り

あけましておめでとうございます。2024 年もお世話になりました。

2025 年もよろしくお願いします。

私生活

  • 第二子
    • 5/27(月曜日)に無事男の子が生まれました。Gen Alpha の最後の年です(あと一年遅ければ AI 時代の Gen Beta でした 🤖)。
    • 前回同様、1ヶ月の育児休暇を取得しました。その間に各種手続きを済ませたり、お世話のやり方を思い出したりできました。ありがとうございました。
    • 「第二子は身体が強い」という話を聞いたことがありますが、その通りで第一子に比べて半年くらいまで病気にならず助かりました。その後は一度だけ当日朝に熱が出たことがありましたが、あとはフローレンスさんで乗り切れてます。

和泉公園で寝転がる

  • 体調管理
    • 健康です。風邪や熱で会社を休むことはありませんでした。
    • よく歩くようになりました。片道40分くらいなら徒歩移動を選ぶことも増えてきました。
  • 旅行
    • 2月に北海道トマムへ二泊三日で旅行しました。羽田空港から新千歳空港まで約1時間35分、新千歳空港駅からトマム駅まで特急おおぞらで約1時間20分とアクセスが良かったです。
    • 第二子誕生後はしばらく遠出が難しいので、このタイミングで飛行機に乗っていく旅行をしました。一面雪に覆われていて、非日常の体験を満喫できました。

トマム山は寒かった

  • 子育て
    • 第一子は5歳になりました。小学校訪問、ランドセル購入も済み、次の4月は小学校入学です。それに合わせて9月から水泳教室に通いはじめました。親は隣で泳いで待っていてもいいということだったので、自分も毎週1時間泳いでます 🉐
    • 休日は家にいても大変なので、公園やピクニックに出かけてます。5月に訪れた舎人公園日暮里・舎人ライナーに乗ることが主目的でしたが、広い池と緑豊かな環境で楽しめました。

飛鳥山公園の象と遊ぶ

  • 洋書
    • 小説を中心に26冊くらい読んでました。今年は細切れの時間が多くなると分かっていたので、そういうときは本を読むと決めてました。
    • ノンフィクションだと少し古いですが Chip War がタイミング的に一番ためになりました。半導体産業の歴史、主要プレイヤー、産業構造、重要さがわかりやすく書かれているので AI 時代のニュースを理解するときに役立ちます。
    • フィクションだと年の前半は Michael Connelly の The Lincoln Lawyer シリーズ(1~7) やRenée Ballard シリーズ、The Jack McEvoy シリーズなどにハマってました。弁護士物だったり FBI や刑事物なのでそりゃ面白いですね。心に残ってるのは Kristin Hannah の The Women です。Historical Fiction というジャンルでベトナム戦争を舞台に女性ナースの従軍と帰国後が描かれてます。ベトナム戦争が当時のアメリカ世論にどう受け止められたのか様々な立場からその変遷が理解できます。常に死と隣り合わせの中、凄惨な現場で働くナースを一人称視点で描く前半部分は特にリアルでした。その辺りはこの小説でしか味わえない雰囲気があります。

仕事

  • 入社から四年半経ちました
  • 開発チーム全体では、運用が長くなるにつれてソースコードも仕様も複雑化してる中でスピード感を維持できてると思います。また、androidのクラッシュフリーレート改善、ゲームの安定化と効率化、広告システムの開発あたりは特に目を見張る改善や新システムでした 👍
  • 個人的に大きな issue としては、前半に Flink の本番導入が、後半に Vitess migration 導入ができました。Flink は複雑な JOIN クエリのマテリアライズテーブルをリアルタイムに作成するために使ってます。通常必要になる Debezium や Kafka の構築なく、Flink CDC Connector を使うことで Flink と Flink Kubernetes Operator だけを運用すれば済みます。
  • Vitess migration は gh-ost や pt-online-schema-change の代わりに使ってます。こういうツールは大きいテーブルのときだけ使う運用をすることも多いと思いますが、パラレルでは metadata lock によるサービス影響を避けるためにすべてのマイグレーションに一律で適用してます。年末に大きいテーブルでのスロークエリ改善をしたときに、インデックスを以前よりも早く気軽に追加・削除できて良い変化を実感しました。
  • CEE 2024 Singapore (Agora x Oracle 主催)で登壇するため、11月にシンガポール出張がありました 🇸🇬。Agora チームが毎晩夕食に招待してくれたおかげで三泊四日もあっという間に過ぎました。英語の技術プレゼンははじめてだったのでかなり練習しました。ただ定期的にカンファレンスで発表していたおかげで、どれくらい準備するとどのくらいの完成度になるかのイメージは持てていました。たまに外で話す機会を持っておくと、こういうとき役に立ちますね。全く観光できなかったのと今回は一人だったので、近い将来家族で再訪したいです。

ソフトウェアエンジニアリング

  • SRE NEXT 2024 に「500万人が利用する「友達と遊べるたまり場アプリ パラレル」におけるデータベース基盤の継続的改善」というタイトルで登壇しました。タイムアウト・サーキットブレーカー・コネクションプーリングプロキシについて、MySQL、Semian、Vitessを例に挙げてパラレルでの導入背景と実装を解説しました。
  • Vitess migration を ActiveRecord で透過的に使うために vitess-activerecord-migration を書きました。テストは RubyRails、Vitess の各バージョンの組み合わせ(バージョンマトリクス)で 24 ジョブが並行して実行されます。OSS プロジェクトのため、GitHub Actions の 4 vCPU を無料で利用できる点が非常に助かっています。開発体験の工夫としては、docker compose を CI でも使い回すようにしたり、docker の中で kind (k8s) を動かしてます。
  • 2024 年も Vitess など会社で使用している OSS に bug fix や feature request の PR を送りました。細かい Bug Report を除くと今年はそこまで大きなものはないですが、挙げると以下。
      • unmanaged MySQL 構成(Aurora や Cloud SQL などを使う場合)の e2e テストを追加しました。この構成で Vitess の最新バージョンが動作しないバグを修正した際、e2e テストが存在しないことに気づき、再発防止策として対応しました。ここで kind を使った k8s での e2e テストの実装方法を学びました。また、この取り組みを通じて初めて Buildkite を利用しました。
      • vttestserver(テスト用の Vitess)内部で動作する MySQL に直接 TCP/IP 接続できる機能を追加しました。パラレルで Flink を活用する機能の e2e テストを用意する際に、Debezium が vttestserver 内で動いている MySQL のバイナリログを読み込む必要があり、それを実現するために実装しました。Vitess v20 から使えます。
      • paranoia gem に after_restore_commit オプションを追加しました。このオプションを true に設定すると、restore 処理の commit 後にコールバックがトリガーされるようになります。以前はこの機能がなく、after_restore にコールバックを登録する形で対応していました。しかし、after_restore のコールバック内で DB 以外への I/O 処理が発生するケースもあり、commit 後にトリガーされる方が適切な場合もあります。この変更は現在 core ブランチにマージ済みですが、まだリリースはされていません。

読書メモ: 入門 eBPF

www.oreilly.co.jp

1章 eBPF とは何か?なぜ重要なのか?

eBPF の歴史

  • [1993年] 「BSD Packet Filter(BPF)」が論文に登場
    • eBPF のルーツは「BSD Packet Filter(BPF)」で、ローレンス・バークレー国立研究所の Steven McCanne と Van Jacobson により書かれた論文で初めて登場した。BPF は、プログラマが自分で書いたフィルタプログラムをカーネルの中で実行できるようにする機能で、プログラムは汎用的な BPF 命令セットを使って書くことができた。
  • [1997年] Linuxカーネルバージョン2.1.75 で初めて導入
    • tcpdump の中でトレースされるべきパケットをキャプチャするためのより効率的な方法として使われはじめた。
  • [2012年] seccomp-bpf がカーネル v3.5 で導入
    • この機能を使うと、ユーザー空間のアプリケーションがシステムコールを呼び出すことについて、BPF プログラムを使って許可するか禁止するかを決定できる。この機能はパケットフィルタの範囲を超えていて、この辺りから「packet filter」という名前はあまり意味を持たなくなった。
  • [2014年] 拡張された(extended)BPF(eBPF) がカーネルバージョン 3.18 でリリース
    • eBPF の基盤部分が追加され、この後の大きな進化につながる。
  • [2015年] eBPF プログラムを kprobe にアタッチする機能が追加
    • kprobe(kernel probe)はカーネル内のほとんどの命令に対してフックを入れて、追加の命令を実行できる。開発者は kprobe に関数をアタッチしたカーネルモジュールを書いて、デバッグや性能評価の目的に利用できる。
    • 同時期に、カーネルのネットワークスタック内部にフックが追加され、eBPF プログラムはより多くのネットワーク機能に対して利用できるようになった
  • [2020年] LSM(Linux Security Module)BPF が登場
    • これは eBPF プログラムに LSM カーネルインターフェースへのアタッチを可能にするもので、これによって eBPF はネットワークと可観測性に加えて、セキュリティツールとしての用途が可能になった。

なぜ eBPF?

eBPF によって、開発者はカーネルの振る舞いを変更できるカスタムコードを、カーネルの内部にロードし、実行することができるようになった。

今までこれを行うには、カーネルに新機能を追加するか、カーネルモジュールを書く必要があった。問題は前者はリリースまでに時間がかかるし、またどちらもカーネルプログラミングの技術が必要になる。また、安全に実行できるカーネルのコードを書くのが難しい問題もある。

その点、eBPF プログラムは動的ロードをサポートしているので、カーネルのアップグレードもマシンの再起動も必要ない利点がある。また、eBPF 検証器が eBPF プログラムが安全に実行できると判定されたときのみロードされることを保証する仕組みになっている。

2章 eBPF の 「Hello World

BCCHello World

MacOS を使っている場合は https://github.com/lizrice/learning-ebpf の手順に沿って、VM 上で実行環境を用意するといい。

# クローン
$ git clone --recurse-submodules https://github.com/lizrice/learning-ebpf
$ cd learning-ebpf

# lima のインストール
$ brew install lima 

# VM 起動
limactl start learning-ebpf.yaml

# VM ログイン
limactl shell learning-ebpf

VM ログイン後、次のように hello.py を実行すると、使用中のマシンで execve() システムコールが呼び出されるたびに "Hello World" の文字列を含むトレースが出力される。execve() システムコールは、新しいプログラムを実行しようとするたびに呼び出される。

yoheimuta@lima-learning-ebpf:/Users/yoheimuta/lizrice/learning-ebpf$ sudo -s

root@lima-learning-ebpf:/Users/yoheimuta/lizrice/learning-ebpf# cd chapter2/
root@lima-learning-ebpf:/Users/yoheimuta/lizrice/learning-ebpf/chapter2# python3 hello.py
b'           <...>-5873    [003] d...1   787.501458: bpf_trace_printk: Hello World!'
b'            bash-5873    [003] d...1   787.507280: bpf_trace_printk: Hello World!'
b'           <...>-5874    [001] d...1   787.509545: bpf_trace_printk: Hello World!'
b'           <...>-5875    [002] d...1   787.511014: bpf_trace_printk: Hello World!'
b'           <...>-5876    [000] d...1   787.512891: bpf_trace_printk: Hello World!'
b'           <...>-5879    [001] d...1   787.524347: bpf_trace_printk: Hello World!'
b'           <...>-5880    [000] d...1   787.525203: bpf_trace_printk: Hello World!'
b'           <...>-5882    [001] d...1   787.526269: bpf_trace_printk: Hello World!'
b'           <...>-5883    [000] d...1   787.527529: bpf_trace_printk: Hello World!'
b'           <...>-5885    [001] d...1   908.367247: bpf_trace_printk: Hello World!'

hello.py は次のようなコードで、BCC Python フレームワークを使って記述されている。このコードは二つの部分から構成されていて、最初の program 変数に C 言語で書かれた eBPF プログラムが定義されている。後半は、その eBPF プログラムをカーネルにロード、イベントにアタッチ、トレース結果を読み出すためのユーザー空間のコードになる。

#!/usr/bin/python3  
from bcc import BPF

program = r"""
int hello(void *ctx) {
    bpf_trace_printk("Hello World!");
    return 0;
}
"""

b = BPF(text=program)
syscall = b.get_syscall_fnname("execve")
b.attach_kprobe(event=syscall, fn_name="hello")

b.trace_print()

BPF Map

BPF Map は eBPF プログラムとユーザー空間の両方からアクセスできるデータ構造で、典型的なユースケースは次の通り:

  • ユーザー空間で設定情報を書き込み、eBPF プログラムからその情報を取得する
  • eBPF プログラム間で状態を共有する
  • eBPF プログラムから実行結果やメトリクスを書き込み、ユーザー空間のアプリケーションから取得して結果を表示する

様々な種類の Map が定義されており、例えばハッシュテーブル、Perf リングバッファ、配列として利用できる。

次のサンプルはハッシュテーブル Map を作成し、その Key に Linux のユーザーID、Value にそのユーザーが動かすプロセスが execve() を呼び出した回数のカウンタを記録する。 別ターミナルを立ち上げてみることで、このプログラムが execve() を記録できていることが確認できる。

# ターミナル1
# python3 hello-map.py

ID 501: 1
ID 501: 2
ID 501: 2
ID 501: 2
ID 501: 11
ID 501: 11
# ターミナル2
12:40:09 ~/lizrice/learning-ebpf $ limactl shell learning-ebpf

$ ls
LICENSE  README.md  chapter10  chapter2  chapter3  chapter4  chapter5  chapter6  chapter7  chapter8  learning-ebpf-cover.png  learning-ebpf.yaml  libbpf

$ echo $UID
501

前半が C 言語風で書かれた eBPF プログラムで、counter_table ハッシュテーブルにカウンタを書き込んでいる。 後半が python で書かれたユーザー空間のアプリケーションで、b["counter_table"] ハッシュテーブルからカウンタを呼び出している。

#!/usr/bin/python3  
from bcc import BPF
from time import sleep

program = r"""
BPF_HASH(counter_table);

int hello(void *ctx) {
   u64 uid;
   u64 counter = 0;
   u64 *p;

   uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
   p = counter_table.lookup(&uid);
   if (p != 0) {
      counter = *p;
   }
   counter++;
   counter_table.update(&uid, &counter);
   return 0;
}
"""

b = BPF(text=program)
syscall = b.get_syscall_fnname("execve")
b.attach_kprobe(event=syscall, fn_name="hello")

while True:
    sleep(2)
    s = ""
    for k,v in b["counter_table"].items():
        s += f"ID {k.value}: {v.value}\t"
    print(s)

3章 eBPF プログラムの仕組み

この章からしばらく内部構造の説明が続く。eBPF の利用者に詳細の把握は必要ないが、これ以降の章を理解するための最低限は整理しておく。

eBPF 仮想マシン

eBPF プログラムのソースコードが実行されるまでには次の変換を辿る:

  1. eBPF コードを開発者が C または Rust 言語で書く
  2. Clang コンパイラなどで eBPF バイトコードコンパイルする
  3. カーネル内にある eBPF 仮想マシンが eBPF バイトコードを動かす
  4. 実行時にバイトコードJIT コンパイルされて機械語命令(ネイティブマシン命令)に逐次翻訳される

カーネルへの eBPF プログラムの操作

ここでは bpftool というコマンドを使って、プログラムのロード・情報の確認・イベントへのアタッチなどを行う。 bpftool を使うと、BCC で記述して実行していた操作を、コマンドライン経由で行える。

man でも説明されている通り、ここでもデバッグ用途で使っていく。

BPFTOOL - tool for inspection and simple manipulation of eBPF programs and maps https://man.archlinux.org/man/bpftool.8.en

# ロード
$ bpftool prog load hello.bpf.o /sys/fs/bpf/hello

# イベントにアタッチ
$ bpftool net attach xdp id 540 dev eth0

4章 bpf() システムコール

ユーザー空間のアプリケーションがカーネルに eBPF プログラムをロードするには、bpf() システムコールを呼ぶ必要がある。

eBPF プログラムを作るときには、eBPF を高レベルで抽象化したライブラリを使うことが多いので、bpf() システムコールを直接呼ぶことはない。 ただ、ライブラリ関数は bpf() システムコールから呼び出す様々なコマンドとおおよそ 1対1 で対応しているので、bpf() システムコールの概要を知っておくのは必要である。

bpf() のシグネチャは次の通り:

int bpf(int cmd, union bpf_attr *attr, unsigned int size);
  • cmd はどのコマンドを実行するかを指定する。eBPF プログラムのロード、Map の作成、プログラムのイベントへのアタッチ、Map の Key-Value ペアへのアクセスと更新などに対応するコマンドがある
  • attr はコマンドのパラメーターを指定する。

以降は BCC プログラムを strace して、出力された bpf() のコマンドを詳説している。何か作るときにまた読むと良さそう。

5章 CO-RE、BTF、libbpf

多くの eBPF プログラムはカーネルのデータ構造にアクセスするため、あるマシンでコンパイルされた eBPF プログラムが別の Linux カーネルのバージョンで動く保証はない。 このカーネル間の移植性の問題には二つのアプローチが存在する:

BCC のアプローチ

BCC はマシン上で実際に実行するときに eBPF コードをコンパイルすることで移植性の問題を解決している。 ただし、このアプローチには次のような問題がある:

  • プログラムを実行したいすべてのマシンにコンパイルチェーンとカーネルのヘッダファイルをインストールしておく必要がある
  • プログラムを実行するたびにコンパイル処理が走るため、実行開始までに時間がかかる

このような問題があるため、BCC は実運用で使う eBPF プログラムの開発には向いていない。 一方で、Python のユーザー空間コードはコンパクトで読みやすいので、凝ったことをせずに即座に動かしたい場合は、BCC は第一候補になる。

CO-RE のアプローチ

CO-RE は compile once, run everywhere の略で、BCC よりもはるかに上手にカーネル間移植性に対処しているため、実運用ではこちらを検討することになる。

このアプローチでは、まずコンパイルされたデータ構造のレイアウト情報を eBPF プログラムに含める必要がある。この情報は BTF(BPF Type Format)と呼ばれる。Linux カーネルv5.4以降サポートされている。 そして、そのレイアウトが実行しようとしているマシンとで異なる場合に、フィールドのアクセス方法を調整することで移植が可能になる。

ライブラリによって、この調整を行う。この調整が可能なライブラリはいくつか存在する:

  • libbpf(Cライブラリ)。bpftool の裏側は libbpf
  • Cilium eBPF(Goライブラリ)
  • Aya(Rustライブラリ)

以降は libbpf を使用したコードを書く方法を紹介している。何か作るときにまた読むと良さそう。

6章 eBPF 検証器

eBPF プログラムをカーネルにロードするときは、検証プロセスによってプログラムが安全であることを保証している。 「検証(verification)」とは、プログラムを通して考えうるすべての実行経路をチェックし、どの命令も安全であると保証することを指す。

この章では検証器がどのようなことをしているかについて解説する。eBPF コードを書くときに発生する検証器のエラーを理解したくなったら、また読むと良さそう。

7章 eBPF のプログラムとアタッチメント

eBPF プログラムを書くときにはプログラムタイプを指定する必要がある。これによってそのプログラムがどのイベントにアタッチできるかを決定する。 プログラムタイプは大きく二つのカテゴリに分類される。トレーシングとネットワーク関連である。

トレーシング

Perf 関連のプログラムとも呼ばれる。このカテゴリに分類されるプログラムタイプには次のようなものがある:

  • kprobe: カーネル関数の入り口へのアタッチに使う。関数の引数にアクセスできる。
  • kretprobe: カーネル関数の終了時へのアタッチに使う。関数の戻り値にアクセスできる。
  • fentry: kprobe のより新しく効率的な代替。
  • fexit: kretprobe のより新しく効率的な代替。さらに戻り値だけでなく引数にもアクセスできる。
  • Tracepoint: カーネルコード内でマークされた場所。5.15 のカーネルでは 1400 以上の Tracepoint が定義されている。以前から SystemTap のようなツールでトレース出力に使われてきた。

ユーザー空間へのアタッチもできる。例えば、OpenSSL の SSL_write 関数の開始地点にアタッチできる。

  • uprobe/uretprobe: ユーザー空間の関数の開始と終了の地点へのアタッチに使う。
  • USDT(User Statically Defined Tracepoint): ユーザー空間のコードの特定の Tracepoint にアタッチできる。

ネットワーク

ネットワーク関連のプログラムタイプでは、トレーシング関連のプログラムタイプと違って、ネットワークの振る舞いをカスタマイズするために使われる。 例えば、ネットワークパケットをドロップしたりリダイレクトしたり、ソケットの設定パラメーターの変更を行う。

  • ソケット操作: TCP 接続に割り込んだり、TCP タイムアウトを設定したりできる
  • トラフィックコントール: 帯域制限の設定を変更したりできる
  • XDP: XDP プログラムは eth0 インターフェースなどにアタッチして、ネットワークデバイス上でパケットを処理できる。Linux のネットワークスタックに通る前に処理するので高速に動く
  • cgroup: 特定の cgroup において、ソケットの操作やデータ転送の実行を禁止できる

8章 ネットワーク用 eBPF

eBPF をベースにしたネットワークツールは広く使われている:

  • CNCF の Cilium プロジェクトによる Kubernetes ネットワークのカスタマイズ
  • Meta の Load Balancer
  • Cloudflare の DDos 防衛機能

これらはパケットのドロップや転送を XDP プログラムで実現することで、既存のものに比べて高いパフォーマンスを発揮している。 一部のネットワークカードは XDP オフローディング機能をサポートしている。これを使えば、全ての処理はネットワークカード上で完結するため、ホストマシンの CPU サイクルは 1 クロックも使わなくていい。

eBPF と Kubernetes ネットワーク

Kubernetes は CNI (Container Network Interface) を通して、どのネットワーク実装を利用するかをユーザーが選べる。 多くの CNI プルグインは Kubenetes における L3/L4 のネットワークポリシーを実装するために iptables を利用している。

Kubernetes では Pod とその IP アドレスが動的に更新されるため、iptables の更新に数時間かかる場合がある。 また、iptables のルール検索はルールの数に比例して時間がかかる問題もある。

Cillium は eBPF ハッシュテーブルで kube-proxy の iptables を置き換えることで、高いスケーラビリティを実現している。 Cillium は AWS, GCP, Azure で使える。

9章 セキュリティ用 eBPF

eBPF は不正な活動を検出・禁止するセキュリティツールを作成するためにも使われている。

Linux において、セキュリティイベントを検知するために使われるイベントは次の二つ:

  • システムコール
    • Docker や Kubernetes では BPF を使ってシステムコールを制限する seccomp という機能が使える
    • システムコールの追跡をベースにしたセキュリティツールで有名なものは、CNCF プロジェクトの Falco がある。Falco はセキュリティアラート機能を提供している。
  • LSM インターフェース
    • システムコールのフックは厳密なセキュリティツールの実装には不十分という問題を解決するために、LSM(Linux Security Module) API に eBPF プログラムをアタッチする新しい機能、 BPF LSM がある。
    • セキュリティの観点で好ましくないことが起こりうるシステムコールには、それに対応する LSM のフックが存在するので、eBPF プログラムをシステムコールの入り口の代わりにそちらにアタッチさせればいい。

10章 プログラミング eBPF

eBPF プログラムを書くときの最もシンプルな方法は bpftrace を使うこと。bpftrace を使えば、カーネル空間とユーザー空間の分離を意識せずにコードを書ける。 https://github.com/bpftrace/bpftrace/tree/master?tab=readme-ov-file#one-liners には便利なワンライナーのサンプルがたくさん載っている。

例えば、2章で BCC を使って書いた execve システムコールが呼ばれた回数を uid 別に集計するコードは bpftrace だと次のように書ける。

# 動作中のマシン上で利用できる kprobe の一覧を表示
$ sudo bpftrace -l "*execve*"
...
kprobe:__arm64_compat_sys_execve
kprobe:__arm64_compat_sys_execveat
kprobe:__arm64_sys_execve
kprobe:__arm64_sys_execveat
...

$ sudo bpftrace -e 'kprobe:__arm64_sys_execve { @[uid] = count(); }'
Attaching 1 probe...
^C

@[501]: 2
@[0]: 2

📝 https://github.com/bpftrace/bpftrace/blob/master/docs/tutorial_one_liners.md にあるチュートリアルはわかりやすい。

Vitess: Introduction, New Features and the Vinted User Story

www.youtube.com

関心のあるところだけ抜粋。

Introduction

  • 4 つのキーワード:
    • Keyspace は MySQL のデータベースに相当する。論理的なデータベースを表すので、実際は複数に horizontal sharding された物理データベースで構成される。
    • Shard は Keyspace の subset。Keyspace は複数の Shard で構成できる。1 Shard は 1 primary と 0 ~ n の replica で構成される。
    • VSchema は特定のテーブルをどう horizontal sharding するかを定義する。
    • VIndex は VSchema 内で利用されるもので、MySQL のインデックスに相当する。

Vinted User Story

  • Vinted は 8000 万ユーザーいる中古ファッションマーケット
  • Vitess を導入している。トラフィックは 2.2 Million QPS、38 TB data、5K VStreams
  • Why Vitess?
    • いくつかあるが、Throttling API はその一つ。バッチで db に負荷のかかる処理をするときに、負荷が上がりすぎたら処理を止めるようなコードが書ける。
    • もともとは application 側でシャーディングしていた
    • latency overhead は 2ms くらいで優秀という評価

New Features (v18, v19)

  • Query Serving
    • Basic CTE support for SELECTs
    • MySQL syntax extension: VEXPLAIN
    • DELETES with JOIN
  • Performance and Reliability
    • Connection pooling がより効率的になった。以前はまず pool が full になるまで新規接続を確立していたが、新規接続はコストがかかるのでなるべく行わない挙動に変わった。

Upcoming (v20+)

  • More CTE support

読書メモ: Prometheus実践ガイド: クラウドネイティブな監視システムの構築

どんな本?

Prometheus の概要から実践的な運用ノウハウまでまとまっていて、それを日本語で読めるのがポイント。 また、雑多になるのでここにはメモしてないが PromQL の解説も詳しいので、クエリの書き方に迷ったときの辞書としても便利そう。

Prometheus のアーキテクチャ

Prometheus のシステムを構成するコンポーネントは主に次の四つ:

  • Prometheus Server: Prometheus 本体。Exporter から受け取ったメトリクスを保存してクエリの実行を担う。
  • Exporter: メトリクスの収集。監視したい対象に合わせて必要なものを導入する。種類は非常に多く、200 以上存在する。
  • AlertManager: アラートの発報
  • Grafana: 収集したメトリクスの可視化

これらのコンポーネントは Go 言語で実装されており、シンプルなバイナリで提供されている。

Why Prometheus?

一つ目の理由は、他の監視システムに比べて構成がシンプルであること。 Web サーバーやデータベースといったミドルウェアが必要ないので、どんな環境でも導入が容易になってる。

二つ目の理由は、クラウドネイティブな監視が可能であること。 ここでいうクラウドネイティブな監視とは、監視対象となるシステムが頻繁に変化しても適切に追従でき、システム全体の正常性が常に把握できること を意味してる。 そのために Prometheus は、監視サーバーがクライアントに対してメトリクスを収集しに行く、 Pull 型のメトリクス収集の仕組みを採用している。 同時に、クラウドシステムではサーバーの IP アドレスもクライアントの数も固定されていないので、Prometheus はサービスディスカバリの仕組みも備えている。 サービスディスカバリは、クラウドサービスの API などを利用して対象の情報を能動的に取得・更新して、構成の変化に動的に追従して監視を継続できるようにする仕組みのこと。

三つ目の理由は、Kubernetes 上での監視のデファクトスタンダードになっているため、エコシステムが充実していること。 結果、エクスポーターが豊富に提供されている。サーバーのメトリクス監視には Node exporter、nginx の監視には nginx-prometheus-exporter、MySQL の監視には mysqld_exporter などが利用できる。 また、Kubernetes とともに CNCF にホスティングされている。Kubernetes 上で Prometheus を構築するための Prometheus Operator というプロジェクトもある。

データ構造とクエリ

Prometheus では、すべてのデータがメトリクスとして表現される。メトリクスは Key-Value 構造になっている。 たとえば、localhost:9090 マシンの CPU 使用率が 50% というメトリクスは、cpu_usage{instance="localhost:9090"} が Key で、50 が Value になるデータで表せる。 このとき、cpu_usage は メトリクス名、instance="localhost:9090" はラベルと呼ばれる。

メトリクスを取得するには、PromQL を使ってクエリを実行する。http_requests_total{instance="prometheus"} というクエリを実行すると、http_requests_total というメトリクスのうち、サーバー名に "prometheus" という文字列が含まれる情報を取得できる。

k8s 上のセットアップ

Prometheus の管理の効率化と自動化が可能になる、Prometheus Operator を使うのが良い。 カスタムリソースによって監視ターゲット設定を簡単に定義できる。

監視ターゲットを追加するには ServiceMonitor や PodMonitor を、ルールを追加するには PrometheusRule といった対応する CRD を定義する。

冗長構成

Prometheus の可用性を高めるためには、Prometheus 本体の冗長化と、AlertManager の冗長化をそれぞれ行う必要がある。

Prometheus 本体には冗長化の仕組みは備わっていないので、実現するには同一の設定を持つ Prometheus を二つ構築する方法が取られる。 こうすることで Prometheus はそれぞれ独立してメトリクスを収集する。

AlertManager では単純に複数起動するとそれぞれの AlertManager からアラートが発報されてしまうので、クラスタリングの仕組みが用意されている。 AlertManager を起動する際に別の AlertManager のアドレスをフラグで渡すことで、クラスターが構成される。 Prometheus からはクラスタ化された二つの AlertManager の両方にアラートを通知すればよく、あとは重複排除されて一つだけアラートが発報されるようになる。

ストレージ

Prometheus が利用するストレージには、ローカルストレージリモートストレージの二種類がある。

  • ローカルストレージは、Prometheus を起動しているサーバー上に作成されるストレージである。デフォルトでは、Prometheus のバイナリと同じディレクトリに data ディレクトリを作成し、そこに TSDB(Time Series Database)を構築する。
  • リモートストレージは、外部のストレージソフトウェアへメトリクスを転送する機能のことを指す。リモートストレージを利用するには、Prometheus のリモートストレージ機能に対応したソフトウェアを用意する必要がある。たとえば InfluxDB は標準機能として Prometheus のリモートストレージ API をサポートしており、比較的安定している OSS の時系列データベースである。

短期間かつ小規模であればローカルストレージでも十分実用に耐える。逆に、長期間データを運用する場合、PromQL のパフォーマンス低下などが問題になるのでそのような現象に陥った場合はリモートストレージの利用を検討すべき。

読書メモ: 大規模言語モデル入門

どんな本?

大規模言語モデルの理論と実践についての入門書。 英語だと、大規模言語モデルは Large Language Mode / LLM になる。

前半は大規模言語モデルの理論的な解説。 後半は Hugging Face が開発する Python ライブラリの transformers などを使って、実用的に使える日本語自然言語処理のモデルを作成する。

大規模言語モデルって何?何が新しいの?

大規模言語モデルは要するにニューラルネットワークのこと。 大規模なテキストデータを使って訓練された大規模なパラメーターで構成されたことで、飛躍的に性能が向上した点が新しい。

これを使うと自然言語処理(Natural Language Processing)のさまざまなタスクを解くことができる。 例えば、

  • 文書分類(document classification): ニュース記事などをジャンルに分類する
  • 自然言語推論(natural language inference; NLI): 二つのテキストの論理関係(矛盾してるとか)を予測する
  • 意味的類似度計算(semantic textural similarity; STS): 二つのテキストの意味が似ているかを予測する
  • 固有表現認識(named entity recognition; NER): テキストに含まれる固有表現を抽出する
  • 要約(summarization generation): 比較的長い文章から短い要約を生成する
  • 質問応答(question answering): 質問にコンピューターが回答する
  • 機械翻訳(machine translation)
  • 対話システム(dialogue system)

Transformer とは

Transformer を理解しないとこの本の前半を読むのが厳しかったので、まずはそこだけ他の資料も見つつまとめておく。

Transformer とは 2017 年に Google が提案したニューラルネットワーク。GPT などの今世の中で使われているモデルはこれをベースにしてるので大事。

GPT (Generative Pre-trained Transformer) は Transformer を採用した最初の大規模言語モデルで、2018 年に OpenAI が提案した。Transformer が設計で、GPT がその実装という関係。BERT とか T5 も実装。

ニューラルネットワークを歴史的にみると、Convolutional Neural Network (CNN) は画像などを扱う Vision タスクを解くには優秀で、2012 年以降写真の中の物を特定するとかは進展していた。一方で、言語の解析は遅れていて Recurrent Neural Network (RNN) が使われていた。RNN の問題は、長いテキストを扱えないこと、トレーニングを十分に並列して行えないので遅く結果として大量のデータを扱えないことの二つがあった。Transformer は両方の問題を解消したことで、GPU を同時に動かして、例えば GPT-3 なら45TBのトレーニングデータを使った大規模言語モデルを実現している。

www.youtube.com

どうやって大規模言語モデルを使うの?

本書の後半ではいくつかの自然言語処理を解くために、公開されているモデルとデータセットを transformers から利用する方法が紹介されている。 基本的には、大規模言語モデルがすでにあって、そこにファインチューニングを施して個別タスクに適応させて解く。

transformers を使えばかなりシンプルだが、それでも評価・エラー分析は地道に作業が必要。

本書の最後には ChatGPT API を使った検索システムの実装例も載っている。外部の情報を検索させるために、プロンプトにクエリの関連情報を載せる手法が紹介されている。