From a631362d5d2027ae6f10be6e5d95fc01043bc98a Mon Sep 17 00:00:00 2001 From: Panos Koutsovasilis Date: Thu, 19 Dec 2024 18:12:56 +0200 Subject: [PATCH] [k8s] embed hints inputs in agent container image (#6381) * feat: embed hints inputs in elastic-agent container image * chore: disable lint overflow warnings --- ...hints-inputs-in-agent-container-image.yaml | 32 ++++++++ .../rendered/manifest.yaml | 25 ------ .../rendered/manifest.yaml | 25 ------ .../integrations/_kubernetes/_kubernetes.tpl | 3 +- .../_kubernetes/_kubernetes_hints.tpl | 4 - .../_kubernetes/_preset_pernode.tpl | 33 -------- dev-tools/packaging/package_test.go | 35 +++++--- dev-tools/packaging/packages.yml | 3 + internal/pkg/agent/application/application.go | 8 +- .../composable/providers/kubernetes/config.go | 21 +++++ .../providers/kubernetes/config_test.go | 80 +++++++++++++++++++ internal/pkg/config/loader.go | 5 +- 12 files changed, 174 insertions(+), 100 deletions(-) create mode 100644 changelog/fragments/1734512365-embed-hints-inputs-in-agent-container-image.yaml create mode 100644 internal/pkg/composable/providers/kubernetes/config_test.go diff --git a/changelog/fragments/1734512365-embed-hints-inputs-in-agent-container-image.yaml b/changelog/fragments/1734512365-embed-hints-inputs-in-agent-container-image.yaml new file mode 100644 index 00000000000..c005769ca3a --- /dev/null +++ b/changelog/fragments/1734512365-embed-hints-inputs-in-agent-container-image.yaml @@ -0,0 +1,32 @@ +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: enhancement + +# Change summary; a 80ish characters long description of the change. +summary: Embed hints-based inputs in the Elastic Agent container image. + +# Long description; in case the summary is not enough to describe the change +# this field accommodates a description without length limits. +# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. +description: This change includes the addition of hints-based inputs directly within the Elastic Agent container image, enabling streamlined configurations for input discovery when deployed in containerized environments. + +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: elastic-agent + +# PR URL; optional; the PR number that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +pr: https://github.com/elastic/elastic-agent/pull/6381 + +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +issue: https://github.com/elastic/elastic-agent/issues/5661 diff --git a/deploy/helm/elastic-agent/examples/kubernetes-hints-autodiscover/rendered/manifest.yaml b/deploy/helm/elastic-agent/examples/kubernetes-hints-autodiscover/rendered/manifest.yaml index 941a2f89621..65f66cc1d6b 100644 --- a/deploy/helm/elastic-agent/examples/kubernetes-hints-autodiscover/rendered/manifest.yaml +++ b/deploy/helm/elastic-agent/examples/kubernetes-hints-autodiscover/rendered/manifest.yaml @@ -1142,8 +1142,6 @@ spec: - mountPath: /hostfs/var/lib name: var-lib readOnly: true - - mountPath: /usr/share/elastic-agent/state/inputs.d - name: external-inputs - mountPath: /usr/share/elastic-agent/state name: agent-data - mountPath: /etc/elastic-agent/agent.yml @@ -1151,27 +1149,6 @@ spec: readOnly: true subPath: agent.yml dnsPolicy: ClusterFirstWithHostNet - initContainers: - - args: - - -c - - mkdir -p /etc/elastic-agent/inputs.d && mkdir -p /etc/elastic-agent/inputs.d - && wget -O - https://github.com/elastic/elastic-agent/archive/v9.0.0.tar.gz - | tar xz -C /etc/elastic-agent/inputs.d --strip=5 "elastic-agent-9.0.0/deploy/kubernetes/elastic-agent-standalone/templates.d" - command: - - sh - image: busybox:1.36.1 - name: k8s-templates-downloader - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - runAsGroup: 1000 - runAsUser: 1000 - volumeMounts: - - mountPath: /etc/elastic-agent/inputs.d - name: external-inputs nodeSelector: kubernetes.io/os: linux serviceAccountName: agent-pernode-example @@ -1194,8 +1171,6 @@ spec: - hostPath: path: /var/lib name: var-lib - - emptyDir: {} - name: external-inputs - hostPath: path: /etc/elastic-agent/default/agent-pernode-example/state type: DirectoryOrCreate diff --git a/deploy/helm/elastic-agent/examples/multiple-integrations/rendered/manifest.yaml b/deploy/helm/elastic-agent/examples/multiple-integrations/rendered/manifest.yaml index d1823fc104b..cc3c2742a2c 100644 --- a/deploy/helm/elastic-agent/examples/multiple-integrations/rendered/manifest.yaml +++ b/deploy/helm/elastic-agent/examples/multiple-integrations/rendered/manifest.yaml @@ -1158,8 +1158,6 @@ spec: - mountPath: /hostfs/var/lib name: var-lib readOnly: true - - mountPath: /usr/share/elastic-agent/state/inputs.d - name: external-inputs - mountPath: /usr/share/elastic-agent/state name: agent-data - mountPath: /etc/elastic-agent/agent.yml @@ -1167,27 +1165,6 @@ spec: readOnly: true subPath: agent.yml dnsPolicy: ClusterFirstWithHostNet - initContainers: - - args: - - -c - - mkdir -p /etc/elastic-agent/inputs.d && mkdir -p /etc/elastic-agent/inputs.d - && wget -O - https://github.com/elastic/elastic-agent/archive/v9.0.0.tar.gz - | tar xz -C /etc/elastic-agent/inputs.d --strip=5 "elastic-agent-9.0.0/deploy/kubernetes/elastic-agent-standalone/templates.d" - command: - - sh - image: busybox:1.36.1 - name: k8s-templates-downloader - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - runAsGroup: 1000 - runAsUser: 1000 - volumeMounts: - - mountPath: /etc/elastic-agent/inputs.d - name: external-inputs nodeSelector: kubernetes.io/os: linux serviceAccountName: agent-pernode-example @@ -1210,8 +1187,6 @@ spec: - hostPath: path: /var/lib name: var-lib - - emptyDir: {} - name: external-inputs - hostPath: path: /etc/elastic-agent/default/agent-pernode-example/state type: DirectoryOrCreate diff --git a/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes.tpl b/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes.tpl index f8cc9a1c5fe..b8af13d2116 100644 --- a/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes.tpl +++ b/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes.tpl @@ -17,7 +17,6 @@ {{- include "elasticagent.kubernetes.config.state.statefulsets.init" $ -}} {{- include "elasticagent.kubernetes.config.state.storageclasses.init" $ -}} {{- include "elasticagent.kubernetes.config.kube_controller.init" $ -}} -{{- include "elasticagent.kubernetes.config.hints.init" $ -}} {{- include "elasticagent.kubernetes.config.audit_logs.init" $ -}} {{- include "elasticagent.kubernetes.config.container_logs.init" $ -}} {{- include "elasticagent.kubernetes.config.kubelet.containers.init" $ -}} @@ -28,4 +27,4 @@ {{- include "elasticagent.kubernetes.config.kube_proxy.init" $ -}} {{- include "elasticagent.kubernetes.config.kube_scheduler.init" $ -}} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes_hints.tpl b/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes_hints.tpl index 45c75220398..4388990db4d 100644 --- a/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes_hints.tpl +++ b/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_kubernetes_hints.tpl @@ -1,6 +1,2 @@ {{- define "elasticagent.kubernetes.config.hints.init" -}} -{{- if eq $.Values.kubernetes.hints.enabled true -}} -{{- $preset := $.Values.agent.presets.perNode -}} -{{- include "elasticagent.preset.applyOnce" (list $ $preset "elasticagent.kubernetes.pernode.preset") -}} -{{- end -}} {{- end -}} diff --git a/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_preset_pernode.tpl b/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_preset_pernode.tpl index 3f252e64868..396dd117238 100644 --- a/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_preset_pernode.tpl +++ b/deploy/helm/elastic-agent/templates/integrations/_kubernetes/_preset_pernode.tpl @@ -3,7 +3,6 @@ {{- include "elasticagent.preset.mutate.volumes" (list $ $.Values.agent.presets.perNode "elasticagent.kubernetes.pernode.preset.volumes") -}} {{- include "elasticagent.preset.mutate.outputs.byname" (list $ $.Values.agent.presets.perNode $.Values.kubernetes.output)}} {{- if eq $.Values.kubernetes.hints.enabled true -}} -{{- include "elasticagent.preset.mutate.initcontainers" (list $ $.Values.agent.presets.perNode "elasticagent.kubernetes.pernode.preset.initcontainers") -}} {{- include "elasticagent.preset.mutate.providers.kubernetes.hints" (list $ $.Values.agent.presets.perNode "elasticagent.kubernetes.pernode.preset.providers.kubernetes.hints") -}} {{- end -}} {{- if or (eq $.Values.kubernetes.scheduler.enabled true) (eq $.Values.kubernetes.controller_manager.enabled true) -}} @@ -37,10 +36,6 @@ extraVolumeMounts: - name: var-lib mountPath: /hostfs/var/lib readOnly: true -{{- if eq $.Values.kubernetes.hints.enabled true }} -- name: external-inputs - mountPath: /usr/share/elastic-agent/state/inputs.d -{{- end }} {{- end -}} {{- define "elasticagent.kubernetes.pernode.preset.volumes" -}} @@ -63,34 +58,6 @@ extraVolumes: - name: var-lib hostPath: path: /var/lib -{{- if eq $.Values.kubernetes.hints.enabled true }} -- name: external-inputs - emptyDir: {} -{{- end }} -{{- end -}} - -{{- define "elasticagent.kubernetes.pernode.preset.initcontainers" -}} -initContainers: -- name: k8s-templates-downloader - image: busybox:1.36.1 - securityContext: - allowPrivilegeEscalation: false - privileged: false - runAsUser: 1000 - runAsGroup: 1000 - capabilities: - drop: - - ALL - command: [ 'sh' ] - args: - - -c - - >- - mkdir -p /etc/elastic-agent/inputs.d && - mkdir -p /etc/elastic-agent/inputs.d && - wget -O - https://github.com/elastic/elastic-agent/archive/v{{$.Values.agent.version}}.tar.gz | tar xz -C /etc/elastic-agent/inputs.d --strip=5 "elastic-agent-{{$.Values.agent.version}}/deploy/kubernetes/elastic-agent-standalone/templates.d" - volumeMounts: - - name: external-inputs - mountPath: /etc/elastic-agent/inputs.d {{- end -}} {{- define "elasticagent.kubernetes.pernode.preset.providers.kubernetes.hints" -}} diff --git a/dev-tools/packaging/package_test.go b/dev-tools/packaging/package_test.go index ebf4aac261b..41104997df1 100644 --- a/dev-tools/packaging/package_test.go +++ b/dev-tools/packaging/package_test.go @@ -49,14 +49,15 @@ const ( ) var ( - excludedPathsPattern = regexp.MustCompile(`node_modules`) - configFilePattern = regexp.MustCompile(`.*beat\.spec.yml$|.*beat\.yml$|apm-server\.yml|elastic-agent\.yml$$`) - manifestFilePattern = regexp.MustCompile(`manifest.yml`) - modulesDirPattern = regexp.MustCompile(`module/.+`) - modulesDDirPattern = regexp.MustCompile(`modules.d/$`) - modulesDFilePattern = regexp.MustCompile(`modules.d/.+`) - monitorsDFilePattern = regexp.MustCompile(`monitors.d/.+`) - systemdUnitFilePattern = regexp.MustCompile(`/lib/systemd/system/.*\.service`) + excludedPathsPattern = regexp.MustCompile(`node_modules`) + configFilePattern = regexp.MustCompile(`.*beat\.spec.yml$|.*beat\.yml$|apm-server\.yml|elastic-agent\.yml$$`) + manifestFilePattern = regexp.MustCompile(`manifest.yml`) + modulesDirPattern = regexp.MustCompile(`module/.+`) + modulesDDirPattern = regexp.MustCompile(`modules.d/$`) + modulesDFilePattern = regexp.MustCompile(`modules.d/.+`) + monitorsDFilePattern = regexp.MustCompile(`monitors.d/.+`) + systemdUnitFilePattern = regexp.MustCompile(`/lib/systemd/system/.*\.service`) + hintsInputsDFilePattern = regexp.MustCompile(`usr/share/elastic-agent/hints.inputs.d/.*\.yml`) licenseFiles = []string{"LICENSE.txt", "NOTICE.txt"} ) @@ -297,6 +298,7 @@ func checkDocker(t *testing.T, file string) { checkManifestPermissionsWithMode(t, p, os.FileMode(0644)) checkModulesPresent(t, "", p) checkModulesDPresent(t, "", p) + checkHintsInputsD(t, "hints.inputs.d", hintsInputsDFilePattern, p) checkLicensesPresent(t, "licenses/", p) } @@ -447,6 +449,21 @@ func checkMonitorsDPresent(t *testing.T, prefix string, p *packageFile) { } } +func checkHintsInputsD(t *testing.T, name string, r *regexp.Regexp, p *packageFile) { + t.Run(fmt.Sprintf("%s %s contents", p.Name, name), func(t *testing.T) { + total := 0 + for _, entry := range p.Contents { + if r.MatchString(entry.File) { + total++ + } + } + + if total == 0 { + t.Errorf("no hints inputs found under %s", name) + } + }) +} + func checkModules(t *testing.T, name, prefix string, r *regexp.Regexp, p *packageFile) { t.Run(fmt.Sprintf("%s %s contents", p.Name, name), func(t *testing.T) { minExpectedModules := *minModules @@ -711,7 +728,7 @@ func readTarContents(tarName string, data io.Reader) (*packageFile, error) { File: header.Name, UID: header.Uid, GID: header.Gid, - Mode: os.FileMode(header.Mode), + Mode: os.FileMode(header.Mode), //nolint:gosec // Reason: header.Mode should never overflow from int64 -> uint32 } } diff --git a/dev-tools/packaging/packages.yml b/dev-tools/packaging/packages.yml index ec7008748a9..f2bdddb36be 100644 --- a/dev-tools/packaging/packages.yml +++ b/dev-tools/packaging/packages.yml @@ -274,6 +274,9 @@ shared: content: > {{ commit }} mode: 0644 + 'hints.inputs.d': + source: '{{ repo.RootDir }}/deploy/kubernetes/elastic-agent-standalone/templates.d' + mode: 0755 # cloud build to beats-ci repository - &agent_docker_cloud_spec diff --git a/internal/pkg/agent/application/application.go b/internal/pkg/agent/application/application.go index 4e42ccca917..397cf6a2eeb 100644 --- a/internal/pkg/agent/application/application.go +++ b/internal/pkg/agent/application/application.go @@ -23,6 +23,7 @@ import ( "github.com/elastic/elastic-agent/internal/pkg/agent/storage" "github.com/elastic/elastic-agent/internal/pkg/capabilities" "github.com/elastic/elastic-agent/internal/pkg/composable" + "github.com/elastic/elastic-agent/internal/pkg/composable/providers/kubernetes" "github.com/elastic/elastic-agent/internal/pkg/config" otelmanager "github.com/elastic/elastic-agent/internal/pkg/otel/manager" "github.com/elastic/elastic-agent/internal/pkg/release" @@ -135,7 +136,12 @@ func New( log.Info("Parsed configuration and determined agent is managed locally") loader := config.NewLoader(log, paths.ExternalInputs()) - discover := config.Discoverer(pathConfigFile, cfg.Settings.Path, paths.ExternalInputs()) + rawCfgMap, err := rawConfig.ToMapStr() + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to transform agent configuration into a map: %w", err) + } + discover := config.Discoverer(pathConfigFile, cfg.Settings.Path, paths.ExternalInputs(), + kubernetes.GetHintsInputConfigPath(log, rawCfgMap)) if !cfg.Settings.Reload.Enabled { log.Debug("Reloading of configuration is off") configMgr = newOnce(log, discover, loader) diff --git a/internal/pkg/composable/providers/kubernetes/config.go b/internal/pkg/composable/providers/kubernetes/config.go index c3fbc957c79..0e287fd2c3f 100644 --- a/internal/pkg/composable/providers/kubernetes/config.go +++ b/internal/pkg/composable/providers/kubernetes/config.go @@ -5,13 +5,18 @@ package kubernetes import ( + "errors" "time" "github.com/elastic/elastic-agent-autodiscover/kubernetes" "github.com/elastic/elastic-agent-autodiscover/kubernetes/metadata" "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/utils" ) +const hintsInputsPathPattern = "/usr/share/elastic-agent/hints.inputs.d/*.yml" + // Config for kubernetes provider type Config struct { Scope string `config:"scope"` @@ -57,6 +62,22 @@ type Enabled struct { Enabled bool `config:"enabled"` } +func GetHintsInputConfigPath(log *logger.Logger, agentCfg map[string]interface{}) string { + hintsVal, err := utils.GetNestedMap(agentCfg, "providers", "kubernetes", "hints", "enabled") + if err != nil { + if errors.Is(err, utils.ErrKeyNotFound) { + return "" + } + log.Errorf("error at reading providers.kubernetes.hints.enabled from config: %v", err) + return "" + } + hintsEnabled, ok := hintsVal.(bool) + if !ok || !hintsEnabled { + return "" + } + return hintsInputsPathPattern +} + // InitDefaults initializes the default values for the config. func (c *Config) InitDefaults() { c.CleanupTimeout = 60 * time.Second diff --git a/internal/pkg/composable/providers/kubernetes/config_test.go b/internal/pkg/composable/providers/kubernetes/config_test.go new file mode 100644 index 00000000000..e840b9319cc --- /dev/null +++ b/internal/pkg/composable/providers/kubernetes/config_test.go @@ -0,0 +1,80 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/internal/pkg/config" + "github.com/elastic/elastic-agent/pkg/core/logger" +) + +func TestGetHintsInputConfigPath(t *testing.T) { + + log, err := logger.New("loader_test", true) + require.NoError(t, err, "failed to create logger ", err) + + for _, tc := range []struct { + name string + cfg map[string]any + expectedPath string + }{ + { + name: "fully composite yaml key", + cfg: map[string]any{ + "providers.kubernetes.hints.enabled": true, + }, + expectedPath: hintsInputsPathPattern, + }, + { + name: "partially composite yaml key", + cfg: map[string]any{ + "providers.kubernetes": map[string]any{ + "hints.enabled": false, + }, + }, + expectedPath: "", + }, + { + name: "normal yaml key", + cfg: map[string]any{ + "providers": map[string]any{ + "kubernetes": map[string]any{ + "hints": map[string]any{ + "enabled": true, + }, + }, + }, + }, + expectedPath: hintsInputsPathPattern, + }, + { + name: "hints enabled no bool", + cfg: map[string]any{ + "providers": map[string]any{ + "kubernetes": map[string]any{ + "hints": map[string]any{ + "enabled": "true", + }, + }, + }, + }, + expectedPath: "", + }, + } { + t.Run(tc.name, func(t *testing.T) { + cfg, err := config.NewConfigFrom(tc.cfg) + require.NoError(t, err) + + mapCfg, err := cfg.ToMapStr() + require.NoError(t, err) + + require.Equal(t, tc.expectedPath, GetHintsInputConfigPath(log, mapCfg)) + }) + } + +} diff --git a/internal/pkg/config/loader.go b/internal/pkg/config/loader.go index 00b6552b1e2..8dd8e0b043a 100644 --- a/internal/pkg/config/loader.go +++ b/internal/pkg/config/loader.go @@ -7,6 +7,7 @@ package config import ( "fmt" "path/filepath" + "strings" "go.opentelemetry.io/collector/confmap" @@ -102,9 +103,11 @@ func getInput(c *Config) ([]*ucfg.Config, error) { return tmpConfig.Inputs, nil } +// isFileUnderInputsFolder checks if the given f path matches the Loader inputsFolder or +// if the parent directory of it has the suffix inputs.d func (l *Loader) isFileUnderInputsFolder(f string) bool { if matches, err := filepath.Match(l.inputsFolder, f); !matches || err != nil { - return false + return strings.HasSuffix(filepath.Dir(f), "inputs.d") } return true }