Grafana Meetup Japan #10 参加レポート

先月の 2026/09/07 に Grafana Meetup Japan #10 に参加しました。 私は xk6 で k6 にプロトコルを追加する話をしました。資料はSpeaker Deckに残してます。

全体の感想

当日の事前検証や案内など運営の方々に助けてもらいました。ありがとうございました。

民間ロケット開発を支えるGrafana

今回のメインセッションの民間ロケット開発でのGrafana活用は、単純にロケット開発というだけで楽しくなりました。

宇宙開発では、飛んでいるときはメトリックを絞らないといけない一方、地上の実験では大量に取得したい、という違いがある。 その状況でも、Grafanaを中心に自作しており統一的に扱えて嬉しいという話が面白かったです。 特に、軌道などの可視化も自作プラグインで対応されていて基盤となるGrafanaにダッシュボードとしての成熟を感じました。

地上局など物理施設に関するあらゆるメトリックを集めていて、トラブル時に現地に向かう前に分析できるのが良いと話されていたのも印象に残ってます。 物理作業や雪での移動が伴う場面では、リモートでの詳細な分析の価値がずっと大きい。 オブザーバビリティの重要性をあらためて認識したしIoT的な話で訴求できるところを作れないものかと思ってしまいました。

xk6によるプロトコル追加: テストライフサイクルと文字列変換の注意点

自分の発表はモバイルコアの試験で使っているk6拡張の話。

k6はGrafana Labsの負荷試験ツールです。VUという仮想ユーザー(それぞれが独立したJavaScript(JS)実行環境)が並列で処理を行います。 xk6はk6拡張の開発ツールキットです。

xk6 単体で扱うセッションは初めてということで内容に悩みました。 発表後に聞いたのですが、xk6を知らなかったり使い方を知らなかった方もいたようで意義はあったと思います。

ただk6自体の入門やランタイム内のVUまわりの参照関係・ライフサイクルの説明に時間を割きすぎて、 プロトコル追加や利用や改善の話ができなかったのは反省しています。

発表の概要

詳しくは資料を読んでもらえば良いので、ここでは要点だけ残します。

携帯電話網のコアネットワークの試験はシーケンスが複雑で、またインターネットでは馴染みの薄いプロトコルも多く登場します。 複雑なシナリオをJSで書けるk6は便利です。しかし使いたいプロトコルはサポートされていない。 そこでxk6を使って拡張を提供するとモバイルコアの世界でk6エコシステムで楽ができて嬉しい。 という背景です。

Diameter追加する k6のJS拡張(xk6-diameter)を作っていたので、 xk6でプロトコル追加する際の注意点を紹介することにしました。

プロトコルを追加するときに気を付けることは3点。

  • 共有セッションの管理方法
  • 共有セッションの提供 API
  • JavaScript/Go 境界

xk6 のはじめかた

はじめるのは簡単。拡張の雛形はxk6 newで作れます。

go install go.k6.io/xk6@latest
xk6 new -p example github.com/masu-mi/xk6-example
make   # = xk6 build --with github.com/masu-mi/xk6-example=.
./k6 version

-pで指定した名前(ここではexample)から、次の2 つが決まる。

  • JSのインポートパス: k6/x/example(スクリプトではimport example from 'k6/x/example')
  • Goのパッケージ名: example

モジュールパス (github.com/masu-mi/xk6-example) とは別なので、リポジトリ名にxk6-を付けてもJS 側の名前はすっきりしたままにできる。

共有セッションの管理方法

プロトコルによっては1つのL4セッションで複数のメッセージを流したい場面があるため、プロトコル追加する拡張モジュールでは共有セッションを提供したい。 ところがk6のテストスクリプトは複数のJSランタイムが協調して処理を実行します。 例えば、グローバルスコープで値を評価するランタイムとシナリオ処理を実行するランタイムが別になります。 JS 側では変数の値をどこで初期化できるか? など考える必要があります。 Go側では複数のランタイムで共有するため管理が必要になります。

k6のテストは init → setup → VU code → teardown → handleSummary と5ステージで構成されています。 (テストライフサイクルの公式資料ではhandleSummaryの記載が省かれてます)

ref. Test lifecycle

  • 全ての VU は分離されていて、グローバル変数の状態は共有されない
  • setup / teardown / handleSummary はそれぞれ別環境
    • setup()でグローバル変数に値を入れてもVU codeでは使えない
  • VUから値を取り出すとJSONパースが挟まるので、setup()の返り値でGoオブジェクトは渡せない

そのためDiameterセッションのようにVU間で共有するリソースはグローバルスコープで確保しつつGo側で重複排除などプロトコルに適した管理を提供する必要があります。

そこで xk6-diameter では UUIDv7 を識別子に明示的に与えてテーブル管理するようにしました。 簡略なオブジェクト図は次のセクションで載せておきます。

API 内容
NewConn(attr) 共有セッションの生成; ID: UUIDv7
GetConn(id, attr) 共有セッションの取得; ID: UUIDv7
EnsureConn(name, attr) 共有セッションの確保、名前をキーにユニーク性を担保

共有セッションの提供API

共有セッションを通じて通信機能を提供します。そこでの課題を整理します。

メッセージの振り分け

Diameterはピア間に1 本のセッションを張り、その上で複数メッセージを流します。 1セッションで複数のリクエスト・アンサーが流れるため、モジュールでは受け取ったアンサーの送り先を決める必要があります。 そこでCorrelation IDを決めて返送先をテーブルで管理します。 またシナリオによってはリクエストを受け取ることもあります。その振り分けも設定可能だと便利です。

  • Correlation IDを決めてアンサーの返送先をテーブル管理する
  • プロトコルと用例を考慮してフィルターによるリクエストのコンシューマーを定義する

メトリック集計

k6は性能検証ツールなので各種メトリックを収集したくなります。拡張モジュールとしてもメトリックを提供できます。 メトリック収集用APIはk6全体でのサービスで記録要求をそれぞれのVUから送信します。 この時、共有セッションの生成元VUのコンテキストで送信しようとすると送信できないことがあります。 初めて共有セッションを作成したVUが停止済みでコンテキストがクローズされている場合です。 (多くの場合でinitステージのエフェメラルVUなので送信されません)

かといってcontext.Background()を使うと共有セッションはk6管理外のゴルーチンで動くため k6 のサービス停止後にメトリック送信要求を出そうとしてpanicとなる可能性が残ります。

そこで共有セッションを2階層にわけます。共有セッション自体とVUなど呼び出しコンテキストを紐づけるハンドラです。 メトリックの記録についてはハンドラ側で行うことで呼び出しコンテキストで集計記録を取れるので安全に扱えます。

  • 共有リソース(セッションなど)は ID を与えてテーブル(Pool)で管理
    • sync.Mutex で守る
    • EnsureConn(name, params) のような再利用前提のAPIも準備する
  • アンサーの送り先はCorrelation IDを用いてテーブル管理する
    • 共有リソースからどのIterationに返却するかの判断が必要
    • Diameterならhop-by-hop IDが使える
  • 計測はVUコンテキストと紐づくハンドラ(ConnHdr, 図のClientHdr)で行う
    • metrics.PushIfNotDone() には呼び出し元 VU の VU.Context() が適切

前のセクションと組み合わせて概念的なオブジェクト図にすると下のようになります。

プロトコル関連の共有リソースの参照関係

エンティティ 役割
SharedClient 共有セッションに対応
Pool 共有セッションを管理するテーブル
CorrelationTbl 共有セッションで流れるアンサーの返送先管理に使うテーブル
ClientHdr ハンドラ(JS側のClientに対応); 共有セッションに呼び出しコンテキスト(VU)を組み合わせる処理を実施

JavaScript/Go 境界

DiameterのOctetString AVP (Attribute-Value Pair)には、TBCDのようにバイト列として使われているものがあります。 これを名前に引っ張られてJSの文字列 "\x00\xF1\x10"で書くと、JS(UTF-16)からGo(UTF-8)への変換で壊れてしまいます。

ダメな文字列の使い方

3バイトのつもりが4バイトで送られてしまう。

  • バイト列として使う文字列がないか確認する
  • バイト列・文字列の両方を受け取れる変換層を用意する (引数は interface{} などで受ける)

発表後のアップデート

発表のあとbbsakura/xk6-diameterに次の対応を入れました。

  • Promise 対応
    • シナリオでは非同期なやりとりが必要となるため
  • モジュール内でのエラーをJS側へスロー
    • Go側で勝手に判断せずスクリプト側で対処するため

【Go Conference 2026】参加レポート

こんにちは、BBSakura Networksの篠田です。

9/11に開催されたGo Conference 2026に、普段からGo言語で開発をしているBBSakuraのエンジニア9名が参加しました!

今年度のGoConでは、BBSakuraは昨年に引き続きブロンズスポンサーをしており、早坂(@takemioIO)が「When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go」というテーマで登壇しました。

本ブログでも、BBSakuraの若手メンバーを中心に、セッションやワークショップでの学びや会場の様子をレポートします。

main-visual

目次

印象に残ったセッション

When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go

When Goroutines Are Not Enough: Runtime Locality in High-Throughput Goでは、高スループットなデータ処理におけるgoroutineへの仕事の分担方法と、性能が不足した際の調査・改善の進め方について紹介されました。

speakerdeck.com

