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

Add Granular Services Counts to Telemetry #5627

Merged
merged 17 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/content/overview/product-telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ 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.
- **Services** Number of Services referenced by VirtualServers, VirtualServerRoutes, TransportServers and Ingresses.
- **ClusterIPServices** Number of ClusterIP Services managed by NGINX Ingress Controller.
- **NodePortServices** Number of NodePort Services managed by NGINX Ingress Controller.
- **LoadBalancerServices** Number of LoadBalancer Services managed by NGINX Ingress Controller.
- **ExternalNameServices** Number of ExternalName Services managed by NGINX Ingress Controller.
AlexFenlon marked this conversation as resolved.
Show resolved Hide resolved
- **Ingresses** The number of Ingress resources managed by the NGINX Ingress Controller.
- **IngressClasses** Number of Ingress Classes in the cluster.
- **IngressAnnotations** List of Ingress annotations managed by NGINX Ingress Controller
Expand Down
100 changes: 0 additions & 100 deletions internal/configs/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1604,106 +1604,6 @@ func (cnf *Configurator) getMinionIngressAnnotations(annotationSet map[string]bo
return annotationSet
}

// GetServiceCount returns the total number of unique services referenced by Ingresses, VS's, VSR's, and TS's
func (cnf *Configurator) GetServiceCount() int {
AlexFenlon marked this conversation as resolved.
Show resolved Hide resolved
setOfUniqueServices := make(map[string]bool)
cnf.addVSAndVSRServicesToSet(setOfUniqueServices)
cnf.addTSServicesToSet(setOfUniqueServices)
cnf.addIngressesServicesToSet(setOfUniqueServices)
return len(setOfUniqueServices)
}

// addVSAndVSRServicesToSet adds services from VirtualServers and VirtualServerRoutes to the set
func (cnf *Configurator) addVSAndVSRServicesToSet(set map[string]bool) {
for _, vs := range cnf.virtualServers {
ns := vs.VirtualServer.Namespace
for _, upstream := range vs.VirtualServer.Spec.Upstreams {
svc := upstream.Service
addServiceToSet(set, ns, svc)

if upstream.Backup != "" {
addServiceToSet(set, ns, upstream.Backup)
}

if upstream.HealthCheck != nil && upstream.HealthCheck.GRPCService != "" {
addServiceToSet(set, ns, upstream.HealthCheck.GRPCService)
}
}

for _, vsr := range vs.VirtualServerRoutes {
ns := vsr.Namespace
for _, upstream := range vsr.Spec.Upstreams {
svc := upstream.Service
addServiceToSet(set, ns, svc)

if upstream.Backup != "" {
addServiceToSet(set, ns, upstream.Backup)
}

if upstream.HealthCheck != nil && upstream.HealthCheck.GRPCService != "" {
addServiceToSet(set, ns, upstream.HealthCheck.GRPCService)
}
}
}
}
}

// addTSServicesToSet adds services from TransportServers to the set
func (cnf *Configurator) addTSServicesToSet(set map[string]bool) {
for _, ts := range cnf.transportServers {
ns := ts.TransportServer.Namespace
for _, upstream := range ts.TransportServer.Spec.Upstreams {
svc := upstream.Service
addServiceToSet(set, ns, svc)

if upstream.Backup != "" {
addServiceToSet(set, ns, upstream.Backup)
}

}
}
}

// addIngressesServicesToSet adds services from Ingresses to the set
func (cnf *Configurator) addIngressesServicesToSet(set map[string]bool) {
for _, ing := range cnf.ingresses {
cnf.addIngressServicesToSet(ing, set)
}
for _, mergeIngs := range cnf.mergeableIngresses {
cnf.addIngressServicesToSet(mergeIngs.Master, set)
for _, minion := range mergeIngs.Minions {
cnf.addIngressServicesToSet(minion, set)
}
}
}

// addIngressServicesToSet processes a single ingress and adds its services to the set
func (cnf *Configurator) addIngressServicesToSet(ing *IngressEx, set map[string]bool) {
if ing == nil || ing.Ingress == nil {
return
}
ns := ing.Ingress.Namespace
if ing.Ingress.Spec.DefaultBackend != nil && ing.Ingress.Spec.DefaultBackend.Service != nil {
svc := ing.Ingress.Spec.DefaultBackend.Service.Name
addServiceToSet(set, ns, svc)
}
for _, rule := range ing.Ingress.Spec.Rules {
if rule.HTTP != nil {
for _, path := range rule.HTTP.Paths {
if path.Backend.Service != nil {
svc := path.Backend.Service.Name
addServiceToSet(set, ns, svc)
}
}
}
}
}

// Helper function to add services to the set
func addServiceToSet(set map[string]bool, ns string, svc string) {
set[fmt.Sprintf("%s/%s", ns, svc)] = true
}

