# =========================
# 0) Namespace
# =========================
apiVersion: v1
kind: Namespace
metadata:
name: portal-dev
---
# =========================
# 1) Shared Postgres (one instance, two DBs: odoo & portal)
# - Superuser: POSTGRES_USER=postgres
# - Creates users/DBs: odoo / portal
# =========================
apiVersion: v1
kind: Secret
metadata:
name: postgres-auth
namespace: portal-dev
type: Opaque
stringData:
POSTGRES_PASSWORD: "change-super-pass" # superuser
ODOO_DB_USER: odoo
ODOO_DB_PASSWORD: "change-odoo-pass"
PORTAL_DB_USER: portal
PORTAL_DB_PASSWORD: "change-portal-pass"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-init
namespace: portal-dev
data:
init-multi-db.sh: |
#!/usr/bin/env bash
set -euo pipefail
echo "Initializing multiple DBs (odoo, portal)..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='${ODOO_DB_USER}') THEN
CREATE ROLE ${ODOO_DB_USER} LOGIN PASSWORD '${ODOO_DB_PASSWORD}';
END IF;
IF NOT EXISTS (SELECT FROM pg_database WHERE datname='odoo') THEN
CREATE DATABASE odoo OWNER ${ODOO_DB_USER};
END IF;
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='${PORTAL_DB_USER}') THEN
CREATE ROLE ${PORTAL_DB_USER} LOGIN PASSWORD '${PORTAL_DB_PASSWORD}';
END IF;
IF NOT EXISTS (SELECT FROM pg_database WHERE datname='portal') THEN
CREATE DATABASE portal OWNER ${PORTAL_DB_USER};
END IF;
END
\$\$;
SQL
echo "Enable common extensions in portal DB..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -d portal <<-SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
SQL
echo "Enable useful extensions in odoo DB..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -d odoo <<-SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
SQL
echo "Done."
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: portal-dev
spec:
serviceName: postgres
replicas: 1
selector: { matchLabels: { app: postgres } }
template:
metadata: { labels: { app: postgres } }
spec:
containers:
- name: db
image: postgres:16
ports: [{ containerPort: 5432, name: pg }]
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom: { secretKeyRef: { name: postgres-auth, key: POSTGRES_PASSWORD } }
- name: ODOO_DB_USER
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_USER } }
- name: ODOO_DB_PASSWORD
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_PASSWORD } }
- name: PORTAL_DB_USER
valueFrom: { secretKeyRef: { name: postgres-auth, key: PORTAL_DB_USER } }
- name: PORTAL_DB_PASSWORD
valueFrom: { secretKeyRef: { name: postgres-auth, key: PORTAL_DB_PASSWORD } }
volumeMounts:
- { name: data, mountPath: /var/lib/postgresql/data }
- { name: init, mountPath: /docker-entrypoint-initdb.d }
volumes:
- name: init
configMap:
name: postgres-init
defaultMode: 0755
volumeClaimTemplates:
- metadata: { name: data }
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 20Gi } }
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: portal-dev
spec:
selector: { app: postgres }
ports: [{ name: pg, port: 5432, targetPort: 5432 }]
---
# =========================
# 2) Odoo (uses DB=odoo on same Postgres)
# =========================
apiVersion: v1
kind: ConfigMap
metadata:
name: odoo-config
namespace: portal-dev
data:
ODOO_DB_NAME: odoo
ODOO_DB_HOST: postgres
ODOO_DB_PORT: "5432"
ODOO_PROXY_MODE: "true"
ODOO_DB_FILTER: "^odoo$"
ODOO_LOG_LEVEL: info
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: odoo
namespace: portal-dev
spec:
replicas: 1
selector: { matchLabels: { app: odoo } }
template:
metadata: { labels: { app: odoo } }
spec:
containers:
- name: odoo
image: odoo:16.0
ports: [{ containerPort: 8069, name: http }]
envFrom:
- configMapRef: { name: odoo-config }
env:
- name: ODOO_DB_USER
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_USER } }
- name: ODOO_DB_PASSWORD
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_PASSWORD } }
volumeMounts:
- { name: filestore, mountPath: /var/lib/odoo }
readinessProbe:
httpGet: { path: /web, port: http }
initialDelaySeconds: 20
periodSeconds: 5
livenessProbe:
httpGet: { path: /web, port: http }
initialDelaySeconds: 60
periodSeconds: 10
resources:
requests: { cpu: "250m", memory: "512Mi" }
limits: { cpu: "1000m", memory: "2Gi" }
volumes:
- name: filestore
persistentVolumeClaim: { claimName: odoo-filestore }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: odoo-filestore
namespace: portal-dev
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 20Gi } }
---
apiVersion: v1
kind: Service
metadata:
name: odoo
namespace: portal-dev
spec:
selector: { app: odoo }
ports: [{ name: http, port: 80, targetPort: 8069 }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: odoo
namespace: portal-dev
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: odoo.dev.example.com
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: odoo, port: { number: 80 } } }
---
# =========================
# 3) Portal API (uses DB=portal on same Postgres)
# =========================
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
namespace: portal-dev
data:
APP_ENV: dev
LOG_LEVEL: info
API_PORT: "8080"
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: portal
# Odoo FDW DSN hint (optional)
ODOO_FDW_HOST: postgres
ODOO_FDW_PORT: "5432"
ODOO_FDW_DB: odoo
# Chroma
CHROMA_HOST: chroma
CHROMA_PORT: "8000"
CHROMA_COLLECTION_FIELD_JA: portal_field_ja
CHROMA_COLLECTION_VIEW_JA: portal_view_common_ja
---
apiVersion: v1
kind: Secret
metadata:
name: api-auth
namespace: portal-dev
type: Opaque
stringData:
DB_USER: portal
DB_PASSWORD: "change-portal-pass" # mirror of postgres-auth PORTAL_DB_PASSWORD
API_BEARER_TOKEN: "dev-token" # CronJobs use this to call API
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: portal-api
namespace: portal-dev
spec:
replicas: 2
selector: { matchLabels: { app: portal-api } }
template:
metadata: { labels: { app: portal-api } }
spec:
containers:
- name: api
image: your-registry/portal-api:0.1.0
ports: [{ containerPort: 8080, name: http }]
envFrom:
- configMapRef: { name: api-config }
- secretRef: { name: api-auth }
readinessProbe:
httpGet: { path: /healthz, port: http }
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet: { path: /livez, port: http }
initialDelaySeconds: 10
periodSeconds: 10
startupProbe:
httpGet: { path: /startupz, port: http }
failureThreshold: 30
periodSeconds: 2
resources:
requests: { cpu: "200m", memory: "256Mi" }
limits: { cpu: "1", memory: "1Gi" }
---
apiVersion: v1
kind: Service
metadata:
name: portal-api
namespace: portal-dev
spec:
selector: { app: portal-api }
ports: [{ name: http, port: 80, targetPort: 8080 }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: portal-api
namespace: portal-dev
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: api.dev.example.com
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: portal-api, port: { number: 80 } } }
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: portal-api
namespace: portal-dev
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: portal-api
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 60 } }
---
# =========================
# 4) ChromaDB (dev用)
# =========================
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: chroma
namespace: portal-dev
spec:
serviceName: chroma
replicas: 1
selector: { matchLabels: { app: chroma } }
template:
metadata: { labels: { app: chroma } }
spec:
containers:
- name: chroma
image: chromadb/chroma:latest
ports: [{ containerPort: 8000, name: http }]
env:
- { name: CHROMA_SERVER_HOST, value: "0.0.0.0" }
- { name: CHROMA_SERVER_HTTP_PORT, value: "8000" }
volumeMounts: [{ name: data, mountPath: /chroma/chroma }]
volumeClaimTemplates:
- metadata: { name: data }
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 20Gi } }
---
apiVersion: v1
kind: Service
metadata:
name: chroma
namespace: portal-dev
spec:
selector: { app: chroma }
ports: [{ name: http, port: 8000, targetPort: 8000 }]
---
# =========================
# 5) DB Migration Job (apply DDL to portal DB)
# =========================
apiVersion: batch/v1
kind: Job
metadata:
name: portal-db-migrate
namespace: portal-dev
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: your-registry/portal-api:0.1.0
envFrom:
- configMapRef: { name: api-config }
- secretRef: { name: api-auth }
command: ["sh", "-c"]
args:
- >
python -m scripts.apply_sql /app/sql/000_extensions.sql &&
python -m scripts.apply_sql /app/sql/010_portal_core.sql &&
python -m scripts.apply_sql /app/sql/020_portal_translate.sql &&
python -m scripts.apply_sql /app/sql/030_portal_chroma_doc.sql
---
# =========================
# 6) FDW + ir_*_src Rebuild (Portal DB ←→ Odoo DB on same Postgres)
# =========================
apiVersion: batch/v1
kind: CronJob
metadata:
name: ir-src-rebuild
namespace: portal-dev
spec:
schedule: "0 * * * *" # hourly
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: psql
image: postgres:16
envFrom:
- configMapRef: { name: api-config } # DB_HOST/DB_NAME=portal
env:
- name: DB_USER
valueFrom: { secretKeyRef: { name: api-auth, key: DB_USER } }
- name: DB_PASSWORD
valueFrom: { secretKeyRef: { name: api-auth, key: DB_PASSWORD } }
- name: ODOO_DB_USER
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_USER } }
- name: ODOO_DB_PASSWORD
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_PASSWORD } }
command: ["bash", "-lc"]
args:
- |
export PGPASSWORD="$DB_PASSWORD";
psql "host=$DB_HOST dbname=$DB_NAME user=$DB_USER" -v ON_ERROR_STOP=1 <<'SQL'
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_foreign_server WHERE srvname='odoo_server') THEN
CREATE SERVER odoo_server FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'postgres', dbname 'odoo', port '5432');
END IF;
END$$;
CREATE SCHEMA IF NOT EXISTS odoo_fdw;
-- User mapping for portal app user to access odoo DB
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_user_mappings
WHERE srvname='odoo_server' AND umuser = (SELECT usesysid FROM pg_user WHERE usename = current_user)
) THEN
CREATE USER MAPPING FOR CURRENT_USER SERVER odoo_server
OPTIONS (user :'odoo_user', password :'odoo_pass');
END IF;
END$$;
-- Optional IMPORT FOREIGN SCHEMA (run initially / on schema change)
-- IMPORT FOREIGN SCHEMA public LIMIT TO
-- (ir_model, ir_model_fields, ir_ui_view, ir_act_window, ir_act_window_view, ir_model_data)
-- FROM SERVER odoo_server INTO odoo_fdw;
-- ===== Your action-centric rebuild SQL here =====
-- Drops & recreates public.ir_model_src / ir_field_src / ir_view_src
-- (貼り付け推奨:既に共有いただいた action-centric 版)
-- ================================================
SQL
# Pass env to psql heredoc
env:
- name: odoo_user
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_USER } }
- name: odoo_pass
valueFrom: { secretKeyRef: { name: postgres-auth, key: ODOO_DB_PASSWORD } }
---
# =========================
# 7) Pipeline CronJobs (call Portal API)
# =========================
apiVersion: batch/v1
kind: CronJob
metadata:
name: cj-extract-view
namespace: portal-dev
spec:
schedule: "*/30 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl:8.8.0
env:
- name: TOKEN
valueFrom: { secretKeyRef: { name: api-auth, key: API_BEARER_TOKEN } }
args:
- >
-sS -X POST
-H "Authorization: Bearer $(TOKEN)"
-H "Content-Type: application/json"
--data '{"action_xmlids":[],"targets":["ai_purpose","help"],"mode":"upsert_if_changed"}'
http://portal-api.portal-dev.svc.cluster.local/extract/view_common
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cj-extract-field
namespace: portal-dev
spec:
schedule: "15 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl:8.8.0
env:
- name: TOKEN
valueFrom: { secretKeyRef: { name: api-auth, key: API_BEARER_TOKEN } }
args:
- >
-sS -X POST
-H "Authorization: Bearer $(TOKEN)"
-H "Content-Type: application/json"
--data '{"models":[],"fields":[],"mode":"upsert_if_changed"}'
http://portal-api.portal-dev.svc.cluster.local/extract/field
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cj-translate-run
namespace: portal-dev
spec:
schedule: "*/20 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl:8.8.0
env:
- name: TOKEN
valueFrom: { secretKeyRef: { name: api-auth, key: API_BEARER_TOKEN } }
args:
- >
-sS -X POST
-H "Authorization: Bearer $(TOKEN)"
-H "Content-Type: application/json"
--data '{"limit":200,"source_lang":"ja_JP","target_lang":"en_US","entities":["field","view_common"]}'
http://portal-api.portal-dev.svc.cluster.local/translate/run
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cj-chroma-package
namespace: portal-dev
spec:
schedule: "*/30 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl:8.8.0
env:
- name: TOKEN
valueFrom: { secretKeyRef: { name: api-auth, key: API_BEARER_TOKEN } }
args:
- >
-sS -X POST
-H "Authorization: Bearer $(TOKEN)"
-H "Content-Type: application/json"
--data '{"entities":["field","view_common"],"lang":"ja","collections":{"field":"portal_field_ja","view_common":"portal_view_common_ja"},"limit":500}'
http://portal-api.portal-dev.svc.cluster.local/chroma/package
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cj-chroma-upsert
namespace: portal-dev
spec:
schedule: "*/15 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl:8.8.0
env:
- name: TOKEN
valueFrom: { secretKeyRef: { name: api-auth, key: API_BEARER_TOKEN } }
args:
- >
-sS -X POST
-H "Authorization: Bearer $(TOKEN)"
-H "Content-Type: application/json"
--data '{"collections":["portal_field_ja","portal_view_common_ja"],"limit":1000,"dry_run":false}'
http://portal-api.portal-dev.svc.cluster.local/chroma/upsert
コメントを残す