Skip to content

Commit 44a9fcf

Browse files
authored
configfilesdiscovery: add heartbeats (#53719)
### What does this PR do? Implements configuration files discovery heartbeats. When an autodiscovery config is first scheduled, the Agent collects and sends its configuration files. It then retains the collection target and periodically runs the collector again. Each heartbeat: - Recollects the configuration files from the target runtime. - Sends every file returned by the collector. Repeated autodiscovery `Schedule` calls do not cause collection between heartbeats. Unscheduling removes the corresponding watch and stops future collections. Heartbeat scheduling uses configurable jitter to distribute intake traffic across Agents. Failed collections and sends are retried using a shorter jittered retry interval. The following settings are added: - `config_files_discovery.heartbeat_interval` - `config_files_discovery.heartbeat_jitter` ### Motivation DSCVR-463 ### Describe how you validated your changes Unit tests ### Additional Notes The default configuration is: ``` config_files_discovery: heartbeat_interval: 1h heartbeat_jitter: 10m ``` The configured jitter is applied in both directions around the heartbeat interval. Its effective value is clamped to the smaller of: - 1 hour. - Half of the configured heartbeat interval. This prevents jitter from producing excessively short or negative heartbeat delays. Co-authored-by: guillaume.pagnoux <guillaume.pagnoux@datadoghq.com>
1 parent a592bb9 commit 44a9fcf

8 files changed

Lines changed: 1255 additions & 135 deletions

File tree

comp/core/configfilesdiscovery/fx/fx.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func newOptionalComponent(reqs Requires) Provides {
5757

5858
provides := configfilesdiscoveryimpl.NewComponent(configfilesdiscoveryimpl.Requires{
5959
Lifecycle: reqs.Lifecycle,
60+
Config: reqs.Config,
6061
Autodiscovery: reqs.Autodiscovery,
6162
Hostname: reqs.Hostname,
6263
WorkloadMeta: reqs.WorkloadMeta,

comp/core/configfilesdiscovery/impl/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ go_library(
2020
"//comp/core/autodiscovery/def",
2121
"//comp/core/autodiscovery/integration",
2222
"//comp/core/autodiscovery/scheduler",
23+
"//comp/core/config",
2324
"//comp/core/configfilesdiscovery/def",
2425
"//comp/core/hostname",
2526
"//comp/core/workloadmeta/def",
@@ -30,6 +31,7 @@ go_library(
3031
"//pkg/util/containers/cri",
3132
"//pkg/util/docker",
3233
"//pkg/util/log",
34+
"@com_github_benbjohnson_clock//:clock",
3335
"@com_github_containerd_containerd_v2//pkg/oci",
3436
"@com_github_datadog_agent_payload_v5//agentdiscovery",
3537
"@org_golang_google_protobuf//proto",
@@ -73,6 +75,7 @@ dd_agent_go_test(
7375
"//comp/def",
7476
"//comp/forwarder/eventplatform/def",
7577
"//pkg/logs/message",
78+
"@com_github_benbjohnson_clock//:clock",
7679
"@com_github_containerd_containerd_v2//pkg/oci",
7780
"@com_github_datadog_agent_payload_v5//agentdiscovery",
7881
"@com_github_opencontainers_runtime_spec//specs-go",

comp/core/configfilesdiscovery/impl/configfilesdiscovery.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,31 @@ package configfilesdiscoveryimpl
88

99
import (
1010
"context"
11+
"time"
1112

1213
autodiscovery "github.com/DataDog/datadog-agent/comp/core/autodiscovery/def"
1314
"github.com/DataDog/datadog-agent/comp/core/autodiscovery/scheduler"
15+
"github.com/DataDog/datadog-agent/comp/core/config"
1416
configfilesdiscovery "github.com/DataDog/datadog-agent/comp/core/configfilesdiscovery/def"
1517
"github.com/DataDog/datadog-agent/comp/core/hostname"
1618
workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def"
1719
compdef "github.com/DataDog/datadog-agent/comp/def"
1820
eventplatform "github.com/DataDog/datadog-agent/comp/forwarder/eventplatform/def"
21+
"github.com/DataDog/datadog-agent/pkg/util/log"
22+
)
23+
24+
const (
25+
heartbeatIntervalConfigKey = "config_files_discovery.heartbeat_interval"
26+
heartbeatJitterConfigKey = "config_files_discovery.heartbeat_jitter"
27+
startupJitterConfigKey = "config_files_discovery.startup_jitter"
1928
)
2029

2130
// Requires defines the dependencies for the config files discovery component.
2231
type Requires struct {
2332
compdef.In
2433

2534
Lifecycle compdef.Lifecycle
35+
Config config.Component
2636
Autodiscovery autodiscovery.Component
2737
Hostname hostname.Component
2838
WorkloadMeta workloadmeta.Component
@@ -47,6 +57,16 @@ func newComponent(
4757
resolver targetResolver,
4858
sender collectedConfigSender,
4959
configCollectors map[string]ConfigCollector,
60+
) *component {
61+
return newComponentWithSchedulerConfig(ad, resolver, sender, configCollectors, defaultADSchedulerConfig())
62+
}
63+
64+
func newComponentWithSchedulerConfig(
65+
ad autodiscovery.Component,
66+
resolver targetResolver,
67+
sender collectedConfigSender,
68+
configCollectors map[string]ConfigCollector,
69+
schedulerCfg adSchedulerConfig,
5070
) *component {
5171
readers := map[RuntimeType]configReaderFactory{
5272
RuntimeDocker: newDockerConfigReader,
@@ -57,22 +77,71 @@ func newComponent(
5777
}
5878
return &component{
5979
ad: ad,
60-
scheduler: newADScheduler(resolver, readers, configCollectors, sender),
80+
scheduler: newADSchedulerWithConfig(resolver, readers, configCollectors, sender, schedulerCfg),
6181
}
6282
}
6383

6484
// NewComponent creates the config files discovery component.
6585
func NewComponent(reqs Requires) Provides {
66-
c := newComponent(
86+
schedulerCfg := defaultADSchedulerConfig()
87+
if reqs.Config != nil {
88+
schedulerCfg = adSchedulerConfigFromAgentConfig(reqs.Config)
89+
}
90+
91+
c := newComponentWithSchedulerConfig(
6792
reqs.Autodiscovery,
6893
targetResolver{store: reqs.WorkloadMeta},
6994
newEventPlatformCollectedConfigSender(reqs.EventPlatform, reqs.Hostname.GetSafe(context.Background())),
7095
reqs.Collectors,
96+
schedulerCfg,
7197
)
7298
reqs.Lifecycle.Append(compdef.Hook{OnStart: c.start, OnStop: c.stop})
7399
return Provides{Comp: c}
74100
}
75101

102+
func adSchedulerConfigFromAgentConfig(agentConfig config.Component) adSchedulerConfig {
103+
cfg := defaultADSchedulerConfig()
104+
if agentConfig == nil {
105+
return cfg
106+
}
107+
108+
heartbeatInterval := agentConfig.GetDuration(heartbeatIntervalConfigKey)
109+
if heartbeatInterval <= 0 {
110+
log.Warnf("configured %s must be positive, using default %s", heartbeatIntervalConfigKey, defaultHeartbeatInterval)
111+
} else {
112+
cfg.heartbeatInterval = heartbeatInterval
113+
}
114+
115+
heartbeatJitter := agentConfig.GetDuration(heartbeatJitterConfigKey)
116+
jitterLimit := heartbeatJitterLimit(cfg.heartbeatInterval)
117+
switch {
118+
case heartbeatJitter < 0:
119+
log.Warnf("configured %s must be non-negative, using 0", heartbeatJitterConfigKey)
120+
cfg.heartbeatJitter = 0
121+
case heartbeatJitter > jitterLimit:
122+
log.Warnf("configured %s exceeds maximum %s for heartbeat interval %s, clamping", heartbeatJitterConfigKey, jitterLimit, cfg.heartbeatInterval)
123+
cfg.heartbeatJitter = jitterLimit
124+
default:
125+
cfg.heartbeatJitter = heartbeatJitter
126+
}
127+
128+
startupJitter := agentConfig.GetDuration(startupJitterConfigKey)
129+
if startupJitter < 0 {
130+
log.Warnf("configured %s must be non-negative, using 0", startupJitterConfigKey)
131+
cfg.startupJitter = 0
132+
} else {
133+
cfg.startupJitter = startupJitter
134+
}
135+
136+
if cfg.heartbeatCheckInterval > cfg.heartbeatInterval/10 {
137+
cfg.heartbeatCheckInterval = cfg.heartbeatInterval / 10
138+
}
139+
if cfg.heartbeatCheckInterval <= 0 {
140+
cfg.heartbeatCheckInterval = time.Second
141+
}
142+
return cfg
143+
}
144+
76145
func (c *component) start(context.Context) error {
77146
c.ad.AddScheduler(schedulerName, c.scheduler, true)
78147
return nil

0 commit comments

Comments
 (0)