mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
feat(demo): add a live demo setup for heroku
This commit is contained in:
@@ -8,4 +8,4 @@ ui/coverage
|
||||
ui/node_modules
|
||||
vendor
|
||||
Dockerfile
|
||||
Dockerfile.*
|
||||
demo
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM alpine:latest
|
||||
COPY --from=lmierzwa/karma:latest /karma /karma
|
||||
RUN adduser -D karma
|
||||
USER karma
|
||||
ENV LOG_CONFIG=false
|
||||
ENV ALERTMANAGER_INTERVAL=2400h
|
||||
ENV ALERTMANAGER_URI=file:///mock
|
||||
ENV LABELS_COLOR_UNIQUE="@receiver instance cluster"
|
||||
ENV LABELS_COLOR_STATIC="job"
|
||||
ENV FILTERS_DEFAULT="@receiver=by-cluster-service"
|
||||
COPY internal/mock/0.15.2 /mock
|
||||
CMD /karma
|
||||
6
Makefile
6
Makefile
@@ -156,9 +156,3 @@ mock-assets: .build/deps-build-go.ok
|
||||
|
||||
.PHONY: ui
|
||||
ui: .build/artifacts-ui.ok
|
||||
|
||||
.PHONY: heroku
|
||||
heroku:
|
||||
docker pull lmierzwa/karma:latest
|
||||
heroku container:push web -R
|
||||
heroku container:release web
|
||||
|
||||
16
demo/Dockerfile.web
Normal file
16
demo/Dockerfile.web
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --update supervisor python && rm -rf /tmp/* /var/cache/apk/*
|
||||
COPY supervisord.conf /etc/supervisord.conf
|
||||
|
||||
COPY --from=prom/alertmanager:latest /bin/alertmanager /alertmanager
|
||||
COPY alertmanager.yaml /etc/alertmanager.yaml
|
||||
|
||||
COPY generator.py /generator.py
|
||||
|
||||
COPY --from=lmierzwa/karma:latest /karma /karma
|
||||
COPY karma.yaml /etc/karma.yaml
|
||||
|
||||
RUN adduser -D karma
|
||||
USER karma
|
||||
CMD supervisord --nodaemon --configuration /etc/supervisord.conf
|
||||
5
demo/Makefile
Normal file
5
demo/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
.PHONY: heroku
|
||||
heroku:
|
||||
docker pull lmierzwa/karma:latest
|
||||
heroku container:push web -R
|
||||
heroku container:release web
|
||||
32
demo/alertmanager.yaml
Normal file
32
demo/alertmanager.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
global:
|
||||
resolve_timeout: 30s
|
||||
route:
|
||||
group_by: ['alertname']
|
||||
group_wait: 5s
|
||||
group_interval: 10s
|
||||
repeat_interval: 999h
|
||||
receiver: 'default'
|
||||
routes:
|
||||
- receiver: 'by-cluster-service'
|
||||
group_by: ['alertname', 'cluster', 'service']
|
||||
match_re:
|
||||
alertname: .*
|
||||
continue: true
|
||||
- receiver: 'by-name'
|
||||
group_by: [alertname]
|
||||
match_re:
|
||||
alertname: .*
|
||||
continue: true
|
||||
|
||||
inhibit_rules:
|
||||
- source_match:
|
||||
severity: 'critical'
|
||||
target_match:
|
||||
severity: 'warning'
|
||||
# Apply inhibition if the alertname is the same.
|
||||
equal: ['alertname', 'cluster', 'service']
|
||||
|
||||
receivers:
|
||||
- name: 'default'
|
||||
- name: 'by-cluster-service'
|
||||
- name: 'by-name'
|
||||
271
demo/generator.py
Executable file
271
demo/generator.py
Executable file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Generates alerts and sends to Alertmanager API.
|
||||
|
||||
1. Start Alertmanager:
|
||||
|
||||
$ docker run \
|
||||
--rm \
|
||||
--name prom \
|
||||
-p 9093:9093 \
|
||||
-v $(pwd)/alertmanager.yml:/etc/alertmanager/alertmanager.yml \
|
||||
prom/alertmanager
|
||||
|
||||
2. Start this script
|
||||
3. Start karma:
|
||||
|
||||
$ karma \
|
||||
--alertmanager.uri http://localhost:9093 \
|
||||
--alertmanager.interval 10s \
|
||||
--annotations.hidden help \
|
||||
--labels.color.unique "@receiver instance cluster" \
|
||||
--labels.color.static job \
|
||||
--filters.default "@receiver=by-cluster-service"
|
||||
"""
|
||||
|
||||
|
||||
import random
|
||||
import json
|
||||
import datetime
|
||||
import time
|
||||
import urllib2
|
||||
|
||||
|
||||
API = "http://localhost:9093"
|
||||
MAX_INTERVAL = 10
|
||||
MIN_INTERVAL = 5
|
||||
|
||||
|
||||
def jsonGetRequest(uri):
|
||||
req = urllib2.Request(uri)
|
||||
response = urllib2.urlopen(req)
|
||||
return json.load(response)
|
||||
|
||||
def jsonPostRequest(uri, data):
|
||||
req = urllib2.Request(uri)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
response = urllib2.urlopen(req, json.dumps(data))
|
||||
|
||||
|
||||
def addSilence(matchers, startsAt, endsAt, createdBy, comment):
|
||||
uri = "{}/api/v1/silences".format(API)
|
||||
|
||||
silences = jsonGetRequest(uri)
|
||||
found = False
|
||||
for silence in silences["data"]:
|
||||
if silence["status"]["state"] != "active":
|
||||
continue
|
||||
if silence["createdBy"] == createdBy and silence["comment"] == comment:
|
||||
if json.dumps(silence["matchers"], sort_keys=True) == json.dumps(
|
||||
matchers, sort_keys=True):
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
jsonPostRequest(uri, {
|
||||
"matchers": matchers,
|
||||
"startsAt": startsAt,
|
||||
"endsAt": endsAt,
|
||||
"createdBy": createdBy,
|
||||
"comment": comment
|
||||
})
|
||||
|
||||
|
||||
def addAlerts(alerts):
|
||||
jsonPostRequest("{}/api/v1/alerts".format(API), alerts)
|
||||
|
||||
|
||||
def newMatcher(name, value, isRegex):
|
||||
return {"name": name, "value": value, "isRegex": isRegex}
|
||||
|
||||
|
||||
def newAlert(labels, annotations=None, generatorURL="http://localhost:9093"):
|
||||
return {
|
||||
"labels": labels,
|
||||
"annotations": annotations or {},
|
||||
"generatorURL": generatorURL
|
||||
}
|
||||
|
||||
|
||||
class AlertGenerator(object):
|
||||
name = "Fake Alert"
|
||||
comment = ""
|
||||
|
||||
def __init__(self, interval=15):
|
||||
self._interval = interval
|
||||
self._lastSend = 1
|
||||
|
||||
def _annotations(self, **kwargs):
|
||||
annotations = {"help": self.comment}
|
||||
annotations.update(kwargs)
|
||||
return annotations
|
||||
|
||||
def _labels(self, **kwargs):
|
||||
labels = {"alertname": self.name}
|
||||
labels.update(kwargs)
|
||||
return labels
|
||||
|
||||
def _send(self):
|
||||
alerts = self.alerts()
|
||||
if alerts:
|
||||
addAlerts(alerts)
|
||||
|
||||
for silence in self.silences():
|
||||
addSilence(*silence)
|
||||
|
||||
def alerts(self):
|
||||
return []
|
||||
|
||||
def silences(self):
|
||||
return []
|
||||
|
||||
def tick(self):
|
||||
if time.time() - self._lastSend >= self._interval:
|
||||
self._send()
|
||||
self._lastSend = time.time()
|
||||
|
||||
|
||||
class AlwaysOnAlert(AlertGenerator):
|
||||
name = "Always On Alert"
|
||||
comment = "This alert is always firing"
|
||||
|
||||
def alerts(self):
|
||||
def _gen(size, cluster):
|
||||
return [newAlert(
|
||||
self._labels(instance="server{}".format(i), cluster=cluster),
|
||||
self._annotations(
|
||||
summary="Silence this alert, it's always firing")
|
||||
) for i in xrange(1, size)]
|
||||
return _gen(10, "dev") + _gen(5, "staging") + _gen(3, "prod")
|
||||
|
||||
|
||||
class RandomInstances(AlertGenerator):
|
||||
name = "Random Instances"
|
||||
comment = "This alerts will have a random number of instances"
|
||||
|
||||
def alerts(self):
|
||||
instances = random.randint(0, 30)
|
||||
return [
|
||||
newAlert(
|
||||
self._labels(instance="server{}".format(i), cluster="staging"),
|
||||
self._annotations(
|
||||
dashboard="https://www.google.com/search?q="
|
||||
"server{}".format(i))
|
||||
) for i in xrange(0, instances)
|
||||
]
|
||||
|
||||
|
||||
class RandomName(AlertGenerator):
|
||||
name = "Random Alert Name"
|
||||
comment = "This alerts will have a random name"
|
||||
|
||||
def alerts(self):
|
||||
alerts = []
|
||||
for i in xrange(0, 1):
|
||||
throw = random.randint(0, 1000)
|
||||
alerts.append(
|
||||
newAlert(
|
||||
self._labels(alertname="Alert Nr {}".format(throw),
|
||||
instance="server{}".format(i),
|
||||
cluster="dev"),
|
||||
self._annotations(
|
||||
summary="This is a random alert",
|
||||
dashboard="https://www.google.com/search?q="
|
||||
"server{}".format(i))
|
||||
)
|
||||
)
|
||||
return alerts
|
||||
|
||||
|
||||
class LowChance(AlertGenerator):
|
||||
name = "Low Chance"
|
||||
comment = "This alert has only a 20% chance of firing"
|
||||
|
||||
def alerts(self):
|
||||
throw = random.randint(0, 100)
|
||||
if throw > 20:
|
||||
return []
|
||||
return [
|
||||
newAlert(
|
||||
self._labels(instance="server{}".format(i), cluster="dev"),
|
||||
self._annotations()
|
||||
) for i in xrange(0, 3)
|
||||
]
|
||||
|
||||
|
||||
class TimeAnnotation(AlertGenerator):
|
||||
name = "Time Annotation"
|
||||
comment = "This alert includes a 'time' annotation that changes every N \
|
||||
seconds"
|
||||
|
||||
def alerts(self):
|
||||
return [
|
||||
newAlert(self._labels(instance="server1", cluster="prod"),
|
||||
self._annotations(time=str(int(time.time())))
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class DiskFreeLowAlert(AlertGenerator):
|
||||
name = "Disk Free Low"
|
||||
comment = "This alert simulates a warning about low disk space"
|
||||
|
||||
def alerts(self):
|
||||
alerts = []
|
||||
for i in xrange(0, 10):
|
||||
spaceFree = throw = random.randint(0, 10)
|
||||
alerts.append(
|
||||
newAlert(self._labels(instance="server{}".format(i),
|
||||
cluster="prod",
|
||||
device="/dev/sda{}".format(i),
|
||||
mount_point="/disk"),
|
||||
self._annotations(
|
||||
summary="Only {}% free space left on /disk".format(
|
||||
spaceFree),
|
||||
dashboard="https://wikipedia.org/wiki/Disk_storage")
|
||||
)
|
||||
)
|
||||
return alerts
|
||||
|
||||
|
||||
class SilencedAlert(AlertGenerator):
|
||||
name = "Always Silenced Alert"
|
||||
comment = "This alert is always silenced"
|
||||
|
||||
def alerts(self):
|
||||
return [
|
||||
newAlert(self._labels(instance="server1", cluster="prod"),
|
||||
self._annotations(
|
||||
alertReference="https://www."
|
||||
"youtube.com/watch?v=dQw4w9WgXcQ")
|
||||
)
|
||||
]
|
||||
|
||||
def silences(self):
|
||||
now = datetime.datetime.utcnow().replace(microsecond=0)
|
||||
return [
|
||||
(
|
||||
[newMatcher("alertname", SilencedAlert.name, False)],
|
||||
"{}Z".format(now.isoformat()),
|
||||
"{}Z".format((now + datetime.timedelta(
|
||||
minutes=30)).isoformat()),
|
||||
"me@example.com",
|
||||
"Silence '{}''".format(self.name)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generators = [
|
||||
AlwaysOnAlert(MAX_INTERVAL),
|
||||
RandomInstances(MAX_INTERVAL),
|
||||
LowChance(MAX_INTERVAL),
|
||||
TimeAnnotation(MIN_INTERVAL),
|
||||
DiskFreeLowAlert(MIN_INTERVAL),
|
||||
SilencedAlert(MAX_INTERVAL),
|
||||
RandomName(MAX_INTERVAL),
|
||||
]
|
||||
while True:
|
||||
for g in generators:
|
||||
g.tick()
|
||||
time.sleep(1)
|
||||
27
demo/karma.yaml
Normal file
27
demo/karma.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
alertmanager:
|
||||
interval: 10s
|
||||
servers:
|
||||
- name: demo
|
||||
uri: "http://localhost:9093"
|
||||
timeout: 10s
|
||||
proxy: true
|
||||
annotations:
|
||||
hidden:
|
||||
- help
|
||||
filters:
|
||||
default:
|
||||
- "@receiver=by-cluster-service"
|
||||
labels:
|
||||
color:
|
||||
static:
|
||||
- job
|
||||
unique:
|
||||
- cluster
|
||||
- instance
|
||||
- "@receiver"
|
||||
log:
|
||||
config: false
|
||||
level: warning
|
||||
sentry:
|
||||
private: https://84a9ef37a6ed4fdb80e9ea2310d1ed26:8c6ee6f0ab02406482ff4b4e824e2c27@sentry.io/1279017
|
||||
public: https://84a9ef37a6ed4fdb80e9ea2310d1ed26@sentry.io/1279017
|
||||
31
demo/supervisord.conf
Normal file
31
demo/supervisord.conf
Normal file
@@ -0,0 +1,31 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
pidfile=/tmp/supervisord.pid
|
||||
logfile = /tmp/supervisord.log
|
||||
logfile_maxbytes = 1MB
|
||||
logfile_backups=0
|
||||
loglevel = info
|
||||
|
||||
[program:alertmanager]
|
||||
command=/alertmanager --config.file=/etc/alertmanager.yaml --storage.path=/tmp/alertmanager
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:generator]
|
||||
command=/generator.py
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:karma]
|
||||
command=/karma --config.dir /etc/
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Generates alerts and sends to Alertmanager API.
|
||||
|
||||
1. Start Alertmanager:
|
||||
|
||||
$ docker run \
|
||||
--rm \
|
||||
--name prom \
|
||||
-p 9093:9093 \
|
||||
-v $(pwd)/alertmanager.yml:/etc/alertmanager/alertmanager.yml \
|
||||
prom/alertmanager
|
||||
|
||||
2. Start this script
|
||||
3. Start karma:
|
||||
|
||||
$ karma \
|
||||
--alertmanager.uri http://localhost:9093 \
|
||||
--alertmanager.interval 10s \
|
||||
--annotations.hidden help \
|
||||
--labels.color.unique "@receiver instance cluster" \
|
||||
--labels.color.static job \
|
||||
--filters.default "@receiver=by-cluster-service"
|
||||
"""
|
||||
|
||||
|
||||
import random
|
||||
import json
|
||||
import time
|
||||
import urllib2
|
||||
|
||||
|
||||
API = "http://localhost:9093"
|
||||
|
||||
|
||||
def jsonPostRequest(uri, data):
|
||||
req = urllib2.Request(uri)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
response = urllib2.urlopen(req, json.dumps(data))
|
||||
|
||||
|
||||
def addSilence(matchers, startsAt, endsAt, createdBy, comment):
|
||||
jsonPostRequest("{}/api/v1/silences".format(API), {
|
||||
"matchers": matchers,
|
||||
"startsAt": startsAt,
|
||||
"endsAt": endsAt,
|
||||
"createdBy": createdBy,
|
||||
"comment": comment
|
||||
})
|
||||
|
||||
|
||||
def addAlerts(alerts):
|
||||
jsonPostRequest("{}/api/v1/alerts".format(API), alerts)
|
||||
|
||||
|
||||
def newMatcher(name, value, isRegex):
|
||||
return {"name": name, "value": value, "isRegex": isRegex}
|
||||
|
||||
|
||||
def newAlert(labels, annotations=None, generatorURL="http://localhost:9093"):
|
||||
return {
|
||||
"labels": labels,
|
||||
"annotations": annotations or {},
|
||||
"generatorURL": generatorURL
|
||||
}
|
||||
|
||||
|
||||
class AlertGenerator(object):
|
||||
name = "Fake Alert"
|
||||
|
||||
def __init__(self, interval=15):
|
||||
self._interval = interval
|
||||
self._lastSend = 1
|
||||
|
||||
def _labels(self, **kwargs):
|
||||
labels = {"alertname": self.name}
|
||||
labels.update(kwargs)
|
||||
return labels
|
||||
|
||||
def _send(self):
|
||||
alerts = self.generate()
|
||||
if alerts:
|
||||
print("{} sending {} alert(s)".format(self.name, len(alerts)))
|
||||
addAlerts(alerts)
|
||||
|
||||
def tick(self):
|
||||
if time.time() - self._lastSend >= self._interval:
|
||||
self._send()
|
||||
self._lastSend = time.time()
|
||||
|
||||
|
||||
class AlwaysOnAlert(AlertGenerator):
|
||||
name = "Always On Alert"
|
||||
|
||||
def generate(self):
|
||||
return [
|
||||
newAlert(
|
||||
self._labels(instance="server{}".format(i))
|
||||
) for i in xrange(0, 10)
|
||||
]
|
||||
|
||||
|
||||
class RandomInstances(AlertGenerator):
|
||||
name = "Random Instances"
|
||||
|
||||
def generate(self):
|
||||
instances = random.randint(0, 30)
|
||||
return [
|
||||
newAlert(
|
||||
self._labels(instance="server{}".format(i))
|
||||
) for i in xrange(0, instances)
|
||||
]
|
||||
|
||||
|
||||
class LowChance(AlertGenerator):
|
||||
name = "Low Chance"
|
||||
|
||||
def generate(self):
|
||||
throw = random.randint(0, 100)
|
||||
if throw > 10:
|
||||
return []
|
||||
return [
|
||||
newAlert(
|
||||
self._labels(instance="server{}".format(i))
|
||||
) for i in xrange(0, 3)
|
||||
]
|
||||
|
||||
|
||||
class TimeAnnotation(AlertGenerator):
|
||||
name = "Time Annotation"
|
||||
|
||||
def generate(self):
|
||||
annotations = {"time": str(int(time.time()))}
|
||||
return [
|
||||
newAlert(self._labels(instance="server1"), annotations)
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generators = [
|
||||
AlwaysOnAlert(15),
|
||||
RandomInstances(30),
|
||||
LowChance(60),
|
||||
TimeAnnotation(5),
|
||||
]
|
||||
while True:
|
||||
for g in generators:
|
||||
g.tick()
|
||||
time.sleep(1)
|
||||
Reference in New Issue
Block a user