Skip to content

Commit 3ae4fb0

Browse files
itisallgoodaantnRobusta Runner
authored
Add events count and export metrics (#86)
* add support for CRDs * Fixed usage of ListWatch and WatchFunc for conf.CustomResources; removed unused fmt import from config/config.go * Added rbac style permissions for custom resources * Updated yaml files to use customresources; Added to readme Working with CRDs section * Added kubewatchEventsMetrics and /metrics endpoint * Removed depricated io/ioutil functions * Added LISTEN_ADDRESS as env to set metrics endpoint * Updated Metrics docs * small readme fix --------- Co-authored-by: Robusta Runner <[email protected]> Co-authored-by: Robusta Runner <[email protected]>
1 parent 2b3d5cb commit 3ae4fb0

File tree

10 files changed

+1489
-68
lines changed

10 files changed

+1489
-68
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,21 @@ Alternatively, you can pass this configuration directly using the `--set` flag:
236236
helm install kubewatch robusta/kubewatch --set='rbac.create=true,slack.channel=#YOUR_CHANNEL,slack.token=xoxb-YOUR_TOKEN,customRoles[0].apiGroups={monitoring.coreos.com},customRoles[0].resources={prometheusrules},customRoles[0].verbs={get,list,watch}'
237237
```
238238

239+
#### Metrics
240+
`kubewatch` runs a Prometheus metrics endpoint at `/metrics` on port `2112` by default. This endpoint can be used to monitor health and the performance of `kubewatch`.
241+
242+
The `kubewatch_events_total` metric can help track the total number of Kubernetes events, categorized by resource type (e.g., `Pods`, `Deployments`) and event type (e.g., `Create`, `Delete`).
243+
244+
You can change the default port (`2112`) on which the metrics server listens by setting the `LISTEN_ADDRESS` environment variable.
245+
Format is `host:port`. `:5454` means any host, and port `5454`
246+
247+
248+
```yaml
249+
extraEnvVars:
250+
- name: LISTEN_ADDRESS
251+
value: ":5454"
252+
```
253+
239254
### Local Installation
240255
#### Using go package installer:
241256

cmd/config.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package cmd
1818

1919
import (
2020
"fmt"
21-
"io/ioutil"
2221
"os"
2322
"path/filepath"
2423

@@ -94,7 +93,7 @@ var configViewCmd = &cobra.Command{
9493
Display the contents of the contents of ~/.kubewatch.yaml`,
9594
Run: func(cmd *cobra.Command, args []string) {
9695
fmt.Fprintln(os.Stderr, "Contents of ~/.kubewatch.yaml")
97-
configFile, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), kubewatchConfigFile))
96+
configFile, err := os.ReadFile(filepath.Join(os.Getenv("HOME"), kubewatchConfigFile))
9897
if err != nil {
9998
fmt.Fprintf(os.Stderr, "%v\n", err)
10099
os.Exit(1)

config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ limitations under the License.
1919
package config
2020

2121
import (
22-
"io/ioutil"
22+
"io"
2323
"os"
2424
"path/filepath"
2525
"runtime"
@@ -237,7 +237,7 @@ func (c *Config) Load() error {
237237
return err
238238
}
239239

240-
b, err := ioutil.ReadAll(file)
240+
b, err := io.ReadAll(file)
241241
if err != nil {
242242
return err
243243
}

go.mod

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ require (
99
github.com/inconshreveable/mousetrap v1.0.0 // indirect
1010
github.com/magiconair/properties v1.7.4 // indirect
1111
github.com/mkmik/multierror v0.3.0
12-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
1312
github.com/pelletier/go-toml v1.0.1 // indirect
13+
github.com/prometheus/client_golang v1.20.3
1414
github.com/segmentio/textio v1.2.0
1515
github.com/sirupsen/logrus v1.6.0
1616
github.com/slack-go/slack v0.6.5
@@ -19,10 +19,6 @@ require (
1919
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect
2020
github.com/spf13/viper v1.0.0
2121
github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb
22-
golang.org/x/net v0.23.0 // indirect
23-
google.golang.org/protobuf v1.33.0 // indirect
24-
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
25-
gopkg.in/yaml.v2 v2.3.0 // indirect
2622
gopkg.in/yaml.v3 v3.0.1
2723
k8s.io/api v0.20.15
2824
k8s.io/apimachinery v0.20.15

go.sum

Lines changed: 1408 additions & 28 deletions
Large diffs are not rendered by default.

pkg/client/run.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ limitations under the License.
1717
package client
1818

1919
import (
20+
"net/http"
21+
"os"
22+
"github.com/prometheus/client_golang/prometheus/promhttp"
23+
2024
"github.com/bitnami-labs/kubewatch/config"
2125
"github.com/bitnami-labs/kubewatch/pkg/controller"
2226
"github.com/bitnami-labs/kubewatch/pkg/handlers"
@@ -35,6 +39,18 @@ import (
3539

3640
// Run runs the event loop processing with given handler
3741
func Run(conf *config.Config) {
42+
listenAddress := os.Getenv("LISTEN_ADDRESS")
43+
if listenAddress == "" {
44+
listenAddress = ":2112"
45+
}
46+
47+
go func() {
48+
http.Handle("/metrics", promhttp.Handler())
49+
logrus.Infof("Starting metrics server on port %s", listenAddress)
50+
if err := http.ListenAndServe(listenAddress, nil); err != nil {
51+
logrus.Errorf("Error starting metrics server on port %s: %v", listenAddress, err)
52+
}
53+
}()
3854

3955
var eventHandler = ParseEventHandler(conf)
4056
controller.Start(conf, eventHandler)

pkg/controller/controller.go

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ import (
5151
"k8s.io/client-go/rest"
5252
"k8s.io/client-go/tools/cache"
5353
"k8s.io/client-go/util/workqueue"
54+
55+
"github.com/prometheus/client_golang/prometheus"
56+
"github.com/prometheus/client_golang/prometheus/promauto"
5457
)
5558

5659
const maxRetries = 5
@@ -93,6 +96,14 @@ func objName(obj interface{}) string {
9396
func Start(conf *config.Config, eventHandler handlers.Handler) {
9497
var kubeClient kubernetes.Interface
9598
var dynamicClient dynamic.Interface
99+
100+
kubewatchEventsMetrics := promauto.NewCounterVec(
101+
prometheus.CounterOpts{
102+
Name: "kubewatch_events_total",
103+
Help: "The total number of Kubernetes events observed by Kubewatch, labeled by resource and event type",
104+
},
105+
[]string{"resourceType", "eventType"},
106+
)
96107

97108
if _, err := rest.InClusterConfig(); err != nil {
98109
kubeClient = utils.GetClientOutOfCluster()
@@ -120,7 +131,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
120131
cache.Indexers{},
121132
)
122133

123-
allCoreEventsController := newResourceController(kubeClient, eventHandler, allCoreEventsInformer, objName(api_v1.Event{}), V1)
134+
allCoreEventsController := newResourceController(kubeClient, eventHandler, allCoreEventsInformer, objName(api_v1.Event{}), V1, kubewatchEventsMetrics)
124135
stopAllCoreEventsCh := make(chan struct{})
125136
defer close(stopAllCoreEventsCh)
126137

@@ -144,7 +155,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
144155
cache.Indexers{},
145156
)
146157

147-
allEventsController := newResourceController(kubeClient, eventHandler, allEventsInformer, objName(events_v1.Event{}), EVENTS_V1)
158+
allEventsController := newResourceController(kubeClient, eventHandler, allEventsInformer, objName(events_v1.Event{}), EVENTS_V1, kubewatchEventsMetrics)
148159
stopAllEventsCh := make(chan struct{})
149160
defer close(stopAllEventsCh)
150161

@@ -166,7 +177,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
166177
cache.Indexers{},
167178
)
168179

169-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Pod{}), V1)
180+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Pod{}), V1, kubewatchEventsMetrics)
170181
stopCh := make(chan struct{})
171182
defer close(stopCh)
172183

@@ -188,7 +199,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
188199
cache.Indexers{},
189200
)
190201

191-
c := newResourceController(kubeClient, eventHandler, informer, objName(autoscaling_v1.HorizontalPodAutoscaler{}), AUTOSCALING_V1)
202+
c := newResourceController(kubeClient, eventHandler, informer, objName(autoscaling_v1.HorizontalPodAutoscaler{}), AUTOSCALING_V1, kubewatchEventsMetrics)
192203
stopCh := make(chan struct{})
193204
defer close(stopCh)
194205

@@ -211,7 +222,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
211222
cache.Indexers{},
212223
)
213224

214-
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.DaemonSet{}), APPS_V1)
225+
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.DaemonSet{}), APPS_V1, kubewatchEventsMetrics)
215226
stopCh := make(chan struct{})
216227
defer close(stopCh)
217228

