Skip to content

Commit

Permalink
Add Granular Services Counts to Telemetry (#5627)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexFenlon committed Jun 4, 2024
1 parent 529dd58 commit 04bd4e9
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 597 deletions.
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.
- **RegularIngressCount** The number of Regular Ingress resources managed by NGINX Ingress Controller.
- **MasterIngressCount** The number of Master Ingress resources managed by NGINX Ingress Controller.
- **MinionIngressCount** The number of Minion Ingress resources 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 {
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 @@ -212,6 +212,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

0 comments on commit 04bd4e9

Please sign in to comment.