Networking

1. Software-Defined Networking (SDN) 软件定义网络

传统网络的问题

传统网络设备(交换机、路由器)把两件事混在一起:

  • 控制平面:决定数据包该往哪走(路由决策)
  • 数据平面:实际转发数据包

每台设备各自为政,想改网络行为就要一台一台登录进去配置,非常难管理。


SDN 的核心思想

把控制平面从设备里剥离出来,集中到一个叫 SDN Controller 的软件里统一管理:

┌─────────────────────────────┐
│       SDN Controller        │  ← 大脑,决定所有转发规则
│   (OpenDaylight / ONOS)     │
└──────────┬──────────────────┘
           │ OpenFlow 协议下发规则
    ┌──────┴──────┐
    ▼             ▼
┌────────┐   ┌────────┐
│ 交换机  │   │ 交换机  │  ← 只负责按规则转发,不思考
└────────┘   └────────┘

三层架构

应用层 — 你写的网络应用,告诉控制器你想要什么策略(比如:这类流量优先)

控制层 — SDN Controller,把策略翻译成具体转发规则下发给设备

基础设施层 — 实际的网络设备,只管按规则转发


OpenFlow

SDN 控制器和设备之间通信用的协议,控制器通过它告诉交换机:

如果:源IP=192.168.1.1,目标端口=80
那么:从端口3转发出去

这叫一条流表规则(Flow Entry)


为什么重要

  • 数据中心可以用软件动态调整网络拓扑
  • 不需要买专用硬件,普通服务器跑软件就能当交换机
  • 这是 Kubernetes 网络、云计算网络的基础思想

2.Real-Time Communications (RTC)

┌─────────────────────────────────┐
│ 应用层 │
│ WebRTC / SIP / XMPP │
├─────────────────────────────────┤
│ 会话层 │
│ SDP(描述媒体格式) │
│ ICE(穿透 NAT) │
├─────────────────────────────────┤
│ 传输层 │
│ SRTP(加密音视频) │
│ DTLS(密钥协商) │
│ SCTP(数据通道) │
├─────────────────────────────────┤
│ 网络层 │
│ UDP(主要)/ TCP(备用) │
└─────────────────────────────────┘

RTP / SRTP

RTP = Real-time Transport Protocol,在 UDP 上跑,专门为音视频设计:

每个 RTP 包头包含:

┌──────────────────────────────────────┐
│ 序列号(检测丢包和乱序)                │
│ 时间戳(同步音视频)                 │
│ SSRC(标识哪个媒体流)                │
│ 负载类型(编码格式:H.264/VP8/Opus)  │
└──────────────────────────────────────┘

SRTP 是加密版本的 RTP,对内容加密,防止被窃听。

RTCP

RTP 的控制协议,负责反馈网络质量:

  • 丢包率是多少
  • 延迟是多少
  • 抖动(jitter)是多少

发送端收到这些反馈后,会动态调整码率,这叫 自适应码率(ABR)

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
│V=2│P│ RC │ PT │ length │
├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤

RTCP 深度讲解

RTCP 在整个 RTP 栈里的位置

应用层
  │
  ├── RTP  (偶数端口,如 5004) ── 实际媒体数据
  └── RTCP (奇数端口,如 5005) ── 控制和统计
  │
 UDP

RTP 和 RTCP 永远成对出现,端口号约定是 RTP 用偶数,RTCP 用 RTP+1。


RTCP 包的基本结构

所有 RTCP 包共享同一个头部格式:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
│V=2│P│    RC   │      PT       │             length            │
├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤

字段含义:

  • V — 版本号,固定是 2
  • P — 是否有填充字节
  • RC — Report Count,包含几个报告块
  • PT — Packet Type,包类型(200=SR, 201=RR, 202=SDES, 203=BYE, 204=APP)
  • length — 包长度(单位是32bit字,减1)

RTCP 包类型

SR(Sender Report,200)

发送方发的,既报告自己发了什么,也包含接收质量统计:

┌─────────────────────────────────────────┐
│              Common Header              │  4 bytes
├─────────────────────────────────────────┤
│                  SSRC                   │  4 bytes  发送方标识
├─────────────────────────────────────────┤
│           NTP Timestamp (高32位)         │  4 bytes  \
├─────────────────────────────────────────┤            ├ 绝对时间,用于音视频同步
│           NTP Timestamp (低32位)         │  4 bytes  /
├─────────────────────────────────────────┤
│           RTP Timestamp                 │  4 bytes  对应的RTP时间戳
├─────────────────────────────────────────┤
│        Sender's Packet Count            │  4 bytes  总共发了多少包
├─────────────────────────────────────────┤
│         Sender's Octet Count            │  4 bytes  总共发了多少字节
├─────────────────────────────────────────┤
│          Report Block(s)...             │  每个接收源一个块
└─────────────────────────────────────────┘

RR(Receiver Report,201)

纯接收方发的,不包含发送者信息,只包含接收质量统计。

Report Block(核心)

SR 和 RR 都包含这个结构,每个正在接收的 RTP 源对应一个:

┌─────────────────────────────────────────┐
│              SSRC of source             │  4 bytes  被统计的那个发送方
├─────────────────────────────────────────┤
│  fraction │                             │
│   lost    │    cumulative lost          │  4 bytes
├─────────────────────────────────────────┤
│        extended highest seq num         │  4 bytes
├─────────────────────────────────────────┤
│              interarrival jitter        │  4 bytes
├─────────────────────────────────────────┤
│                  last SR                │  4 bytes
├─────────────────────────────────────────┤
│              delay since last SR        │  4 bytes
└─────────────────────────────────────────┘

各字段计算方式(代码级别)

丢包率 fraction lost

// 上次报告以来的丢包比例,0-255 对应 0%-100%
// expected = 这段时间内期望收到的包数
// received = 实际收到的包数

uint32_t expected = extended_max_seq - last_extended_max_seq;
uint32_t received = packet_count - last_packet_count;
uint32_t lost     = expected - received;

uint8_t fraction_lost;
if (expected == 0 || lost <= 0)
    fraction_lost = 0;
else
    fraction_lost = (lost << 8) / expected;  // 左移8位相当于乘256

累计丢包数 cumulative lost

// 从开始到现在总共丢了多少包,24bit有符号整数
int32_t cumulative_lost = total_expected - total_received;
// 范围 -2^23 到 2^23-1,负数表示有重复包

序列号 extended highest sequence number

// RTP序列号是16bit,会回绕(65535→0)
// 这里扩展成32bit,高16位是回绕次数,低16位是当前序列号
uint32_t extended_seq = (cycles << 16) | max_seq;

Jitter 抖动计算

这是最精妙的部分,用指数加权移动平均

// RFC 3550 定义的 jitter 计算
// D(i,j) = 包j和包i之间的到达时间差 - 发送时间差

double compute_jitter(
    uint32_t recv_time_i,   // 包i的接收时间(RTP时间戳单位)
    uint32_t recv_time_j,   // 包j的接收时间
    uint32_t send_time_i,   // 包i的RTP时间戳
    uint32_t send_time_j,   // 包j的RTP时间戳
    double   jitter_prev    // 上一次的jitter值
) {
    // 到达间隔差
    int32_t d = (recv_time_j - recv_time_i) 
              - (send_time_j - send_time_i);

    if (d < 0) d = -d;  // 取绝对值

    // RFC 3550 公式:J(i) = J(i-1) + (|D(i-1,i)| - J(i-1)) / 16
    return jitter_prev + ((double)d - jitter_prev) / 16.0;
}

除以16而不是取平均,是因为这样对突发抖动响应快,对平稳期遗忘慢,是个低通滤波器。

  If Si is the RTP timestamp from packet i, and Ri is the time of
  arrival in RTP timestamp units for packet i, then for two packets
  i and j, D may be expressed as
     D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)
  The interarrival jitter SHOULD be calculated continuously as each
  data packet i is received from source SSRC_n, using this
  difference D for that packet and the previous packet i-1 in order
  of arrival (not necessarily in sequence), according to the formula
     J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16
  Whenever a reception report is issued, the current value of J is
  sampled.

RTT 往返延迟计算

RTCP 巧妙地用时间戳计算 RTT,不需要额外的 ping:

// 发送方 A 在 SR 里写入当前 NTP 时间(LSR字段)
// 接收方 B 收到 SR 后,在 RR 里写:
//   last_sr = SR里的NTP时间戳中间32位
//   delay_since_last_sr = 从收到SR到发出RR经过了多少时间

// 发送方 A 收到 RR 后计算RTT:
uint32_t rtt = now - last_sr - delay_since_last_sr;
// now            = 当前时间
// last_sr        = RR里带回来的,我发SR时的时间
// delay_since_last_sr = B处理花了多少时间(排除掉)
// 剩下的就是纯网络往返时间

// 单位是 1/65536 秒,转换成毫秒:
double rtt_ms = (double)rtt / 65536.0 * 1000.0;

自适应码率(ABR)怎么用这些数据

发送方收到 RTCP RR 之后,根据统计信息调整发送策略:

void adjust_bitrate(RTCPReport* report) {
    double fraction_lost = report->fraction_lost / 256.0;
    double jitter_ms     = report->jitter / 90.0;  // 90kHz时钟
    double rtt_ms        = compute_rtt(report);

    if (fraction_lost > 0.10) {
        // 丢包超过10%,降低码率
        current_bitrate *= 0.85;
    } 
    else if (fraction_lost < 0.02 && jitter_ms < 20.0) {
        // 丢包很少且抖动小,试探性提高码率
        current_bitrate *= 1.05;
    }

    if (rtt_ms > 200.0) {
        // 延迟太高,降低码率减少缓冲区积压
        current_bitrate *= 0.90;
    }

    // 限制在合理范围内
    current_bitrate = clamp(current_bitrate, MIN_BITRATE, MAX_BITRATE);
    encoder_set_bitrate(current_bitrate);
}

真实的 WebRTC 用的是更复杂的 GCC 算法(Google Congestion Control),还会分析包的到达时间斜率来预测拥塞,但基本思路和这个一样。


RTCP 发送间隔

RTCP 不能发太频繁,RFC 3550 规定 RTCP 流量不超过总带宽的 5%

// 简化版间隔计算
double rtcp_interval(int senders, int members, double avg_rtcp_size, double bandwidth) {
    double rtcp_bw = bandwidth * 0.05;  // 5% 给 RTCP

    double interval = avg_rtcp_size * members / rtcp_bw;

    // 最小间隔 5 秒,防止网络刚启动时轰炸
    if (interval < 5.0) interval = 5.0;

    // 加随机抖动,防止所有人同时发
    return interval * (0.5 + (rand() / RAND_MAX));
}

WebRTC 发送方码率预估实现解析

WebRTC使用的是Google Congestion Control (简称GCC)拥塞控制,目前有两种实现:
* 旧的实现是接收方根据收到的音视频RTP报文, 预估码率,并使用REMB RTCP报文反馈回发送方。 * 新的实现是在发送方根据接收方反馈的TransportFeedback RTCP报文,预估码率。

基于延迟的拥塞控制原理

先来看下Google Congestion Control(GCC)的标准草案:https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02
结合草案,可以得知GCC是基于网络延迟梯度的拥塞控制算法,判断的依据如下图:

网络延迟梯度的拥塞控制算法

发送方发送两个包组的间隔为 : Ds = Ts1 - Ts0

接收方接收两个包组的间隔为: Dr = Tr1 - Tr0

如果网络没有任何抖动,那么 [ delta = Dr - Ds ] 应该是一个恒定不变的值,但是现实中网络有抖动、拥塞、丢包等情况,所以delta也是一个抖动的值。
GCC通过测量delta,来判断当前网络的使用情况,分为 OverUse (过载),Normal(正常),UnderUse(轻载) 这三种情况。
有同学可能会问,发送方和接收方时钟不统一,怎么计算差值呢,需要做时间对齐或者NTP同步吗?

不需要,因为我们对比的是delta,只需要单位一致即可,举个例子:
seq1的包 发送时间为 16000ms(发送方时钟),接收时间为 900ms(接收方时钟)
seq2的包 发送时间为 16001ms(发送方时钟),接收时间为 905ms(接收方时钟)
那么延迟梯度delta=(905-900) - (16001-16000) = 4

Pacing和包组

值得注意的是,延迟梯度的判断是以包组为单位的,而且必须在发送方开启pacing发送, 有以下几点原因:
单个包测量误差会过大,基于包组的测量更能反应网络的情况。

burst发送容易冲击网络,影响测量的精度。
那么怎么判断哪几个包属于一个包组呢,非常简单,按发送方的pacing速率分包组。
WebRTC pacing默认是5ms一个包组,如下图所示

WebRTC pacing

TransportFeedback RTCP报文

再来看看transport-feedback的包结构:https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01

transport-feedback的包结构

解析这个报文,我们可以得到下面的信息:

  • 接收到的包seq和包的接收时间
  • 丢失的包seq
  • 可以看到本质上transport-feedback是接收方对数据的ACK,并且捎带了接收的延迟梯度。

发送方码率预估

收到transport-feedback报文后需要怎么处理,结合GCC的算法来看,分为以下几步:
1.计算接收方ack了多少个字节, 统计在采样的时间窗口内接收方的接收速率
看看GCC怎么说:

采样的时间窗口内接收方的接收速率

按照这个算法实现acked_bitrate_estimate,可以计算出接收方在当前时间窗口内的接收速率。

2.将包按包组归类, 计算包组的发送时间 接收时间的差值
在前面的【Pacing和包组】中已经讲过,这里不再赘述

3.按包组的delta, 进行网络状态评估
GCC的标准草案里面使用的是卡尔曼滤波器(接收方评估),发送方评估默认的实现是Trendline Filter。

基本的原理是最小二乘法, 将多个时间点的网络抖动(delta)拟合成一条直线,如下图所示:

网络抖动

根据直线斜率的变化趋势判断网络的负载情况。
上面已经得到了包组的delta,对delta做平滑计算后,按照(时间点, 平滑后的delta), 可以在坐标系上绘制出散点图,使用最小二乘法拟合出delta随时间变化的直线,根据直线斜率计算出变化趋势。
来看看GCC里面的说法:

平滑
  • m(i)为i时刻的包组delta,del_var_th(i)为当前判断是否过载的门槛
  • m(i) < -del_var_th(i),判断为under-use(低载)
  • m(i) > del_var_th(i) 且持续至少overuse_time_th时长,判断为over-use(过载)

过载的门槛

del_var_th必须动态调整,不然可能会在跟TCP的竞争中被饿死(出于公平性考虑)。过大的del_var_th会对延迟变化不敏感,过小的del_var_th则会过于敏感,抖动容易被频繁误判为过载,必须动态调整,才能和基于丢包的连接(比如TCP)竞争。

根据探测的网络情况, 预估码率

总体的思想是根据当前接收方的接收码率,结合当前的网络负载情况,进行AIMD码率调整:

  • 在接近收敛前,使用乘性增,接近收敛时使,用加性增。
  • 当网络过载时,使用乘性减。

AIMD码率调整

在Decrease状态下,会不停的计算平均最大码率(average max bitrate),当前预估码率和平均最大码率差值在3个标准差以内时,进行乘性增,否则进行加性增。如果包到达速率超过了平均最大码率的3个标准差,那么需要重新计算平均最大码率。


重新计算平均最大码率

乘性增期间,每秒最多增加8%的码率


增加8%的码率

加性增期间,每个rtt增加“半个”包大小

半个

评估出的码率不能超过接收速率的1.5倍

速率的1.5倍

当探测到网络过载时,按照0.85的速率降低码率

发送方码率预估的算法流程

将上面的几步结合起来,可以得到一个大致的算法框架

struct FeedbackResultVector {
    int64_t send_time_ms; // 包发送时间(发送时记录)
    int64_t recv_time_ms; // 包接收时间(从TransportFeedback RTCP报文解析得到)
    int packet_size; // 包大小
};

// 解析TransportFeedback RTCP报文
FeedbackResultVector feedback_result_vec = 
    TransportFeedbackRtcp.Parse(rtcp_feedback);
// 遍历每个包, 进行处理
for (feedback_result : feedback_result_vec) {
    double delta = 0.0;
    // 计算ack速率(接收方接收速率)
    AckBitrateEstimate.Update(now_ms, feedback_result.packet_size);
    // 把接收反馈包按照包组分类,计算包组delta
    bool compute_delta = PacketGroup.AddPacket(feedback_result, delta);
    if (comupute_delta) {
        // 探测网络状态
        TrendLineFilter.Detect(delta);
        // 根据GCC状态机,进行AIMD码率调整
        AimdRateControl.Update(
            TrendLineFilter.NetState(), 
            AckBitrateEstimate.Bitrate()
        );
    }
}

Reference:


WebRTC研究:包组时间差计算-InterArrival: https://blog.jianchihu.net/webrtc-research-interarrival.html


WebRTC研究:Trendline滤波器-TrendlineEstimator: https://blog.jianchihu.net/webrtc-research-trendlineestimator.html


袁荣喜的一个GCC C语言的实现: https://github.com/yuanrongxi/razor

3.SNMP

当前可探索的层级结构:

  • 架构模型 → 深入可到:NMS 管理站 / SNMP Agent(含 AgentX 协议)/ MIB 数据库
  • MIB 管理信息库 → 深入可到:OID 树形结构 / MIB-II 的 10 个功能组(含接口表、IP 表、ARP 表)
  • PDU 消息类型 → 完整展开 6 种操作报文(Get / GetNext / GetBulk / Set / Trap / Inform)
  • 版本演进 → v1 明文陷阱 → v2c Counter64 → v3 USM 安全模型(HMAC 认证 + AES 加密全流程)
  • 安全模型 USM → 密钥本地化算法 / 防重放时间窗口 / AES-CFB 加密过程(含伪代码)
  • 常用工具 → snmpwalk 命令示例 / Zabbix LLD 自动发现 / Prometheus SNMP Exporter 完整配置

4.Cilium

Cilium —— eBPF 驱动的现代网络安全平台
Cilium provides networking and security capabilities for containerized apps, microservices, and virtual machines.


0. 根节点:Cilium 到底是什么?

最顶层可以这样理解:

Cilium 是一个面向云原生环境的网络、安全与可观测性平台。

再展开一点:

Cilium 主要用于 Kubernetes、容器、微服务以及相关云原生基础设施中,负责让服务之间能够通信、控制谁能访问谁、并帮助你看清楚网络里发生了什么。 Cilium 官方也将其定位为面向 Kubernetes 等云原生环境的 networking、security、observability 项目。(Cilium)

所以,Cilium 不是单纯的“网卡插件”,也不是单纯的“防火墙”,而是把下面几类能力组合在了一起:

Cilium
├── 网络连接 Networking
├── 网络安全 Security
├── 可观测性 Observability
├── 负载均衡 Load Balancing
├── 服务网格 Service Mesh
├── 多集群连接 Cluster Mesh
└── eBPF 数据平面 eBPF Datapath

1. 第一层递归:Cilium 解决什么问题?

在现代系统里,应用通常不是一个大程序,而是很多小服务组成的。

比如一个电商系统:

用户请求
  ↓
frontend 前端服务
  ↓
user-service 用户服务
  ↓
order-service 订单服务
  ↓
payment-service 支付服务
  ↓
database 数据库

这些服务可能运行在:

容器 Container
Pod
Kubernetes Node
不同 Kubernetes 集群
虚拟机 VM
云服务器
本地数据中心

这就带来三个核心问题:

问题 1:它们怎么连?
问题 2:谁允许访问谁?
问题 3:出了问题怎么看见?

Cilium 对应给出三个核心答案:

怎么连?        → Networking
谁能访问谁?    → Security / Network Policy
怎么观察?      → Observability / Hubble

2. 第二层递归:Networking 是什么?

2.1 Networking 的最简单解释

Networking 就是让服务之间能互相通信。

比如:

frontend 可以访问 backend
backend 可以访问 database
payment 可以访问 external-bank-api

在 Kubernetes 中,服务通信不是简单的“机器 A 连机器 B”。因为 Pod 会频繁创建、销毁、迁移:

今天 backend 的 IP 是 10.0.1.23
明天 backend 重启后变成 10.0.2.88
后天 backend 扩容成 10 个 Pod

如果安全规则、路由规则都依赖固定 IP,就会非常脆弱。

所以 Cilium 要解决的是:

动态工作负载之间的稳定通信问题

也就是:

