PgAdmin4 k8s helm chart initialization

pull/9122/head
Koren Peretz 2025-08-31 17:27:56 +03:00
parent e09af0a0e9
commit 77459ed3d4
14 changed files with 559 additions and 0 deletions

10
helm/Chart.yaml Normal file

File diff suppressed because one or more lines are too long

32
helm/README.md Normal file
View File

@ -0,0 +1,32 @@
# PgAdmin4 K8S Helm Chart
Its been a struggle to deploy pgadmin4 container on different restricted k8s distributions for example openshift, gke or vanilla k8s pod security standards.
This helm chart follows best security measures and practices and compatible with all different security contexts and restrictions.
Further explanation about the security implementation can be read here: https://korenp1.github.io
It is known that not all yaml fields are customizable or tpl'able, open for contributions.
### Installation Example:
`helm install mypgadmin4 oci://docker.io/dpage/pgadmin4-helm --set ingress.enabled=true`
### Important Values
| Value | Description | Default |
| --------- | ----------- | ------- |
| `containerPort` | Internal PgAdmin4 Port | `8080` |
| `image.registry` | Image registry | `"docker.io"` |
| `image.repository` | Image Repository | `"dpage/pgadmin4"` |
| `image.tag` | Image tag (If empty, will use .Chart.AppVersion) | `""` |
| `auth.email` | Admin Email | `"admin@pgadmin.org"` |
| `auth.password` | Admin password (If both auth.password and auth.existingSecret are empty, the password will be randomly generated) | `""` |
| `auth.existingSecret` | Existing secret name for admin password (If both auth.password and auth.existingSecret are empty, the password will be randomly generated) | `""` |
| `extraEnvVars` | Extra environment variables | `[]` |
| `serverDefinitions.enabled` | Do predefined server definitions should be created | `false` |
| `serverDefinitions.servers` | Server definitions to load | `{}` |
| `preferences.enabled` | Do preferences should be initialized | `false` |
| `preferences.data` | Preferences to load | `{}` |
| `resources.*` | Allocated requests and limits resources | `{"requests": {...}, "limits": {...}}` |
| `persistence.enabled` | PVC resource creation | `false` |
| `service.type` | Service type | `"ClusterIP"` |
| `service.loadBalancerIP` | Load balancer IP (Only if service.type is LoadBalancer) | `""` |
| `ingress.enabled` | Ingress resource creation | `false` |
| `ingress.hostname` | Ingress resource hostname | `"pgadmin4.local"` |

11
helm/templates/NOTES.txt Normal file
View File

@ -0,0 +1,11 @@
Thanks for using PgAdmin4 Helm Chart :)
Image: {{ template "pgadmin4.image" . }}
Credentials:
Email: {{ .Values.auth.email }}
Password: {{ printf "$(kubectl get secret %s -n %s -o jsonpath='{.data.%s}' | base64 -d)" (include "pgadmin4.fullname" .) .Release.Namespace .Values.auth.passwordKey }}
Ingress: {{ ternary (tpl .Values.ingress.hostname .) "DISABLED" .Values.ingress.enabled }}
GOOD LUCK!

View File

@ -0,0 +1,25 @@
{{- define "pgadmin4.fullname" -}}
{{- tpl .Values.fullname . -}}
{{- end -}}
{{- define "pgadmin4.image" -}}
{{- printf "%s/%s:%s" (default .Values.image.registry .Values.global.imageRegistry) .Values.image.repository (default .Chart.AppVersion .Values.image.tag | toString) -}}
{{- end -}}
{{- define "pgadmin4.serviceAccountName" -}}
{{- default (ternary (include "pgadmin4.fullname" .) "default" .Values.serviceAccount.create) .Values.serviceAccount.name -}}
{{- end -}}
{{- define "renderSecurityContext" -}}
{{- $securityContext := omit .securityContext "enabled" -}}
{{- if or (eq .context.Values.global.compatibility.openshift.adaptSecurityContext "force") (and (eq .context.Values.global.compatibility.openshift.adaptSecurityContext "auto") (ternary "true" "" (.context.Capabilities.APIVersions.Has "security.openshift.io/v1"))) -}}
{{- $securityContext = omit $securityContext "fsGroup" "runAsUser" "runAsGroup" -}}
{{- if not $securityContext.seLinuxOptions }}
{{- $securityContext = omit $securityContext "seLinuxOptions" -}}
{{- end -}}
{{- end -}}
{{- if $securityContext.privileged }}
{{- $securityContext = omit $securityContext "capabilities" -}}
{{- end -}}
{{- $securityContext | toYaml -}}
{{- end -}}

