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

Added default set of features to Agent Configuration #868

Merged
merged 10 commits into from
Sep 27, 2024
Merged
207 changes: 207 additions & 0 deletions docs/features/features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# NGINX Agent Features

- [Introduction](#introduction)
- [Feature Flags Overview](#feature-flags-overview)
- [Conflicting Combinations](#conflicting-combinations)
- [Cobra CLI Parameters](#cobra-cli-parameters)
- [Environment Variables](#environment-variables)
- [Configuration File](#configuration-file)
- [Dynamic Updates via gRPC](#dynamic-updates-via-grpc)
- [Internal State Management](#internal-state-management)
- [Code Path Management](#code-path-management)
- [Dynamic Feature Toggling](#dynamic-feature-toggling)
- [Security Considerations](#security-considerations)
- [Conclusion](#conclusion)

## Introduction

This document outlines the design of a feature flag management system
for the NGINX Agent. The system enables dynamic toggling of features
based on configurations provided via multiple sources, including CLI
parameters, environment variables, configuration files, and via protobuf
gRPC messages. Feature flags control the execution paths within the
application, allowing for flexible and controlled feature management.

## Feature Flags Overview

The design goal of this document is to capture fine grained features in
NGINX Agent v3.

The ultimate goal of this design is to delegate fine-grained control of
high-level feature sets to the configuration.

The system manages the following feature flags:

| Feature Flag | Sub Category 1 | Description | Default |
|---------------|-------------------|--------------------------------------------------------------------------------------------------------------|--------------------|
| configuration | | Full read/write management of configurations toggled by DataPlaneConfig ConfigMode | On |
| certificates | | Inclusion of public keys and other certificates in the configurations toggled by DataPlaneConfig CertMode | Off |
| connection | | Sends an initial connection message reporting instance information on presence of Command ServerConfig Host and Port | On |
| file-watcher | | Monitoring of file changes in allowed directories; may trigger DP => MP sync of files configured by Watchers | On |
oliveromahony marked this conversation as resolved.
Show resolved Hide resolved
| agent-api | | REST API for NGINX Agent | Off |
| metrics | | Full metrics reporting | On |
| | metrics-host | All host-level metrics | On (inherited) |
| | metrics-container | Container-level metrics read from cgroup information | On (inherited) |
| | metrics-instance | All OSS and Plus Metrics, depending on what instance is present | On (if instance present e.g. NGINX) |

## Conflicting Combinations

**config-certificates** being read-only and reporting ssl under
instances may conflict.

**metrics** will include every specified metrics category (hence the
inherited specification). If fine-grained metrics are required, this
needs to be absent from the list. If there is a feature flag enabled and
conflicting
oliveromahony marked this conversation as resolved.
Show resolved Hide resolved

### Cobra CLI Parameters

**Usage**: Users can enable or disable features via CLI flags when
launching the application.

**Example**:

```bash
./nginx-agent --features=connection,config,metrics,process-watcher,file-watcher,agent-api
```

Specifies a comma-separated list of features enabled for the Agent.

- **Implementation**: Utilises [Cobra](https://github.com/spf13/cobra)
to define and parse CLI flags for each feature.

### Environment Variables

- **Usage**: Environment variables provide an alternative
configuration method, suitable for containerised deployments.

- **Naming Convention**: NGINX_AGENT_FEATURES and is added to the list

- **Implementation**: Use a library
[Viper](https://github.com/spf13/viper) to bind environment
variables to feature flags.

### Configuration File

- **Usage**: A configuration file (e.g., YAML, JSON, TOML) can list
enabled features and their parameters.

- **Example (YAML)**:

```yaml
features:
- connection
- configuration
- metrics
- process-watcher
- file-watcher
- agent-api

```

**Implementation**: Parse the configuration file during initialisation using Viper.

### Dynamic Updates via gRPC

- Through the MPI
send an updated AgentConfig message with a different list of
features.

## Internal State Management

- **Singleton Pattern**: Implement a singleton FeaturePlugin to
maintain the state of all feature flags. On configuration change,
use the message bus to notify throughout the application, any
oliveromahony marked this conversation as resolved.
Show resolved Hide resolved
changes.

- **Thread-Safety**: Use synchronisation mechanisms to ensure safe
concurrent access of changes

## Code Path Management

**Conditional Execution**: Use the FeatureManager to check feature
states before executing specific code paths.

**Example**:

```go
if featureManager.IsEnabled("metrics") {
// Execute full metrics reporting code path
} else {
// Execute alternative or no-op code path
}
```

- **Abstraction**: Encapsulate feature checks within helper functions
or middleware to streamline conditional logic across the codebase.

## Dynamic Feature Toggling

Implement methods within FeaturePlugin to enable, disable, and retrieve
feature states. Watch the nginx-agent.conf file for changes. Listen to
gRPC messages for AgentConfig changes.

Example useful functionality:

```go

func (fm *FeatureManager) IsEnabled(featureName string) bool {

fm.mutex.RLock()

defer fm.mutex.RUnlock()

// Code check to see if the feature is enabled in the AgentConfig

}

func (fm *FeatureManager) UpdateFeature(featureName string, enabled bool, parameters map[string]interface{}) error {

fm.mutex.Lock()

defer fm.mutex.Unlock()

switch featureName {

case "config":

// update the AgentConfig with the new feature and it\'s configuration

}

return nil

}
```

## Security Considerations

- **Authentication & Authorisation**: Ensure that only authorised
entities can send gRPC messages to update feature flags.

- **Validation**: Validate feature names and parameters received via
gRPC to prevent invalid configurations (e.g. using
<https://buf.build/bufbuild/protovalidate/docs/main:buf.validate> )

- **Audit Logging**: Log all feature flag changes for auditing and
rollback purposes.

- **Secret Management**: Securely handle sensitive configuration
parameters, especially for features like dealing with secrets and
connection settings.

## Conclusion

This design provides a flexible and dynamic feature flag management
system that integrates multiple configuration sources and allows
real-time updates via gRPC.

By centralising feature state management and ensuring thread safety, the
system enables controlled feature toggling with minimal impact on the
running application.

Proper security measures and validation ensure the integrity and
reliability of the feature management process.


[def]: #conflictingcombinations
7 changes: 7 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func ResolveConfig() (*Config, error) {
Command: resolveCommand(),
Common: resolveCommon(),
Watchers: resolveWatchers(),
Features: viperInstance.GetStringSlice(FeaturesKey),
}

slog.Debug("Agent config", "config", config)
Expand Down Expand Up @@ -236,6 +237,12 @@ func registerFlags() {
"Updates the client grpc setting MaxSendMsgSize with the specific value in MB.",
)

fs.StringSlice(
FeaturesKey,
GetDefaultFeatures(),
"A comma-separated list of features enabled for the agent.",
)

registerCollectorFlags(fs)

fs.SetNormalizeFunc(normalizeFunc)
Expand Down
11 changes: 11 additions & 0 deletions internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package config
import (
"math"
"time"

pkg "github.com/nginx/agent/v3/pkg/config"
)

const (
Expand Down Expand Up @@ -54,3 +56,12 @@ const (
DefCollectorBatchProcessorSendBatchMaxSize = 0
DefCollectorBatchProcessorTimeout = 200 * time.Millisecond
)

func GetDefaultFeatures() []string {
return []string{
pkg.FeatureConfiguration,
pkg.FeatureConnection,
pkg.FeatureMetrics,
pkg.FeatureFileWatcher,
}
}
1 change: 1 addition & 0 deletions internal/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
CollectorRootKey = "collector"
VersionKey = "version"
UUIDKey = "uuid"
FeaturesKey = "features"
InstanceWatcherMonitoringFrequencyKey = "watchers_instance_watcher_monitoring_frequency"
InstanceHealthWatcherMonitoringFrequencyKey = "watchers_instance_health_watcher_monitoring_frequency"
FileWatcherMonitoringFrequencyKey = "watchers_file_watcher_monitoring_frequency"
Expand Down
1 change: 1 addition & 0 deletions internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type (
ConfigDir string `yaml:"-" mapstructure:"config-dirs"`
UUID string `yaml:"-"`
AllowedDirectories []string `yaml:"-"`
Features []string `yaml:"-"`
}

Log struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/watcher/instance/instance_watcher_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (iw *InstanceWatcherService) agentInstance(ctx context.Context) *mpi.Instan
Metrics: &mpi.MetricsServer{},
File: &mpi.FileServer{},
Labels: []*structpb.Struct{},
Features: []string{},
Features: iw.agentConfig.Features,
MessageBufferSize: "",
},
},
Expand Down
34 changes: 18 additions & 16 deletions nginx-agent.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@
#

log:
# set log level (panic, fatal, error, info, debug, trace; default "info")
# set log level (error, info, debug; default "info")
level: info
# set log path. if empty, don't log to file.
path: /var/log/nginx-agent/

watchers:
instance_watcher:
monitoring_frequency: 5s
instance_health_watcher:
monitoring_frequency: 5s
file_watcher:
monitoring_frequency: 5s

data_plane_config:
nginx:
reload_monitoring_period: 5s
treat_warnings_as_error: true

config_dirs: "/etc/nginx:/usr/local/etc/nginx:/usr/share/nginx/modules:/var/run/nginx:/var/log/nginx"

client:
timeout: 10s
## command server settings
# command:
# server:
# host: "127.0.0.1" # Command server host
# port: 8080 # Command server port
# type: 0 # Server type (e.g., 0 for gRPC)
# auth:
# token: "secret-auth-token" # Authentication token for the command server

## collector metrics settings
# collector:
# exporters: # exporters
# - type: otlp # exporter type
# server:
# host: "127.0.0.1" # OTLP exporter server host
# port: 5643 # OTLP exporter server port
# tls: {}
18 changes: 18 additions & 0 deletions pkg/config/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package config

const (
FeatureCertificates = "certificates"
FeatureConfiguration = "configuration"
FeatureConnection = "connection"
FeatureMetrics = "metrics"
FeatureMetricsContainer = "metrics-container"
FeatureMetricsHost = "metrics-host"
FeatureMetricsInstance = "metrics-instance"
FeatureFileWatcher = "file-watcher"
FeatureAgentAPI = "agent-api"
)
Loading