本文分析的istio代码版本为0.8.0,commit为0cd8d67,commit时间为2018年6月18日。
本文为Service Mesh深度学习系列
之一:
,因为是old_pilot_repo目录下,可能与最新架构有出入,仅供参考。所谓的pilot包含两个组件:pilot-agent和pilot-discovery。图里的agent对应pilot-agent二进制,proxy对应envoy二进制,它们两个在同一个容器中,discovery service对应pilot-discovery二进制,在另外一个跟应用分开部署的单独的deployment中。
对于模块的命名方法,本文采用模块对应源码main.go所在包名称命名法。其他istio分析文章有其他命名方法。比如pilot-agent也被称为istio pilot,因为它在Kubernetes上的部署形式为一个叫istio-pilot的deployment。
pilot-agent在pilot/cmd包下面,是个单独的二进制。
pilot-agent跟envoy打包在同一个docker镜像里,镜像由Dockerfile.proxy定义。Makefile(include了tools/istio-docker.mk)把这个dockerfile build成了${HUB}/proxy:${TAG}
镜像,也就是Kubernetes里跟应用放在同一个pod下的sidecar。非Kubernetes情况下需要把pilot-agent、envoy跟应用部署在一起,这个就有点“污染”应用的意思了。
支持v2 api的sidecar镜像为proxyv2,镜像中包含的pilot-agent和envoy二进制文件和proxy镜像中的完全相同,只是使用不同的envoy bootstrap配置(envoy_bootstrap_tmpl.json vs. envoy_bootstrap_v2.json)。但是当前仅完成部分开发工作,makefile中build proxyv2镜像的target默认也不会自动执行。
以上的HUB和TAG是编译istio源码过程中makefile中的一些变量,HUB对应镜像保存的仓库,TAG默认为istio版本号,如0.8.0。
在proxy镜像中,pilot-agent负责的工作包括:
而envoy负责接受所有发往该pod的网络流量,分发所有从pod中发出的网络流量。
根据代码中的sidecar-injector-configmap.yaml(用来配置如何自动化地inject istio sidecar),inject过程中,除了proxy镜像作为sidecar之外,每个pod还会带上initcontainer(Kubernetes中的概念),具体镜像为proxy_init。proxy_init通过注入iptables规则改写流入流出pod的网络流量规则,使得流入流出pod的网络流量重定向到proxy的监听端口,而应用对此无感。
envoy的配置主要在pilot-agent的init方法与proxy命令处理流程的前半部分生成。其中init方法为pilot-agent二进制的命令行配置大量的flag与flag默认值,而proxy命令处理流程的前半部分负责将这些flag组装成为envoy的配置ProxyConfig对象。下面分析几个相对重要的配置。
pilot-agent的role类型为model包下的Proxy,决定了pilot-agent的“角色”,role包括以下属性:
registry值
role.IPAddress
rule.ID
role.Domain
Kubernetes
环境变量INSTANCE_IP
环境变量POD_NAME.环境变量POD_NAMESPACE
环境变量POD_NAMESPACE.svc.cluster.local
Consul
private IP,默认127.0.0.1
IPAddress.service.consul
service.consul
Other
private IP,默认127.0.0.1
IPAddress
“”
其中的private ip通过WaitForPrivateNetwork
函数获得。
Istio需要从服务注册中心(service registry)获取微服务注册的情况。当前版本中istio可以对接的服务注册中心类型包括:
Mock
MockRegistry is a service registry that contains 2 hard-coded test services
Config
ConfigRegistry is a service registry that listens for service entries in a backing ConfigStore
Kubernetes
KubernetesRegistry is a service registry backed by k8s API server
Consul
ConsulRegistry is a service registry backed by Consul
Eureka
EurekaRegistry is a service registry backed by Eureka
CloudFoundry
CloudFoundryRegistry is a service registry backed by Cloud Foundry.
官方文档说当前支持Kubernetes、Nomad with Consul,未来准备支持 Cloud Foundry、Apache Mesos。另外根据官方的feature成熟度文档,当前只有Kubernetes的集成达到stable程度,Consul、Eureka和Cloud Foundry都还是alpha水平。
agent.waitForExit会调用envoy.Run方法启动envoy进程,为此需要获取envoy二进制所在文件系统路径和flag两部分信息:
envoy二进制所在文件系统路径:evony.Run通过proxy.config.BinaryPath变量得知envoy二进制所在的文件系统位置,proxy就是envoy对象,config就是pilot-agent的main方法在一开始初始化的proxyConfig对象。里面的BinaryPath在pilot-agent的init方法中被初始化,初始值来自pilot/pkg/model/context.go
的DefaultProxyConfig
函数,值是/usr/local/bin/envoy
envoy的启动flag形式为下面的startupArgs,包含一个-c
指定的配置文件,还有一些flag。除了下面代码片段中展示的这些flag,还可以根据启动agent时的flag,再加上--concurrency
, --service-zone
等flag。
startupArgs := []string{"-c", fname,
"--restart-epoch", fmt.Sprint(epoch),
"--drain-time-s", fmt.Sprint(int(convertDuration(proxy.config.DrainDuration) / time.Second)),
"--parent-shutdown-time-s", fmt.Sprint(int(convertDuration(proxy.config.ParentShutdownDuration) / time.Second)),
"--service-cluster", proxy.config.ServiceCluster,
"--service-node", proxy.node,
"--max-obj-name-len", fmt.Sprint(MaxClusterNameLength),
}
关于以上启动envoy的flag及其值的解释:
--restart-epoch
:epoch决定了envoy hot restart的顺序,在后面会有详细描述,第一个envoy进程对应的epoch为0,后面新建的envoy进程对应epoch顺序递增1--drain-time-s
:在pilot-agent init函数中指定默认值为2秒,可通过pilot-agent proxy命令的drainDuration flag指定--parent-shutdown-time-s
:在pilot-agent init函数中指定默认值为3秒,可通过pilot-agent proxy命令的parentShutdownDuration flag指定--service-cluster
:在pilot-agent init函数中指定默认值为”istio-proxy”,可通过pilot-agent proxy命令的serviceCluster flag指定--service-node
:将agent.role的Type,IPAddress,ID和Domain用”~“连接起来而上面的-c
指定的envoy配置文件有几种生成的方式:
/etc/istio/proxy/envoy-rev%d.json
,这里的%d
会用epoch序列号代替。WriteBootstrap在envoy.Run方法中被调用举个例子的话,根据参考文献中某人实验,第一个envoy进程启动参数为:
-c /etc/istio/proxy/envoy-rev0.json --restart-epoch 0
--drain-time-s 45 --parent-shutdown-time-s 60
--service-cluster sleep
--service-node sidecar~172.00.00.000~sleep-55b5877479-rwcct.default~default.svc.cluster.local
--max-obj-name-len 189 -l info --v2-config-only
如果使用第三种方式自动生成默认的envoy配置文件,如上面例子中的envoy-rev0.json,那么pilot-agent的proxy命令处理流程中前半部分整理的大量envoy参数中的一部分会被写入这个配置文件中,比如DiscoveryAddress
,DiscoveryRefreshDelay
,ZipkinAddress
,StatsdUdpAddress
。
agent会监控chainfile,keyfile和rootcert三个证书文件的变化,如果是Ingress工作模式,则还会加入ingresscert、ingress key这两个证书文件。
为envoy生成好配置文件之后,pilot-agent还要负责envoy进程的监控与管理工作,包括:
上面的pilot/pkg/proxy包下的agent中采用Proxy接口管理pilot/pkg/proxy/envoy包下的envoy对象,从理论上来说也可以把envoy换成其他proxy实现管理。不过此事还牵扯discovery service等其他组件。
上面第三步启动协程执行的agent.Run是agent的主循环,会一直通过监听以下几个channel来监控envoy进程:
agent的configCh:如果配置文件,主要是那些证书文件发生变化,则调用agent.reconcile来reload envoy
statusCh:这里的status其实就是exitStatus,处理envoy进程退出状态,处理流程如下:
把刚刚退出的epoch从agent维护的两个map里删了,后面会讲到这两个map。把agent.currentConfig置为agent.latestEpoch对应的config,因为agent在reconcile的过程中只有在desired config和current config不同的时候才会创建新的epoch,所以这里把currentConfig设置为上一个config之后,必然会造成下一次reconcile的时候current与desired不等,从而创建新的envoy
如果exitStatus.err是errAbort,表示是agent让envoy退出的(这个error是调用agent.abortAll时发出的),这时只要log记录epoch序列号为xxx的envoy进程退出了
如果exitStatus.err并非errAbort,则log记录epoch异常退出,并给所有当前正在运行的其他epoch进程对应的abortCh发出errAbort,所以后续其他envoy进程也都会被kill掉,并全都往agent.statusCh写入exitStatus,当前的流程会全部再为每个epoch进程走一遍
如果是其他exitStatus(什么时候会进入这个否则情况?比如exitStatus.err是wait epoch进程得到的正常退出信息,即nil),则log记录envoy正常退出
调用envoy.Cleanup,删除刚刚退出的envoy进程对应的配置文件,文件路径由ConfigPath和epoch序列号串起来得到
如果envoy进程为非正常退出,也就是除了“否则”描述的case之外的2中情况,则试图恢复刚刚退出的envoy进程(可见前面向所有其他进程发出errAbort消息的意思,并非永远停止envoy,pilot-agent接下来马上就会重启被abort的envoy)。恢复方式并不是当场启动新的envoy,而是schedule一次reconcile。如果启动不成功,可以在得到exitStatus之后再次schedule(每次间隔时间为 $2^n*200$ 毫秒 ),最多重试10次(budget),如果10次都失败,则退出整个golang的进程(os.Exit),由容器环境决定如何恢复pilot-agent。所谓的schedule,就是往agent.retry.restart写入一个预定的未来的某个时刻,并扣掉一次budget(budget在每次reconcile之前都会被重置为10),然后就结束当前循环。在下一个开始的时候,会检测agent.retry.restart,如果非空,则计算距离reconcile的时间delay
time.After(delay):监听是否到时间执行schedule的reconcile了,到了则执行agent.reconcile
ctx.Done:执行agent.terminate terminate方法比较简单,向所有的envoy进程的abortCh发出errAbort消息,造成他们全体被kill(Cmd.Kill),然后agent自己return,退出当前的循环,这样就不会有人再去重启envoy
前面pilot-agent proxy命令处理流程中,watcher.Run会调用agent.ScheduleConfigUpdate,这个方法只是简单地往configCh里写一个新的配置,所谓的配置是所有certificate算出的sha256哈希值
configCh的这个事件会被agent.Run监控到,然后调用agent.reconcile。
reconcile方法会启动协程执行agent.waitForExit从而启动envoy 看reconcile方法名就知道是用来保证desired config和current config保持一致的。reconcile首先会检查desired config和current config是否一致,如果是的话,就不用启动新的envoy进程。否则就启动新的envoy。在启动过程中,agent维护两个map来管理一堆envoy进程,在调用waitForExit之前会将desiredConfig赋值给currentConfig,表示reconcile工作完成:
waitForExit会调用agent.proxy.Run,也就是envoy的Run方法,这里会启动envoy。envoy的Run方法流程如下:
调用exec.Cmd.Start方法(启动了一个新进程),并将envoy的标准输出和标准错误置为os.Stdout和Stderr。
持续监听前面说到由agent创建并管理的,并与envoy进程一一对应的abortCh,如果收到abort事件通知,则会调用Cmd.Process.Kill方法杀掉envoy,如果杀进程的过程中发生错误,也会把错误信息log一下,然后把从abortCh读到的事件返回给waitForExit。waitForExit会把该错误再封装一下,加入epoch序列号,然后作为envoy的exitStatus,并写入到agent.statusCh里
启动一个新的协程来wait刚刚启动的envoy进程,并把得到的结果写到done channel里,envoy结构体的Run方法也会监听done channel,并把得到的结果返回给waitForExit
这里我们总结启动envoy过程中的协程关系:agent是全局唯一一个agent协程,它在启动每个envoy的时候,会再启动一个waitForExit协程,waitForExit会调用Command.Start启动另外一个进程运行envoy,然后waitForExit负责监听abortCh和envoy进程执行结果。
Cmd.Wait只能用于等待由Cmd.Start启动的进程,如果进程结束并范围值为0,则返回nil,如果返回其他值则返回ExitError,也可能在其他情况下返回IO错误等,Wait会释放Cmd所占用的所有资源
每次配置发生变化,都会调用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.
而为了触发这种hot restart的机制,让新envoy进程替换之前所有的envoy进程,新启动的envoy进程的epoch序列号必须比之前所有envoy进程的最大epoch序列号大1。
代码注释原文: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.
丁轶群博士,谐云科技CTO
2004年作为高级技术顾问加入美国道富银行(浙江)技术中心,负责分布式大型金融系统的设计与研发。2011年开始领导浙江大学开源云计算平台的研发工作,是浙江大学SEL实验室负责人,2013年获得浙江省第一批青年科学家称号,CNCF会员,多次受邀在Cloud Foundry, Docker大会上发表演讲,《Docker:容器与容器云》主要作者之一。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章