From 1fdab5a18a320bf48ed3e28cefd9a3692388c85b Mon Sep 17 00:00:00 2001 From: Lorenzo Fontana Date: Thu, 17 May 2018 14:25:38 +0200 Subject: [PATCH] Annotations for the InfluxDB Module Signed-off-by: Lorenzo Fontana --- Makefile | 2 +- .../nginx-configuration/annotations.md | 27 +++++ internal/ingress/annotations/annotations.go | 3 + internal/ingress/annotations/influxdb/main.go | 104 ++++++++++++++++++ .../ingress/annotations/influxdb/main_test.go | 101 +++++++++++++++++ internal/ingress/controller/config/config.go | 5 + internal/ingress/controller/controller.go | 3 + internal/ingress/types.go | 4 + internal/ingress/types_equals.go | 4 + rootfs/etc/nginx/template/nginx.tmpl | 4 + 10 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 internal/ingress/annotations/influxdb/main.go create mode 100644 internal/ingress/annotations/influxdb/main_test.go diff --git a/Makefile b/Makefile index f91a0c599..42c785ab7 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ IMAGE = $(REGISTRY)/$(IMGNAME) MULTI_ARCH_IMG = $(IMAGE)-$(ARCH) # Set default base image dynamically for each arch -BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.45 +BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.46 ifeq ($(ARCH),arm) QEMUARCH=arm diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index bf288f317..9b9d0f8d5 100644 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -77,6 +77,11 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz |[nginx.ingress.kubernetes.io/lua-resty-waf-debug](#lua-resty-waf)|"true" or "false"| |[nginx.ingress.kubernetes.io/lua-resty-waf-ignore-rulesets](#lua-resty-waf)|string| |[nginx.ingress.kubernetes.io/lua-resty-waf-extra-rules](#lua-resty-waf)|string| +|[nginx.ingress.kubernetes.io/enable-influxdb](#influxdb)|"true" or "false"| +|[nginx.ingress.kubernetes.io/influxdb-measurement](#influxdb)|string| +|[nginx.ingress.kubernetes.io/influxdb-port](#influxdb)|string| +|[nginx.ingress.kubernetes.io/influxdb-host](#influxdb)|string| +|[nginx.ingress.kubernetes.io/influxdb-server-name](#influxdb)|string| ### Rewrite @@ -553,3 +558,25 @@ Additionally, if the gRPC service requires TLS, add `nginx.ingress.kubernetes.io Exposing a gRPC service using HTTP is not supported. [configmap]: ./configmap.md + +### InfluxDB + +Using `influxdb-*` annotations we can monitor requests passing through a Location by sending them to an InfluxDB backend exposing the UDP socket +using the [nginx-influxdb-module](https://github.com/influxdata/nginx-influxdb-module/). + +```yaml +nginx.ingress.kubernetes.io/enable-influxdb: "true" +nginx.ingress.kubernetes.io/influxdb-measurement: "nginx-reqs" +nginx.ingress.kubernetes.io/influxdb-port: "8089" +nginx.ingress.kubernetes.io/influxdb-host: "influxdb" +nginx.ingress.kubernetes.io/influxdb-server-name: "nginx-ingress" +``` + +For the `influxdb-host` parameter you have two options: + +To use the module in the Kubernetes Nginx ingress controller, you have two options: + +- Use an InfluxDB server configured to enable the [UDP protocol](https://docs.influxdata.com/influxdb/v1.5/supported_protocols/udp/). +- Deploy Telegraf as a sidecar proxy to the Ingress controller configured to listen UDP with the [socket listener input](https://github.com/influxdata/telegraf/tree/release-1.6/plugins/inputs/socket_listener) and to write using +anyone of the [outputs plugins](https://github.com/influxdata/telegraf/tree/release-1.6/plugins/outputs) + diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index 495ca7def..30a5dbfb8 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -34,6 +34,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend" "k8s.io/ingress-nginx/internal/ingress/annotations/grpc" "k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck" + "k8s.io/ingress-nginx/internal/ingress/annotations/influxdb" "k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist" "k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing" "k8s.io/ingress-nginx/internal/ingress/annotations/log" @@ -95,6 +96,7 @@ type Ingress struct { Logs log.Config GRPC bool LuaRestyWAF luarestywaf.Config + InfluxDB influxdb.Config } // Extractor defines the annotation parsers to be used in the extraction of annotations @@ -136,6 +138,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "Logs": log.NewParser(cfg), "GRPC": grpc.NewParser(cfg), "LuaRestyWAF": luarestywaf.NewParser(cfg), + "InfluxDB": influxdb.NewParser(cfg), }, } } diff --git a/internal/ingress/annotations/influxdb/main.go b/internal/ingress/annotations/influxdb/main.go new file mode 100644 index 000000000..d8125ec44 --- /dev/null +++ b/internal/ingress/annotations/influxdb/main.go @@ -0,0 +1,104 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package influxdb + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +type influxdb struct { + r resolver.Resolver +} + +// Config contains the IfluxDB configuration to be used in the Ingress +type Config struct { + InfluxDBEnabled bool `json:"influxDBEnabled"` + InfluxDBMeasurement string `json:"influxDBMeasurement"` + InfluxDBPort string `json:"influxDBPort"` + InfluxDBHost string `json:"influxDBHost"` + InfluxDBServerName string `json:"influxDBServerName"` +} + +// NewParser creates a new InfluxDB annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return influxdb{r} +} + +// Parse parses the annotations to look for InfluxDB configurations +func (c influxdb) Parse(ing *extensions.Ingress) (interface{}, error) { + influxdbEnabled, err := parser.GetBoolAnnotation("enable-influxdb", ing) + if err != nil { + influxdbEnabled = false + } + + influxdbMeasurement, err := parser.GetStringAnnotation("influxdb-measurement", ing) + if err != nil { + influxdbMeasurement = "default" + } + + influxdbPort, err := parser.GetStringAnnotation("influxdb-port", ing) + if err != nil { + // This is not the default 8086 port but the port usually used to expose + // influxdb in UDP, the module uses UDP to talk to influx via the line protocol. + influxdbPort = "8089" + } + + influxdbHost, err := parser.GetStringAnnotation("influxdb-host", ing) + if err != nil { + influxdbHost = "127.0.0.1" + } + + influxdbServerName, err := parser.GetStringAnnotation("influxdb-server-name", ing) + if err != nil { + influxdbServerName = "nginx-ingress" + } + + return &Config{ + InfluxDBEnabled: influxdbEnabled, + InfluxDBMeasurement: influxdbMeasurement, + InfluxDBPort: influxdbPort, + InfluxDBHost: influxdbHost, + InfluxDBServerName: influxdbServerName, + }, nil +} + +// Equal tests for equality between two Config types +func (e1 *Config) Equal(e2 *Config) bool { + if e1 == e2 { + return true + } + if e1 == nil || e2 == nil { + return false + } + if e1.InfluxDBEnabled != e2.InfluxDBEnabled { + return false + } + if e1.InfluxDBPort != e2.InfluxDBPort { + return false + } + if e1.InfluxDBHost != e2.InfluxDBHost { + return false + } + if e1.InfluxDBServerName != e2.InfluxDBServerName { + return false + } + + return true +} diff --git a/internal/ingress/annotations/influxdb/main_test.go b/internal/ingress/annotations/influxdb/main_test.go new file mode 100644 index 000000000..f9ddef59b --- /dev/null +++ b/internal/ingress/annotations/influxdb/main_test.go @@ -0,0 +1,101 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package influxdb + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +func buildIngress() *extensions.Ingress { + defaultBackend := extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + + return &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{ + Backend: &extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + Rules: []extensions.IngressRule{ + { + Host: "foo.bar.com", + IngressRuleValue: extensions.IngressRuleValue{ + HTTP: &extensions.HTTPIngressRuleValue{ + Paths: []extensions.HTTPIngressPath{ + { + Path: "/foo", + Backend: defaultBackend, + }, + }, + }, + }, + }, + }, + }, + } +} + +func TestIngressInfluxDB(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true" + data[parser.GetAnnotationWithPrefix("influxdb-measurement")] = "nginxmeasures" + data[parser.GetAnnotationWithPrefix("influxdb-port")] = "9091" + data[parser.GetAnnotationWithPrefix("influxdb-host")] = "mytelegrafserver.mycompany.mytld" + data[parser.GetAnnotationWithPrefix("influxdb-server-name")] = "nginx-test-1" + ing.SetAnnotations(data) + + influx, _ := NewParser(&resolver.Mock{}).Parse(ing) + nginxInflux, ok := influx.(*Config) + if !ok { + t.Errorf("expected a Config type") + } + + if !nginxInflux.InfluxDBEnabled { + t.Errorf("expected influxdb enabled but returned %v", nginxInflux.InfluxDBEnabled) + } + + if nginxInflux.InfluxDBMeasurement != "nginxmeasures" { + t.Errorf("expected measurement name not found. Found %v", nginxInflux.InfluxDBMeasurement) + } + + if nginxInflux.InfluxDBPort != "9091" { + t.Errorf("expected port not found. Found %v", nginxInflux.InfluxDBPort) + } + + if nginxInflux.InfluxDBHost != "mytelegrafserver.mycompany.mytld" { + t.Errorf("expected host not found. Found %v", nginxInflux.InfluxDBHost) + } + + if nginxInflux.InfluxDBServerName != "nginx-test-1" { + t.Errorf("expected server name not found. Found %v", nginxInflux.InfluxDBServerName) + } +} diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go index 1e1640764..7cfdeab14 100644 --- a/internal/ingress/controller/config/config.go +++ b/internal/ingress/controller/config/config.go @@ -516,6 +516,11 @@ type Configuration struct { // DisableLuaRestyWAF disables lua-resty-waf globally regardless // of whether there's an ingress that has enabled the WAF using annotation DisableLuaRestyWAF bool `json:"disable-lua-resty-waf"` + + // EnableInfluxDB enables the nginx InfluxDB extension + // http://github.com/influxdata/nginx-influxdb-module/ + // By default this is disabled + EnableInfluxDB bool `json:"enable-influxdb"` } // NewDefault returns the default nginx configuration diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index b47fb384b..bcf5cacf6 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -450,6 +450,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] loc.Logs = anns.Logs loc.GRPC = anns.GRPC loc.LuaRestyWAF = anns.LuaRestyWAF + loc.InfluxDB = anns.InfluxDB if loc.Redirect.FromToWWW { server.RedirectFromToWWW = true @@ -486,6 +487,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] Logs: anns.Logs, GRPC: anns.GRPC, LuaRestyWAF: anns.LuaRestyWAF, + InfluxDB: anns.InfluxDB, } if loc.Redirect.FromToWWW { @@ -922,6 +924,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress, defLoc.Denied = anns.Denied defLoc.GRPC = anns.GRPC defLoc.LuaRestyWAF = anns.LuaRestyWAF + defLoc.InfluxDB = anns.InfluxDB } } } diff --git a/internal/ingress/types.go b/internal/ingress/types.go index 6506bbacf..647bebeb2 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -28,6 +28,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/authtls" "k8s.io/ingress-nginx/internal/ingress/annotations/connection" "k8s.io/ingress-nginx/internal/ingress/annotations/cors" + "k8s.io/ingress-nginx/internal/ingress/annotations/influxdb" "k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist" "k8s.io/ingress-nginx/internal/ingress/annotations/log" "k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf" @@ -271,6 +272,9 @@ type Location struct { GRPC bool `json:"grpc"` // LuaRestyWAF contains parameters to configure lua-resty-waf LuaRestyWAF luarestywaf.Config `json:"luaRestyWAF"` + // InfluxDB allows to monitor the incoming request by sending them to an influxdb database + // +optional + InfluxDB influxdb.Config `json:"influxDB,omitempty"` } // SSLPassthroughBackend describes a SSL upstream server configured diff --git a/internal/ingress/types_equals.go b/internal/ingress/types_equals.go index 9d7a6b3dc..c0018592e 100644 --- a/internal/ingress/types_equals.go +++ b/internal/ingress/types_equals.go @@ -389,6 +389,10 @@ func (l1 *Location) Equal(l2 *Location) bool { return false } + if !(&l1.InfluxDB).Equal(&l2.InfluxDB) { + return false + } + return true } diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 35004eef1..ba148e7be 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -980,6 +980,10 @@ stream { {{ template "CORS" $location }} {{ end }} + {{ if $location.InfluxDB.InfluxDBEnabled }} + influxdb server_name=$location.InfluxDB.InfluxDBServerName host=$location.InfluxDB.InfluxDBHost port=$location.InfluxDB.InfluxDBPort measurement=$location.InfluxDB.InfluxDBMeasurement enabled=true; + {{ end }} + {{ if not (empty $location.Redirect.URL) }} if ($uri ~* {{ $path }}) { return {{ $location.Redirect.Code }} {{ $location.Redirect.URL }};