ステータス:ドラフト(2026-10-17)、一部実測。 基準バージョン cilium/cilium v1.20.1(コミット 7d68cfb394f2)。小規模なクラスタがまさにこのバージョン(エージェント・ operator-generic・hubble-relay v1.20.1、cilium-envoy 1.37系)を実行しているため、実測は2026-10-17にそのクラスタで 読み取りのみ で行いました(エージェントのポッド1つに対して
cilium-dbg·bpftool·hubble observe読み取りコマンド、ConfigMap・ポリシー数の読み取り、relayログの読み取り)。 DNSパターンのアクセスの結果は、コントリビューションの準備を進めながら2026-10-16〜17の間に測定した記録(PRドラフト)を引用します。 執筆のルール:実測データが基本であり、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表示します。
目次
- 1. 一言まとめ
- 2. 構成図
- 3. コードマップ
- 4. コアパスを辿る
- 5. ユーザコントリビューションとの繋がり
- 6. 実測
- 7. どこから読むべきか
- 8. 初めてのコントリビューションに良い場所
- 9. 制限事項
- 参考
1. 一言まとめ
Ciliumを一言で要約すると、 「ノードごとの事務所(エージェント)がルール表(BPFマップ)を書き換え、カーネル各地の検問所(BPFプログラム)はそのルール表だけを見て通過・遮断・アドレス変換を決定する」 です。検問所は事務所に電話をかけません。パケットごとにユーザー空間に上がってくると遅いため、判断に必要なものはすべてあらかじめマップに書き込んでおきます。そのため、Ciliumのコードを読むときに真っ先に問うべき質問は常に 「この情報は誰がどのマップに書き, どのフックが読み取るのか」 です。
| コンポーネント | 役割 | どこで動くか |
|---|---|---|
| cilium-agent | Kubernetesの状態を受け取り、ポリシー・サービス・identityを計算してBPFマップに書き込む。BPFプログラムをコンパイル・ロードする。DNSプロキシとHubbleオブザーバーもこのプロセス内にある | ノードごと(DaemonSet) |
BPFプログラム(bpf/) |
ポッドのveth・ホストデバイス・トンネルデバイス・ソケットcgroupにアタッチされ、パケットと connect() を処理 |
カーネル内 |
| cilium-operator | CRDの登録、IPAM(モードによる)、残ったidentity・CiliumEndpointのクリーンアップ、LB-IPAM | クラスターに1つ(リーダー選出) |
| cilium-envoy | HTTPなどのL7ポリシーおよびIngress・Gatewayのためのプロキシ。xDSを使用してエージェントから設定を受け取る | ノードごと(個別DaemonSet) |
| Hubble | エージェント内のオブザーバーがBPFの発行するイベントをフローとして解釈してリングバファに格納し、relayが各ノードのフローを集約する | オブザーバーはエージェント内、relayはクラスターに1つ |
2. 構成図
- 上の2つのボックスはクラスターにそれぞれ1つずつです。エージェントはAPIサーバーを 直接 監視します。operatorを経由しないため、operatorが一時停止しても、すでに稼働しているノードのデータパスはそのまま維持されます。
- 中央の大きなボックスが1つのノードです。エージェントボックス内の6つのチップがこの記事で追いかけるパーツであり、下の円柱がそれらのパーツが使用するBPFマップです。
- 一番下の表が「フック位置 → アタッチされるプログラム」です。ファイル名がそのままアタッチされる位置を表しています。
bpf_lxc.cはポッド側、bpf_host.cはホスト側、bpf_overlay.cはトンネルデバイス、bpf_sock.cはソケットシステムコールです。点線の枠(XDP)は小規模なクラスタでは無効になっています。 - 左側の点線矢印は、BPFが
cilium_eventsperfマップに出力したイベントがエージェント内のHubbleオブザーバーへと上がっていく道です(4.4)。
3. コードマップ
リポジトリは大きいですが、最初に見るべき場所は決まっています。
| パス | 内容 | 最初に読むファイル |
|---|---|---|
daemon/ |
エージェントのエントリポイントとセルのまとめ | daemon/cmd/cells.go |
bpf/ |
カーネル上で実行されるCプログラム。エントリファイル bpf_*.c と共通ヘッダー bpf/lib/ |
bpf/bpf_lxc.c |
pkg/datapath/ |
BPFのコンパイル・ロード(loader)、デバイス・ルート・sysctlの調整、マップの定義 |
pkg/datapath/loader/endpoint.go |
pkg/loadbalancer/ |
v1.18以降で再設計されたサービスロードバランシング制御部(StateDBテーブル + reconciler) | pkg/loadbalancer/README.md |
pkg/policy/ |
ポリシーリポジトリ、selector cache、エンドポイントごとのポリシー計算 | pkg/policy/selectorcache.go |
pkg/fqdn/ |
DNSプロキシ、名前→IPキャッシュ、 matchPattern 正規表現変換 |
pkg/fqdn/matchpattern/matchpattern.go |
pkg/hubble/, hubble-relay/ |
フローの解釈・リングバファ・gRPCサーバー、およびrelay | pkg/hubble/observer/local_observer.go |
operator/ |
operatorのエントリポイントとセル | operator/cmd/root.go |
エージェントとoperatorはどちらも hive という依存性注入フレームワークで組み立てられます。1つの機能が1つの「セル(cell)」であり、セルは何を提供し何を必要とするかだけを宣言します。エージェント main は daemon/main.go:11 での
hive.New(cmd.Agent) 1行であり、その Agent が daemon/cmd/cells.go:103
で Infrastructure·ControlPlane·datapath.Cell の3つに分かれています。 ControlPlane(cells.go:194) コメントに分かれている理由が直接書かれています。制御部は「純粋なビジネスロジック」であるため、カーネル権限なしで統合テストを実行できるようにするためです。BPFを実際に触る処理は pkg/datapath/cells.go:52 の datapath モジュールに集約しました。実測した cilium-dbg status --verbose のモジュールツリーも controlplane / datapath / infra の3つに分かれていました(セクション6)。
4. コアパスを辿る
4.1 エージェントがBPFをコンパイル・ロードする道
CiliumはBPFオブジェクトをイメージにあらかじめ焼き込まず、 ノード上でclangによりコンパイルします(エージェントイメージにclang 19.1.7が含まれていることを実測)。ノードごとにカーネル機能や設定(トンネルの有無、マスカレード、IPv6など)が異なるため、使用しない機能を #ifdef で完全に削ることで、検証器(Verifier)を通過させ、命令数の上限にも引っかからないようにするためです。
- ベースのデータパス。
pkg/datapath/orchestrator/orchestrator.go:167reconcilerがノードの設定が変更されるたびにReinitializeを呼び出し、pkg/datapath/loader/base.go:298Reinitializeがトンネルデバイス用のbpf_overlay.c、XDP、およびソケットLBが有効な場合はbpf_sock.cをコンパイルしてbase.go:406) cgroupにアタッチします (pkg/socketlb/socketlb.go:83Enable). - エンドポイント(ポッド)ごと。
pkg/datapath/loader/endpoint.go:83ReloadDatapathはまずcache.go:176fetchOrCompileを呼び出します。ここが設計のポイントです。エンドポイントの設定から ポッドごとに異なる値(エンドポイントID、IP、ifindexなど)を除外した残りの部分でハッシュを作成し、同じハッシュのオブジェクトがすでに存在する場合はコンパイルを行わずにコピーして使用します。実際のコンパイルはcompile.go:160compileがclangを実行します。 - 値の埋め込みとアタッチ。
endpoint.go:195reloadEndpointがテンプレートにポッドごとの値を 定数として上書きしながら(Constants: endpointConfiguration(...)ロードし、ポリシープログラムをtail callマップにあらかじめ挿入した上で、cil_from_containerをvethのingressにアタッチします。順序についてのコメントが記載されています。入口を先に開くと半分だけ準備できたプログラムにパケットが入ってしまうためです。ポッド側のegressプログラム(cil_to_container)はRequireEgressProg()が真のときのみアタッチされます。
実測がその理由を示しています。1つのノードの13個のエンドポイントが 1つのテンプレートハッシュを共有しており、テンプレートは bpf_lxc.o(約1.0MB)と
bpf_host.o(約1.4MB)の2つのみでした。コンパイルは1回だけで、残りは定数を書き換えてロードするため、ポッド作成の遅延からclangの処理が排除されます。
4.2 kube-proxyの代替:Serviceが connect() からバックエンドに切り替わるまで
kube-proxyはServiceをiptables(またはIPVS)ルールに変換します。Ciliumは同じ情報をBPFハッシュマップに格納し、 可能であればパケットが生成されるよりも前に ソケットの宛先アドレスを書き換えます。v1.18ではこの制御部がStateDBベースで再設計されており、その構造は
pkg/loadbalancer/README.md の図の通りです。
- 反映(reflect)。
pkg/loadbalancer/reflectors/k8s.go:208runServiceEndpointsReflectorがServiceとEndpointSliceの変更を一時的に集約して1つのトランザクションとして書き込みます(大量のロールアウト時にマップを何千回も個別に修正することを避けるため)。 - テーブルへの書き込み。
pkg/loadbalancer/writer/writer.go:326UpsertServiceAndFrontendsなどがサービス・フロントエンド・バックエンドの3つのテーブルの参照を同期させます。データソースが複数あっても、このWriter経由でのみ書き込まれます。 - マップへの適合。
pkg/loadbalancer/reconciler/bpf_reconciler.go:725Updateがフロントエンドテーブルを監視し、updateFrontend(842行目) でマップに書き込みます。マップcilium_lb4_services_v2(bpf/lib/lb.h:284はキーが(주소, 포트, 프로토콜, 슬롯)であり、スロット0が「ヘッダー部分」(バックエンド数・フラグ)、スロット1..nがバックエンドIDです。リトライはreconcilerが担当するため、データの送信元はマップのエラーを意識する必要がありません。 - ソケットでの解決。
bpf/bpf_sock.c:467cil_sock4_connectがcgroupconnect4フックで__sock4_xlate_fwd(298行目) を呼び出し、そこでlb4_lookup_serviceを用いてヘッダー部分を検索した後、sock_select_slot() % count + 1を使ってスロットを選択し、ソケットアドレスをバックエンドに書き換えます。アプリケーションはClusterIPに接続したと認識しますが、カーネルソケットは最初からバックエンドに接続されているため、パケットごとにNAT処理を行う必要がありません。
しかし、ステップ4には1つ例外の扉があります。
if (is_defined(ENABLE_SOCKET_LB_HOST_ONLY) && !in_hostns)
return -ENXIO;
if (!udp_only && !sock_proto_enabled(protocol))
return -ENOTSUP;
/* In case a direct match fails, we try to look-up surrogate
* service entries via wildcarded lookup for NodePort and
* HostPort services.
*/
svc = lb4_lookup_service(&key, true);
if (!svc)
svc = sock4_wildcard_lookup_full(&key, in_hostns);
if (!svc)
return -ENXIO;
出所:cilium v1.20.1 bpf/bpf_sock.c 322–335行目、GPL-2.0-only OR BSD-2-Clause
socketLB.hostNamespaceOnly を有効にすると、ポッドネームスペースのソケットはここでそのまま処理されます。では、サービスアドレスの変換は誰が行うのでしょうか?
bpf_lxc.c がコンパイル時に自ら判断します。
#if !defined(ENABLE_SOCKET_LB_FULL) || \
defined(ENABLE_SOCKET_LB_HOST_ONLY) || \
defined(ENABLE_L7_LB) || \
defined(ENABLE_SCTP) || \
defined(ENABLE_CLUSTER_AWARE_ADDRESSING)
# define ENABLE_PER_PACKET_LB 1
#endif
出所:cilium v1.20.1 bpf/bpf_lxc.c 90–96行目、GPL-2.0-only OR BSD-2-Clause
ソケットで完全に解決できないケースが1つでもある場合、ポッドのvethのtcプログラムにパケット単位のLB__per_packet_lb_svc_xlate_4(152行目)が挿入されます。公式ドキュメントでは、このバイパスはIstioサイドカのように本来ClusterIPが必要な場合や、KubeVirt・Kata・gVisorのようにソケットLBが効果を発揮しないポッドを対象としたものであると説明されています(kubeproxy-freeドキュメントの「Socket LoadBalancer Bypass in Pod Namespace」)。実測した小規模なクラスタはまさにこのモードでしたSocket LB Coverage: Hostns-only。TT LabクラスタはKubeVirt VMを実行しているため、この選択には理由がありますKubeVirt実習VMシリーズ)。結果として、ポッドからサービスに向かう接続は cil_from_container の内部で解決され、ソケット逆変換マップ(cilium_lb4_reverse_sk)にはホストネームスペースのソケット13個のみが存在していました。「kube-proxyの代替 = ソケットLB」と暗記していると誤解してしまうポイントです。
4.3 toFQDNsポリシーがDNSプロキシを経由して「このIPを許可」するようになる道
BPFはIPとポートしか見ません。しかし人間は *.docker.com のように名前でポリシーを記述したいと考えます。Ciliumの答えは DNS応答を傍受して名前からIPを学習し、学習したIPに名前から導出されたidentityを付与することです。名前はBPFまで降りず、BPFはこれまで通り「このidentityを許可するか?」のみを問い合せます。
- セレクターの登録。 ポリシーに
toFQDNsがある場合、pkg/fqdn/namemanager/manager.go:140RegisterFQDNSelectorがセレクターを受け取り、すでにキャッシュ内にある名前で一致するものがあれば即座にIPを表示します。セレクターの正規表現は、pkg/policy/api/fqdn.go:115ToRegexがmatchpatternで作成します。 - クエリの傍受。 同様のポリシーの
rules.dns(ポート53)により、bpf_lxcがそのポッドのDNSパケットをノードのDNSプロキシに転送します。pkg/fqdn/dnsproxy/proxy.go:895ServeDNSがCheckAllowed(763行目) でクエリ名を検証します。 その正規表現は、GeneratePattern(1439行目) がrules.dnsのパターンを|で結合して作成したものです。一致しない場合は拒否応答を返します(小さなクラスターの設定はrefused). - 応答から名前キャッシュへ。 許可されれば上流へ送り、応答を受け取ったらクライアントに返す 前に
NotifyOnDNSMsgを呼び出します。これがpkg/fqdn/messagehandler/message_handler.go:277UpdateOnDNSMsgを経由してnamemanager/manager.go:214UpdateGenerateDNSに向かいます。 - 名前のマッチング。
manager.go:422deriveLabelsForNameが応答名をすべてのFQDNセレクターの正規表現と照合し、一致するセレクターごとにfqdn:<패턴>ラベルを集めます。 - IPへのidentity付与。
manager.go:315updateMetadataが応答のIPごとにそのラベルをipcacheに登録し、ipcacheがラベルのセットに一致するidentityを割り当てます。新しいidentityはpkg/policy/selectorcache.go:768UpdateIdentitiesを通じてセレクターキャッシュに入り、そのセレクターを使用するエンドポイントのポリシーマップに許可エントリが生成されます。 - 待機してから応答を返す。
UpdateGenerateDNSは、ipcacheがその変更をマップに反映するまで(WaitForRevision)待機するチャネルを返します。ハンドラーはtofqdns-proxy-response-max-delay(小さなクラスターでは100ms)の間だけ待機し、応答をポッドに書き込みます。ポッドが応答を受け取ってすぐに接続しても、ポリシーマップがすでに開いている状態にするための順序です。
ここでmatchpatternが2回使用される点が重要です(図の③・⑤)。変換ルールは簡潔です。
func escapeRegexpCharacters(pattern string) string {
// Convert '.' in the match pattern as literal '.' for regex pattern.
pattern = strings.ReplaceAll(pattern, ".", "[.]")
// '**.' in match pattern prefix is a subdomain wildcard specifier which matches one ore more
// entire labels.
pattern = subdomainWildcardSpecifierPrefix.ReplaceAllString(pattern, dnsWildcardREGroup)
// Base case: * becomes .*, but only for DNS valid characters
// `*` wildcard matches all DNS characters within the subdomain boundary(doesn't include '.' literal)
pattern = wildcardSpecifier.ReplaceAllString(pattern, allowedDNSCharsREGroup+"*")
return pattern
}
出典: cilium v1.20.1 pkg/fqdn/matchpattern/matchpattern.go 131–144行目、Apache-2.0
* は [-a-zA-Z0-9_]*(17行目)に変換され、
ドットを越えることができません。 複数受け入れるのは先頭の **. ひとつだけであり(37行目の ^[*]{2,}\[\.\]、23行目の dnsWildcardREGroup)、中間にある
** は最後の行で単に * のように変換されます。末尾にドットを付与する正規化は Sanitize(87行目)
が実行します。
実測事例。 コントリビューションの準備中に小さなクラスターで測定した記録(2026-10-16〜17、PRドラフト)です。イメージレジストリ用のポリシーが
toFQDNs: matchPattern: "*.docker.com" で、DNSが * として開いているポッドから3つの名前にそれぞれ2回ずつ接続しました。
| 名前 | DNS解決 | 接続 | 理由 |
|---|---|---|---|
hub.docker.com |
成功 | 接続成功 (HTTP 200) | 1つ分 → *.docker.com に一致 |
production.cloudflare.docker.com |
成功 | タイムアウト (curl 28) | 2つ分 → * がドットを越えられない |
docker.com |
成功 | タイムアウト | *. 先頭に1つ分が必要 |
2行目はDocker Hubのイメージレイヤーを配信するCDN名であるため、ポリシーは一致しているように見えるにもかかわらずイメージの取得のみが失敗する現象になります。同じ記録においてmainのmatchpatternをGoで直接実行した(Validate(Sanitize(p)))結果も表と同様であり(**.docker.com は2行目に一致)、記事の執筆中に変換ルールをPythonに移植して再実行した結果も同様でした(図の下の表)。DNSはいずれも解決されていたため、ブロックされている箇所は③ではなく⑤です。同様の性質が実習用ポッドのDNS egressポリシーでどのような問題を引き起こしたかについては、「DNSへの漏洩経路の遮断」に別途記載しています。この記事では、その性質がコードのどの行に起因しているかまでを取り上げます。 DNSへの漏洩経路の遮断 この記事では、その性質がコードのどの行に起因しているかまでを取り上げます。
4.4 Hubble: perfリングバッファからrelayまで
- BPFがイベントを出力する。 パケットが転送・破棄・ポリシー判定される際、
bpf/lib/trace.h:210_send_trace_notifyなどがcilium_events(bpf/lib/events.h:13,PERF_EVENT_ARRAY)に短い記録を書き込みます。すべてのパケットを書き込むと負荷が高いため、MONITOR_AGGREGATIONで削減します。実測したエンドポイントのep_config.hはMONITOR_AGGREGATION 3(アクティブな接続の追跡を簡略化、ConfigMapのmonitor-aggregation: medium)でした。 - エージェントが読み取る。
pkg/monitor/agent/agent.go:324handleEventsがperfリーダーを開き、レコードごとにnotifyPerfEventLocked(428行目) でコンシューマーに分配します。Hubbleはそのコンシューマーの1つです(pkg/hubble/monitor/consumer.go:73). - 解釈してリングバッファへ。
pkg/hubble/observer/local_observer.go:116StartのループがイベントをDecodeしてフロー(Flow)を生成し、メトリクスフックを経由してcontainer/ring.go:168Ring.Writeで固定サイズのリングに格納します。サイズは2のべき乗マイナス1(デフォルト4095)であるため、インデックスは除算ではなくマスクによって求められます。GetFlows(260行目)はこのリングを読み取るだけです。 - relayによる集約。 relayは各エージェントのHubbleサーバー(:4244、TLS)に接続されます(
pkg/hubble/relay/pool/manager.go:51)。 リクエストが来ると、pkg/hubble/relay/observer/server.go:62GetFlowsがすべてのピアに同じリクエストを送信し(observer.go:37)、 返ってきたフローを時系列順に再ソートして(sortFlows、67行目)出力します。
この設計の意図は「保存しない」という点にあります。フローはノードのメモリ上のリングにのみ存在し、relayも保持しません。実測したノードではリングが4095/4095で埋まった状態で毎秒58.5個のフローが流入していたため、1つのノードが記憶している過去の履歴は 約70秒です。長期間保持したい場合は、メトリクスやエクスポーターを別途使用します。
5. ユーザーコントリビューションとの関連
筆者は4.3の性質に関するドキュメントを修正する変更を準備していました(原典第5節「準備のみ完了した項目」)。まだ 公開していません。 CiliumはAIが大部分を作成したコミュニケーション(PR本文など)を受け付けない方針であるため、本文は筆者が自ら記述する必要があります。そのため、ここでも「準備のみ完了した」という段階までを記載しています。
| ドキュメント上の位置(v1.20.1) | 現在の内容 | コードの根拠 |
|---|---|---|
Documentation/security/policy/layer7.rst:159 |
L7 DNSの matchPattern の説明に **. がない |
GeneratePattern も ToUnAnchoredRegexp →同じ escapeRegexpCharacters |
layer7.rst:166, :227 |
1つ分のみ一致する例を「any subdomains」と呼称 | * → [-a-zA-Z0-9_]*(141行目) |
layer3.rst:548 |
**. の説明はあるが「先頭のみ」という記載がない |
37行目の正規表現が ^ に固定、テストの test.**.cilium.io. 事例 |
根拠は4.3の実測表およびGoによる再現結果です。ドキュメントの3つのファイルと matchpattern.go は、v1.20.1とmainが同一であることを準備の段階で確認したとドラフトに記載されています。
6. 実測
2026-10-17、小さなクラスター(ノード8台、kube-proxy DaemonSetなし)のコントロールプレーンノードの1つのエージェントポッドに対してのみ読み取りコマンドを実行しました。生データは別途保管し、以下は名前とアドレスを除外した値です。
| コマンド | 確認結果 |
|---|---|
cilium-dbg status |
KubeProxyReplacement: True, Routing: Network: Tunnel [vxlan] Host: BPF, Attach Mode: TCX, Device Mode: veth, Masquerading: BPF, Host firewall: Enabled、コントローラー 90/90、モジュール 125 OK、クラスターの健全性 8/8 |
cilium-dbg status --verbose |
Socket LB: Enabled, Socket LB Coverage: Hostns-only, Backend Selection: Random, XDP Acceleration: Disabled、IPAMはノードあたり/24から15個を割り当て |
cilium-dbg service list |
フロントエンド 211個 (ClusterIP 171、NodePort 20、LoadBalancer 20) |
cilium-dbg bpf lb list, map list |
cilium_lb4_services_v2 573エントリ = ヘッドエントリ 358 + バックエンドスロット 215。ヘッドエントリのうち127個はポート0/ANYのワイルドカードエントリ(用途の詳細は未確認)。バックエンドマップ 163、逆変換マップ 231、ソケット逆変換 13 |
bpftool net show |
lxcデバイス13個すべて tcx/ingress cil_from_container のみ、egressなし。物理NIC cil_from/to_netdev, cilium_host·cilium_net cil_to_host·cil_from_host, cilium_vxlan cil_from/to_overlay、XDPは空 |
bpftool prog show |
Ciliumプログラム 231個 (tc 218、cgroup sock_addr 8、cgroup sock 3、ソケット終了用tracing 2)。tc側はエンドポイントごとに概ね ingress 3個 + tail call 10個 |
bpftool cgroup tree |
cgroupのルートに cil_sock4_connect·sendmsg4·recvmsg4·getpeername4·post_bind4·sock_release など11個 |
ステータスディレクトリ ls |
テンプレート2個(bpf_lxc.o 1,086,040バイト、 bpf_host.o 1,452,768バイト)。エンドポイント13個の template.txt のハッシュがすべて一致 |
hubble observe --last 500 |
500個が10.5秒分、FORWARDED 489、DROPPED 11(すべて UNSUPPORTED_L3_PROTOCOL)。リング 4095/4095、58.5フロー/秒 |
cilium-dbg fqdn cache list, fqdn names |
このノードは空(FQDNセレクター 0) — このノードにはDNSルールが適用されたポッドが存在しなかった |
| ポリシー数(クラスター全体) | CNP 22個のうち rules.dns が存在するもの 5、 toFQDNs が存在するもの 2 |
| relayのログ | 8個のピアに :4244、TLSで接続 |
読むべき対象は3つあります。
- ソケットLBは半分だけ有効になっています。 4.2で確認した
ENABLE_SOCKET_LB_HOST_ONLYのパスです。プログラムの数からも確認できます。エンドポイントごとにtail_nodeport_rev_dnat_ipv4·tail_no_service_ipv4のようなtc側のLB tail callが紐づいており、ソケット逆変換マップは13エントリのみです。 - ポッド側のegressフックが存在しません。 ポッドに 入る パケットは、vethのegressではなく、ホスト側のプログラムがポリシーtail call(
cil_lxc_policy)を呼び出した直後に引き渡されます。4.1のRequireEgressProg()が偽の場合にあたります。「ポッドごとにプログラムが2つ」という一般的な図式とは異なります。 - FQDNはこのノードでは確認対象がありませんでした。 エージェントを1つだけ読み取ったため、DNSルールが適用されたポッドが存在するノードのキャッシュは未計測です。
7. どこから読むべきか
daemon/cmd/cells.go:103— セルのリストを走査し、エージェントに何が含まれているかのマップを作成します。コメントが親切です。pkg/loadbalancer/README.mdとbpf_reconciler.go:842— 「テーブル → reconciler → マップ」というCiliumの新しい制御プレーンの作法を最も短時間で学べる箇所です。bpf/bpf_sock.c:298— マップを読み取る側。2番で使用したエントリがどのように読み取られるかを確認します。pkg/datapath/loader/endpoint.go:195— テンプレート、定数、tcxアタッチの順序。pkg/fqdn/dnsproxy/proxy.go:895からnamemanager/manager.go:214まで — ユーザー空間プロキシとBPFポリシーが交差する場所。bpf/bpf_lxc.c:1780cil_from_container— 最後に読みます。tail callと#ifdefが多く含まれているため、前半の5つを理解した上で読まないと道に迷ってしまいます。
8. 最初の一歩としてコントリビューションしやすい場所
ソースコードを読みながら実際に気付いた点です(未公開であり、mainでそのまま維持されているかは未確認)。
pkg/loadbalancer/README.md:33の存在しないファイル名。 Kubernetesデータの取得元がreflector.goにあると記載されているものの、v1.20.1にはそのファイルが存在せず、実際の実装はreflectors/k8s.goです(同READMEの61行目のリストはreflectorsディレクトリを正しく記述しています)。1行のドキュメント修正に適した箇所です。bpf/bpf_lxc.c:88コメントの誤字。 「enable per-packet LB is SCTP is enabled」の「is」は「if」であるべき箇所です。4.2で引用した条件のすぐ上に位置するため、読者が思わず手を止めてしまう行です。pkg/fqdn/matchpattern/matchpattern.go:19·:135の「one ore more」。 2箇所の誤字です。さらに、ToAnchoredRegexp(95–99行目)のドキュメントコメントは*のみを説明しており、**.に言及していません。第5節のドキュメント修正と同等のテーマであるため、あわせて取り扱うのに適しています。- ワイルドカードヘッドエントリの説明。 実測したヘッドエントリの3分の1がポート0/ANYのエントリでしたが、その用途は
bpf-lb-enable-wildcard-entriesオプションのコメントの1行以外で見つけることができませんでした。bpf lb listの/ANYを説明するドキュメントの候補となります(既存ドキュメントの有無は未確認)。 - 公式案内。 初めての場合は コントリビューションガイド
が指し示す
good-first-issueラベルから確認してください。AIポリシー: CiliumではAIが大部分を作成したPR本文やコメントを受け付けないため、コミュニケーションは自身で記述します。
9. 制限事項
- 取り上げていない事項: ClusterMesh、BGP制御プレーン、WireGuard・IPsec暗号化、Gateway API・IngressのEnvoy設定パス(
pkg/ciliumenvoyconfig)、egress gateway、Maglev、XDPアクセラレーション、IPv6、identity割当ての詳細、operatorのIPAM割り当て機能(ビルドタグipam_provider_*で有効化 — 小さなクラスターではipam: kubernetesとして指定していません)。 - 未計測の事項: DNSルールが適用されたポッドが存在するノードにおけるFQDNキャッシュとセレクター、
tofqdns-proxy-response-max-delayを通過して応答が先に出力されるケース、BPFのコンパイルにかかる時間(テンプレートは約6週間前のエージェント起動時に作成されたため今回確認できなかった)、ノード再起動時のエンドポイントの復旧。 - 4.3の接続テーブルは今回(2026-10-17)再測定した値ではなく、コントリビューション準備時の記録です。新しいポッドを起動しないという今回の実測のルールによるものです。
- EnvoyについてはxDSの関係のみを記載しています。L7ポリシーのパスは未計測です(
Proxy Statusは「0 redirects active」)。
著者の一言。 Ciliumの検問所は電話に出ません。事前に受け取ったルール表を見るだけです。だから変だと思ったら検問所ではなく 誰がその表を書いたのかを探しに行ってみてください。そして
*はドット一つも超えられない臆病者だということを、忘れないでください。
参考
すべての資料は2026-10-17に閲覧しました。ソースはcilium/cilium v1.20.1タグ(コミット7d68cfb394f2)です。
- エージェント・セル: https://github.com/cilium/cilium/blob/v1.20.1/daemon/cmd/cells.go, https://github.com/cilium/cilium/blob/v1.20.1/pkg/datapath/cells.go
- BPFロード: https://github.com/cilium/cilium/tree/v1.20.1/pkg/datapath/loader, https://github.com/cilium/cilium/blob/v1.20.1/pkg/datapath/orchestrator/orchestrator.go
- BPFプログラム: https://github.com/cilium/cilium/tree/v1.20.1/bpf (
bpf_lxc.c,bpf_host.c,bpf_overlay.c,bpf_sock.c,bpf_xdp.c,lib/lb.h,lib/trace.h,lib/events.h) - ロードバランス制御部: https://github.com/cilium/cilium/tree/v1.20.1/pkg/loadbalancer
- FQDN: https://github.com/cilium/cilium/tree/v1.20.1/pkg/fqdn, https://github.com/cilium/cilium/blob/v1.20.1/pkg/policy/api/fqdn.go, https://github.com/cilium/cilium/blob/v1.20.1/pkg/policy/selectorcache.go
- Hubble: https://github.com/cilium/cilium/tree/v1.20.1/pkg/hubble, https://github.com/cilium/cilium/tree/v1.20.1/pkg/monitor/agent, https://github.com/cilium/cilium/tree/v1.20.1/hubble-relay
- operator: https://github.com/cilium/cilium/blob/v1.20.1/operator/cmd/root.go, https://github.com/cilium/cilium/tree/v1.20.1/operator/pkg/ipam
- kube-proxy代替ドキュメント(ソケットLBバイパス): https://github.com/cilium/cilium/blob/v1.20.1/Documentation/network/kubernetes/kubeproxy-free.rst
- DNSポリシー・ドキュメント: https://github.com/cilium/cilium/blob/v1.20.1/Documentation/security/policy/layer3.rst, https://github.com/cilium/cilium/blob/v1.20.1/Documentation/security/policy/layer7.rst
- 貢献ガイド: https://github.com/cilium/cilium/blob/v1.20.1/Documentation/contributing/development/contributing_guide.rst
- 同じシリーズの他の記事: MetalLBを読む(LoadBalancerサービスの住所を誰が広告するのか)
引用したコード: bpf/bpf_sock.c·bpf/bpf_lxc.c は GPL-2.0-only OR BSD-2-Clause、 pkg/fqdn/matchpattern/matchpattern.go は Apache-2.0 (Copyright Authors of Cilium)。