Type
pilot-agent有三种运行模式。根据role.Type变量定义,类型为model.Proxy,定义在context.go文件中,允许的3个取值范围为:
i. "sidecar"
默认值,可以在启动pilot-agent,调用proxy命令时覆盖。Sidecar type is used for sidecar proxies in the application containers.
ii. "ingress"
Ingress type is used for cluster ingress proxies.
iii. "router"
Router type is used for standalone proxies acting as L7/L4 routers.
创建watcher并启动协程执行watcher.Runwatcher.Run首先启动协程执行agent.Run(agent的主循环),然后调用watcher.Reload(kickstart the proxy with partial state (in case there are no notifications coming)),Reload会调用agent.ScheduleConfigUpdate,并最终导致第一个envoy进程启动,见后面分析。然后监控各种证书,如果证书文件发生变化,则调用ScheduleConfigUpdate来reload envoy,然后watcher.retrieveAZ(TODO)。
statusCh:这里的status其实就是exitStatus,处理envoy进程退出状态,处理流程如下:
i. 把刚刚退出的epoch从agent维护的两个map里删了,后面会讲到这两个map。把agent.currentConfig置为agent.latestEpoch对应的config,因为agent在reconcile的过程中只有在desired config和current config不同的时候才会创建新的epoch,所以这里把currentConfig设置为上一个config之后,必然会造成下一次reconcile的时候current与desired不等,从而创建新的envoy。
ii. 如果exitStatus.err是errAbort,表示是agent让envoy退出的(这个error是调用agent.abortAll时发出的),这时只要log记录epoch序列号为xxx的envoy进程退出了。
iii. 如果exitStatus.err并非errAbort,则log记录epoch异常退出,并给所有当前正在运行的其他envoy进程对应的abortCh发出errAbort。所以后续其他envoy进程也都会被kill掉,并全都往agent.statusCh写入exitStatus,当前的流程会全部再为每个epoch进程走一遍。
iv. 如果是其他exitStatus(什么时候会进入这个情况?比如exitStatus.err是wait epoch进程得到的正常退出信息,即nil),则log记录epoch正常退出。
v. 调用envoy.Cleanup,删除刚刚退出的envoy进程对应的配置文件,文件路径由ConfigPath和epoch序列号串起来得到。
vi. 如果envoy进程为非正常退出,也就是除了“否则”描述的case之外的两种情况,则试图恢复刚刚退出的envoy进程(可见前面向所有其他进程发出errAbort消息的意思,并非永远停止envoy,pilot-agent接下来马上就会重启被abort的envoy)。恢复方式并不是当场启动新的envoy,而是schedule一次reconcile。如果启动不成功,可以在得到exitStatus之后再次schedule(每次间隔时间为2ⁿ*200毫秒),最多重试10次(budget),如果10次都失败,则退出整个golang的进程(os.Exit),由容器运行时环境决定如何恢复pilot-agent。所谓的schedule,就是往agent.retry.restart写入一个预定的未来的某个时刻,并扣掉一次budget(budget在每次reconcile之前都会被重置为10),然后就结束当前循环。在下一个循环开始的时候,会检测agent.retry.restart,如果非空,则计算距离reconcile的时间delay。
reconcile方法会启动协程执行agent.waitForExit从而启动envoy看reconcile方法名就知道是用来保证desired config和current config保持一致的。reconcile首先会检查desired config和current config是否一致,如果是的话,就不用启动新的envoy进程。否则就启动新的envoy。在启动过程中,agent维护两个map来管理一堆envoy进程,在调用waitForExit之前会将desiredConfig赋值给currentConfig,表示reconcile工作完成:
i. 第一个map是agent.epochs,它将整数epoch序列号映射到agent.desiredConfig。这个序列号从0开始计数,也就是第一个envoy进程对应epoch 0,后面递增1。但是如果有envoy进程异常退出,它对应的序列号并非是最大的情况下,这个空出来的序列号不会在计算下一个新的epoch序列号时(agent.latestEpoch方法负责计算当前最大的epoch序列号)被优先使用。所以从理论上来说序列号是会被用光的。
ii. 第二个map是agent.abortCh,它将epoch序列号映射到与envoy进程一一对应的abortCh。abortCh使得pilot-agent可以在必要时通知对应的envoy进程推出。这个channel初始化buffer大小为常量10,至于为什么需要10个buffer,代码中的注释说buffer aborts to prevent blocking on failing proxy,也就是万一想要abort某个envoy进程,但是envoy卡住了abort不了,有buffer的话,就不会使得管理进程也卡住。
每次配置发生变化,都会调用agent.reconcile,也就会启动新的envoy,这样envoy越来越多,老的envoy进程怎么办?agent代码的注释里已经解释了这问题,原来agent不用关闭老的envoy,同一台机器上的多个envoy进程会通过unix domain socket互相通讯,即使不同envoy进程运行在不同容器里,也一样能够通讯。而借助这种通讯机制,可以自动实现新envoy进程替换之前的老进程,也就是所谓的envoy hot restart。
代码注释原文:Hot restarts are performed by launching a new proxy process with a strictly incremented restart epoch. It is up to the proxy to ensure that older epochs gracefully shutdown and carry over all the necessary state to the latest epoch. The agent does not terminate older epochs.
代码注释原文:The restart protocol matches Envoy semantics for restart epochs: to successfully launch a new Envoy process that will replace the running Envoy processes, the restart epoch of the new process must be exactly 1 greater than the highest restart epoch of the currently running Envoy processes.