Skip to content

Commit

Permalink
Merge branch 'v3' into agent-config
Browse files Browse the repository at this point in the history
  • Loading branch information
oliveromahony committed Sep 30, 2024
2 parents 6511622 + 7ab5bea commit 7f3f541
Show file tree
Hide file tree
Showing 47 changed files with 2,121 additions and 1,214 deletions.
33 changes: 31 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ jobs:
name: nginx-agent-unsigned-snapshots
path: build
retention-days: 1

generate-pgo-profile:
name: Generate Profile
needs: build-unsigned-snapshot
Expand Down Expand Up @@ -261,7 +260,6 @@ jobs:
- name: Push benchmark result
if: ${{ success() && github.ref_name == 'v3'}}
run: git push 'https://github-actions:${{ secrets.GITHUB_TOKEN }}@github.com/nginx/agent.git' benchmark-results:benchmark-results

load-tests:
name: Load Tests
if: ${{ !github.event.pull_request.head.repo.fork && !startsWith(github.ref_name, 'dependabot/') }}
Expand Down Expand Up @@ -334,3 +332,34 @@ jobs:
- name: Push load test result
if: ${{ success() && github.ref_name == 'v3'}}
run: git push 'https://github-actions:${{ secrets.GITHUB_TOKEN }}@github.com/nginx/agent.git' benchmark-results:benchmark-results
publish-packages-vars:
name: Set workflow variables
if: ${{ github.ref_name == 'v3' &&
!github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-22.04
outputs:
package_build_num: ${{ steps.get_build_num.outputs.build_num }}
steps:
- name: Get the build number
id: get_build_num
run: echo "build_num=${{ github.run_number }}-$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT

publish-packages:
name: Publish NGINX Agent v3 packages
if: ${{ github.ref_name == 'v3' &&
!github.event.pull_request.head.repo.fork }}
needs: [ lint, unit-test, performance-tests,
load-tests, official-oss-image-integration-tests,
official-plus-image-integration-tests,
race-condition-test, publish-packages-vars ]
uses: ./.github/workflows/release-branch.yml
secrets: inherit
permissions:
id-token: write
contents: read
with:
packageVersion: "3.0.0"
packageBuildNo: "${{ needs.publish-packages-vars.outputs.package_build_num }}"
uploadAzure: true
publishPackages: true
releaseBranch: "v3"
26 changes: 26 additions & 0 deletions .github/workflows/release-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,32 @@ on:
description: 'Release branch to build & publish from'
required: true
type: string
workflow_call:
inputs:
githubRelease:
type: boolean
default: false
packageVersion:
type: string
default: "3.0.0"
packageBuildNo:
type: string
default: "1"
uploadAzure:
type: boolean
default: true
publishPackages:
type: boolean
default: true
tagRelease:
type: boolean
default: false
createPullRequest:
type: boolean
default: false
releaseBranch:
type: string
required: true

env:
NFPM_VERSION: 'v2.35.3'
Expand Down
14 changes: 7 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ OS_RELEASE ?= ubuntu
OS_VERSION ?= 22.04
BASE_IMAGE = "docker.io/$(OS_RELEASE):$(OS_VERSION)"
IMAGE_TAG = "agent_$(OS_RELEASE)_$(OS_VERSION)"
DOCKERFILE_PATH = "./scripts/docker/nginx-oss/$(CONTAINER_OS_TYPE)/Dockerfile"
OFFICIAL_IMAGE_DOCKERFILE_PATH = "./test/docker/oss/$(CONTAINER_OS_TYPE)/Dockerfile"
DOCKERFILE_PATH = "./test/docker/nginx-oss/$(CONTAINER_OS_TYPE)/Dockerfile"
OFFICIAL_IMAGE_DOCKERFILE_PATH = "./test/docker/nginx-official-image/$(CONTAINER_OS_TYPE)/Dockerfile"
IMAGE_PATH ?= "/nginx/agent"
TAG ?= ""

Expand Down Expand Up @@ -161,7 +161,7 @@ official-image-integration-test: $(SELECTED_PACKAGE) build-mock-management-plane
TEST_ENV="Container" CONTAINER_OS_TYPE=$(CONTAINER_OS_TYPE) CONTAINER_NGINX_IMAGE_REGISTRY=${CONTAINER_NGINX_IMAGE_REGISTRY} BUILD_TARGET="install" \
PACKAGES_REPO=$(OSS_PACKAGES_REPO) TAG=${TAG} PACKAGE_NAME=$(PACKAGE_NAME) BASE_IMAGE=$(BASE_IMAGE) DOCKERFILE_PATH=$(OFFICIAL_IMAGE_DOCKERFILE_PATH) \
OS_VERSION=$(OS_VERSION) OS_RELEASE=$(OS_RELEASE) IMAGE_PATH=$(IMAGE_PATH) \
go test -v ./test/integration
go test -v ./test/integration/grpc_management_plane_api_test.go

performance-test:
@mkdir -p $(TEST_BUILD_DIR)
Expand Down Expand Up @@ -190,23 +190,23 @@ run-mock-management-grpc-server: ## Run mock management plane gRPC server
.PHONY: build-test-plus-image
build-test-plus-image:
$(CONTAINER_BUILDENV) $(CONTAINER_CLITOOL) build -t nginx_plus_$(IMAGE_TAG) . \
--no-cache -f ./scripts/docker/nginx-plus/deb/Dockerfile \
--no-cache -f ./test/docker/nginx-plus/deb/Dockerfile \
--secret id=nginx-crt,src=$(CERTS_DIR)/nginx-repo.crt \
--secret id=nginx-key,src=$(CERTS_DIR)/nginx-repo.key \
--build-arg PACKAGE_NAME=$(PACKAGE_NAME) \
--build-arg PACKAGES_REPO=$(OSS_PACKAGES_REPO) \
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
--build-arg ENTRY_POINT=./scripts/docker/entrypoint.sh
--build-arg ENTRY_POINT=./test/docker/entrypoint.sh

.PHONY: build-test-oss-image
build-test-oss-image:
$(CONTAINER_BUILDENV) $(CONTAINER_CLITOOL) build -t nginx_oss_$(IMAGE_TAG) . \
--no-cache -f ./scripts/docker/nginx-oss/deb/Dockerfile \
--no-cache -f ./test/docker/nginx-oss/deb/Dockerfile \
--target install-agent-local \
--build-arg PACKAGE_NAME=$(PACKAGE_NAME) \
--build-arg PACKAGES_REPO=$(OSS_PACKAGES_REPO) \
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
--build-arg ENTRY_POINT=./scripts/docker/entrypoint.sh
--build-arg ENTRY_POINT=./test/docker/entrypoint.sh

.PHONY: run-mock-management-otel-collector
run-mock-management-otel-collector: ## Run mock management plane OTel collector
Expand Down
4 changes: 2 additions & 2 deletions Makefile.containers
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ endif

ifeq ($(CONTAINER_CLITOOL), docker)
CONTAINER_BUILDENV ?= DOCKER_BUILDKIT=1 BUILDKIT_PROGRESS=plain
ifeq ($(shell docker-compose -v >/dev/null 2>&1 || echo FAIL),)
CONTAINER_COMPOSE = docker-compose
ifeq ($(shell docker -v >/dev/null 2>&1 || echo FAIL),)
CONTAINER_COMPOSE = docker compose
endif
else ifeq ($(CONTAINER_CLITOOL), podman)
ifeq ($(shell podman-compose -v >/dev/null 2>&1 || echo FAIL),)
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ make install-tools
```

### Building NGINX Agent from Source Code
Run the following commands to build and run NGINX Agent:
Build NGINX Agent deb package:
```
make build
sudo make run
OSARCH=<operating system archiecture> make local-deb-package
```
Build NGINX Agent rpm package:
```
OSARCH=<operating system archiecture> make local-rpm-package
```
Build NGINX Agent apk package:
```
OSARCH=<operating system archiecture> make local-apk-package
```

## NGINX Agent Technical Specifications
Expand Down
211 changes: 211 additions & 0 deletions docs/features/features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# 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 DataPlane (DP) => ManagementPlane synchronisation of files configured by Watchers | On |
| agent-api | | REST Application Programming Interface (API) for NGINX Agent | Off |
| metrics | | Full metrics reporting | On |
| | metrics-host | All host-level metrics | On (if running in a host, virtualised or not) |
| | metrics-container | Container-level metrics read from cgroup information | On (if running in a container) |
| | metrics-instance | All OSS and Plus Metrics, depending on what instance is present | On (if instance present e.g. NGINX) |

## Conflicting Combinations

If there is a feature flag enabled and conflicting features are
enabled, the more specific feature flag takes precedence. If the
feature flag is set e.g. metrics-container in a non-containerised
execution environment, then no useful metrics will be reported.

**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.

### 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 of any
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
Loading

0 comments on commit 7f3f541

Please sign in to comment.