diff --git a/charts/posthog/templates/ingress.yaml b/charts/posthog/templates/ingress.yaml index fd2a7dc24..363f5475b 100644 --- a/charts/posthog/templates/ingress.yaml +++ b/charts/posthog/templates/ingress.yaml @@ -124,12 +124,17 @@ spec: - pathType: Prefix path: "/s/" - backend: *INGESTION + backend: &SESSION_RECORDINGS + service: + name: {{ template "posthog.fullname" . }}-session-recordings + port: + number: {{ .Values.service.externalPort }} - pathType: Exact path: "/s" - backend: *INGESTION + backend: *SESSION_RECORDINGS {{- else }} + - pathType: ImplementationSpecific path: "/capture/*" backend: *INGESTION @@ -147,7 +152,11 @@ spec: backend: *INGESTION - pathType: ImplementationSpecific path: "/s/*" - backend: *INGESTION + backend: + service: + name: {{ template "posthog.fullname" . }}-session-recordings + port: + number: {{ .Values.service.externalPort }} {{- end }} {{- if .Values.ingress.tls }} {{ toYaml .Values.ingress.tls | indent 4 }} diff --git a/charts/posthog/templates/session-recordings-deployment.yaml b/charts/posthog/templates/session-recordings-deployment.yaml new file mode 100644 index 000000000..2fe39a767 --- /dev/null +++ b/charts/posthog/templates/session-recordings-deployment.yaml @@ -0,0 +1,167 @@ +{{- if .Values.sessionRecordings.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "posthog.fullname" . }}-session-recordings + labels: {{- include "_snippet-metadata-labels-common" . | nindent 4 }} + annotations: {{- include "_snippet-metadata-annotations-common" . | nindent 4 }} +spec: + selector: + matchLabels: + app: {{ template "posthog.fullname" . }} + release: "{{ .Release.Name }}" + role: session-recordings + {{- if not .Values.sessionRecordings.hpa.enabled }} + replicas: {{ .Values.sessionRecordings.replicacount }} + {{- end }} + template: + metadata: + annotations: + checksum/secrets.yaml: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }} + {{- if .Values.web.podAnnotations }} +{{ toYaml .Values.web.podAnnotations | indent 8 }} + {{- end }} + labels: + app: {{ template "posthog.fullname" . }} + release: "{{ .Release.Name }}" + role: session-recordings + {{- if (eq (default .Values.image.tag "none") "latest") }} + date: "{{ now | unixEpoch }}" + {{- end }} + {{- if .Values.web.podLabels }} +{{ toYaml .Values.web.podLabels | indent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: {{ include "snippet.web-deployments.terminationGracePeriodSeconds" . }} + serviceAccountName: {{ template "posthog.serviceAccountName" . }} + + {{- if .Values.web.affinity }} + affinity: +{{ toYaml .Values.web.affinity | indent 8 }} + {{- end }} + + {{- if .Values.web.nodeSelector }} + nodeSelector: +{{ toYaml .Values.web.nodeSelector | indent 8 }} + {{- end }} + + {{- if .Values.web.tolerations }} + tolerations: +{{ toYaml .Values.web.tolerations | indent 8 }} + {{- end }} + + {{- if .Values.web.schedulerName }} + schedulerName: "{{ .Values.web.schedulerName }}" + {{- end }} + + {{- if .Values.image.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.image.imagePullSecrets | indent 8 }} + {{- end }} + + # I do not know for sure if the old one has been used anywhere, so do both :( + {{- if .Values.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.image.pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} + + {{- if .Values.sessionRecordings.podSecurityContext.enabled }} + securityContext: {{- omit .Values.sessionRecordings.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + + containers: + - name: {{ .Chart.Name }}-session-recordings + image: {{ template "posthog.image.fullPath" . }} + command: + - ./bin/docker-server + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.service.internalPort }} + # Expose the port on which Prometheus /metrics endpoint resides + - containerPort: 8001 + env: + # Kafka env variables + {{- include "snippet.kafka-env" . | indent 8 }} + + # Object Storage env variables + {{- include "snippet.objectstorage-env" . | indent 8 }} + + # Redis env variables + {{- include "snippet.redis-env" . | indent 8 }} + + # statsd env variables + {{- include "snippet.statsd-env" . | indent 8 }} + + - name: DISABLE_SECURE_SSL_REDIRECT + value: '1' + - name: IS_BEHIND_PROXY + value: '1' + {{- if eq .Values.web.secureCookies false }} + - name: SECURE_COOKIES + value: '0' + {{- end }} + # PostHog app settings + {{- include "snippet.posthog-env" . | indent 8 }} + {{- include "snippet.posthog-sentry-env" . | indent 8 }} + - name: PRIMARY_DB + value: clickhouse + {{- include "snippet.postgresql-env" . | nindent 8 }} + {{- include "snippet.clickhouse-env" . | nindent 8 }} + {{- include "snippet.email-env" . | nindent 8 }} +{{- if .Values.env }} +{{ toYaml .Values.env | indent 8 }} +{{- end }} +{{- if .Values.sessionRecordings.env }} +{{ toYaml .Values.sessionRecordings.env | indent 8 }} +{{- else if .Values.web.env }} +{{ toYaml .Values.web.env | indent 8 }} +{{- end }} + + {{- include "snippet.web-deployments.lifecycle" . | nindent 8 }} + + livenessProbe: + httpGet: + path: /_livez + port: {{ .Values.service.internalPort }} + scheme: HTTP + failureThreshold: {{ .Values.web.livenessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.web.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.web.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.web.livenessProbe.successThreshold }} + timeoutSeconds: {{ .Values.web.livenessProbe.timeoutSeconds }} + readinessProbe: + httpGet: + # For readiness, we are using the events role as this should be the + # same. + path: /_readyz?role=events + port: {{ .Values.service.internalPort }} + scheme: HTTP + failureThreshold: {{ .Values.web.readinessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.web.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.web.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.web.readinessProbe.successThreshold }} + timeoutSeconds: {{ .Values.web.readinessProbe.timeoutSeconds }} + startupProbe: + httpGet: + # For startup, we want to make sure that everything is in place, + # including postgres. This does however mean we would not be able to + # deploy new releases when we have a postgres outage + path: /_readyz + port: {{ .Values.service.internalPort }} + scheme: HTTP + failureThreshold: {{ .Values.web.startupProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.web.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.web.startupProbe.periodSeconds }} + successThreshold: {{ .Values.web.startupProbe.successThreshold }} + timeoutSeconds: {{ .Values.web.startupProbe.timeoutSeconds }} + resources: +{{ toYaml .Values.sessionRecordings.resources | indent 12 }} + {{- if .Values.sessionRecordings.securityContext.enabled }} + securityContext: {{- omit .Values.sessionRecordings.securityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + initContainers: + {{- include "_snippet-initContainers-wait-for-service-dependencies" . | indent 8 }} + {{- include "_snippet-initContainers-wait-for-migrations" . | indent 8 }} +{{- end }} diff --git a/charts/posthog/templates/session-recordings-hpa.yaml b/charts/posthog/templates/session-recordings-hpa.yaml new file mode 100644 index 000000000..9eb8dd4a1 --- /dev/null +++ b/charts/posthog/templates/session-recordings-hpa.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.sessionRecordings.enabled .Values.sessionRecordings.hpa.enabled -}} +apiVersion: autoscaling/v2beta2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "posthog.fullname" . }}-session-recordings + labels: {{- include "_snippet-metadata-labels-common" . | nindent 4 }} +spec: + scaleTargetRef: + kind: Deployment + apiVersion: apps/v1 + name: {{ template "posthog.fullname" . }}-session-recordings + minReplicas: {{ .Values.sessionRecordings.hpa.minpods }} + maxReplicas: {{ .Values.sessionRecordings.hpa.maxpods }} + metrics: + {{- with .Values.sessionRecordings.hpa.cputhreshold }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ . }} + {{- end }} + behavior: + {{ toYaml .Values.sessionRecordings.hpa.behavior | nindent 4 }} +{{- end }} diff --git a/charts/posthog/templates/session-recordings-service.yaml b/charts/posthog/templates/session-recordings-service.yaml new file mode 100644 index 000000000..90bea27b0 --- /dev/null +++ b/charts/posthog/templates/session-recordings-service.yaml @@ -0,0 +1,32 @@ +{{- if .Values.sessionRecordings.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "posthog.fullname" . }}-session-recordings + annotations: {{- include "_snippet-metadata-annotations-common" . | nindent 4 }} + {{- range $key, $value := .Values.service.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: {{- include "_snippet-metadata-labels-common" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} +{{- if and (.Values.service.nodePort) (eq .Values.service.type "NodePort") }} + nodePort: {{ .Values.service.nodePort }} +{{- end }} +{{- if .Values.service.externalIPs }} + externalIPs: +{{ toYaml .Values.service.externalIPs | indent 4 }} +{{- end }} + selector: + app: {{ template "posthog.fullname" . }} + role: session-recordings + {{- with .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/posthog/tests/__snapshot__/ingress.yaml.snap b/charts/posthog/tests/__snapshot__/ingress.yaml.snap index ebdb74544..f8f468cd7 100644 --- a/charts/posthog/tests/__snapshot__/ingress.yaml.snap +++ b/charts/posthog/tests/__snapshot__/ingress.yaml.snap @@ -89,14 +89,14 @@ the "spec" path should match the snapshot when using default values: pathType: Exact - backend: service: - name: RELEASE-NAME-posthog-events + name: RELEASE-NAME-posthog-session-recordings port: number: 8000 path: /s/ pathType: Prefix - backend: service: - name: RELEASE-NAME-posthog-events + name: RELEASE-NAME-posthog-session-recordings port: number: 8000 path: /s diff --git a/charts/posthog/tests/session-recordings-deployment.yaml b/charts/posthog/tests/session-recordings-deployment.yaml new file mode 100644 index 000000000..14af97fb0 --- /dev/null +++ b/charts/posthog/tests/session-recordings-deployment.yaml @@ -0,0 +1,229 @@ +suite: PostHog session-recordings deployment definition +templates: + - templates/session-recordings-deployment.yaml + - templates/secrets.yaml + +tests: + - it: should be empty if session-recordings.enabled is set to false + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + sessionRecordings.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should have the correct apiVersion + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + asserts: + - hasDocuments: + count: 1 + - isAPIVersion: + of: apps/v1 + + - it: should be the correct kind + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Deployment + + - it: should have a pod securityContext + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + sessionRecordings.podSecurityContext.enabled: true + sessionRecordings.podSecurityContext.runAsUser: 1001 + sessionRecordings.podSecurityContext.fsGroup: 2000 + asserts: + - hasDocuments: + count: 1 + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 1001 + - equal: + path: spec.template.spec.securityContext.fsGroup + value: 2000 + + - it: should have a container securityContext + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + sessionRecordings.securityContext.enabled: true + sessionRecordings.securityContext.runAsUser: 1001 + sessionRecordings.securityContext.allowPrivilegeEscalation: false + asserts: + - hasDocuments: + count: 1 + - equal: + path: spec.template.spec.containers[0].securityContext.runAsUser + value: 1001 + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + + - it: should not have a pod securityContext + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + sessionRecordings.podSecurityContext.enabled: false + asserts: + - hasDocuments: + count: 1 + - isEmpty: + path: spec.template.spec.securityContext + value: 1001 + + - it: should not have a container securityContext + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + sessionRecordings.securityContext.enabled: false + asserts: + - hasDocuments: + count: 1 + - isEmpty: + path: spec.template.spec.containers[0].securityContext + + # NOTE: historically we have had the session-recordings pod use any resources specified + # for web. However, we would like to separate the connection and allow them to + # be set independently, while maintaining backwards compat. for installs that + # are setting the web resources. + - it: should set resources when specified + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local + sessionRecordings: + resources: + limits: + cpu: 1000m + memory: 16Gi + requests: + cpu: 4000m + memory: 16Gi + asserts: + - equal: + path: spec.template.spec.containers[0].resources + value: + limits: + cpu: 1000m + memory: 16Gi + requests: + cpu: 4000m + memory: 16Gi + + - it: should set resources when specified and override web resource settings + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local + sessionRecordings: + resources: + limits: + cpu: 1000m + memory: 16Gi + requests: + cpu: 4000m + memory: 16Gi + web: + resources: + limits: + cpu: 2000m + memory: 6Gi + requests: + cpu: 2000m + memory: 6Gi + asserts: + - equal: + path: spec.template.spec.containers[0].resources + value: + limits: + cpu: 1000m + memory: 16Gi + requests: + cpu: 4000m + memory: 16Gi + + - it: sets SENTRY_DSN env var + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + sentryDSN: sentry.endpoint + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SENTRY_DSN + value: sentry.endpoint + + - it: uses web.env if session-recordings.env is empty + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + env: + - name: FIRST + value: one + web: + env: + - name: SECOND + value: two + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: FIRST + value: one + - contains: + path: spec.template.spec.containers[0].env + content: + name: SECOND + value: two + + - it: ignores web.env if session-recordings.env is set + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local # TODO: remove once secrets.yaml will be fixed/removed + env: + - name: FIRST + value: one + web: + env: + - name: SECOND + value: two + sessionRecordings: + env: + - name: THIRD + value: three + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: FIRST + value: one + - contains: + path: spec.template.spec.containers[0].env + content: + name: THIRD + value: three + - notContains: + path: spec.template.spec.containers[0].env + content: + name: SECOND + value: two + + - it: allows setting imagePullSecrets + template: templates/session-recordings-deployment.yaml # TODO: remove once secrets.yaml will be fixed/removed + set: + cloud: local + image.pullSecrets: [secret] + sessionRecordings.enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: spec.template.spec.imagePullSecrets + value: [name: secret] diff --git a/charts/posthog/tests/session-recordings-hpa.yaml b/charts/posthog/tests/session-recordings-hpa.yaml new file mode 100644 index 000000000..088fa2170 --- /dev/null +++ b/charts/posthog/tests/session-recordings-hpa.yaml @@ -0,0 +1,81 @@ +suite: PostHog session-recordings HPA definition +templates: + - templates/session-recordings-hpa.yaml + +tests: + - it: should be empty if session-recordings.enabled and session-recordings.hpa.enabled are set to false + set: + sessionRecordings.enabled: false + sessionRecordings.hpa.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should be empty if session-recordings.enabled is true and session-recordings.hpa.enabled is set to false + set: + sessionRecordings.enabled: true + sessionRecordings.hpa.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should be not empty if session-recordings.enabled and session-recordings.hpa.enabled are set to true + set: + sessionRecordings.enabled: true + sessionRecordings.hpa.enabled: true + asserts: + - hasDocuments: + count: 1 + + - it: should have the correct apiVersion + set: + sessionRecordings.enabled: true + sessionRecordings.hpa.enabled: true + asserts: + - hasDocuments: + count: 1 + - isAPIVersion: + of: autoscaling/v2beta2 + + - it: should be the correct kind + set: + sessionRecordings.enabled: true + sessionRecordings.hpa.enabled: true + asserts: + - hasDocuments: + count: 1 + - isKind: + of: HorizontalPodAutoscaler + + - it: sets hpa spec + set: + sessionRecordings.enabled: true + sessionRecordings: + hpa: + enabled: true + minpods: 2 + maxpods: 10 + cputhreshold: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 3600 + asserts: + - equal: + path: spec + value: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: RELEASE-NAME-posthog-session-recordings + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleDown: + stabilizationWindowSeconds: 3600 diff --git a/charts/posthog/tests/session-recordings-service.yaml b/charts/posthog/tests/session-recordings-service.yaml new file mode 100644 index 000000000..080f2d6c7 --- /dev/null +++ b/charts/posthog/tests/session-recordings-service.yaml @@ -0,0 +1,30 @@ +suite: PostHog session-recordings service definition +templates: + - templates/session-recordings-service.yaml + +tests: + - it: should be empty if session-recordings.enabled is set to false + set: + sessionRecordings.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should be not empty if session-recordings.enabled is set to true + asserts: + - hasDocuments: + count: 1 + + - it: should have the correct apiVersion + asserts: + - hasDocuments: + count: 1 + - isAPIVersion: + of: v1 + + - it: should be the correct kind + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service diff --git a/charts/posthog/values.yaml b/charts/posthog/values.yaml index 262af8287..32d710de3 100644 --- a/charts/posthog/values.yaml +++ b/charts/posthog/values.yaml @@ -84,6 +84,40 @@ events: podSecurityContext: enabled: false +sessionRecordings: + # -- Whether to install the PostHog session recordings stack or not. + enabled: true + + # -- Count of session recordings pods to run. This setting is ignored if `session recordings.hpa.enabled` is set to `true`. + replicacount: 1 + + hpa: + # -- Whether to create a HorizontalPodAutoscaler for the session recordings stack. + enabled: false + # -- CPU threshold percent for the session recordings stack HorizontalPodAutoscaler. + cputhreshold: 60 + # -- Min pods for the session recordings stack HorizontalPodAutoscaler. + minpods: 1 + # -- Max pods for the session recordings stack HorizontalPodAutoscaler. + maxpods: 10 + # -- Set the HPA behavior. See + # https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ + # for configuration options + behavior: + + # -- Resource limits for session recordings service. + resources: {} + + # -- Additional env variables to inject into the session recordings stack, uses `web.env` if empty. + env: [] + + # -- Container security context for the session recordings stack HorizontalPodAutoscaler. + securityContext: + enabled: false + # -- Pod security context for the session recordings stack HorizontalPodAutoscaler. + podSecurityContext: + enabled: false + web: # -- Whether to install the PostHog web stack or not. enabled: true