[Serie de análisis del código fuente de Thanos] Un breve análisis del código fuente del componente sidecar de Thanos

1. Información general:

1.1 Entorno del código fuente

La información de la versión es la siguiente:
a. Versión del componente Thanos: v0.16.0

1.2 El papel de Thanos Sidecar

El componente Thanos Query está vinculado a la instancia de prometheus y tiene tres funciones:
1) Como agente de acceso, expone la interfaz grpc al cliente. La lógica empresarial es acceder a la interfaz http de la instancia vinculada de prometheus para obtener métricas y datos de la regla y, finalmente, volver al cliente.
2) Si la función de almacenamiento de objetos está habilitada, todos los directorios de bloques del directorio promethues tsdb se cargarán en el sistema de almacenamiento de objetos especificado.
3) Supervise los cambios del archivo de configuración de promethues y descubra que también se accede a la interfaz http de la instancia de prometheus después de que el archivo cambie para que prometheus vuelva a cargar la configuración.
Inserte la descripción de la imagen aquí

2 Breve análisis del código fuente:

Utilice el paquete github.com/oklog/run para iniciar un conjunto de corrutinas. La lógica de estas corrutinas es principalmente iniciar el servidor http, el servidor grpc y descubrir dinámicamente los componentes posteriores que implementan la API de TIENDA.

2.1 método principal

El formato de comando de inicio de Thanos es el siguiente, y el formato comienza con thanos (porque es el mismo archivo binario ejecutable). Qué componente iniciar depende del primer parámetro, en este ejemplo es sidecar, por lo que este comando es la lógica para iniciar el componente sidecar.

thanos sidecar \
--prometheus.url=http://localhost:9090/ \
--tsdb.path=/prometheus \
--grpc-address=[$(POD_IP)]:10901 \
--http-address=[$(POD_IP)]:10902 \


Veamos el método principal en detalle. Cree un objeto de aplicación. El objeto de aplicación contiene las funciones de inicio de todos los componentes de Thanos, pero solo se toma una función del mapa para iniciarse cuando se inicia realmente. La función que se elimine depende del comando de inicio.

func main() {


	/*
		其他代码
	*/

	app := extkingpin.NewApp(kingpin.New(filepath.Base(os.Args[0]), "A block storage based long-term storage for Prometheus").Version(version.Print("thanos")))
	/*
		其他代码
	*/


	// 把所有组件的启动逻辑都放进app对象中的setups列表中
	registerSidecar(app)
	registerStore(app)
	registerQuery(app)
	registerRule(app)
	registerCompact(app)
	registerTools(app)
	registerReceive(app)
	registerQueryFrontend(app)

	// 根据命令行的信息,从app对象的setups列表中取出一个组件逻辑
	cmd, setup := app.Parse()
	logger := logging.NewLogger(*logLevel, *logFormat, *debugName)

	/*
		其他代码
	*/

	var g run.Group
	var tracer opentracing.Tracer
	
	/*
		tracing相关的代码
	*/
	
	
	reloadCh := make(chan struct{}, 1)

	// 启动特定的一个组件(sidecar、query、store等组件中的一种),底层还是执行g.Add(...)
	if err := setup(&g, logger, metrics, tracer, reloadCh, *logLevel == "debug"); err != nil {		
		os.Exit(1)
	}

	// 监听来自系统的杀死信号.
	{
		cancel := make(chan struct{})
		g.Add(func() error {
			return interrupt(logger, cancel)
		}, func(error) {
			close(cancel)
		})
	}

	// 监听来配置重载的信号
	{
		cancel := make(chan struct{})
		g.Add(func() error {
			return reload(logger, cancel, reloadCh)
		}, func(error) {
			close(cancel)
		})
	}

	// 阻塞地等待所有协程中的退出
	// 有一个协程返回,其他协程也会返回
	if err := g.Run(); err != nil {
		level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "%s command failed", cmd)))
		os.Exit(1)
	}
	
	// 到达此处,说明整个程序结束了。
	level.Info(logger).Log("msg", "exiting")
}

2.2 método registerQuery


func registerSidecar(app *extkingpin.App) {
	cmd := app.Command(component.Sidecar.String(), "Sidecar for Prometheus server")
	conf := &sidecarConfig{}
	// 解析命令行参数
	conf.registerFlag(cmd)
	
	// Setup()的入参方法,会被放入app对象的setups列表中
	// 最核心的是runSidecar()方法
	cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
		rl := reloader.New(log.With(logger, "component", "reloader"),
			extprom.WrapRegistererWithPrefix("thanos_sidecar_", reg),
			&reloader.Options{
				ReloadURL:     reloader.ReloadURLFromBase(conf.prometheus.url),
				CfgFile:       conf.reloader.confFile,
				CfgOutputFile: conf.reloader.envVarConfFile,
				WatchedDirs:   conf.reloader.ruleDirectories,
				WatchInterval: conf.reloader.watchInterval,
				RetryInterval: conf.reloader.retryInterval,
			})

		return runSidecar(g, logger, reg, tracer, rl, component.Sidecar, *conf)
	})
}

