Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Flow services #4253

Closed
rfratto opened this issue Jun 23, 2023 · 1 comment · Fixed by #4870
Closed

Proposal: Flow services #4253

rfratto opened this issue Jun 23, 2023 · 1 comment · Fixed by #4870
Labels
enhancement New feature or request flow Related to Grafana Agent Flow frozen-due-to-age Locked due to a period of inactivity. Please open new issues or PRs if more discussion is needed. proposal Proposal or RFC proposal-accepted Proposal has been accepted.
Milestone

Comments

@rfratto
Copy link
Member

rfratto commented Jun 23, 2023

The most recent copy of this proposal can be found on Google docs. The below may be out of date, but is left in for posterity.

Background

In the context of Flow, a "service" is a low-level primitive which exposes additional behavior on top of the Flow controller. Today, there are three services that exist in pkg/flow:

  • The telemetry service, allowing components to create metrics, logs, and traces for component observability.
  • The HTTP service, allowing components to expose an HTTP endpoint on a shared server for things such as Prometheus exporters.
  • The clustering service, allowing components to distribute work amongst themselves.
    • The clustering service depends on the HTTP service to function properly.

All of the services today are started prior to the Flow controller.

One of the remaining items for Flow feature parity is adding support for configuring TLS settings for the HTTP service at runtime. This would be added as a new config block into the Flow configuration file. This leads to a minimum set of functional requirements:

  • For security reasons, plaintext HTTP traffic must only be accepted when a root config is loaded that does not configure TLS.
  • If the TLS settings reference a component export (such as remote.vault), those components must be evaluated first to be able to evaluate the TLS settings properly.
  • TLS settings must not have a cyclic dependency: it is not valid for the TLS settings to depend on a component which depends on the HTTP service to function.
    • For example, prometheus.exporter.unix depends on the HTTP service to create its targets.

These functional requirements extend to the clustering service, which must only start after the HTTP service is ready.

To allow services to be configured at runtime, those services (any any service which depends on them) must now exist in the DAG to ensure proper evaluation order; today, only the telemetry service exists in the DAG.

Additionally, there has been ongoing work to prepare Grafana Agent for a 1.0 release. This included some preliminary offline discussions about moving Flow and River to a separate repository so they may have their own versions separate from Grafana Agent.

This proposal aims to solve both problems at once:

  1. Allow TLS settings to be configurable at runtime.
  2. Decouple the agent-specific HTTP and clustering services from pkg/flow in preparation for Flow and River to be moved to a dedicated Git repository.

Goals

  • Allow TLS settings to be configurable at runtime.
  • Ensure that plaintext HTTP traffic is only accepted when TLS is not configured.
  • Prevent cyclic dependencies between services and components.
  • Allow services to depend on other services.
  • Decouple the HTTP and clustering service from pkg/flow.
  • Keep definition of a service to a minimum for initial implementation.

Non-goals

  • Decouple telemetry APIs (metrics, logs, traces) from Flow.
    • Telemetry APIs are fundamental to all components, so it makes sense for telemetry to be the one thing always provided to Flow components, even after Flow is moved to a separate repository.
  • Ensure that existing usages of the service are unchanged.
    • Usages of the clustering service will have to change following the decoupling.

Design

A new service package will be introduced which defines the API that services will have at their disposal:

package service // import "github.com/grafana/agent/service"

Package service defines a pluggable service for the Flow system.

Services are low-level constructs which run for the lifetime of the 
Flow controller, and are given higher levels of access to the system 
compared to components, such as the ability to request components
which depend on a service.

TYPES

type Definition struct {
	// Name uniquely defines a service.
	Name string

	// ConfigType is an optional config type to configure a 
	// service at runtime.
	//
	// If provided, ConfigType must be a struct type with River
	// tags for decoding as a config block.
	//
	// If ConfigType is non-nil, the Name of the service is used
	// as the block name in River to configure the service.
	ConfigType interface{}

	// DependsOn defines a set of dependencies for a
	// specific service by name.
	DependsOn []string
}
    Definition describes an individual Flow service. Services have 
    unique names and optional ConfigTypes where they can be configured 
    within a root Flow module.

type Host interface {
	// GetComponent gets a running component by ID.
	GetComponent(id component.ID, opts component.InfoOptions) (*component.Info, error)

	// ListComponent lists all running components.
	ListComponent(opts component.InfoOptions) []*component.Info

	// GetServiceConsumers gets the list of components and services 
	// which depend on a service by name. The returned values will be 
	// an instance of [component.Component] or [Service].
	GetServiceConsumers(serviceName string) []any
}
    The Host is responsible for running a given service.

