mixs启动入口在项目mixer/cmd/mixs/main.go中。
// 从mixer/pkg/template包获取所有注册的模板信息。
func supportedTemplates() map[string]template.Info {
return generatedTmplRepo.SupportedTmplInfo
}
// 从mixer/pkg/adapter包获取所有注册的适配器信息。
func supportedAdapters() []adptr.InfoFn {
return adapter.Inventory()
}
func main() {
// 构造cobra.Command实例,mixs server子命令设计在serverCmd方法中定义。
// supportedTemplates() map[string]template.Info 和 supportedAdapters() []adptr.InfoFn
// 这两个方法都是自动生成的,包含编译的Mixer支持的模板、适配器的列表
// 模板/适配器信息中包含其属性清单
rootCmd := cmd.GetRootCmd(os.Args[1:], supportedTemplates(), supportedAdapters(), shared.Printf, shared.Fatalf)
if err := rootCmd.Execute(); err != nil {
os.Exit(-1)
}
}
继续看serverCmd方法。
func serverCmd(info map[string]template.Info, adapters []adapter.InfoFn, printf, fatalf shared.FormatFn) *cobra.Command {
// 默认Mixer参数
sa := server.DefaultArgs()
// 使用自动生成的模板、适配器信息
sa.Templates = info
sa.Adapters = adapters
serverCmd := &cobra.Command{
Use: "server",
Short: "Starts Mixer as a server",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
// 调用runServer启动服务
runServer(sa, printf, fatalf)
},
}
}
进入runServer方法里面。
func runServer(sa *server.Args, printf, fatalf shared.FormatFn) {
printf("Mixer started with\n%s", sa)
// 创建服务器对象
s, err := server.New(sa)
if err != nil {
fatalf("Unable to initialize Mixer: %v", err)
}
// 启动gRPC服务
s.Run()
// 等待shutdown信号可读
err = s.Wait()
if err != nil {
fatalf("Mixer unexpectedly terminated: %v", err)
}
// 关闭并执行清理工作
_ = s.Close()
}
server.New
// 创建一个全功能的Mixer服务器,并且准备好接收请求
func New(a *Args) (*Server, error) {
return newServer(a, newPatchTable())
}
server.Run
// 启动Mixs服务器
func (s *Server) Run() {
// 准备好关闭通道
s.shutdown = make(chan error, 1)
// 设置可用性状态,并通知探针控制器,探针被嵌入到Server
s.SetAvailable(nil)
go func() {
// 启动gRPC服务,传入原始套接字的监听器对象
err := s.server.Serve(s.listener)
// 关闭通道
s.shutdown <- err
}()
}
server.Wait
func (s *Server) Wait() error {
if s.shutdown == nil {
return fmt.Errorf("server not running")
}
// 在shutdown通道上等待
err := <-s.shutdown
s.shutdown = nil
return err
}
创建一个新的patchTable结构。
func newPatchTable() *patchTable {
return &patchTable{
newRuntime: runtime.New,
configTracing: tracing.Configure,
startMonitor: startMonitor,
listen: net.Listen,
configLog: log.Configure,
runtimeListen: func(rt *runtime.Runtime) error { return rt.StartListening() },
remove: os.Remove,
newOpenCensusExporter: func() (view.Exporter, error) {
return prometheus.NewExporter(prometheus.Options{Registry: oprometheus.DefaultRegisterer.(*oprometheus.Registry)})
},
registerOpenCensusViews: view.Register,
}
}
此结构就是几个函数的集合。
type patchTable struct {
// 此函数创建一个Runtime,Runtime是Mixer运行时环境的主要入口点
// 它监听配置、实例化Handler、创建分发机制(dispatch machinery)、处理请求
newRuntime func(s store.Store, templates map[string]*template.Info, adapters map[string]*adapter.Info,
defaultConfigNamespace string, executorPool *pool.GoroutinePool,
handlerPool *pool.GoroutinePool, enableTracing bool) *runtime.Runtime
// 配置追踪系统,通常在启动时调用一次,此调用返回后,追踪系统可以接受数据
configTracing func(serviceName string, options *tracing.Options) (io.Closer, error)
// 暴露Mixer自我监控信息的HTTP服务
startMonitor func(port uint16, enableProfiling bool, lf listenFunc) (*monitor, error)
// 监听本地端口并返回一个监听器
listen listenFunc
// 配置Istio的日志子系统
configLog func(options *log.Options) error
// 让Runtime开始监听配置变更,每当配置变更,Runtime处理新配置并创建Dispatcher
runtimeListen func(runtime *runtime.Runtime) error
// 一个remove方法,用来再unix系统移除套接字
remove func(name string) error
// 监控相关方法
newOpenCensusExporter func() (view.Exporter, error)
registerOpenCensusViews func(...*view.View) error
}
gRPC server启动主要逻辑在istio/mixer/pkg/server/server.go#newServer:
首先看下返回的Server类型数据结构。
type Server struct {
// 关闭通道
shutdown chan error
// 服务API请求的gRPC服务器
server *grpc.Server
// API线程池
gp *pool.GoroutinePool
// 适配器线程池
adapterGP *pool.GoroutinePool
// API网络监听器
listener net.Listener
// 监控服务器,此结构包含两个字段,一个是http.Server,一个是关闭通道
monitor *monitor
// 用于关闭追踪子系统
tracer io.Closer
// 可伸缩的策略检查缓存
checkCache *checkcache.Cache
// 将入站API调用分发给配置好的适配器
dispatcher dispatcher.Dispatcher
// ControlZ监听器
controlZ *ctrlz.Server
// probes
livenessProbe probe.Controller
readinessProbe probe.Controller
// 管理探针控制器所需要的可用性状态,内嵌
*probe.Probe
// mixer配置存储
configStore store.Store
}
func newServer(a *Args, p *patchTable) (*Server, error) {
// 校验Mixs启动参数
if err := a.validate(); err != nil {
return nil, err
}
// 配置日志子系统
if err := p.configLog(a.LoggingOptions); err != nil {
return nil, err
}
s := &Server{}
// 初始化API worker线程池
s.gp = pool.NewGoroutinePool(apiPoolSize, a.SingleThreaded)
s.gp.AddWorkers(a.APIWorkerPoolSize - 1)
// 初始化adapter worker线程池
s.adapterGP = pool.NewGoroutinePool(adapterPoolSize, a.SingleThreaded)
s.adapterGP.AddWorkers(a.AdapterWorkerPoolSize - 1)
// 构造存放Mixer模板仓库
tmplRepo := template.NewRepository(a.Templates)
// 从适配器名称到adapter.Info的映射
adapterMap := config.AdapterInfoMap(a.Adapters, tmplRepo.SupportsTemplate)
// 状态探针
s.Probe = probe.NewProbe()
if a.LivenessProbeOptions.IsValid() {
s.livenessProbe = probe.NewFileController(a.LivenessProbeOptions)
s.RegisterProbe(s.livenessProbe, "server")
s.livenessProbe.Start()
}
// 构建gRPC选项
var grpcOptions []grpc.ServerOption
grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(uint32(a.MaxConcurrentStreams)), grpc.MaxRecvMsgSize(int(a.MaxMessageSize)))
// 如果启用了追踪(tracing.option提供了ZipkinURL、JaegerURL或LogTraceSpans=true)
// OpenTracing、Prometheus监控拦截器,都来自项目https://github.com/grpc-ecosystem
if a.TracingOptions.TracingEnabled() {
s.tracer, err = p.configTracing("istio-mixer", a.TracingOptions)
if err != nil {
return nil, fmt.Errorf("unable to setup tracing")
}
// 添加基于OpenTracing的追踪拦截器
grpcOptions = append(grpcOptions, grpc.UnaryInterceptor(TracingServerInterceptor(ot.GlobalTracer())))
}
// 获取网络设置信息
network, address := extractNetAddress(a.APIPort, a.APIAddress)
if network == "unix" {
// 如果监听unix socket,则移除先前的文件
if err = p.remove(address); err != nil && !os.IsNotExist(err) {
// 除了文件未找到以外的错误,都不允许.
return nil, fmt.Errorf("unable to remove unix://%s: %v", address, err)
}
}
// 调用net.Listen监听
if s.listener, err = p.listen(network, address); err != nil {
return nil, fmt.Errorf("unable to listen: %v", err)
}
// ConfigStore用于测试目的,通常都会使用ConfigStoreURL(例如k8s:///home/alex/.kube/config)
st := a.ConfigStore
if st == nil {
configStoreURL := a.ConfigStoreURL
if configStoreURL == "" {
configStoreURL = "k8s://"
}
// Registry存储URL scheme与后端实现之间的对应关系
reg := store.NewRegistry(config.StoreInventory()...)
groupVersion := &schema.GroupVersion{Group: crd.ConfigAPIGroup, Version: crd.ConfigAPIVersion}
// 创建一个Store实例,它持有Backend,Backend代表一个无类型的Mixer存储后端 —— 例如K8S
// 默认情况下,configStoreURL的Scheme为k8s,Istio会调用config/crd.NewStore
// 传入configStoreURL、GroupVersion、criticalKinds 来创建Backend
if st, err = reg.NewStore(configStoreURL, groupVersion, a.CredentialOptions, runtimeconfig.CriticalKinds()); err != nil {
return nil, fmt.Errorf("unable to connect to the configuration server: %v", err)
}
}
// 所有模板、目标决定了各分类的适配器(例如所有metric类适配器)在运行时需要处理的数据类型
templateMap := make(map[string]*template.Info, len(a.Templates))
for k, v := range a.Templates {
t := v // Make a local copy, otherwise we end up capturing the location of the last entry
templateMap[k] = &t
}
// 判断是否通过CRD获得的配置
var configAdapterMap map[string]*adapter.Info
if a.UseAdapterCRDs {
configAdapterMap = adapterMap
}
var configTemplateMap map[string]*template.Info
if a.UseTemplateCRDs {
configTemplateMap = templateMap
}
// 生成adapter、template等对象类型到它的proto消息的映射(合并到一个映射中)
// adapter.Info.DefaultConfig、template.Info.CtrCfg,以及
// &configpb.Rule{}、&configpb.AttributeManifest{}、&v1beta1.Info{} ...都实现了proto.Message接口
kinds := runtimeconfig.KindMap(configAdapterMap, configTemplateMap)
// 初始化配置存储的backend
if err := st.Init(kinds); err != nil {
return nil, fmt.Errorf("unable to initialize config store: %v", err)
}
// 等待配置存储同步完成
log.Info("Awaiting for config store sync...")
if err := st.WaitForSynced(a.ConfigWaitTimeout); err != nil {
return nil, err
}
// 把配置存入server实例中
s.configStore = st
// 构造Mixer runtime实例。runtime实例是Mixer运行时环境的主要入口。
// 它会监听配置变更,配置变更时会动态构造新的handler实例和dispatcher实例。
// dispatcher会基于配置和attributes对请求进行调度,调用相应的adapters处理请求。
rt = p.newRuntime(st, templateMap, adapterMap, a.ConfigIdentityAttribute, a.ConfigDefaultNamespace,
s.gp, s.adapterGP, a.TracingOptions.TracingEnabled())
// runtime实例初始化,并开始监听配置变更,一旦配置变更,runtime实例会构造新的dispatcher。
if err = p.runtimeListen(rt); err != nil { _ = s.Close()
return nil, fmt.Errorf("unable to listen: %v", err)
}
// 设置分发器,分发器负责将API请求分发给配置好的适配器处理
s.dispatcher = rt.Dispatcher()
// 这里将缓存关闭,详见https://github.com/istio/istio/issues/9596
a.NumCheckCacheEntries = 0
// 如果启用了策略检查缓存,则创建LRU缓存对象
if a.NumCheckCacheEntries > 0 {
s.checkCache = checkcache.New(a.NumCheckCacheEntries)
}
// 此全局变量决定是否利用包golang.org/x/net/trace进行gRPC调用追踪
grpc.EnableTracing = a.EnableGRPCTracing
// 创建一个exporter,作用是上传检测数据
exporter, err := p.newOpenCensusExporter()
if err != nil {
return nil, fmt.Errorf("could not build opencensus exporter: %v", err)
}
// 注册exporter
view.RegisterExporter(exporter)
// 注册收集request的视图
if err := p.registerOpenCensusViews(ocgrpc.DefaultServerViews...); err != nil {
return nil, fmt.Errorf("could not register default server views: %v", err)
}
// 节流阀,限制调用频度
throttler := loadshedding.NewThrottler(a.LoadSheddingOptions)
// Evaluator方法根据名称返回配置好的LoadEvaluator
// LoadEvaluator能够评估请求是否超过阈值
if eval := throttler.Evaluator(loadshedding.GRPCLatencyEvaluatorName); eval != nil {
grpcOptions = append(grpcOptions, grpc.StatsHandler(newMultiStatsHandler(&ocgrpc.ServerHandler{}, eval.(*loadshedding.GRPCLatencyEvaluator))))
} else {
grpcOptions = append(grpcOptions, grpc.StatsHandler(&ocgrpc.ServerHandler{}))
}
// 创建gRPC服务器
s.server = grpc.NewServer(grpcOptions...)
// 注册服务到gRPC服务器
// 注册时需要提供grpc.ServiceDesc,其中包含服务名、方法集合(方法名到处理函数的映射
// api.NewGRPCServer返回 mixerpb.MixerServer 接口,它仅仅包含Check / Report两个方法
mixerpb.RegisterMixerServer(s.server, api.NewGRPCServer(s.dispatcher, s.gp))
// 探针启动
if a.ReadinessProbeOptions.IsValid() {
s.readinessProbe = probe.NewFileController(a.ReadinessProbeOptions)
rt.RegisterProbe(s.readinessProbe, "dispatcher")
st.RegisterProbe(s.readinessProbe, "store")
s.readinessProbe.Start()
}
// 启动监控服务
if s.monitor, err = p.startMonitor(a.MonitoringPort, a.EnableProfiling, p.listen); err != nil {
return nil, fmt.Errorf("unable to setup monitoring: %v", err)
}
// 启动ControlZ监听器,ControlZ提供了Istio的内省功能。Mixer与ctrlz集成时,会启动一个
// web service监听器用于展示Mixer的环境变量、参数版本信息、内存信息、进程信息、metrics等。
go ctrlz.Run(a.IntrospectionOptions, nil)
return s, nil
}
patchTable的newRuntime函数会调用runtime.New,创建一个新的Mixer运行时 —— Mixer运行时环境的主要入口点,负责监听配置、实例化Handler、创建分发机制(dispatch machinery)、处理请求。
func New(
s store.Store,
templates map[string]*template.Info,
adapters map[string]*adapter.Info,
defaultConfigNamespace string,
executorPool *pool.GoroutinePool,
handlerPool *pool.GoroutinePool,
enableTracing bool) *Runtime {
// Ephemeral表示一个短暂的配置状态,它可以被入站配置变更事件所更新
// Ephemeral本身包含的数据没有价值,你必须调用它的BuildSnapshot方法来创建稳定的、完全解析的配置的快照
e := config.NewEphemeral(templates, adapters)
rt := &Runtime{
// 默认配置命名空间
defaultConfigNamespace: defaultConfigNamespace,
// 短暂配置状态
ephemeral: e,
// 配置快照
snapshot: config.Empty(),
// 适配器处理器列表
handlers: handler.Empty(),
// API请求分发器,需要协程池
dispatcher: dispatcher.New(executorPool, enableTracing),
// 适配器处理器的协程池
handlerPool: handlerPool,
// 探针
Probe: probe.NewProbe(),
// 配置存储
store: s,
}
// 从ephemeral构建出新c.snapshot、新c.handlers、新路由表(用于解析入站请求并将其路由给适当的处理器)
// 然后替换路由表,最后清理上一次配置对应的处理器
rt.processNewConfig()
// 设置探针结果为:尚未监听存储
rt.Probe.SetAvailable(errNotListening)
return rt
}
创建Runtime之后,p.runtimeListen被调用。此函数会调用Runtime.StartListening方法来监听配置的变更,同样会立即触发processNewConfig调用。之后,processNewConfig调用会通过store.WatchChanges的回调反复发生。
func (c *Runtime) StartListening() error {
// Runtime的状态锁
c.stateLock.Lock()
defer c.stateLock.Unlock()
if c.shutdown != nil {
return errors.New("already listening")
}
// 开始监控存储,返回当前资源集(key到spec的映射)、监控用的通道
data, watchChan, err := store.StartWatch(c.store)
if err != nil {
return err
}
// 设置并覆盖相同的临时状态,其实就是把ephemeral.entries = data
c.ephemeral.SetState(data)
// 处理新配置
c.processNewConfig()
// 初始化运行时的关闭通道
c.shutdown = make(chan struct{})
// 增加一个计数
c.waitQuiesceListening.Add(1)
go func() {
// 只有shutdown通道关闭,此监控配置存储变化的循环才会退出
// 当有新的配置变更被发现后,调用onConfigChange,此方法会导致processNewConfig
store.WatchChanges(watchChan, c.shutdown, watchFlushDuration, c.onConfigChange)
// shutdown通道关闭后
c.waitQuiesceListening.Done()
}()
// 重置可用性状态,此等待组不再阻塞,StopListening方法可以顺利返回
c.Probe.SetAvailable(nil)
return nil
}
当配置存储有变化后,Runtime的该方法会被调用。
func (c *Runtime) onConfigChange(events []*store.Event) {
// 更新或者删除ephemeral.entries中的条目
c.ephemeral.ApplyEvent(events)
// 对最新的配置进行处理
c.processNewConfig()
}
Runtime的processNewConfig方法负责处理从配置存储(K8S)中拉取的最新CRD,然后创建配置快照、创建处理器表、路由表,并改变Dispatcher的路由。
func (c *Runtime) processNewConfig() {
// 构建一个稳定的、完全解析的配置的快照
newSnapshot, err := c.ephemeral.BuildSnapshot()
log.Infof("Built new config.Snapshot: id='%d'", newSnapshot.ID)
if err != nil {
log.Error(err.Error())
}
// 记录当前运行时使用的处理器
oldHandlers := c.handlers
// 创建新的处理器表
newHandlers := handler.NewTable(oldHandlers, newSnapshot, c.handlerPool)
// 构建并返回路由表,路由表决定了什么条件下调用什么适配器
newRoutes := routing.BuildTable(
newHandlers, newSnapshot, c.defaultConfigNamespace, log.DebugEnabled())
// 改变分发器的路由,分发器负责基于路由表来调用适配器
oldContext := c.dispatcher.ChangeRoute(newRoutes)
// 修改实例变量
c.handlers = newHandlers
c.snapshot = newSnapshot
log.Debugf("New routes in effect:\n%s", newRoutes)
// 关闭旧的处理器,注意处理器实现了io.Closer接口,这个接口由Istio自己负责,和适配器开发无关
cleanupHandlers(oldContext, oldHandlers, newHandlers, maxCleanupDuration)
}
该方法生成一个完全解析的(没有任何外部依赖)的配置快照。快照主要包含静态、动态模板/适配器信息、以及规则信息
func (e *Ephemeral) BuildSnapshot() (*Snapshot, error) {
errs := &multierror.Error{}
// 获取快照id
id := e.nextID
e.nextID++
log.Debugf("Building new config.Snapshot: id='%d'", id)
// Allocate new monitoring context to use with the new snapshot.
monitoringCtx := context.Background()
e.lock.RLock()
// 处理属性清单,获得属性列表。清单来源有两个地方:
// 1、配置存储中attributemanifest类型的CR。第一次调用该方法时,尚未加载这些CR
// 2、自动生成的template.Info.AttributeManifests
// 注意清单中每个属性,都具有全网格唯一的名称
attributes := e.processAttributeManifests(monitoringCtx)
// 处理静态适配器的处理器配置 —— 各种适配器的CR/实例,获得处理器(HandlerStatic)列表
// 对于从配置存储加载的资源,如果在自动生成的adapter.Info中找到对应条目,则认为是合法的处理器
// 对于每个处理器,会创建HandlerStatic结构,此结构表示基于Compiled-in的适配器的处理器
shandlers := e.processStaticAdapterHandlerConfigs()
// 返回属性描述符查找器(AttributeDescriptorFinder)
af := attribute.NewFinder(attributes)
e.attributes = af
// 处理静态模板的实例配置 —— 各种模板的CR,获得实例(InstanceStatic)列表
// 对于从配置存储加载的资源,如果在自动生成的template.Info中找到对应条目,则认为是合法的实例
// 对于每个实例,会创建InstanceStatic结构,此结构表示基于Compiled-in的模板的Instance
instances, instErrs := e.processInstanceConfigs(errs)
// 开始处理动态资源,所谓动态资源,是指没有特定CRD的模板(也就没有对应CR的实例)
// 以及没有特定CRD的适配器(也就没有对应CR的处理器)
// 动态模板注册为template类型的CR
dTemplates := e.processDynamicTemplateConfigs(monitoringCtx, errs)
// 动态适配器注册为adapter类型的CR
dAdapters := e.processDynamicAdapterConfigs(monitoringCtx, dTemplates, errs)
// 动态处理器注册为handler类型的CR,它必须引用某个adapter的名称
dhandlers := e.processDynamicHandlerConfigs(monitoringCtx, dAdapters, errs)
// 动态处理器注册为instance类型的CR,它必须引用某个template的名称
dInstances, dInstErrs := e.processDynamicInstanceConfigs(dTemplates, errs)
// 处理规则,规则可以引用上述的静态和动态资源
rules := e.processRuleConfigs(monitoringCtx, shandlers, instances, dhandlers, dInstances, errs)
stats.Record(monitoringCtx,
monitoring.HandlersTotal.M(int64(len(shandlers)+len(dhandlers))),
monitoring.InstancesTotal.M(int64(len(instances)+len(dInstances))),
monitoring.RulesTotal.M(int64(len(rules))),
monitoring.AdapterInfosTotal.M(int64(len(dAdapters))),
monitoring.TemplatesTotal.M(int64(len(dTemplates))),
monitoring.InstanceErrs.M(instErrs+dInstErrs),
)
// 构建配置快照
s := &Snapshot{
ID: id,
Templates: e.templates,
Adapters: e.adapters,
TemplateMetadatas: dTemplates,
AdapterMetadatas: dAdapters,
Attributes: af,
HandlersStatic: shandlers,
InstancesStatic: instances,
Rules: rules,
HandlersDynamic: dhandlers,
InstancesDynamic: dInstances,
MonitoringContext: monitoringCtx,
}
e.lock.RUnlock()
log.Debugf("config.Snapshot creation error=%v, contents:\n%s", errs.ErrorOrNil(), s)
return s, errs.ErrorOrNil()
}
看一下processAttributeManifests,processStaticAdapterHandlerConfigs,processInstanceConfigs这三个方法。
func (e *Ephemeral) processAttributeManifests(ctx context.Context) map[string]*config.AttributeManifest_AttributeInfo {
// 新建attribute map
// key是指属性名称
// value中有两个属性,一个是Description,是指这个属性的描述,是可选字段
// 另外一个是ValueType,是指这个属性的数据类型,这个是必填字段
attrs := make(map[string]*config.AttributeManifest_AttributeInfo)
// 遍历所有的实体(例如K8S配置)
for k, obj := range e.entries {
// 跳过不是attributemanifest类型的资源
// attributemanifest定义了属性的集合资源(CRD),istio默认有两个CR
// 一个是istioproxy,是指proxy会上报的属性
// 一个是kubernetes,是指mixer中kubernetesenv adapter会收集的属性
if k.Kind != constant.AttributeManifestKind {
continue
}
log.Debug("Start processing attributes from changed manifest...")
cfg := obj.Spec
// 取出所有属性
for an, at := range cfg.(*config.AttributeManifest).Attributes {
// 记录到attribute map中
attrs[an] = at
log.Debugf("Attribute '%s': '%s'.", an, at.ValueType)
}
}
// 从静态模板(template)中追加属性
// 只有ATTRIBUTE_GENERATOR类型的模板(template)会生成属性,所以只需要检测这一类型的模板即可
// 属性的名字可以在Template.Info.AttributeManifests(即生成的template.gen.go)中找到
for _, info := range e.templates {
// 判断模板是否是ATTRIBUTE_GENERATOR类型的
if info.Variety != v1beta1.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR {
continue
}
log.Debugf("Processing attributes from template: '%s'", info.Name)
// 记录属性
for _, v := range info.AttributeManifests {
for an, at := range v.Attributes {
attrs[an] = at
log.Debugf("Attribute '%s': '%s'", an, at.ValueType)
}
}
}
log.Debug("Completed processing attributes.")
stats.Record(ctx, monitoring.AttributesTotal.M(int64(len(attrs))))
return attrs
}
func (e *Ephemeral) processStaticAdapterHandlerConfigs() map[string]*HandlerStatic {
// 新建handler map
// key格式是[handler name].handler.[namespace],例如prometheus.handler.istio-system
// value中有三个属性
// Name属性是handler的name,例如prometheus.istio-system
// Adapter则记录了handler对应的adapter信息
// Params中存放了handler中的参数
handlers := make(map[string]*HandlerStatic, len(e.adapters))
// 遍历所有的资源
for key, resource := range e.entries {
var info *adapter.Info
var found bool
// 找到handler类型的资源
if key.Kind == constant.HandlerKind {
log.Debugf("Static Handler: %#v (name: %s)", key, key.Name)
// 将spec属性存入handlerProto中
handlerProto := resource.Spec.(*config.Handler)
// 判断handler中的adapter是否存在
a, ok := e.adapters[handlerProto.CompiledAdapter]
if !ok {
continue
}
// 组装配置
staticConfig := &HandlerStatic{
// handler的name+命名空间
// example: stdio.istio-system
Name: key.Name + "." + key.Namespace,
Adapter: a,
Params: a.DefaultConfig,
}
// 如果handler还有额外的参数
if handlerProto.Params != nil {
// 克隆一份默认的配置
c := proto.Clone(staticConfig.Adapter.DefaultConfig)
if handlerProto.GetParams() != nil {
// 把handler的配置转成字典
dict, err := toDictionary(handlerProto.Params)
if err != nil {
log.Warnf("could not convert handler params; using default config: %v", err)
}
// 将handler的配置填充到默认的配置中
else if err := convert(dict, c); err != nil {
log.Warnf("could not convert handler params; using default config: %v", err)
}
}
staticConfig.Params = c
}
handlers[key.String()] = staticConfig
continue
}
// 判断kind不是handler的资源,这个资源的kind名称是否能在adapter map中找到
// 做这个判断的原因是因为之前的handler没有集中在一个CRD中,为了兼容老版的handler配置,所以有下面的判断
if info, found = e.adapters[key.Kind]; !found {
// This config resource is not for an adapter (or at least not for one that Mixer is currently aware of).
continue
}
adapterName := key.String()
log.Debugf("Processing incoming handler config: name='%s'\n%s", adapterName, resource.Spec)
cfg := &HandlerStatic{
Name: adapterName,
Adapter: info,
Params: resource.Spec,
}
handlers[cfg.Name] = cfg
}
return handlers
}
func (e *Ephemeral) processInstanceConfigs(errs *multierror.Error) (map[string]*InstanceStatic, int64) {
// 创建instance map
// key是[instance name].[kind].[namespace]
// value中有5个属性
// Name和key相同
// Template是对应的模板信息
// Param是template构建instance的参数
// InferredType是instance的推断类型
// Language 运行时语言
instances := make(map[string]*InstanceStatic, len(e.templates))
var instErrs int64
// 遍历所有的资源
for key, resource := range e.entries {
var info *template.Info
var found bool
var params proto.Message
instanceName := key.String()
// 找到类型为Instance类型的资源
if key.Kind == constant.InstanceKind {
inst := resource.Spec.(*config.Instance)
if inst.CompiledTemplate == "" {
continue
}
// 查找instance对应的template
info, found = e.templates[inst.CompiledTemplate]
if !found {
instErrs++
appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), "missing compiled template")
continue
}
// 拷贝一份template的配置
params = proto.Clone(info.CtrCfg)
buf := &bytes.Buffer{}
if inst.Params == nil {
inst.Params = &types.Struct{Fields: make(map[string]*types.Value)}
}
// 填充属性绑定
// 属性绑定是将instance中的属性绑定到mixer的属性列表中
// 比如属性生成的instance会用AttributeBindings这个字段
if len(inst.AttributeBindings) > 0 {
// 新建一个绑定
bindings := &types.Struct{Fields: make(map[string]*types.Value)}
// 添加绑定属性
for k, v := range inst.AttributeBindings {
bindings.Fields[k] = &types.Value{Kind: &types.Value_StringValue{StringValue: v}}
}
// 将绑定加到inst的params field里面
inst.Params.Fields["attribute_bindings"] = &types.Value{
Kind: &types.Value_StructValue{StructValue: bindings},
}
}
// 将配置转成json
if err := (&jsonpb.Marshaler{}).Marshal(buf, inst.Params); err != nil {
instErrs++
appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error())
continue
}
// 再将转成json的配置填充到上面拷贝的template配置中
if err := (&jsonpb.Unmarshaler{AllowUnknownFields: false}).Unmarshal(buf, params); err != nil {
instErrs++
appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error())
continue
}
} else {
// 这里和handler一样,用来兼容老的配置
info, found = e.templates[key.Kind]
if !found {
// This config resource is not for an instance (or at least not for one that Mixer is currently aware of).
continue
}
params = resource.Spec
}
mode := lang.GetLanguageRuntime(resource.Metadata.Annotations)
log.Debugf("Processing incoming instance config: name='%s'\n%s", instanceName, params)
inferredType, err := info.InferType(params, func(s string) (config.ValueType, error) {
return e.checker(mode).EvalType(s)
})
if err != nil {
instErrs++
appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error())
continue
}
cfg := &InstanceStatic{
Name: instanceName,
Template: info,
Params: params,
InferredType: inferredType,
Language: mode,
}
instances[cfg.Name] = cfg
}
return instances, instErrs
}
其中istio/mixer/pkg/api/grpcServer.go#NewGRPCServer函数中初始化了保存attributes的list和全局字典。
func NewGRPCServer(dispatcher dispatcher.Dispatcher, gp *pool.GoroutinePool, cache *checkcache.Cache, throttler *loadshedding.Throttler) mixerpb.MixerServer {
// 从globalList拷贝出list切片,list形如[]string{"source.ip","source.port","request.id"...}
list := attribute.GlobalList()
// 将以attribute.name作为key,index作为value,构造map。形如:map[string][int]{"source.ip":1, "source.port":2, "request.id":3...}
globalDict := make(map[string]int32, len(list))
for i := 0; i < len(list); i++ {
globalDict[list[i]] = int32(i)
}
return &grpcServer{
dispatcher: dispatcher,
gp: gp,
globalWordList: list,
globalDict: globalDict,
cache: cache,
throttler: throttler,
}
}
https://blog.gmem.cc/interaction-between-istio-mixer-and-envoy