まず、goroutineを用いたストリーム処理について、データの特性に応じて並列化の単位を決めるという基本的な設計の考え方が説明されました。 続いて自作のpacket capture tool「bpf-ninja」を題材に、具体的な性能調査と改善の過程が紹介されました。計測によってボトルネックを絞り込み、pprofなどの結果から仮説を立て、非同期プリエンプションやコピー処理、CPU配置などを一つずつ検証することで、スループットを0.54 Mppsから4.287 Mppsへと約8倍まで改善していきます。 最後はpacket captureでの知見をPrometheus Remote Writeなどのストリーム処理へ展開し、「仕事の分担」「受け渡し相手」「処理内容」「実行場所」という4つの観点から性能問題を切り分ける考え方へ一般化されていました。

個々のチューニングテクニックも興味深かったのですが、特に印象に残ったのは一般的な設計の考え方から入りeBPFによるpacket captureという具体例で計測・仮説・検証を行い、最後に再び一般的なストリーム処理へ落とし込むという発表全体の構成です。「まず観測し、仮説を立て、検証する」という性能改善の基本姿勢が一貫しており、低レイヤーな話題を扱いながらもとても理解しやすい発表でした。今後開発で性能問題に直面した際にも、この進め方を意識したいと思いました。(樂野)

synctest時代のhttptest: Go 1.27で変わるHTTPサーバテストの裏側

本セッションについて、Go初心者の視点から、Webサーバーや非同期処理のテストを便利にする2つの仕組みと、最新バージョンでの進化をまとめてみます。私自身、普段はGoを使わない知識はほぼゼロの状態でセッションを聴いたのですが、まずGoには、テストを助けてくれる仕組みとして httptest と synctest というパッケージがあることを知りました。

httptest はHTTP通信のテストを行うためのツールで、単体テストコードが手軽に書けるのが特徴です。テスト用サーバーが裏側でノンブロッキング(メイン処理を止めない形)で起動するため、リクエストの送信テストをスムーズに行えて便利です。

一方の synctest は、時間や並行処理のテストを行うための仕組みです。テスト対象を「バブル」と呼ばれる独立した空間に入れて動かすのが特徴で、バブル内では時間が独立して流れており、処理が完全に停止した状態(durably blocked)を検知できます。

HTTP通信や非同期処理は他言語でも標準的にある仕組みですが、Goにおいてもバージョンが上がるにつれて、これらのテスト機能もより便利に使えるよう進化しています。synctest は Go 1.24 で導入され(Go 1.25で正式化)、テスト中の待ち時間を大幅に短縮できるようになりました。そして Go 1.27 からは NewTestServer が登場し、クライアント/サーバー間通信の擬似実現が可能になりました。これは実際のOSやネットワークを介するのではなく、サーバー側のメモリの中で通信を再現する仕組みです。そして、この NewTestServer と synctest は一緒に使うことで、 「本番と同じURLのまま、非同期なHTTP通信のテストが一瞬で終わる」という真価を発揮することが分かりました。

本セッションを通して、Go 1.27のアップデートにより、手軽でコントロールしやすいテスト実行が可能になったことが理解できました。テストをシンプルに扱えるような進歩は、Go初心者にとっても非常に頼もしい進化だと感じます。(光山)

標準パッケージに uuid が追加された背景から見る Go らしい意思決定

多くのGopherが標準ライブラリにuuidパッケージ欲しいなと思いながら、google/uuidを使用していたことと思います。この発表では、Go 1.27でuuid実装が標準パッケージに追加された経緯や、標準パッケージはどういったポリシーで管理されているのかについて紹介されていました。 個人的な印象ですが、2024年にUUIDv7がRFC9562として標準化されて以降、2025年にPostgreSQL18でUUIDv7の生成関数がサポートされるなど、着実にUUIDv7が普及してきているように感じます。 uuidパッケージのAPIの中でちょっと面白いのが、NilとMaxを関数で実装しているところです。google/uuidではuuid.Nilという変数でNilを実装していたのに対し、標準ライブラリのuuidパッケージでは、Nil()関数が実装されました。 Ask the Speaker で@convto さんにお聞きしたところ、パッケージ変数にすると、勝手に書き換えられる可能性があるという議論があったという経緯を教えていただきました。constの機能が他言語よりも弱いGo側が悪いなと思いつつ、標準ライブラリの設計の意思決定に非常に興味を持つことができました。(石井)

標準ライブラリをどこまで信じるか — 900アプリを支えるプラットフォームへのファイルアップロード導入から学ぶ io・mime・multipart

Goでファイルアップロード機能を実装するにあたって、標準ライブラリをそのまま使うのではなく、実装や仕様まで掘り下げながら、一つひとつ挙動を確認していった発表でした。

特に印象に残ったのは、HEIC対応で標準ライブラリが期待した結果を返さなかった際に、単に別のライブラリに置き換えるのではなく、Goの実装まで読み進め、OSのMIME DBに依存しているところまで調査していた点です。

今はAIに聞くことで、実装方法だけでなく「ここが怪しそう」といった調査のヒントまで得られるようになっています。一方で、実際の挙動が本当に想定どおりなのかを疑い、自分で動かしながら地道に確認していく作業は、やはり必要なんだなと感じました。

自分は普段そこまで開発をすることがなく、コードを書くとしてもAIに頼れば十分な範囲であることが多いです。ただ、今後しっかり開発に関わる機会があれば、AIやライブラリの情報をそのまま信じるのではなく、挙動に疑問を持ち、必要に応じて実装を読んだり、実際に動かして確かめたりする姿勢を意識したいと思いました。(松下)

OpenTelemetry eBPF Instrumentationの舞台裏

「OpenTelemetry eBPF Instrumentationの舞台裏」を聞いてきました。 OBI(OpenTelemetry eBPF Instrumentation) は OpenTelemetry に寄贈されて生まれたツールで、アプリのコードを一切変えずに、カーネル側から eBPF で割り込んで HTTP や gRPC のトレースを取ります。

Go のスタックは動くので出口を取る定番の uretprobe を使うとアプリごと落ちる、Go 1.17 からレジスタ渡しになったので C の規約で読む汎用ツールは黙って別の値を返す、gRPC の内部フィールドの位置はバージョンごとに動くのにずれてもクラッシュせず「それらしく動く」、goroutine をまたぐと context.Context を追えない、という4つの難所が順に紹介され、どれも Go を速く書きやすくする設計の裏返しだと整理されていました。

私はeBPF に興味はあったものの触った経験がなく、この難所4つは会場で聞いている最中はほとんど追いきれませんでした。 冒頭に「uprobe を説明できるか」「Go の可動スタックを知っているか」といった会場アンケートがあったのですが、どの質問にもほとんど手が上がらず、私も同じでした。おかげで基礎ダイジェストから丁寧に始まったのはありがたかったです。それでも印象に残ったのは、defer があるとソース上の return は1つでも機械語の RET は2つになる、という実演です。自分の書いたコードがバイナリとしてどう見えているのかを、あまり意識してこなかったことに気づかされました。

山口さんは今回の内容を基礎から含めた解説本を Zenn で公開しているそうなので(zenn.dev/ymotongpoo/books/go-ebpf-primer)、そちらで補いつつ後で読んでみようと思います。(平山)

Go Context Wallなど

昨年のGoConではSwiss Tablesの仕組みやtesting/synctestなどGoの標準化機能を深掘りする登壇が多かった印象でした。一方、今年度はGoコミュニティでの機能が標準化されるまでの議論の過程や実務でのトラブルシューティングを扱うセッションが多かったように感じました。

聞いた中でもこれらのセッションが、コミュニティ活動や機能の標準化に触れていました。

コミュニケーションスペースにもさまざまな企画が用意されていました。なかでも、Go Context Wallや各スポンサーブースで展示されているアンケートから、最近のGoの機能やトレンドを知れるのがとてもよかったです。

あとは、他の方の感想にもありましたが、最近だとhttptest.NewTestServer がとても便利とのこと。(外山)

go-context-wall
Go Context Wall

印象に残ったワークショップ

TDDを通じてGoらしいテストを体験しよう!

「TDDを通じてGoらしいテストを体験しよう!」というワークショップに参加したので、その感想を書きます。

TDDでは、最初に実装をすべて考えるのではなく、まずTodoリストに全体像を書き出します。ただし、そこに書いたことは一旦忘れて、対応する項目を1つだけ選ぶのがポイントだと教わりました。そして、実現したい振る舞いをテストで定義し、まだ実装されていないためテストが失敗する状態(Red)を確認します。その後、最短の実装でテストを通し(Green)、最後にコードを整理する(Refactor)という流れです。Redは意図的にエラーを作ることが目的ではなく、テストによって仕様と実装の差分を確認するための段階です。

今回教わった流れを、FizzBuzzを例に図にまとめました。(図はOpenAIの画像生成ツールを利用して作成)

tdd-cycle
Test-Driven DevelopmentとTable-Driven Testsのイメージ

これまでテストは実装の後に書くことが多かったので、先にテストを書いてから実装するのが新鮮でした。テストを実行するとコードの動きがすぐに分かるので、楽しみながら取り組めたと思います。

題材も初学者が取り組みやすいシンプルなコードで、運営のみなさんのサポートも手厚かったです。参加者全員がいい感じに手を動かせていた印象でした。

2つ目の演習であるFizzBuzzの実装では、ペアプログラミングにも取り組みました。ペアプロは1台のマシン・1つのキーボードを共有し、「ドライバー」(実装する人)と「ナビゲーター」(一歩引いて設計や間違いに気づく人)を交代しながら進めるプラクティスです。ペアの方とキーボード配列などが違っていたため、ドライバーを交代するたびに少し戸惑わせてしまう場面もありましたが、TDDのサイクルを一人で回すのと違い、Redになった瞬間や実装方針をその場で相談しながら進められるのは新鮮で面白かったです。

