TECH PLAY

C言語

イベント

マガジン

技術ブログ

はじめに エンタープライズ 第一本部、2025 Japan AWS Jr. Champions の佐藤悠です。 私は Kubernetes を触る機会が多く、その中でも監視に最近興味を持っています。 監視を実現するセキュリティソリューションの中に Tetragon などが挙げられますが、この監視のベースとなっている技術にeBPFがあります。 このeBPFをEC2 インスタンス の Amazon Linux 上で動かして、その面白さと何が起きているのかを解説します。 はじめに Linuxユーザー空間/カーネル空間 カーネルモジュール 環境 カーネルヘッダのインストール ソースコードの保存 Makefileの作成 モジュールのビルド モジュールのロード モジュールのリスト モジュールのアンロード 感想 ようやく、eBPF eBPF Verifier 呼び出せるカーネル関数はかなり限定的 Stepの制限 メモリアクセスの制限 NULLポインタ参照がないか eBPFでHello World 環境 ファイルの作成 実行 結果 感想 そしてTetragonへ... おわりに Linux ユーザー空間/ カーネル 空間 まずはeBPFの背景知識として Linux のユーザー空間と カーネル 空間に関して理解する必要があります 下図のように Linux にはユーザー空間と カーネル 空間というものが存在しています。 これは一度 カーネル を経由させることで物理デ バイス へのアクセスを制御するのと共に、プロセスが利用するリソースの一元管理を行い配分をする狙いがあります。 そのためユーザーと カーネル の空間を区切り、 システムコール というインターフェースでのみ カーネル へアクセスを強制し、アクセス制御とリソース配分を漏れなく実現しているというわけです。 以下はlsコマンドを実行したときに呼び出されている システムコール です。 さまざまな システムコール を組み合わせて カーネル にアクセスして、コマンドの実行結果を出力していることが理解できますね。 $strace -c ls % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 7 read 0.00 0.000000 0 3 write 0.00 0.000000 0 23 close 0.00 0.000000 0 30 mmap 0.00 0.000000 0 7 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 3 brk 0.00 0.000000 0 2 ioctl 0.00 0.000000 0 4 pread64 0.00 0.000000 0 2 2 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 2 statfs 0.00 0.000000 0 2 1 arch_prctl 0.00 0.000000 0 1 futex 0.00 0.000000 0 2 getdents64 0.00 0.000000 0 1 set_tid_address 0.00 0.000000 0 34 13 openat 0.00 0.000000 0 22 newfstatat 0.00 0.000000 0 1 set_robust_list 0.00 0.000000 0 1 prlimit64 0.00 0.000000 0 1 getrandom 0.00 0.000000 0 1 rseq ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 0 151 18 total ここでプロセスは カーネル を経由するから、アプリケーションの振る舞いは カーネル を見ていれば監視できるのでは?という気づきがあるわけです。 カーネル モジュール じゃあ「 Linux の カーネル にコードの変更をしよう!」と思っても、 Linux の利用されている規模、そもそものコード量などを鑑みると、コミュニティの理解を得て、開発のコストをかけて、アップストリームになるまで待つという気の遠くなる時間がかかる作業となってしまいます。 この問題に対して、 Linux は カーネル の変更・拡張のためにモジュールを受け入れるようにできています。 これが カーネル モジュールの概念です。 では、 カーネル モジュールを作成してみます。 私は C言語 の経験がないので以下の書籍「 Linux Device Drivers, 3rd Edition」のChapter2 p16 The Hello world Moduleの項を参考にしました。 参考: https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch02.html 適当に変更してgogo.cを作成します。 以下のモジュールを カーネル にロードするとgo!go!、アンロードするとbyebye!とログバッファに出力します。 #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE ( "GPL" ); //ロードされたら実行される関数 static int gogo ( void ) { printk (KERN_ALERT "go!go! \n " ); return 0 ; } //アンロードされたら実行される関数 static void byebye ( void ) { printk (KERN_ALERT "byebye! \n " ); } module_init (gogo); module_exit (byebye); これが今回のモジュールです。 ではロード/アンロードしてみます。 環境 アウトバウンド443ポートの通信のみを許可したサブネット内です。 EC2 インスタンス で Amazon linux を起動し、SessionManagerで接続をしています。 sh-5.2$ cat /etc/os-release NAME="Amazon Linux" VERSION="2023" ID="amzn" ID_LIKE="fedora" VERSION_ID="2023" PLATFORM_ID="platform:al2023" PRETTY_NAME="Amazon Linux 2023.8.20250804" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023" HOME_URL="https://aws.amazon.com/linux/amazon-linux-2023/" DOCUMENTATION_URL="https://docs.aws.amazon.com/linux/" SUPPORT_URL="https://aws.amazon.com/premiumsupport/" BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023" VENDOR_NAME="AWS" VENDOR_URL="https://aws.amazon.com/" SUPPORT_END="2029-06-30" カーネル ヘッダのインストール sudo dnf install gcc make kernel-devel kernel-headers Last metadata expiration check: 1:37:35 ago on Sat Oct 18 05:49:32 2025. Package gcc-11.5.0-5.amzn2023.0.4.x86_64 is already installed. Package make-1:4.3-5.amzn2023.0.2.x86_64 is already installed. Package kernel-devel-1:6.1.147-172.259.amzn2023.x86_64 is already installed. Package kernel-headers-1:6.1.147-172.259.amzn2023.x86_64 is already installed. Dependencies resolved. Nothing to do. Complete! ソースコード の保存 #先ほどのファイルをコピペ sh-5.2$ sudo vim gogo.c sh-5.2$ cat gogo.c #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); //ロードされたら実行される関数 static int gogo(void) { printk(KERN_ALERT "go!go!\n"); return 0; } //アンロードされたら実行される関数 static void byebye(void) { printk(KERN_ALERT "byebye!\n"); } module_init(gogo); module_exit(byebye); Makefile の作成 同じ ディレクト リで Makefile を作成 sh-5.2$ sudo vim Makefile sh-5.2$ sudo cat Makefile obj-m += gogo.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean モジュールのビルド make gogo.koが出来ました。 モジュールのロード ※私にはこの末尾のログは紫に光って見えています。 sh-5.2$ sudo insmod gogo.ko sh-5.2$ sudo dmesg | tail ... [ 6689.540149] gogo: loading out-of-tree module taints kernel. [ 6689.540977] gogo: module verification failed: signature and/or required key missing - tainting kernel [ 6689.542802] go!go! モジュールのリスト lsmodで確認しました。gogoモジュールがあります。 sh-5.2$ lsmod Module Size Used by gogo 16384 0 ・・・ モジュールのアンロード sh-5.2$ sudo rmmod gogo sh-5.2$ sudo dmesg | tail ... [ 6689.540149] gogo: loading out-of-tree module taints kernel. [ 6689.540977] gogo: module verification failed: signature and/or required key missing - tainting kernel [ 6689.542802] go!go! [ 6999.290169] byebye! 感想 こんなに手軽に カーネル が拡張できるんですね。 ところで、 カーネル モジュールをロードしたタイミングで以下のログが出ています。 loading out-of-tree module taints kernel. ツリー外(公式外)のモジュールをロードしたため、 カーネル が汚染されました。(翻訳:ChatGPT) まあ自作のモジュールなので、メッセージの通りですね。 ただ、安易なモジュールのロードって自作や第 三者 の提供、問わず カーネル が壊れる可能性がありリスクが大きそうだと思えてきます。 ようやく、eBPF そんな カーネル モジュールの代わりに登場するのが eBPF です。 eBPFは カーネル の ソースコード の改変や、 カーネル モジュールのロードを必要とせずに、安全かつ効率的な カーネル の機能拡張に利用されています。 参考: https://ebpf.io/ja/what-is-ebpf/ この 安全な拡張 は主にeBPF Verifierによって保障されています eBPF Verifier eBPF プログラムはクラッシュしたりシステムに悪影響を及ぼしたりしません。 プログラムは常に実行が完了します(つまり、プログラムは無限ループに陥って処理が実行し続けられることはありません)。 参考: https://ebpf.io/ja/what-is-ebpf/ このeBPF Verifier(検証器)でチェックしている内容は多岐にわたっているため、私が調査した範囲で代表的な項目を以下に列挙します。 呼び出せる カーネル 関数はかなり限定的 基本的には安全性が検証されたヘルパー関数しか使えません。 ヘルパー関数でも関係外の関数を呼ぼうとするとエラーとなります。 ※BPFプログラムタイプの概念 Stepの制限 100万Stepの指示数に以内の時に限り許可することで、CPUを占有しないようにしています メモリアクセスの制限 アクセス可能な範囲外のメモリに触れません。 ※eBPF mapという概念を使用して カーネル とユーザー空間でデータのやり取りをします。 NULLポインタ参照がないか    ( ・∀・)   | | ガッ   と    )    | |     Y /ノ    人      / )    <  >__Λ∩    _/し' //. V`Д´)/   (_フ彡        / これ以上にも、もっとたくさんの検証事があって安全性を保障しています。 ここに踏み込むと、分からないことだらけになってしまうので、「さまざまな検証を経て、 カーネル 空間での実行を許される」としておきます。 eBPFで Hello World ではeBPFプログラムの実装をします。 これは以下の書籍「入門eBPF」2章 p15 eBPFの「 Hello World 」を参考にしています。 参考: https://www.oreilly.co.jp/books/9784814400560/ BCC の Python ライブラリで C言語 で書いたeBPFコードを バイトコード へ変換し、 カーネル 空間にロードさせます。 #!/usr/bin/python from bcc import BPF program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ b = BPF(text=program) syscall = b.get_syscall_fnname( "execve" ) b.attach_kprobe(event=syscall, fn_name= "hello" ) b.trace_print() それぞれ分解しながら解説します。 以下のコードが カーネル にロードされるeBPFコードです。 program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ eBPFのプログラム作成時には、先述のヘルパー関数の1つ、bpf_trace_printk()を用いて出力していることが分かります。 この C言語 を コンパイル して動作するようにします。 b = BPF(text=program) 以上のように書くことで、BPFオブジェクトの生成時に引数に渡すことで BCC に コンパイル させます。 syscall = b.get_syscall_fnname( "execve" ) 次のコードでバイナリのパスを渡して実行する システムコール 「execve()」の関数名を取得します。 b.attach_kprobe(event=syscall, fn_name= "hello" ) ここでexecve()に関数helloをアタッチします。 kprobeとは Linux カーネル の任意の関数や命令の実行時に動的にフックして処理を挿入できる仕組みです このプログラムが カーネル にロードされると、execve()が実行された際には hello World がtrace_pipe(ユーザー空間から取得可能)に出力されます。 b.trace_print() 最後に、トレースデータを Python が実行されている標準出力へ出力します。 実際に動かしてみます。 環境 カーネル モジュールの検証時と同様 ファイルの作成 sh-5.2$ sudo vim hello.py sh-5.2$ cat hello.py #!/usr/bin/python from bcc import BPF program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ b = BPF(text=program) syscall = b.get_syscall_fnname("execve") b.attach_kprobe(event=syscall, fn_name="hello") b.trace_print() 実行 #実行権限の付与 h-5.2$ sudo chmod +x hello.py sh-5.2$ sudo ./hello.py ちなみにeBPFは原則特権ユーザーでしか実行できないので、./hello.pyの実行だと以下のようになります sh-5.2$ ./hello.py bpf: Failed to load program: Operation not permitted Traceback (most recent call last): File "/home/ssm-user/./hello.py", line 13, in <module> b.attach_kprobe(event=syscall, fn_name="hello") File "/usr/lib/python3.9/site-packages/bcc/__init__.py", line 841, in attach_kprobe fn = self.load_func(fn_name, BPF.KPROBE) File "/usr/lib/python3.9/site-packages/bcc/__init__.py", line 523, in load_func raise Exception("Need super-user privileges to run") Exception: Need super-user privileges to run 結果 sh-5.2$ sudo ./hello.py b' <...>-46202 [001] ...21 84764.107466: bpf_trace_printk: Hello World!' b' <...>-46203 [001] ...21 84823.020341: bpf_trace_printk: Hello World!' 何も操作してなくても裏でexecve()って呼ばれているんですね。 別の接続を開いてls等のコマンドを打つと、 Hello World !が同時に出力されるのが分かります。 感想 このように特定の システムコール に紐づけて、監視できると分かりました。 まあ、今回の実験では何かが実行されたという情報しかないですが... そしてTetragonへ... このようにeBPFは カーネル で動作できるので、実行環境で監視の範囲を広げられるように感じます。 ただ当然ですが、毎回監視のためのコードを書くのは辛いものがあります。 そこで、はじめに例にあげたTetragonのようなeBPFをベースにしたサービスを利用するんですね。 ここまでくれば、 Kubernetes ではよくある監視の サイドカー コンテナ利用に対して、eBPFという切り口で監視を提供するときに拡張される範囲がわかってくると思います。 メインコンテナに対して同じボリュームを共有し、 localhost で接続可能な サイドカー コンテナとして実行する監視(例:Datadogの サイドカー インジェクション)に加えて、ノードにDaemonSetsとしてデプロイされることで、 カーネル で実行された実行イベントまで監視の目を広げることができますね。 参考: https://tetragon.io/docs/getting-started/execution/ 本記事ではeBPFの紹介なので、Tetragonの詳細の解説は省略します。 おわりに eBPFではよく蜂を見かけますが、これはeBeeという名前です。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu レビュー: @akutsu.masahiro ( Shodo で執筆されました )
はじめまして!株式会社スタメンでバックエンドエンジニアをしている ちぇる と申します。 この度、2025/9/6(土)に開催された「ながらRuby会議01」に参加しました。 会場は岐阜県の「うかいミュージアム」で、自然に囲まれた素敵な場所でした✨ 近くには金華山(きんかざん)があり、山頂に築かれた岐阜城が見えます🏯 いざ、会場へ! まず驚いたのは、 今回開催された「ながらRuby会議01」でしたが、なんと応募はほぼ満員...! 東海地方にも、これだけ多くのRubyistが集まり、熱量を持って交流できる場があることに感激しました。 ちなみに、nagara.rbは、今回の「ながらRuby会議01」で記念すべき第100回目の開催だそうです。Rubyコミュニティを作ってくれた先人たちに感謝です。 アフターパーティーでは、皆さんと鵜飼に参加しました✨ 以下、実施されたプログラム内容となります! ふむふむ。ジュニアエンジニアの自分にとって、目の前に Ruby のアタラシイ世界が広がっているぞ...🤔 regional.rubykaigi.org この記事では、特に気になったセッションについてご紹介させていただきます。 refinementsのメソッド定義を4000倍速くした話 スピーカー:alpaca さん( @alpaca_tc ) speakerdeck.com 社内で Ruby をアップデートしたところ、どうやらアプリケーションの起動やファイル変更時における反映が遅くなってしまったようです。 そのため、 vernier という gem を使って実際に速度を計測します。 すると、Ruby3.2 と比べて Ruby3.3以降では、 Module#refine を呼び出すタイミングで、なんと実行時間に4000倍以上もの差分が見られたそうです👀( 計測 ) Module#refine とは、簡単にいうと「モンキーパッチを安全に使うための仕組み」です。 例えば、以下のように書くと、String 全体に shout メソッドが定義されてしまいます。 class String def shout self .upcase + " ! " end end " hello " .shout # => "HELLO!" しかし、refine を使うことで、モジュール内での定義にスコープを限定することが可能となります。 module StringRefinement refine String do def shout self .upcase + " ! " end end end using StringRefinement # こちらを実行した場合のみ有効に! " hello " .shout # => "HELLO!" では、この refine がなぜ遅くなってしまったのでしょうか...? ここから原因調査に入ります。 調査を続けていくうちに、rubyリポジトリ内で rb_clear_all_refinement_method_cache(); の処理が追加されたことが判明します。 これが怪しいのでは...?👀 そこで、実際に Revert パッチを当てて確認すると、なんと速度が改善されました🎊 ( 再現 ) つまり、Ruby3.3以降では、refineに関連する callcache の無効化処理が追加され、それによってパフォーマンスリグレッションが起こってしまったのですね。 callcacheとは「同じメソッドを再度呼び出すときに、以前の呼び出し結果を再利用する仕組み」のことです。 PR にあるように、refineに関するキャッシュによるバグが発生してしまったために、このキャッシュを使用しない変更が反映されたんですね。 Rubyでは、メソッドを実行する際、継承チェーンに沿って順にメソッド定義を探します。 しかし、今回の alpacaさんのケースでは、 ObjectSpace になんと、70億のオブジェクトが存在したそうです...😳 そのため、refine 関連のメソッド探索がキャッシュされない場合、相当のオーバーヘッドが発生することは想像できますね...。 そこで alpacaさんは、refine callcache 専用の格納先を用意する方針で実装することにしたそうです。 しかしご存知の通り、Ruby は C言語で書かれています。そのため、上記の方針をC言語で書いて実現する必要があります。 Rubyistにとって、なかなか大変な作業だと考えられます...。 alpacaさんは案の定、C言語特有のGC(ガベージコレクション)によるメモリ管理や、セグメンテーションフォールト(不正なメモリアクセスを行ったときに OS が強制終了させるエラー)等に苦戦します。 しかし、それらにもめげず、ChatGPT や rubyhackchallenge を駆使して、ついにマージまで実現したそうです!🎉( 改善 ) すごいですよね。 該当PR: Optimize callcache invalidation for refinements ここから、alpacaさんは以下の教訓を話されてました。 パフォーマンス改善の流れは同じ(計測 → 再現 → 改善) AIやコミュニティの助けを得て貢献できる また、alpacaさんは以前に RubyKaigi にも参加されており、GC やセグフォについても学んでいました。こうした知識が、実際の現場で活かせることを実感されたそうです。 TUNAGのモノリスとアーキテクチャ また、スポンサーセッションとして、弊社の近藤( @sei_kondo97 )も登壇しました! 弊社は、現在 TUNAG というエンゲージメントプラットフォームを提供させていただいております。TUNAGはモノリス構造のシステムですが、年々サービス内容が肥大化しており、今後はサブサービスやサブアプリケーションに分割し、各ドメインごとに分けて管理していく方針となります。 最近では、「TUNAG本体」から「TUNAGチャット」が分離されました。また、「TUNAG本体」を認可サービスとして利用することで、「TUNAGチャット」へのSSOログインも可能となりました。 最後に 僕は今回、初めて地域Ruby会議に参加しました。そこで感じたのは、 自分が使っている道具の構造を知ることの重要性 です。 現実として、アプリケーション側のエンジニアであれば、道具の使い方さえ分かっていれば、内部構造を知らなくても開発は可能です。高水準言語の強みはまさにここにあります。たとえば、多くの開発者は機械語やアセンブリの仕組みを知らなくてもコードを書けますし、私自身も知りません。 コミッターの方々がRubyの内部をブラックボックス化してくれているからこそ、私たちは便利に開発できているわけです。しかしその一方で、構造を理解していないことで見落としたり、最適な使い方ができない場合もあります。 Rubyの内部やメソッドの実装を知っていれば、パフォーマンスチューニングやデバッグでより効率的で安全な判断が可能になります。つまり、「道具の使い方」と「構造の理解」は表裏一体で、両方あって初めて深い応用力が得られるのではないかと感じました。 今回の「ながらRuby会議01」における学びは、普段はブラックボックスとして使っている部分にも興味を持ち、仕組みを知ることで、開発者としての視野や判断力が広がるということです。これからは、自分のコードだけでなく、その下で動く仕組みや背景にも目を向けていきたいと思います。 株式会社スタメンでは、一緒に働く仲間を募集しています!詳しくはこちらをご覧ください🙌 herp.careers
概要 ベクトル検索データベースを利用するにあたり、 PostgreSQL + pgvector は有力な選択肢の1つに挙げられると思います。 pgvectorがサポートするインデックスアルゴリズムは一般的で信頼性の高いものですが、近似最近傍探索アルゴリズムは近年でも新しい手法の提案が頻繁に行われている分野であり、そういった新手法をPostgreSQL向けに実装した野心的な拡張機能も存在します。 本稿は、新しいベクトル検索用拡張のひとつである VectorChord の紹介と、簡単な性能検証を試みるものです。 PostgreSQLのベクトル検索拡張 ! 本項の内容は筆者の見聞による

動画

書籍