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

[Fleet] Add support for routing rules in integrations #514

Closed
Tracked by #151898
kpollich opened this issue Apr 26, 2023 · 7 comments · Fixed by #535
Closed
Tracked by #151898

[Fleet] Add support for routing rules in integrations #514

kpollich opened this issue Apr 26, 2023 · 7 comments · Fixed by #535
Assignees
Labels
Team:Fleet Label for the Fleet team

Comments

@kpollich
Copy link
Member

kpollich commented Apr 26, 2023

In order to support document-based routing in Fleet, integrations need to expose their routing rules as part of their data stream manifest files.

These rules would be translated to reroute processors appropriately by Fleet during integration installation.

A routing rule is composed of a few pieces of data:

  • "Source" dataset: in which dataset should Fleet place this rule?
  • "Destination" dataset: where should documents be routed by this rule?
  • Condition: what logic determines when a document is routed?
  • Namespace: under what namespace should this document be written once routed?

We'll need to support two types of routing rules defined by an integration:

  • "Local" routing rules that route from a dataset on a given integration to other datasets on that same integration
  • "Injected" routing rules that route from a dataset on a external integration back to the given integration

As far as integrations are concerned, though, there is no meaningful difference between writing a "local" routing rule and an "injected" routing rule in a data stream manifest. Fleet will be responsible for generating the appropriate processors in the appropriate ingest pipelines based on these rules. So, the implementation on the package-spec side will be a generic routing_rules object at the data stream manifest level.

Typically, routing rules will be defined on a "catch-all" or "data sink" style dataset like kubernetes.router that is essentially a passthrough to more specific data streams.

For example, we might have an nginx catch-all dataset that routes Nginx logs to more specific data sets like nginx.error and nginx.access based on the logfile path reported in each document.

Here's a proposed example of the above in action. Please see the annotative comments for more details:

# nginx/data_stream/nginx/manifest.yml
title: Nginx logs
type: logs

# This is a catch-all "sink" data stream that routes documents to 
# other datasets based on conditions or variables
dataset: nginx

# Ensures agents have permissions to write data to `logs-nginx.*-*`
elasticsearch.dynamic_dataset: true
elasticsearch.dynamic_namespace: true

routing_rules:
  # "Local" routing rules are included under this current dataset, not a special case
  nginx:
    # Route error logs to `nginx.error` when they're sourced from an error logfile
    - dataset: nginx.error
      if: "ctx?.file?.path?.contains('/var/log/nginx/error')"
      namespace:
        - {{labels.data_stream.namespace}}
        - default

    # Route access logs to `nginx.access` when they're sourced from an access logfile
    - dataset: nginx.access
      if: "ctx?.file?.path?.contains('/var/log/nginx/access')"
      namespace:
        - {{labels.data_stream.namespace}}
        - default
  
  # Route K8's container logs to this catch-all dataset for further routing
  k8s.router: 
    - dataset: nginx
      if: "ctx?.container?.image?.name == 'nginx'"
      namespace:
        - {{labels.data_stream.namespace}}
        - default
   
  # Route syslog entries tagged with nginx to this catch-all dataset
  syslog:
    - dataset: nginx
      if: "ctx?.tags?.contains('nginx')"
      namespace:
        - {{labels.data_stream.namespace}}
        - default

Fleet support will be implemented as follow:

@kpollich kpollich added the Team:Fleet Label for the Fleet team label Apr 26, 2023
@kpollich kpollich transferred this issue from elastic/kibana Apr 26, 2023
@kpollich kpollich changed the title [Fleet] Add support for routing_rules.yml file in integrations [Fleet] Add support for routing rules in integrations Apr 28, 2023
@jlind23 jlind23 assigned criamico and mrodm and unassigned criamico May 22, 2023
@mrodm
Copy link
Contributor

mrodm commented Jun 9, 2023

By reviewing this definition, I have some questions about it:

  • Are these two settings mandatory for every package that uses routing rules ?

    # Ensures agents have permissions to write data to `logs-nginx.*-*`
    elasticsearch.dynamic_dataset: true
    elasticsearch.dynamic_namespace: true

    To avoid defining this in every package (datastream) using routing rules, could it be done automatically by Fleet when installing the package if any routing rule is defined ?

  • In this example, it is used dataset that it is already defined in package-spec:

    # This is a catch-all "sink" data stream that routes documents to 
    # other datasets based on conditions or variables
    dataset: nginx

    Should it be updated the description ? Should we add another key for this ?

  • Checking how to define this new routing_rules key in package-spec:

    • As each data-stream manifest could be very long, I was thinking to move this definition to its own file inside the data-stream folder, is that ok?
    • I was wondering if this could be rewritten as an array of objects:
      • instead of name, would it better dataset or another naming?
      routing_rules:
        - name: nginx
          rules:
            - dataset: ...
        - name: k8s.router
          rules:
            - dataset: ... 

cc @juliaElastic @jsoriano

@juliaElastic
Copy link