应用在变
Pod 在变
IP 在变
节点在变
但通信规则和安全语义要稳定

2.2 继续递归:Cilium 怎么实现 Networking?

Cilium 的核心做法是:

用 eBPF 在 Linux 内核中处理网络数据包

也就是说,当一个 Pod 发出网络请求时,请求不只是走传统的 iptables 规则,而是可以被 Cilium 加载到内核中的 eBPF 程序处理。

Cilium 文档中提到,它用 eBPF 实现分布式负载均衡,并使用高效的哈希表来支持高服务密度和大规模低延迟场景;它也可以在 socket 层重写服务连接,从而避免逐包 NAT 的开销,并可替代 kube-proxy。(docs.cilium.io)

递归拆开:

Cilium Networking
├── Pod-to-Pod 通信
├── Service 负载均衡
├── 集群内东西向流量
├── 集群外南北向流量
├── 多集群互联
├── 外部服务访问
└── kube-proxy 替代

2.3 东西向流量和南北向流量

这两个词很常见。

东西向流量 East-West Traffic

指系统内部服务之间的通信。

例如:

frontend → user-service
user-service → order-service
order-service → payment-service

它们都在集群内部,像地图上左右移动,所以叫“东西向”。

南北向流量 North-South Traffic

指外部用户或外部系统和集群之间的通信。

例如:

用户浏览器 → Kubernetes Ingress / Gateway → frontend
payment-service → 银行 API

也就是“外部进来”或“内部出去”。

Cilium 同时处理这两类流量。


3. 第二层递归:Security 是什么?

3.1 Security 的最简单解释

Security 就是控制谁能访问谁。

不是所有服务都应该互相访问。

比如:

frontend 可以访问 user-service
frontend 不应该直接访问 database
order-service 可以访问 payment-service
random-test-pod 不应该访问 payment-service

这就是网络安全策略。


3.2 继续递归:传统网络安全有什么问题?

传统网络安全通常基于:

源 IP
目标 IP
端口
协议

例如:

允许 10.0.1.23 访问 10.0.2.10:5432

问题是,在 Kubernetes 中 IP 经常变化:

Pod 重启 → IP 变
Pod 迁移 → IP 变
Pod 扩容 → IP 数量变
节点故障 → 调度位置变

所以基于 IP 的安全策略在动态环境中很容易变得脆弱。


3.3 Cilium 的核心安全思想:身份,而不是 IP

Cilium 的重要思想是:

不要只相信 IP
要识别工作负载身份

也就是说,它更关心:

这个请求来自哪个服务?
这个服务有哪些 Kubernetes labels?
它属于哪个 namespace?
它是不是 payment 组件?
它是不是 production 环境?

而不是只看:

它现在的 IP 是多少?

Cilium 官方文档说明,Cilium Network Policy 提供 L3-L7 的 identity-aware enforcement;为避免依赖易变的 IP 地址,Cilium 会给共享相同安全策略的应用容器分配 security identity,并把身份关联到应用发出的网络包上。(docs.cilium.io)

这就是:

Identity-based Security

也就是基于身份的安全


4. 第三层递归:L3、L4、L7 是什么?

Cilium 经常说自己支持:

L3-L7 network policy

这是什么意思?

它来自网络模型。


4.1 L3:网络层

L3 主要关心 IP。

例如:

允许访问 10.0.0.0/16
禁止访问 192.168.1.0/24

L3 规则回答的是:

能不能访问这个网络地址?

4.2 L4:传输层

L4 主要关心端口和协议。

例如:

允许访问 TCP 443
允许访问 TCP 5432
禁止访问 TCP 6379

L4 规则回答的是:

能不能访问这个端口?

比如:

frontend → backend:8080 允许
frontend → database:5432 禁止

4.3 L7:应用层

L7 关心具体应用协议,比如:

HTTP
gRPC
Kafka
DNS

L7 规则可以细到:

只允许 GET /public/*
禁止 POST /admin/*
只允许访问 api.example.com
只允许调用某个 gRPC 方法

Cilium 文档中给出的例子包括按 HTTP method、URL path、gRPC call 等进行过滤,例如只允许 GET 请求访问 /public/.*。(docs.cilium.io)

所以 Cilium 的安全能力可以从粗到细递归展开:

Cilium Security
├── L3:控制 IP / CIDR
├── L4:控制端口 / 协议
├── DNS:控制域名访问
└── L7:控制 HTTP / gRPC / Kafka 等应用请求

5. 第二层递归:Observability 是什么?

5.1 Observability 的最简单解释

Observability 就是看清楚系统里发生了什么。

在微服务系统中,问题经常是这样的:

用户说支付失败
开发说代码没问题
运维说服务都活着
安全说策略没改过
网络说流量好像被拒绝了

这时你需要回答:

哪个服务访问了哪个服务?
请求有没有被允许?
被哪条策略拒绝了?
DNS 解析到了哪里?
HTTP 请求路径是什么?
延迟在哪里变高了?

这就是可观测性。


5.2 Cilium 的可观测性组件:Hubble

Cilium 生态中用于网络可观测性的核心组件叫 Hubble

Hubble 可以帮助你看到:

服务之间的流量
请求方向
源身份
目标身份
DNS 请求
HTTP / gRPC 信息
策略允许或拒绝
服务依赖关系图

Cilium 文档描述 Hubble 是一个集成的可观测性平台,提供实时 service map、带 identity 和 label metadata 的流量可见性、DNS-aware filtering 和协议级洞察;Cilium 也能与 Prometheus、Grafana 等监控系统集成。(docs.cilium.io)

递归展开:

Observability
├── Flow Visibility:看到网络流
├── Service Map:看到服务依赖图
├── DNS Visibility:看到域名访问
├── Policy Verdict:看到允许 / 拒绝
├── Metrics:指标
├── Tracing:追踪
└── Troubleshooting:排障

6. 第二层递归:eBPF 是什么?

6.1 eBPF 的最简单解释

eBPF 是一种可以在 Linux 内核中安全运行小程序的技术。

更直白一点:

以前想改变内核行为:
要么改 Linux 内核源码
要么写内核模块

现在:
可以用 eBPF 在内核中挂载安全、受限制的小程序

eBPF 官方文档将其描述为起源于 Linux 内核的技术,可以在操作系统内核这样的特权上下文中运行沙箱程序,并且可以安全、高效地扩展内核能力,而不需要修改内核源码或加载内核模块。(ebpf.io)


6.2 继续递归:为什么网络、安全、观测都喜欢 eBPF?

因为网络包本来就要经过 Linux 内核。

如果你能在内核路径上做判断,就可以更早、更快地处理问题:

网络包刚进入节点
  ↓
eBPF 程序检查
  ↓
决定允许、拒绝、转发、负载均衡、记录事件

而不是等包走完很多传统路径之后再处理。

所以 eBPF 很适合:

网络转发
负载均衡
网络策略
安全审计
系统调用观测
性能分析
流量追踪

eBPF 程序是事件驱动的,可以挂在系统调用、函数入口/出口、内核 tracepoint、网络事件等 hook 点上;加载进内核前还会经过 verifier 检查,以确保程序不会伤害系统、不会无限循环、不会越界访问内存。(ebpf.io)


7. 第三层递归:Cilium 和 eBPF 的关系

可以这样理解:

eBPF 是底层技术
Cilium 是基于 eBPF 构建出来的网络安全平台

类比:

eBPF 像发动机技术
Cilium 像一辆完整的车

eBPF 提供能力
Cilium 把能力产品化、平台化、Kubernetes 化

再换个比喻:

Linux 内核 = 城市道路系统
eBPF = 可以安装在路口的智能交通规则
Cilium = 交通管理平台
Kubernetes Pod = 城市里的车辆
Network Policy = 交通规则
Hubble = 监控摄像头和交通大屏

当一个 Pod 发出请求时:

Pod 发包
  ↓
Linux 内核网络路径
  ↓
Cilium eBPF 程序拦截 / 判断 / 转发
  ↓
检查身份
  ↓
检查策略
  ↓
做负载均衡
  ↓
记录可观测数据
  ↓
请求到达目标服务

8. 第四层递归:Cilium 的控制平面和数据平面

理解 Cilium,最好区分两个概念:

控制平面 Control Plane
数据平面 Data Plane

8.1 数据平面:真正处理流量的地方

数据平面负责处理真实网络包。

在 Cilium 中,数据平面主要由:

eBPF programs
eBPF maps
Linux kernel hooks

组成。

它做这些事情:

转发数据包
执行网络策略
做服务负载均衡
记录流量信息
处理 NAT / 路由相关逻辑

8.2 控制平面:生成规则和配置的地方

控制平面负责理解 Kubernetes 状态。

例如:

有哪些 Pod?
Pod 的 labels 是什么?
有哪些 Service?
Service 后面有哪些 Endpoint?
有哪些 NetworkPolicy?
哪些身份对应哪些 workloads?

Cilium 的 cilium-agent 运行在每个节点上,接受来自 Kubernetes 或 API 的配置,理解网络、负载均衡、网络策略、可见性与监控需求;它还监听 Kubernetes 等编排系统的事件,并管理 Linux 内核用于控制容器网络访问的 eBPF 程序。(docs.cilium.io)

递归展开:

Cilium Agent
├── 监听 Kubernetes API
├── 发现 Pod / Service / Endpoint / Policy
├── 计算身份 Identity
├── 生成或更新 eBPF 配置
├── 管理 eBPF maps
└── 让内核按最新规则处理流量

9. 用一个完整请求递归串起来

假设有这样一个请求:

frontend Pod 想访问 payment-service

Cilium 会如何参与?


9.1 第一步:frontend 发起请求

frontend → payment-service

应用代码可能只是访问:

http://payment-service/pay

它不关心 payment-service 背后有多少个 Pod,也不关心它们在哪个节点。


9.2 第二步:Cilium 处理服务负载均衡

Kubernetes Service 背后可能有多个 payment Pod:

payment-service
├── payment-pod-1
├── payment-pod-2
└── payment-pod-3

Cilium 可以用 eBPF 做服务负载均衡,把请求转发到某个后端 Pod。


9.3 第三步:Cilium 检查身份

Cilium 不只是看源 IP,而是识别:

源服务是谁?
它有哪些 labels?
它在哪个 namespace?
它的 security identity 是什么?

例如:

app: frontend
namespace: production

目标可能是:

app: payment
namespace: production

9.4 第四步:Cilium 检查策略

假设策略是:

允许 app=frontend 访问 app=payment 的 TCP 443
禁止其他服务访问 payment

那么:

frontend → payment:443     允许
debug-pod → payment:443    拒绝
frontend → payment:22      拒绝

如果启用了 L7 策略,还可以继续检查:

GET /pay/status      允许
POST /admin/refund   拒绝

9.5 第五步:Cilium 记录可观测数据

Hubble 可以记录:

源:frontend
目标:payment
端口:443
协议:HTTP
路径:/pay
结果:FORWARDED / DROPPED
原因:policy allowed / policy denied

于是排障时你能看到:

是谁访问了谁
什么时候访问
访问是否成功
如果失败,为什么失败

10. Cilium 为什么适合微服务?

微服务的特点是:

服务多
实例多
变化快
调用关系复杂
边界模糊

传统网络安全更适合静态环境:

固定机器
固定 IP
固定端口
固定边界

而 Kubernetes / 微服务是动态环境:

Pod 随时重建
IP 随时变化
服务随时扩缩容
调用链随时变复杂

所以 Cilium 的优势是:

用身份代替 IP
用策略描述意图
用 eBPF 提升性能
用 Hubble 增强可见性
用 Kubernetes labels 对齐应用语义

也就是说,它不是只在“网络层”理解系统,而是试图在网络层、身份层、应用层之间建立连接。


11. 用一句递归定义总结 Cilium

可以这样定义:

Cilium
= 一个云原生网络安全平台
= 用 eBPF 在 Linux 内核中高效处理网络流量
= 为容器、微服务和虚拟机等工作负载提供连接、安全和可观测能力
= 通过身份而不是脆弱的 IP 来执行访问控制
= 通过 Hubble 让服务间通信变得可见

再压缩成一句话:

Cilium 是一个基于 eBPF 的云原生网络、安全与可观测性平台,它把 Linux 内核变成一个可编程的数据平面,用来高效连接、保护和观察容器化应用、微服务以及相关工作负载。