Skip to content
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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,35 @@ docker compose \

Read more: [Docker installation](docs/getting-started/docker.md)

### Kubernetes (Helm)

A Helm chart lives in [`helm/incidentrelay`](helm/incidentrelay). It deploys the web UI plus the scheduler and Telegram workers, renders the application config from values into a Secret, and wires up the `/healthz` and `/readyz` probes.

> **Note:** no public image is published yet — the default `image.repository` (`roxywi/incidentrelay`) does not exist on Docker Hub, so an out-of-the-box install ends in `ImagePullBackOff`. Build the image yourself, push it to your registry and set `image.repository` / `image.tag`.

```bash
helm install incidentrelay ./helm/incidentrelay \
--set image.repository=registry.example.com/incidentrelay \
--set image.tag=1.0.15-beta \
--set config.main.secret_key="$(openssl rand -hex 32)"
```

The default values use SQLite on a shared PersistentVolumeClaim. This is strictly a single-node setup: SQLite over network-backed `ReadWriteMany` storage (NFS and friends) is a known way to corrupt the database. Database migrations run on web pod start, so keep `web.replicaCount` at `1` (or disable `web.runMigrations` and migrate out of band). For anything multi-node or multi-replica, point `config.database` at PostgreSQL:

```bash
helm install incidentrelay ./helm/incidentrelay \
--set config.main.secret_key="$(openssl rand -hex 32)" \
--set config.database.type=postgresql \
--set config.database.host=postgres.example.svc \
--set config.database.port=5432 \
--set config.database.name=incidentrelay \
--set config.database.user=incidentrelay \
--set config.database.password=change-me \
--set persistence.enabled=false
```

All settings from `incidentrelay.conf` are available under `config.*` in [values.yaml](helm/incidentrelay/values.yaml); you can also bring a pre-rendered config via `existingConfigSecret`.

### RedHat-like distributions from RPM repository

Recommended for RHEL, Rocky Linux, AlmaLinux, and CentOS Stream.
Expand Down
9 changes: 9 additions & 0 deletions helm/incidentrelay/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Patterns to ignore when building packages.
*.tgz
.DS_Store
.git/
.gitignore
*.swp
*.bak
*.tmp
*.orig
14 changes: 14 additions & 0 deletions helm/incidentrelay/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v2
name: incidentrelay
description: On-call scheduling, alert routing and incident notification service
type: application
version: 0.1.0
appVersion: "1.0.15-beta"
home: https://github.com/roxy-wi/IncidentRelay
sources:
- https://github.com/roxy-wi/IncidentRelay
keywords:
- oncall
- incident
- alerting
- notifications
44 changes: 44 additions & 0 deletions helm/incidentrelay/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
IncidentRelay {{ .Chart.AppVersion }} has been deployed.

Components:
web {{ .Values.web.replicaCount }} replica(s)
scheduler {{ .Values.scheduler.enabled | ternary "enabled" "disabled" }}
telegram {{ .Values.telegram.enabled | ternary "enabled" "disabled" }}

Access the web UI:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
{{- end }}
{{- else }}
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "incidentrelay.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }}
then open http://127.0.0.1:{{ .Values.service.port }}
{{- end }}

{{- if eq .Values.image.repository "roxywi/incidentrelay" }}

WARNING: image.repository is left at its default ("roxywi/incidentrelay").
No public image is published under that name yet, so pods will sit in
ImagePullBackOff. Build the image yourself and set image.repository /
image.tag to your registry.
{{- end }}

{{- $dbType := "" }}
{{- if .Values.config }}{{- if .Values.config.database }}{{- $dbType = .Values.config.database.type | default "" }}{{- end }}{{- end }}
{{- if eq ($dbType | toString) "sqlite" }}

NOTE: you are running SQLite. All components share one
PersistentVolumeClaim, which is only safe while every pod runs on the
same node — SQLite over network-backed ReadWriteMany storage (NFS and
friends) is a known way to corrupt the database. For any multi-node
setup switch config.database to PostgreSQL. Migrations run on web pod
start: keep web.replicaCount at 1, or disable web.runMigrations and
migrate out of band before scaling.
{{- end }}

{{- if not .Values.existingConfigSecret }}

