Merge remote-tracking branch 'origin/dev' into oss-launch-doc-updates

This commit is contained in:
Jack Baldry 2022-06-14 17:07:44 +01:00
commit c7ebe220dc
No known key found for this signature in database
GPG key ID: 246FDFBB45F98F4A
33 changed files with 1171 additions and 48 deletions

View file

@ -1,40 +0,0 @@
name: publish_docs
on:
pull_request:
push:
branches:
- main
paths:
- 'docs/sources/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v1
- name: Build Website
run: |
docker run -v ${PWD}/sources:/hugo/content/docs/amixr --rm grafana/docs-base:latest /bin/bash -c 'make hugo'
# sync:
# runs-on: ubuntu-latest
# needs: test
# if: github.ref == 'refs/heads/main'
# steps:
# - uses: actions/checkout@v1
# - run: git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync
# - name: publish-to-git
# uses: ./.github/actions/website-sync
# id: publish
# with:
# repository: grafana/website
# branch: master
# host: github.com
# github_pat: '${{ secrets.GH_BOT_ACCESS_TOKEN }}'
# source_folder: docs/sources
# target_folder: content/docs/amixr/v0.0.39
# - shell: bash
# run: |
# test -n "${{ steps.publish.outputs.commit_hash }}"
# test -n "${{ steps.publish.outputs.working_directory }}"

View file

