From a915b3158825b534405bffa34642e787ab606490 Mon Sep 17 00:00:00 2001 From: git_admin Date: Sun, 26 Apr 2026 17:46:59 +0300 Subject: [PATCH] Initial chart import from local infrastructure/ --- Chart.yaml | 15 +++++++ templates/_helpers.tpl | 36 +++++++++++++++ templates/ingressroute.yaml | 21 +++++++++ templates/odoo-deployment.yaml | 69 +++++++++++++++++++++++++++++ templates/odoo-pvc.yaml | 11 +++++ templates/odoo-service.yaml | 15 +++++++ templates/postgres-secret.yaml | 11 +++++ templates/postgres-service.yaml | 16 +++++++ templates/postgres-statefulset.yaml | 58 ++++++++++++++++++++++++ values.yaml | 59 ++++++++++++++++++++++++ 10 files changed, 311 insertions(+) create mode 100644 Chart.yaml create mode 100644 templates/_helpers.tpl create mode 100644 templates/ingressroute.yaml create mode 100644 templates/odoo-deployment.yaml create mode 100644 templates/odoo-pvc.yaml create mode 100644 templates/odoo-service.yaml create mode 100644 templates/postgres-secret.yaml create mode 100644 templates/postgres-service.yaml create mode 100644 templates/postgres-statefulset.yaml create mode 100644 values.yaml diff --git a/Chart.yaml b/Chart.yaml new file mode 100644 index 0000000..c066bac --- /dev/null +++ b/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: instance-template-v3 +description: | + OdooSky v3 — single Helm chart for every Odoo instance. + Variation between instances is expressed via values.yaml only. + No chart variants. No string-templating in Tower. +type: application +version: 0.1.0 +appVersion: "1.0" +keywords: + - odoo + - erp + - odoosky +maintainers: + - name: OdooSky platform diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl new file mode 100644 index 0000000..1612db5 --- /dev/null +++ b/templates/_helpers.tpl @@ -0,0 +1,36 @@ +{{/* +Per-instance fully-qualified name. Used as the prefix for every K8s +object in the chart so instances in the same namespace can't collide. +*/}} +{{- define "instance.fullname" -}} +{{- .Values.instance.code | trunc 40 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Standard labels applied to every K8s object. Keeps `kubectl get -l` +queries by-instance trivial. +*/}} +{{- define "instance.labels" -}} +app.kubernetes.io/name: odoo +app.kubernetes.io/instance: {{ .Values.instance.code | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/version: {{ .Values.odoo.tag | quote }} +odoosky.io/component: instance +{{- end -}} + +{{/* +Postgres password. Looks up the existing Secret on upgrades; uses +.Values.postgres.password if set; otherwise generates a 32-char +random string on first install. The lookup ensures `helm upgrade` +does NOT silently rotate the password. +*/}} +{{- define "instance.pgPassword" -}} +{{- $existing := lookup "v1" "Secret" .Release.Namespace (printf "%s-pg" .Values.instance.code) -}} +{{- if and $existing $existing.data $existing.data.POSTGRES_PASSWORD -}} +{{- index $existing.data "POSTGRES_PASSWORD" | b64dec -}} +{{- else if .Values.postgres.password -}} +{{- .Values.postgres.password -}} +{{- else -}} +{{- randAlphaNum 32 -}} +{{- end -}} +{{- end -}} diff --git a/templates/ingressroute.yaml b/templates/ingressroute.yaml new file mode 100644 index 0000000..63cb029 --- /dev/null +++ b/templates/ingressroute.yaml @@ -0,0 +1,21 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: {{ include "instance.fullname" . }} + labels: + {{- include "instance.labels" . | nindent 4 }} +spec: + entryPoints: + - {{ .Values.ingress.entryPoint }} + routes: + - match: Host(`{{ .Values.instance.domain }}`) + kind: Rule + services: + - name: {{ include "instance.fullname" . }}-odoo + port: 8069 + tls: + # Shared wildcard cert for *.tenants.odoosky.org. The Secret is + # provisioned ONCE per cluster (see infrastructure/cluster/) and + # mirrored into the chart's release namespace by the cluster + # operator. Instances do NOT issue their own certs. + secretName: {{ .Values.ingress.tlsSecret }} diff --git a/templates/odoo-deployment.yaml b/templates/odoo-deployment.yaml new file mode 100644 index 0000000..8d18803 --- /dev/null +++ b/templates/odoo-deployment.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "instance.fullname" . }}-odoo + labels: + {{- include "instance.labels" . | nindent 4 }} +spec: + replicas: 1 + # ReadWriteOnce filestore volume + Odoo's session locks rule out + # rolling deploys with two pods overlapping. Recreate is safe and + # only causes brief downtime. + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/instance: {{ .Values.instance.code | quote }} + odoosky.io/role: odoo + template: + metadata: + labels: + {{- include "instance.labels" . | nindent 8 }} + odoosky.io/role: odoo + spec: + containers: + - name: odoo + image: "{{ .Values.odoo.image }}:{{ .Values.odoo.tag }}" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8069 + env: + - name: HOST + value: {{ include "instance.fullname" . }}-pg + - name: PORT + value: "5432" + - name: USER + valueFrom: + secretKeyRef: + name: {{ include "instance.fullname" . }}-pg + key: POSTGRES_USER + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "instance.fullname" . }}-pg + key: POSTGRES_PASSWORD + volumeMounts: + - name: filestore + mountPath: /var/lib/odoo + resources: + {{- toYaml .Values.odoo.resources | nindent 12 }} + # /web/login is the most stable health endpoint across Odoo + # 16/17/18/19 — /web/health is 17+. Use login HTTP 200 as + # readiness signal. + readinessProbe: + httpGet: + path: /web/login + port: 8069 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + tcpSocket: + port: 8069 + initialDelaySeconds: 60 + periodSeconds: 30 + volumes: + - name: filestore + persistentVolumeClaim: + claimName: {{ include "instance.fullname" . }}-odoo diff --git a/templates/odoo-pvc.yaml b/templates/odoo-pvc.yaml new file mode 100644 index 0000000..fc685e5 --- /dev/null +++ b/templates/odoo-pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "instance.fullname" . }}-odoo + labels: + {{- include "instance.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: {{ .Values.odoo.filestoreSize | quote }} diff --git a/templates/odoo-service.yaml b/templates/odoo-service.yaml new file mode 100644 index 0000000..b1fde56 --- /dev/null +++ b/templates/odoo-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "instance.fullname" . }}-odoo + labels: + {{- include "instance.labels" . | nindent 4 }} +spec: + selector: + app.kubernetes.io/instance: {{ .Values.instance.code | quote }} + odoosky.io/role: odoo + ports: + - name: http + port: 8069 + targetPort: 8069 + type: ClusterIP diff --git a/templates/postgres-secret.yaml b/templates/postgres-secret.yaml new file mode 100644 index 0000000..7c0277c --- /dev/null +++ b/templates/postgres-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "instance.fullname" . }}-pg + labels: + {{- include "instance.labels" . | nindent 4 }} +type: Opaque +stringData: + POSTGRES_USER: {{ .Values.postgres.user | quote }} + POSTGRES_PASSWORD: {{ include "instance.pgPassword" . | quote }} + POSTGRES_DB: {{ .Values.postgres.database | quote }} diff --git a/templates/postgres-service.yaml b/templates/postgres-service.yaml new file mode 100644 index 0000000..df3466d --- /dev/null +++ b/templates/postgres-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "instance.fullname" . }}-pg + labels: + {{- include "instance.labels" . | nindent 4 }} +spec: + selector: + app.kubernetes.io/instance: {{ .Values.instance.code | quote }} + odoosky.io/role: postgres + ports: + - name: pg + port: 5432 + targetPort: 5432 + # Headless service — required for StatefulSet stable DNS. + clusterIP: None diff --git a/templates/postgres-statefulset.yaml b/templates/postgres-statefulset.yaml new file mode 100644 index 0000000..162ea2f --- /dev/null +++ b/templates/postgres-statefulset.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "instance.fullname" . }}-pg + labels: + {{- include "instance.labels" . | nindent 4 }} +spec: + serviceName: {{ include "instance.fullname" . }}-pg + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/instance: {{ .Values.instance.code | quote }} + odoosky.io/role: postgres + template: + metadata: + labels: + {{- include "instance.labels" . | nindent 8 }} + odoosky.io/role: postgres + spec: + containers: + - name: postgres + image: "{{ .Values.postgres.image }}:{{ .Values.postgres.tag }}" + imagePullPolicy: IfNotPresent + ports: + - name: pg + containerPort: 5432 + envFrom: + - secretRef: + name: {{ include "instance.fullname" . }}-pg + env: + # PGDATA in a sub-dir so the mount-point itself isn't the + # data dir — postgres refuses to init when `lost+found` + # exists at the root of the volume. + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: pgdata + mountPath: /var/lib/postgresql/data + resources: + {{- toYaml .Values.postgres.resources | nindent 12 }} + readinessProbe: + exec: + command: ["sh", "-c", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"] + initialDelaySeconds: 10 + periodSeconds: 5 + livenessProbe: + exec: + command: ["sh", "-c", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"] + initialDelaySeconds: 30 + periodSeconds: 15 + volumeClaimTemplates: + - metadata: + name: pgdata + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: {{ .Values.postgres.storage | quote }} diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..06529ee --- /dev/null +++ b/values.yaml @@ -0,0 +1,59 @@ +# OdooSky v3 instance-template-v3 — default values. +# +# Per-tenant overlay repos override only the keys that differ from these +# defaults. Keep this file as the single source of truth for what an +# Odoo instance looks like by default; do not duplicate defaults in the +# overlay schema or in Tower-Go. + +instance: + # Short slug used in K8s object names + as the Helm release name. + # Must be DNS-safe (lowercase, no underscores, <= 40 chars). + code: demo + # The full HTTPS hostname this instance answers on. + # Tenants live under *.tenants.odoosky.org (covered by wildcard DNS A). + domain: demo.tenants.odoosky.org + +odoo: + image: odoo + tag: "18.0" + # Filestore PVC size (Odoo's /var/lib/odoo). + filestoreSize: 10Gi + resources: + requests: + memory: 512Mi + cpu: 250m + limits: + memory: 2Gi + cpu: "2" + +postgres: + image: postgres + tag: "16-alpine" + user: odoo + database: postgres + # If empty, the chart auto-generates on first install and re-reads + # the existing Secret on subsequent upgrades (lookup pattern). + password: "" + storage: 10Gi + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: "1" + +ingress: + # Traefik entrypoint name (set on the Traefik install in the + # `traefik` namespace). + entryPoint: websecure + # The pre-provisioned wildcard cert for *.tenants.odoosky.org — + # one Certificate resource issued ONCE in the chart's release + # namespace, then every instance's IngressRoute references the + # resulting Secret. Avoids Let's Encrypt's per-week certificate + # issuance ceiling (50/week/registered-domain) as we scale to + # many tenants. + # + # See infrastructure/cluster/wildcard-cert.yaml for the + # provisioning manifest. + tlsSecret: tenants-wildcard-tls