@@ -233,7 +244,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
233244
cache.Indexers{},
234245
)
235246

236-
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.StatefulSet{}), APPS_V1)
247+
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.StatefulSet{}), APPS_V1, kubewatchEventsMetrics)
237248
stopCh := make(chan struct{})
238249
defer close(stopCh)
239250

@@ -255,7 +266,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
255266
cache.Indexers{},
256267
)
257268

258-
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.ReplicaSet{}), APPS_V1)
269+
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.ReplicaSet{}), APPS_V1, kubewatchEventsMetrics)
259270
stopCh := make(chan struct{})
260271
defer close(stopCh)
261272

@@ -277,7 +288,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
277288
cache.Indexers{},
278289
)
279290

280-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Service{}), V1)
291+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Service{}), V1, kubewatchEventsMetrics)
281292
stopCh := make(chan struct{})
282293
defer close(stopCh)
283294

@@ -299,7 +310,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
299310
cache.Indexers{},
300311
)
301312

302-
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.Deployment{}), APPS_V1)
313+
c := newResourceController(kubeClient, eventHandler, informer, objName(apps_v1.Deployment{}), APPS_V1, kubewatchEventsMetrics)
303314
stopCh := make(chan struct{})
304315
defer close(stopCh)
305316

@@ -321,7 +332,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
321332
cache.Indexers{},
322333
)
323334

324-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Namespace{}), V1)
335+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Namespace{}), V1, kubewatchEventsMetrics)
325336
stopCh := make(chan struct{})
326337
defer close(stopCh)
327338

@@ -343,7 +354,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
343354
cache.Indexers{},
344355
)
345356

346-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.ReplicationController{}), V1)
357+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.ReplicationController{}), V1, kubewatchEventsMetrics)
347358
stopCh := make(chan struct{})
348359
defer close(stopCh)
349360

@@ -365,7 +376,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
365376
cache.Indexers{},
366377
)
367378

368-
c := newResourceController(kubeClient, eventHandler, informer, objName(batch_v1.Job{}), BATCH_V1)
379+
c := newResourceController(kubeClient, eventHandler, informer, objName(batch_v1.Job{}), BATCH_V1, kubewatchEventsMetrics)
369380
stopCh := make(chan struct{})
370381
defer close(stopCh)
371382

@@ -387,7 +398,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
387398
cache.Indexers{},
388399
)
389400

390-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Node{}), V1)
401+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Node{}), V1, kubewatchEventsMetrics)
391402
stopCh := make(chan struct{})
392403
defer close(stopCh)
393404

@@ -409,7 +420,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
409420
cache.Indexers{},
410421
)
411422

412-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.ServiceAccount{}), V1)
423+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.ServiceAccount{}), V1, kubewatchEventsMetrics)
413424
stopCh := make(chan struct{})
414425
defer close(stopCh)
415426

@@ -431,7 +442,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
431442
cache.Indexers{},
432443
)
433444

434-
c := newResourceController(kubeClient, eventHandler, informer, objName(rbac_v1.ClusterRole{}), RBAC_V1)
445+
c := newResourceController(kubeClient, eventHandler, informer, objName(rbac_v1.ClusterRole{}), RBAC_V1, kubewatchEventsMetrics)
435446
stopCh := make(chan struct{})
436447
defer close(stopCh)
437448

@@ -453,7 +464,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
453464
cache.Indexers{},
454465
)
455466

456-
c := newResourceController(kubeClient, eventHandler, informer, objName(rbac_v1.ClusterRoleBinding{}), RBAC_V1)
467+
c := newResourceController(kubeClient, eventHandler, informer, objName(rbac_v1.ClusterRoleBinding{}), RBAC_V1, kubewatchEventsMetrics)
457468
stopCh := make(chan struct{})
458469
defer close(stopCh)
459470

@@ -475,7 +486,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
475486
cache.Indexers{},
476487
)
477488

478-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.PersistentVolume{}), V1)
489+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.PersistentVolume{}), V1, kubewatchEventsMetrics)
479490
stopCh := make(chan struct{})
480491
defer close(stopCh)
481492

@@ -497,7 +508,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
497508
cache.Indexers{},
498509
)
499510

500-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Secret{}), V1)
511+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.Secret{}), V1, kubewatchEventsMetrics)
501512
stopCh := make(chan struct{})
502513
defer close(stopCh)
503514