@ -64,7 +64,7 @@ python manage.py createsuperuser
3. Launch the backend:
```bash
# Http server:
python manage.py runserver
python manage.py runserver 8080
# Worker for background tasks (run it in the parallel terminal, don't forget to export .env there)
python manage.py start_celery
@ -226,6 +226,7 @@ pytest --ds=settings.dev
- Set Settings to settings/dev.py
5. Create a new Django Server run configuration to Run/Debug the engine
- Use a plugin such as EnvFile to load the .env file
- Change port from 8000 to 8080
## Update drone build
The .drone.yml build file must be signed when changes are made to it. Follow these steps:

View file

@ -15,7 +15,7 @@ We prepared multiple environments: [production](https://grafana.com/docs/grafana
1. Download docker-compose.yaml:
```bash
curl https://github.com/grafana/oncall/blob/dev/docker-compose.yml -o docker-compose.yaml
curl -fsSL https://raw.githubusercontent.com/grafana/oncall/dev/docker-compose.yml -o docker-compose.yaml
```
2. Set variables:

View file

@ -1,8 +1,6 @@
services:
engine:
# TODO: change to the public image once it's public
# image: ...
build: engine
image: grafana/oncall
ports:
- 8080:8080
command: >
@ -36,7 +34,7 @@ services:
celery:
# TODO: change to the public image once it's public
build: engine
image: grafana/oncall
command: sh -c "./celery_with_exporter.sh"
environment:
BASE_URL: $DOMAIN
@ -70,7 +68,7 @@ services:
condition: service_started
oncall_db_migration:
build: engine
image: grafana/oncall
command: python manage.py migrate --noinput
environment:
BASE_URL: $DOMAIN

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

85
engine/settings/helm.py Normal file
View file

@ -0,0 +1,85 @@
import os
import sys
# Workaround to use pymysql instead of mysqlclient
import pymysql
from .prod_without_db import * # noqa
pymysql.install_as_MySQLdb()
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.environ.get("MYSQL_DB_NAME"),
"USER": os.environ.get("MYSQL_USER"),
"PASSWORD": os.environ["MYSQL_PASSWORD"],
"HOST": os.environ.get("MYSQL_HOST"),
"PORT": os.environ.get("MYSQL_PORT"),
"OPTIONS": {
"charset": "utf8mb4",
"connect_timeout": 1,
},
},
}
RABBITMQ_USERNAME = os.environ.get("RABBITMQ_USERNAME")
RABBITMQ_PASSWORD = os.environ.get("RABBITMQ_PASSWORD")
RABBITMQ_HOST = os.environ.get("RABBITMQ_HOST")
RABBITMQ_PORT = os.environ.get("RABBITMQ_PORT")
CELERY_BROKER_URL = f"amqp://{RABBITMQ_USERNAME}:{RABBITMQ_PASSWORD}@{RABBITMQ_HOST}:{RABBITMQ_PORT}"
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD")
REDIS_HOST = os.environ.get("REDIS_HOST")
REDIS_PORT = os.environ.get("REDIS_PORT", "6379")
REDIS_URI = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}"
CACHES = {
"default": {
"BACKEND": "redis_cache.RedisCache",
"LOCATION": [
REDIS_URI,
],
"OPTIONS": {
"DB": 1,
"PARSER_CLASS": "redis.connection.HiredisParser",
"CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool",
"CONNECTION_POOL_CLASS_KWARGS": {
"max_connections": 50,
"timeout": 20,
},
"MAX_CONNECTIONS": 1000,
"PICKLE_VERSION": -1,
},
},
}
APPEND_SLASH = False
SECURE_SSL_REDIRECT = False
TESTING = "pytest" in sys.modules or "unittest" in sys.modules
if TESTING:
TELEGRAM_TOKEN = "0000000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXX"
TWILIO_AUTH_TOKEN = "twilio_auth_token"
# TODO: OSS: Add these setting to oss settings file. Add Version there too.
OSS_INSTALLATION_FEATURES_ENABLED = True
INSTALLED_APPS += ["apps.oss_installation"] # noqa
CELERY_BEAT_SCHEDULE["send_usage_stats"] = { # noqa
"task": "apps.oss_installation.tasks.send_usage_stats_report",
"schedule": crontab(hour=0, minute=randrange(0, 59)), # Send stats report at a random minute past midnight # noqa
"args": (),
} # noqa
CELERY_BEAT_SCHEDULE["send_cloud_heartbeat"] = { # noqa
"task": "apps.oss_installation.tasks.send_cloud_heartbeat",
"schedule": crontab(minute="*/3"), # noqa
"args": (),
} # noqa
SEND_ANONYMOUS_USAGE_STATS = True

View file

@ -1 +0,0 @@
../CHANGELOG.md

View file

@ -0,0 +1,5 @@
# Change Log
## 0.0.71 (2022-06-06)
- Initial Release

23
helm/oncall/.helmignore Normal file
View file

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

43
helm/oncall/Chart.yaml Normal file
View file

@ -0,0 +1,43 @@
apiVersion: v2
name: oncall
description: Developer-friendly incident response with brilliant Slack integration
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v1.0.0"
dependencies:
- name: cert-manager
version: v1.8.0
repository: https://charts.jetstack.io
condition: cert-manager.enabled
- name: mariadb
version: 11.0.10
repository: https://charts.bitnami.com/bitnami
condition: mariadb.enabled
- name: rabbitmq
version: 10.1.1
repository: https://charts.bitnami.com/bitnami
condition: rabbitmq.enabled
- name: redis
version: 16.10.1
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
- name: grafana
version: 6.29.6
repository: https://grafana.github.io/helm-charts
condition: grafana.enabled
- name: ingress-nginx
version: 4.1.4
repository: https://kubernetes.github.io/ingress-nginx
condition: ingress-nginx.enabled

120
helm/oncall/README.md Normal file
View file

@ -0,0 +1,120 @@
# Grafana OnCall Helm Chart
This Grafana OnCall Chart is the best way to operate Grafana OnCall on Kubernetes.
It will deploy Grafana OnCall engine and celery workers, along with RabbitMQ cluster, Redis Cluster, and MySQL 5.7 database.
It will also deploy cert manager and nginx ingress controller, as Grafana OnCall backend might need to be externally available
to receive alerts from other monitoring systems. Grafana OnCall engine acts as a backend and can be connected to the Grafana frontend plugin named Grafana OnCall.
Architecture diagram can be found [here](https://raw.githubusercontent.com/grafana/oncall/dev/docs/img/architecture_diagram.png)
> Default helm chart configuration is not intended for production. The helm chart includes all the services into a single release,
> which is not recommended for production usage. It is recommended to run stateful services such as MySQL and RabbitMQ
> separately from this release or use managed PaaS solutions. It will significantly reduce the overhead of managing them
Cluster requirements:
* ensure you can run x86-64/amd64 workloads. arm64 architecture is currently not supported
## Install
### Installing the helm chart
```bash
helm install \
--wait \
--set base_url=example.com \
--set grafana."grafana\.ini".server.domain=example.com \
oncall \
.
```
Follow the `helm install` output to finish setting up Grafana OnCall backend and Grafana OnCall frontend plugin
## Configuration
You can edit values.yml to make changes to the helm chart configuration and re-deploy the release with the following command:
```bash
helm upgrade \
--install \
--wait \
--set base_url=example.com \
--set grafana."grafana\.ini".server.domain=example.com \
oncall \
.
```
### Set up external access
Grafana OnCall can be connected to the external monitoring systems or grafana deployed to the other cluster.
Nginx Ingress Controller and Cert Manager charts are included in the helm chart with the default configuration.
If you set the DNS A Record pointing to the external IP address of the installation with the Hostname matching base_url parameter, https will be automatically set up. If grafana is enabled in the chart values, it will also be available on https://<base_url>/grafana/. See the details in `helm install` output.
To use a different ingress controller or tls certificate management system, set the following values to false and edit ingress settings
```
nginx-ingress:
enabled: false
cert-manager:
enabled: false
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/issuer: "letsencrypt-prod"
```
### Connect external MySQL
It is recommended to use the managed MySQL 5.7 database provided by your cloud provider
Make sure to create the database with the following parameters before installing this chart
```
CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
To use an external MySQL instance set mysql.enabled to `false` and configure the `externalMysql` parameters.
```
mariadb:
enabled: true
# Make sure to create the database with the following parameters:
# CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
externalMysql:
host:
port:
db_name:
user:
password:
```
### Connect external RabbitMQ
Option 1. Install RabbitMQ separately into the cluster using the [official documentation](https://www.rabbitmq.com/kubernetes/operator/operator-overview.html)
Option 2. Use managed solution such as [CloudAMPQ](https://www.cloudamqp.com/)
To use an external RabbitMQ instance set rabbitmq.enabled to `false` and configure the `externalRabbitmq` parameters.
```
rabbitmq:
enabled: false # Disable the RabbitMQ dependency from the release
externalRabbitmq:
host:
port:
user:
password:
```
## Uninstall
### Uninstalling the helm chart
```bash
helm delete oncall
```
### Clean up PVC's
```bash
kubectl delete pvc data-oncall-mariadb-0 data-oncall-rabbitmq-0 \
redis-data-oncall-redis-master-0 redis-data-oncall-redis-replicas-0 \
redis-data-oncall-redis-replicas-1 redis-data-oncall-redis-replicas-2
```
### Clean up secrets
```bash
kubectl delete secrets certificate-tls oncall-cert-manager-webhook-ca oncall-ingress-nginx-admission
```

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,54 @@
=================================================================
📞 Grafana OnCall Notes
=================================================================
👋 Your Grafana OnCall instance has been successfully deployed
TODO: Add disclaimer about stateful services, ingress controller, certificates
{{- if not .Values.migrate.enabled }}
🤖 To migrate the database run these commands:
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "oncall.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=engine" -o jsonpath="{.items[0].metadata.name}")
kubectl exec -it $POD_NAME -c wait-for-db -- bash -c "python manage.py migrate;"
{{- end }}
❗ Set up a DNS record for your domain (use A Record and "@" to point a root domain to the IP address)
Get the external IP address by running the following commands and point {{ .Values.base_url }} to it:
kubectl get ingress {{ include "oncall.fullname" . }} -o jsonpath="{.status.loadBalancer.ingress[0].ip}"
Wait until the dns record got propagated.
NOTE: Check with the following command: nslookup {{ .Values.base_url }}
Try reaching https://{{ .Values.base_url }}/ready/ from the browser, make sure it is not cached locally
{{- if .Values.grafana.enabled }}
🦎 Grafana was installed as a part of this helm release. Open https://{{ .Values.base_url }}/grafana/plugins/grafana-oncall-app
The User is {{ .Values.grafana.adminUser }}
Get password by running this command:
kubectl get secret --namespace {{ .Release.Namespace }} {{ template "oncall.grafana.fullname" . }} -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
{{- else }}
🦎 Grafana was NOT installed as a part of this helm release. Open external Grafana, go to "Configuration" - "Plugins" and find Grafana OnCall plugin
NOTE: Make sure your external Grafana is available by the network for the containers installed by this release.
{{- end }}
🔗 Connect Grafana OnCall Plugin to Grafana OnCall backend:
Issue the one-time token to connect Grafana OnCall backend and Grafana OnCall plugin by running these commands:
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "oncall.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=engine" -o jsonpath="{.items[0].metadata.name}")
kubectl exec -it $POD_NAME -- bash -c "python manage.py issue_invite_for_the_frontend --override"
Fill the Grafana OnCall Backend URL:
http://{{ include "oncall.engine.fullname" . }}:8080
Fill the Grafana URL:
{{ if .Values.grafana.enabled }}http://{{ include "oncall.grafana.fullname" . }}{{ else }}https://<external-grafana>{{- end }}
🎉🎉🎉 Done! 🎉🎉🎉

