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

feat: basic metrics #147

Merged
merged 2 commits into from
Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
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` |
moolen marked this conversation as resolved.
Show resolved Hide resolved
| `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