type Service interface {
	// Definition returns the Definition of the Service. Definition 
	// must always return the same value across all calls.
	Definition() Definition

	// Run starts a Service. Run must block until the provided context
	// is canceled. Returning an error should be treated as a fatal
	// error for the Service.
	Run(ctx context.Context) error

	// Update updates a Service at runtime. Update is never called
	// if [Definition.ConfigType] is nil. newConfig will be the same
	// type as ConfigType; if ConfigType is a pointer to a type,
	// newConfig will be a pointer to the same type.
	//
	// Update will be called once before Run, and may be called
	// while Run is active. 
	Update(newConfig interface{}) error

	// Data returns the Data associated with a Service. Data must
	// always return the same value across multiple calls, as callers
	// are expected to be able to cache the result.
	//
	// Data may be invoked before Run.  
	Data() interface{}
}
    Service is an individual service to run.

The component package will be updated to be aware of services:

package component // import "github.com/grafana/agent/component"

type Registration struct {
	... 

	// NeedsServices holds the set of service names which this 
	// component depends on to run. 
	NeedsServices []string  
}

type Options struct {
	...

	// GetServiceData retrieves data for a service by key. 
	//
	// GetServiceData will return an error if the service does not 
	// exist or was not listed as a dependency with the registration 
	// of the component.
	//
	// The result of GetServiceData may be cached as the value will 
	// not change at runtime.  
	GetServiceData func(name string) (interface{}, error)   
}

The flow package will also be updated to be aware of services:

package flow // import "github.com/grafana/agent/pkg/flow"

type Options struct {
	...

	// List of Services to run with the Flow controller.
	// 
	// Services will start when the Flow controller runs. 
	// Services will be configured when LoadFile is invoked. 
	Services []service.Service
}
	Options holds static options for a Flow controller.

Notes

  • The service.Host interface will be implemented by the Flow controller.
  • service.Host.GetServiceConsumers is required for the HTTP interface to install HTTP handlers for the clustering service, and for an optimization, look up components which depend on a service.
    • This must unfortunately return any to avoid circular dependencies. The component, flow, and service packages should be merged in the future to avoid this, but this change is out of scope for this proposal.
  • service.Service.Data can return anything which might be useful to components, such as information about the service in a struct (such as the HTTP listen address), or an interface so components can invoke methods (such as looking up the clustering owner for at token).
  • There is no way for a service to request a reload for a component; the clustering service will have to change its interface so that re-sharding data happens outside of a call to component.Component.Update.

Details

The Flow controller will create DAG nodes for each service assigned to it. Edges from service nodes will be based on:

  • Other services that each service depends on by their service.Definition.
  • Services that components depend on by their component.Registration.

Having services in the DAG will ensure that there are no cyclic dependencies.

Services will only run once the Flow controller is run; Flow.LoadFile must be called before Flow.Run to make sure that the HTTP service does not run before TLS is configured.

Refer to the design above for other aspects of how the services API behaves.

Thanks

Thanks to @erikbaranowski for his help on this proposal.

@rfratto rfratto added enhancement New feature or request proposal Proposal or RFC flow/feature-parity flow Related to Grafana Agent Flow labels Jun 23, 2023
rfratto added a commit to rfratto/agent that referenced this issue Jun 30, 2023
The `service/` package is used to define Flow services, low-level
constructs which run for the lifetime of the Flow controller.

This commit introduces just the API for services, without using it
anywhere.

Related to grafana#4253.
@rfratto
Copy link
Member Author

rfratto commented Jun 30, 2023

This proposal has been reviewed by all maintainers with consensus for implementing it.

Given there are no objections on the issue or on the doc, I'm marking this proposal as accepted.

@rfratto rfratto added the proposal-accepted Proposal has been accepted. label Jun 30, 2023
rfratto added a commit to rfratto/agent that referenced this issue Jun 30, 2023
The `service/` package is used to define Flow services, low-level
constructs which run for the lifetime of the Flow controller.

This commit introduces just the API for services, without using it
anywhere.

Related to grafana#4253.
rfratto added a commit that referenced this issue Jul 5, 2023
* service: introduce new service package

The `service/` package is used to define Flow services, low-level
constructs which run for the lifetime of the Flow controller.

This commit introduces just the API for services, without using it
anywhere.

Related to #4253.

* pkg/flow: implement service.Host in Flow controller

This commit updates the Flow controller to implement the new
service.Host interface.

Note that because it is not currently possible for a service or
component to define a dependency on a service, the GetServiceConsumers
method always returns nil.

GetServiceConsumers will be updated in the future to return a non-nil
list once it is possible for consumers of a service to exist.