View file

@ -0,0 +1,165 @@
{{- define "snippet.oncall.env" -}}
- name: BASE_URL
value: https://{{ .Values.base_url }}
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ template "oncall.fullname" . }}
key: SECRET_KEY
- name: MIRAGE_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ template "oncall.fullname" . }}
key: MIRAGE_SECRET_KEY
- name: MIRAGE_CIPHER_IV
value: "1234567890abcdef"
- name: DJANGO_SETTINGS_MODULE
value: "settings.helm"
- name: AMIXR_DJANGO_ADMIN_PATH
value: "admin"
- name: OSS
value: "True"
{{- end }}
{{- define "snippet.celery.env" -}}
- name: CELERY_WORKER_QUEUE
value: "default,critical,long,slack,telegram,webhook,celery"
- name: CELERY_WORKER_CONCURRENCY
value: "1"
- name: CELERY_WORKER_MAX_TASKS_PER_CHILD
value: "100"
- name: CELERY_WORKER_SHUTDOWN_INTERVAL
value: "65m"
- name: CELERY_WORKER_BEAT_ENABLED
value: "True"
{{- end }}
{{- define "snippet.mysql.env" -}}
- name: MYSQL_HOST
value: {{ include "snippet.mysql.host" . }}
- name: MYSQL_PORT
value: {{ include "snippet.mysql.port" . }}
- name: MYSQL_DB_NAME
value: {{ include "snippet.mysql.db" . }}
- name: MYSQL_USER
value: {{ include "snippet.mysql.user" . }}
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "snippet.mysql.password.secret.name" . }}
key: mariadb-root-password
{{- end }}
{{- define "snippet.mysql.password.secret.name" -}}
{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.password -}}
{{ include "oncall.fullname" . }}-mysql-external
{{- else -}}
{{ include "oncall.mariadb.fullname" . }}
{{- end -}}
{{- end -}}
{{- define "snippet.mysql.host" -}}
{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.host -}}
{{- required "externalMysql.host is required if not mariadb.enabled" .Values.externalMysql.host | quote }}
{{- else -}}
{{ include "oncall.mariadb.fullname" . }}
{{- end -}}
{{- end -}}
{{- define "snippet.mysql.port" -}}
{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.port -}}
{{- required "externalMysql.port is required if not mariadb.enabled" .Values.externalMysql.port | quote }}
{{- else -}}
"3306"
{{- end -}}
{{- end -}}
{{- define "snippet.mysql.db" -}}
{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.db -}}
{{- required "externalMysql.db is required if not mariadb.enabled" .Values.externalMysql.db | quote}}
{{- else -}}
"oncall"
{{- end -}}
{{- end -}}
{{- define "snippet.mysql.user" -}}
{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.user -}}
{{- .Values.externalMysql.user | quote}}
{{- else -}}
"root"
{{- end -}}
{{- end -}}
{{- define "snippet.rabbitmq.env" -}}
- name: RABBITMQ_USERNAME
value: {{ include "snippet.rabbitmq.user" . }}
- name: RABBITMQ_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "snippet.rabbitmq.password.secret.name" . }}
key: rabbitmq-password
- name: RABBITMQ_HOST
value: {{ include "snippet.rabbitmq.host" . }}
- name: RABBITMQ_PORT
value: {{ include "snippet.rabbitmq.port" . }}
{{- end }}
{{- define "snippet.rabbitmq.user" -}}
{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.user -}}
{{- required "externalRabbitmq.user is required if not rabbitmq.enabled" .Values.externalRabbitmq.user | quote }}
{{- else -}}
"user"
{{- end -}}
{{- end -}}
{{- define "snippet.rabbitmq.host" -}}
{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.host -}}
{{- required "externalRabbitmq.host is required if not rabbitmq.enabled" .Values.externalRabbitmq.host | quote }}
{{- else -}}
{{ include "oncall.rabbitmq.fullname" . }}
{{- end -}}
{{- end -}}
{{- define "snippet.rabbitmq.port" -}}
{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.port -}}
{{- required "externalRabbitmq.port is required if not rabbitmq.enabled" .Values.externalRabbitmq.port | quote }}
{{- else -}}
"5672"
{{- end -}}
{{- end -}}
{{- define "snippet.rabbitmq.password.secret.name" -}}
{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.password -}}
{{ include "oncall.fullname" . }}-rabbitmq-external
{{- else -}}
{{ include "oncall.rabbitmq.fullname" . }}
{{- end -}}
{{- end -}}
{{- define "snippet.redis.host" -}}
{{- if and (not .Values.redis.enabled) .Values.externalRedis.host -}}
{{- required "externalRedis.host is required if not redis.enabled" .Values.externalRedis.host | quote }}
{{- else -}}
{{ include "oncall.redis.fullname" . }}-master
{{- end -}}
{{- end -}}
{{- define "snippet.redis.password.secret.name" -}}
{{- if and (not .Values.redis.enabled) .Values.externalRedis.password -}}
{{ include "oncall.fullname" . }}-redis-external
{{- else -}}
{{ include "oncall.redis.fullname" . }}
{{- end -}}
{{- end -}}
{{- define "snippet.redis.env" -}}
- name: REDIS_HOST
value: {{ include "snippet.redis.host" . }}
- name: REDIS_PORT
value: "6379"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ template "snippet.redis.password.secret.name" . }}
key: redis-password
{{- end }}

