Istio源码解析--Mixer启动流程

文章目录

  • main
  • runServer
  • newPatchTable
  • newServer
  • p.newRuntime
  • runtimeListen
  • onConfigChange
  • processNewConfig
  • BuildSnapshot
  • processAttributeManifests
  • processStaticAdapterHandlerConfigs
  • processInstanceConfigs
  • NewGRPCServer
  • 参考资料

代码基于istio 1.2.2版本的代码。

main

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

进入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
}

newPatchTable

创建一个新的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
}

newServer

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
}

p.newRuntime

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
}

runtimeListen

创建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
}

onConfigChange

当配置存储有变化后,Runtime的该方法会被调用。

func (c *Runtime) onConfigChange(events []*store.Event) {
	// 更新或者删除ephemeral.entries中的条目
   c.ephemeral.ApplyEvent(events)
	// 对最新的配置进行处理
   c.processNewConfig()
}

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)
}

BuildSnapshot

该方法生成一个完全解析的(没有任何外部依赖)的配置快照。快照主要包含静态、动态模板/适配器信息、以及规则信息

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这三个方法。

processAttributeManifests

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
}

processStaticAdapterHandlerConfigs

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
}

processInstanceConfigs


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
}

NewGRPCServer

其中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

你可能感兴趣的:(Istio)