2.3 método runSidecar

Utilice el objeto run.Group para iniciar el servidor http, el servidor grpc, transferir el directorio de bloques a la rutina de almacenamiento de objetos, supervisar el archivo de configuración de prometheus y comprobar periódicamente la rutina de supervivencia de la instancia de prometheus.

Instrucciones detalladas:
1) Vea el mecanismo de latido de la instancia de prometheus a través de la interfaz / api / v1 / status / config.
2) El kit de herramientas para monitorear los cambios en el archivo de configuración de prometheus es github.com/fsnotify/fsnotify.
3) Active la función de carga de bloques, recorra todos los directorios de bloques en el directorio prometheus tsdb cada 30 segundos (los bloques cargados o los bloques vacíos se ignorarán y los bloques comprimidos también se ignorarán de forma predeterminada) y cargue el archivo correspondiente al almacenamiento de objetos .
4) Si la etiqueta externa de la instancia de prometheus no se puede obtener o la etiqueta externa no está configurada en prometheus, el sidecar no se iniciará.

func runSidecar(
	g *run.Group,
	logger log.Logger,
	reg *prometheus.Registry,
	tracer opentracing.Tracer,
	reloader *reloader.Reloader,
	comp component.Component,
	conf sidecarConfig,
) error {

	// 用一个结构体来保存prometheus实例的url、prometheus实例的external label、prometheus client等信息。
	var m = &promMetadata{
		promURL: conf.prometheus.url,

		mint: conf.limitMinTime.PrometheusTimestamp(),
		maxt: math.MaxInt64,

		limitMinTime: conf.limitMinTime,
		client:       promclient.NewWithTracingClient(logger, "thanos-sidecar"),
	}
	
	// 获取对象存储的配置信息,如果有,说明是开启上传block至对象存储的功能。
	confContentYaml, err := conf.objStore.Content()
	if err != nil {
		return errors.Wrap(err, "getting object store config")
	}
	var uploads = true
	if len(confContentYaml) == 0 {
		level.Info(logger).Log("msg", "no supported bucket was configured, uploads will be disabled")
		uploads = false
	}
	

	grpcProbe := prober.NewGRPC()
	httpProbe := prober.NewHTTP()
	statusProber := prober.Combine(
		httpProbe,
		grpcProbe,
		prober.NewInstrumentation(comp, logger, extprom.WrapRegistererWithPrefix("thanos_", reg)),
	)
	
	// 创建http server,并启动server(只有/metrics、/-/healthy、/-/ready等接口)
	srv := httpserver.New(logger, reg, comp, httpProbe,
		httpserver.WithListen(conf.http.bindAddress),
		httpserver.WithGracePeriod(time.Duration(conf.http.gracePeriod)),
	)
	g.Add(func() error {
		statusProber.Healthy()
		return srv.ListenAndServe()
	}, func(err error) {
		statusProber.NotReady(err)
		defer statusProber.NotHealthy(err)
		srv.Shutdown(err)
	})


	// 获取promehtues实例的external label,并做心跳
	{
		// promUp记录promehtues是否正常,0表示不正常,1表示正常
		promUp := promauto.With(reg).NewGauge(prometheus.GaugeOpts{
			Name: "thanos_sidecar_prometheus_up",
			Help: "Boolean indicator whether the sidecar can reach its Prometheus peer.",
		})
		// lastHeartbeat记录最后一次心跳时间
		lastHeartbeat := promauto.With(reg).NewGauge(prometheus.GaugeOpts{
			Name: "thanos_sidecar_last_heartbeat_success_time_seconds",
			Help: "Timestamp of the last successful heartbeat in seconds.",
		})

		ctx, cancel := context.WithCancel(context.Background())
		// 获取prometheus实例的external label(/api/v1/status/config接口),并通过定期(30s)做这件事情来做心跳
		g.Add(func() error {
			/*
				检查性代码
			*/
			
			// 获取prometheus实例的external label
			err := runutil.Retry(2*time.Second, ctx.Done(), func() error {
				// m.UpdateLabels(ctx)去访问prometheus实例的/api/v1/status/config接口,并将返回的数据设置到自己的属性labels
				if err := m.UpdateLabels(ctx); err != nil {						
					promUp.Set(0)
					statusProber.NotReady(err)
					return err
				}			
				promUp.Set(1)
				statusProber.Ready()
				// 记录心跳时间
				lastHeartbeat.SetToCurrentTime()
				return nil
			})
			
			// 拿不到prometheus实例的external label或者prometheus没有配置external label则退出
			if err != nil {
				return errors.Wrap(err, "initial external labels query")
			}			
			if len(m.Labels()) == 0 {
				return errors.New("no external labels configured on Prometheus server, uniquely identifying external labels must be configured; see https://thanos.io/tip/thanos/storage.md#external-labels for details.")
			}

			// 每个30s从prometheus实例获取exterlan label,通过此方式来记录心跳时间
			return runutil.Repeat(30*time.Second, ctx.Done(), func() error {				
				/*
					其他代码
				*/
				
				if err := m.UpdateLabels(iterCtx); err != nil {
					level.Warn(logger).Log("msg", "heartbeat failed", "err", err)
					promUp.Set(0)
				} else {
					promUp.Set(1)
					// 记录心跳时间
					lastHeartbeat.SetToCurrentTime()
				}
				return nil
			})
		}, func(error) {
			cancel()
		})
	}
	
	// 使用github.com/fsnotify/fsnotify包监听prometheus实例的配置文件的变化
	// 如果文件发生变化则发送一个POST请求给prometheus实例,让它重新加载配置文件
	{
		ctx, cancel := context.WithCancel(context.Background())
		g.Add(func() error {
			return reloader.Watch(ctx)
		}, func(error) {
			cancel()
		})
	}

	{
		t := exthttp.NewTransport()
		t.MaxIdleConnsPerHost = conf.connection.maxIdleConnsPerHost
		t.MaxIdleConns = conf.connection.maxIdleConns
		c := promclient.NewClient(&http.Client{Transport: tracing.HTTPTripperware(logger, t)}, logger, thanoshttp.ThanosUserAgent)

		promStore, err := store.NewPrometheusStore(logger, reg, c, conf.prometheus.url, component.Sidecar, m.Labels, m.Timestamps)
		if err != nil {
			return errors.Wrap(err, "create Prometheus store")
		}

		tlsCfg, err := tls.NewServerConfig(log.With(logger, "protocol", "gRPC"),
			conf.grpc.tlsSrvCert, conf.grpc.tlsSrvKey, conf.grpc.tlsSrvClientCA)
		if err != nil {
			return errors.Wrap(err, "setup gRPC server")
		}

		// 创建并grpc server
		s := grpcserver.New(logger, reg, tracer, comp, grpcProbe,
			// 注册grpc handler(通过http client从prometheus实例中获取指标数据)
			grpcserver.WithServer(store.RegisterStoreServer(promStore)),	
			// 注册grpc handler(通过http client从prometheus实例中获取rule数据)
			grpcserver.WithServer(rules.RegisterRulesServer(rules.NewPrometheus(conf.prometheus.url, c, m.Labels))), 
			grpcserver.WithListen(conf.grpc.bindAddress),
			grpcserver.WithGracePeriod(time.Duration(conf.grpc.gracePeriod)),
			grpcserver.WithTLSConfig(tlsCfg),
		)
		g.Add(func() error {
			statusProber.Ready()
			return s.ListenAndServe()
		}, func(err error) {
			statusProber.NotReady(err)
			s.Shutdown(err)
		})
	}

	// 若开启了上传block功能,则定期遍历prometehus tsdb目录下的所有block目录并上传文件至对象存储。
	if uploads {
		
		// 获取一个对象存储bucket
		bkt, err := client.NewBucket(logger, confContentYaml, reg, component.Sidecar.String())
		if err != nil {
			return err
		}
		
		/*
			其他代码
		*/

		ctx, cancel := context.WithCancel(context.Background())
		g.Add(func() error {
			/*
				其他代码
			*/

			/*
				拿不到prometheus实例的external label或者prometheus没有配置external label则退出
			*/
			
			s := shipper.New(logger, reg, conf.tsdb.path, bkt, m.Labels, metadata.SidecarSource,
				conf.shipper.uploadCompacted, conf.shipper.allowOutOfOrderUpload)
			
			// 每30执行一次s.Sync(ctx)
			// s.Sync(ctx)会遍历prometheus tsdb目录下的所有block目录(已上传的block或空block会被忽略,默认情况下被压缩过的block也会被忽略),并上传相应的文件
			return runutil.Repeat(30*time.Second, ctx.Done(), func() error {
				if uploaded, err := s.Sync(ctx); err != nil {
					// 至少有一个block上传失败,则打印日志
				}
				/*
					其他代码
				*/
				return nil
			})
		}, func(error) {
			cancel()
		})
	}
	
	level.Info(logger).Log("msg", "starting sidecar")
	return nil
}

3 Resumen:

La lógica del código del componente Thanos Sidecar es simple y fácil de entender. Accede a la instancia de prometheus vinculada a él a través del protocolo http. Los datos obtenidos de la instancia de prometheus se exponen a través de la interfaz grpc, atraviesa todos los directorios de bloques para la carga de archivos, y monitorea promethues.Pequeñas funciones para cambios de archivos de configuración.

Supongo que te gusta

Origin blog.csdn.net/nangonghen/article/details/110731518
Recomendado
Clasificación