また、FizzBuzzの実装ではテーブル駆動テストも体験しました。テーブル駆動テストは、入力値と期待値を表にまとめるための書き方です。ただ表に並べるだけではなく、「3の倍数ならFizzを返す」「5の倍数ならBuzzを返す」といった、確認したい振る舞いが分かる名前を付けて管理します。テストケースを追加しやすく、何を確認しているのかも分かりやすくなります。Goらしいテストの書き方を実際に試せたのもよかったです。

実際には、次のようにテストケースをスライスにまとめ、t.Runでケースごとにテストを実行します。

tests := []struct {
    name     string
    input    int
    expected string
}{
    {"3の倍数ならFizz", 3, "Fizz"},
    {"5の倍数ならBuzz", 5, "Buzz"},
    {"3と5の倍数ならFizzBuzz", 15, "FizzBuzz"},
    {"どちらの倍数でもなければ数字", 2, "2"},
}

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        got := fizzbuzz(tt.input)
        if got != tt.expected {
            t.Errorf("got %q, want %q", got, tt.expected)
        }
    })
}

TDDを実際に体験してみて、テストを先に書くことで実装のゴールが明確になり、小さな単位で動作を確認しながら進められることが分かりました。

楽しく参加できたので、今後の開発でも小さな機能からTDDを試してみたいと思います。(篠田)

TinyGoで遊んでみよう!TinyGo Keeb Workshop

本ワークショップでは、TinyGo(組み込み向けGo言語)を使って、キーボードや基板へのプログラム書き込みを体験しました。

会場では4種類のハードウェアから選ぶことができ、私は「zero-kb02」という小型キーボードを選択。具体的な導入や動かし方は、GitHubのREADMEを読みながら進めていきました。

zero-kb02
zero-kb02

私自身、Goの知識はほぼゼロで、最初はパソコンに TinyGo をインストールする段階からうまく環境が整わず焦ってしまいました。ですが、スタッフの方がとても親切・丁寧に進め方を教えてくださり、つまずきを1つずつ解消して進めることができました。事前知識がない状態だったため、こうした手厚いサポートは本当にありがたかったです。

最終的には、無事に TinyGo を導入し、キーボードへ送るGoプログラムのコンパイルから接続・書き込みまでの一連の流れをやり切ることができました。

体験を通して感じたこと

  • 画面上の処理だけでなく、キーボードという「ハードウェア」を通して自分の実装成果を確認できる体験がとても新鮮だった。
  • TinyGoの知識がなくても、丁寧なサポートと用意された実行手順に沿って進めることで、組み込み開発を体験することができた。

最初は難しそうな印象でしたが、実際に自分の手で触って動かしてみると初心者でも楽しく学びきることができ、非常に貴重な体験となりました!(光山)

Let’s Play Go: The Card Game

本ワークショップで、Go言語をテーマにしたカードゲームを体験してきました。想像以上に白熱して楽しかったです!

ゲームは2〜4人対戦で、ツール・バグ・コードカードを駆使して全6枚の「機能カード」完成を目指す、ソフトウェア開発を再現したルールです。最終的にカードの獲得ポイントが最も高い人が勝者となります。会場では各テーブル4人で予選を行い、1位通過者による決勝戦が開催されました。優勝者には賞品としてこの「Goカードゲーム」が贈られるそうです(私は残念ながら予選敗退でした…)。

Goの詳しい知識がなくても気軽に遊べるうえ、コードカードを並べると実際のプログラムが完成する工夫がとても面白かったです。ゲームを通して参加者のGo経験レベルを問わず自然と会話が弾み、Go開発への良い入口になる素晴らしいツールだと感じました。

オンラインでも遊べるそうなので、気になる方はぜひプレイしてみてください!(光山)

Goカードゲーム 公式ガイドブックへのQRコード
Goカードゲーム 公式ガイドブック

まとめ

当日は700名を超えるGopherが集まり、セッションやスポンサーブース、ワークショップなど、さまざまな企画が行われていました。

セッションで新しい知識を得られたのはもちろん、ワークショップで実際に手を動かしたり、会場で他の参加者やスタッフの方と交流したりできたことで、Goをより身近に感じられる一日になりました。

今回学んだことを今後の開発にも活かしつつ、来年もまたGo Conferenceに参加したいと思います。

3GPP MCP Server を作った話

こんにちは。 BBSakura Networks CTO の日下部(@higebu)です。

3GPP 仕様に基づいて調査や実装をするとき、ChatGPT や Claude Code のように大規模言語モデル(LLM)を使うツールに頼る場面が増えています。 一方、仕様本文を参照できないまま回答させると、もっともらしい誤答や、仕様を誤って解釈したコードが生成されることがあります。 そのたびに人間が仕様書を探し直すことになります。

では仕様書そのものを読ませればよいかというと、そう簡単でもありません。 エージェントに任せれば、Python で Word ファイルからテキストを抜き出して該当箇所を探すか、それらしい解説を Web から拾ってくるか、ETSI や ARIB の PDF を取りに行くか、といったあたりに落ち着きます。 どれも仕様本文をそのまま扱えず、表や図が落ちるか、一次情報から離れます。

そこで、LLM が 3GPP 仕様書の本文や図表を必要な単位で取得できる Model Context Protocol(MCP)サーバを作りました。 コードは全部 Claude Code に書かせています。

リポジトリは higebu/3gpp-mcp です。

何をするツールか

3gpp-mcp は、LLM クライアントから 3GPP 仕様書を検索して参照するための MCP サーバです。 主に次のツールを公開しています。

ツール 用途
list_specs シリーズで絞り込める仕様書一覧の取得
list_versions ある仕様書に存在するバージョンと、それぞれをどこから読めるかの取得
search SQLite の全文検索機能である FTS5 を使った検索
get_toc 仕様書の目次取得
get_section ページネーションに対応したセクション本文の取得
compare_versions 2 つのバージョン間で変わったセクションの一覧と、セクション単位の差分の取得
get_asn1 型や IE の名前から ASN.1 定義と、それを定義しているセクションの取得
get_references 3GPP 仕様書と RFC に対する outgoing / incoming の参照取得
list_images / get_image 仕様書中の図を一覧取得し、必要な図を LLM から参照できる形で取得
list_openapi / get_openapi パスやスキーマで絞り込める OpenAPI 定義の取得
search_openapi OpenAPI 定義に対する全文検索

中でも get_references は、あるセクションから参照されている別の 3GPP 仕様書や RFC 番号をまとめて確認できるので、as defined in clause ... が続く 3GPP 仕様書を読むときに便利です。 3GPP 仕様書への参照を見つけたあと、その参照先のセクション本文まで続けて取得できます。

本文以外の要素も、LLM が扱いやすい形式に変換しています。 表は Markdown テーブルにすると列結合の構造が崩れるため、HTML の table として保存しています。 --convert-image を付けてビルドすると EMF/WMF 形式の図が PNG に変換して保存され、get_image で LLM から見られる形になります。 数式は LaTeX に、ASN.1 や Diameter、SIP/SDP などの記述はコードブロックとしています。

セットアップ

使い方は大きく分けて 2 通りあります。 DB を同梱した Docker イメージを自分でビルドする方法と、コマンドをインストールしてローカルに DB を作る方法です。 どちらも 3GPP 仕様書を取り込んで SQLite の DB を作るところは共通で、初回はその取り込みに時間がかかります。

Docker イメージをビルドする

Dockerfile はマルチステージ構成で、ビルド時に DB の作成まで済ませた自己完結型のイメージを作ります。 RELEASE build-arg の既定は latest で、全リリースを通じた各仕様書の最新バージョンを同梱します。 リリースで絞るなら --build-arg MAX_RELEASE=19 を使います。

# 全リリースの最新版を同梱(既定)
docker build -t 3gpp-mcp:latest .

# ...Rel-19 までに抑える
docker build --build-arg MAX_RELEASE=19 -t 3gpp-mcp:max-rel19 .

おすすめは、MCP クライアントから毎回 docker run で起動するのではなく、Streamable HTTP transport と Web ビューアーを有効にしたコンテナを常駐させておく方法です。

docker run -d --name 3gpp-mcp-server \
  -p 127.0.0.1:8080:8080 \
  --restart unless-stopped \
  3gpp-mcp:latest \
  serve --db /3gpp.db --transport http --addr :8080 --web

起動すると、Web ビューアーは http://localhost:8080/、MCP エンドポイントは http://localhost:8080/mcp/ で利用できます。 --web を付けている場合は MCP エンドポイントが /mcp/ になる点に注意してください。 この例では 127.0.0.1 にバインドし、同じホストからの接続だけを受け付けます。 別のホストから接続させる場合は、TLS を終端したうえで、--bearer-token または THREEGPP_MCP_BEARER_TOKEN による認証を有効にしてください。

たとえば Claude Code なら、次のように Streamable HTTP transport で登録できます。

claude mcp add --scope user --transport http 3gpp http://localhost:8080/mcp/

接続先に http://localhost:8080/mcp/ を設定すれば、他の MCP クライアントでも同じように使えます。 コンテナを常駐させておくと、クライアントを開くたびに仕様 DB を含むサーバの起動処理を待たなくてよくなります。