View File

@ -0,0 +1,14 @@
{{- if .Values.preferences.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "pgadmin4.fullname" . }}-preferences
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- with .Values.commonAnnotations }}
annotations: {{ . | toYaml | nindent 4 }}
{{- end }}
data:
preferences.json: | {{ dict "preferences" .Values.preferences.data | toPrettyJson | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,14 @@
{{- if .Values.serverDefinitions.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "pgadmin4.fullname" . }}-server-definitions
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- with .Values.commonAnnotations }}
annotations: {{ . | toYaml | nindent 4 }}
{{- end }}
data:
servers.json: | {{ tpl (dict "Servers" .Values.serverDefinitions.servers | toYaml) . | fromYaml | toPrettyJson | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,212 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "pgadmin4.fullname" . }}
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- with .Values.commonAnnotations }}
annotations: {{ . | toYaml | nindent 4 }}
{{- end }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ default "pgadmin4" .Values.commonLabels.app }}
{{- with omit .Values.commonLabels "app" }}
{{- . | toYaml | nindent 6 }}
{{- end }}
template:
metadata:
labels:
app: {{ default "pgadmin4" .Values.commonLabels.app }}
{{- with omit .Values.commonLabels "app" }}
{{- . | toYaml | nindent 8 }}
{{- end }}
{{- if or (not (empty .Values.commonAnnotations)) (not .Values.existingSecret) .Values.preferences.enabled .Values.serverDefinitions.enabled }}
annotations:
{{- with .Values.commonAnnotations }}
{{- . | toYaml | nindent 8 }}
{{- end }}
{{- if not .Values.existingSecret }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.preferences.enabled }}
checksum/configmap-preferences: {{ include (print $.Template.BasePath "/configmap-preferences.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.serverDefinitions.enabled }}
checksum/configmap-server-definitions: {{ include (print $.Template.BasePath "/configmap-serverDefinitions.yaml") . | sha256sum }}
{{- end }}
{{- end }}
spec:
{{- if or .Values.global.imagePullSecrets .Values.image.pullSecrets }}
imagePullSecrets: {{- concat .Values.global.imagePullSecrets .Values.image.pullSecrets | toYaml | nindent 8 }}
{{- end }}
serviceAccountName: {{ template "pgadmin4.serviceAccountName" . }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
{{- if .Values.podSecurityContext.enabled }}
securityContext: {{- include "renderSecurityContext" (dict "securityContext" .Values.podSecurityContext "context" .) | nindent 8 }}
{{- end }}
volumes:
- name: empty-dir
emptyDir: {}
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ template "pgadmin4.fullname" . }}
{{- end }}
{{- if .Values.serverDefinitions.enabled }}
- name: server-definitions
configMap:
name: {{ template "pgadmin4.fullname" . }}-server-definitions
items:
- key: servers.json
path: servers.json
{{- end }}
{{- if .Values.preferences.enabled }}
- name: preferences
configMap:
name: {{ template "pgadmin4.fullname" . }}-preferences
items:
- key: preferences.json
path: preferences.json
{{- end }}
{{- with .Values.extraVolumes }}
{{- . | toYaml | nindent 8 }}
{{- end }}
containers:
- name: pgadmin4
{{- if .Values.containerSecurityContext.enabled }}
securityContext: {{- include "renderSecurityContext" (dict "securityContext" .Values.containerSecurityContext "context" .) | nindent 12 }}
{{- end }}
image: {{ template "pgadmin4.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
protocol: TCP
containerPort: {{ .Values.containerPort }}
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
env:
- name: PGADMIN_DEFAULT_EMAIL
value: {{ .Values.auth.email }}
- name: PGADMIN_DEFAULT_PASSWORD
valueFrom:
secretKeyRef:
name: {{ default (include "pgadmin4.fullname" .) .Values.auth.existingSecret }}
key: {{ .Values.auth.passwordKey }}
- name: PGADMIN_LISTEN_PORT
value: {{ .Values.containerPort | quote }}
- name: PGADMIN_DISABLE_POSTFIX
value: {{ .Values.disablePostfix | quote }}
{{- with .Values.extraEnvVars }}
{{- tpl (. | toYaml) $ | nindent 14 }}
{{- end }}
volumeMounts:
- mountPath: /var/lib/pgadmin
{{- if .Values.persistence.enabled }}
name: data
{{- else }}
name: empty-dir
subPath: data
{{- end }}
- name: empty-dir
mountPath: /pgadmin4/config_distro.py
subPath: config_distro.py
- name: empty-dir
mountPath: /usr/bin/python3
subPath: python3
- name: empty-dir
mountPath: /tmp
subPath: tmp
- name: empty-dir
mountPath: /var/log/pgadmin
subPath: logs
{{- if .Values.serverDefinitions.enabled }}
- name: server-definitions
mountPath: /pgadmin4/servers.json
subPath: servers.json
{{- end }}
{{- if .Values.preferences.enabled }}
- name: preferences
mountPath: /pgadmin4/preferences.json
subPath: preferences.json
{{- end }}
{{- with .Values.extraVolumes }}
{{- . | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.startupProbe.enabled }}
startupProbe: {{- omit .Values.startupProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe: {{- omit .Values.readinessProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.livenessProbe.enabled }}
livenessProbe: {{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
initContainers:
- name: modify-config-distro-py-permissions
image: {{ template "pgadmin4.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["sh", "-x", "-c"]
args: ['cp /pgadmin4/config_distro.py . && chmod 777 config_distro.py']
workingDir: /emptyDir
volumeMounts:
- name: empty-dir
mountPath: /emptyDir
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 50m
memory: 64Mi
securityContext:
seLinuxOptions: {}
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
privileged: false
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
windowsOptions:
hostProcess: false
- name: unset-python3-cli-net-cap
image: {{ template "pgadmin4.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["sh", "-x", "-c"]
args: ['ls /usr/bin/python3.* | sort -V -r | head -n 1 | xargs -I {} cp {} python3']
workingDir: /emptyDir
volumeMounts:
- name: empty-dir
mountPath: /emptyDir
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 50m
memory: 64Mi
securityContext:
seLinuxOptions: {}
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
privileged: false
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
windowsOptions:
hostProcess: false

View File

@ -0,0 +1,4 @@
{{- range .Values.extraDeploy }}
---
{{ tpl (. | toYaml) $ }}
{{- end }}

View File

@ -0,0 +1,24 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ template "pgadmin4.fullname" . }}
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- if or .Values.ingress.annotations .Values.commonAnnotations }}
annotations: {{ merge .Values.ingress.annotations .Values.commonAnnotations | toYaml | nindent 4 }}
{{- end }}
spec:
rules:
- host: {{ tpl .Values.ingress.hostname . }}
http:
paths:
- backend:
service:
name: {{ template "pgadmin4.fullname" . }}
port:
name: http
path: /
pathType: Prefix
{{- end }}

20
helm/templates/pvc.yaml Normal file
View File

@ -0,0 +1,20 @@
{{- if .Values.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ template "pgadmin4.fullname" . }}
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- with .Values.commonAnnotations }}
annotations: {{ . | toYaml | nindent 4 }}
{{- end }}
spec:
{{- if or .Values.global.defaultStorageClass .Values.persistence.storageClass }}
storageClassName: {{ default .Values.global.defaultStorageClass .Values.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
accessModes: {{ .Values.persistence.accessModes }}
{{- end }}

View File

@ -0,0 +1,15 @@
{{- if empty .Values.auth.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ template "pgadmin4.fullname" . }}
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- with .Values.commonAnnotations }}
annotations: {{ . | toYaml | nindent 4 }}
{{- end }}
type: Opaque
stringData:
{{ .Values.auth.passwordKey }}: {{ coalesce .Values.auth.password (dig "data" .Values.auth.passwordKey "" (lookup "v1" "Secret" .Release.Namespace (include "pgadmin4.fullname" .)) | b64dec) (randAlphaNum 12) | quote }}
{{- end }}

View File

@ -0,0 +1,25 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "pgadmin4.fullname" . }}
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- if or .Values.service.annotations .Values.commonAnnotations }}
annotations: {{ merge .Values.service.annotations .Values.commonAnnotations | toYaml | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
{{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
selector:
app: {{ default "pgadmin4" .Values.commonLabels.app }}
{{- with omit .Values.commonLabels "app" }}
{{- . | toYaml | nindent 4 }}
{{- end }}
ports:
- name: http
protocol: TCP
targetPort: http
port: {{ .Values.service.port }}

View File

@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "pgadmin4.serviceAccountName" . }}
{{- with .Values.commonLabels }}
labels: {{ . | toYaml | nindent 4 }}
{{- end }}
{{- with .Values.commonAnnotations }}
annotations: {{ . | toYaml | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
{{- end }}

140
helm/values.yaml Normal file
View File

@ -0,0 +1,140 @@
global:
imageRegistry: ""
defaultStorageClass: ""
imagePullSecrets: []
compatibility:
openshift:
adaptSecurityContext: auto
fullname: pgadmin4
image:
registry: docker.io
repository: dpage/pgadmin4
tag: ""
pullPolicy: IfNotPresent
pullSecrets: []
commonLabels:
app: pgadmin4
commonAnnotations: {}
extraDeploy: []
replicas: 1
containerPort: 8080
disablePostfix: true
auth:
email: admin@pgadmin.org
password: ""
existingSecret: ""
passwordKey: password
extraEnvVars: []
# - name: PGADMIN_CONFIG_LOGIN_BANNER
# value: '"Welcome to pgAdmin4 :)"'
extraVolumes: []
extraVolumeMounts: []
serverDefinitions:
enabled: false
servers: {}
# "1":
# Name: Minimally Defined Server
# Group: Servers
# Port: 5432
# Username: postgres
# Host: myHost
# MaintenanceDB: postgres
# ConnectionParameters:
# sslmode: prefer
# connect_timeout: 10
preferences:
enabled: false
data: {}
# misc:user_interface:theme: dark
# file_manager:options:file_upload_size: 500
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 250m
memory: 512Mi
persistence:
enabled: false
size: 1Gi
storageClass: ""
accessModes: ["ReadWriteMany"]
service:
type: ClusterIP
port: 80
annotations: {}
loadBalancerIP: ""
serviceAccount:
create: true
name: ""
automountServiceAccountToken: false
ingress:
enabled: false
hostname: pgadmin4.local
annotations: {}
startupProbe:
enabled: false
httpGet:
path: /misc/ping
port: http
initialDelaySeconds: null
periodSeconds: null
timeoutSeconds: null
failureThreshold: null
readinessProbe:
enabled: true
httpGet:
path: /misc/ping
port: http
initialDelaySeconds: null
periodSeconds: null
timeoutSeconds: null
failureThreshold: null
livenessProbe:
enabled: false
httpGet:
path: /misc/ping
port: http
initialDelaySeconds: null
periodSeconds: null
timeoutSeconds: null
failureThreshold: null
podSecurityContext:
enabled: true
fsGroupChangePolicy: Always
sysctls: []
supplementalGroups: []
fsGroup: 1001
containerSecurityContext:
enabled: true
seLinuxOptions: {}
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
privileged: false
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
windowsOptions:
hostProcess: false