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

Commit

Permalink
feat: basic metrics (#147)
Browse files Browse the repository at this point in the history
* feat: basic metrics

Signed-off-by: Moritz Johner <[email protected]>

* chore: update chart docs & env vars
  • Loading branch information
moolen authored and keweilu committed Sep 27, 2019
1 parent 438b8a3 commit ded6d31
Show file tree
Hide file tree
Showing 14 changed files with 715 additions and 6 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The following table lists the configurable parameters of the `kubernetes-externa
| Parameter | Description | Default |
| ----------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
| `env.AWS_REGION` | Set AWS_REGION in Deployment Pod | `us-west-2` |
| `env.LOG_LEVEL` | Set the application log level | `info` |
| `env.METRICS_PORT` | Specify the port for the prometheus metrics server | `3001` |
| `env.POLLER_INTERVAL_MILLISECONDS` | Set POLLER_INTERVAL_MILLISECONDS in Deployment Pod | `10000` |
| `envVarsFromSecret.AWS_ACCESS_KEY_ID` | Set AWS_ACCESS_KEY_ID (from a secret) in Deployment Pod | |
| `envVarsFromSecret.AWS_SECRET_ACCESS_KEY` | Set AWS_SECRET_ACCESS_KEY (from a secret) in Deployment Pod | |
Expand Down Expand Up @@ -186,6 +188,15 @@ secretDescriptor:
property: username
```
## Metrics
kubernetes-external-secrets exposes the following metrics over a prometheus endpoint:
| Metric | Description | Example |
| ----------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `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` |


## Development

[Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) is a tool that makes it easy to run a Kubernetes cluster locally.
Expand Down
15 changes: 15 additions & 0 deletions bin/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
// with an exit code of 1, just like any uncaught exception.
require('make-promises-safe')

const Prometheus = require('prom-client')
const Daemon = require('../lib/daemon')
const MetricsServer = require('../lib/metrics-server')
const Metrics = require('../lib/metrics')
const { getExternalSecretEvents } = require('../lib/external-secret')

const {
Expand All @@ -16,6 +19,7 @@ const {
customResourceManager,
customResourceManifest,
logger,
metricsPort,
pollerIntervalMilliseconds
} = require('../config')

Expand All @@ -33,16 +37,27 @@ async function main () {
logger
})

const registry = Prometheus.register
const metrics = new Metrics({ registry })

const daemon = new Daemon({
backends,
externalSecretEvents,
kubeClient,
logger,
metrics,
pollerIntervalMilliseconds
})

const metricsServer = new MetricsServer({
port: metricsPort,
registry,
logger
})

logger.info('starting app')
daemon.start()
metricsServer.start()
logger.info('successfully started app')
}

Expand Down
2 changes: 2 additions & 0 deletions charts/kubernetes-external-secrets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ The following table lists the configurable parameters of the `kubernetes-externa
| Parameter | Description | Default |
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------- |
| `env.AWS_REGION` | Set AWS_REGION in Deployment Pod | `us-west-2` |
| `env.LOG_LEVEL` | Set the application log level | `info` |
| `env.METRICS_PORT` | Specify the port for the prometheus metrics server | `3001` |
| `env.POLLER_INTERVAL_MILLISECONDS` | Set POLLER_INTERVAL_MILLISECONDS in Deployment Pod | `10000` |
| `envVarsFromSecret.AWS_ACCESS_KEY_ID` | Set AWS_ACCESS_KEY_ID (from a secret) in Deployment Pod | |
| `envVarsFromSecret.AWS_SECRET_ACCESS_KEY` | Set AWS_SECRET_ACCESS_KEY (from a secret) in Deployment Pod | |
Expand Down
2 changes: 2 additions & 0 deletions charts/kubernetes-external-secrets/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
env:
AWS_REGION: us-west-2
POLLER_INTERVAL_MILLISECONDS: 10000
LOG_LEVEL: info
METRICS_PORT: 3001

# Create environment variables from exists k8s secrets
# envVarsFromSecret:
Expand Down
3 changes: 3 additions & 0 deletions config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ const pollerIntervalMilliseconds = process.env.POLLER_INTERVAL_MILLISECONDS

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

const metricsPort = process.env.METRICS_PORT || 3001

module.exports = {
environment,
pollerIntervalMilliseconds,
metricsPort,
logLevel
}
3 changes: 3 additions & 0 deletions lib/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ class Daemon {
externalSecretEvents,
kubeClient,
logger,
metrics,
pollerIntervalMilliseconds
}) {
this._backends = backends
this._kubeClient = kubeClient
this._externalSecretEvents = externalSecretEvents
this._logger = logger
this._metrics = metrics
this._pollerIntervalMilliseconds = pollerIntervalMilliseconds

this._pollers = {}
Expand Down Expand Up @@ -74,6 +76,7 @@ class Daemon {
intervalMilliseconds: this._pollerIntervalMilliseconds,
kubeClient: this._kubeClient,
logger: this._logger,
metrics: this._metrics,
namespace: descriptor.namespace,
secretDescriptor: descriptor.secretDescriptor,
ownerReference: descriptor.ownerReference
Expand Down
54 changes: 54 additions & 0 deletions lib/metrics-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict'

const express = require('express')
const Prometheus = require('prom-client')

/** MetricsServer class. */
class MetricsServer {
/**
* Create Metrics Server
* @param {number} port - the port to listen on
* @param {Object} logger - Logger for logging stuff
* @param {Object} register - Prometheus registry that holds metric data
*/
constructor ({ port, logger, registry }) {
this._port = port
this._logger = logger
this._registry = registry

this._app = express()
this._app.get('/metrics', (req, res) => {
res.set('Content-Type', Prometheus.register.contentType)
res.end(this._registry.metrics())
})
}

/**
* Start the metrics server: Listen on a TCP port and serve metrics over HTTP
*/
start () {
return new Promise((resolve, reject) => {
this._server = this._app.listen(this._port, () => {
this._logger.info(`MetricsServer listening on port ${this._port}`)
resolve()
})
this._app.on('error', err => reject(err))
})
}

/**
* Stop the metrics server
*/
stop () {
return new Promise((resolve, reject) => {
this._server.close(err => {
if (err) {
return reject(err)
}
resolve()
})
})
}
}

module.exports = MetricsServer
61 changes: 61 additions & 0 deletions lib/metrics-server.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* eslint-env mocha */
'use strict'

const { expect } = require('chai')
const sinon = require('sinon')
const Prometheus = require('prom-client')
const request = require('supertest')

const MetricsServer = require('./metrics-server')
const Metrics = require('./metrics')

describe('MetricsServer', () => {
let server
let loggerMock
let registry
let metrics

beforeEach(async () => {
loggerMock = sinon.mock()
loggerMock.info = sinon.stub()
registry = new Prometheus.Registry()
metrics = new Metrics({ registry })

server = new MetricsServer({
logger: loggerMock,
registry: registry,
port: 3918
})

await server.start()
})

afterEach(async () => {
sinon.restore()
await server.stop()
})

it('start server to serve metrics', async () => {
metrics.observeSync({
name: 'foo',
namespace: 'example',
backend: 'foo',
status: 'success'
})

metrics.observeSync({
name: 'bar',
namespace: 'example',
backend: 'foo',
status: 'failed'
})

const res = await request('http://localhost:3918')
.get('/metrics')
.expect('Content-Type', Prometheus.register.contentType)
.expect(200)

expect(res.text).to.have.string('sync_calls{name="foo",namespace="example",backend="foo",status="success"} 1')
expect(res.text).to.have.string('sync_calls{name="bar",namespace="example",backend="foo",status="failed"} 1')
})
})
37 changes: 37 additions & 0 deletions lib/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict'

const Prometheus = require('prom-client')

/** Metrics class. */
class Metrics {
/**
* Create Metrics object
*/
constructor ({ registry }) {
this._registry = registry
this._syncCalls = new Prometheus.Counter({
name: 'sync_calls',
help: 'number of sync operations',
labelNames: ['name', 'namespace', 'backend', 'status'],
registers: [registry]
})
}

/**
* Observe the result a sync process
* @param {String} name - the name of the externalSecret
* @param {String} namespace - the namespace of the externalSecret
* @param {String} backend - the backend used to fetch the externalSecret
* @param {String} status - the result of the sync process: error|success
*/
observeSync ({ name, namespace, backend, status }) {
this._syncCalls.inc({
name,
namespace,
backend,
status
})
}
}

module.exports = Metrics
32 changes: 32 additions & 0 deletions lib/metrics.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-env mocha */
'use strict'

const { expect } = require('chai')
const sinon = require('sinon')
const Prometheus = require('prom-client')

const Metrics = require('./metrics')

describe('Metrics', () => {
let registry
let metrics

beforeEach(async () => {
registry = new Prometheus.Registry()
metrics = new Metrics({ registry })
})

afterEach(async () => {
sinon.restore()
})

it('should store metrics', async () => {
metrics.observeSync({
name: 'foo',
namespace: 'example',
backend: 'foo',
status: 'success'
})
expect(registry.metrics()).to.have.string('sync_calls{name="foo",namespace="example",backend="foo",status="success"} 1')
})
})
17 changes: 16 additions & 1 deletion lib/poller.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class Poller {
logger,
namespace,
secretDescriptor,
ownerReference
ownerReference,
metrics
}) {
this._backends = backends
this._intervalMilliseconds = intervalMilliseconds
Expand All @@ -39,6 +40,7 @@ class Poller {
this._namespace = namespace
this._secretDescriptor = secretDescriptor
this._ownerReference = ownerReference
this._metrics = metrics
this._interval = null
}

Expand Down Expand Up @@ -75,7 +77,20 @@ class Poller {
await this._upsertKubernetesSecret()
} catch (err) {
this._logger.error(err, `failure while polling the secret ${this._secretDescriptor.name}`)
this._metrics.observeSync({
name: this._secretDescriptor.name,
namespace: this._namespace,
backend: this._secretDescriptor.backendType,
status: 'error'
})
}

this._metrics.observeSync({
name: this._secretDescriptor.name,
namespace: this._namespace,
backend: this._secretDescriptor.backendType,
status: 'success'
})
}

/**
Expand Down
Loading

0 comments on commit ded6d31

Please sign in to comment.