このイメージは scratch ベースで書き込み先を持たないため、後述する DB にないバージョンのオンデマンド取得は既定では動きません。 使う場合は書き込み可能なボリュームをマウントし、HOME(または XDG_CACHE_HOME)と TMPDIR をその中に向けます。

systemd で常駐させる場合の unit ファイルや、Cloud Run にデプロイする場合の設定は、リポジトリの examples/systemd/ と cloudbuild.yaml にあります。

コマンドをインストールする

コマンドは Homebrew(brew install higebu/tap/3gpp-mcp)かリリースのバイナリで入ります。 go install github.com/higebu/3gpp-mcp/cmd/3gpp-mcp@latest でも入りますが、その場合は Go 1.26 以上が必要です。 また、次の例では .doc と EMF/WMF も変換するため、LibreOffice を先にインストールしておきます。

DB は build コマンドで作ります。 --latest を付けると、全リリースを通じた各仕様書の最新バージョンを取り込みます。 リリースで絞るなら --max-release を使います。

mkdir -p data

# 全リリースの最新版を取り込む
3gpp-mcp build --latest --db data/3gpp.db --convert-doc --convert-image

# ...Rel-19 までに抑える
3gpp-mcp build --max-release 19 --db data/3gpp.db --convert-doc --convert-image

--release でそのリリースだけに限ることもできますが、そのリリースに版が無い仕様書は丸ごと落ちます。 参照先が欠けて get_references からの辿り読みが途切れるので、上限を決めたいだけなら --max-release の方が安全です。

取り込んだあと、DB にある仕様書を最新バージョンに追従させるには update コマンドを使います。 --max-release で絞った DB には、update にも同じ上限を渡します。渡さないと上限が外れます。

3gpp-mcp update --db data/3gpp.db --convert-doc --convert-image

すでに手元に docx がある場合は、ダウンロードを飛ばして import と import-dir で取り込めます。

3gpp-mcp import --db data/3gpp.db path/to/spec.docx
3gpp-mcp import-dir --db data/3gpp.db ./specs

過去のバージョンを読む

build コマンドが DB に入れるのは、仕様書ごとに 1 バージョンだけです。 そのため、それ以外のバージョンを読むときは get_section や get_toc に version を渡します。 list_images と get_image も同じで、そのバージョンの図を見られます。 どのバージョンが存在するかは list_versions で確認できます。 version には、ドット区切りの 15.8.0 のほか、Rel-15 のようなリリース指定(そのリリースの最新バージョンを選びます)や latest を渡せます。

DB にないバージョンは、初回参照時に 3GPP 公式 FTP サーバからダウンロードして変換し、キャッシュします。 大きい仕様書だと数分かかり、それまでは変換中であることが返ります。

バージョン間で何が変わったのかを見るには compare_versions を使います。

ただし、search と get_references が見るのは DB に入っているバージョンだけで、キャッシュしたバージョンは対象になりません。

もう一つ、参照先のセクションが見つからないことがあります。 仕様書ごとにバージョンが違ううえ、参照している側の本文が参照先のセクション番号の振り直しに追随していないこともあるためです。 たとえば TS 23.501 v20.2.0 の 4.2.8.1 が参照する TS 23.502 の 4.12.2a は、同じ v20.2.0 の TS 23.502 では 4.12a.2 に振り直されていて存在しません。 このとき Web ビューアーは、リンク先をセクションではなく仕様書のトップにして、解決できなかったことが分かる印を付けます。

search の結果だけで答えてしまうとき

使っているモデルによっては search の結果に含まれる周辺テキストだけを見て、本文を読まずに雑な回答を返すことがあります。 その場合は、回答前に get_section で該当セクションの本文を読むように指示すると、仕様どおりの回答が返りやすくなります。

search の結果だけを基に回答してしまったため、get_section で本文を読むように指示した例
search の結果だけを基に回答してしまったため、get_section で本文を読むように指示した例

Web ビューアーで仕様書を確認する

--web を付けて起動すると、MCP エンドポイントと同じサーバで Web ビューアーを利用できます。

3gpp-mcp serve --db data/3gpp.db --transport http --addr :8080 --web
# Web viewer:   http://localhost:8080/
# MCP エンドポイント: http://localhost:8080/mcp/

TS 23.501 のセクション表示。目次のサイドバーと、docx から取り出した図が表示される
TS 23.501 のセクション表示。目次のサイドバーと、docx から取り出した図が表示される

Web ビューアーには、次の機能があります。

  • 仕様書一覧(シリーズやキーワードでのフィルタ、バージョンとリリースの表示)
  • どのページからも使えるナビゲーションバーの検索フォーム
  • 目次サイドバー付きのセクションビューアー(前後のセクションへの移動は左右の矢印キーでも可能)
  • FTS5 クエリ構文に対応した全文検索
  • バージョン一覧(MCP ツールと同じく、DB にないバージョンはオンデマンドで取得)
  • バージョン比較(変更されたセクションの一覧と、セクション単位の差分)
  • 埋め込み画像表示
  • 相互参照のリンク
  • 数式表示
  • ASN.1、Diameter、SIP、RTSP、SDP、XML、OpenAPI 定義の構文ハイライト表示(配色は設定から切り替え可能)
  • ダークモード / レスポンシブ

自分で読みたいときも、zip を展開して Word で開くより手軽です。

3gpp-mcp を付けない場合と比べる

NGAP の 2 つの型の違いを聞いてみます。 どちらも PDU セッションのセットアップ要求を表す型です。 3GPP 仕様書には、こうした紛らわしい名前が多く出てきます。

PDUSessionResourceSetupItemSUReq と PDUSessionResourceSetupItemCxtReq について、トップレベルのフィールドをそれぞれ挙げ、2 つの違いを教えてください。定義しているセクション番号も書いてください。

正解は TS 38.413 v19.3.0 のセクション 9.4.5 にあります。 フィールドは両方 5 つで、違いは 2 番目の名前が pDUSessionNAS-PDU か nAS-PDU かだけです。

ツールを与えない場合

ツールを与えない場合の前半。定義箇所を 9.3.4.1 とし、フィールドを 4 つ挙げている
ツールを与えない場合の前半。定義箇所を 9.3.4.1 とし、フィールドを 4 つ挙げている

ツールを与えない場合の後半。「構造上の違いはありません」と回答している
ツールを与えない場合の後半。「構造上の違いはありません」と回答している

フィールドを 4 つに減らし、定義しているセクションを 9.3.4.1 と答えます。 そのうえで「構造上の違いはありません」と結論します。

Web 検索とシェルを与えた場合

Web 検索とシェルを与えた場合の前半。Wireshark の ASN.1 定義を取得している
Web 検索とシェルを与えた場合の前半。Wireshark の ASN.1 定義を取得している

Web 検索とシェルを与えた場合の後半。違いは当てるが、定義セクションを間違えている
Web 検索とシェルを与えた場合の後半。違いは当てるが、定義セクションを間違えている

5 分かけて、違いを言い当てます。 ただし、拡張 IE をトップレベルのフィールドに混ぜ、定義されているセクションは「専用の番号は割り当てられていない」と答えます。 読んでいたのは仕様書ではなく、Wireshark の dissector の ASN.1 定義でした。

3gpp-mcp を付けた場合

3gpp-mcp を付けた場合。15 秒でセクション 9.4.5 を参照している
3gpp-mcp を付けた場合。15 秒でセクション 9.4.5 を参照している

15 秒で、9.4.5 の ASN.1 定義そのものを参照して答えます。

どれも 1 回ずつ実行した結果です。 Claude Code は v2.1.267、モデルは Sonnet 5、thinking は high です。 LLM の回答は毎回変わるので、同じ条件でも別の結果になることがあります。 まとまった測定結果は BENCHMARK.md にあります。

仕様書を調べる作業はどう変わったか

以前は、3GPP の細かい話になると、LLM の回答をほとんど調査や実装に使えませんでした。 3gpp-mcp を使うと、仕様書の本文、目次、参照関係、図、OpenAPI 定義を必要な単位で取得し、LLM が回答に使った仕様本文や関連仕様を人間も確認できます。 最終確認は必要ですが、最初から一次情報を参照させることで、仕様に沿った回答やコードを得られる場面が増えました。 3GPP 仕様を LLM で調べる機会がある方は、ぜひ試してみてください。

参考

OCX Mobile Accessを使ってMicrosoft 365に閉域接続してみた

はじめに

こんにちは!BBSakura Networks株式会社(以下、BBSakura)でクラウド接続を担当している松下です。

今回は、Open Connectivity eXchange(以下、OCX)で提供している OCX Mobile Access と XaaS Connection(Microsoft Azure Peering Service / MAPS) を組み合わせて、SIM を入れた端末から Microsoft 365 (以下、M365)へ閉域接続できるか検証してみました。

MAPS(Microsoft Azure Peering Service) とは、Microsoft が提供するサービスで、インターネットを経由せずに Microsoft のネットワークへ直接接続できる仕組みです。M365 等の Microsoft クラウドサービスへの通信を最適化することができます。

また、設定は OCX 側で完結し、Microsoft 側で追加の設定を行う必要がありません。これにより、手軽に M365 への閉域接続環境を構築できます。

本記事では、構成概要と実際の設定手順に加え、OCX のトラフィックレポート機能を使って 、M365 向けのトラフィックが MAPS 経由で流れていることを確認するところまでご紹介します。

構成

今回の構成はこんな感じです。

構成図
構成図

