Skip to content

network-agent:节点网络配置谁在管

本文基于 CubeSandbox 开源仓库中 network-agent/ 目录下的源码进行分析。

概述

network-agent 是 CubeSandbox 的节点本地网络组件,负责管理单个节点上所有沙箱的网络配置。它是从 Cubelet 中分离出来的独立服务,主要职责包括:

  1. TAP 设备生命周期管理:创建、分配、回收 TAP 虚拟网卡
  2. IPAM 地址分配:管理沙箱的 IP 地址池
  3. 端口映射:HostPort 到容器端口的映射
  4. CubeVS 集成:调用 eBPF 程序配置网络数据面
  5. 状态持久化:网络状态落盘,支持重启恢复

架构总览

启动流程

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 协作:

协作流程

  1. 创建沙箱:Cubelet 调用 EnsureNetwork,network-agent 创建 TAP 并返回网络句柄
  2. 获取 TAP FD:Cubelet 通过 FD Server 获取 TAP 设备的文件描述符,传递给容器运行时
  3. 删除沙箱:Cubelet 调用 ReleaseNetwork,network-agent 回收资源
  4. 状态同步:Cubelet 可以通过 ReconcileNetwork 修复网络状态不一致

健康检查

network-agent 提供多层健康检查:

go
// HTTP 端点
GET /healthz    // 存活检查
GET /readyz     // 就绪检查

// gRPC 端点
Health(ctx context.Context) error

检查内容

  • CubeVS eBPF 程序是否加载
  • cube-dev 网关设备是否存在
  • IP 地址池是否可用
  • TAP 设备池是否正常

延伸阅读