juliaElastic commented Jun 12, 2023

  • Are these two settings mandatory for every package that uses routing rules ?
    # Ensures agents have permissions to write data to `logs-nginx.*-*`
    elasticsearch.dynamic_dataset: true
    elasticsearch.dynamic_namespace: true
    To avoid defining this in every package (datastream) using routing rules, could it be done automatically by Fleet when installing the package if any routing rule is defined ?

I think it makes sense to add this during Fleet installation to avoid repeating in every package, it seems mandatory if routing_rules are present. We can add it as a task in elastic/kibana#155910

  • In this example, it is used dataset that it is already defined in package-spec:
    # This is a catch-all "sink" data stream that routes documents to 
    # other datasets based on conditions or variables
    dataset: nginx
    Should it be updated the description ? Should we add another key for this ?

I think it's good to use the existing data stream.

  • Checking how to define this new routing_rules key in package-spec:

    • As each data-stream manifest could be very long, I was thinking to move this definition to its own file inside the data-stream folder, is that ok?

I think it makes sense to add to its own file.

  • I was wondering if this could be rewritten as an array of objects:

    • instead of name, would it better dataset or another naming?
    routing_rules:
      - name: nginx
        rules:
          - dataset: ...
      - name: k8s.router
        rules:
          - dataset: ... 

I think array of objects sounds good.
The name refers to the current package in the first phase, so I wouldn't call it dataset. Maybe keeping it name is generic enough.

@mrodm
Copy link
Contributor

mrodm commented Jun 12, 2023

  • In this example, it is used dataset that it is already defined in package-spec:

    # This is a catch-all "sink" data stream that routes documents to 
    # other datasets based on conditions or variables
    dataset: nginx

    Should it be updated the description ? Should we add another key for this ?

I think it's good to use the existing data stream.

Just one more question about this. Should dataset field be mandatory in case routing_rules key is defined ? @juliaElastic
If so, probably routing_rules should be defined at the same manifest file so it can be expressed that requirement.

@juliaElastic
Copy link

I found that Fleet sets the default dataset name as packageInfo.name + '.' + policyTemplate.name if not set.

I think it's a good idea to make dataset mandatory if routing_rules are defined, as the routing rule name has to match the dataset, so it's better to be explicit about it.

@mrodm
Copy link
Contributor

mrodm commented Jun 22, 2023

Once #535 has been completed.
These routing rules are going to be defined in its own file in each data stream folder. For instance:

  • data_stream/rules/routing_rules.yml

And this is one example of the contents/syntax of this file:

  # "Local" routing rules are included under this current dataset, not a special case
- source_dataset:  nginx
  # Route error logs to `nginx.error` when they're sourced from an error logfile
  - target_dataset: nginx.error
    if: "ctx?.file?.path?.contains('/var/log/nginx/error')"
    namespace:
      - {{labels.data_stream.namespace}}
      - default

  # Route access logs to `nginx.access` when they're sourced from an access logfile
  - target_dataset: nginx.access
    if: "ctx?.file?.path?.contains('/var/log/nginx/access')"
    namespace:
      - {{labels.data_stream.namespace}}
      - default
  
# Route K8's container logs to this catch-all dataset for further routing
- source_dataset: k8s.router
  - target_dataset: nginx
    if: "ctx?.container?.image?.name == 'nginx'"
    namespace:
      - {{labels.data_stream.namespace}}
      - default
   
  # Route syslog entries tagged with nginx to this catch-all dataset
- source_dataset: syslog
  - target_dataset: nginx
    if: "ctx?.tags?.contains('nginx')"
    namespace:
      - {{labels.data_stream.namespace}}
      - default

@zmoog
Copy link
Contributor

zmoog commented Jul 27, 2023

These routing rules are going to be defined in its own file in each data stream folder.

@mrodm the data_stream/rules/routing_rules.yml file replaces the routing_rules attribute in the manifest.yml file, right?

@mrodm
Copy link
Contributor

mrodm commented Jul 27, 2023

These routing rules are going to be defined in its own file in each data stream folder.

@mrodm the data_stream/rules/routing_rules.yml file replaces the routing_rules attribute in the manifest.yml file, right?

@zmoog Exactly, routing rules are going to be defined in its own file in each datastream (not in the manifest of the datastream):
https://github.com/elastic/package-spec/blob/991ae115d7ac50175902072d2e3a4bb685f36c02/spec/integration/data_stream/routing_rules.spec.yml

zmoog added a commit to zmoog/integrations that referenced this issue Sep 5, 2023
Instead of using the resource processor, we are switching to the new
routing rules[^1] available in 8.10.

The routing rules allow Fleet to build a better pipeline where the
custom pipeline is executed *before* routing the document to a
different dataset or namespace.

Here's an example of the final pipeline created by Fleet after the
integration installation:

