Skip to content
This repository was archived by the owner on Jul 26, 2022. It is now read-only.

Commit ded6d31

Browse files
moolenkeweilu
authored andcommitted
feat: basic metrics (#147)
* feat: basic metrics Signed-off-by: Moritz Johner <[email protected]> * chore: update chart docs & env vars
1 parent 438b8a3 commit ded6d31

14 files changed

+715
-6
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ The following table lists the configurable parameters of the `kubernetes-externa
5353
| Parameter | Description | Default |
5454
| ----------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
5555
| `env.AWS_REGION` | Set AWS_REGION in Deployment Pod | `us-west-2` |
56+
| `env.LOG_LEVEL` | Set the application log level | `info` |
57+
| `env.METRICS_PORT` | Specify the port for the prometheus metrics server | `3001` |
5658
| `env.POLLER_INTERVAL_MILLISECONDS` | Set POLLER_INTERVAL_MILLISECONDS in Deployment Pod | `10000` |
5759
| `envVarsFromSecret.AWS_ACCESS_KEY_ID` | Set AWS_ACCESS_KEY_ID (from a secret) in Deployment Pod | |
5860
| `envVarsFromSecret.AWS_SECRET_ACCESS_KEY` | Set AWS_SECRET_ACCESS_KEY (from a secret) in Deployment Pod | |
@@ -186,6 +188,15 @@ secretDescriptor:
186188
property: username
187189
```
188190
191+
## Metrics
192+
193+
kubernetes-external-secrets exposes the following metrics over a prometheus endpoint:
194+
195+
| Metric | Description | Example |
196+
| ----------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
197+
| `sync_calls` | This metric counts the number of sync calls by backend, secret name and status | `sync_calls{name="foo",namespace="example",backend="foo",status="success"} 1` |
198+
199+
189200
## Development
190201

191202
[Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) is a tool that makes it easy to run a Kubernetes cluster locally.

bin/daemon.js

+15
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
// with an exit code of 1, just like any uncaught exception.
88
require('make-promises-safe')
99

10+
const Prometheus = require('prom-client')
1011
const Daemon = require('../lib/daemon')
12+
const MetricsServer = require('../lib/metrics-server')
13+
const Metrics = require('../lib/metrics')
1114
const { getExternalSecretEvents } = require('../lib/external-secret')
1215

1316
const {
@@ -16,6 +19,7 @@ const {
1619
customResourceManager,
1720
customResourceManifest,
1821
logger,
22+
metricsPort,
1923
pollerIntervalMilliseconds
2024
} = require('../config')
2125

@@ -33,16 +37,27 @@ async function main () {
3337
logger
3438
})
3539

40+
const registry = Prometheus.register
41+
const metrics = new Metrics({ registry })
42+
3643
const daemon = new Daemon({
3744
backends,
3845
externalSecretEvents,
3946
kubeClient,
4047
logger,
48+
metrics,
4149
pollerIntervalMilliseconds
4250
})
4351

52+
const metricsServer = new MetricsServer({
53+
port: metricsPort,
54+
registry,
55+
logger
56+
})
57+
4458
logger.info('starting app')
4559
daemon.start()
60+
metricsServer.start()
4661
logger.info('successfully started app')
4762
}
4863

charts/kubernetes-external-secrets/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ The following table lists the configurable parameters of the `kubernetes-externa
3737
| Parameter | Description | Default |
3838
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------- |
3939
| `env.AWS_REGION` | Set AWS_REGION in Deployment Pod | `us-west-2` |
40+
| `env.LOG_LEVEL` | Set the application log level | `info` |
41+
| `env.METRICS_PORT` | Specify the port for the prometheus metrics server | `3001` |
4042
| `env.POLLER_INTERVAL_MILLISECONDS` | Set POLLER_INTERVAL_MILLISECONDS in Deployment Pod | `10000` |
4143
| `envVarsFromSecret.AWS_ACCESS_KEY_ID` | Set AWS_ACCESS_KEY_ID (from a secret) in Deployment Pod | |
4244
| `envVarsFromSecret.AWS_SECRET_ACCESS_KEY` | Set AWS_SECRET_ACCESS_KEY (from a secret) in Deployment Pod | |

charts/kubernetes-external-secrets/values.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
env:
77
AWS_REGION: us-west-2
88
POLLER_INTERVAL_MILLISECONDS: 10000
9+
LOG_LEVEL: info
10+
METRICS_PORT: 3001
911

1012
# Create environment variables from exists k8s secrets
1113
# envVarsFromSecret:

config/environment.js

+3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ const pollerIntervalMilliseconds = process.env.POLLER_INTERVAL_MILLISECONDS
2121

2222
const logLevel = process.env.LOG_LEVEL || 'info'
2323

24+
const metricsPort = process.env.METRICS_PORT || 3001
25+
2426
module.exports = {
2527
environment,
2628
pollerIntervalMilliseconds,
29+
metricsPort,
2730
logLevel
2831
}

lib/daemon.js

+3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ class Daemon {
1919
externalSecretEvents,
2020
kubeClient,
2121
logger,
22+
metrics,
2223
pollerIntervalMilliseconds
2324
}) {
2425
this._backends = backends
2526
this._kubeClient = kubeClient
2627
this._externalSecretEvents = externalSecretEvents
2728
this._logger = logger
29+
this._metrics = metrics
2830
this._pollerIntervalMilliseconds = pollerIntervalMilliseconds
2931

3032
this._pollers = {}
@@ -74,6 +76,7 @@ class Daemon {
7476
intervalMilliseconds: this._pollerIntervalMilliseconds,
7577
kubeClient: this._kubeClient,
7678
logger: this._logger,
79+
metrics: this._metrics,
7780
namespace: descriptor.namespace,
7881
secretDescriptor: descriptor.secretDescriptor,
7982
ownerReference: descriptor.ownerReference

lib/metrics-server.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict'
2+
3+
const express = require('express')
4+
const Prometheus = require('prom-client')
5+
6+
/** MetricsServer class. */
7+
class MetricsServer {
8+
/**
9+
* Create Metrics Server
10+
* @param {number} port - the port to listen on
11+
* @param {Object} logger - Logger for logging stuff
12+
* @param {Object} register - Prometheus registry that holds metric data
13+
*/
14+
constructor ({ port, logger, registry }) {
15+
this._port = port
16+
this._logger = logger
17+
this._registry = registry
18+
19+
this._app = express()
20+
this._app.get('/metrics', (req, res) => {
21+
res.set('Content-Type', Prometheus.register.contentType)
22+
res.end(this._registry.metrics())
23+
})
24+
}
25+
26+
/**
27+
* Start the metrics server: Listen on a TCP port and serve metrics over HTTP
28+
*/
29+
start () {
30+
return new Promise((resolve, reject) => {
31+
this._server = this._app.listen(this._port, () => {
32+
this._logger.info(`MetricsServer listening on port ${this._port}`)
33+
resolve()
34+
})
35+
this._app.on('error', err => reject(err))
36+
})
37+
}
38+
39+
/**
40+
* Stop the metrics server
41+
*/
42+
stop () {
43+
return new Promise((resolve, reject) => {
44+
this._server.close(err => {
45+
if (err) {
46+
return reject(err)
47+
}
48+
resolve()
49+
})
50+
})
51+
}
52+
}
53+
54+
module.exports = MetricsServer

lib/metrics-server.test.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const { expect } = require('chai')
5+
const sinon = require('sinon')
6+
const Prometheus = require('prom-client')
7+
const request = require('supertest')
8+
9+
const MetricsServer = require('./metrics-server')
10+
const Metrics = require('./metrics')
11+
12+
describe('MetricsServer', () => {
13+
let server
14+
let loggerMock
15+
let registry
16+
let metrics
17+
18+
beforeEach(async () => {
19+
loggerMock = sinon.mock()
20+
loggerMock.info = sinon.stub()
21+
registry = new Prometheus.Registry()
22+
metrics = new Metrics({ registry })
23+
24+
server = new MetricsServer({
25+
logger: loggerMock,
26+
registry: registry,
27+
port: 3918
28+
})
29+
30+
await server.start()
31+
})
32+
33+
afterEach(async () => {
34+
sinon.restore()
35+
await server.stop()
36+
})
37+
38+
it('start server to serve metrics', async () => {
39+
metrics.observeSync({
40+
name: 'foo',
41+
namespace: 'example',
42+
backend: 'foo',
43+
status: 'success'
44+
})
45+
46+
metrics.observeSync({
47+
name: 'bar',
48+
namespace: 'example',
49+
backend: 'foo',
50+
status: 'failed'
51+
})
52+
53+
const res = await request('http://localhost:3918')
54+
.get('/metrics')
55+
.expect('Content-Type', Prometheus.register.contentType)
56+
.expect(200)
57+
58+
expect(res.text).to.have.string('sync_calls{name="foo",namespace="example",backend="foo",status="success"} 1')
59+
expect(res.text).to.have.string('sync_calls{name="bar",namespace="example",backend="foo",status="failed"} 1')
60+
})
61+
})

lib/metrics.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
const Prometheus = require('prom-client')
4+
5+
/** Metrics class. */
6+
class Metrics {
7+
/**
8+
* Create Metrics object
9+
*/
10+
constructor ({ registry }) {
11+
this._registry = registry
12+
this._syncCalls = new Prometheus.Counter({
13+
name: 'sync_calls',
14+
help: 'number of sync operations',
15+
labelNames: ['name', 'namespace', 'backend', 'status'],
16+
registers: [registry]
17+
})
18+
}
19+
20+
/**
21+
* Observe the result a sync process
22+
* @param {String} name - the name of the externalSecret
23+
* @param {String} namespace - the namespace of the externalSecret
24+
* @param {String} backend - the backend used to fetch the externalSecret
25+
* @param {String} status - the result of the sync process: error|success
26+
*/
27+
observeSync ({ name, namespace, backend, status }) {
28+
this._syncCalls.inc({
29+
name,
30+
namespace,
31+
backend,
32+
status
33+
})
34+
}
35+
}
36+
37+
module.exports = Metrics

lib/metrics.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const { expect } = require('chai')
5+
const sinon = require('sinon')
6+
const Prometheus = require('prom-client')
7+
8+
const Metrics = require('./metrics')
9+
10+
describe('Metrics', () => {
11+
let registry
12+
let metrics
13+
14+
beforeEach(async () => {
15+
registry = new Prometheus.Registry()
16+
metrics = new Metrics({ registry })
17+
})
18+
19+
afterEach(async () => {
20+
sinon.restore()
21+
})
22+
23+
it('should store metrics', async () => {
24+
metrics.observeSync({
25+
name: 'foo',
26+
namespace: 'example',
27+
backend: 'foo',
28+
status: 'success'
29+
})
30+
expect(registry.metrics()).to.have.string('sync_calls{name="foo",namespace="example",backend="foo",status="success"} 1')
31+
})
32+
})

lib/poller.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class Poller {
3030
logger,
3131
namespace,
3232
secretDescriptor,
33-
ownerReference
33+
ownerReference,
34+
metrics
3435
}) {
3536
this._backends = backends
3637
this._intervalMilliseconds = intervalMilliseconds
@@ -39,6 +40,7 @@ class Poller {
3940
this._namespace = namespace
4041
this._secretDescriptor = secretDescriptor
4142
this._ownerReference = ownerReference
43+
this._metrics = metrics
4244
this._interval = null
4345
}
4446

@@ -75,7 +77,20 @@ class Poller {
7577
await this._upsertKubernetesSecret()
7678
} catch (err) {
7779
this._logger.error(err, `failure while polling the secret ${this._secretDescriptor.name}`)
80+
this._metrics.observeSync({
81+
name: this._secretDescriptor.name,
82+
namespace: this._namespace,
83+
backend: this._secretDescriptor.backendType,
84+
status: 'error'
85+
})
7886
}
87+
88+
this._metrics.observeSync({
89+
name: this._secretDescriptor.name,
90+
namespace: this._namespace,
91+
backend: this._secretDescriptor.backendType,
92+
status: 'success'
93+
})
7994
}
8095

8196
/**

0 commit comments

Comments
 (0)