* service/http: port HTTP service to a service implementation

This ports the HTTP service to implement the service.Service interface.
The old HTTP service code has been removed in favor of the service.

This required some hacks to wire everything in correctly; this will be
cleaned up in the future once services are fully implemented.
@mattdurham mattdurham added this to the v0.36.0 milestone Jul 25, 2023
rfratto added a commit to rfratto/agent that referenced this issue Jul 31, 2023
This change adds a new field to flow.Options, Services, which causes the
lifecycle management of services to be handled by the Flow controller.

Services are added to the DAG as nodes, and are run with the Flow
controller when the Flow controller starts. Services that declare a
dependency on another service are only evaluated after the services they
depend on have been evaluated.

GetServiceConsumers will now currently return instances of
component.Component and service.Service which depend on a service. The
set of component.Component dependants is currently empty since it is not
yet possible for a component to define a dependency on a service.

The follow items are left as follow-up work:

* Allow components to define a dependency on a service.

* Allow service data to be exposed to components which depend on that
  service.

* Allow services to propagate to loaded modules.

* Migrate existing services to be managed by the Flow controller.

Related to grafana#4253.
rfratto added a commit that referenced this issue Aug 1, 2023
* flow/internal/controller: rename files holding DAG node implementations

Rename all files holding DAG node implementations to start with node_ so
they're easier to locate.

* flow/internal/controller: add ServiceNode

ServiceNode is the representation of a Flow service in the DAG.

Note that it is not currently wired up anywhere.

* flow/internal/controller: make NewLoader more flexible

Create a new type, LoaderOptions, which passes options to a Loader, to
make extending Loader with new options more flexible.

LoaderOptions currently encapsulates ComponentGlobals, but will be
expanded in the future to contain other relevant options (such as
services to load in the DAG).

* flow/internal/controller: add service nodes to graph

Add service nodes to the graph when the graph is being constructed. If a
service declares a dependency on another service, it forms a DAG edge.

* flow/internal/controller: expose ServiceNodes to caller

This change stores and exposes ServiceNodes after calls to Apply.

* flow: enable the management of services

This change adds a new field to flow.Options, Services, which causes the
lifecycle management of services to be handled by the Flow controller.

Services are added to the DAG as nodes, and are run with the Flow
controller when the Flow controller starts. Services that declare a
dependency on another service are only evaluated after the services they
depend on have been evaluated.

GetServiceConsumers will now currently return instances of
component.Component and service.Service which depend on a service. The
set of component.Component dependants is currently empty since it is not
yet possible for a component to define a dependency on a service.

The follow items are left as follow-up work:

* Allow components to define a dependency on a service.

* Allow service data to be exposed to components which depend on that
  service.

* Allow services to propagate to loaded modules.

* Migrate existing services to be managed by the Flow controller.

Related to #4253.
rfratto added a commit to rfratto/agent that referenced this issue Aug 1, 2023
This commit allows a component to define a dependency on a service. When
wiring dependencies in the graph, the registration of a component is
used to create the dependency.

This also means that Flow.GetServiceConsumers will now implicitly return
instances of [component.Component] which declare a dependency on the
service.

Related to grafana#4253.
rfratto added a commit to rfratto/agent that referenced this issue Aug 1, 2023
This commit updates the Flow controller to expose service data to
components.

Components are only allowed to access service data for services they
have listed as an explicit dependency. This ensures that services cannot
access data for a service they depend on until that service has been
evaluated.

Related to grafana#4253.
rfratto added a commit that referenced this issue Aug 2, 2023
* component: allow components to depend on a service

This commit allows a component to define a dependency on a service. When
wiring dependencies in the graph, the registration of a component is
used to create the dependency.

This also means that Flow.GetServiceConsumers will now implicitly return
instances of [component.Component] which declare a dependency on the
service.

Related to #4253.

* component: expose service data to components

This commit updates the Flow controller to expose service data to
components.

Components are only allowed to access service data for services they
have listed as an explicit dependency. This ensures that services cannot
access data for a service they depend on until that service has been
evaluated.

Related to #4253.

* flow: add tests for components using services

---------

Co-authored-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com>
rfratto added a commit to rfratto/agent that referenced this issue Aug 2, 2023
This change allows components to propagate services to module
controllers.

To avoid cyclic dependencies, only services which module loader depends
on can be propagated. This effectively means that module loaders must
always depend on all services.

Related to grafana#4253.
rfratto added a commit to rfratto/agent that referenced this issue Aug 2, 2023
This change allows components to propagate services to module
controllers.