```json
[
  {
    "set": {
      "field": "service.name",
      "copy_from": "kubernetes.labels.app_kubernetes_io/name",
      "ignore_empty_value": true
    }
  },
  {
    "set": {
      "field": "service.name",
      "copy_from": "kubernetes.container.name",
      "override": false,
      "ignore_empty_value": true
    }
  },
  {
    "set": {
      "field": "service.version",
      "copy_from": "kubernetes.labels.app_kubernetes_io/version",
      "ignore_empty_value": true
    }
  },
  {
    "pipeline": {
      "name": "logs-kubernetes.container_logs@custom",
      "ignore_missing_pipeline": true
    }
  },
  {
    "reroute": {
      "tag": "kubernetes.container_logs",
      "dataset": [
        "{{kubernetes.labels.elastic_co/dataset}}",
        "{{data_stream.dataset}}",
        "kubernetes.container_logs"
      ],
      "namespace": [
        "{{kubernetes.labels.elastic_co/namespace}}",
        "{{data_stream.namespace}}",
        "default"
      ],
      "if": "ctx?.kubernetes?.labels != null"
    }
  }
]

```

We upgrade the package-spec to 2.9.0 to enable the routing rules.

[^1]: elastic/package-spec#514

refs: elastic#7118
zmoog added a commit to elastic/integrations that referenced this issue Sep 13, 2023
* Add pipeline failure handler

* Set service.name and service.version

`service.name` should use value from the label `app.kubernetes.io/name`
first, and then fallback to the `kubernetes.container.name` if not
present. I need to double-check if I can use the container name as is
of I need to parse it in some form.

`service.version` use value from the label `app.kubernetes.io/version`,
if present.

* Add the routing rules

Instead of using the resource processor, we will use the new
routing rules[^1] available in 8.10.

The routing rules allow Fleet to build a better pipeline where the
custom pipeline is executed *before* routing the document to a
different dataset or namespace.

Here's an example of the final pipeline created by Fleet after the
integration installation:

```json
[
  {
    "set": {
      "field": "service.name",
      "copy_from": "kubernetes.labels.app_kubernetes_io/name",
      "ignore_empty_value": true
    }
  },
  {
    "set": {
      "field": "service.name",
      "copy_from": "kubernetes.container.name",
      "override": false,
      "ignore_empty_value": true
    }
  },
  {
    "set": {
      "field": "service.version",
      "copy_from": "kubernetes.labels.app_kubernetes_io/version",
      "ignore_empty_value": true
    }
  },
  {
    "pipeline": {
      "name": "logs-kubernetes.container_logs@custom",
      "ignore_missing_pipeline": true
    }
  },
  {
    "reroute": {
      "tag": "kubernetes.container_logs",
      "dataset": [
        "{{kubernetes.labels.elastic_co/dataset}}",
        "{{data_stream.dataset}}",
        "kubernetes.container_logs"
      ],
      "namespace": [
        "{{kubernetes.labels.elastic_co/namespace}}",
        "{{data_stream.namespace}}",
        "default"
      ],
      "if": "ctx?.kubernetes?.labels != null"
    }
  }
]

```

We upgrade the package-spec to 2.9.0 to enable the routing rules.

[^1]: elastic/package-spec#514

refs: #7118

* Expand the rerouting docs

The docs now focus on describing what the routing offers and how users
can customize it setting pod annotations.

We offer an example at definition time (using a deployment) and
runtime (using `kubectl`).

* Mention container-logs routing in the README

The main README file is what most users will see before and after
installing the integration.

Adding a short mention of the container-logs routing capability, with
a link to the complete docs, could improve the discoverability of this
feature without too much noise.

* Docs: add a namespace customization example

Show how to customize the namespace setting a label on the pod.

* Update docs

Rephrase the Nginx example to avoid ambiguity; the Nginx integration
is not required for the routing purpose.

Update the pod labels table to avoid ambiguity about the target
namespace; it's the data stream namespace, not the k8s namespace.

* Switch from labels to annotations

We learned that Kubernetes annotations are the correct representation
for data such as routing rules.

The annotations docs[^1] mention the following use case for
annotations:

> "Directives from the end-user to the implementations to modify
> behavior or engage non-standard features."

So, we switch from labels to annotations.

Unfortunately, the Kubernetes provider[^2] does not add annotations to
the  event out-of-the-box, and we can't enable this on Fleet-managed
agents.

So, we decided to make the relevant annotations available in the event
adding field[^3] using Filebeat processors.

We decided to keep the `app.kubernetes.io/name` and
`app.kubernetes.io/version` metadata as labels since
the Recommended Labels[^4] document mentions them.

[^1]: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#attaching-metadata-to-objects
[^2]: https://www.elastic.co/guide/en/fleet/current/kubernetes-provider.html
[^3]: https://www.elastic.co/guide/en/beats/filebeat/current/add-fields.html
[^4]: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/

* Explain WHY we added processors for annotations

Add a simple section that explains why we had to add a few Filebeat
processors to *export* routing-focused annotations from the Kubernetes
provider to the event.

---------

Co-authored-by: Felix Barnsteiner <felixbarny@users.noreply.github.com>
Co-authored-by: Chris Mark <chrismarkou92@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Team:Fleet Label for the Fleet team
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants