diff --git a/.gitignore b/.gitignore index 9b8fdb9a3..b6fb8802d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +**/build/ bin/ whitesource/ .vscode/ diff --git a/examples/grafana-metrics/Dockerfile b/examples/grafana-metrics/Dockerfile new file mode 100644 index 000000000..2f0208f8b --- /dev/null +++ b/examples/grafana-metrics/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:22.04 as install +LABEL maintainer="NGINX Agent Maintainers " + +WORKDIR /agent +COPY ./build/nginx-agent.deb /agent/nginx-agent.deb +COPY ./entrypoint.sh /agent/entrypoint.sh +COPY ./nginx-agent.conf /agent/nginx-agent.conf +COPY ./nginx.conf /agent/nginx.conf + +RUN set -x \ + && addgroup --system --gid 101 nginx \ + && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx \ + && apt-get update \ + && apt-get install --no-install-recommends --no-install-suggests -y \ + ca-certificates \ + gnupg1 \ + lsb-release \ + git \ + wget \ + make \ + curl \ + vim \ + && apt-get update \ + && apt-get install -y build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev \ + && wget http://nginx.org/download/nginx-1.23.2.tar.gz \ + && tar xfz nginx-1.23.2.tar.gz \ + && cd nginx-1.23.2 \ + && cp /agent/nginx.conf ./conf/nginx.conf \ + && ./configure --with-http_stub_status_module \ + && make \ + && make install \ + && apt-get install -y -f /agent/nginx-agent.deb + +# run the nginx and agent +FROM install as runtime + +COPY --from=install /agent/entrypoint.sh /agent/entrypoint.sh +COPY --from=install /agent/nginx-agent.conf /etc/nginx-agent/nginx-agent.conf + +RUN chmod +x /agent/entrypoint.sh +STOPSIGNAL SIGTERM +EXPOSE 80 443 + +ENTRYPOINT ["/agent/entrypoint.sh"] diff --git a/examples/grafana-metrics/Makefile b/examples/grafana-metrics/Makefile new file mode 100644 index 000000000..9e9cf00db --- /dev/null +++ b/examples/grafana-metrics/Makefile @@ -0,0 +1,15 @@ + +help: ## Show help message + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\033[36m\033[0m\n"} /^[$$()% 0-9a-zA-Z_-]+:.*?##/ { printf " \033[36m%-24s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +clean: ## Remove docker containers and agent package + docker-compose down + rm -rf ./build + +build: ## Build agent package + mkdir ./build + cd ../../ && GOWORK=off CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o ./build/nginx-agent + cd ../../ && nfpm pkg --config ./scripts/.local-nfpm.yaml --packager deb --target ./examples/grafana-metrics/build/nginx-agent.deb + +run: ## Start docker containers + docker-compose up --build diff --git a/examples/grafana-metrics/README.md b/examples/grafana-metrics/README.md new file mode 100644 index 000000000..c0f5bbe41 --- /dev/null +++ b/examples/grafana-metrics/README.md @@ -0,0 +1,12 @@ +# Grafana Metrics Example +This example demonstrates how the NGINX agent can be used to report metrics using prometheus and grafana servers. + +## Run Example + +``` +make clean build run +``` + +## Example of Grafana Dashboard + +![Dashboard](grafana-dashboard-example.png) diff --git a/examples/grafana-metrics/docker-compose.yml b/examples/grafana-metrics/docker-compose.yml new file mode 100644 index 000000000..64ee17fc9 --- /dev/null +++ b/examples/grafana-metrics/docker-compose.yml @@ -0,0 +1,45 @@ +version: '3.9' + +networks: + monitoring: + driver: bridge + +volumes: + prometheus_data: {} + +services: + agent: + build: ./ + image: nginx/agent-example + container_name: agent + ports: + - 9091:9091 + networks: + - monitoring + prometheus: + image: prom/prometheus:latest + container_name: prometheus + restart: unless-stopped + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--web.enable-lifecycle' + ports: + - 9090:9090 + networks: + - monitoring + grafana: + image: grafana/grafana-oss:latest + volumes: + - ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/grafana-datasources.yml + - ./grafana-dashboards.yml:/etc/grafana/provisioning/dashboards/grafana-dashboards.yml + - ./nginx-agent-dashboard.json:/var/lib/grafana/dashboards/nginx-agent-dashboard.json + ports: + - 3000:3000 + networks: + - monitoring diff --git a/examples/grafana-metrics/entrypoint.sh b/examples/grafana-metrics/entrypoint.sh new file mode 100644 index 000000000..86f02532f --- /dev/null +++ b/examples/grafana-metrics/entrypoint.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +set -e +set -x +set -euxo pipefail + +handle_term() +{ + echo "received TERM signal" + echo "stopping nginx-agent ..." + kill -TERM "${agent_pid}" 2>/dev/null + echo "stopping nginx ..." + kill -TERM "${nginx_pid}" 2>/dev/null +} + +trap 'handle_term' TERM + +# Launch nginx +echo "starting nginx ..." +/usr/local/nginx/sbin/nginx -g "daemon off;" & + +nginx_pid=$! + +cp /agent/nginx-agent.conf /etc/nginx-agent/nginx-agent.conf +cat /etc/nginx-agent/nginx-agent.conf + +# start nginx-agent, pass args +echo "starting nginx-agent ..." +nginx-agent "$@" & + +agent_pid=$! + +if [ $? != 0 ]; then + echo "couldn't start the agent, please check the log file" + exit 1 +fi + +wait_term() +{ + wait ${agent_pid} + trap - TERM + kill -QUIT "${nginx_pid}" 2>/dev/null + echo "waiting for nginx to stop..." + wait ${nginx_pid} +} + +wait_term + +echo "nginx-agent process has stopped, exiting." diff --git a/examples/grafana-metrics/grafana-dashboard-example.png b/examples/grafana-metrics/grafana-dashboard-example.png new file mode 100644 index 000000000..f97a5c525 Binary files /dev/null and b/examples/grafana-metrics/grafana-dashboard-example.png differ diff --git a/examples/grafana-metrics/grafana-dashboards.yml b/examples/grafana-metrics/grafana-dashboards.yml new file mode 100644 index 000000000..cc54394ea --- /dev/null +++ b/examples/grafana-metrics/grafana-dashboards.yml @@ -0,0 +1,13 @@ +apiVersion: 1 + +providers: + - name: 'dashboards' + type: file + disableDeletion: false + editable: true + allowUiUpdates: true + updateIntervalSeconds: 10 + folder: "NGINX" + options: + path: /var/lib/grafana/dashboards + foldersFromFilesStructure: false diff --git a/examples/grafana-metrics/grafana-datasources.yml b/examples/grafana-metrics/grafana-datasources.yml new file mode 100644 index 000000000..c82179cd8 --- /dev/null +++ b/examples/grafana-metrics/grafana-datasources.yml @@ -0,0 +1,20 @@ +apiVersion: 1 + +deleteDatasources: + - name: Prometheus + orgId: 1 + +datasources: + - name: Prometheus + type: prometheus + typeName: Prometheus + typeLogoUrl: public/app/plugins/datasource/prometheus/img/prometheus_logo.svg + access: proxy + url: http://prometheus:9090 + user: "" + database: "" + basicAuth: false + isDefault: true + jsonData: + httpMethod: POST + readOnly: false diff --git a/examples/grafana-metrics/nginx-agent-dashboard.json b/examples/grafana-metrics/nginx-agent-dashboard.json new file mode 100644 index 000000000..10af02342 --- /dev/null +++ b/examples/grafana-metrics/nginx-agent-dashboard.json @@ -0,0 +1,665 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 5, + "x": 0, + "y": 0 + }, + "id": 19, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "system_cpu_system + system_cpu_user", + "instant": true, + "legendFormat": "CPU %", + "range": false, + "refId": "A" + } + ], + "title": "CPU %", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 5, + "x": 5, + "y": 0 + }, + "id": 18, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "system_mem_used / system_mem_total * 100", + "instant": true, + "legendFormat": "% memory used", + "range": false, + "refId": "A" + } + ], + "title": "Memory %", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 5, + "x": 10, + "y": 0 + }, + "id": 20, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "exemplar": false, + "expr": "system_swap_pct_free", + "instant": true, + "legendFormat": "Swap used", + "range": false, + "refId": "A" + } + ], + "title": "Swap Free %", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 9, + "x": 15, + "y": 0 + }, + "id": 2, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "nginx_status", + "legendFormat": "Status", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "nginx_workers_count", + "hide": false, + "legendFormat": "Worker Count", + "range": true, + "refId": "B" + } + ], + "title": "NGINX", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 7 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(nginx_http_request_count[1m]) * 60", + "instant": false, + "legendFormat": "requests / min", + "range": true, + "refId": "A" + } + ], + "title": "HTTP requests per minute", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 7, + "x": 8, + "y": 7 + }, + "id": 24, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(nginx_http_request_bytes_sent[1m]) * 60", + "instant": false, + "legendFormat": "bytes / min", + "range": true, + "refId": "A" + } + ], + "title": "HTTP bytes sent per minute", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 9, + "x": 15, + "y": 7 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "nginx_http_status_1xx", + "hide": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "nginx_http_status_2xx", + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "nginx_http_status_3xx", + "hide": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "nginx_http_status_4xx", + "hide": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "nginx_http_status_5xx", + "hide": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "hide": false, + "refId": "F" + } + ], + "title": "NGINX HTTP Responses", + "transformations": [], + "type": "timeseries" + } + ], + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "NGINX Agent", + "uid": "zjoqqbd4k", + "version": 2, + "weekStart": "" +} diff --git a/examples/grafana-metrics/nginx-agent.conf b/examples/grafana-metrics/nginx-agent.conf new file mode 100644 index 000000000..b0212e62f --- /dev/null +++ b/examples/grafana-metrics/nginx-agent.conf @@ -0,0 +1,56 @@ +# +# /etc/nginx-agent/nginx-agent.conf +# +# Configuration file for NGINX Agent. +# +# This file is to track agent configuration values that are meant to be statically set. There +# are additional agent configuration values that are set via the API and agent install script +# which can be found in /etc/nginx-agent/agent-dynamic.conf. + +api: + # port to expose http api + port: 9091 +# tls options +tls: + # enable tls in the nginx-agent setup for grpcs + # default to enable to connect with tls connection but without client cert for mtls + enable: true + # specify the absolute path to the CA certificate file to use for verifying + # the server certificate (also requires 'skip_verify: false' below) + # by default, this will be the trusted root CAs found in the OS CA store + # ca: /etc/nginx-agent/ca.pem + # specify the absolute path to the client cert, when mtls is enabled + # cert: /etc/nginx-agent/client.crt + # specify the absolute path to the client cert key, when mtls is enabled + # key: /etc/nginx-agent/client.key + # controls whether the server certificate chain and host name are verified. + # for production use, see instructions for configuring TLS + skip_verify: true +log: + # set log level (panic, fatal, error, info, debug, trace; default "info") + level: info + # set log path. if empty, don't log to file. + path: /var/log/nginx-agent/ +# data plane status message / 'heartbeat' +nginx: + # path of NGINX logs to exclude + exclude_logs: "" + socket: "unix:/var/run/nginx-agent/nginx.sock" + +dataplane: + status: + # poll interval for data plane status - the frequency the agent will query the dataplane for changes + poll_interval: 30s + # report interval for data plane status - the maximum duration to wait before syncing dataplane information if no updates have being observed + report_interval: 24h +metrics: + # specify the size of a buffer to build before sending metrics + bulk_size: 20 + # specify metrics poll interval + report_interval: 1m + collection_interval: 15s + mode: aggregated + +# OSS NGINX default config path +# path to aux file dirs can also be added +config_dirs: "/etc/nginx:/usr/local/etc/nginx:/usr/share/nginx/modules:/etc/nms:/usr/local/nginx" diff --git a/examples/grafana-metrics/nginx.conf b/examples/grafana-metrics/nginx.conf new file mode 100644 index 000000000..c57a0f451 --- /dev/null +++ b/examples/grafana-metrics/nginx.conf @@ -0,0 +1,68 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '"$bytes_sent" "$request_length" "$request_time" ' + '"$gzip_ratio" $server_protocol '; + + access_log /usr/local/nginx/logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 80; + server_name localhost; + + #charset koi8-r; + + #access_log logs/host.access.log main; + + location / { + root /usr/local/nginx/html; + index index.html index.htm; + } + + ## + # Enable Metrics + ## + location /api { + stub_status; + allow 127.0.0.1; + deny all; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/local/nginx/html; + } + } +} diff --git a/examples/grafana-metrics/prometheus.yml b/examples/grafana-metrics/prometheus.yml new file mode 100755 index 000000000..306ebd75b --- /dev/null +++ b/examples/grafana-metrics/prometheus.yml @@ -0,0 +1,8 @@ +global: + scrape_interval: 15s + +scrape_configs: + - job_name: "prometheus" + scrape_interval: 15s + static_configs: + - targets: ["agent:9091"] diff --git a/main.go b/main.go index df6a794f8..7f2ee419e 100644 --- a/main.go +++ b/main.go @@ -70,7 +70,7 @@ func main() { sdkGRPC.InitMeta(loadedConfig.ClientID, loadedConfig.CloudAccountID) controller, commander, reporter := createGrpcClients(ctx, loadedConfig) - + if controller != nil { if err := controller.Connect(); err != nil { log.Warnf("Unable to connect to control plane: %v", err) @@ -135,7 +135,7 @@ func handleSignals( } func connectionUnavilable(loadedConfig *config.Config) bool { - return loadedConfig.Server.Host == "" || loadedConfig.Server.GrpcPort == 0 + return loadedConfig.Server.Host == "" || loadedConfig.Server.GrpcPort == 0 } func createGrpcClients(ctx context.Context, loadedConfig *config.Config) (client.Controller, client.Commander, client.MetricReporter) { @@ -143,7 +143,7 @@ func createGrpcClients(ctx context.Context, loadedConfig *config.Config) (client log.Infof("GRPC clients not created") return nil, nil, nil } - + grpcDialOptions := setDialOptions(loadedConfig) secureMetricsDialOpts, err := sdkGRPC.SecureDialOptions( loadedConfig.TLS.Enable, @@ -190,6 +190,8 @@ func loadPlugins(commander client.Commander, binary *core.NginxBinaryType, env * if commander != nil { corePlugins = append(corePlugins, plugins.NewCommander(commander, loadedConfig), + plugins.NewFileWatcher(loadedConfig, env), + plugins.NewFileWatchThrottle(), ) } @@ -208,8 +210,6 @@ func loadPlugins(commander client.Commander, binary *core.NginxBinaryType, env * plugins.NewDataPlaneStatus(loadedConfig, sdkGRPC.NewMessageMeta(uuid.NewString()), binary, env, version), plugins.NewProcessWatcher(env, binary), plugins.NewExtensions(loadedConfig, env), - plugins.NewFileWatcher(loadedConfig, env), - plugins.NewFileWatchThrottle(), plugins.NewEvents(loadedConfig, env, sdkGRPC.NewMessageMeta(uuid.NewString()), binary), plugins.NewAgentAPI(loadedConfig, env, binary), ) diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go index 17c088c70..125f47c36 100644 --- a/src/core/config/defaults.go +++ b/src/core/config/defaults.go @@ -37,8 +37,8 @@ var ( Path: "/var/log/nginx-agent", }, Server: Server{ - Command: "", - Metrics: "", + Command: "", + Metrics: "", // token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable // so setting to random uuid at the moment, tls connection won't work without the auth header Token: uuid.New().String(), @@ -204,12 +204,12 @@ var ( DefaultValue: Defaults.Log.Path, }, &StringFlag{ - Name: ServerHost, - Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.", + Name: ServerHost, + Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.", }, &IntFlag{ - Name: ServerGrpcPort, - Usage: "The desired GRPC port to use for nginx-agent traffic.", + Name: ServerGrpcPort, + Usage: "The desired GRPC port to use for nginx-agent traffic.", }, &StringFlag{ Name: ServerToken, diff --git a/src/core/metrics/sources/nginx_worker.go b/src/core/metrics/sources/nginx_worker.go index 1db7e439c..230b58173 100644 --- a/src/core/metrics/sources/nginx_worker.go +++ b/src/core/metrics/sources/nginx_worker.go @@ -235,7 +235,7 @@ func (client *NginxWorkerClient) GetWorkerStats(childProcs []*proto.NginxDetails kbsr += float64(ioc.ReadBytes / 1000) kbsw += float64(ioc.WriteBytes / 1000) } else { - log.Warn("unable to get io counter metrics") + log.Debug("unable to get io counter metrics") } } diff --git a/src/plugins/registration.go b/src/plugins/registration.go index 05a99c331..e435c9fc0 100644 --- a/src/plugins/registration.go +++ b/src/plugins/registration.go @@ -121,7 +121,13 @@ func (r *OneTimeRegistration) registerAgent() { for _, proc := range r.env.Processes() { // only need master process for registration if proc.IsMaster { - details = append(details, r.binary.GetNginxDetailsFromProcess(proc)) + nginxDetails := r.binary.GetNginxDetailsFromProcess(proc) + details = append(details, nginxDetails) + // Reading nginx config during registration to populate nginx fields like access/error logs, etc. + _, err := r.binary.ReadConfig(nginxDetails.GetConfPath(), nginxDetails.NginxId, r.env.GetSystemUUID()) + if err != nil { + log.Warnf("Unable to read config for NGINX instance %s, %v", nginxDetails.NginxId, err) + } } else { log.Tracef("NGINX non-master process: %d", proc.Pid) } diff --git a/src/plugins/registration_test.go b/src/plugins/registration_test.go index 20c4f41c6..3a79897b2 100644 --- a/src/plugins/registration_test.go +++ b/src/plugins/registration_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/nginx/agent/sdk/v2/proto" "github.com/nginx/agent/v2/src/core" @@ -31,6 +32,7 @@ func TestRegistration_Process(t *testing.T) { tt.Parallel() binary := tutils.GetMockNginxBinary() + binary.On("ReadConfig", mock.Anything, mock.Anything, mock.Anything).Return(&proto.NginxConfig{}, nil) env := tutils.GetMockEnvWithHostAndProcess() cfg := &config.Config{ diff --git a/test/performance/plugins_test.go b/test/performance/plugins_test.go index 933d73127..a3f42c132 100644 --- a/test/performance/plugins_test.go +++ b/test/performance/plugins_test.go @@ -88,6 +88,7 @@ func BenchmarkPluginOneTimeRegistration(b *testing.B) { binary.On("GetNginxDetailsMapFromProcesses", mock.Anything).Return(detailsMap) binary.On("GetNginxIDForProcess", mock.Anything).Return(processID) binary.On("GetNginxDetailsFromProcess", mock.Anything).Return(detailsMap[processID]) + binary.On("ReadConfig", mock.Anything, mock.Anything, mock.Anything).Return(&proto.NginxConfig{}, nil) env := utils.NewMockEnvironment() env.Mock.On("NewHostInfo", mock.Anything, mock.Anything, mock.Anything).Return(&proto.HostInfo{ diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go index 17c088c70..125f47c36 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go @@ -37,8 +37,8 @@ var ( Path: "/var/log/nginx-agent", }, Server: Server{ - Command: "", - Metrics: "", + Command: "", + Metrics: "", // token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable // so setting to random uuid at the moment, tls connection won't work without the auth header Token: uuid.New().String(), @@ -204,12 +204,12 @@ var ( DefaultValue: Defaults.Log.Path, }, &StringFlag{ - Name: ServerHost, - Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.", + Name: ServerHost, + Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.", }, &IntFlag{ - Name: ServerGrpcPort, - Usage: "The desired GRPC port to use for nginx-agent traffic.", + Name: ServerGrpcPort, + Usage: "The desired GRPC port to use for nginx-agent traffic.", }, &StringFlag{ Name: ServerToken, diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go index f18dc9fb7..69d0dcf43 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go @@ -18,6 +18,10 @@ import ( "github.com/nginx/agent/v2/src/core/metrics/sources/tailer" ) +const ( + spaceDelim = " " +) + // This metrics source is used to tail the NGINX access logs to retrieve http metrics. type NginxAccessLog struct { @@ -207,26 +211,30 @@ func (c *NginxAccessLog) logStats(ctx context.Context, logFile, logFormat string } if access.Request != "" { - splitRequest := strings.Split(access.Request, " ") - n := fmt.Sprintf("method.%s", strings.ToLower(splitRequest[0])) + method, _, protocol := getParsedRequest(access.Request) + n := fmt.Sprintf("method.%s", strings.ToLower(method)) if isOtherMethod(n) { n = "method.others" } counters[n] = counters[n] + 1 if access.ServerProtocol == "" { - httpProtocolVersion := strings.Split(splitRequest[2], "/")[1] - httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_") - n = fmt.Sprintf("v%s", httpProtocolVersion) - counters[n] = counters[n] + 1 + if strings.Count(protocol, "/") == 1 { + httpProtocolVersion := strings.Split(protocol, "/")[1] + httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_") + n = fmt.Sprintf("v%s", httpProtocolVersion) + counters[n] = counters[n] + 1 + } } } if access.ServerProtocol != "" { - httpProtocolVersion := strings.Split(access.ServerProtocol, "/")[1] - httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_") - n := fmt.Sprintf("v%s", httpProtocolVersion) - counters[n] = counters[n] + 1 + if strings.Count(access.ServerProtocol, "/") == 1 { + httpProtocolVersion := strings.Split(access.ServerProtocol, "/")[1] + httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_") + n := fmt.Sprintf("v%s", httpProtocolVersion) + counters[n] = counters[n] + 1 + } } // don't need the http status for NGINX Plus @@ -294,6 +302,35 @@ func (c *NginxAccessLog) logStats(ctx context.Context, logFile, logFormat string } } +func getParsedRequest(request string) (method string, uri string, protocol string) { + if len(request) == 0 { + return + } + + startURIIdx := strings.Index(request, spaceDelim) + if startURIIdx == -1 { + return + } + + endURIIdx := strings.LastIndex(request, spaceDelim) + // Ideally, endURIIdx should never be -1 here, as startURIIdx should have handled it already + if endURIIdx == -1 { + return + } + + // For Example: GET /user/register?ahrefp' or ' HTTP/1.1 + + // method -> GET + method = request[:startURIIdx] + + // uri -> /user/register?ahrefp' or ' + uri = request[startURIIdx+1 : endURIIdx] + + // protocol -> HTTP/1.1 + protocol = request[endURIIdx+1:] + return +} + func getRequestLengthMetricValue(requestLengths []float64) float64 { value := 0.0 diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_worker.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_worker.go index 1db7e439c..230b58173 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_worker.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_worker.go @@ -235,7 +235,7 @@ func (client *NginxWorkerClient) GetWorkerStats(childProcs []*proto.NginxDetails kbsr += float64(ioc.ReadBytes / 1000) kbsw += float64(ioc.WriteBytes / 1000) } else { - log.Warn("unable to get io counter metrics") + log.Debug("unable to get io counter metrics") } } diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go b/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go index ea34ce712..9a93efe49 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go @@ -81,7 +81,6 @@ var ( subViolations, supportID, threatCampaignNames, - httpURI, violationRating, httpHostname, xForwardedForHeaderVal, @@ -97,6 +96,7 @@ var ( clientApplication, clientApplicationVersion, transportProtocol, + httpURI, } ) @@ -464,7 +464,7 @@ func setValue(napConfig *NAPConfig, key, value string, logger *logrus.Entry) err case sigSetNames: napConfig.SigSetNames = replaceEncodedList(value, listSeperator) case threatCampaignNames: - napConfig.ThreatCampaignNames = value + napConfig.ThreatCampaignNames = replaceEncodedList(value, listSeperator) case violationDetails: napConfig.ViolationDetailsXML = func(data string) *BADMSG { var xmlData BADMSG @@ -513,7 +513,7 @@ func setValue(napConfig *NAPConfig, key, value string, logger *logrus.Entry) err case sigCVEs: napConfig.SignatureCVEs = replaceEncodedList(value, listSeperator) case subViolations: - napConfig.SubViolations = value + napConfig.SubViolations = replaceEncodedList(value, listSeperator) case supportID: napConfig.SupportID = value case violations: diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go index 832b88562..e10d28b1b 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go @@ -108,7 +108,7 @@ func (n *NAPMonitoring) run() { } case <-riTicker.C: if len(report.Events) > 0 { - log.Infof("reached a report interval of %vs, sending %d Security Violation Events as a report", n.reportInterval.Seconds(), n.reportCount) + log.Infof("reached a report interval of %vs, sending %d Security Violation Events as a report", n.reportInterval.Seconds(), len(report.Events)) n.send(report) } case <-n.ctx.Done(): diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/registration.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/registration.go index 05a99c331..e435c9fc0 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/registration.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/registration.go @@ -121,7 +121,13 @@ func (r *OneTimeRegistration) registerAgent() { for _, proc := range r.env.Processes() { // only need master process for registration if proc.IsMaster { - details = append(details, r.binary.GetNginxDetailsFromProcess(proc)) + nginxDetails := r.binary.GetNginxDetailsFromProcess(proc) + details = append(details, nginxDetails) + // Reading nginx config during registration to populate nginx fields like access/error logs, etc. + _, err := r.binary.ReadConfig(nginxDetails.GetConfPath(), nginxDetails.NginxId, r.env.GetSystemUUID()) + if err != nil { + log.Warnf("Unable to read config for NGINX instance %s, %v", nginxDetails.NginxId, err) + } } else { log.Tracef("NGINX non-master process: %d", proc.Pid) }