概述
之前的问文章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 是通过 eBPF 来对网络进行管控的,因此 cilium-agent 的启动就是围绕 eBPF 配置初始化有关的过程,例如 Maps 和 tail call 的创建、更新等等。
参考资料
- cilium-code-agent-start
- cilium-code-agent-cidr-init
警告
本文最后更新于 2024年4月1日,文中内容可能已过时,请谨慎参考。