View file

@ -0,0 +1,96 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "oncall.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "oncall.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 }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "oncall.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "oncall.labels" -}}
helm.sh/chart: {{ include "oncall.chart" . }}
{{ include "oncall.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "oncall.selectorLabels" -}}
app.kubernetes.io/name: {{ include "oncall.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "oncall.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "oncall.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/* Generate the fullname of mariadb subchart */}}
{{- define "oncall.mariadb.fullname" -}}
{{- printf "%s-%s" .Release.Name "mariadb" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "oncall.grafana.fullname" -}}
{{- printf "%s-%s" .Release.Name "grafana" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/* Generate the fullname of rabbitmq subchart */}}
{{- define "oncall.rabbitmq.fullname" -}}
{{- printf "%s-%s" .Release.Name "rabbitmq" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/* Generate the fullname of redis subchart */}}
{{- define "oncall.redis.fullname" -}}
{{- printf "%s-%s" .Release.Name "redis" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "oncall.mariadb.wait-for-db" }}
- name: wait-for-db
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ['sh', '-c', "until (python manage.py migrate --check); do echo Waiting for database migrations; sleep 2; done"]
env:
{{- include "snippet.oncall.env" . | nindent 12 }}
{{- include "snippet.mysql.env" . | nindent 12 }}
{{- include "snippet.rabbitmq.env" . | nindent 12 }}
{{- include "snippet.redis.env" . | nindent 12 }}
{{- if .Values.env }}
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,60 @@
{{- define "template.oncall.celery.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "oncall.celery.fullname" . }}
labels:
{{- include "oncall.celery.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.celery.replicaCount }}
selector:
matchLabels:
{{- include "oncall.celery.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
random-annotation: {{ randAlphaNum 10 | lower }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "oncall.celery.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "oncall.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
initContainers:
{{- include "oncall.mariadb.wait-for-db" . | indent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["./celery_with_exporter.sh"]
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
{{- include "snippet.celery.env" . | nindent 12 }}
{{- include "snippet.oncall.env" . | nindent 12 }}
{{- include "snippet.mysql.env" . | nindent 12 }}
{{- include "snippet.rabbitmq.env" . | nindent 12 }}
{{- include "snippet.redis.env" . | nindent 12 }}
{{- if .Values.env }}
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
livenessProbe:
exec:
command: [
"bash",
"-c",
"celery inspect ping -A engine -d celery@$HOSTNAME"
]
initialDelaySeconds: 30
periodSeconds: 300
timeoutSeconds: 10
resources:
{{- toYaml .Values.celery.resources | nindent 12 }}
{{- end}}

View file

@ -0,0 +1,26 @@
{{/*
Maximum of 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "oncall.celery.name" -}}
{{ include "oncall.name" . | trunc 55 }}-celery
{{- end }}
{{- define "oncall.celery.fullname" -}}
{{ include "oncall.fullname" . | trunc 55 }}-celery
{{- end }}
{{/*
Engine common labels
*/}}
{{- define "oncall.celery.labels" -}}
{{ include "oncall.labels" . }}
app.kubernetes.io/component: celery
{{- end }}
{{/*
Engine selector labels
*/}}
{{- define "oncall.celery.selectorLabels" -}}
{{ include "oncall.selectorLabels" . }}
app.kubernetes.io/component: celery
{{- end }}

View file

@ -0,0 +1 @@
{{ include "template.oncall.celery.deployment" . }}

View file

@ -0,0 +1,22 @@
{{- if (index .Values "cert-manager") }}
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-prod
annotations:
"helm.sh/hook": post-install,post-upgrade
spec:
acme:
# The ACME server URL
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: no-reply@{{ .Values.base_url }}
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-prod
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
class: nginx
{{- end }}

View file

@ -0,0 +1,26 @@
{{/*
Maximum of 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "oncall.engine.name" -}}
{{ include "oncall.name" . | trunc 55 }}-engine
{{- end }}
{{- define "oncall.engine.fullname" -}}
{{ include "oncall.fullname" . | trunc 55 }}-engine
{{- end }}
{{/*
Engine common labels
*/}}
{{- define "oncall.engine.labels" -}}
{{ include "oncall.labels" . }}
app.kubernetes.io/component: engine
{{- end }}
{{/*
Engien selector labels
*/}}
{{- define "oncall.engine.selectorLabels" -}}
{{ include "oncall.selectorLabels" . }}
app.kubernetes.io/component: engine
{{- end }}

View file

@ -0,0 +1,73 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "oncall.engine.fullname" . }}
labels:
{{- include "oncall.engine.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.engine.replicaCount }}
selector:
matchLabels:
{{- include "oncall.engine.selectorLabels" . | nindent 6 }}
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
random-annotation: {{ randAlphaNum 10 | lower }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "oncall.engine.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "oncall.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
initContainers:
{{- include "oncall.mariadb.wait-for-db" . | indent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
{{- include "snippet.oncall.env" . | nindent 12 }}
{{- include "snippet.mysql.env" . | nindent 12 }}
{{- include "snippet.rabbitmq.env" . | nindent 12 }}
{{- include "snippet.redis.env" . | nindent 12 }}
{{- if .Values.env }}
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
livenessProbe:
httpGet:
path: /health/
port: http
periodSeconds: 60
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /ready/
port: http
periodSeconds: 60
timeoutSeconds: 3
startupProbe:
httpGet:
path: /startupprobe/
port: http
periodSeconds: 60
timeoutSeconds: 3
resources:
{{- toYaml .Values.engine.resources | nindent 12 }}

View file

@ -0,0 +1,54 @@
{{- if .Values.migrate.enabled -}}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ printf "%s-migrate-%s" (include "oncall.engine.fullname" .) (now | date "2006-01-02-15-04-05") }}
labels:
{{- include "oncall.engine.labels" . | nindent 4 }}
spec:
ttlSecondsAfterFinished: 20
template:
metadata:
name: {{ printf "%s-migrate-%s" (include "oncall.engine.fullname" .) (now | date "2006-01-02-15-04-05") }}
{{- with .Values.podAnnotations }}
annotations:
random-annotation: {{ randAlphaNum 10 | lower }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "oncall.engine.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "oncall.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}-migrate
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/sh
- -c
- |
until (nc -vz $MYSQL_HOST $MYSQL_PORT);
do
echo "waiting for MySQL"; sleep 1;
done
python manage.py migrate
env:
{{- include "snippet.oncall.env" . | nindent 12 }}
{{- include "snippet.mysql.env" . | nindent 12 }}
{{- include "snippet.rabbitmq.env" . | nindent 12 }}
{{- include "snippet.redis.env" . | nindent 12 }}
{{- if .Values.env }}
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.engine.resources | nindent 12 }}
{{- end }}

View file

@ -0,0 +1,17 @@
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "oncall.engine.fullname" . }}-external
labels:
{{- include "oncall.engine.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "oncall.engine.selectorLabels" . | nindent 4 }}
{{- end }}

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "oncall.engine.fullname" . }}
labels:
{{- include "oncall.engine.labels" . | nindent 4 }}
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: http
protocol: TCP
name: http
selector:
{{- include "oncall.engine.selectorLabels" . | nindent 4 }}

View file

@ -0,0 +1,48 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "oncall.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "oncall.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
tls:
- hosts:
- {{ .Values.base_url | quote }}
secretName: certificate-tls
rules:
- host: {{ .Values.base_url | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "oncall.engine.fullname" . }}
port:
number: 8080
- path: /grafana
pathType: Prefix
backend:
service:
name: {{ include "oncall.grafana.fullname" . }}
port:
number: 80
{{- end }}

View file

@ -0,0 +1,43 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "oncall.fullname" . }}
labels:
{{- include "oncall.labels" . | nindent 4 }}
type: Opaque
data:
SECRET_KEY: {{ randAlphaNum 40 | b64enc | quote }}
MIRAGE_SECRET_KEY: {{ randAlphaNum 40 | b64enc | quote }}
MIRAGE_CIPHER_IV: {{ randAlphaNum 40 | b64enc | quote }}
---
{{ if not .Values.mariadb.enabled -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "oncall.fullname" . }}-mysql-external
type: Opaque
data:
mariadb-root-password: {{ required "externalMysql.password is required if not mariadb.enabled" .Values.externalMysql.password | b64enc | quote }}
{{- end }}
---
{{ if not .Values.rabbitmq.enabled -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "oncall.fullname" . }}-rabbitmq-external
type: Opaque
data:
rabbitmq-password: {{ required "externalRabbitmq.password is required if not rabbitmq.enabled" .Values.externalRabbitmq.password | b64enc | quote }}
{{- end }}
---
{{ if not .Values.redis.enabled -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "oncall.fullname" . }}-redis-external
type: Opaque
data:
rabbitmq-password: {{ required "externalRedis.password is required if not redis.enabled" .Values.externalRedis.password | b64enc | quote }}
{{- end }}

View file

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "oncall.serviceAccountName" . }}
labels:
{{- include "oncall.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "oncall.fullname" . }}-test-connection"
labels:
{{- include "oncall.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "oncall.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

162
helm/oncall/values.yaml Normal file
View file

@ -0,0 +1,162 @@
# Values for configuring the deployment of Grafana OnCall
# Set the domain name Grafana OnCall will be installed on.
# If you want to install grafana as a part of this release make sure to configure grafana.grafana.ini.server.domain too
base_url: example.com
image:
# Grafana OnCall docker image repository
repository: grafana/oncall
tag:
pullPolicy: IfNotPresent
# Whether to create additional service for external connections
# ClusterIP service is always created
service:
enabled: false
type: LoadBalancer
port: 8080
# Engine pods configuration
engine:
replicaCount: 1
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# Celery workers pods configuration
celery:
replicaCount: 1
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# Whether to run django database migrations automatically
migrate:
enabled: true
# Additional env variables to add to deployments
env: []
# Enable ingress object for external access to the resources
ingress:
enabled: true
# className: ""
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/issuer: "letsencrypt-prod"
# Whether to install ingress controller
nginx-ingress:
enabled: true
# Install cert-manager as a part of the release
cert-manager:
enabled: true
# Instal CRD resources
installCRDs: true
webhook:
timeoutSeconds: 30
# cert-manager tries to use the already used port, changing to another one
# https://github.com/cert-manager/cert-manager/issues/3237
# https://cert-manager.io/docs/installation/compatibility/
securePort: 10260
# Fix self-checks https://github.com/jetstack/cert-manager/issues/4286
podDnsPolicy: None
podDnsConfig:
nameservers:
- 8.8.8.8
- 1.1.1.1
# MySQL is included into this release for the convenience.
# It is recommended to host it separately from this release
# Set mariadb.enabled = false and configure externalMysql
mariadb:
enabled: true
auth:
database: oncall
primary:
extraEnvVars:
- name: MARIADB_COLLATE
value: utf8mb4_unicode_ci
- name: MARIADB_CHARACTER_SET
value: utf8mb4
secondary:
extraEnvVars:
- name: MARIADB_COLLATE
value: utf8mb4_unicode_ci
- name: MARIADB_CHARACTER_SET
value: utf8mb4
# Make sure to create the database with the following parameters:
# CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
externalMysql:
host:
port:
db_name:
user:
password:
# RabbitMQ is included into this release for the convenience.
# It is recommended to host it separately from this release
# Set rabbitmq.enabled = false and configure externalRabbitmq
rabbitmq:
enabled: true
externalRabbitmq:
host:
port:
user:
password:
redis:
enabled: true
external_redis:
host:
password:
grafana:
enabled: true
grafana.ini:
server:
domain: example.com
root_url: "%(protocol)s://%(domain)s/grafana"
serve_from_sub_path: true
persistence:
enabled: true
plugins:
- grafana-oncall-app
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000