To avoid cyclic dependencies, only services which module loader depends
on can be propagated. This effectively means that module loaders must
always depend on all services.

Related to grafana#4253.
rfratto added a commit to rfratto/agent that referenced this issue Aug 2, 2023
This change allows components to propagate services to module
controllers.

To avoid cyclic dependencies, only services which module loader depends
on can be propagated. This effectively means that module loaders must
always depend on all services.

Related to grafana#4253.
rfratto added a commit that referenced this issue Aug 2, 2023
* pkg/flow: create internal options type for creating controllers

Create an internal options type, `controllerOptions`, which extends the
public `Options` type. This will be used for internal settings which
should not be settable via users.

For the initial commit, the module registry has been moved to this type.

* flow/internal/controller: add ServiceMap type

Add a new ServiceMap type to make set operations around services easier.

* flow: propagate services to modules

This change allows components to propagate services to module
controllers.

To avoid cyclic dependencies, only services which module loader depends
on can be propagated. This effectively means that module loaders must
always depend on all services.

Related to #4253.

* flow/internal/controller: allow passing custom component registries

The component registry is used when constructing the graph. A custom
component registry allows unit tests to make fake components without
having to globally register them.

* flow: use custom component registry for service tests

* flow: write tests for propagating services to modules
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* service: introduce new service package

The `service/` package is used to define Flow services, low-level
constructs which run for the lifetime of the Flow controller.

This commit introduces just the API for services, without using it
anywhere.

Related to #4253.

* pkg/flow: implement service.Host in Flow controller

This commit updates the Flow controller to implement the new
service.Host interface.

Note that because it is not currently possible for a service or
component to define a dependency on a service, the GetServiceConsumers
method always returns nil.

GetServiceConsumers will be updated in the future to return a non-nil
list once it is possible for consumers of a service to exist.

* service/http: port HTTP service to a service implementation

This ports the HTTP service to implement the service.Service interface.
The old HTTP service code has been removed in favor of the service.

This required some hacks to wire everything in correctly; this will be
cleaned up in the future once services are fully implemented.
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* flow/internal/controller: rename files holding DAG node implementations

Rename all files holding DAG node implementations to start with node_ so
they're easier to locate.

* flow/internal/controller: add ServiceNode

ServiceNode is the representation of a Flow service in the DAG.

Note that it is not currently wired up anywhere.

* flow/internal/controller: make NewLoader more flexible

Create a new type, LoaderOptions, which passes options to a Loader, to
make extending Loader with new options more flexible.

LoaderOptions currently encapsulates ComponentGlobals, but will be
expanded in the future to contain other relevant options (such as
services to load in the DAG).

* flow/internal/controller: add service nodes to graph

Add service nodes to the graph when the graph is being constructed. If a
service declares a dependency on another service, it forms a DAG edge.

* flow/internal/controller: expose ServiceNodes to caller

This change stores and exposes ServiceNodes after calls to Apply.

* flow: enable the management of services

This change adds a new field to flow.Options, Services, which causes the
lifecycle management of services to be handled by the Flow controller.

Services are added to the DAG as nodes, and are run with the Flow
controller when the Flow controller starts. Services that declare a
dependency on another service are only evaluated after the services they
depend on have been evaluated.

GetServiceConsumers will now currently return instances of
component.Component and service.Service which depend on a service. The
set of component.Component dependants is currently empty since it is not
yet possible for a component to define a dependency on a service.

The follow items are left as follow-up work:

* Allow components to define a dependency on a service.

* Allow service data to be exposed to components which depend on that
  service.

* Allow services to propagate to loaded modules.

* Migrate existing services to be managed by the Flow controller.

Related to #4253.
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* component: allow components to depend on a service

This commit allows a component to define a dependency on a service. When
wiring dependencies in the graph, the registration of a component is
used to create the dependency.

This also means that Flow.GetServiceConsumers will now implicitly return
instances of [component.Component] which declare a dependency on the
service.

Related to #4253.

* component: expose service data to components

This commit updates the Flow controller to expose service data to
components.

Components are only allowed to access service data for services they
have listed as an explicit dependency. This ensures that services cannot
access data for a service they depend on until that service has been
evaluated.

Related to #4253.

* flow: add tests for components using services

---------

Co-authored-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com>
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* pkg/flow: create internal options type for creating controllers

Create an internal options type, `controllerOptions`, which extends the
public `Options` type. This will be used for internal settings which
should not be settable via users.

For the initial commit, the module registry has been moved to this type.

* flow/internal/controller: add ServiceMap type

Add a new ServiceMap type to make set operations around services easier.

* flow: propagate services to modules

This change allows components to propagate services to module
controllers.