// GetVirtualServerCounts returns the total count of
// VirtualServer and VirtualServerRoute resources that are handled by the Ingress Controller
func (cnf *Configurator) GetVirtualServerCounts() (int, int) {
Expand Down
16 changes: 16 additions & 0 deletions internal/telemetry/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ func (c *Collector) InstallationFlags() []string {
return c.Config.InstallationFlags
}

// ServiceCounts returns a map of service names and their counts in the Kubernetes cluster.
func (c *Collector) ServiceCounts() (map[string]int, error) {
serviceCounts := make(map[string]int)

services, err := c.Config.K8sClientReader.CoreV1().Services("").List(context.Background(), metaV1.ListOptions{})
if err != nil {
return nil, err
}

for _, service := range services.Items {
serviceCounts[string(service.Spec.Type)]++
}

return serviceCounts, nil
}

// lookupPlatform takes a string representing a K8s PlatformID
// retrieved from a cluster node and returns a string
// representing the platform name.
Expand Down
188 changes: 188 additions & 0 deletions internal/telemetry/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,80 @@ func TestGetInstallationFlags(t *testing.T) {
}
}

func TestGetServices(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
config telemetry.CollectorConfig
want map[string]int
}{
{
name: "OneClusterIP",
config: telemetry.CollectorConfig{
K8sClientReader: newTestClientset(defaultNS, kubeNS, clusterIPService),
},
want: map[string]int{
"ClusterIP": 1,
},
},
{
name: "MultipleClusterIPs",
config: telemetry.CollectorConfig{
K8sClientReader: newTestClientset(defaultNS, kubeNS, clusterIPService, clusterIPService2),
},
want: map[string]int{
"ClusterIP": 2,
},
},
{
name: "MultipleExternalNamesAndNodePort",
config: telemetry.CollectorConfig{
K8sClientReader: newTestClientset(defaultNS, kubeNS, externalNameService, externalNameService2, nodePortService),
},
want: map[string]int{
"ExternalName": 2,
"NodePort": 1,
},
},
{
name: "MultipleServices",
config: telemetry.CollectorConfig{
K8sClientReader: newTestClientset(defaultNS, kubeNS, externalNameService, externalNameService2, nodePortService, nodePortService2, clusterIPService2, clusterIPService, loadBalancerService),
},
want: map[string]int{
"ClusterIP": 2,
"ExternalName": 2,
"NodePort": 2,
"LoadBalancer": 1,
},
},
{
name: "noServices",
config: telemetry.CollectorConfig{
K8sClientReader: newTestClientset(defaultNS, kubeNS),
},
want: map[string]int{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c, err := telemetry.NewCollector(tc.config)
if err != nil {
t.Fatal(err)
}

got, err := c.ServiceCounts()
if err != nil {
t.Fatal(err)
}

if !cmp.Equal(tc.want, got) {
t.Error(cmp.Diff(tc.want, got))
}
})
}
}

// newTestCollectorForClusterWithNodes returns a telemetry collector configured
// to simulate collecting data on a cluser with provided nodes.
func newTestCollectorForClusterWithNodes(t *testing.T, nodes ...runtime.Object) *telemetry.Collector {
Expand Down Expand Up @@ -671,6 +745,18 @@ var (
},
Spec: apiCoreV1.NamespaceSpec{},
}

defaultNS = &apiCoreV1.Namespace{
TypeMeta: metaV1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "default",
UID: "329766ff-5d78-4c9e-8736-7fesd1f2e937",
},
Spec: apiCoreV1.NamespaceSpec{},
}
)

// Cloud providers' nodes for testing ProviderID lookups.
Expand Down Expand Up @@ -937,3 +1023,105 @@ var (
Data: map[string][]byte{},
}
)

// Services for testing
var (
clusterIPService = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "coffee-svc",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "ClusterIP",
},
Status: apiCoreV1.ServiceStatus{},
}
clusterIPService2 = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "coffee-svc2",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "ClusterIP",
},
Status: apiCoreV1.ServiceStatus{},
}
nodePortService = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "tea-svc",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "NodePort",
},
Status: apiCoreV1.ServiceStatus{},
}
nodePortService2 = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "tea-svc2",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "NodePort",
},
Status: apiCoreV1.ServiceStatus{},
}
externalNameService = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "external-svc",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "ExternalName",
},
Status: apiCoreV1.ServiceStatus{},
}
externalNameService2 = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "external-svc2",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "ExternalName",
},
Status: apiCoreV1.ServiceStatus{},
}
loadBalancerService = &apiCoreV1.Service{
TypeMeta: metaV1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metaV1.ObjectMeta{
Name: "default-svc",
Namespace: "default",
},
Spec: apiCoreV1.ServiceSpec{
Type: "LoadBalancer",
},
Status: apiCoreV1.ServiceStatus{},
}
)
Loading
Loading