NOTE: the application config (including secret_key) is rendered from
values into Secret "{{ include "incidentrelay.configSecretName" . }}".
Change the default "change-me" values before exposing the service.
{{- end }}
140 changes: 140 additions & 0 deletions helm/incidentrelay/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "incidentrelay.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name (63 char limit).
*/}}
{{- define "incidentrelay.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Chart name and version for the chart label.
*/}}
{{- define "incidentrelay.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels.
*/}}
{{- define "incidentrelay.labels" -}}
helm.sh/chart: {{ include "incidentrelay.chart" . }}
{{ include "incidentrelay.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels.
*/}}
{{- define "incidentrelay.selectorLabels" -}}
app.kubernetes.io/name: {{ include "incidentrelay.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Service account name.
*/}}
{{- define "incidentrelay.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "incidentrelay.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Image reference.
*/}}
{{- define "incidentrelay.image" -}}
{{- printf "%s:%s" .Values.image.repository (default .Chart.AppVersion .Values.image.tag) }}
{{- end }}

{{/*
Name of the Secret holding incidentrelay.conf.
*/}}
{{- define "incidentrelay.configSecretName" -}}
{{- default (printf "%s-config" (include "incidentrelay.fullname" .)) .Values.existingConfigSecret }}
{{- end }}

{{/*
Render the values under .Values.config as an INI file.
*/}}
{{- define "incidentrelay.config" -}}
{{- range $section, $options := .Values.config }}
[{{ $section }}]
{{- range $key, $value := $options }}
{{ $key }} = {{ $value }}
{{- end }}
{{ end }}
{{- end }}

{{/*
Name of the PVC backing /var/lib/incidentrelay.
*/}}
{{- define "incidentrelay.dataClaimName" -}}
{{- default (printf "%s-data" (include "incidentrelay.fullname" .)) .Values.persistence.existingClaim }}
{{- end }}

{{/*
Volumes shared by every component.
*/}}
{{- define "incidentrelay.volumes" -}}
- name: config
secret:
secretName: {{ include "incidentrelay.configSecretName" . }}
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ include "incidentrelay.dataClaimName" . }}
{{- else }}
emptyDir: {}
{{- end }}
- name: logs
emptyDir: {}
{{- with .Values.extraVolumes }}
{{ toYaml . }}
{{- end }}
{{- end }}

{{/*
Volume mounts shared by every component.
*/}}
{{- define "incidentrelay.volumeMounts" -}}
- name: config
mountPath: /etc/incidentrelay
readOnly: true
- name: data
mountPath: /var/lib/incidentrelay
- name: logs
mountPath: /var/log/incidentrelay
{{- with .Values.extraVolumeMounts }}
{{ toYaml . }}
{{- end }}
{{- end }}

{{/*
Pod annotation with the config checksum so config changes roll pods.
Empty when an existing Secret is used (the chart cannot see its content).
*/}}
{{- define "incidentrelay.configChecksum" -}}
{{- if not .Values.existingConfigSecret -}}
checksum/config: {{ include "incidentrelay.config" . | sha256sum }}
{{- end }}
{{- end }}
80 changes: 80 additions & 0 deletions helm/incidentrelay/templates/deployment-scheduler.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{{- if .Values.scheduler.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "incidentrelay.fullname" . }}-scheduler
labels:
{{- include "incidentrelay.labels" . | nindent 4 }}
app.kubernetes.io/component: scheduler
spec:
replicas: 1
{{- with .Values.scheduler.strategy }}
strategy:
{{- toYaml . | nindent 4 }}
{{- end }}
selector:
matchLabels:
{{- include "incidentrelay.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: scheduler
template:
metadata:
annotations:
{{- include "incidentrelay.configChecksum" . | nindent 8 }}
{{- with .Values.scheduler.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "incidentrelay.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: scheduler
{{- with .Values.scheduler.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "incidentrelay.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: scheduler
image: {{ include "incidentrelay.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
- name: INCIDENTRELAY_CONFIG_FILE
value: /etc/incidentrelay/incidentrelay.conf
- name: INCIDENTRELAY_SERVICE
value: scheduler
- name: INCIDENTRELAY_RUN_MIGRATIONS
value: "0"
{{- with .Values.scheduler.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.scheduler.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- include "incidentrelay.volumeMounts" . | nindent 12 }}
volumes:
{{- include "incidentrelay.volumes" . | nindent 8 }}
{{- with .Values.scheduler.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.scheduler.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.scheduler.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
Loading