@@ -519,7 +530,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
519530
cache.Indexers{},
520531
)
521532

522-
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.ConfigMap{}), V1)
533+
c := newResourceController(kubeClient, eventHandler, informer, objName(api_v1.ConfigMap{}), V1, kubewatchEventsMetrics)
523534
stopCh := make(chan struct{})
524535
defer close(stopCh)
525536

@@ -541,7 +552,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
541552
cache.Indexers{},
542553
)
543554

544-
c := newResourceController(kubeClient, eventHandler, informer, objName(networking_v1.Ingress{}), NETWORKING_V1)
555+
c := newResourceController(kubeClient, eventHandler, informer, objName(networking_v1.Ingress{}), NETWORKING_V1, kubewatchEventsMetrics)
545556
stopCh := make(chan struct{})
546557
defer close(stopCh)
547558

@@ -572,7 +583,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
572583
cache.Indexers{},
573584
)
574585

575-
c := newResourceController(kubeClient, eventHandler, informer, crd.Resource, fmt.Sprintf("%s/%s", crd.Group, crd.Version))
586+
c := newResourceController(kubeClient, eventHandler, informer, crd.Resource, fmt.Sprintf("%s/%s", crd.Group, crd.Version), kubewatchEventsMetrics)
576587
stopCh := make(chan struct{})
577588
defer close(stopCh)
578589

@@ -585,7 +596,7 @@ func Start(conf *config.Config, eventHandler handlers.Handler) {
585596
<-sigterm
586597
}
587598

588-
func newResourceController(client kubernetes.Interface, eventHandler handlers.Handler, informer cache.SharedIndexInformer, resourceType string, apiVersion string) *Controller {
599+
func newResourceController(client kubernetes.Interface, eventHandler handlers.Handler, informer cache.SharedIndexInformer, resourceType string, apiVersion string, kubewatchEventsMetrics *prometheus.CounterVec) *Controller {
589600
queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
590601
var newEvent Event
591602
var err error
@@ -605,6 +616,8 @@ func newResourceController(client kubernetes.Interface, eventHandler handlers.Ha
605616
if err == nil {
606617
queue.Add(newEvent)
607618
}
619+
620+
kubewatchEventsMetrics.WithLabelValues(resourceType, "create").Inc()
608621
},
609622
UpdateFunc: func(old, new interface{}) {
610623
var ok bool
@@ -625,6 +638,8 @@ func newResourceController(client kubernetes.Interface, eventHandler handlers.Ha
625638
if err == nil {
626639
queue.Add(newEvent)
627640
}
641+
642+
kubewatchEventsMetrics.WithLabelValues(resourceType, "update").Inc()
628643
},
629644
DeleteFunc: func(obj interface{}) {
630645
var ok bool
@@ -641,6 +656,8 @@ func newResourceController(client kubernetes.Interface, eventHandler handlers.Ha
641656
if err == nil {
642657
queue.Add(newEvent)
643658
}
659+
660+
kubewatchEventsMetrics.WithLabelValues(resourceType, "delete").Inc()
644661
},
645662
})
646663

pkg/handlers/msteam/msteam.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"encoding/json"
2222
"fmt"
2323
"github.com/sirupsen/logrus"
24-
"io/ioutil"
24+
"io"
2525
"net/http"
2626
"os"
2727

@@ -98,7 +98,7 @@ func sendCard(ms *MSTeams, card *TeamsMessageCard) (*http.Response, error) {
9898
ms.TeamsWebhookURL, err)
9999
}
100100
if res.StatusCode != http.StatusOK {
101-
resMessage, err := ioutil.ReadAll(res.Body)
101+
resMessage, err := io.ReadAll(res.Body)
102102
if err != nil {
103103
return nil, fmt.Errorf("Failed reading Teams http response: %v", err)
104104
}

pkg/handlers/webhook/webhook.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"crypto/x509"
2222
"fmt"
2323
"github.com/sirupsen/logrus"
24-
"io/ioutil"
2524
"os"
2625

2726
"bytes"
@@ -88,7 +87,7 @@ func (m *Webhook) Init(c *config.Config) error {
8887
if cert == "" {
8988
logrus.Printf("No webhook cert is given")
9089
} else {
91-
caCert, err := ioutil.ReadFile(cert)
90+
caCert, err := os.ReadFile(cert)
9291
if err != nil {
9392
logrus.Printf("%s\n", err)
9493
return err

0 commit comments

Comments
 (0)