feat: project skeleton

- infra (k8s, kind, helm, docker) backbone is implemented
- security: implementation + unit tests are done
This commit is contained in:
2025-11-21 12:00:00 -05:00
commit fcce32bca9
46 changed files with 3468 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
apiVersion: v2
name: incidentops
description: A Helm chart for IncidentOps - Incident Management Platform
type: application
version: 0.1.0
appVersion: "0.1.0"
# Dependencies removed - using bundled postgres/redis templates instead
# To use external bitnami charts, uncomment and run helm dependency update:
# dependencies:
# - name: postgresql
# version: "16.x.x"
# repository: https://charts.bitnami.com/bitnami
# condition: bitnami.postgresql.enabled
# - name: redis
# version: "20.x.x"
# repository: https://charts.bitnami.com/bitnami
# condition: bitnami.redis.enabled
keywords:
- incidentops
- incident-management
- on-call
- alerting
maintainers:
- name: IncidentOps Team

View File

@@ -0,0 +1,33 @@
IncidentOps has been deployed!
{{- if .Values.ingress.enabled }}
Access the application at:
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}
{{- else }}
To access the application, run:
API:
kubectl port-forward svc/{{ include "incidentops.fullname" . }}-api {{ .Values.api.service.port }}:{{ .Values.api.service.port }} -n {{ .Release.Namespace }}
Then open: http://localhost:{{ .Values.api.service.port }}
Web:
kubectl port-forward svc/{{ include "incidentops.fullname" . }}-web {{ .Values.web.service.port }}:{{ .Values.web.service.port }} -n {{ .Release.Namespace }}
Then open: http://localhost:{{ .Values.web.service.port }}
{{- end }}
To check the status of your deployment:
kubectl get pods -n {{ .Release.Namespace }} -l "app.kubernetes.io/instance={{ .Release.Name }}"
{{- if .Values.migration.enabled }}
Database migrations will run automatically as a Helm hook.
Check migration status:
kubectl get jobs -n {{ .Release.Namespace }} -l "app.kubernetes.io/component=migration"
{{- end }}
For more information, visit the documentation.

View File