MAPS のトラフィックの流れとしては、SIM 端末 → VMG → OCX-Router(V1) → XaaS Connection という経路になります。

MAPS は、M365 等の Microsoft クラウドサービス向け通信の最適化を目的としたサービスです。 そのため、M365 関連の主要トラフィックについては MAPS 経由で閉域接続可能ですが、すべての Microsoft 宛通信を対象としているわけではありません。そのため、MAPS 非対応のトラフィックは Internet Gateway 経由でインターネットに出すようにしています。

設定手順

OCX-Router(V1) と OCX Mobile Access 側はすでに作成済みの前提です。OCX Mobile Access 側の設定は、こちらをご参照ください。

ここでは追加で必要な XaaS Connection(MAPS)、Internet Gateway、Router Interface、BGP Parameter の 4 つを作成していきます。

XaaS Connection(MAPS)の作成

まず MAPS への出口となる XaaS Connection を作成します。

XaaS Connection(MAPS)の作成

Internet Gateway の作成

MAPS 非対応のトラフィック用に Internet Gateway も作成します。

Internet Gateway の作成

Router Interface の作成

OCX-Router と各リソースを結ぶ Interface(RouterConnection)を作成します。

Router Interface の作成

BGP Parameter の設定

OCX-Router(V1) の BGP Parameter を設定します。実際には primary・secondary の OCX-Router(V1) の interface と、 Internet Gateway、XaaS Connection のすべてに対して設定が必要ですが、ここでは XaaS Connection へむけた primary の Router Interface への設定を例として紹介します。

BGP Parameter の設定

あとは構成図に従って VC の作成、アタッチをしていただくことで、SIM 端末 から M365 に閉域接続するネットワークの部分は完成です。

この構成により、SIM 端末から M365 への通信はインターネットを経由せず、閉域網を通じて Microsoft ネットワークへ接続されます。

トラフィックレポートで動作確認する

OCX Mobile Access の SIM を入れた端末から OneDrive に 1GB のファイルをアップロードしました。

OCX ポータルのトラフィックレポートで、OCX-Router(V1) の interface のトラフィックも見てみます。

XaaS Connection(MAPS)側のトラフィック
XaaS Connection(MAPS)側のトラフィック

アップロードのタイミングで XaaS Connection(MAPS)側のトラフィックが増えているのが確認できました。Internet Gateway 側には ほとんどトラフィックは流れておらず、想定通りの動作です。

Internet Gateway  側のトラフィック
Internet Gateway 側のトラフィック

まとめ

専用線なしで、M365 への閉域接続が実現できました。OCX-Router(V1) に BGP 設定を集約できるので、管理もシンプルにまとまります。

構築にあたっての注意点や詳細な設定についてはOCXのネットワークデザインパターンも参照してください。

Docker上でのGitHub Actions self-hosted runnerのデプロイ

はじめまして、BBSakura Networks株式会社でアルバイトとしてお世話になっておりましたnyahahanohaと申します。 高専の卒業と新しい会社への入社によりアルバイトを退職することとなりました。 最後に業務の一部を技術ブログとして紹介する機会を頂いたため、執筆しております。

はじめに

GitHub Actionsを利用してCD(Continuous Delivery)を構築する際、セキュリティの観点から外部にポートを公開したくないためにself-hosted runnerを利用する方や企業は多いのではないでしょうか。

この場合、self-hosted runnerをVM内に直接デプロイしてしまうと、その環境に対して手作業による操作が増えやすくなります。 また、手作業で入れたツールや依存関係が累積し、別環境への移行、障害時の復旧、同一構成の横展開の際に再現性の低さが問題になります。

そこで本記事では、私たちが取ったアプローチとしてDocker上にself-hosted runnerを構築する方法を紹介します。 これにより可搬性と再現性を高められます。

準備

この方法でself-hosted runnerを利用するにはGitHub Appが必要です。 下記の権限をつけたClientIDと秘密鍵を取得してください。

  • "Administration" repository permissions (write)
  • "Self-hosted runners" organization permissions (write)

これはコンテナ内で自動でself-hosted runnerの追加、削除を行うために必要です。

構成

Dockerコンテナは下記の構成で構築します。

/ dir
|- Dockerfile
|- docker-compose.yaml
|- entry-point.sh
|- private-key.pem

Dockerfile

Dockerfileそのものはとても単純です。 aptで必要なツール群のインストール、runnerユーザの作成、self-hosted runnerのインストールという三つの処理で構成されています。

FROM ubuntu:24.04

