- Adds odoo.{workers,limitTime*,limitMemory*,maxCronThreads,serverWideModules,dbFilter,extraArgs} knobs
- New instance.odooArgs helper renders flags only when set
- Empty odoo block byte-identical to previous chart output
489 lines
26 KiB
YAML
489 lines
26 KiB
YAML
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
|
|
annotations:
|
|
# Bumping this hash whenever the addons list changes forces
|
|
# Helm/ArgoCD to roll the Deployment, which re-runs the init
|
|
# containers and re-materializes the shared volume from the
|
|
# current image set. Without this, changing only `addons:` in
|
|
# values.yaml would leave the existing pod alone.
|
|
odoosky.io/addons-hash: {{ .Values.addons | toJson | sha256sum | trunc 16 }}
|
|
spec:
|
|
{{- with .Values.imageMirror.pullSecret }}
|
|
# Air-gap support (B.10): when imageMirror.pullSecret is set,
|
|
# K8s authenticates against the mirror with this Secret to pull
|
|
# the upstream Odoo image. Default empty = anonymous (Docker
|
|
# Hub library images need no auth).
|
|
imagePullSecrets:
|
|
- name: {{ . }}
|
|
{{- end }}
|
|
# fsGroup=101 makes the kubelet recursively chgrp the filestore
|
|
# PVC's root inode to gid=101 on attach. The Odoo user is in
|
|
# group 101 (`odoo`) so it inherits group access to the volume.
|
|
#
|
|
# NOTE: fsGroup ONLY changes group, never owner UID. If a helper
|
|
# job (Tower's checkpoint, spawn-seed, import) ever wrote files
|
|
# as a different UID, fsGroup wouldn't fix that. The
|
|
# filestore-chown initContainer below is what closes the gap —
|
|
# it's the single source of truth for "files in /var/lib/odoo
|
|
# are owned by 100:101 with mode g+rwX before Odoo runs."
|
|
securityContext:
|
|
fsGroup: 101
|
|
fsGroupChangePolicy: OnRootMismatch
|
|
# Bootstrap initContainers always run; addon initContainers only
|
|
# run when there are addons. We always need filestore-chown +
|
|
# db-init to ensure the filestore is writable + the tenant DB
|
|
# exists + base module is initialized before Odoo's main process
|
|
# starts serving — without these the operator would either hit
|
|
# Permission denied on first attachment write (filestore-chown
|
|
# fixes that) or have to click through Odoo's setup wizard
|
|
# manually for every new instance (db-init fixes that).
|
|
initContainers:
|
|
# filestore-chown: load-bearing self-heal. Runs as root, chowns
|
|
# /var/lib/odoo to 100:101 (the Odoo runtime user inside the
|
|
# official Odoo image: uid 100, gid 101) and chmods g+rwX.
|
|
# Idempotent — runs on every pod boot.
|
|
#
|
|
# Why this exists: Tower's clone helpers (checkpoint Job,
|
|
# spawn-seed Job, import Job) used to chown to 101:101 (uid
|
|
# 101 doesn't even exist as a user — that's the gid). Files
|
|
# ended up owned by a non-existent UID, group=odoo. mode 0755
|
|
# gave the running Odoo (uid 100) only group r-x — install
|
|
# operations failed with "Permission denied: mkdir
|
|
# /var/lib/odoo/filestore/<db>/<hash-prefix>" inside Odoo's
|
|
# mail.data load. This initContainer fixes ownership-drift
|
|
# regardless of what wrote the files. Defence in depth.
|
|
- name: filestore-chown
|
|
image: {{ include "instance.odooImage" . | quote }}
|
|
imagePullPolicy: IfNotPresent
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
set -eu
|
|
# uid 100 = odoo, gid 101 = odoo group (per the Odoo
|
|
# base image's /etc/passwd + /etc/group). Hardcoded
|
|
# because the chart targets that specific image.
|
|
chown -R 100:101 /var/lib/odoo
|
|
chmod -R u+rwX,g+rwX /var/lib/odoo
|
|
echo "filestore ownership: $(stat -c '%u:%g %a' /var/lib/odoo)"
|
|
securityContext:
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
volumeMounts:
|
|
- name: filestore
|
|
mountPath: /var/lib/odoo
|
|
# platform-shim: workaround for upstream Odoo manifest bug.
|
|
# Materializes a tiny addon (`odoosky_hoot_dom_shim`) into the
|
|
# shared addons volume; the addon injects
|
|
# `web/static/lib/hoot-dom/**/*` into the `web.assets_backend`
|
|
# bundle.
|
|
#
|
|
# Why: Odoo 18.0 nightlies (verified through 2026-05-08)
|
|
# include production source files
|
|
# (`barcodes/static/src/barcode_handlers.js`,
|
|
# `web_tour/.../tour_step_automatic.js`,
|
|
# `sale/.../tour_utils.js`) that `import '@odoo/hoot-dom'`,
|
|
# but the upstream `web/__manifest__.py` only places the
|
|
# hoot-dom library in `web.assets_unit_tests_setup` (the test
|
|
# bundle). Production bundle ends up with dangling references
|
|
# and the browser fails to bootstrap. erp18 incident
|
|
# 2026-05-08 — surfaced after a heavy addon install pulled
|
|
# barcode_handlers.js into the active backend bundle.
|
|
#
|
|
# Remove this initContainer (and the shim install step in
|
|
# db-init) once upstream patches the manifest.
|
|
- name: platform-shim
|
|
image: {{ include "instance.odooImage" . | quote }}
|
|
imagePullPolicy: IfNotPresent
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
set -eu
|
|
mkdir -p /target/odoosky_hoot_dom_shim/static/src
|
|
: > /target/odoosky_hoot_dom_shim/__init__.py
|
|
# Investigated three approaches before settling on this one:
|
|
#
|
|
# 1. Drop the hoot-dom lib files into web.assets_backend
|
|
# (`web/static/lib/hoot-dom/**/*`). They got bundled
|
|
# but the asset compiler registered them under their
|
|
# path-based names (`@web/../lib/hoot-dom/hoot-dom`)
|
|
# and ignored the `@odoo-module alias=@odoo/hoot-dom`
|
|
# directive — that directive only fires for files
|
|
# under `static/src/`, not `static/lib/`. Bundle grew,
|
|
# `@odoo/hoot-dom` still undefined.
|
|
# 2. Add `web/static/tests/_framework/hoot_module_loader.js`
|
|
# on top of #1 (mirror of upstream assets_unit_tests_setup).
|
|
# Same outcome — the loader patches `odoo.define` for
|
|
# follow-on hoot test files but doesn't register the
|
|
# canonical alias.
|
|
# 3. (this) Inject a tiny alias file under our addon's
|
|
# `static/src/` that bridges the two names with a
|
|
# plain `odoo.define` call. The lib files are already
|
|
# in the prod bundle (the broken manifest puts them
|
|
# only in unit_tests_setup, but Odoo's barcode/tour
|
|
# code in static/src/ references them so they get
|
|
# pulled in transitively); the alias file just
|
|
# re-exports them under the canonical name production
|
|
# code asks for.
|
|
cat > /target/odoosky_hoot_dom_shim/static/src/hoot_dom_alias.js <<'JSEOF'
|
|
/** @odoo-module ignore */
|
|
// OdooSky platform shim: register `@odoo/hoot-dom` as an
|
|
// alias for the path-based module the asset compiler
|
|
// produces from web/static/lib/hoot-dom/hoot-dom.js.
|
|
// Without this, prod source files in barcodes / sale /
|
|
// web_tour that `import '@odoo/hoot-dom'` fail to bootstrap
|
|
// because no module is registered under that name in the
|
|
// production bundle.
|
|
odoo.define(
|
|
"@odoo/hoot-dom",
|
|
["@web/../lib/hoot-dom/hoot-dom"],
|
|
function (require) {
|
|
return require("@web/../lib/hoot-dom/hoot-dom");
|
|
}
|
|
);
|
|
JSEOF
|
|
# Manifest version MUST start with "<odoo_major>.0." or
|
|
# Odoo refuses to install with: "incompatible version,
|
|
# setting installable=False" (v19 ran into this — shim
|
|
# was hardcoded as 18.0.1.0.2 and silently became
|
|
# uninstallable, leaving the bundle without hoot-dom).
|
|
# Render the major from .Values.odoo.tag so the shim is
|
|
# compatible across every Odoo major in `pinnedTags`.
|
|
cat > /target/odoosky_hoot_dom_shim/__manifest__.py <<PYEOF
|
|
{
|
|
'name': 'OdooSky hoot-dom backend shim',
|
|
'version': '{{ .Values.odoo.tag }}.1.0.4',
|
|
'category': 'Hidden',
|
|
'author': 'OdooSky',
|
|
'summary': 'Register @odoo/hoot-dom alias in web.assets_backend (workaround for upstream alias-not-honored bug)',
|
|
'depends': ['web'],
|
|
'assets': {
|
|
# Empty-DB Odoo 19 doesn't transitively pull
|
|
# hoot-dom into the prod bundle (no barcode /
|
|
# sale / web_tour static/src code is installed).
|
|
# The alias below declares a dep on
|
|
# @web/../lib/hoot-dom/hoot-dom — if no other
|
|
# source pulls it, the bundle errors with
|
|
# "module not defined" and the backend fails to
|
|
# boot (2026-05-13 erp19 fresh-instance finding).
|
|
# Pull the lib files explicitly so the path-
|
|
# based name is always registered; the alias
|
|
# then re-exports under @odoo/hoot-dom.
|
|
'web.assets_backend': [
|
|
'web/static/lib/hoot-dom/**/*',
|
|
'odoosky_hoot_dom_shim/static/src/hoot_dom_alias.js',
|
|
],
|
|
},
|
|
'installable': True,
|
|
'auto_install': True,
|
|
'license': 'LGPL-3',
|
|
}
|
|
PYEOF
|
|
echo "── platform shim materialized at /target/odoosky_hoot_dom_shim ──"
|
|
volumeMounts:
|
|
- name: addons
|
|
mountPath: /target
|
|
# db-init: idempotent on every pod boot.
|
|
# 1. createdb if missing (PG-level)
|
|
# 2. odoo -i base --stop-after-init to install base module
|
|
# and create Odoo's tables. After base is installed,
|
|
# `-i base` is a no-op so subsequent boots add ~5s.
|
|
- name: db-init
|
|
image: {{ include "instance.odooImage" . | quote }}
|
|
imagePullPolicy: IfNotPresent
|
|
# Override the official Odoo entrypoint so we can run psql
|
|
# before odoo. The image ships with postgresql-client, so
|
|
# createdb is on PATH.
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
set -eu
|
|
DBNAME="{{ .Values.instance.code }}"
|
|
CLONE_FROM="{{ .Values.instance.cloneFromCode | default "" }}"
|
|
echo "── ensuring database $DBNAME exists ──"
|
|
# Wait for PG to accept connections (max 60s)
|
|
for i in $(seq 1 30); do
|
|
if PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres -c '\q' 2>/dev/null; then break; fi
|
|
echo "(waiting for postgres, $i/30)"
|
|
sleep 2
|
|
done
|
|
# VolumeClone fast-path rename (ADR 0003 phase 4): when
|
|
# spawn-env clones a source PG PVC, the data dir carries
|
|
# source's database name. Rename it to target's instance
|
|
# code BEFORE the existence check below, so the rest of
|
|
# the script sees the correct DB name. Idempotent — if
|
|
# CLONE_FROM == DBNAME or target already exists, skip.
|
|
if [ -n "$CLONE_FROM" ] && [ "$CLONE_FROM" != "$DBNAME" ]; then
|
|
if PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres -tAc \
|
|
"SELECT 1 FROM pg_database WHERE datname = '$CLONE_FROM'" | grep -q 1; then
|
|
if ! PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres -tAc \
|
|
"SELECT 1 FROM pg_database WHERE datname = '$DBNAME'" | grep -q 1; then
|
|
echo "── renaming cloned database $CLONE_FROM → $DBNAME ──"
|
|
PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres \
|
|
-c "ALTER DATABASE \"$CLONE_FROM\" RENAME TO \"$DBNAME\""
|
|
else
|
|
echo "(both $CLONE_FROM and $DBNAME exist — leaving rename to operator)"
|
|
fi
|
|
fi
|
|
fi
|
|
# createdb is idempotent if we wrap with an existence check.
|
|
if PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres -tAc \
|
|
"SELECT 1 FROM pg_database WHERE datname = '$DBNAME'" | grep -q 1; then
|
|
echo "(database $DBNAME already exists)"
|
|
else
|
|
echo "── creating database $DBNAME ──"
|
|
PGPASSWORD="$PASSWORD" createdb -h "$HOST" -p "$PORT" -U "$USER" "$DBNAME"
|
|
fi
|
|
# Initialise base ONLY if the DB has never been initialised.
|
|
# The earlier comment claimed `-i base` was a no-op on
|
|
# already-initialised databases — that's wrong on Odoo
|
|
# 16+. `-i base` always runs `_process_end` which tries
|
|
# to unlink stale `ir.model.data` records flagged
|
|
# noupdate=False; if any of those records are
|
|
# referenced by REAL user data (e.g. a sale_order
|
|
# pointing at the partner Odoo wants to clean up),
|
|
# ForeignKeyViolation crashes the init and the pod
|
|
# crash-loops.
|
|
#
|
|
# The migrate-from-bundle flow restores a DB that has
|
|
# `base` installed AND real user FKs. Re-running
|
|
# `-i base` against it deterministically breaks. The
|
|
# check below skips the odoo step when base is already
|
|
# installed — fresh deploys still bootstrap correctly
|
|
# because base isn't installed in a brand-new database.
|
|
# Init both vars to "" so `set -u` doesn't crash when
|
|
# the schema query returns nothing (fresh DB case —
|
|
# ir_module_module doesn't exist yet, so the inner
|
|
# query block is skipped, leaving BASE_INSTALLED
|
|
# unset). Without this, db-init crashlooped on every
|
|
# fresh deploy with: "BASE_INSTALLED: parameter not
|
|
# set".
|
|
IS_INIT=""
|
|
BASE_INSTALLED=""
|
|
IS_INIT=$(PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d "$DBNAME" -tAc \
|
|
"SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='ir_module_module'" 2>/dev/null || true)
|
|
if [ "$IS_INIT" = "1" ]; then
|
|
BASE_INSTALLED=$(PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d "$DBNAME" -tAc \
|
|
"SELECT 1 FROM ir_module_module WHERE name='base' AND state='installed'" 2>/dev/null || true)
|
|
fi
|
|
if [ "$BASE_INSTALLED" = "1" ]; then
|
|
echo "── base already installed — skipping --init (preserves restored data) ──"
|
|
else
|
|
echo "── initializing base module ──"
|
|
odoo -i base -d "$DBNAME" --stop-after-init --without-demo=all --no-http \
|
|
--db_host="$HOST" --db_port="$PORT" --db_user="$USER" --db_password="$PASSWORD" \
|
|
--workers=0
|
|
fi
|
|
# Install the platform-shim addon if not already
|
|
# installed. The shim's files were materialized into
|
|
# the addons volume by the platform-shim initContainer
|
|
# above; here we just register them in this DB. Once
|
|
# registered, Odoo's asset machinery emits an ir.asset
|
|
# row that adds web/static/lib/hoot-dom/**/* to the
|
|
# web.assets_backend bundle on next regeneration. See
|
|
# platform-shim initContainer for the why.
|
|
SHIM_INSTALLED_VER=""
|
|
IS_INIT_AFTER=$(PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d "$DBNAME" -tAc \
|
|
"SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='ir_module_module'" 2>/dev/null || true)
|
|
if [ "$IS_INIT_AFTER" = "1" ]; then
|
|
SHIM_INSTALLED_VER=$(PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d "$DBNAME" -tAc \
|
|
"SELECT latest_version FROM ir_module_module WHERE name='odoosky_hoot_dom_shim' AND state='installed'" 2>/dev/null || true)
|
|
fi
|
|
# Manifest version is the source of truth — on bump, run -u
|
|
# so Odoo re-reads assets and regenerates affected bundles.
|
|
# H2: anchor on the literal `'version':` key (with optional
|
|
# leading whitespace) so we don't accidentally match other
|
|
# lines containing the substring "version" (e.g. a future
|
|
# 'description' or 'summary' that mentions "versioning").
|
|
# Falls back to the python parser if awk produces empty —
|
|
# that path also catches the case where the manifest has
|
|
# been switched to double-quoted values.
|
|
SHIM_MANIFEST_VER=$(awk -F"'" '/^[[:space:]]*.version.:/{print $4; exit}' /mnt/extra-addons/odoosky_hoot_dom_shim/__manifest__.py)
|
|
if [ -z "$SHIM_MANIFEST_VER" ]; then
|
|
SHIM_MANIFEST_VER=$(python3 -c "import ast,sys; print(ast.literal_eval(open(sys.argv[1]).read()).get('version',''))" /mnt/extra-addons/odoosky_hoot_dom_shim/__manifest__.py 2>/dev/null || echo "")
|
|
fi
|
|
if [ -z "$SHIM_INSTALLED_VER" ]; then
|
|
echo "── installing odoosky_hoot_dom_shim ($SHIM_MANIFEST_VER) — hoot-dom workaround ──"
|
|
odoo -i odoosky_hoot_dom_shim -d "$DBNAME" --stop-after-init --no-http \
|
|
--db_host="$HOST" --db_port="$PORT" --db_user="$USER" --db_password="$PASSWORD" \
|
|
--workers=0 \
|
|
--addons-path="/usr/lib/python3/dist-packages/odoo/addons,{{ .Values.addonsMountPath }}"
|
|
elif [ "$SHIM_INSTALLED_VER" != "$SHIM_MANIFEST_VER" ]; then
|
|
echo "── upgrading odoosky_hoot_dom_shim ($SHIM_INSTALLED_VER → $SHIM_MANIFEST_VER) ──"
|
|
odoo -u odoosky_hoot_dom_shim -d "$DBNAME" --stop-after-init --no-http \
|
|
--db_host="$HOST" --db_port="$PORT" --db_user="$USER" --db_password="$PASSWORD" \
|
|
--workers=0 \
|
|
--addons-path="/usr/lib/python3/dist-packages/odoo/addons,{{ .Values.addonsMountPath }}"
|
|
else
|
|
echo "── odoosky_hoot_dom_shim $SHIM_INSTALLED_VER up-to-date — skipping ──"
|
|
fi
|
|
echo "── db-init done ──"
|
|
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:
|
|
# db-init reads the platform-shim addon from this mount
|
|
# to install it via `odoo -i odoosky_hoot_dom_shim`. The
|
|
# mount is unconditional because the shim is unconditional;
|
|
# any tenant addon images write here too if .Values.addons
|
|
# is non-empty.
|
|
- name: addons
|
|
mountPath: {{ .Values.addonsMountPath }}
|
|
{{- if .Values.addons }}
|
|
# One initContainer per selected addon. Each addon image's
|
|
# ENTRYPOINT/CMD copies its bundled content into /target/<code>,
|
|
# which is the shared volume the Odoo container reads from.
|
|
# Init containers run in order, but each writes to its own
|
|
# subdir, so order doesn't matter.
|
|
#
|
|
# `imagePullPolicy: Always` is intentional: addon images can be
|
|
# rebuilt under the same {code}:{version} tag (e.g. when Tower's
|
|
# build pipeline changes — adding pip-install layer for python
|
|
# external_dependencies). With IfNotPresent, kubelet would
|
|
# reuse the cached old digest and the pod would never see the
|
|
# new content. Always forces a manifest fetch on each pod
|
|
# start; layers themselves are still cached so the pull is
|
|
# cheap (~50 ms when content is unchanged, fresh otherwise).
|
|
{{- range $i, $a := .Values.addons }}
|
|
{{- /* Skip odoo-builtin rows — they ship inside the odoo
|
|
image, have no addon image to materialize, and have
|
|
empty $a.image / $a.version which would render
|
|
`image: :` and break YAML parsing. They get tracked in
|
|
the overlay (Phase 2 Promote work, #344) for
|
|
reconcile + audit but produce no init container. */ -}}
|
|
{{- if and $a.code $a.image $a.version (ne $a.source "odoo-builtin") }}
|
|
- name: addon-{{ $a.code | replace "_" "-" | lower }}
|
|
image: {{ $a.image }}:{{ $a.version }}
|
|
imagePullPolicy: Always
|
|
volumeMounts:
|
|
- name: addons
|
|
mountPath: /target
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- end }}
|
|
containers:
|
|
- name: odoo
|
|
image: {{ include "instance.odooImage" . | quote }}
|
|
imagePullPolicy: IfNotPresent
|
|
# Pin the active database to our tenant code. Without this
|
|
# Odoo runs in multi-DB mode and exposes /web/database/manager;
|
|
# for the SaaS UX we want one instance == one DB and never
|
|
# show the manager. db-init has already created and bootstrapped
|
|
# this DB, so Odoo opens it cleanly.
|
|
# Args list assembled by instance.odooArgs helper:
|
|
# * hard baseline (-d, --db-filter, --addons-path, --proxy-mode)
|
|
# encodes v3 deployment invariants (Traefik termination,
|
|
# single-DB mode) — operators cannot drop these.
|
|
# * runtime knobs from .Values.odoo.* (workers, limit_time_*,
|
|
# server-wide modules, etc.) render only when set; null
|
|
# keeps Odoo's internal defaults.
|
|
# See templates/_helpers.tpl and values.yaml `odoo:` block.
|
|
args:
|
|
{{- include "instance.odooArgs" . | nindent 12 }}
|
|
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
|
|
{{- if .Values.addons }}
|
|
# Python packages declared by addons (paramiko, lxml,
|
|
# cryptography, …) are baked into each addon's image at
|
|
# build time, then materialized into
|
|
# /mnt/extra-addons/.python-deps by the init containers.
|
|
# PYTHONPATH points Odoo's interpreter at that dir so
|
|
# `import paramiko` from inside an addon Just Works without
|
|
# the operator ever pip-installing anything at runtime.
|
|
- name: PYTHONPATH
|
|
value: "{{ .Values.addonsMountPath }}/.python-deps"
|
|
{{- end }}
|
|
volumeMounts:
|
|
- name: filestore
|
|
mountPath: /var/lib/odoo
|
|
# addons mount is always present — at minimum it carries
|
|
# the platform-shim addon (see platform-shim initContainer).
|
|
- name: addons
|
|
mountPath: {{ .Values.addonsMountPath }}
|
|
readOnly: true
|
|
resources:
|
|
{{- include "instance.resources" (dict "Values" .Values "role" "odoo") | 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.
|
|
#
|
|
# 5s period catches a true pod restart fast (initialDelay
|
|
# gates the boot path; failureThreshold only matters once
|
|
# the pod has been ready). failureThreshold: 15 (= 75s of
|
|
# consecutive probe failures) gives Odoo headroom to install
|
|
# heavy modules (e.g. ks_dashboard_ninja) without K8s pulling
|
|
# the Pod from Endpoints mid-install — which produced 503s
|
|
# at the operator surface during addon-apply. Paired with
|
|
# the Traefik retry middleware for short gaps.
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /web/login
|
|
port: 8069
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 5
|
|
timeoutSeconds: 5
|
|
failureThreshold: 15
|
|
livenessProbe:
|
|
tcpSocket:
|
|
port: 8069
|
|
initialDelaySeconds: 60
|
|
periodSeconds: 30
|
|
volumes:
|
|
- name: filestore
|
|
persistentVolumeClaim:
|
|
claimName: {{ include "instance.fullname" . }}-odoo
|
|
# Shared scratch volume that init containers populate. Always
|
|
# created — at minimum the platform-shim initContainer drops
|
|
# `odoosky_hoot_dom_shim` here. emptyDir is fine — source of
|
|
# truth is the registry's images / hardcoded shim; if the
|
|
# volume is wiped (pod recreate) initContainers re-materialize.
|
|
- name: addons
|
|
emptyDir: {}
|