network-agent:节点网络配置谁在管
本文基于 CubeSandbox 开源仓库中
network-agent/目录下的源码进行分析。
概述
network-agent 是 CubeSandbox 的节点本地网络组件,负责管理单个节点上所有沙箱的网络配置。它是从 Cubelet 中分离出来的独立服务,主要职责包括:
- TAP 设备生命周期管理:创建、分配、回收 TAP 虚拟网卡
- IPAM 地址分配:管理沙箱的 IP 地址池
- 端口映射:HostPort 到容器端口的映射
- CubeVS 集成:调用 eBPF 程序配置网络数据面
- 状态持久化:网络状态落盘,支持重启恢复
架构总览
启动流程
network-agent 的启动流程在 cmd/network-agent/main.go 中定义:
关键配置参数
go
// 网络配置
-eth-name // 节点上联网卡名称
-cidr // TAP 沙箱 CIDR,默认 192.168.0.0/18
-mvm-inner-ip // MVM 内部 IP,默认 169.254.68.6
-mvm-mac-addr // MVM MAC 地址
-mvm-gw-dest-ip // MVM 网关 IP
-mvm-mask // 子网掩码位数
-mvm-mtu // MTU 大小
// 服务端点
-listen // HTTP API 端点
-grpc-listen // gRPC 端点
-tap-fd-listen // TAP FD 传递端点
-health-listen // 健康检查端点
// 状态管理
-state-dir // 状态持久化目录核心接口
network-agent 定义了最小化的网络服务接口:
go
// Service 接口定义
type Service interface {
// 确保网络配置(创建 TAP、配置 IP、端口映射等)
EnsureNetwork(ctx context.Context, req *EnsureNetworkRequest) (*EnsureNetworkResponse, error)
// 释放网络资源
ReleaseNetwork(ctx context.Context, req *ReleaseNetworkRequest) (*ReleaseNetworkResponse, error)
// 协调网络状态(修复不一致)
ReconcileNetwork(ctx context.Context, req *ReconcileNetworkRequest) (*ReconcileNetworkResponse, error)
// 查询网络信息
GetNetwork(ctx context.Context, req *GetNetworkRequest) (*GetNetworkResponse, error)
// 列出所有网络
ListNetworks(ctx context.Context, req *ListNetworksRequest) (*ListNetworksResponse, error)
// 健康检查
Health(ctx context.Context) error
}EnsureNetwork 流程
TAP 设备生命周期
TAP 池管理
network-agent 维护一个 TAP 设备池,避免频繁创建/销毁:
go
type localService struct {
tapPool []*tapDevice // 可用 TAP 池
abnormalTapPool []*tapDevice // 异常 TAP 池
quarantinedTaps map[string]*tapDevice // 隔离的 TAP
destroyFailedTaps map[string]*tapDevice // 销毁失败的 TAP
}池操作:
go
// 入池 - 回收 TAP 设备
func (s *localService) enqueueTapLocked(tap *tapDevice) {
tap.InUse = false
tap.FailureCount = 0
s.tapPool = append(s.tapPool, tap)
}
// 出池 - 分配 TAP 设备
func (s *localService) dequeueTapLocked() *tapDevice {
for len(s.tapPool) > 0 {
tap := s.tapPool[0]
s.tapPool = s.tapPool[1:]
return tap
}
return nil // 池为空,需要创建新 TAP
}
// 标记异常
func (s *localService) enqueueAbnormalLocked(tap *tapDevice, stage string, err error) {
tap.FailureCount++
tap.LastStage = stage
s.abnormalTapPool = append(s.abnormalTapPool, tap)
}TAP 创建流程
TAP 回收流程
go
func (s *localService) recycleTapLocked(tap *tapDevice) {
// 1. 清理端口映射
s.clearPortMappings(tap)
// 2. 从 CubeVS 移除
cubevs.DelTAPDevice(tap.Index, tap.IP)
// 3. 归还 IP 到分配器
s.allocator.Release(tap.IP)
// 4. 重新配置 TAP(重置状态)
// 5. 加入池中供复用
s.enqueueTapLocked(tap)
}IPAM 地址分配
network-agent 使用位图(bitmap)实现高效的 IP 地址分配:
go
type ipAllocator struct {
sync.Mutex
maxIdx int // 最大已分配索引
mask int // 子网掩码位数
size int // 地址池大小
startIdx int // 起始地址索引
usedIPNum int // 已使用 IP 数量
bitmap []byte // 位图
gwIP net.IP // 网关 IP
}地址分配算法:
go
func (a *ipAllocator) Allocate() (net.IP, error) {
a.Lock()
defer a.Unlock()
// 检查是否耗尽
if a.usedIPNum >= a.size {
return nil, errIPExhausted
}
// 轮询查找空闲位
for range a.size {
a.maxIdx = (a.maxIdx + 1) % a.size
idx := a.maxIdx
if !a.existIdx(idx) {
a.setUsed(idx)
return a.idx2IP(idx), nil
}
}
return nil, errIPExhausted
}地址保留:
- 索引 0:网络地址
- 索引 1:网关地址
- 最后一个索引:广播地址
端口映射
端口映射使用独立的分配器管理 HostPort:
go
type portAllocator struct {
sync.Mutex
ports []bool // 端口使用标记
maxPort uint16 // 最大端口号
minPort uint16 // 最小端口号
}映射配置流程:
go
func (s *localService) configurePortMappings(tap *tapDevice,
requestedMappings []PortMapping) ([]PortMapping, error) {
for _, mapping := range requestedMappings {
hostPort := mapping.HostPort
if hostPort == 0 {
// 自动分配端口
allocatedPort, _ := s.ports.Allocate()
hostPort = int32(allocatedPort)
} else {
// 使用指定端口
s.ports.Assign(uint16(hostPort))
}
// 调用 CubeVS 配置 eBPF 端口映射
cubevs.AddPortMapping(tap.Index, mapping.ContainerPort, hostPort)
}
}状态持久化
network-agent 使用文件系统持久化网络状态,支持重启后恢复:
go
type persistedState struct {
SandboxID string
NetworkHandle string
IP net.IP
PortMappings []PortMapping
PersistMetadata map[string]string
}
type stateStore struct {
dir string // 状态目录
}恢复流程:
go
func (s *localService) recover() error {
// 1. 读取状态目录下的所有文件
states, _ := s.store.List()
for _, state := range states {
// 2. 验证 TAP 设备是否仍然存在
// 3. 验证 CubeVS maps 是否一致
// 4. 重建内存状态
s.states[state.SandboxID] = &managedState{
persistedState: state,
tap: tap,
}
}
}与 Cubelet 的协作
network-agent 作为独立服务,与 Cubelet 通过 API 协作:
协作流程:
- 创建沙箱:Cubelet 调用
EnsureNetwork,network-agent 创建 TAP 并返回网络句柄 - 获取 TAP FD:Cubelet 通过 FD Server 获取 TAP 设备的文件描述符,传递给容器运行时
- 删除沙箱:Cubelet 调用
ReleaseNetwork,network-agent 回收资源 - 状态同步:Cubelet 可以通过
ReconcileNetwork修复网络状态不一致
健康检查
network-agent 提供多层健康检查:
go
// HTTP 端点
GET /healthz // 存活检查
GET /readyz // 就绪检查
// gRPC 端点
Health(ctx context.Context) error检查内容:
- CubeVS eBPF 程序是否加载
- cube-dev 网关设备是否存在
- IP 地址池是否可用
- TAP 设备池是否正常
延伸阅读
- CubeVS:用 eBPF 做 TAP 与 DNAT — 了解 CubeVS eBPF 数据面实现
- CubeProxy:OpenResty 里的路由魔法 — 了解请求如何路由到正确的沙箱