To avoid cyclic dependencies, only services which module loader depends
on can be propagated. This effectively means that module loaders must
always depend on all services.

Related to #4253.

* flow/internal/controller: allow passing custom component registries

The component registry is used when constructing the graph. A custom
component registry allows unit tests to make fake components without
having to globally register them.

* flow: use custom component registry for service tests

* flow: write tests for propagating services to modules
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* service: introduce new service package

The `service/` package is used to define Flow services, low-level
constructs which run for the lifetime of the Flow controller.

This commit introduces just the API for services, without using it
anywhere.

Related to #4253.

* pkg/flow: implement service.Host in Flow controller

This commit updates the Flow controller to implement the new
service.Host interface.

Note that because it is not currently possible for a service or
component to define a dependency on a service, the GetServiceConsumers
method always returns nil.

GetServiceConsumers will be updated in the future to return a non-nil
list once it is possible for consumers of a service to exist.

* service/http: port HTTP service to a service implementation

This ports the HTTP service to implement the service.Service interface.
The old HTTP service code has been removed in favor of the service.

This required some hacks to wire everything in correctly; this will be
cleaned up in the future once services are fully implemented.
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* flow/internal/controller: rename files holding DAG node implementations

Rename all files holding DAG node implementations to start with node_ so
they're easier to locate.

* flow/internal/controller: add ServiceNode

ServiceNode is the representation of a Flow service in the DAG.

Note that it is not currently wired up anywhere.

* flow/internal/controller: make NewLoader more flexible

Create a new type, LoaderOptions, which passes options to a Loader, to
make extending Loader with new options more flexible.

LoaderOptions currently encapsulates ComponentGlobals, but will be
expanded in the future to contain other relevant options (such as
services to load in the DAG).

* flow/internal/controller: add service nodes to graph

Add service nodes to the graph when the graph is being constructed. If a
service declares a dependency on another service, it forms a DAG edge.

* flow/internal/controller: expose ServiceNodes to caller

This change stores and exposes ServiceNodes after calls to Apply.

* flow: enable the management of services

This change adds a new field to flow.Options, Services, which causes the
lifecycle management of services to be handled by the Flow controller.

Services are added to the DAG as nodes, and are run with the Flow
controller when the Flow controller starts. Services that declare a
dependency on another service are only evaluated after the services they
depend on have been evaluated.

GetServiceConsumers will now currently return instances of
component.Component and service.Service which depend on a service. The
set of component.Component dependants is currently empty since it is not
yet possible for a component to define a dependency on a service.

The follow items are left as follow-up work:

* Allow components to define a dependency on a service.

* Allow service data to be exposed to components which depend on that
  service.

* Allow services to propagate to loaded modules.

* Migrate existing services to be managed by the Flow controller.

Related to #4253.
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* component: allow components to depend on a service

This commit allows a component to define a dependency on a service. When
wiring dependencies in the graph, the registration of a component is
used to create the dependency.

This also means that Flow.GetServiceConsumers will now implicitly return
instances of [component.Component] which declare a dependency on the
service.

Related to #4253.

* component: expose service data to components

This commit updates the Flow controller to expose service data to
components.

Components are only allowed to access service data for services they
have listed as an explicit dependency. This ensures that services cannot
access data for a service they depend on until that service has been
evaluated.

Related to #4253.

* flow: add tests for components using services

---------

Co-authored-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com>
clayton-cornell pushed a commit that referenced this issue Aug 14, 2023
* pkg/flow: create internal options type for creating controllers

Create an internal options type, `controllerOptions`, which extends the
public `Options` type. This will be used for internal settings which
should not be settable via users.

For the initial commit, the module registry has been moved to this type.

* flow/internal/controller: add ServiceMap type

Add a new ServiceMap type to make set operations around services easier.

* flow: propagate services to modules

This change allows components to propagate services to module
controllers.

To avoid cyclic dependencies, only services which module loader depends
on can be propagated. This effectively means that module loaders must
always depend on all services.

Related to #4253.

* flow/internal/controller: allow passing custom component registries

The component registry is used when constructing the graph. A custom
component registry allows unit tests to make fake components without
having to globally register them.

* flow: use custom component registry for service tests

* flow: write tests for propagating services to modules
@github-actions github-actions bot added the frozen-due-to-age Locked due to a period of inactivity. Please open new issues or PRs if more discussion is needed. label Feb 21, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request flow Related to Grafana Agent Flow frozen-due-to-age Locked due to a period of inactivity. Please open new issues or PRs if more discussion is needed. proposal Proposal or RFC proposal-accepted Proposal has been accepted.
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

2 participants