# ツール群のインストール
RUN apt update && \
    apt install -y --no-install-recommends \
    software-properties-common \
        ...
    rm -rf /var/lib/apt/lists/*

# ユーザの作成
RUN useradd -m -u 1001 -s /bin/bash runner \
 && mkdir -p /actions-runner \
 && chown -R runner:runner /actions-runner
USER runner

# self-hosted runnerのインストール
WORKDIR /actions-runner
RUN curl -o actions-runner-linux-x64-2.329.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.329.0/actions-runner-linux-x64-2.329.0.tar.gz
RUN echo "194f1e1e4bd02f80b7e9633fc546084d8d4e19f3928a324d512ea53430102e1d  actions-runner-linux-x64-2.329.0.tar.gz" | shasum -a 256 -c
RUN tar xzf ./actions-runner-linux-x64-2.329.0.tar.gz

ENTRYPOINT ["/entry-point.sh"]

ユーザ名は必ずrunnerとして作成してください。 そうでないとself-hosted runnerが動きません。

entry-point.sh

entry-pointは少し複雑です。 registration-tokenの取得、self-hosted runnerの設定、起動、停止を行います。

環境変数取得

処理の前に必要な変数を読み込みます。 今回はコンテナなので環境変数を用いて変数を読み込みました。

#!/bin/bash

set -euo pipefail

REQUIRED_VARS=("GITHUB_CLIENT_ID" "GITHUB_ORG" "GITHUB_REPO" "RUNNER_LABEL" "RUNNER_NAME")
for var in "${REQUIRED_VARS[@]}"; do
  if [ -z "${!var:-}" ]; then
    echo "Error: Environment value $var is not set." >&2
    exit 1
  fi
done

if [[ ! -f /private-key.pem ]]; then
  echo "Error: /private-key.pem does not exist." >&2
  exit 1
fi
if [[ ! -r /private-key.pem ]]; then
  echo "Error: /private-key.pem is not readable." >&2
  exit 1
fi

環境変数としては下記の5つを定義しています。

  • GITHUB_CLIENT_ID: GitHub AppsのクライアントID
  • GITHUB_ORG: GitHubの組織名
  • GITHUB_REPO: GitHubのレポジトリ名
  • RUNNER_LABEL: self-hosted runnerに付与されるラベル
  • RUNNER_NAME: self-hosted runnerの名前

また、秘密鍵が必要なので存在と読み取り権限を確認しています。

registration-tokenの取得

registration-tokenとはself-hosted runnerの起動や停止に使われるtokenのことです。

# 起動時
./config.sh --unattended --token $REGISTRATION_TOKEN

# 停止時
./config.sh remove --token $REGISTRATION_TOKEN

このトークンは自動で取得しない場合はGitHubのrepositoryページから取得することができます。

自動で取得する場合は、下記の流れで取得する必要があります。

1. GitHubAppからJWTセッションを取得

docs.github.com

JWTセッションの取得はドキュメントの通りなので割愛します。 取得したjwtは変数として置いておきます。

2. JWTセッションからAccess Token URL Listを取得します。

docs.github.com

JWTからAccess Token URL Listを取得するのは下記のコマンドです。

  access_token_url=$(curl -sL \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://api.github.com/app/installations | \
    jq -r ".[0].access_tokens_url" )

ドキュメントに示されているコマンドに-H "Authorization: Bearer ${jwt}"を追加するとjwtを利用して取得することが可能です。 ここではjqでaccess_tokens_urlを取得し、access_token_urlという変数に置いています。

3. Access Token URL ListからAccess Tokenを取得

次にAccess Token URLからAccess Tokenを取得するのは下記のコマンドです。

  access_token=$(curl -sL \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    "$access_token_url" | \
    jq -r ".token")

これについてはドキュメントを見つけることができませんでしたが、先ほど取得したurlからtokenを得ることが可能でした。

4. Access TokenからRegistration Tokenを取得

docs.github.com

最後にAccess TokenからRegistration Tokenを取得するのは下記のコマンドです。

  curl -sL \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${access_token}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://api.github.com/repos/${GITHUB_ORG}/${GITHUB_REPO}/actions/runners/registration-token | \
    jq -r ".token"

ここではjwtを使わずにaccess_tokenを使います。 urlには各自のorganizationとrepoを含める必要があります。 jqでtokenを取り出すことでregistration_tokenを取得できます。

これでself-hosted runnerの起動、停止に必要なregistration-tokenの取得が可能になりました。 registration-tokenは1時間で期限切れとなります。 そのため、これらのコマンド群は起動、停止するたびに実行し、registration-tokenを取得することが重要です。 一度取得したものを使いまわすことは推奨されません。ご注意ください。

これらを関数として置くと下記のようになります。

pem=$(cat /private-key.pem)

b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }

get_registration_token() {
  # Get JWT Token (https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-bash-to-generate-a-jwt)
  now=$(date +%s)
  iat=$((${now} - 60)) # Issues 60 seconds in the past
  exp=$((${now} + 600)) # Expires 10 minutes in the future

  header_json='{
      "typ":"JWT",
      "alg":"RS256"
  }'
  # Header encode
  header=$( echo -n "${header_json}" | b64enc )

  payload_json="{
      \"iat\":${iat},
      \"exp\":${exp},
      \"iss\":\"${GITHUB_CLIENT_ID}\"
  }"
  # Payload encode
  payload=$( echo -n "${payload_json}" | b64enc )

  # Signature
  header_payload="${header}"."${payload}"
  signature=$(
      openssl dgst -sha256 -sign <(echo -n "${pem}") \
      <(echo -n "${header_payload}") | b64enc
  )
  # Create JWT
  jwt="${header_payload}"."${signature}"

  # Get the access token url (https://docs.github.com/ja/rest/apps/installations?apiVersion=2022-11-28#list-app-installations-accessible-to-the-user-access-token)
  access_token_url=$(curl -sL \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://api.github.com/app/installations | \
    jq -r ".[0].access_tokens_url" )

  # Get Access token
  access_token=$(curl -sL \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    "$access_token_url" | \
    jq -r ".token")

  # Get Registration token (https://docs.github.com/ja/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-a-repository)
  curl -sL \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${access_token}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://api.github.com/repos/${GITHUB_ORG}/${GITHUB_REPO}/actions/runners/registration-token | \
    jq -r ".token"
}

self-hosted runnerの設定、起動、停止処理

先に環境変数で、GITHUB_CLIENT_ID、GITHUB_ORG、GITHUB_REPO、RUNNER_LABEL、RUNNER_NAME辺りは指定しておきましょう。

設定するのは下記のコマンドです。

./config.sh --unattended \
  --token $(get_registration_token) \
  --url https://github.com/${GITHUB_ORG}/${GITHUB_REPO} \
  --labels ${RUNNER_LABEL} \
  --name ${RUNNER_NAME}

get_registration_tokenは変数ではなく、先ほど実装したregistration-tokenを取得する関数です。 これを--token flagに指定することでregistration-tokenが利用できます。 --unattended flagをつけることで通常のインタラクティブモードを無効化することが可能です。 --url flagには各自のrepoを、--labels flagにはself-hosted runnerに追加するlabelを、--name flagにはself-hosted runnerの名前を指定してください。 これを実行すると./.runner dirが生成されます。 このdirが存在するかどうかで、登録済みかどうかを判別できます。

起動するのは下記のコマンドです。

while true; do
  ./run.sh &
  RUNNER_PID=$!
  wait $RUNNER_PID || true
  RUNNER_PID=""
  sleep 2
done

runnerが意図せず停止してしまった際に対応するため、waitコマンドでプロセスの停止を検知したら再起動するようにしています。

停止するのは下記のコマンドです。

cleanup() {
  if [[ -f ./.runner ]]; then
    echo "Runner cleanup: removing from GitHub..."
    ./config.sh remove --token $(get_registration_token)
  else
    echo "Runner was not registered, skipping cleanup."
  fi
}

stop_runner() {
  echo "Caught signal, stopping runner..."
  if [ -n "$RUNNER_PID" ]; then
    kill -TERM "$RUNNER_PID" 2>/dev/null
    wait "$RUNNER_PID" || true
  fi
  cleanup
  exit 0
}

trap stop_runner SIGINT SIGTERM

stop_runner関数ではrunnerの停止を、cleanup関数ではself-hosted runnerが登録されている場合にrunnerの削除処理を行います。 tokenはget_registration_tokenを利用して再度取得するのが重要です。 trapはrunnerの起動前に実行してください。

これらをまとめると下記のような実装になります。

cd /actions-runner

cleanup() {
  if [[ -f ./.runner ]]; then
    echo "Runner cleanup: removing from GitHub..."
    ./config.sh remove --token $(get_registration_token)
  else
    echo "Runner was not registered, skipping cleanup."
  fi
}

RUNNER_PID=""

stop_runner() {
  echo "Caught signal, stopping runner..."
  if [ -n "$RUNNER_PID" ]; then
    kill -TERM "$RUNNER_PID" 2>/dev/null
    wait "$RUNNER_PID" || true
  fi
  cleanup
  exit 0
}

echo "Runner configuration setup"
./config.sh --unattended \
  --token $(get_registration_token) \
  --url https://github.com/${GITHUB_ORG}/${GITHUB_REPO} \
  --labels ${RUNNER_LABEL} \
  --name ${RUNNER_NAME}

trap stop_runner SIGINT SIGTERM

echo "Starting runner..."
while true; do
  ./run.sh &
  RUNNER_PID=$!
  wait $RUNNER_PID || true
  RUNNER_PID=""
  sleep 2
done

全体

コード全体を置いておきます。

entry-point.sh全体

#!/bin/bash

set -euo pipefail

REQUIRED_VARS=("GITHUB_CLIENT_ID" "GITHUB_ORG" "GITHUB_REPO" "RUNNER_LABEL" "RUNNER_NAME")
for var in "${REQUIRED_VARS[@]}"; do
  if [ -z "${!var:-}" ]; then
    echo "Error: Environment value $var is not set." >&2
    exit 1
  fi
done

if [[ ! -f /private-key.pem ]]; then
  echo "Error: /private-key.pem does not exist." >&2
  exit 1
fi
if [[ ! -r /private-key.pem ]]; then
  echo "Error: /private-key.pem is not readable." >&2
  exit 1
fi

pem=$(cat /private-key.pem)

b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }

get_registration_token() {
  # Get JWT Token (https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-bash-to-generate-a-jwt)
  now=$(date +%s)
  iat=$((${now} - 60)) # Issues 60 seconds in the past
  exp=$((${now} + 600)) # Expires 10 minutes in the future

  header_json='{
      "typ":"JWT",
      "alg":"RS256"
  }'
  # Header encode
  header=$( echo -n "${header_json}" | b64enc )

  payload_json="{
      \"iat\":${iat},
      \"exp\":${exp},
      \"iss\":\"${GITHUB_CLIENT_ID}\"
  }"
  # Payload encode
  payload=$( echo -n "${payload_json}" | b64enc )

  # Signature
  header_payload="${header}"."${payload}"
  signature=$(
      openssl dgst -sha256 -sign <(echo -n "${pem}") \
      <(echo -n "${header_payload}") | b64enc
  )
  # Create JWT
  jwt="${header_payload}"."${signature}"

  # Get the access token url (https://docs.github.com/ja/rest/apps/installations?apiVersion=2022-11-28#list-app-installations-accessible-to-the-user-access-token)
  access_token_url=$(curl -sL \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://api.github.com/app/installations | \
    jq -r ".[0].access_tokens_url" )

  # Get Access token
  access_token=$(curl -sL \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    "$access_token_url" | \
    jq -r ".token")

  # Get Registration token (https://docs.github.com/ja/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-a-repository)
  curl -sL \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${access_token}" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://api.github.com/repos/${GITHUB_ORG}/${GITHUB_REPO}/actions/runners/registration-token | \
    jq -r ".token"
}

cd /actions-runner

cleanup() {
  if [[ -f ./.runner ]]; then
    echo "Runner cleanup: removing from GitHub..."
    ./config.sh remove --token $(get_registration_token)
  else
    echo "Runner was not registered, skipping cleanup."
  fi
}

RUNNER_PID=""

stop_runner() {
  echo "Caught signal, stopping runner..."
  if [ -n "$RUNNER_PID" ]; then
    kill -TERM "$RUNNER_PID" 2>/dev/null
    wait "$RUNNER_PID" || true
  fi
  cleanup
  exit 0
}

echo "Runner configuration setup"
./config.sh --unattended \
  --token $(get_registration_token) \
  --url https://github.com/${GITHUB_ORG}/${GITHUB_REPO} \
  --labels ${RUNNER_LABEL} \
  --name ${RUNNER_NAME}

trap stop_runner SIGINT SIGTERM

echo "Starting runner..."
while true; do
  ./run.sh &
  RUNNER_PID=$!
  wait $RUNNER_PID || true
  RUNNER_PID=""
  sleep 2
done

docker-compose.yaml

docker-compose.yamlについて特筆するところはないですが、本構成では下記のような設定になりました。

---
# 起動の前にこのdocker-compose.yamlと同様のdirにprivate-key.pemを配置する
services:
  actions:
    init: true
    build: .
    container_name: actions
    restart: unless-stopped
    environment:
      - GITHUB_ORG=bbsakura
      - GITHUB_REPO=XXXXX
      - GITHUB_CLIENT_ID=XXXXX
      - RUNNER_LABEL=XXXXX_development
      - RUNNER_NAME=XXXXX_runner
    volumes:
      - ./private-key.pem:/private-key.pem:ro
      - ./entry-point.sh:/entry-point.sh:ro

実行

dir内でdocker compose up -dを実行すればself-hosted runnerが起動します。 self-hosted runnerが起動すればGitHub上からRunnerの存在が確認できます。 設定したlabelを利用し、runnerを選択してActionsを実行してください。

まとめ

GitHub Actionsのためのself-hosted runnerをコンテナ上に構築する方法を紹介しました。 registration-tokenの取得方法などはいくつかのドキュメントをまたがって読む必要があるため、本記事によって調査量を削減できたら幸いです。 また、仮想基盤チームのリーダーである早坂さんをはじめ、金井さん、酒井さん、チームメンバーの皆さんには大変お世話になりました。 本記事の実装についても、いくつか早坂さんによって直して頂いた部分があります。 アルバイト期間の1年間本当にありがとうございました。

参考文献

moba1.hatenablog.com

JANOG57へOCXを提供しました

おつかれさまです、BBSakuraの川畑です。

このたび、2026年2月11日~13日まで開催された JANOG57 の会場ネットワークとして、 OCX を全面提供しました。

振り返り

自身の原籍組織であるさくらインターネットがホストを務めたこともあり、会場ネットワーク構築にあたっては社内でも特に技術力の高いメンバーがそれぞれのパートに着任して設計・構築・運用を行うなど傍から見ててもすごい勢いのNOCであったとしみじみ感じています。 ボランティアで集まったメンバーも学生とは思えないほどの技術力を持ったメンバーも居ましたし、それぞれのパートを楽しみつつ取り組めていたように思います。

私はコントリビュータとしての参加でしたので、行動は共にしつつもNOCの活動は各パートでお手伝いレベルでしか動いていないのですが、いい意味でやりすぎだったNOCの活動は終幕後に振り返ればJANOG史上最高とも言える完成度と安定度でした。

今回のNOCでの活動や技術的な構成情報は事後公開することを前提に設計されており、その情報は こちらにまとまっています ので、気になる方は是非ご覧ください。続々と情報が増えています。

提供のいきさつ

OCXがバックボーンとして採用されるにあたってのエピソードです。 昨年(2025年)にさくらインターネットがJANOG57ホストとしてのスポンサー募集を出しはじめたくらいに、さくらインターネットの執行役員で当社取締役で同期でもある江草と「さくらがホストやるならOCXをバックボーンにして、品質と安定性をコントロールできる範囲が広い状態で会場ネットワーク作りたいよね」と話したのがきっかけです。

「オプテージ様もスポンサーになっていただいているようですし、コングレならOCXのノードがある拠点から近いんで会場からは贅沢にダークファイバで直収とかどうです?先方に打診してみませんか?」といらんこと(?)を吹き込んだ(?)こともあり、ホストとしてオプテージ様へ会場へのダークファイバ敷設が打診され、結果的に今回のイベントに向けて敷設の手配を頂くことになりました。

そんな話からOCXを会場バックボーンにする方針になったのですが、この選択は大きくNOCの構築業務を助けることになります。

今までの課題

JANOG NOCの会場ネットワークの課題として、毎度「会期4~5日前にインターネット回線が敷設される」という時間的ハンデと「違う会場・違う回線事業者で構成される」という構成的ハンデを背負って構築をしています。 ここらへんは特に会場が借り物であるということ、予算の都合上何週間も前から抑えることができないこと、他の催事との兼ね合いなどいくつもの制約条件の下で運用されることになります。

私がNOCを務めたJANOG55(京都)も会期4日前のホットステージ初日に会場インターネットが開通し、当日はライフセグメント(現地の生活用インターネット回線)を立ち上げるために1日使っていたことを覚えています。 本当にホットステージからヨーイドンで機器の物理的な配置・配線やconfigの投入をやらなければならず、何かの制約や考慮漏れでconfigが変更になると設計の整合性が崩れるため、事前に設計を完璧に固めきれないことは綱渡りと同じ状況です。 「毎度違う」内容でいけば、コントリビュートされる機器も異なりますのでNOCの知見がうまく後学で活かしきれない事情もあると感じています。

そんな中、ホストである地の利を活かして「大阪本社にコントリビュータの皆様からの機器を集めて事前検証をする」ことができ、特にバックボーンはOCXを使ったことでこの恩恵を大きく享受できました。本社には施錠可能なサーバーラックが4架建っており、そのうち1つを利用して検証機器および本番に利用する回線などを敷設し、フル活用しました。

本社ラックに検証中機器がぎっしり(ホットステージ期間中の一枚)

OCXの利活用

会場が3つに分かれた特殊構成で、それぞれ下記の構成になっています。

  • コングレコンベンションセンター (プログラム会場 / オプテージ様のダークファイバーでデータセンターまで10GbEを2回線OCXノードに直収)
  • JAM BASE 3F (NETCON会場 / OCX光プライベート 10G[光クロス]回線を敷設し、OCXに収容)
  • JAM BASE 4F~6F (プログラム,BoF会場 / OCX光プライベート 1G[光ネクスト]回線を敷設し、OCXに収容)

JAM BASE用のOCX光プライベート回線は2本ともホットステージ前までには敷設が完了し、ホットステージ中には全て構築が完了した状態になっていました(素晴らしい)。

また、唯一物理接続があるコングレを模擬した環境として、私が東京のデータセンターに設置した設備から「必要なVLANをEtherIPで大阪本社に飛ばして使う」ことで本番と同じVLAN・同じNWをリモートで作り、「本番は繋ぎ替えるだけで接続できる構成」を作り上げることができました。

EtherIPを使った本番同等構成の検証スキーム

アクセスノードがあるデータセンター(オプテージ心斎橋データセンター)までの接続は物理ですが、それ以外のネットワークリソースは全てOCXのマネージドサービスを利用しており、会場入りする前に全て事前に設計・構築しておける環境があり、非常に恵まれました。

具体的には

  • Internet Connection (IPv4/IPv6付きのiDC向けインターネット接続サービス)
  • Internet Gateway (IPv4のみのNAPTサービス)
  • Cloud Connection (さくらのクラウド向けの相互接続サービス)
  • Tunnel Gateway (NTT東西のフレッツからOCX閉域網への接続を終端するマネージド4over6ルータ)

の4種類です。

特にTunnel Gatewayがあることで、物理的に離れたメンバー宅のフレッツ回線をOCXへ直収して「会場からバックボーンへのアクセスを模擬した環境」を作れたのもデバッグに大きく貢献しています。

JANOG ネットワーク構成図

いよいよ開通

会期2日前の2/9夜、通建会社の方に工事いただきコングレコンベンションセンターに無事オプテージ様のダークファイバが4芯敷設されました。 私もIDFでの工事を見守っていました。引き渡しまでひやひやでしたが光レベル及びOCXとのリンクアップを確認して安堵です。 会期まで2日しか無い都合上、ここで上がらなければもうそれはそれは...大変なことです。

IDF前に巻き溜められたファイバ(4芯2条)
OCX側からの光レベルを測定して養生テープでタグ付け

ホットステージ最終日翌日はAPの設置と立ち上げ、物理的な会場内ケーブルの配線、フロアスイッチの配置など各所が慌ただしく動き回る1日でした。 バックボーンチームにお願いをして配線処理を担当させてもらいました。ありがとうございます!

全ての機器が搭載されたコングレ会場のラック

事例として

振り返りでも書いた通り、全体的な設計が良かったこともあり会場ネットワークは非常に快適でした。OCXでのトラブルも無く3日を終えられた事に感謝ですが、高い可用性で毎日動くことが求められる商用サービスですので当たり前といえば当たり前かもしれません...

普段OCXは接続拠点間のDCIやクラウド接続などに利用されており、カンファレンスネットワークでの利用事例は初となりました。3日間のピーク端末2071台、トラフィックは下り1.07Gbps/上り770Mbps、NATセッション数はコングレで約17,000/JAM BASEで約15,000でした。トラフィックだけ見ると用意した10Gbpsx2(コングレ)+10Gbps(JAM3F)+1Gbps(JAM4~6F)としては余裕だったかなと思います。

構成・見せ方として工夫したポイントがあります。それぞれ会場のキャパシティによって捌けるユーザの数も異なるので、オフィス規模に見立てたときにそれぞれの会場を「高品質な回線が必要な大規模拠点/Headquarters」「大容量な回線が必要なブランチオフィス」「スモールビジネスなリモート拠点」として考え、回線を調達しています。言わずもがな先頭からコングレ(OCX直収 10GbEx2)/JAM 3F(OCX光プライベート 光クロス)/JAM4~6F(OCX光プライベート フレッツネクスト)です。

OCXには地場に根付いた回線ビジネスを生業としているパートナー様が豊富にいらっしゃる事に加えて、彼らがOCXへの接続拠点として自社のDCで運用し、更にお客様へ提供されています。特に電力系やケーブルテレビなどは顕著で、地域地域で自社の光ファイバを持っていますし、今回のようにデータセンターに居ない場合でもラストワンマイルをOCXのパートナー回線やフレッツを手配することで直接利用することができる良い事例になりました。

そんなオプテージ様とは今年1月に先方のサービスメニューを正式にOCXと接続することで関西エリアの広い範囲でOCXにリーチしやすくなりました。 optage.co.jp 今回のJANOGへの提供こそこのスキームでは無いものの、OPTAGE Connectionのコンセプトモデルとして意義のある取り組みであったと感じます。

終わりに

たかが無償WiFi、されど無償WiFi、眼前で繰り広げられるミッションクリティカルなNW構築と運用は大変良い刺激になりました。普段から24/365のサービスを開発している身でも、実際に現地で動くとまた感じ方も異なるものです。

ホストであるさくらインターネットのみなさま、NOCで集まっていただいたみなさま、お疲れ様でした!

【参加レポート】JANOG57へ行ってきました

はじめに

こんにちは、BBSakura Networksの外山です。

2月11日〜13日の3日間にわたって、JANOG57 Meeting in Osakaが開催されました!

www.janog.gr.jp

実は今回のJANOGでは、会場ネットワークにOCXが採用されました!詳しくは別の記事で紹介予定です!

JANOGとはJApan Network Operators' Groupを意味し、インターネットに於ける技術的事項、および、それにまつわるオペレーションに関する事項を議論、検討、紹介することにより日本のインターネット技術者、および、利用者に貢献することを目的としたグループです。(JANOG | Informationより)

過去のJANOG関連ブログも是非ご覧ください。

レポート

弊社のエンジニアも一般参加者として現地に行ってきたので、会場の様子やセッションの内容をレポートします!

会場までの道中

今回は大阪駅直結のグランフロントで開催されました。 駅から会場に向かうスカイウォークにはJANOGのフラッグがずらりと並んでおり、会場に入る前から気持ちが高まりました! (外山)

会場までのJANOGフラッグ会場入口のディスプレイ

イベントネットワークの最前線を語ろう ― JANOG57会場ネットワークの知見とこれから

JANOG会場のネットワークを担当されていた方々(JANOG NOC)が、ネットワーク構成やネットワークで使われているサービス、技術について説明し、最後にはイベントネットワークのあり方についてディスカッションする講演でした。

JANOG57は会場が3つに分かれており、これらのネットワークには以下の3要件がありました。

  • 各会場間の接続ができること
  • インターネット接続ができること
  • さくらのクラウドへの接続(監視用)ができること

これらの要件を網羅するサービスとして、OCXが選ばれたということです。 具体的な構成は、以下の通りです。

  • 本会場
    • オプテージDF → 曽根崎DC Physical Port接続
    • Internet Connectionでインターネット提供(v4,v6 デュアルスタック)
  • サブ会場(2拠点)
    • OCX光プライベート(10G / 1G)
    • v4:Internet Gateway
    • v6:さくら契約フレッツ回線
  • ルーティング
    • OCX-Router(v1)
    • BGP / Static は網内設定

接続先として拠点間接続、インターネット接続、クラウド接続を利用いただき、アクセスラインとして、OCX拠点、光プライベート、モバイルアクセス(SSH用)を利用いただいたため、OCXの各種機能を活用して大きなイベントのネットワークを支えたという、大きな実績になりました。

また、今回のJANOGではJANOG NOCがブースを出しており、上記の構成を紹介していたため、我々もNOCブースに立ち寄って、実際にOCXポータルを使って今回のネットワークの設定を行った方とお話ができました。触ってみた感想やサービスに対する感想、わかりにくかった点、OCXと他の技術を組み合わせて作ったことなど、作業者からの生の声を聞くことができ、とても有意義な時間を過ごせました。(青木)

ケーブル作成会

内容は、LANケーブルのもととなるケーブルを渡され、外被を剥き、ツイストペアをほぐし、規定の配列に並べ替え、長さを揃えてコネクタに差し込み、圧着するというものでした。

実際にやってみると、ケーブル内の導線の並び替えが想像以上に難しかったです。 8本の導線はツイストされているため、並び替える過程で何度もクロスし、指でしっかり押さえていないとすぐに順番が崩れてしまいます。きれいに揃えたつもりでも、差し込む直前に微妙にズレていたりして、物理層の繊細さを体感しました。

また、同行した人が、自分がようやく片側を終えたタイミングで両側を完了させていたのも印象的でした。ケーブル作成は意外と“センス”や慣れが問われる分野なのだと実感しました。

圧着後はテスターで導通確認をしました。 実際にケーブルで疎通が確認できた瞬間は、思わず嬉しくなりました。普段は意識することの少ないL1(物理層)ですが、ネットワークの土台を自分の手で作り、その動作を確認できたことは非常に良い経験でした。(松下)

ケーブル作成の様子

【実践】IPv6版ポート解放 〜家庭内ネットワークにおけるIPv6通信パターンの変化の波を乗りこなそう〜

私の印象に残った講演はIPv6ポート解放という、ゲーム業界でのIPv6利用を解説する講演でした。

ポート解放とは外部からの接続を許可することで、一般的な自宅環境だとセキュリティのため外部から内部への接続は一切禁止しているとのこと。オンラインゲームで、各ユーザがこのポート解放をすることでお互い直接通信(P2P)ができるようになるわけですが、IPv6を用いたい場合はこのポート解放が上手くできなくてP2P通信ができない場合が多いらしいです。ゲーム自体がIPv6を十分支援しなかったり、自宅のルータがIPv6を支援しなかったり、相手側のユーザがIPv6がダメだったり、原因は様々です。

その場合も一応ゲーム会社のサーバ経由で通信はできるので複数のユーザが一緒にプレイすることはできますが、やはり通信の経由先が増えるため、どうしても遅延は増えることになります。

上記のような事情もあり、一般的なインターネット環境よりもゲーム業界ではIPv6の導入は遅れているようです。それでも少しずつゲームもIPv6対応が増えてきています。そこで、IoT業界はIPv6が前提のプロトコル「Matter」が普及してきており、IPv6対応のゲームはこのMatterを利用してIoT機器と直接通信することが可能になるので、例えばAIスピーカーやAI家電などをゲーム側から操作できるようになります。直接操作だけでなく、ゲーム内での重要なシーンや暗い場所に入ることに合わせて部屋の照明を自動で暗くするように連動するなど、ユーザ体験自体を向上させることもできそうです。

今後IoTがより普及しゲームもIPv6対応になると、数年後には画面の外にもゲームが広がる時代が来るかもしれませんね。(チョ)

ネットワークエンジニア育成、みんなどこで悩んでる?BoF / エンジニアリングする「組織」の育て方

昨今ネットワークエンジニアが不足してると感じることはどの会社でも多いと思ってますが、興味を持って関わる若手がいないかと言われると今のところ自分の場合はそういうわけでもないので、若手をどうフォローしてあげるかの意見交換がしたいなと思い、ネットワークを取り巻く、人や組織まわりの話に関するプログラムに参加しました。 プログラムの性質上オフレコなので細かくは書きませんが、以下のようなことを議論しました。

  • 資格とどう向き合っていくか
  • みんな余裕がない中で、学びたい側、教えられる経験を持ってる側、それぞれがどう時間を作っていくか
  • 技術向上の優先度、何からやればいいのか
  • 内製化とどう向き合っていくか
  • モチベーション(飽き)とどう向き合っていくか
  • 人の採用、どこまで妥協しないか

などなど。 この手の話は話し手の環境で前提条件が異なるため、話が散らかりやすいのですが、プログラムを主催してる方々の進行が上手いこともあり、整理された形で議論することができとてもよかったです。 替え玉エリアでも話は尽きず、それぞれの苦労を意見交換できたのは大変貴重でした。 (JANOGを通じて長く交流してる方々も、自分と同じように最近管理職側を担う立場の人が多く、その中での苦労や意識してることを会話することができたのはとても有り難かったです。)(丸野)

非ボリューム型DDoSはどこまでネットワークで守れるか? / その他BoF

セッション

非ボリューム型DDoS、特にアプリ層攻撃に対してネットワーク側がどこまで対策できるのか、という講演でした。これまでアプリ層の攻撃はサーバーチーム側で対応することが多く、ネットワーク側ではあまり踏み込んでいなかったそうです。L7WAFや専用の対策装置を導入する方法もありますが、ホスティング事業者としてはコストや運用負荷の面でなかなか現実的ではない、という悩みもあるとのこと。

そこで「完璧に防ぐ」のではなく、「ほどほどに抑える」ことを目標に、悪性の疑いがあるIPを動的に制御する仕組みを構築。外部のIPリストを集約し、BGP Flowspecを使って境界ルータで帯域制限やブロックを行う構成です。

しかし課題として、 - 外部のIPリストが、実際の攻撃の50%程度のカバー率 - Flowspecのルール数上限 - 誤検知のリスク

があり、NetFlowとの連携やAIを使った判定の最適化も検討しているそうです。

1Gbps未満とインパクトはそこまで大きくない攻撃でも、年に数回とはいえアラート対応は発生するため、コストとのバランスを取りながらできる範囲で対応したいという難しい問題ではありつつ、まずはPoCとして最低限動作することを確認し、課題を1つ1つ軽減するための対策を考えていくという進め方が勉強になりました。

BoF

ケーブリングBoFおよび海底ケーブル建設保守工事のBoFに参加しました。

ラボ構築のために私自身がケーブリングを行う機会があり、「美しいケーブリングとは何か」「正解とは何か」がわからないと思っていたこともあって、今回参加してみました。

セッションでは、実際の現場での困りごと(タグ管理をどのように行っているか、パッチパネルの管理はどこまで徹底できているか、など)や、最近ではケーブルを自作する機会が減っている(光ケーブルの融着はすることがあるが)、といった話題について議論が交わされました。

また、海底ケーブル建設保守工事のBoFでは、ケーブル敷設のための経路設計、鋤式埋設機やROVの解説、さらに故障発生時の対応について話を聞くことができました。気の遠くなるような時間軸とスケールでこれらの作業が行われていることを知り、普段何気なく利用している通信インフラのありがたみを改めて実感しました。

どちらのセッションも、普段あまり意識することのない物理層の世界の奥深さや醍醐味を感じられる、大変興味深い内容でした!(外山)

おわりに

今回は大阪での開催ということもあり、大変多くの参加者で賑わっていました。

会場ネットワークにOCXが採用されたことも含め、技術がどのように現場で使われ、どのように支えられているのかを間近で感じられたことは、私たちにとっても大きな刺激となりました。

今回得られた学びや気づきを、今後の業務にも活かしていきたいと思います。

次回のJANOGも非常に楽しみです!

最後に、会場で配っていたシュークリームを載せておきます。

会場で配っていたシュークリーム