目录

cilium-agent进程的启动流程

概述

之前的问文章cilium-agent的DaemonSet启动流程分析了 cilium-agent 作为 DaemonSet Pod 的启动过程,本文走读一下 cilium-agent 的相关代码,解析 cilium-agent 进程本身启动的过程,了解某些需要关注的部分的源码以及逻辑。

整体结构

整体上 cilium-agent 启动是采用 Cobra 开发的 Agent Cli 的模式。其中 daemonCell 包装了尚未转换为单元的 cilium-agent 的实现。提供 Daemon 作为 Promise,一旦守护进程启动,该 Promise 就会被解析,以便于转换为模块。

1
2
3
4
5
cmd.Execute()
|- agentHive.Command()
  |- hive.New(Agent)
    |- daemonCell
      |- newDaemonPromise

代码分析

从上面的整体结构分析下来,我们具体看看 Daemon 的启动逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
newDaemonPromise
|- newDaemon
  |- bootstrapStats.daemonInit.Start() // 开启daemon启动指标采集和监控
  |- option.Config.Validate(Vp) // 检查配置
  |- option.Config.IdentityAllocationMode // 判断以什么方式判断身份,也是配置检查的一部分
  |- rate.NewAPILimiterSet // 流控
  |- option.Config.DryMode // 判断是否dry run,dry模式下不会创建eBPF映射和设备等等
  |- initKubeProxyReplacementOptions() // 在初始化ebpf maps之前做kube-proxy替换的一些初始化,这个方法的定义相当复杂...
  |- ctmap.InitMapInfo // 关于CT的映射
  |- policymap.InitMapInfo // 关于policy相关的映射
  |- lbmap.Init(lbmapInitParams) // lbmap是lbbpfmap接口的实现,实际调用的bpf.NewMap()创建eBPF Map
  |- params.NodeManager.Subscribe(params.Datapath.Node()) // 订阅Node的变化
  |- nodediscovery.NewNodeDiscovery // 自动发现新加入的节点
  |- linuxdatapath.NewDeviceManager() // 初始化可能被挂载BPF程序的设备管理器
  |- d.configModifyQueue.Run() // 监听配置变化
  |- ipcachemap.IPCacheMap().DumpWithCallback // 暂时没有细看其作用
  |- d.initPolicy() // 初始化Policy
  |- d.endpointManager = params.EndpointManager // 初始化EndpointManager
  |- d.svc = service.NewService(&d, d.l7Proxy, d.datapath.LBMap()) // 初始化Service
  |- d.cgroupManager = manager.NewCgroupManager() // 初始化cgroupManager
  |- d.k8sWatcher.RegisterNodeSubscriber // 注册Kubernetes资源变化的订阅者
  |- bootstrapStats.daemonInit.End(true) // 结束Daemon Init流程
  |- cleaner.cleanupFuncs.Add(func() // 配置Cleaner停止所有Endpoint
  |- bootstrapStats.mapsInit.Start() // 关于eBPF Map的初始化现在开始
  |- d.initMaps() // opens all BPF maps
    |- lxcmap.LXCMap().OpenOrCreate() // 
    |- ipcachemap.IPCacheMap().Recreate() //
    |- metricsmap.Metrics.OpenOrCreate() //
    |- d.svc.InitMaps //
    |- policymap.InitCallMaps //
    |- ep.InitMap() //
    |- ctmap.LocalMaps //
    |- ctmap.GlobalMaps //
    |- nat.GlobalMaps // 做NAT
    |- neighborsmap.InitMaps //
    |- fragmap.InitMap //
  |- bandwidth.ProbeBandwidthManager() // 提前做bm的探测
  |- d.startIPAM() // IPAM开启
  |- d.init() // 初始化
  |- d.restoreOldEndpoints // 恢复旧的Endpoint
  |- loader.RestoreTemplates // 恢复
|- runDaemon // 运行非模块化的旧的agent
  |- node.ValidatePostInit() // 检查节点
  |- gc.Enable() // 启动连接跟踪垃圾收集器
  |- d.initRestore // 根据节点上的Map信息恢复
  |- d.endpointManager.AddHostEndpoint //
  |- d.initHealth // 启动健康检查
  |- d.startStatusCollector(cleaner) //
  |- d.startAgentHealthHTTPService() // 
  |- d.processQueuedDeletes() //
  |- d.instantiateAPI(params.SwaggerSpec) // 启动unix的接口服务
  |- d.launchHubble() //

bootstrapStats.daemonInit.Start()

在 Cilium 里,大部分的指标都是这样统计的,这里统计的是 cilium-agent 的启动的过程的时间指标,其中 Start()End() 分别记录当前的时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func newDaemon(ctx context.Context, cleaner *daemonCleanup, params *daemonParams) (*Daemon, *endpointRestoreState, error) {
    bootstrapStats.daemonInit.Start()
    ...
    ...
    ...
    bootstrapStats.daemonInit.End(true)
}

// Start starts a new span
func (s *SpanStat) Start() *SpanStat {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.spanStart = time.Now()
	return s
}

// End ends the current span and adds the measured duration to the total
// cumulated duration, and to the success or failure cumulated duration
// depending on the given success flag
func (s *SpanStat) End(success bool) *SpanStat {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	return s.end(success)
}

option.Config.Validate(Vp)

Cilium 的配置项非常多,不熟悉的话,很容易配置到互相矛盾的配置项导致启动失败,下面是配置检查的具体流程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Validate
  |- validateIPv6ClusterAllocCIDR
    |- c.GetIPv4NativeRoutingCIDR() == nil && c.EnableIPv4Masquerade && !c.TunnelingEnabled() &&
		c.IPAMMode() != ipamOption.IPAMENI && c.EnableIPv4 && c.IPAMMode() != ipamOption.IPAMAlibabaCloud
  |- validateIPv6NAT46x64CIDR
    |- c.GetIPv6NativeRoutingCIDR() == nil && c.EnableIPv6Masquerade && !c.TunnelingEnabled() &&
		c.EnableIPv6
  |- c.MTU < 0
  |- c.RouteMetric < 0
  |- c.IPAM == ipamOption.IPAMENI && c.EnableIPv6
  |- c.EnableIPv6NDP
  |- c.RoutingMode
  |- c.TunnelProtocol
  |- c.RoutingMode == RoutingModeNative && c.UseSingleClusterRoute
  |- c.ClusterID < clustermeshTypes.ClusterIDMin || c.ClusterID > clustermeshTypes.ClusterIDMax
  |- c.ClusterID != 0
  |- c.checkMapSizeLimits()
  |- c.checkIPv4NativeRoutingCIDR()
  |- c.checkIPv6NativeRoutingCIDR()
  |- c.checkIPAMDelegatedPlugin()
  |- c.KVstoreLeaseTTL > defaults.KVstoreLeaseMaxTTL || c.KVstoreLeaseTTL < defaults.LockLeaseTTL
  |- c.EnableVTEP

rate.NewAPILimiterSet

这个是流控相关的配置。NewAPILimiterSet 根据一组速率限制配置和默认配置创建一个新的 APILimiterSet。在配置中配置的任何速率限制器或默认值都将通过 Limiter(name) 和 Wait() 函数进行配置并使其可用,在 Cilium 中,不同的 CRUD 接口可以配置不同的限速参数,在集群规模大的时候,这个是需要注意的。

1
2
// apiRateLimitDefaults
rate.NewAPILimiterSet(option.Config.APIRateLimit, apiRateLimitDefaults, ratemetrics.APILimiterObserver())

initKubeProxyReplacementOptions()

TODO

ctmap.InitMapInfo

TODO

policymap.InitMapInfo

1
policymap.InitMapInfo(option.Config.PolicyMapEntries)

lbmap.Init(lbmapInitParams)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
lbmap.Init(lbmapInitParams)
|- initSVC(params)
  |- Service4MapV2 // cilium_lb4_services_v2
  |- Backend4Map // cilium_lb4_backends
  |- Backend4MapV2 // cilium_lb4_backends_v2
  |- Backend4MapV3 // cilium_lb4_backends_v3
  |- RevNat4Map // cilium_lb4_reverse_nat
|- initAffinity(params)
  |- AffinityMatchMap // cilium_lb_affinity_match
  |- Affinity4Map // cilium_lb4_affinity
|- initSourceRange(params)
  |- SourceRange4Map // cilium_lb4_source_range

d.initMaps()

initMaps 打开所有 BPF 映射(如果不存在则创建它们)。这必须在读取 BPF 映射的任何操作(尤其是恢复端点和服务)之前完成,这里初始化了不同类型的 Maps,包括普通的映射,也包括 tail calls 用的映射。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
initMaps()
  |- lxcmap.LXCMap().OpenOrCreate() // cilium_lxc
  |- ipcachemap.IPCacheMap().Recreate() // cilium_ipcache
  |- metricsmap.Metrics.OpenOrCreate() // cilium_metrics
  |- tunnel.TunnelMap().Recreate() // cilium_tunnel_map
  |- worldcidrsmap.InitWorldCIDRsMap() // cilium_world_cidrs4
  |- vtep.VtepMap().Recreate() // cilium_vtep_map
  |- d.svc.InitMaps()
  |- policymap.InitCallMaps // cilium_call_policy
  |- ep.InitMap()
  |- ctmap.LocalMaps
  |- ctmap.GlobalMaps
  |- nat.GlobalMaps
  |- neighborsmap.InitMaps 
    |- neigh4Map.Create() // cilium_nodeport_neigh4
  |- fragmap.InitMap // cilium_ipv4_frag_datagrams
  |- ipmasq.IPMasq4Map().OpenOrCreate() // cilium_ipmasq_v4
  |- lbmap.AffinityMatchMap.OpenOrCreate() // cilium_lb_affinity_match
  |- lbmap.Affinity4Map.OpenOrCreate() // cilium_lb4_affinity
  |- lbmap.SourceRange4Map.OpenOrCreate() // cilium_lb4_source_range
  |- lbmap.InitMaglevMaps // cilium_lb4_maglev
  
ebpf.OpenOrCreate()
  |- registerMap  

d.restoreOldEndpoints

在分配任何 IP 之前恢复端点,以避免稍后发生 IP 冲突,否则任何 IP 冲突都将导致端点无法恢复。restoreOldEndpoints 执行恢复端点结构的第二步,从 CIDR 块中分配其现有 IP,然后将端点插入端点列表中。一旦端点构建器准备就绪,就需要调用 regenerateRestoredEndpoints()。如果 clean 为 true,则删除无法与容器工作负载关联的端点。

1
2
3
restoredEndpoints, err := d.fetchOldEndpoints(option.Config.StateDir)
d.restoreOldEndpoints(restoredEndpoints, true)
|- state.restored = append(state.restored, ep) // 这里会记录需要被恢复的Endpoint信息

启动远程Debug

为了更好的观察 cilium-agent 启动的时候代码里都发生了什么,推荐启动远程 Debug。

/cilium-agent%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/img.png

总结

Cilium 是通过 eBPF 来对网络进行管控的,因此 cilium-agent 的启动就是围绕 eBPF 配置初始化有关的过程,例如 Maps 和 tail call 的创建、更新等等。

参考资料

  1. cilium-code-agent-start
  2. cilium-code-agent-cidr-init
警告
本文最后更新于 2024年4月1日,文中内容可能已过时,请谨慎参考。