@@ -0,0 +1,218 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "incidentops.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "incidentops.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 "incidentops.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "incidentops.labels" -}}
helm.sh/chart: {{ include "incidentops.chart" . }}
{{ include "incidentops.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "incidentops.selectorLabels" -}}
app.kubernetes.io/name: {{ include "incidentops.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
API labels
*/}}
{{- define "incidentops.api.labels" -}}
{{ include "incidentops.labels" . }}
app.kubernetes.io/component: api
{{- end }}
{{- define "incidentops.api.selectorLabels" -}}
{{ include "incidentops.selectorLabels" . }}
app.kubernetes.io/component: api
{{- end }}
{{/*
Worker labels
*/}}
{{- define "incidentops.worker.labels" -}}
{{ include "incidentops.labels" . }}
app.kubernetes.io/component: worker
{{- end }}
{{- define "incidentops.worker.selectorLabels" -}}
{{ include "incidentops.selectorLabels" . }}
app.kubernetes.io/component: worker
{{- end }}
{{/*
Web labels
*/}}
{{- define "incidentops.web.labels" -}}
{{ include "incidentops.labels" . }}
app.kubernetes.io/component: web
{{- end }}
{{- define "incidentops.web.selectorLabels" -}}
{{ include "incidentops.selectorLabels" . }}
app.kubernetes.io/component: web
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "incidentops.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "incidentops.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
PostgreSQL host
*/}}
{{- define "incidentops.postgresql.host" -}}
{{- if .Values.postgresql.enabled }}
{{- printf "%s-postgresql" (include "incidentops.fullname" .) }}
{{- else }}
{{- .Values.externalDatabase.host }}
{{- end }}
{{- end }}
{{/*
PostgreSQL port
*/}}
{{- define "incidentops.postgresql.port" -}}
{{- if .Values.postgresql.enabled }}
{{- printf "5432" }}
{{- else }}
{{- .Values.externalDatabase.port | default "5432" }}
{{- end }}
{{- end }}
{{/*
Database URL
*/}}
{{- define "incidentops.databaseUrl" -}}
{{- $host := include "incidentops.postgresql.host" . }}
{{- $port := include "incidentops.postgresql.port" . }}
{{- if .Values.postgresql.enabled }}
{{- printf "postgresql://%s:%s@%s:%s/%s" .Values.postgresql.auth.username .Values.postgresql.auth.password $host $port .Values.postgresql.auth.database }}
{{- else }}
{{- printf "postgresql://%s:%s@%s:%s/%s" .Values.externalDatabase.user .Values.externalDatabase.password $host $port .Values.externalDatabase.database }}
{{- end }}
{{- end }}
{{/*
Redis host
*/}}
{{- define "incidentops.redis.host" -}}
{{- if .Values.redis.enabled }}
{{- printf "%s-redis" (include "incidentops.fullname" .) }}
{{- else }}
{{- .Values.externalRedis.host }}
{{- end }}
{{- end }}
{{/*
Redis URL
*/}}
{{- define "incidentops.redisUrl" -}}
{{- $host := include "incidentops.redis.host" . }}
{{- if .Values.redis.enabled }}
{{- printf "redis://%s:6379/0" $host }}
{{- else }}
{{- printf "redis://%s:%s/%s" $host (.Values.externalRedis.port | default "6379") (.Values.externalRedis.database | default "0") }}
{{- end }}
{{- end }}
{{/*
Celery broker URL
*/}}
{{- define "incidentops.celeryBrokerUrl" -}}
{{ include "incidentops.redisUrl" . }}
{{- end }}
{{/*
Celery result backend URL
*/}}
{{- define "incidentops.celeryResultBackend" -}}
{{- $host := include "incidentops.redis.host" . }}
{{- if .Values.redis.enabled }}
{{- printf "redis://%s:6379/1" $host }}
{{- else }}
{{- printf "redis://%s:%s/%s" $host (.Values.externalRedis.port | default "6379") (add (.Values.externalRedis.database | default 0) 1) }}
{{- end }}
{{- end }}
{{/*
API image
*/}}
{{- define "incidentops.api.image" -}}
{{- $registry := .Values.global.imageRegistry | default "" }}
{{- $repository := .Values.api.image.repository }}
{{- $tag := .Values.api.image.tag | default .Chart.AppVersion }}
{{- if $registry }}
{{- printf "%s/%s:%s" $registry $repository $tag }}
{{- else }}
{{- printf "%s:%s" $repository $tag }}
{{- end }}
{{- end }}
{{/*
Worker image
*/}}
{{- define "incidentops.worker.image" -}}
{{- $registry := .Values.global.imageRegistry | default "" }}
{{- $repository := .Values.worker.image.repository }}
{{- $tag := .Values.worker.image.tag | default .Chart.AppVersion }}
{{- if $registry }}
{{- printf "%s/%s:%s" $registry $repository $tag }}
{{- else }}
{{- printf "%s:%s" $repository $tag }}
{{- end }}
{{- end }}
{{/*
Web image
*/}}
{{- define "incidentops.web.image" -}}
{{- $registry := .Values.global.imageRegistry | default "" }}
{{- $repository := .Values.web.image.repository }}
{{- $tag := .Values.web.image.tag | default .Chart.AppVersion }}
{{- if $registry }}
{{- printf "%s/%s:%s" $registry $repository $tag }}
{{- else }}
{{- printf "%s:%s" $repository $tag }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,76 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "incidentops.fullname" . }}-api
labels:
{{- include "incidentops.api.labels" . | nindent 4 }}
spec:
{{- if not .Values.api.autoscaling.enabled }}
replicas: {{ .Values.api.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "incidentops.api.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.api.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "incidentops.api.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "incidentops.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: api
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "incidentops.api.image" . }}
imagePullPolicy: {{ .Values.api.image.pullPolicy }}
ports:
- name: http
containerPort: 8000
protocol: TCP
envFrom:
- configMapRef:
name: {{ include "incidentops.fullname" . }}-config
- secretRef:
name: {{ include "incidentops.fullname" . }}-secret
livenessProbe:
httpGet:
path: /v1/healthz
port: http
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /v1/readyz
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
{{- toYaml .Values.api.resources | nindent 12 }}
{{- with .Values.api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.api.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "incidentops.fullname" . }}-api
labels:
{{- include "incidentops.api.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "incidentops.fullname" . }}-api
minReplicas: {{ .Values.api.autoscaling.minReplicas }}
maxReplicas: {{ .Values.api.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.api.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

View File

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

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "incidentops.fullname" . }}-config
labels:
{{- include "incidentops.labels" . | nindent 4 }}
data:
JWT_ALGORITHM: {{ .Values.config.jwtAlgorithm | quote }}
ACCESS_TOKEN_EXPIRE_MINUTES: {{ .Values.config.accessTokenExpireMinutes | quote }}
REFRESH_TOKEN_EXPIRE_DAYS: {{ .Values.config.refreshTokenExpireDays | quote }}

View File

@@ -0,0 +1,51 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "incidentops.fullname" . }}
labels:
{{- include "incidentops.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: {{ include "incidentops.fullname" . }}-api
port:
number: {{ .Values.api.service.port }}
- path: /v1
pathType: Prefix
backend:
service:
name: {{ include "incidentops.fullname" . }}-api
port:
number: {{ .Values.api.service.port }}
- path: /
pathType: Prefix
backend:
service:
name: {{ include "incidentops.fullname" . }}-web
port:
number: {{ .Values.web.service.port }}
{{- end }}

View File

@@ -0,0 +1,49 @@
{{- if .Values.migration.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "incidentops.fullname" . }}-migrate
labels:
{{- include "incidentops.labels" . | nindent 4 }}
app.kubernetes.io/component: migration
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: {{ .Values.migration.backoffLimit }}
template:
metadata:
labels:
{{- include "incidentops.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: migration
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "incidentops.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
restartPolicy: Never
containers:
- name: migrate
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "incidentops.api.image" . }}
imagePullPolicy: {{ .Values.migration.image.pullPolicy }}
command:
- python
- migrations/migrate.py
- apply
envFrom:
- secretRef:
name: {{ include "incidentops.fullname" . }}-secret
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
{{- end }}

View File

@@ -0,0 +1,91 @@
{{- if .Values.postgresql.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "incidentops.fullname" . }}-postgresql
labels:
{{- include "incidentops.labels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
spec:
serviceName: {{ include "incidentops.fullname" . }}-postgresql
replicas: 1
selector:
matchLabels:
{{- include "incidentops.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: postgresql
template:
metadata:
labels:
{{- include "incidentops.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: postgresql
spec:
containers:
- name: postgresql
image: "{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}"
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
ports:
- name: postgresql
containerPort: 5432
protocol: TCP
env:
- name: POSTGRES_USER
value: {{ .Values.postgresql.auth.username | quote }}
- name: POSTGRES_PASSWORD
value: {{ .Values.postgresql.auth.password | quote }}
- name: POSTGRES_DB
value: {{ .Values.postgresql.auth.database | quote }}
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command:
- pg_isready
- -U
- {{ .Values.postgresql.auth.username }}
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
exec:
command:
- pg_isready
- -U
- {{ .Values.postgresql.auth.username }}
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.postgresql.persistence.size }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "incidentops.fullname" . }}-postgresql
labels:
{{- include "incidentops.labels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: postgresql
protocol: TCP
name: postgresql
selector:
{{- include "incidentops.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
{{- end }}

View File

@@ -0,0 +1,80 @@
{{- if .Values.redis.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "incidentops.fullname" . }}-redis
labels:
{{- include "incidentops.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
serviceName: {{ include "incidentops.fullname" . }}-redis
replicas: 1
selector:
matchLabels:
{{- include "incidentops.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: redis
template:
metadata:
labels:
{{- include "incidentops.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: redis
spec:
containers:
- name: redis
image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}"
imagePullPolicy: {{ .Values.redis.image.pullPolicy }}
ports:
- name: redis
containerPort: 6379
protocol: TCP
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
resources:
{{- toYaml .Values.redis.resources | nindent 12 }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.redis.persistence.size }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "incidentops.fullname" . }}-redis
labels:
{{- include "incidentops.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: redis
protocol: TCP
name: redis
selector:
{{- include "incidentops.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: redis
{{- end }}

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "incidentops.fullname" . }}-secret
labels:
{{- include "incidentops.labels" . | nindent 4 }}
type: Opaque
stringData:
DATABASE_URL: {{ include "incidentops.databaseUrl" . | quote }}
REDIS_URL: {{ include "incidentops.redisUrl" . | quote }}
CELERY_BROKER_URL: {{ include "incidentops.celeryBrokerUrl" . | quote }}
CELERY_RESULT_BACKEND: {{ include "incidentops.celeryResultBackend" . | quote }}
JWT_SECRET_KEY: {{ .Values.secrets.jwtSecretKey | quote }}

View File

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

View File

@@ -0,0 +1,72 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "incidentops.fullname" . }}-web
labels:
{{- include "incidentops.web.labels" . | nindent 4 }}
spec:
{{- if not .Values.web.autoscaling.enabled }}
replicas: {{ .Values.web.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "incidentops.web.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.web.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "incidentops.web.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "incidentops.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: web
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "incidentops.web.image" . }}
imagePullPolicy: {{ .Values.web.image.pullPolicy }}
ports:
- name: http
containerPort: 3000
protocol: TCP
env:
- name: NEXT_PUBLIC_API_URL
value: "http://{{ include "incidentops.fullname" . }}-api:{{ .Values.api.service.port }}"
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
{{- toYaml .Values.web.resources | nindent 12 }}
{{- with .Values.web.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.web.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.web.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.web.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "incidentops.fullname" . }}-web
labels:
{{- include "incidentops.web.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "incidentops.fullname" . }}-web
minReplicas: {{ .Values.web.autoscaling.minReplicas }}
maxReplicas: {{ .Values.web.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.web.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

View File

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

View File

@@ -0,0 +1,79 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "incidentops.fullname" . }}-worker
labels:
{{- include "incidentops.worker.labels" . | nindent 4 }}
spec:
{{- if not .Values.worker.autoscaling.enabled }}
replicas: {{ .Values.worker.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "incidentops.worker.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.worker.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "incidentops.worker.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "incidentops.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: worker
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "incidentops.worker.image" . }}
imagePullPolicy: {{ .Values.worker.image.pullPolicy }}
command:
- celery
- -A
- worker.celery_app
- worker
- --loglevel=info
- -Q
- {{ .Values.worker.queues }}
- --concurrency={{ .Values.worker.concurrency }}
envFrom:
- configMapRef:
name: {{ include "incidentops.fullname" . }}-config
- secretRef:
name: {{ include "incidentops.fullname" . }}-secret
livenessProbe:
exec:
command:
- celery
- -A
- worker.celery_app
- inspect
- ping
- -d
- celery@$HOSTNAME
initialDelaySeconds: 30
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 3
resources:
{{- toYaml .Values.worker.resources | nindent 12 }}
{{- with .Values.worker.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.worker.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "incidentops.fullname" . }}-worker
labels:
{{- include "incidentops.worker.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "incidentops.fullname" . }}-worker
minReplicas: {{ .Values.worker.autoscaling.minReplicas }}
maxReplicas: {{ .Values.worker.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.worker.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

View File

@@ -0,0 +1,82 @@
# Production values for incidentops
# Use external secrets management in production
api:
replicaCount: 3
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
worker:
replicaCount: 3
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
concurrency: 8
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
web:
replicaCount: 3
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
cert-manager.io/cluster-issuer: letsencrypt-prod
host: incidentops.example.com
tls:
- secretName: incidentops-tls
hosts:
- incidentops.example.com
postgresql:
persistence:
size: 50Gi
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
redis:
persistence:
size: 10Gi
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi

View File

@@ -0,0 +1,169 @@
# Default values for incidentops
# Global settings
global:
imageRegistry: ""
imagePullSecrets: []
# API Service
api:
replicaCount: 2
image:
repository: incidentops/api
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8000
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}
# Worker Service (Celery)
worker:
replicaCount: 2
image:
repository: incidentops/worker
tag: latest
pullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
queues: "critical,default,low"
concurrency: 4
podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}
# Web Frontend (Next.js)
web:
replicaCount: 2
image:
repository: incidentops/web
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 3000
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}
# Ingress configuration
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
host: incidentops.local
tls: []
# - secretName: incidentops-tls
# hosts:
# - incidentops.local
# Database migration job
migration:
enabled: true
image:
repository: incidentops/api
tag: latest
pullPolicy: IfNotPresent
backoffLimit: 3
# Application configuration
config:
jwtAlgorithm: HS256
accessTokenExpireMinutes: 30
refreshTokenExpireDays: 30
# Secrets (use external secrets in production)
secrets:
jwtSecretKey: "change-me-in-production"
# PostgreSQL configuration (using official postgres image)
postgresql:
enabled: true
image:
repository: postgres
tag: "16-alpine"
pullPolicy: IfNotPresent
auth:
username: incidentops
password: incidentops
database: incidentops
persistence:
size: 8Gi
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# Redis configuration (using official redis image)
redis:
enabled: true
image:
repository: redis
tag: "7-alpine"
pullPolicy: IfNotPresent
persistence:
size: 2Gi
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# Service Account
serviceAccount:
create: true
annotations: {}
name: ""
# Pod Security Context
podSecurityContext:
fsGroup: 1000
securityContext:
runAsNonRoot: true
runAsUser: 1000