diff --git a/packages/apps/observability/Makefile b/packages/apps/observability/Makefile index a2c63064..054f7dc7 100644 --- a/packages/apps/observability/Makefile +++ b/packages/apps/observability/Makefile @@ -4,4 +4,6 @@ update: helm repo update grafana helm pull grafana/oncall --untar --untardir charts rm -rf charts/oncall/charts - hack/download-dashboards.sh + +dashboards-list: + find ../../../dashboards -name '*.json' | awk -F/ '{sub(".json", ""); printf "%s/%s\n", $$(NF-1), $$NF}' > dashboards.list diff --git a/packages/apps/observability/charts/oncall/.helmignore b/packages/apps/observability/charts/oncall/.helmignore new file mode 100644 index 00000000..fd6e5fd3 --- /dev/null +++ b/packages/apps/observability/charts/oncall/.helmignore @@ -0,0 +1,26 @@ +# 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/ + +# exclude helm unit tests +tests/ diff --git a/packages/apps/observability/charts/oncall/Chart.lock b/packages/apps/observability/charts/oncall/Chart.lock new file mode 100644 index 00000000..12ba8011 --- /dev/null +++ b/packages/apps/observability/charts/oncall/Chart.lock @@ -0,0 +1,27 @@ +dependencies: +- name: cert-manager + repository: https://charts.jetstack.io + version: v1.8.0 +- name: mariadb + repository: https://charts.bitnami.com/bitnami + version: 12.2.5 +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 11.9.10 +- name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 12.0.0 +- name: redis + repository: https://charts.bitnami.com/bitnami + version: 16.13.2 +- name: grafana + repository: https://grafana.github.io/helm-charts + version: 6.57.1 +- name: ingress-nginx + repository: https://kubernetes.github.io/ingress-nginx + version: 4.1.4 +- name: prometheus + repository: https://prometheus-community.github.io/helm-charts + version: 25.8.2 +digest: sha256:edc9fef449a694cd319135e37ac84f8247ac9ad0c48ac86099dae4e428beb7b7 +generated: "2024-01-12T10:31:38.450269584Z" diff --git a/packages/apps/observability/charts/oncall/Chart.yaml b/packages/apps/observability/charts/oncall/Chart.yaml new file mode 100644 index 00000000..e4e09a9a --- /dev/null +++ b/packages/apps/observability/charts/oncall/Chart.yaml @@ -0,0 +1,39 @@ +apiVersion: v2 +appVersion: v1.3.85 +dependencies: +- condition: cert-manager.enabled + name: cert-manager + repository: https://charts.jetstack.io + version: v1.8.0 +- condition: mariadb.enabled + name: mariadb + repository: https://charts.bitnami.com/bitnami + version: 12.2.5 +- condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 11.9.10 +- condition: rabbitmq.enabled + name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 12.0.0 +- condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 16.13.2 +- condition: grafana.enabled + name: grafana + repository: https://grafana.github.io/helm-charts + version: 6.57.1 +- condition: ingress-nginx.enabled + name: ingress-nginx + repository: https://kubernetes.github.io/ingress-nginx + version: 4.1.4 +- condition: prometheus.enabled + name: prometheus + repository: https://prometheus-community.github.io/helm-charts + version: 25.8.2 +description: Developer-friendly incident response with brilliant Slack integration +name: oncall +type: application +version: 1.3.85 diff --git a/packages/apps/observability/charts/oncall/README.md b/packages/apps/observability/charts/oncall/README.md new file mode 100644 index 00000000..993f0649 --- /dev/null +++ b/packages/apps/observability/charts/oncall/README.md @@ -0,0 +1,431 @@ +# 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) + +## Production usage + +**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. +Here are the instructions on how to set up your own [ingress](#set-up-external-access), [MySQL](#connect-external-mysql), +[RabbitMQ](#connect-external-rabbitmq), [Redis](#connect-external-redis) + +### Cluster requirements + +- ensure you can run x86-64/amd64 workloads. arm64 architecture is currently not supported +- kubernetes version 1.25+ is not supported, if cert-manager is enabled + +## Install + +### Prepare the repo + +```bash +# Add the repository +helm repo add grafana https://grafana.github.io/helm-charts +helm repo update +``` + +### Installing the helm chart + +```bash +# Install the chart +helm install \ + --wait \ + --set base_url=example.com \ + --set grafana."grafana\.ini".server.domain=example.com \ + release-oncall \ + grafana/oncall +``` + +Follow the `helm install` output to finish setting up Grafana OnCall backend and Grafana OnCall frontend plugin e.g. + +```bash +👋 Your Grafana OnCall instance has been successfully deployed + + ❗ 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 example.com to it: + + kubectl get ingress release-oncall -o jsonpath="{.status.loadBalancer.ingress[0].ip}" + + Wait until the dns record got propagated. + NOTE: Check with the following command: nslookup example.com + Try reaching https://example.com/ready/ from the browser, make sure it is not cached locally + + 🦎 Grafana was installed as a part of this helm release. Open https://example.com/grafana/plugins/grafana-oncall-app + The User is admin + Get password by running this command: + + kubectl get secret --namespace default release-oncall-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo + + 🔗 Connect Grafana OnCall Plugin to Grafana OnCall backend: + + Fill the Grafana OnCall Backend URL: + + http://release-oncall-engine:8080 + +🎉🎉🎉 Done! 🎉🎉🎉 +``` + +## 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 \ + release-oncall \ + grafana/oncall +``` + +### Passwords and external secrets + +As OnCall subcharts are Bitname charts, there is a common approach to secrets. Bundled charts allow specifying passwords +in values.yaml explicitly or as K8s secret value. OnCall chart refers either to secret created in sub-chart or +to specified external secret. +Similarly, if component chart is disabled, the password(s) can be supplied in `external` value +(e.g. externalMysql) explicitly or as K8s secret value. In the first case, the secret is created with the specified +value. In the second case the external secret is used. + +- If `.auth.existingSecret` is non-empty, then this secret is used. Secret keys are pre-defined by chart. +- If subchart supports password files and `.customPasswordFiles` dictionary is non-empty, then password files + are used. Dictionary keys are pre-defined per sub-chart. Password files are not supported by OnCall chart and should + not be used with bundled sub-charts. +- Passwords are specified via `auth` section values, e.g. `auth.password`. K8s secret is created. + - If `.auth.forcePassword` is `true`, then passwords MUST be specified. Otherwise, missing passwords + are generated. + +If external component is used instead of the bundled one: + +- If existingSecret within appropriate external component values is non-empty (e.g. `externalMysql.existingSecret`) then + it is used together with corresponding key names, e.g. `externalMysql.passwordKey`. +- Otherwise, corresponding password values are used, e.g. `externalMysql.password`. K8s secret is created by OnCall chart. + +Below is the summary for the dependent charts. + +MySQL/MariaDB: + +```yaml +database: + type: "mysql" # This is default +mariaDB: + enabled: true # Default + auth: + existingSecret: "" + forcePassword: false + # Secret name: `-mariadb` + rootPassword: "" # Secret key: mariadb-root-password + password: "" # Secret key: mariadb-password + replicationPassword: "" # Secret key: mariadb-replication-password +externalMysql: + password: "" + existingSecret: "" + passwordKey: "" +``` + +Postgres: + +```yaml +database: + type: postgresql +mariadb: + enabled: false # Must be set to false for Postgres +postgresql: + enabled: true # Must be set to true for bundled Postgres + auth: + existingSecret: "" + secretKeys: + adminPasswordKey: "" + userPasswordKey: "" # Not needed + replicationPasswordKey: "" # Not needed with disabled replication + # Secret name: `-postgresql` + postgresPassword: "" # password for admin user postgres. As non-admin user is not created, only this one is relevant. + password: "" # Not needed + replicationPassword: "" # Not needed with disabled replication +externalPostgresql: + user: "" + password: "" + existingSecret: "" + passwordKey: "" +``` + +Rabbitmq: + +```yaml +rabbitmq: + enabled: true + auth: + existingPasswordSecret: "" # Must contain `rabbitmq-password` key + existingErlangSecret: "" # Must contain `rabbitmq-erlang-cookie` key + # Secret name: `-rabbitmq` + password: "" + erlangCookie: "" +externalRabbitmq: + user: "" + password: "" + existingSecret: "" + passwordKey: "" + usernameKey: "" +``` + +Redis: + +```yaml +redis: + enabled: true + auth: + existingSecret: "" + existingSecretPasswordKey: "" + # Secret name: `-redis` + password: "" +externalRedis: + password: "" + existingSecret: "" + passwordKey: "" +``` + +### Running split ingestion and API services + +You can run a detached service for handling integrations by setting up the following variables: + +```yaml +detached_integrations: + enabled: true +detached_integrations_service: + enabled: true +``` + +This will run an integrations-only service listening by default in port 30003. + +### Set up Slack and Telegram + +You can set up Slack connection via following variables: + +```yaml +oncall: + slack: + enabled: true + commandName: oncall + clientId: ~ + clientSecret: ~ + signingSecret: ~ + existingSecret: "" + clientIdKey: "" + clientSecretKey: "" + signingSecretKey: "" + redirectHost: ~ +``` + +`oncall.slack.commandName` is used for changing default bot slash command, +`oncall`. In slack, it could be called via `/`. + +To set up Telegram token and webhook url use: + +```yaml +oncall: + telegram: + enabled: true + token: ~ + webhookUrl: ~ +``` + +To use Telegram long polling instead of webhook use: + +```yaml +telegramPolling: + enabled: true +``` + +### 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:///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 + +```yaml +ingress-nginx: + enabled: false + +cert-manager: + enabled: false + +ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/issuer: "letsencrypt-prod" +``` + +### Use PostgreSQL instead of MySQL + +It is possible to use PostgreSQL instead of MySQL. To do so, set mariadb.enabled to `false`, +postgresql.enabled to `true` and database.type to `postgresql`. + +```yaml +mariadb: + enabled: false + +postgresql: + enabled: true + +database: + type: postgresql +``` + +### 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 + +```sql +CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +To use an external MySQL instance set mariadb.enabled to `false` and configure the `externalMysql` parameters. + +```yaml +mariadb: + enabled: false + +# 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: + existingSecret: "" + usernameKey: username + passwordKey: password +``` + +### Connect external PostgreSQL + +To use an external PostgreSQL instance set mariadb.enabled to `false`, +postgresql.enabled to `false`, database.type to `postgresql` and configure +the `externalPostgresql` parameters. + +```yaml +mariadb: + enabled: false + +postgresql: + enabled: false + +database: + type: postgresql + +# Make sure to create the database with the following parameters: +# CREATE DATABASE oncall WITH ENCODING UTF8; +externalPostgresql: + host: + port: + db_name: + user: + password: + existingSecret: "" + passwordKey: 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. + +```yaml +rabbitmq: + enabled: false # Disable the RabbitMQ dependency from the release + +externalRabbitmq: + host: + port: + user: + password: + protocol: + vhost: + existingSecret: "" + passwordKey: password + usernameKey: username +``` + +### Connect external Redis + +To use an external Redis instance set redis.enabled to `false` and configure the `externalRedis` parameters. + +```yaml +redis: + enabled: false # Disable the Redis dependency from the release + +externalRedis: + host: + password: + existingSecret: "" + passwordKey: password +``` + +## Update + +```bash +# Add & upgrade the repository +helm repo add grafana https://grafana.github.io/helm-charts +helm repo update + +# Re-deploy +helm upgrade \ + --install \ + --wait \ + --set base_url=example.com \ + --set grafana."grafana\.ini".server.domain=example.com \ + release-oncall \ + grafana/oncall +``` + +After re-deploying, please also update the Grafana OnCall plugin on the plugin version page. +See [Grafana docs](https://grafana.com/docs/grafana/latest/administration/plugin-management/#update-a-plugin) for +more info on updating Grafana plugins. + +## Uninstall + +### Uninstalling the helm chart + +```bash +helm delete release-oncall +``` + +### Clean up PVC's + +```bash +kubectl delete pvc data-release-oncall-mariadb-0 data-release-oncall-rabbitmq-0 \ +redis-data-release-oncall-redis-master-0 redis-data-release-oncall-redis-replicas-0 \ +redis-data-release-oncall-redis-replicas-1 redis-data-release-oncall-redis-replicas-2 +``` + +### Clean up secrets + +```bash +kubectl delete secrets certificate-tls release-oncall-cert-manager-webhook-ca release-oncall-ingress-nginx-admission +``` + +## Troubleshooting + +### Issues during initial configuration + +In the event that you run into issues during initial configuration, it is possible that mismatching versions between +your OnCall backend and UI is the culprit. Ensure that the versions match, and if not, +consider updating your `helm` deployment. diff --git a/packages/apps/observability/charts/oncall/templates/NOTES.txt b/packages/apps/observability/charts/oncall/templates/NOTES.txt new file mode 100644 index 00000000..4eee164c --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/NOTES.txt @@ -0,0 +1,41 @@ +================================================================= +📞 Grafana OnCall Notes +================================================================= + +👋 Your Grafana OnCall instance has been successfully deployed + +{{- 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: + + Fill the Grafana OnCall Backend URL: + + http://{{ include "oncall.engine.fullname" . }}:8080 + +🎉🎉🎉 Done! 🎉🎉🎉 diff --git a/packages/apps/observability/charts/oncall/templates/_env.tpl b/packages/apps/observability/charts/oncall/templates/_env.tpl new file mode 100644 index 00000000..56dff3e5 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/_env.tpl @@ -0,0 +1,656 @@ +{{- define "snippet.oncall.env" -}} +- name: BASE_URL + value: {{ .Values.base_url_protocol }}://{{ .Values.base_url }} +- name: SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "snippet.oncall.secret.name" . }} + key: {{ include "snippet.oncall.secret.secretKey" . | quote }} +- name: MIRAGE_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "snippet.oncall.secret.name" . }} + key: {{ include "snippet.oncall.secret.mirageSecretKey" . | quote }} +- name: MIRAGE_CIPHER_IV + value: {{ .Values.oncall.mirageCipherIV | default "1234567890abcdef" | quote }} +- name: DJANGO_SETTINGS_MODULE + value: "settings.helm" +- name: AMIXR_DJANGO_ADMIN_PATH + value: "admin" +- name: OSS + value: "True" +- name: DETACHED_INTEGRATIONS_SERVER + value: {{ .Values.detached_integrations.enabled | toString | title | quote }} +{{- include "snippet.oncall.uwsgi" . }} +- name: BROKER_TYPE + value: {{ .Values.broker.type | default "rabbitmq" }} +- name: GRAFANA_API_URL + value: {{ include "snippet.grafana.url" . | quote }} +{{- end }} + +{{- define "snippet.oncall.secret.name" -}} +{{ if .Values.oncall.secrets.existingSecret -}} + {{ .Values.oncall.secrets.existingSecret }} +{{- else -}} + {{ include "oncall.fullname" . }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.secret.secretKey" -}} +{{ if .Values.oncall.secrets.existingSecret -}} + {{ required "oncall.secrets.secretKey is required if oncall.secret.existingSecret is not empty" .Values.oncall.secrets.secretKey }} +{{- else -}} + SECRET_KEY +{{- end }} +{{- end }} + +{{- define "snippet.oncall.secret.mirageSecretKey" -}} +{{ if .Values.oncall.secrets.existingSecret -}} + {{ required "oncall.secrets.mirageSecretKey is required if oncall.secret.existingSecret is not empty" .Values.oncall.secrets.mirageSecretKey }} +{{- else -}} + MIRAGE_SECRET_KEY +{{- end }} +{{- end }} + +{{- define "snippet.oncall.uwsgi" -}} +{{- if .Values.uwsgi }} + {{- range $key, $value := .Values.uwsgi }} +- name: UWSGI_{{ $key | upper | replace "-" "_" }} + value: {{ $value | quote }} + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.slack.env" -}} +- name: FEATURE_SLACK_INTEGRATION_ENABLED + value: {{ .Values.oncall.slack.enabled | toString | title | quote }} +{{- if .Values.oncall.slack.enabled }} +- name: SLACK_SLASH_COMMAND_NAME + value: "/{{ .Values.oncall.slack.commandName | default "oncall" }}" +{{- if .Values.oncall.slack.existingSecret }} +- name: SLACK_CLIENT_OAUTH_ID + valueFrom: + secretKeyRef: + name: {{ .Values.oncall.slack.existingSecret }} + key: {{ required "oncall.slack.clientIdKey is required if oncall.slack.existingSecret is not empty" .Values.oncall.slack.clientIdKey | quote }} +- name: SLACK_CLIENT_OAUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.oncall.slack.existingSecret }} + key: {{ required "oncall.slack.clientSecretKey is required if oncall.slack.existingSecret is not empty" .Values.oncall.slack.clientSecretKey | quote }} +- name: SLACK_SIGNING_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.oncall.slack.existingSecret }} + key: {{ required "oncall.slack.signingSecretKey is required if oncall.slack.existingSecret is not empty" .Values.oncall.slack.signingSecretKey | quote }} +{{- else }} +- name: SLACK_CLIENT_OAUTH_ID + value: {{ .Values.oncall.slack.clientId | default "" | quote }} +- name: SLACK_CLIENT_OAUTH_SECRET + value: {{ .Values.oncall.slack.clientSecret | default "" | quote }} +- name: SLACK_SIGNING_SECRET + value: {{ .Values.oncall.slack.signingSecret | default "" | quote }} +{{- end }} +- name: SLACK_INSTALL_RETURN_REDIRECT_HOST + value: {{ .Values.oncall.slack.redirectHost | default (printf "https://%s" .Values.base_url) | quote }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.telegram.env" -}} +{{- if .Values.telegramPolling.enabled -}} +{{- $_ := set .Values.oncall.telegram "enabled" true -}} +{{- end -}} +- name: FEATURE_TELEGRAM_INTEGRATION_ENABLED + value: {{ .Values.oncall.telegram.enabled | toString | title | quote }} +{{- if .Values.oncall.telegram.enabled }} +{{- if .Values.telegramPolling.enabled }} +- name: FEATURE_TELEGRAM_LONG_POLLING_ENABLED + value: {{ .Values.telegramPolling.enabled | toString | title | quote }} +{{- end }} +- name: TELEGRAM_WEBHOOK_HOST + value: {{ .Values.oncall.telegram.webhookUrl | default (printf "https://%s" .Values.base_url) | quote }} +{{- if .Values.oncall.telegram.existingSecret }} +- name: TELEGRAM_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.oncall.telegram.existingSecret }} + key: {{ required "oncall.telegram.tokenKey is required if oncall.telegram.existingSecret is not empty" .Values.oncall.telegram.tokenKey | quote }} +{{- else }} +- name: TELEGRAM_TOKEN + value: {{ .Values.oncall.telegram.token | default "" | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.twilio.env" }} +{{- with .Values.oncall.twilio }} +{{- if .existingSecret }} +- name: TWILIO_ACCOUNT_SID + valueFrom: + secretKeyRef: + name: {{ .existingSecret }} + key: {{ required "oncall.twilio.accountSid is required if oncall.twilio.existingSecret is not empty" .accountSid | quote }} +{{- if .authTokenKey }} +- name: TWILIO_AUTH_TOKEN + valueFrom: + secretKeyRef: + name: {{ .existingSecret }} + key: {{ required "oncall.twilio.authTokenKey is required if oncall.twilio.existingSecret is not empty" .authTokenKey | quote }} +{{- end }} +- name: TWILIO_NUMBER + valueFrom: + secretKeyRef: + name: {{ .existingSecret }} + key: {{ required "oncall.twilio.phoneNumberKey is required if oncall.twilio.existingSecret is not empty" .phoneNumberKey | quote }} +- name: TWILIO_VERIFY_SERVICE_SID + valueFrom: + secretKeyRef: + name: {{ .existingSecret }} + key: {{ required "oncall.twilio.verifySidKey is required if oncall.twilio.existingSecret is not empty" .verifySidKey | quote }} +{{- if and .apiKeySidKey .apiKeySecretKey }} +- name: TWILIO_API_KEY_SID + valueFrom: + secretKeyRef: + name: {{ .existingSecret }} + key: {{ required "oncall.twilio.apiKeySidKey is required if oncall.twilio.existingSecret is not empty" .apiKeySidKey | quote }} +- name: TWILIO_API_KEY_SECRET + valueFrom: + secretKeyRef: + name: {{ .existingSecret }} + key: {{ required "oncall.twilio.apiKeySecretKey is required if oncall.twilio.existingSecret is not empty" .apiKeySecretKey | quote }} +{{- end }} +{{- else }} +{{- if .accountSid }} +- name: TWILIO_ACCOUNT_SID + value: {{ .accountSid | quote }} +{{- end }} +{{- if .authToken }} +- name: TWILIO_AUTH_TOKEN + value: {{ .authToken | quote }} +{{- end }} +{{- if .phoneNumber }} +- name: TWILIO_NUMBER + value: {{ .phoneNumber | quote }} +{{- end }} +{{- if .verifySid }} +- name: TWILIO_VERIFY_SERVICE_SID + value: {{ .verifySid | quote }} +{{- end }} +{{- if .apiKeySid }} +- name: TWILIO_API_KEY_SID + value: {{ .apiKeySid | quote }} +{{- end }} +{{- if .apiKeySecret }} +- name: TWILIO_API_KEY_SECRET + value: {{ .apiKeySecret | quote }} +{{- end }} +{{- end }} +{{- if .limitPhone }} +- name: PHONE_NOTIFICATIONS_LIMIT + value: {{ .limitPhone | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.celery.env" }} +{{- if .Values.celery.worker_queue }} +- name: CELERY_WORKER_QUEUE + value: {{ .Values.celery.worker_queue | quote }} +{{- end }} +{{- if .Values.celery.worker_concurrency }} +- name: CELERY_WORKER_CONCURRENCY + value: {{ .Values.celery.worker_concurrency | quote }} +{{- end }} +{{- if .Values.celery.worker_max_tasks_per_child }} +- name: CELERY_WORKER_MAX_TASKS_PER_CHILD + value: {{ .Values.celery.worker_max_tasks_per_child | quote }} +{{- end }} +{{- if .Values.celery.worker_beat_enabled }} +- name: CELERY_WORKER_BEAT_ENABLED + value: {{ .Values.celery.worker_beat_enabled | quote }} +{{- end }} +{{- if .Values.celery.worker_shutdown_interval }} +- name: CELERY_WORKER_SHUTDOWN_INTERVAL + value: {{ .Values.celery.worker_shutdown_interval | quote }} +{{- end }} +{{- end }} + +{{- define "snippet.grafana.url" -}} +{{ if .Values.grafana.enabled -}} + http://{{ include "oncall.grafana.fullname" . }} +{{- else -}} + {{ required "externalGrafana.url is required when not grafana.enabled" .Values.externalGrafana.url }} +{{- end }} +{{- end }} + +{{- define "snippet.mysql.env" -}} +- name: MYSQL_HOST + value: {{ include "snippet.mysql.host" . | quote }} +- name: MYSQL_PORT + value: {{ include "snippet.mysql.port" . | quote }} +- name: MYSQL_DB_NAME + value: {{ include "snippet.mysql.db" . | quote }} +- name: MYSQL_USER +{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.existingSecret .Values.externalMysql.usernameKey (not .Values.externalMysql.user) }} + valueFrom: + secretKeyRef: + name: {{ include "snippet.mysql.password.secret.name" . }} + key: {{ .Values.externalMysql.usernameKey | quote }} +{{- else }} + value: {{ include "snippet.mysql.user" . | quote }} +{{- end }} +- name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "snippet.mysql.password.secret.name" . }} + key: {{ include "snippet.mysql.password.secret.key" . | quote }} +{{- if not .Values.mariadb.enabled }} +{{- with .Values.externalMysql.options }} +- name: MYSQL_OPTIONS + value: {{ . | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.mysql.password.secret.name" -}} +{{ if .Values.mariadb.enabled -}} + {{ if .Values.mariadb.auth.existingSecret -}} + {{ .Values.mariadb.auth.existingSecret }} + {{- else -}} + {{ include "oncall.mariadb.fullname" . }} + {{- end }} +{{- else -}} + {{ if .Values.externalMysql.existingSecret -}} + {{ .Values.externalMysql.existingSecret }} + {{- else -}} + {{ include "oncall.fullname" . }}-mysql-external + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.mysql.password.secret.key" -}} +{{ if and (not .Values.mariadb.enabled) .Values.externalMysql.existingSecret .Values.externalMysql.passwordKey -}} + {{ .Values.externalMysql.passwordKey }} +{{- else -}} + mariadb-root-password +{{- end }} +{{- end }} + +{{- define "snippet.mysql.host" -}} +{{ if and (not .Values.mariadb.enabled) .Values.externalMysql.host -}} + {{ .Values.externalMysql.host }} +{{- else -}} + {{ include "oncall.mariadb.fullname" . }} +{{- end }} +{{- end }} + +{{- define "snippet.mysql.port" -}} +{{ if and (not .Values.mariadb.enabled) .Values.externalMysql.port -}} + {{ .Values.externalMysql.port }} +{{- else -}} + 3306 +{{- end }} +{{- end }} + +{{- define "snippet.mysql.db" -}} +{{ if and (not .Values.mariadb.enabled) .Values.externalMysql.db_name -}} + {{ .Values.externalMysql.db_name }} +{{- else -}} + {{ .Values.mariadb.auth.database | default "oncall" }} +{{- end }} +{{- end }} + +{{- define "snippet.mysql.user" -}} +{{ if and (not .Values.mariadb.enabled) .Values.externalMysql.user -}} + {{ .Values.externalMysql.user }} +{{- else -}} + {{ .Values.mariadb.auth.username | default "root" }} +{{- end }} +{{- end }} + +{{- define "snippet.postgresql.env" -}} +- name: DATABASE_TYPE + value: {{ .Values.database.type | quote }} +- name: DATABASE_HOST + value: {{ include "snippet.postgresql.host" . | quote }} +- name: DATABASE_PORT + value: {{ include "snippet.postgresql.port" . | quote }} +- name: DATABASE_NAME + value: {{ include "snippet.postgresql.db" . | quote }} +- name: DATABASE_USER + value: {{ include "snippet.postgresql.user" . | quote }} +- name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "snippet.postgresql.password.secret.name" . }} + key: {{ include "snippet.postgresql.password.secret.key" . | quote }} +{{- if not .Values.postgresql.enabled }} +{{- with .Values.externalPostgresql.options }} +- name: DATABASE_OPTIONS + value: {{ . | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.sqlite.env" -}} +- name: DATABASE_TYPE + value: sqlite3 +- name: DATABASE_NAME + value: /etc/app/oncall.db +{{- end }} + +{{- define "snippet.postgresql.password.secret.name" -}} +{{ if .Values.postgresql.enabled -}} + {{ if .Values.postgresql.auth.existingSecret -}} + {{ .Values.postgresql.auth.existingSecret }} + {{- else -}} + {{ include "oncall.postgresql.fullname" . }} + {{- end }} +{{- else -}} + {{ if .Values.externalPostgresql.existingSecret -}} + {{ .Values.externalPostgresql.existingSecret }} + {{- else -}} + {{ include "oncall.fullname" . }}-postgresql-external + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.postgresql.password.secret.key" -}} +{{ if .Values.postgresql.enabled -}} + {{ if .Values.postgresql.auth.existingSecret -}} + {{ required "postgresql.auth.secretKeys.adminPasswordKey is required if database.type=postgres and postgresql.enabled and postgresql.auth.existingSecret" .Values.postgresql.auth.secretKeys.adminPasswordKey }} + {{- else -}} + {{ include "postgresql.userPasswordKey" .Subcharts.postgresql }} + {{- end }} +{{- else -}} + {{ if .Values.externalPostgresql.existingSecret -}} + {{ required "externalPostgresql.passwordKey is required if database.type=postgres and not postgresql.enabled and postgresql.auth.existingSecret" .Values.externalPostgresql.passwordKey }} + {{- else -}} + postgres-password + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.postgresql.host" -}} +{{ if not .Values.postgresql.enabled -}} + {{ required "externalPostgresql.host is required if database.type=postgres and not postgresql.enabled" .Values.externalPostgresql.host }} +{{- else -}} + {{ include "oncall.postgresql.fullname" . }} +{{- end }} +{{- end }} + +{{- define "snippet.postgresql.port" -}} +{{ if and (not .Values.postgresql.enabled) .Values.externalPostgresql.port -}} + {{ .Values.externalPostgresql.port }} +{{- else -}} + 5432 +{{- end }} +{{- end }} + +{{- define "snippet.postgresql.db" -}} +{{ if not .Values.postgresql.enabled -}} + {{ .Values.externalPostgresql.db_name | default "oncall" }} +{{- else -}} + {{ .Values.postgresql.auth.database | default "oncall" }} +{{- end }} +{{- end }} + +{{- define "snippet.postgresql.user" -}} +{{ if not .Values.postgresql.enabled -}} + {{ .Values.externalPostgresql.user | default "postgres" }} +{{- else -}} + {{ .Values.postgresql.auth.username | default "postgres" }} +{{- end }} +{{- end }} + +{{- define "snippet.rabbitmq.env" }} +- name: RABBITMQ_USERNAME +{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.existingSecret .Values.externalRabbitmq.usernameKey (not .Values.externalRabbitmq.user) }} + valueFrom: + secretKeyRef: + name: {{ include "snippet.rabbitmq.password.secret.name" . }} + key: {{ .Values.externalRabbitmq.usernameKey | quote }} +{{- else }} + value: {{ include "snippet.rabbitmq.user" . | quote }} +{{- end }} +- name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "snippet.rabbitmq.password.secret.name" . }} + key: {{ include "snippet.rabbitmq.password.secret.key" . | quote }} +- name: RABBITMQ_HOST + value: {{ include "snippet.rabbitmq.host" . | quote }} +- name: RABBITMQ_PORT + value: {{ include "snippet.rabbitmq.port" . | quote }} +- name: RABBITMQ_PROTOCOL + value: {{ include "snippet.rabbitmq.protocol" . | quote }} +- name: RABBITMQ_VHOST + value: {{ include "snippet.rabbitmq.vhost" . | quote }} +{{- end }} + +{{- define "snippet.rabbitmq.user" -}} +{{ if not .Values.rabbitmq.enabled -}} + {{ required "externalRabbitmq.user is required if not rabbitmq.enabled" .Values.externalRabbitmq.user }} +{{- else -}} + user +{{- end }} +{{- end }} + +{{- define "snippet.rabbitmq.host" -}} +{{ if not .Values.rabbitmq.enabled -}} + {{ required "externalRabbitmq.host is required if not rabbitmq.enabled" .Values.externalRabbitmq.host }} +{{- 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 }} +{{- else -}} + 5672 +{{- end }} +{{- end }} + +{{- define "snippet.rabbitmq.protocol" -}} +{{ if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.protocol -}} + {{ .Values.externalRabbitmq.protocol }} +{{- else -}} + amqp +{{- end }} +{{- end }} + +{{- define "snippet.rabbitmq.vhost" -}} +{{ if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.vhost -}} + {{ .Values.externalRabbitmq.vhost }} +{{- end }} +{{- end }} + +{{- define "snippet.rabbitmq.password.secret.name" -}} +{{ if .Values.rabbitmq.enabled -}} + {{ if .Values.rabbitmq.auth.existingPasswordSecret -}} + {{ .Values.rabbitmq.auth.existingPasswordSecret }} + {{- else -}} + {{ include "oncall.rabbitmq.fullname" . }} + {{- end }} +{{- else -}} + {{ if .Values.externalRabbitmq.existingSecret -}} + {{ .Values.externalRabbitmq.existingSecret }} + {{- else -}} + {{ include "oncall.fullname" . }}-rabbitmq-external + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.rabbitmq.password.secret.key" -}} +{{ if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.passwordKey -}} + {{ .Values.externalRabbitmq.passwordKey }} +{{- else -}} + rabbitmq-password +{{- end }} +{{- end }} + +{{- define "snippet.redis.protocol" -}} +{{ default "redis" .Values.externalRedis.protocol | quote }} +{{- end }} + +{{- define "snippet.redis.host" -}} +{{ if not .Values.redis.enabled -}} + {{ required "externalRedis.host is required if not redis.enabled" .Values.externalRedis.host | quote }} +{{- else -}} + {{ include "oncall.redis.fullname" . }}-master +{{- end }} +{{- end }} + +{{- define "snippet.redis.port" -}} +{{ default 6379 .Values.externalRedis.port | quote }} +{{- end }} + +{{- define "snippet.redis.database" -}} +{{ default 0 .Values.externalRedis.database | quote }} +{{- end }} + +{{- define "snippet.redis.password.secret.name" -}} +{{ if .Values.redis.enabled -}} + {{ if .Values.redis.auth.existingSecret -}} + {{ .Values.redis.auth.existingSecret }} + {{- else -}} + {{ include "oncall.redis.fullname" . }} + {{- end }} +{{- else -}} + {{ if .Values.externalRedis.existingSecret -}} + {{ .Values.externalRedis.existingSecret }} + {{- else -}} + {{ include "oncall.fullname" . }}-redis-external + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.redis.password.secret.key" -}} +{{ if .Values.redis.enabled -}} + {{ if .Values.redis.auth.existingSecret -}} + {{ required "redis.auth.existingSecretPasswordKey is required if redis.auth.existingSecret is non-empty" .Values.redis.auth.existingSecretPasswordKey }} + {{- else -}} + redis-password + {{- end }} +{{- else -}} + {{ if .Values.externalRedis.existingSecret -}} + {{ required "externalRedis.passwordKey is required if externalRedis.existingSecret is non-empty" .Values.externalRedis.passwordKey }} + {{- else -}} + redis-password + {{- end }} +{{- end }} +{{- end }} + +{{- define "snippet.redis.env" -}} +- name: REDIS_PROTOCOL + value: {{ include "snippet.redis.protocol" . }} +- name: REDIS_HOST + value: {{ include "snippet.redis.host" . }} +- name: REDIS_PORT + value: {{ include "snippet.redis.port" . }} +- name: REDIS_DATABASE + value: {{ include "snippet.redis.database" . }} +- name: REDIS_USERNAME + value: {{ default "" .Values.externalRedis.username | quote }} +- name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "snippet.redis.password.secret.name" . }} + key: {{ include "snippet.redis.password.secret.key" . | quote}} +{{- if and (not .Values.redis.enabled) .Values.externalRedis.ssl_options.enabled }} +- name: REDIS_USE_SSL + value: "true" +{{- with .Values.externalRedis.ssl_options.ca_certs }} +- name: REDIS_SSL_CA_CERTS + value: {{ . | quote }} +{{- end }} +{{- with .Values.externalRedis.ssl_options.certfile }} +- name: REDIS_SSL_CERTFILE + value: {{ . | quote }} +{{- end }} +{{- with .Values.externalRedis.ssl_options.keyfile }} +- name: REDIS_SSL_KEYFILE + value: {{ . | quote }} +{{- end }} +{{- with .Values.externalRedis.ssl_options.cert_reqs }} +- name: REDIS_SSL_CERT_REQS + value: {{ . | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{- /* +when broker.type != rabbitmq, we do not need to include rabbitmq environment variables +*/}} +{{- define "snippet.broker.env" -}} +{{- include "snippet.redis.env" . }} +{{- if eq .Values.broker.type "rabbitmq" -}} +{{- include "snippet.rabbitmq.env" . }} +{{- end }} +{{- end }} + +{{- define "snippet.db.env" -}} +{{- if eq .Values.database.type "mysql" }} +{{- include "snippet.mysql.env" . }} +{{- else if eq .Values.database.type "postgresql" }} +{{- include "snippet.postgresql.env" . }} +{{- else if eq .Values.database.type "sqlite" -}} +{{- include "snippet.sqlite.env" . }} +{{- else -}} +{{- fail "value for .Values.db.type must be either 'mysql', 'postgresql', or 'sqlite'" }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.smtp.env" -}} +- name: FEATURE_EMAIL_INTEGRATION_ENABLED + value: {{ .Values.oncall.smtp.enabled | toString | title | quote }} +{{- if .Values.oncall.smtp.enabled }} +- name: EMAIL_HOST + value: {{ .Values.oncall.smtp.host | quote }} +- name: EMAIL_PORT + value: {{ .Values.oncall.smtp.port | default "587" | quote }} +- name: EMAIL_HOST_USER + value: {{ .Values.oncall.smtp.username | quote }} +- name: EMAIL_HOST_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "oncall.fullname" . }}-smtp + key: smtp-password + optional: true +- name: EMAIL_USE_TLS + value: {{ .Values.oncall.smtp.tls | default true | toString | title | quote }} +- name: EMAIL_FROM_ADDRESS + value: {{ .Values.oncall.smtp.fromEmail | quote }} +- name: EMAIL_NOTIFICATIONS_LIMIT + value: {{ .Values.oncall.smtp.limitEmail | default "200" | quote }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.exporter.env" -}} +{{ if .Values.oncall.exporter.enabled -}} +- name: FEATURE_PROMETHEUS_EXPORTER_ENABLED + value: {{ .Values.oncall.exporter.enabled | toString | title | quote }} +- name: PROMETHEUS_EXPORTER_SECRET + valueFrom: + secretKeyRef: + name: {{ include "oncall.fullname" . }}-exporter + key: exporter-secret + optional: true +{{- else -}} +- name: FEATURE_PROMETHEUS_EXPORTER_ENABLED + value: {{ .Values.oncall.exporter.enabled | toString | title | quote }} +{{- end }} +{{- end }} + +{{- define "snippet.oncall.engine.env" -}} +{{ include "snippet.oncall.env" . }} +{{ include "snippet.oncall.slack.env" . }} +{{ include "snippet.oncall.telegram.env" . }} +{{ include "snippet.oncall.smtp.env" . }} +{{ include "snippet.oncall.twilio.env" . }} +{{ include "snippet.oncall.exporter.env" . }} +{{ include "snippet.db.env" . }} +{{ include "snippet.broker.env" . }} +{{ include "oncall.extraEnvs" . }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/_helpers.tpl b/packages/apps/observability/charts/oncall/templates/_helpers.tpl new file mode 100644 index 00000000..6486bfe5 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/_helpers.tpl @@ -0,0 +1,121 @@ +{{/* +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 }} + +{{/* Generate the fullname of postgresql subchart */}} +{{- define "oncall.postgresql.fullname" -}} +{{- printf "%s-%s" .Release.Name "postgresql" | 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 }} + +{{/* Generate engine image name */}} +{{- define "oncall.engine.image" -}} +{{- printf "%s:%s" .Values.image.repository (.Values.image.tag | default .Chart.AppVersion) }} +{{- end }} + +{{- define "oncall.initContainer" }} +- 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"] + securityContext: + {{ toYaml .Values.init.securityContext | nindent 4 }} + resources: + {{ toYaml .Values.init.resources | nindent 4 }} + env: + {{- include "snippet.oncall.env" . | nindent 4 }} + {{- include "snippet.db.env" . | nindent 4 }} + {{- include "snippet.broker.env" . | nindent 4 }} + {{- include "oncall.extraEnvs" . | nindent 4 }} +{{- end }} + +{{- define "oncall.extraEnvs" -}} +{{- if .Values.env }} + {{- if (kindIs "map" .Values.env) }} + {{- range $key, $value := .Values.env }} +- name: {{ $key }} + value: {{ $value }} + {{- end -}} + {{/* support previous schema */}} + {{- else }} +{{- toYaml .Values.env }} + {{- end }} +{{- end }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/celery/_helpers.tpl b/packages/apps/observability/charts/oncall/templates/celery/_helpers.tpl new file mode 100644 index 00000000..8c37e957 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/celery/_helpers.tpl @@ -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 }} diff --git a/packages/apps/observability/charts/oncall/templates/celery/deployment.yaml b/packages/apps/observability/charts/oncall/templates/celery/deployment.yaml new file mode 100644 index 00000000..b2498dd1 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/celery/deployment.yaml @@ -0,0 +1,89 @@ +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 }} + {{- if .Values.celery.podLabels }} + {{- toYaml .Values.celery.podLabels | nindent 8}} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "oncall.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + {{- include "oncall.initContainer" . | indent 8 }} + {{- with .Values.celery.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.celery.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.celery.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.celery.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.celery.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "oncall.engine.image" . }} + {{- if .Values.oncall.devMode }} + command: ["python", "manage.py", "start_celery"] + {{- else }} + command: ["./celery_with_exporter.sh"] + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + {{- include "snippet.celery.env" . | nindent 12 }} + {{- include "snippet.oncall.engine.env" . | nindent 12 }} + {{- if .Values.celery.livenessProbe.enabled }} + livenessProbe: + exec: + command: [ + "bash", + "-c", + "celery -A engine inspect ping -d celery@$HOSTNAME" + ] + initialDelaySeconds: {{ .Values.celery.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.celery.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.celery.livenessProbe.timeoutSeconds }} + {{- end }} + resources: + {{- toYaml .Values.celery.resources | nindent 12 }} + {{- with .Values.celery.extraVolumeMounts }} + volumeMounts: {{- . | toYaml | nindent 12 }} + {{- end }} + {{- with .Values.celery.extraContainers }} + {{- tpl . $ | nindent 8 }} + {{- end }} + {{- with .Values.celery.extraVolumes }} + volumes: {{- . | toYaml | nindent 8 }} + {{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/cert-issuer.yaml b/packages/apps/observability/charts/oncall/templates/cert-issuer.yaml new file mode 100644 index 00000000..8b1716f3 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/cert-issuer.yaml @@ -0,0 +1,22 @@ +{{- if (index .Values "cert-manager").enabled }} +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 }} diff --git a/packages/apps/observability/charts/oncall/templates/engine/_helpers-engine.tpl b/packages/apps/observability/charts/oncall/templates/engine/_helpers-engine.tpl new file mode 100644 index 00000000..6d498e93 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/engine/_helpers-engine.tpl @@ -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 }} diff --git a/packages/apps/observability/charts/oncall/templates/engine/deployment.yaml b/packages/apps/observability/charts/oncall/templates/engine/deployment.yaml new file mode 100644 index 00000000..ccb770df --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/engine/deployment.yaml @@ -0,0 +1,98 @@ +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: + {{- toYaml .Values.engine.updateStrategy | nindent 4 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + random-annotation: {{ randAlphaNum 10 | lower }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "oncall.engine.selectorLabels" . | nindent 8 }} + {{- if .Values.engine.podLabels }} + {{- toYaml .Values.engine.podLabels | nindent 8}} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "oncall.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + {{- include "oncall.initContainer" . | indent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "oncall.engine.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.oncall.devMode }} + command: ["sh", "-c", "uwsgi --disable-logging --py-autoreload 3 --ini uwsgi.ini"] + {{- end }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + {{- include "snippet.oncall.engine.env" . | nindent 12 }} + 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: 10 + timeoutSeconds: 3 + resources: + {{- toYaml .Values.engine.resources | nindent 12 }} + {{- with .Values.engine.extraVolumeMounts }} + volumeMounts: {{- . | toYaml | nindent 12 }} + {{- end }} + {{- with .Values.engine.extraContainers }} + {{- tpl . $ | nindent 8 }} + {{- end }} + {{- with .Values.engine.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.engine.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.engine.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.engine.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.engine.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + {{- with .Values.engine.extraVolumes }} + volumes: {{- . | toYaml | nindent 8 }} + {{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/engine/job-migrate.yaml b/packages/apps/observability/charts/oncall/templates/engine/job-migrate.yaml new file mode 100644 index 00000000..09782954 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/engine/job-migrate.yaml @@ -0,0 +1,102 @@ +{{- if .Values.migrate.enabled -}} +apiVersion: batch/v1 +kind: Job +metadata: + {{- if .Values.migrate.useHook }} + name: {{ printf "%s-migrate" (include "oncall.engine.fullname" .) }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-1" + {{- with .Values.migrate.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- else }} + name: {{ printf "%s-migrate-%s" (include "oncall.engine.fullname" .) (now | date "2006-01-02-15-04-05") }} + {{- with .Values.migrate.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + labels: + {{- include "oncall.engine.labels" . | nindent 4 }} +spec: + backoffLimit: 15 + {{- if .Values.migrate.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.migrate.ttlSecondsAfterFinished }} + {{- end }} + 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 }} + {{- with .Values.migrate.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.migrate.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.migrate.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }}-migrate + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "oncall.engine.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/sh + - -c + {{- if eq .Values.database.type "mysql" }} + - | + until (nc -vz $MYSQL_HOST $MYSQL_PORT); + do + echo "waiting for MySQL"; sleep 1; + done + python manage.py migrate + {{- else if eq .Values.database.type "postgresql" }} + - | + until (nc -vz $DATABASE_HOST $DATABASE_PORT); + do + echo "waiting for PostgreSQL"; sleep 1; + done + python manage.py migrate + {{- else }} + - python manage.py migrate + {{- end }} + env: + {{- include "snippet.oncall.env" . | nindent 12 }} + {{- include "snippet.oncall.smtp.env" . | nindent 12 }} + {{- include "snippet.oncall.exporter.env" . | nindent 12 }} + {{- include "snippet.db.env" . | nindent 12 }} + {{- include "snippet.broker.env" . | nindent 12 }} + {{- include "oncall.extraEnvs" . | nindent 12 }} + resources: + {{- toYaml .Values.migrate.resources | nindent 12 }} + {{- with .Values.migrate.extraVolumeMounts }} + volumeMounts: {{- . | toYaml | nindent 10 }} + {{- end }} + {{- with .Values.migrate.extraContainers }} + {{- tpl . $ | nindent 6 }} + {{- end }} + {{- with .Values.migrate.extraVolumes }} + volumes: {{- . | toYaml | nindent 8 }} + {{- end }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/engine/service-external.yaml b/packages/apps/observability/charts/oncall/templates/engine/service-external.yaml new file mode 100644 index 00000000..eec1f0bf --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/engine/service-external.yaml @@ -0,0 +1,24 @@ +{{- if .Values.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "oncall.engine.fullname" . }}-external + labels: + {{- include "oncall.engine.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + {{- if and (eq .Values.service.type "NodePort") (.Values.service.nodePort) }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + selector: + {{- include "oncall.engine.selectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/packages/apps/observability/charts/oncall/templates/engine/service-internal.yaml b/packages/apps/observability/charts/oncall/templates/engine/service-internal.yaml new file mode 100644 index 00000000..07785035 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/engine/service-internal.yaml @@ -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 }} diff --git a/packages/apps/observability/charts/oncall/templates/ingress-regular.yaml b/packages/apps/observability/charts/oncall/templates/ingress-regular.yaml new file mode 100644 index 00000000..9a5357ff --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/ingress-regular.yaml @@ -0,0 +1,65 @@ +{{- 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: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- tpl (toYaml .Values.ingress.tls) . | nindent 4 }} + {{- end }} + rules: + - host: {{ .Values.base_url | quote }} + http: + paths: +{{- if .Values.ingress.extraPaths }} +{{ toYaml .Values.ingress.extraPaths | indent 6}} +{{- end }} + - path: / + pathType: Prefix + backend: + service: + name: {{ include "oncall.engine.fullname" . }} + port: + number: 8080 + {{ if .Values.grafana.enabled }} + - path: /grafana + pathType: Prefix + backend: + service: + name: {{ include "oncall.grafana.fullname" . }} + port: + number: 80 + {{- end }} + {{ if .Values.detached_integrations.enabled }} + - path: /integrations + pathType: Prefix + backend: + service: + name: {{ include "oncall.detached_integrations.fullname" . }} + port: + number: 8080 + {{- end }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/integrations/_helpers.tpl b/packages/apps/observability/charts/oncall/templates/integrations/_helpers.tpl new file mode 100644 index 00000000..6727ed42 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/integrations/_helpers.tpl @@ -0,0 +1,26 @@ +{{/* +Maximum of 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "oncall.detached_integrations.name" -}} +{{ include "oncall.name" . | trunc 55 }}-integrations +{{- end }} + +{{- define "oncall.detached_integrations.fullname" -}} +{{ include "oncall.fullname" . | trunc 55 }}-integrations +{{- end }} + +{{/* +Integrations common labels +*/}} +{{- define "oncall.detached_integrations.labels" -}} +{{ include "oncall.labels" . }} +app.kubernetes.io/component: integrations +{{- end }} + +{{/* +Integrations selector labels +*/}} +{{- define "oncall.detached_integrations.selectorLabels" -}} +{{ include "oncall.selectorLabels" . }} +app.kubernetes.io/component: integrations +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/integrations/deployment.yaml b/packages/apps/observability/charts/oncall/templates/integrations/deployment.yaml new file mode 100644 index 00000000..5e08eaf7 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/integrations/deployment.yaml @@ -0,0 +1,99 @@ +{{- if .Values.detached_integrations.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "oncall.detached_integrations.fullname" . }} + labels: + {{- include "oncall.detached_integrations.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.detached_integrations.replicaCount }} + selector: + matchLabels: + {{- include "oncall.detached_integrations.selectorLabels" . | nindent 6 }} + strategy: + {{- toYaml .Values.detached_integrations.updateStrategy | nindent 4 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + random-annotation: {{ randAlphaNum 10 | lower }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "oncall.detached_integrations.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.initContainer" . | indent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "oncall.engine.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.oncall.devMode }} + command: ["sh", "-c", "uwsgi --disable-logging --py-autoreload 3 --ini uwsgi.ini"] + {{- end }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + {{- include "snippet.oncall.engine.env" . | nindent 12 }} + - name: ROOT_URLCONF + value: "engine.integrations_urls" + 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: 10 + timeoutSeconds: 3 + resources: + {{- toYaml .Values.detached_integrations.resources | nindent 12 }} + {{- with .Values.detached_integrations.extraVolumeMounts }} + volumeMounts: {{- . | toYaml | nindent 12 }} + {{- end }} + {{- with .Values.detached_integrations.extraContainers }} + {{- tpl . $ | nindent 8 }} + {{- end }} + {{- with .Values.detached_integrations.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.detached_integrations.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.detached_integrations.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.detached_integrations.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.detached_integrations.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + {{- with .Values.detached_integrations.extraVolumes }} + volumes: {{- . | toYaml | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/packages/apps/observability/charts/oncall/templates/integrations/service-external.yaml b/packages/apps/observability/charts/oncall/templates/integrations/service-external.yaml new file mode 100644 index 00000000..455d4aa0 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/integrations/service-external.yaml @@ -0,0 +1,24 @@ +{{- if .Values.detached_integrations_service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "oncall.detached_integrations.fullname" . }}-external + labels: + {{- include "oncall.detached_integrations.labels" . | nindent 4 }} + {{- with .Values.detached_integrations_service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.detached_integrations_service.type }} + ports: + - port: {{ .Values.detached_integrations_service.port }} + targetPort: http + protocol: TCP + name: http + {{- if and (eq .Values.detached_integrations_service.type "NodePort") (.Values.detached_integrations_service.nodePort) }} + nodePort: {{ .Values.detached_integrations_service.nodePort }} + {{- end }} + selector: + {{- include "oncall.detached_integrations.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/integrations/service-internal.yaml b/packages/apps/observability/charts/oncall/templates/integrations/service-internal.yaml new file mode 100644 index 00000000..3527b247 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/integrations/service-internal.yaml @@ -0,0 +1,17 @@ +{{- if .Values.detached_integrations.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "oncall.detached_integrations.fullname" . }} + labels: + {{- include "oncall.detached_integrations.labels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "oncall.detached_integrations.selectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/packages/apps/observability/charts/oncall/templates/secrets.yaml b/packages/apps/observability/charts/oncall/templates/secrets.yaml new file mode 100644 index 00000000..821592fa --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/secrets.yaml @@ -0,0 +1,98 @@ +{{- if not .Values.oncall.secrets.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }} + labels: + {{- include "oncall.labels" . | nindent 4 }} + {{- if .Values.migrate.useHook }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + {{- end }} +type: Opaque +data: + {{ include "snippet.oncall.secret.secretKey" . }}: {{ randAlphaNum 40 | b64enc | quote }} + {{ include "snippet.oncall.secret.mirageSecretKey" . }}: {{ randAlphaNum 40 | b64enc | quote }} +--- +{{- end }} +{{- if and (eq .Values.database.type "mysql") (not .Values.mariadb.enabled) (not .Values.externalMysql.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }}-mysql-external + {{- if .Values.migrate.useHook }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + {{- end }} +type: Opaque +data: + mariadb-root-password: {{ required "externalMysql.password is required if not mariadb.enabled and not externalMysql.existingSecret" .Values.externalMysql.password | b64enc | quote }} +--- +{{- end }} +{{- if and (not .Values.postgresql.enabled) (eq .Values.database.type "postgresql") (not .Values.externalPostgresql.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }}-postgresql-external + {{- if .Values.migrate.useHook }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + {{- end }} +type: Opaque +data: + postgres-password: {{ required "externalPostgresql.password is required if not postgresql.enabled and not externalPostgresql.existingSecret" .Values.externalPostgresql.password | b64enc | quote }} +--- +{{- end }} +{{- if and (eq .Values.broker.type "rabbitmq") (not .Values.rabbitmq.enabled) (not .Values.externalRabbitmq.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }}-rabbitmq-external + {{- if .Values.migrate.useHook }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + {{- end }} +type: Opaque +data: + rabbitmq-password: {{ required "externalRabbitmq.password is required if not rabbitmq.enabled and not externalRabbitmq.existingSecret" .Values.externalRabbitmq.password | b64enc | quote }} +--- +{{- end }} +{{- if and (eq .Values.broker.type "redis") (not .Values.redis.enabled) (not .Values.externalRedis.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }}-redis-external + {{- if .Values.migrate.useHook }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + {{- end }} +type: Opaque +data: + redis-password: {{ required "externalRedis.password is required if not redis.enabled and not externalRedis.existingSecret" .Values.externalRedis.password | b64enc | quote }} +--- +{{- end }} +{{- if and .Values.oncall.smtp.enabled .Values.oncall.smtp.password }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }}-smtp +type: Opaque +data: + smtp-password: {{ .Values.oncall.smtp.password | b64enc | quote }} +--- +{{- end }} +{{- if and .Values.oncall.exporter.enabled .Values.oncall.exporter.authToken }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "oncall.fullname" . }}-exporter +type: Opaque +data: + exporter-secret: {{ .Values.oncall.exporter.authToken | b64enc | quote }} +--- +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/serviceaccount.yaml b/packages/apps/observability/charts/oncall/templates/serviceaccount.yaml new file mode 100644 index 00000000..d0a5a9eb --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/serviceaccount.yaml @@ -0,0 +1,18 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "oncall.serviceAccountName" . }} + labels: + {{- include "oncall.labels" . | nindent 4 }} + {{- if or (.Values.migrate.useHook) (.Values.serviceAccount.annotations) }} + annotations: + {{- if .Values.migrate.useHook }} + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + {{- end }} + {{- with .Values.serviceAccount.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/telegram-polling/_helpers.tpl b/packages/apps/observability/charts/oncall/templates/telegram-polling/_helpers.tpl new file mode 100644 index 00000000..d2053dc0 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/telegram-polling/_helpers.tpl @@ -0,0 +1,22 @@ +{{/* +Maximum of 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "oncall.telegramPolling.fullname" -}} +{{ include "oncall.fullname" . | trunc 45 }}-telegram-polling +{{- end }} + +{{/* +Telegram polling common labels +*/}} +{{- define "oncall.telegramPolling.labels" -}} +{{ include "oncall.labels" . }} +app.kubernetes.io/component: telegram-polling +{{- end }} + +{{/* +Telegram polling selector labels +*/}} +{{- define "oncall.telegramPolling.selectorLabels" -}} +{{ include "oncall.selectorLabels" . }} +app.kubernetes.io/component: telegram-polling +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/telegram-polling/deployment.yaml b/packages/apps/observability/charts/oncall/templates/telegram-polling/deployment.yaml new file mode 100644 index 00000000..2e448897 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/telegram-polling/deployment.yaml @@ -0,0 +1,53 @@ +{{- if .Values.telegramPolling.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "oncall.telegramPolling.fullname" . }} + labels: + {{- include "oncall.telegramPolling.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "oncall.telegramPolling.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "oncall.telegramPolling.selectorLabels" . | nindent 8 }} + {{- if .Values.telegramPolling.podLabels }} + {{- toYaml .Values.telegramPolling.podLabels | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "oncall.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + {{- include "oncall.initContainer" . | nindent 8 }} + containers: + - name: telegram-polling + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "oncall.engine.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ['sh', '-c', 'python manage.py start_telegram_polling'] + env: + {{- include "snippet.oncall.env" . | nindent 12 }} + {{- include "snippet.oncall.telegram.env" . | nindent 12 }} + {{- include "snippet.db.env" . | nindent 12 }} + {{- include "snippet.broker.env" . | nindent 12 }} + {{- include "oncall.extraEnvs" . | nindent 12 }} + {{- with .Values.telegramPolling.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.telegramPolling.extraVolumeMounts }} + volumeMounts: {{- . | toYaml | nindent 12 }} + {{- end }} + {{- with .Values.telegramPolling.extraVolumes }} + volumes: {{- . | toYaml | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/packages/apps/observability/charts/oncall/templates/ui/_helpers.tpl b/packages/apps/observability/charts/oncall/templates/ui/_helpers.tpl new file mode 100644 index 00000000..49dcc2e5 --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/ui/_helpers.tpl @@ -0,0 +1,8 @@ +{{- define "ui.env" -}} +{{- if .Values.ui.env }} + {{- range $key, $value := .Values.ui.env }} +- name: {{ $key }} + value: "{{ $value }}" + {{- end -}} +{{- end }} +{{- end }} diff --git a/packages/apps/observability/charts/oncall/templates/ui/deployment.yaml b/packages/apps/observability/charts/oncall/templates/ui/deployment.yaml new file mode 100644 index 00000000..c094368f --- /dev/null +++ b/packages/apps/observability/charts/oncall/templates/ui/deployment.yaml @@ -0,0 +1,31 @@ +{{- if .Values.ui.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: oncall-ui + labels: + app.kubernetes.io/component: oncall-ui +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: oncall-ui + template: + metadata: + labels: + app.kubernetes.io/component: oncall-ui + spec: + containers: + - name: oncall-ui + image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: IfNotPresent + env: + {{- include "ui.env" . | nindent 12 }} + volumeMounts: + - mountPath: /etc/app + name: hot-reloaded-plugin + volumes: + - name: hot-reloaded-plugin + hostPath: + path: /oncall-plugin +{{- end }} diff --git a/packages/apps/observability/charts/oncall/values.yaml b/packages/apps/observability/charts/oncall/values.yaml new file mode 100644 index 00000000..3306f005 --- /dev/null +++ b/packages/apps/observability/charts/oncall/values.yaml @@ -0,0 +1,719 @@ +# 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 +base_url_protocol: https + +## Optionally specify an array of imagePullSecrets. +## Secrets must be manually created in the namespace. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +## e.g: +## imagePullSecrets: +## - name: myRegistryKeySecretName +imagePullSecrets: [] + +image: + # Grafana OnCall docker image repository + repository: grafana/oncall + tag: + pullPolicy: Always + +# Whether to create additional service for external connections +# ClusterIP service is always created +service: + enabled: false + type: LoadBalancer + port: 8080 + annotations: {} + +# Engine pods configuration +engine: + replicaCount: 1 + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # Labels for engine pods + podLabels: {} + + ## Deployment update strategy + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy + updateStrategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 0 + type: RollingUpdate + + ## Affinity for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + affinity: {} + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + nodeSelector: {} + + ## Tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + tolerations: [] + + ## Topology spread constraints for pod assignment + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ + topologySpreadConstraints: [] + + ## Priority class for the pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ + priorityClassName: "" + + # Extra containers which runs as sidecar + extraContainers: "" + # extraContainers: | + # - name: cloud-sql-proxy + # image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.1.2 + # args: + # - --private-ip + # - --port=5432 + # - example:europe-west3:grafana-oncall-db + + # Extra volume mounts for the main app container + extraVolumeMounts: [] + # - mountPath: /mnt/postgres-tls + # name: postgres-tls + # - mountPath: /mnt/redis-tls + # name: redis-tls + + # Extra volumes for the pod + extraVolumes: [] + # - name: postgres-tls + # configMap: + # name: my-postgres-tls + # defaultMode: 0640 + # - name: redis-tls + # configMap: + # name: my-redis-tls + # defaultMode: 0640 + +detached_integrations_service: + enabled: false + type: LoadBalancer + port: 8080 + annotations: {} + +# Integrations pods configuration +detached_integrations: + enabled: false + replicaCount: 1 + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + ## Deployment update strategy + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy + updateStrategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 0 + type: RollingUpdate + + ## Affinity for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + affinity: {} + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + nodeSelector: {} + + ## Tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + tolerations: [] + + ## Topology spread constraints for pod assignment + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ + topologySpreadConstraints: [] + + ## Priority class for the pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ + priorityClassName: "" + + # Extra containers which runs as sidecar + extraContainers: "" + # extraContainers: | + # - name: cloud-sql-proxy + # image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.1.2 + # args: + # - --private-ip + # - --port=5432 + # - example:europe-west3:grafana-oncall-db + + # Extra volume mounts for the container + extraVolumeMounts: [] + # - mountPath: /mnt/postgres-tls + # name: postgres-tls + # - mountPath: /mnt/redis-tls + # name: redis-tls + + # Extra volumes for the pod + extraVolumes: [] + # - name: postgres-tls + # configMap: + # name: my-postgres-tls + # defaultMode: 0640 + # - name: redis-tls + # configMap: + # name: my-redis-tls + # defaultMode: 0640 + +# Celery workers pods configuration +celery: + replicaCount: 1 + worker_queue: "default,critical,long,slack,telegram,webhook,celery,grafana" + worker_concurrency: "1" + worker_max_tasks_per_child: "100" + worker_beat_enabled: "True" + ## Restart of the celery workers once in a given interval as an additional precaution to the probes + ## If this setting is enabled TERM signal will be sent to celery workers + ## It will lead to warm shutdown (waiting for the tasks to complete) and restart the container + ## If this setting is set numbers of pod restarts will increase + ## Comment this line out if you want to remove restarts + worker_shutdown_interval: "65m" + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 300 + timeoutSeconds: 10 + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # Labels for celery pods + podLabels: {} + + ## Affinity for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + affinity: {} + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + nodeSelector: {} + + ## Tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + tolerations: [] + + ## Topology spread constraints for pod assignment + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ + topologySpreadConstraints: [] + + ## Priority class for the pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ + priorityClassName: "" + + # Extra containers which runs as sidecar + extraContainers: "" + # extraContainers: | + # - name: cloud-sql-proxy + # image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.1.2 + # args: + # - --private-ip + # - --port=5432 + # - example:europe-west3:grafana-oncall-db + + # Extra volume mounts for the main container + extraVolumeMounts: [] + # - mountPath: /mnt/postgres-tls + # name: postgres-tls + # - mountPath: /mnt/redis-tls + # name: redis-tls + + # Extra volumes for the pod + extraVolumes: [] + # - name: postgres-tls + # configMap: + # name: my-postgres-tls + # defaultMode: 0640 + # - name: redis-tls + # configMap: + # name: my-redis-tls + # defaultMode: 0640 + +# Telegram polling pod configuration +telegramPolling: + enabled: false + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # Labels for telegram-polling pods + podLabels: {} + + # Extra volume mounts for the main container + extraVolumeMounts: [] + # - mountPath: /mnt/postgres-tls + # name: postgres-tls + # - mountPath: /mnt/redis-tls + # name: redis-tls + + # Extra volumes for the pod + extraVolumes: [] + # - name: postgres-tls + # configMap: + # name: my-postgres-tls + # defaultMode: 0640 + # - name: redis-tls + # configMap: + # name: my-redis-tls + # defaultMode: 0640 + +oncall: + # this is intended to be used for local development. In short, it will mount the ./engine dir into + # any backend related containers, to allow hot-reloading + also run the containers with slightly modified + # startup commands (which configures the hot-reloading) + devMode: false + + # Override default MIRAGE_CIPHER_IV (must be 16 bytes long) + # For existing installation, this should not be changed. + # mirageCipherIV: 1234567890abcdef + # oncall secrets + secrets: + # Use existing secret. (secretKey and mirageSecretKey is required) + existingSecret: "" + # The key in the secret containing secret key + secretKey: "" + # The key in the secret containing mirage secret key + mirageSecretKey: "" + # Slack configures the Grafana Oncall Slack ChatOps integration. + slack: + # Enable the Slack ChatOps integration for the Oncall Engine. + enabled: false + # Sets the Slack bot slash-command + commandName: oncall + # clientId configures the Slack app OAuth2 client ID. + # api.slack.com/apps/ -> Basic Information -> App Credentials -> Client ID + clientId: ~ + # clientSecret configures the Slack app OAuth2 client secret. + # api.slack.com/apps/ -> Basic Information -> App Credentials -> Client Secret + clientSecret: ~ + # signingSecret - configures the Slack app signature secret used to sign + # requests comming from Slack. + # api.slack.com/apps/ -> Basic Information -> App Credentials -> Signing Secret + signingSecret: ~ + # Use existing secret for clientId, clientSecret and signingSecret. + # clientIdKey, clientSecretKey and signingSecretKey are required + existingSecret: "" + # The key in the secret containing OAuth2 client ID + clientIdKey: "" + # The key in the secret containing OAuth2 client secret + clientSecretKey: "" + # The key in the secret containing the Slack app signature secret + signingSecretKey: "" + # OnCall external URL + redirectHost: ~ + telegram: + enabled: false + token: ~ + webhookUrl: ~ + # Use existing secret. (tokenKey is required) + existingSecret: "" + # The key in the secret containing Telegram token + tokenKey: "" + smtp: + enabled: true + host: ~ + port: ~ + username: ~ + password: ~ + tls: ~ + fromEmail: ~ + exporter: + enabled: false + authToken: ~ + twilio: + # Twilio account SID/username to allow OnCall to send SMSes and make phone calls + accountSid: "" + # Twilio password to allow OnCall to send SMSes and make calls + authToken: "" + # Number from which you will receive calls and SMS + # (NOTE: must be quoted, otherwise would be rendered as float value) + phoneNumber: "" + # SID of Twilio service for number verification. You can create a service in Twilio web interface. + # twilio.com -> verify -> create new service + verifySid: "" + # Twilio API key SID/username to allow OnCall to send SMSes and make phone calls + apiKeySid: "" + # Twilio API key secret/password to allow OnCall to send SMSes and make phone calls + apiKeySecret: "" + # Use existing secret for authToken, phoneNumber, verifySid, apiKeySid and apiKeySecret. + existingSecret: "" + # Twilio password to allow OnCall to send SMSes and make calls + # The key in the secret containing the auth token + authTokenKey: "" + # The key in the secret containing the phone number + phoneNumberKey: "" + # The key in the secret containing verify service sid + verifySidKey: "" + # The key in the secret containing api key sid + apiKeySidKey: "" + # The key in the secret containing the api key secret + apiKeySecretKey: "" + # Phone notifications limit (the only non-secret value). + # TODO: rename to phoneNotificationLimit + limitPhone: + +# Whether to run django database migrations automatically +migrate: + enabled: true + # TTL can be unset by setting ttlSecondsAfterFinished: "" + ttlSecondsAfterFinished: 20 + # use a helm hook to manage the migration job + useHook: false + annotations: {} + + ## Affinity for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + affinity: {} + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + nodeSelector: {} + + ## Tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + tolerations: [] + + # Extra containers which runs as sidecar + extraContainers: "" + # extraContainers: | + # - name: cloud-sql-proxy + # image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.1.2 + # args: + # - --private-ip + # - --port=5432 + # - example:europe-west3:grafana-oncall-db + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # Extra volume mounts for the main container + extraVolumeMounts: [] + # - mountPath: /mnt/postgres-tls + # name: postgres-tls + # - mountPath: /mnt/redis-tls + # name: redis-tls + + # Extra volumes for the pod + extraVolumes: [] + # - name: postgres-tls + # configMap: + # name: my-postgres-tls + # defaultMode: 0640 + # - name: redis-tls + # configMap: + # name: my-redis-tls + # defaultMode: 0640 + +# Sets environment variables with name capitalized and prefixed with UWSGI_, +# and dashes are substituted with underscores. +# see more: https://uwsgi-docs.readthedocs.io/en/latest/Configuration.html#environment-variables +# Set null to disable all UWSGI environment variables +uwsgi: + listen: 1024 + +# 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" + tls: + - hosts: + - "{{ .Values.base_url }}" + secretName: certificate-tls + # Extra paths to prepend to the host configuration. If using something + # like an ALB ingress controller, you may want to configure SSL redirects + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + ## Or for k8s > 1.19 + # - path: /* + # pathType: Prefix + # backend: + # service: + # name: ssl-redirect + # port: + # name: use-annotation + +# Whether to install ingress controller +ingress-nginx: + 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 + +database: + # can be either mysql or postgresql + type: mysql + +# 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 + existingSecret: + 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: + # Use an existing secret for the mysql password. + existingSecret: + # The key in the secret containing the mysql username + usernameKey: + # The key in the secret containing the mysql password + passwordKey: + # Extra options (see example below) + # Reference: https://pymysql.readthedocs.io/en/latest/modules/connections.html + options: + # options: >- + # ssl_verify_cert=true + # ssl_verify_identity=true + # ssl_ca=/mnt/mysql-tls/ca.crt + # ssl_cert=/mnt/mysql-tls/client.crt + # ssl_key=/mnt/mysql-tls/client.key + +# PostgreSQL is included into this release for the convenience. +# It is recommended to host it separately from this release +# Set postgresql.enabled = false and configure externalPostgresql +postgresql: + enabled: false + auth: + database: oncall + existingSecret: + +# Make sure to create the database with the following parameters: +# CREATE DATABASE oncall WITH ENCODING UTF8; +externalPostgresql: + host: + port: + db_name: + user: + password: + # Use an existing secret for the database password + existingSecret: + # The key in the secret containing the database password + passwordKey: + # Extra options (see example below) + # Reference: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS + options: + # options: >- + # sslmode=verify-full + # sslrootcert=/mnt/postgres-tls/ca.crt + # sslcert=/mnt/postgres-tls/client.crt + # sslkey=/mnt/postgres-tls/client.key + +# 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 + auth: + existingPasswordSecret: + +broker: + type: rabbitmq + +externalRabbitmq: + host: + port: + user: + password: + protocol: + vhost: + # Use an existing secret for the rabbitmq password + existingSecret: + # The key in the secret containing the rabbitmq password + passwordKey: "" + # The key in the secret containing the rabbitmq username + usernameKey: username + +# Redis is included into this release for the convenience. +# It is recommended to host it separately from this release +redis: + enabled: true + auth: + existingSecret: + +externalRedis: + protocol: + host: + port: + database: + username: + password: + # Use an existing secret for the redis password + existingSecret: + # The key in the secret containing the redis password + passwordKey: + + # SSL options + ssl_options: + enabled: false + # CA certificate + ca_certs: + # Client SSL certs + certfile: + keyfile: + # SSL verification mode: "cert_none" | "cert_optional" | "cert_required" + cert_reqs: + +# Grafana is included into this release for the convenience. +# It is recommended to host it separately from this release +grafana: + enabled: true + grafana.ini: + server: + domain: example.com + root_url: "%(protocol)s://%(domain)s/grafana" + serve_from_sub_path: true + persistence: + enabled: true + # Disable psp as PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ + rbac: + pspEnabled: false + plugins: + - grafana-oncall-app + +externalGrafana: + # Example: https://grafana.mydomain.com + url: + +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 + # runAsGroup: 2000 + # runAsUser: 1000 + +init: + securityContext: + {} + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + # privileged: false + # readOnlyRootFilesystem: true + # runAsGroup: 2000 + # runAsNonRoot: true + # runAsUser: 1000 + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +ui: + # this is intended to be used for local development. In short, it will spin up an additional container + # running the plugin frontend, such that hot reloading can be enabled + enabled: false + image: + repository: oncall/ui + tag: dev + # Additional env vars for the ui container + env: {} + +prometheus: + enabled: false + # extraScrapeConfigs: | + # - job_name: 'oncall-exporter' + # metrics_path: /metrics/ + # static_configs: + # - targets: + # - oncall-dev-engine.default.svc.cluster.local:8080 diff --git a/packages/apps/observability/dashboards.list b/packages/apps/observability/dashboards.list new file mode 100644 index 00000000..8ab12fd0 --- /dev/null +++ b/packages/apps/observability/dashboards.list @@ -0,0 +1,33 @@ +dotdc/k8s-views-global +dotdc/k8s-views-namespaces +dotdc/k8s-system-coredns +dotdc/k8s-views-pods +cache/nginx-vts-stats +victoria-metrics/vmalert +victoria-metrics/vmagent +victoria-metrics/victoriametrics-cluster +victoria-metrics/backupmanager +victoria-metrics/victoriametrics +victoria-metrics/operator +ingress/controller-detail +ingress/controllers +ingress/namespaces +ingress/vhosts +ingress/vhost-detail +ingress/namespace-detail +db/cloudnativepg +db/maria-db +db/redis +main/controller +main/namespaces +main/capacity-planning +main/ntp +main/namespace +main/pod +main/volumes +main/node +main/nodes +control-plane/control-plane-status +control-plane/deprecated-resources +control-plane/dns-coredns +control-plane/kube-etcd3 diff --git a/packages/apps/observability/releases.yaml b/packages/apps/observability/releases.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/apps/observability/templates/dashboards.yaml b/packages/apps/observability/templates/dashboards.yaml index 3a5df309..2d2293a8 100644 --- a/packages/apps/observability/templates/dashboards.yaml +++ b/packages/apps/observability/templates/dashboards.yaml @@ -1,27 +1,16 @@ -{{ range $path, $bytes := $.Files.Glob "grafana-dashboards/**.json" }} -{{ $parts := split "/" ( trimSuffix ".json" (trimPrefix "grafana-dashboards/" $path)) }} +{{- range (split "\n" (.Files.Get "dashboards.list")) }} +{{- $parts := split "/" . }} +{{- if eq (len $parts) 2 }} --- apiVersion: grafana.integreatly.org/v1beta1 kind: GrafanaDashboard metadata: name: {{ $parts._0 }}-{{ $parts._1 }} - namespace: {{ $.Release.Namespace }} spec: folder: {{ $parts._0 }} instanceSelector: matchLabels: - dashboards: "grafana" - {{- if regexMatch "flant-statusmap-panel|grafana-worldmap-panel" (toString $bytes) }} - plugins: - {{- if regexMatch "flant-statusmap-panel" (toString $bytes) }} - - name: flant-statusmap-panel - version: 0.5.1 - {{- end }} - {{- if regexMatch "grafana-worldmap-panel" (toString $bytes) }} - - name: grafana-worldmap-panel - version: 1.0.6 - {{- end }} - {{- end }} - json: |- - {{- toString $bytes | nindent 4 }} + dashboards: {{ $.Release.Name }}-grafana + url: http://cozystack.cozy-system.svc/dashboards/{{ . }}.json +{{- end }} {{- end }} diff --git a/packages/apps/observability/templates/grafana/secret.yaml b/packages/apps/observability/templates/grafana/secret.yaml index ce4f1d26..6e29741c 100644 --- a/packages/apps/observability/templates/grafana/secret.yaml +++ b/packages/apps/observability/templates/grafana/secret.yaml @@ -3,6 +3,12 @@ kind: Secret metadata: name: {{ .Release.Name }}-grafana-admin-password namespace: -stringData: - user: admin - password: asdasdASD +data: +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-grafana-admin-password" .Release.Name) }} + {{- if $existingSecret }} + user: {{ index $existingSecret.data "user" }} + password: {{ index $existingSecret.data "password" }} + {{- else }} + user: {{ b64enc "admin" }} + password: {{ randAlphaNum 32 | b64enc }} + {{- end }} diff --git a/packages/apps/observability/templates/vm/grafana-datasource.yaml b/packages/apps/observability/templates/vm/grafana-datasource.yaml index c6f88978..66903812 100644 --- a/packages/apps/observability/templates/vm/grafana-datasource.yaml +++ b/packages/apps/observability/templates/vm/grafana-datasource.yaml @@ -13,7 +13,7 @@ spec: name: {{ .name }} type: prometheus access: proxy - url: http://vmselect-vmcluster.{{ $.Release.Namespace }}.svc:8481/select/0/prometheus/ + url: http://vmselect-{{ $.Release.Name }}-{{ .name }}.{{ $.Release.Namespace }}.svc:8481/select/0/prometheus/ isDefault: {{ if $first }}true{{ $first = false }}{{ else }}false{{ end }} jsonData: 'tlsSkipVerify': true diff --git a/packages/apps/observability/templates/vm/vmalert-scrape.yaml b/packages/apps/observability/templates/vm/vmalert-scrape.yaml index b81d8251..333cbe4c 100644 --- a/packages/apps/observability/templates/vm/vmalert-scrape.yaml +++ b/packages/apps/observability/templates/vm/vmalert-scrape.yaml @@ -13,5 +13,5 @@ spec: selector: matchLabels: app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: vmalert + app.kubernetes.io/instance: {{ $.Release.Name }} app.kubernetes.io/name: vmalert diff --git a/packages/apps/observability/templates/vm/vmalert.yaml b/packages/apps/observability/templates/vm/vmalert.yaml index 41fe8f04..3ed61e6a 100644 --- a/packages/apps/observability/templates/vm/vmalert.yaml +++ b/packages/apps/observability/templates/vm/vmalert.yaml @@ -9,10 +9,10 @@ spec: extraArgs: remoteWrite.disablePathAppend: "true" notifiers: - - url: http://vmalertmanager-alertmanager.{{ .Release.Namespace }}.svc:9093 + - url: http://vmalertmanager-{{ $.Release.Name }}.{{ .Release.Namespace }}.svc:9093 remoteRead: - url: http://vmselect-vmcluster.{{ .Release.Namespace }}.svc:8481/select/0/prometheus + url: http://vmselect-{{ $.Release.Name }}-shortterm.{{ .Release.Namespace }}.svc:8481/select/0/prometheus remoteWrite: - url: http://vminsert-vmcluster.{{ .Release.Namespace }}.svc:8480/insert/0/prometheus/api/v1/write + url: http://vminsert-{{ $.Release.Name }}-shortterm.{{ .Release.Namespace }}.svc:8480/insert/0/prometheus/api/v1/write resources: {} selectAllByDefault: true diff --git a/packages/apps/observability/templates/vm/vmalertmanager.yaml b/packages/apps/observability/templates/vm/vmalertmanager.yaml index 9780b51e..c22ce56f 100644 --- a/packages/apps/observability/templates/vm/vmalertmanager.yaml +++ b/packages/apps/observability/templates/vm/vmalertmanager.yaml @@ -18,7 +18,7 @@ stringData: receivers: - name: 'webhook' webhook_configs: - - url: http://grafana-oncall-engine.{{ .Release.Namespace }}.svc:8080/integrations/v1/alertmanager/Kjb2NWxxSlgGtxz9F4ihovQBB/ + - url: http://{{ .Release.Name }}-oncall-engine.{{ .Release.Namespace }}.svc:8080/integrations/v1/alertmanager/Kjb2NWxxSlgGtxz9F4ihovQBB/ --- apiVersion: operator.victoriametrics.com/v1beta1 kind: VMAlertmanager diff --git a/packages/apps/observability/templates/vm/vmcluster-scrape.yaml b/packages/apps/observability/templates/vm/vmcluster-scrape.yaml index 2d4f17a3..4ce9607c 100644 --- a/packages/apps/observability/templates/vm/vmcluster-scrape.yaml +++ b/packages/apps/observability/templates/vm/vmcluster-scrape.yaml @@ -14,7 +14,7 @@ spec: selector: matchLabels: app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: vmcluster + app.kubernetes.io/instance: {{ $.Release.Name }}-{{ .name }} app.kubernetes.io/name: vminsert --- apiVersion: operator.victoriametrics.com/v1beta1 @@ -31,7 +31,7 @@ spec: selector: matchLabels: app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: vmcluster + app.kubernetes.io/instance: {{ $.Release.Name }}-{{ .name }} app.kubernetes.io/name: vmselect --- apiVersion: operator.victoriametrics.com/v1beta1 @@ -48,6 +48,6 @@ spec: selector: matchLabels: app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: vmcluster + app.kubernetes.io/instance: {{ $.Release.Name }}-{{ .name }} app.kubernetes.io/name: vmstorage {{- end }} diff --git a/packages/apps/observability/templates/vm/vmcluster.yaml b/packages/apps/observability/templates/vm/vmcluster.yaml index 4534cee4..69f7956d 100644 --- a/packages/apps/observability/templates/vm/vmcluster.yaml +++ b/packages/apps/observability/templates/vm/vmcluster.yaml @@ -14,7 +14,7 @@ spec: replicaCount: 2 extraArgs: search.maxUniqueTimeseries: "600000" - vmalert.proxyURL: http://vmalert.cozy-monitoring.svc:8080 + vmalert.proxyURL: http://vmalert-{{ $.Release.Name }}.{{ $.Release.Namespace }}.svc:8080 dedup.minScrapeInterval: "15s" cacheMountPath: /select-cache storage: diff --git a/packages/apps/observability/values.yaml b/packages/apps/observability/values.yaml index ff68e16a..a230d9d3 100644 --- a/packages/apps/observability/values.yaml +++ b/packages/apps/observability/values.yaml @@ -29,8 +29,8 @@ oncall: externalRedis: host: rfrm-grafana-oncall password: "" - #existingSecret: grafana-oncall-keydb - #passwordKey: password + existingSecret: grafana-oncall-keydb + passwordKey: password mariadb: enabled: false diff --git a/packages/system/monitoring/templates/vmagent.yaml b/packages/system/monitoring/templates/vmagent.yaml index fa67c6d7..2041387a 100644 --- a/packages/system/monitoring/templates/vmagent.yaml +++ b/packages/system/monitoring/templates/vmagent.yaml @@ -9,8 +9,8 @@ spec: extraArgs: promscrape.streamParse: "true" remoteWrite: - - url: http://vminsert-vmcluster.cozy-monitoring-hub.svc:8480/insert/0/prometheus/api/v1/write - - url: http://vminsert-vmcluster-longterm.cozy-monitoring-hub.svc:8480/longterm/insert/0/prometheus/api/v1/write + - url: http://vminsert-monitoring-system-shortterm.tenant-root.svc:8480/insert/0/prometheus/api/v1/write + - url: http://vminsert-monitoring-system-longterm.tenant-root.svc:8480/longterm/insert/0/prometheus/api/v1/write scrapeInterval: 30s selectAllByDefault: true additionalScrapeConfigs: diff --git a/packages/system/monitoring/templates/vmoperator-scrape.yaml b/packages/system/monitoring/templates/vmoperator-scrape.yaml index 3d7ede34..b6846001 100644 --- a/packages/system/monitoring/templates/vmoperator-scrape.yaml +++ b/packages/system/monitoring/templates/vmoperator-scrape.yaml @@ -3,7 +3,7 @@ apiVersion: operator.victoriametrics.com/v1beta1 kind: VMServiceScrape metadata: name: victoria-metrics-operator - namespace: cozy-monitoring + namespace: cozy-victoria-metrics-operator spec: endpoints: - path: /metrics @@ -13,5 +13,5 @@ spec: targetLabel: node selector: matchLabels: - app.kubernetes.io/instance: monitoring app.kubernetes.io/name: victoria-metrics-operator + app.kubernetes.io/instance: victoria-metrics-operator