diff --git a/docs/content/configuration/security.md b/docs/content/configuration/security.md index 728eab2f68..68779c0df5 100644 --- a/docs/content/configuration/security.md +++ b/docs/content/configuration/security.md @@ -24,7 +24,7 @@ The Ingress Controller requires a service account which is configured using RBAC We strongly recommend using the [RBAC configuration](https://github.com/nginxinc/kubernetes-ingress/blob/v3.5.0/deployments/rbac/rbac.yaml) provided in our standard deployment configuration. It is configured with the least amount of privilege required for the Ingress Controller to work. We strongly recommend inspecting the RBAC configuration for [Manifests](https://github.com/nginxinc/kubernetes-ingress/blob/v3.5.0/deployments/rbac/rbac.yaml) -or for [Helm](https://github.com/nginxinc/kubernetes-ingress/blob/v3.5.0/charts/nginx-ingress/templates/rbac.yaml) to understand what access the Ingress Controller service account has and to which resources. For example, by default the service account has access to all Secret resources in the cluster. +or for [Helm](https://github.com/nginxinc/kubernetes-ingress/blob/v3.5.0/charts/nginx-ingress/templates/clusterrole.yaml) to understand what access the Ingress Controller service account has and to which resources. For example, by default the service account has access to all Secret resources in the cluster. ### Certificates and Privacy Keys diff --git a/docs/content/overview/product-telemetry.md b/docs/content/overview/product-telemetry.md index b8660bca92..5a23fa5151 100644 --- a/docs/content/overview/product-telemetry.md +++ b/docs/content/overview/product-telemetry.md @@ -20,6 +20,7 @@ If you would prefer to avoid sending any telemetry data, you can [opt-out](#opt- ## Data Collected These are the data points collected and reported by NGINX Ingress Controller: + - **Project Name** The name of the software, which will be labelled `NIC`. - **Project Version** NGINX Ingress Controller version. - **Project Architecture** The architecture of the kubernetes environment. (e.g. amd64, arm64, etc...) @@ -33,6 +34,8 @@ These are the data points collected and reported by NGINX Ingress Controller: - **TransportServers** The number of TransportServer resources managed by NGINX Ingress Controller. - **Replicas** Number of Deployment replicas, or Daemonset instances. - **Secrets** Number of Secret resources managed by NGINX Ingress Controller. +- **Ingress Count** Number of Ingresses. + ## Opt out diff --git a/internal/telemetry/cluster.go b/internal/telemetry/cluster.go index 220ef40a0f..82ccd030dd 100644 --- a/internal/telemetry/cluster.go +++ b/internal/telemetry/cluster.go @@ -123,6 +123,19 @@ func (c *Collector) Secrets() (int, error) { return len(c.Config.SecretStore.GetSecretReferenceMap()), nil } +// IngressCount returns number of Ingresses in the namespaces watched by NIC. +func (c *Collector) IngressCount() int { + if c.Config.Configurator == nil { + return 0 + } + ic := c.Config.Configurator.GetIngressCounts() + total := 0 + for _, v := range ic { + total += v + } + return total +} + // lookupPlatform takes a string representing a K8s PlatformID // retrieved from a cluster node and returns a string // representing the platform name. diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go index 94b68ff0f9..f8adf3ff43 100644 --- a/internal/telemetry/collector.go +++ b/internal/telemetry/collector.go @@ -116,6 +116,7 @@ func (c *Collector) Collect(ctx context.Context) { TransportServers: int64(report.TransportServers), Replicas: int64(report.NICReplicaCount), Secrets: int64(report.Secrets), + Ingresses: int64(report.IngressCount), }, } @@ -143,6 +144,7 @@ type Report struct { VirtualServerRoutes int TransportServers int Secrets int + IngressCount int } // BuildReport takes context, collects telemetry data and builds the report. @@ -190,6 +192,7 @@ func (c *Collector) BuildReport(ctx context.Context) (Report, error) { if err != nil { glog.Errorf("Error collecting telemetry data: Secrets: %v", err) } + ingressCount := c.IngressCount() return Report{ Name: "NIC", @@ -205,5 +208,6 @@ func (c *Collector) BuildReport(ctx context.Context) (Report, error) { VirtualServerRoutes: vsrCount, TransportServers: tsCount, Secrets: secrets, + IngressCount: ingressCount, }, err } diff --git a/internal/telemetry/collector_test.go b/internal/telemetry/collector_test.go index aa9343618d..019819e223 100644 --- a/internal/telemetry/collector_test.go +++ b/internal/telemetry/collector_test.go @@ -8,17 +8,19 @@ import ( "testing" "time" - "github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets" - "github.com/nginxinc/kubernetes-ingress/internal/configs" "github.com/nginxinc/kubernetes-ingress/internal/configs/version1" "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" + "github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets" "github.com/nginxinc/kubernetes-ingress/internal/nginx" "github.com/google/go-cmp/cmp" "github.com/nginxinc/kubernetes-ingress/internal/telemetry" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" tel "github.com/nginxinc/telemetry-exporter/pkg/telemetry" + coreV1 "k8s.io/api/core/v1" + networkingV1 "k8s.io/api/networking/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -249,6 +251,101 @@ func TestCollectClusterVersion(t *testing.T) { } } +func TestIngressCountReportsNoDeployedIngresses(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + cfg := telemetry.CollectorConfig{ + Configurator: newConfigurator(t), + K8sClientReader: newTestClientset(node1, kubeNS), + Version: telemetryNICData.ProjectVersion, + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + telData := tel.Data{ + ProjectName: telemetryNICData.ProjectName, + ProjectVersion: telemetryNICData.ProjectVersion, + ProjectArchitecture: telemetryNICData.ProjectArchitecture, + ClusterNodeCount: 1, + ClusterID: telemetryNICData.ClusterID, + ClusterVersion: telemetryNICData.ClusterVersion, + ClusterPlatform: "other", + } + + nicResourceCounts := telemetry.NICResourceCounts{ + VirtualServers: 0, + VirtualServerRoutes: 0, + TransportServers: 0, + Ingresses: 0, + } + + td := telemetry.Data{ + telData, + nicResourceCounts, + } + + want := fmt.Sprintf("%+v", &td) + got := buf.String() + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestIngressCountReportsNumberOfDeployedIngresses(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + + configurator := newConfiguratorWithIngress(t) + + cfg := telemetry.CollectorConfig{ + Configurator: configurator, + K8sClientReader: newTestClientset(node1, kubeNS), + Version: telemetryNICData.ProjectVersion, + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + telData := tel.Data{ + ProjectName: telemetryNICData.ProjectName, + ProjectVersion: telemetryNICData.ProjectVersion, + ProjectArchitecture: telemetryNICData.ProjectArchitecture, + ClusterNodeCount: 1, + ClusterID: telemetryNICData.ClusterID, + ClusterVersion: telemetryNICData.ClusterVersion, + ClusterPlatform: "other", + } + + nicResourceCounts := telemetry.NICResourceCounts{ + VirtualServers: 0, + VirtualServerRoutes: 0, + TransportServers: 0, + Ingresses: 1, + } + + td := telemetry.Data{ + telData, + nicResourceCounts, + } + + want := fmt.Sprintf("%+v", &td) + got := buf.String() + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + func TestCountVirtualServers(t *testing.T) { t.Parallel() @@ -666,10 +763,95 @@ func TestCountSecretsAddTwoSecretsAndDeleteOne(t *testing.T) { } } +func createCafeIngressEx() configs.IngressEx { + cafeIngress := networkingV1.Ingress{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "cafe-ingress", + Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "nginx", + }, + }, + Spec: networkingV1.IngressSpec{ + TLS: []networkingV1.IngressTLS{ + { + Hosts: []string{"cafe.example.com"}, + SecretName: "cafe-secret", + }, + }, + Rules: []networkingV1.IngressRule{ + { + Host: "cafe.example.com", + IngressRuleValue: networkingV1.IngressRuleValue{ + HTTP: &networkingV1.HTTPIngressRuleValue{ + Paths: []networkingV1.HTTPIngressPath{ + { + Path: "/coffee", + Backend: networkingV1.IngressBackend{ + Service: &networkingV1.IngressServiceBackend{ + Name: "coffee-svc", + Port: networkingV1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + { + Path: "/tea", + Backend: networkingV1.IngressBackend{ + Service: &networkingV1.IngressServiceBackend{ + Name: "tea-svc", + Port: networkingV1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + cafeIngressEx := configs.IngressEx{ + Ingress: &cafeIngress, + Endpoints: map[string][]string{ + "coffee-svc80": {"10.0.0.1:80"}, + "tea-svc80": {"10.0.0.2:80"}, + }, + ExternalNameSvcs: map[string]bool{}, + ValidHosts: map[string]bool{ + "cafe.example.com": true, + }, + SecretRefs: map[string]*secrets.SecretReference{ + "cafe-secret": { + Secret: &coreV1.Secret{ + Type: coreV1.SecretTypeTLS, + }, + Path: "/etc/nginx/secrets/default-cafe-secret", + }, + }, + } + return cafeIngressEx +} + func getResourceKey(namespace, name string) string { return fmt.Sprintf("%s_%s", namespace, name) } +func newConfiguratorWithIngress(t *testing.T) *configs.Configurator { + t.Helper() + + ingressEx := createCafeIngressEx() + c := newConfigurator(t) + _, err := c.AddOrUpdateIngress(&ingressEx) + if err != nil { + t.Fatal(err) + } + return c +} + func newConfigurator(t *testing.T) *configs.Configurator { t.Helper() @@ -705,6 +887,7 @@ func newConfigurator(t *testing.T) *configs.Configurator { IsPrometheusEnabled: false, IsLatencyMetricsEnabled: false, }) + return cnf } diff --git a/internal/telemetry/data.avdl b/internal/telemetry/data.avdl index 96c59e738c..ce16345061 100644 --- a/internal/telemetry/data.avdl +++ b/internal/telemetry/data.avdl @@ -48,5 +48,8 @@ It is the UID of the `kube-system` Namespace. */ /** Secrets is the number of Secret resources managed by the Ingress Controller. */ long? Secrets = null; + /** Ingresses is the number of Ingresses. */ + long? Ingresses = null; + } } diff --git a/internal/telemetry/exporter.go b/internal/telemetry/exporter.go index 211474ede0..94b50b8441 100644 --- a/internal/telemetry/exporter.go +++ b/internal/telemetry/exporter.go @@ -72,4 +72,6 @@ type NICResourceCounts struct { Replicas int64 // Secrets is the number of Secret resources managed by the Ingress Controller. Secrets int64 + // Ingresses is the number of Ingresses. + Ingresses int64 } diff --git a/internal/telemetry/nicresourcecounts_attributes_generated.go b/internal/telemetry/nicresourcecounts_attributes_generated.go index 61fae501ae..90e726d052 100644 --- a/internal/telemetry/nicresourcecounts_attributes_generated.go +++ b/internal/telemetry/nicresourcecounts_attributes_generated.go @@ -18,6 +18,7 @@ func (d *NICResourceCounts) Attributes() []attribute.KeyValue { attrs = append(attrs, attribute.Int64("TransportServers", d.TransportServers)) attrs = append(attrs, attribute.Int64("Replicas", d.Replicas)) attrs = append(attrs, attribute.Int64("Secrets", d.Secrets)) + attrs = append(attrs, attribute.Int64("Ingresses", d.Ingresses)) return attrs }