From 71d76a688e48dac7faaa6b9540ffa486f5802825 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 15:49:56 +0900 Subject: [PATCH 1/9] helm: make service type, port, and annotations configurable --- deploy/helm/codex-lb/templates/service.yaml | 16 +++++++++++++--- deploy/helm/codex-lb/values.yaml | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/deploy/helm/codex-lb/templates/service.yaml b/deploy/helm/codex-lb/templates/service.yaml index e10492cf..98a29135 100644 --- a/deploy/helm/codex-lb/templates/service.yaml +++ b/deploy/helm/codex-lb/templates/service.yaml @@ -5,16 +5,26 @@ metadata: namespace: {{ .Release.Namespace | quote }} labels: {{- include "codex-lb.labels" . | nindent 4 }} - {{- with .Values.commonAnnotations }} + {{- with mustMerge (.Values.service.annotations | default dict) (.Values.commonAnnotations | default dict) }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - type: ClusterIP + type: {{ .Values.service.type }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} selector: {{- include "codex-lb.selectorLabels" . | nindent 4 }} ports: - name: http - port: 2455 + port: {{ .Values.service.port }} targetPort: http protocol: TCP + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} diff --git a/deploy/helm/codex-lb/values.yaml b/deploy/helm/codex-lb/values.yaml index e7537e1e..f7defcc2 100644 --- a/deploy/helm/codex-lb/values.yaml +++ b/deploy/helm/codex-lb/values.yaml @@ -231,6 +231,21 @@ tolerations: [] # @param priorityClassName Pod priority class name priorityClassName: "" +# @section Service parameters +service: + # @param service.type Kubernetes Service type (ClusterIP, NodePort, LoadBalancer) + type: ClusterIP + # @param service.port Service port + port: 2455 + # @param service.nodePort NodePort number (only when type is NodePort) + nodePort: "" + # @param service.loadBalancerIP Static IP for LoadBalancer type + loadBalancerIP: "" + # @param service.loadBalancerSourceRanges Allowed source ranges for LoadBalancer + loadBalancerSourceRanges: [] + # @param service.annotations Additional annotations for the Service + annotations: {} + # @section Network parameters networkPolicy: # @param networkPolicy.enabled Enable NetworkPolicy with default-deny + whitelist From 12d416404d3dce6507342d8c23b38bc7575b5003 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 15:54:19 +0900 Subject: [PATCH 2/9] helm: add db-init pre-install hook for external PostgreSQL Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) --- deploy/helm/codex-lb/templates/_helpers.tpl | 10 ++++ .../helm/codex-lb/templates/deployment.yaml | 8 +-- .../codex-lb/templates/hooks/db-init-job.yaml | 57 +++++++++++++++++++ .../templates/hooks/migration-job.yaml | 8 ++- .../templates/tests/test-connection.yaml | 4 ++ deploy/helm/codex-lb/values.yaml | 24 ++++++++ 6 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 deploy/helm/codex-lb/templates/hooks/db-init-job.yaml diff --git a/deploy/helm/codex-lb/templates/_helpers.tpl b/deploy/helm/codex-lb/templates/_helpers.tpl index 4b61fd84..e70b568d 100644 --- a/deploy/helm/codex-lb/templates/_helpers.tpl +++ b/deploy/helm/codex-lb/templates/_helpers.tpl @@ -139,3 +139,13 @@ Image string — resolves registry/repository:tag with optional digest override {{- printf "%s/%s:%s" $registry $repository $tag }} {{- end }} {{- end }} + +{{/* +Merged nodeSelector: global.nodeSelector + local nodeSelector (local wins). +*/}} +{{- define "codex-lb.nodeSelector" -}} +{{- $merged := mustMerge (.Values.nodeSelector | default dict) (.Values.global.nodeSelector | default dict) -}} +{{- if $merged }} +{{- toYaml $merged }} +{{- end }} +{{- end -}} diff --git a/deploy/helm/codex-lb/templates/deployment.yaml b/deploy/helm/codex-lb/templates/deployment.yaml index defca0a3..99eaefb4 100644 --- a/deploy/helm/codex-lb/templates/deployment.yaml +++ b/deploy/helm/codex-lb/templates/deployment.yaml @@ -89,10 +89,10 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} + {{- with (include "codex-lb.nodeSelector" .) }} + nodeSelector: + {{- . | nindent 8 }} + {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} diff --git a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml new file mode 100644 index 00000000..38bdb2e0 --- /dev/null +++ b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml @@ -0,0 +1,57 @@ +{{- if and .Values.dbInit.enabled (not .Values.postgresql.enabled) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ printf "%s-db-init" (include "codex-lb.fullname" . | trunc 52 | trimSuffix "-") }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "codex-lb.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-10" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + template: + spec: + restartPolicy: OnFailure + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: db-init + image: {{ printf "%s/bitnami/postgresql:16" (.Values.global.imageRegistry | default "docker.io") }} + command: ["sh", "-ec"] + args: + - | + PGPASSWORD="$ADMIN_PASSWORD" psql \ + -h "$DB_HOST" -p "$DB_PORT" -U "$ADMIN_USER" -d postgres <<'SQL' + {{- range .Values.dbInit.databases }} + DO $$ BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '{{ .user }}') THEN + CREATE ROLE {{ .user }} WITH LOGIN PASSWORD '{{ .password }}'; + END IF; + END $$; + SELECT format('CREATE DATABASE %I OWNER %I', '{{ .name }}', '{{ .user }}') + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '{{ .name }}')\gexec + GRANT ALL PRIVILEGES ON DATABASE {{ .name }} TO {{ .user }}; + {{- end }} + SQL + env: + - name: DB_HOST + value: {{ .Values.dbInit.host | quote }} + - name: DB_PORT + value: {{ .Values.dbInit.port | default "5432" | quote }} + - name: ADMIN_USER + value: {{ .Values.dbInit.adminUser | quote }} + - name: ADMIN_PASSWORD + {{- if .Values.dbInit.adminPasswordSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.dbInit.adminPasswordSecret.name }} + key: {{ .Values.dbInit.adminPasswordSecret.key }} + {{- else }} + value: {{ .Values.dbInit.adminPassword | quote }} + {{- end }} + backoffLimit: 3 +{{- end }} diff --git a/deploy/helm/codex-lb/templates/hooks/migration-job.yaml b/deploy/helm/codex-lb/templates/hooks/migration-job.yaml index 55e2daf4..097bfbc0 100644 --- a/deploy/helm/codex-lb/templates/hooks/migration-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/migration-job.yaml @@ -35,8 +35,12 @@ spec: {{- range $pullSecrets }} - name: {{ . }} {{- end }} - {{- end }} - {{- if .Values.postgresql.enabled }} + {{- end }} + {{- with (include "codex-lb.nodeSelector" .) }} + nodeSelector: + {{- . | nindent 8 }} + {{- end }} + {{- if .Values.postgresql.enabled }} initContainers: - name: wait-for-db image: postgres:16-alpine diff --git a/deploy/helm/codex-lb/templates/tests/test-connection.yaml b/deploy/helm/codex-lb/templates/tests/test-connection.yaml index 68396259..cc5e2ad5 100644 --- a/deploy/helm/codex-lb/templates/tests/test-connection.yaml +++ b/deploy/helm/codex-lb/templates/tests/test-connection.yaml @@ -15,6 +15,10 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault + {{- with (include "codex-lb.nodeSelector" .) }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} containers: - name: test-connection image: busybox:1.37 diff --git a/deploy/helm/codex-lb/values.yaml b/deploy/helm/codex-lb/values.yaml index f7defcc2..92c466d2 100644 --- a/deploy/helm/codex-lb/values.yaml +++ b/deploy/helm/codex-lb/values.yaml @@ -6,6 +6,8 @@ global: imagePullSecrets: [] # @param global.storageClass Global storage class for PVCs storageClass: "" + # @param global.nodeSelector Node selector labels applied to ALL pods (deployment, jobs, tests) + nodeSelector: {} # @section Common parameters # @param nameOverride Override the chart name @@ -370,6 +372,28 @@ externalDatabase: # @param externalDatabase.existingSecret Secret containing external DB credentials existingSecret: "" +# @section Database initialization parameters +dbInit: + # @param dbInit.enabled Enable pre-install Job to create databases/users on external PostgreSQL + enabled: false + # @param dbInit.host External PostgreSQL host + host: "" + # @param dbInit.port External PostgreSQL port + port: "5432" + # @param dbInit.adminUser Admin username for creating databases/users + adminUser: "adminuser" + # @param dbInit.adminPassword Admin password (use adminPasswordSecret for production) + adminPassword: "" + # @param dbInit.adminPasswordSecret Reference to existing Secret for admin password + adminPasswordSecret: {} + # name: pg-admin-secret + # key: password + # @param dbInit.databases List of databases and users to create + databases: + - name: codexlb + user: codexlb + password: changeme + # @section Migration parameters migration: # @param migration.enabled Run database migration Job on install/upgrade From 6f993b9103d70ea9bfc19e9a1bc9f58bf950a545 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 15:54:21 +0900 Subject: [PATCH 3/9] helm: add global.nodeSelector propagated to all pod specs Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) --- deploy/helm/codex-lb/README.md | 178 +++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/deploy/helm/codex-lb/README.md b/deploy/helm/codex-lb/README.md index 22fe43a8..d58e86e9 100644 --- a/deploy/helm/codex-lb/README.md +++ b/deploy/helm/codex-lb/README.md @@ -245,6 +245,184 @@ total_connections = (databasePoolSize + databaseMaxOverflow) × replicas Keep this within your PostgreSQL `max_connections` budget or place PgBouncer in front of the database. +## Production Deployment + +Multi-replica production deployments require careful coordination of database connectivity, session routing, and graceful shutdown. This section covers the key patterns and tuning parameters. + +### Prerequisites for Multi-Replica + +Single-replica deployments can use SQLite, but **multi-replica requires PostgreSQL**: + +- **Database**: PostgreSQL is mandatory for multi-replica because: + - SQLite does not support concurrent writes from multiple pods + - Leader election requires a shared database backend + - Session bridge ring membership is stored in the database + +- **Leader Election**: Enabled by default (`config.leaderElectionEnabled=true`) + - Ensures only one pod performs background tasks (e.g., session cleanup, metrics aggregation) + - Uses database-backed locking with a TTL (`config.leaderElectionTtlSeconds=30`) + - If the leader crashes, another pod acquires the lock within 30 seconds + +- **Circuit Breaker**: Enabled by default (`config.circuitBreakerEnabled=true`) + - Protects upstream API endpoints from cascading failures + - Opens after `config.circuitBreakerFailureThreshold=5` consecutive failures + - Enters half-open state after `config.circuitBreakerRecoveryTimeoutSeconds=60` seconds + - Prevents thundering herd when upstream is degraded + +### Session Bridge Ring + +The session bridge is an in-memory cache of upstream WebSocket connections, shared across the pod ring. + +**Automatic Ring Membership (PostgreSQL)** + +When using PostgreSQL, ring membership is **automatic and database-backed**: + +- Each pod registers itself in the database on startup +- The `sessionBridgeInstanceRing` field is **optional** and only needed for manual pod list override +- Pods discover each other via database queries; no manual configuration required +- Ring membership is cleaned up automatically when pods terminate + +**Manual Ring Override (Advanced)** + +If you need to manually specify the pod ring (e.g., for testing or debugging): + +```yaml +config: + sessionBridgeInstanceRing: "codex-lb-0.codex-lb.default.svc.cluster.local,codex-lb-1.codex-lb.default.svc.cluster.local" +``` + +This is rarely needed in production; the database-backed discovery is preferred. + +### Connection Pool Budget + +Each pod maintains its own SQLAlchemy connection pool. The total connections across all replicas must fit within PostgreSQL's `max_connections`: + +``` +(databasePoolSize + databaseMaxOverflow) × maxReplicas ≤ PostgreSQL max_connections +``` + +**Example for `values-prod.yaml`:** + +```yaml +config: + databasePoolSize: 3 + databaseMaxOverflow: 2 +autoscaling: + maxReplicas: 20 +``` + +Calculation: `(3 + 2) × 20 = 100` connections, which fits within PostgreSQL's default `max_connections=100`. + +**Tuning:** + +- Increase `databasePoolSize` if pods frequently wait for connections +- Increase `databaseMaxOverflow` for temporary spikes, but keep it small (overflow is slower) +- Reduce `maxReplicas` if you cannot increase PostgreSQL's `max_connections` +- Use PgBouncer or pgcat as a connection pooler in front of PostgreSQL if needed + +### values-prod.yaml Reference + +The `values-prod.yaml` overlay is pre-configured for production multi-replica deployments: + +```yaml +replicaCount: 3 # Start with 3 replicas +postgresql: + enabled: false # Use external PostgreSQL +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 20 + behavior: + scaleDown: + stabilizationWindowSeconds: 600 # 10 min cooldown (see below) +affinity: + podAntiAffinity: hard # Spread pods across nodes +topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone # Spread across zones +networkPolicy: + enabled: true # Restrict ingress/egress +metrics: + serviceMonitor: + enabled: true # Prometheus scraping + prometheusRule: + enabled: true # Alerting rules + grafanaDashboard: + enabled: true # Pre-built dashboards +externalSecrets: + enabled: true # Use External Secrets Operator +``` + +Install with: + +```bash +helm install codex-lb oci://ghcr.io/soju06/charts/codex-lb \ + -f deploy/helm/codex-lb/values-prod.yaml \ + --set externalDatabase.url='postgresql+asyncpg://user:pass@db.example.com:5432/codexlb' +``` + +### Graceful Shutdown Tuning + +Graceful shutdown coordinates three timeout parameters to drain in-flight requests and session bridge connections: + +``` +preStopSleepSeconds (15s) → shutdownDrainTimeoutSeconds (30s) → terminationGracePeriodSeconds (60s) +``` + +**Timeline:** + +1. **preStopSleepSeconds (15s)**: Pod receives SIGTERM + - Sleep briefly to allow load balancer to remove the pod from rotation + - Prevents new requests from arriving during shutdown + +2. **shutdownDrainTimeoutSeconds (30s)**: Drain in-flight requests + - HTTP server stops accepting new connections + - Existing requests are allowed to complete (up to 30 seconds) + - Session bridge connections are gracefully closed + +3. **terminationGracePeriodSeconds (60s)**: Hard deadline + - Total time from SIGTERM to SIGKILL + - Must be ≥ `preStopSleepSeconds + shutdownDrainTimeoutSeconds` + - Default 60s allows 15s + 30s + 15s buffer + +**Tuning:** + +- Increase `preStopSleepSeconds` if your load balancer takes longer to deregister +- Increase `shutdownDrainTimeoutSeconds` if requests typically take >30s to complete +- Increase `terminationGracePeriodSeconds` proportionally (must be larger than the sum) +- Keep the buffer small; long shutdown times delay pod replacement + +Example for long-running requests: + +```yaml +preStopSleepSeconds: 20 +shutdownDrainTimeoutSeconds: 60 +terminationGracePeriodSeconds: 90 +``` + +### Scale-Down Caution + +The `stabilizationWindowSeconds: 600` (10 minutes) in `values-prod.yaml` is intentionally high. + +**Why?** + +- Session bridge connections have idle TTLs (`sessionBridgeIdleTtlSeconds=120` for API, `sessionBridgeCodexIdleTtlSeconds=900` for Codex) +- When a pod scales down, its in-memory sessions are lost +- Clients reconnecting to a different pod must re-establish upstream connections +- A 10-minute cooldown prevents rapid scale-down/up cycles that would thrash session state + +**Behavior:** + +- HPA will scale down at most 1 pod every 2 minutes (when cooldown is active) +- If load drops suddenly, scale-down is delayed by up to 10 minutes +- This trades off faster scale-down for session stability + +**Tuning:** + +- Reduce `stabilizationWindowSeconds` if you prioritize cost over session stability +- Increase it if you see frequent session reconnections during scale events +- Monitor `sessionBridgeInstanceRing` size changes in logs to detect scale-down impact + ## Security The chart targets the Kubernetes Restricted Pod Security Standard. From ced453c39100bf09fa804997154ed20d7abb5891 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 15:52:18 +0900 Subject: [PATCH 4/9] helm: enhance test to verify readiness (includes DB connectivity) --- .../codex-lb/templates/tests/test-connection.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/deploy/helm/codex-lb/templates/tests/test-connection.yaml b/deploy/helm/codex-lb/templates/tests/test-connection.yaml index cc5e2ad5..e230f9b9 100644 --- a/deploy/helm/codex-lb/templates/tests/test-connection.yaml +++ b/deploy/helm/codex-lb/templates/tests/test-connection.yaml @@ -20,7 +20,7 @@ spec: {{- . | nindent 4 }} {{- end }} containers: - - name: test-connection + - name: test-health image: busybox:1.37 imagePullPolicy: IfNotPresent securityContext: @@ -33,5 +33,10 @@ spec: - sh - -c - | - wget --spider --timeout=10 http://{{ include "codex-lb.fullname" . }}:2455/health || exit 1 - echo "Connection test passed!" + echo "=== Health endpoint ===" + wget --spider --timeout=10 http://{{ include "codex-lb.fullname" . }}:{{ .Values.service.port | default 2455 }}/health || exit 1 + echo "Health check passed!" + + echo "=== Startup probe ===" + wget -qO- --timeout=10 http://{{ include "codex-lb.fullname" . }}:{{ .Values.service.port | default 2455 }}/health/ready || exit 1 + echo "Readiness check passed!" From 6be50b50ee401ec28b5106dc682b075587f555e9 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 16:12:08 +0900 Subject: [PATCH 5/9] helm: fix global nodeSelector rendering across pod specs --- deploy/helm/codex-lb/templates/_helpers.tpl | 2 +- deploy/helm/codex-lb/templates/deployment.yaml | 8 ++++---- .../helm/codex-lb/templates/hooks/db-init-job.yaml | 4 ++-- .../helm/codex-lb/templates/hooks/migration-job.yaml | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/deploy/helm/codex-lb/templates/_helpers.tpl b/deploy/helm/codex-lb/templates/_helpers.tpl index e70b568d..89971479 100644 --- a/deploy/helm/codex-lb/templates/_helpers.tpl +++ b/deploy/helm/codex-lb/templates/_helpers.tpl @@ -144,7 +144,7 @@ Image string — resolves registry/repository:tag with optional digest override Merged nodeSelector: global.nodeSelector + local nodeSelector (local wins). */}} {{- define "codex-lb.nodeSelector" -}} -{{- $merged := mustMerge (.Values.nodeSelector | default dict) (.Values.global.nodeSelector | default dict) -}} +{{- $merged := mustMergeOverwrite (.Values.global.nodeSelector | default dict) (.Values.nodeSelector | default dict) -}} {{- if $merged }} {{- toYaml $merged }} {{- end }} diff --git a/deploy/helm/codex-lb/templates/deployment.yaml b/deploy/helm/codex-lb/templates/deployment.yaml index 99eaefb4..e0fcecd5 100644 --- a/deploy/helm/codex-lb/templates/deployment.yaml +++ b/deploy/helm/codex-lb/templates/deployment.yaml @@ -89,10 +89,10 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} - {{- with (include "codex-lb.nodeSelector" .) }} - nodeSelector: - {{- . | nindent 8 }} - {{- end }} + {{- with (include "codex-lb.nodeSelector" .) }} + nodeSelector: + {{- . | nindent 8 }} + {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} diff --git a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml index 38bdb2e0..b6bc68c8 100644 --- a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml @@ -14,9 +14,9 @@ spec: template: spec: restartPolicy: OnFailure - {{- with .Values.nodeSelector }} + {{- with (include "codex-lb.nodeSelector" .) }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- . | nindent 8 }} {{- end }} containers: - name: db-init diff --git a/deploy/helm/codex-lb/templates/hooks/migration-job.yaml b/deploy/helm/codex-lb/templates/hooks/migration-job.yaml index 097bfbc0..ee238765 100644 --- a/deploy/helm/codex-lb/templates/hooks/migration-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/migration-job.yaml @@ -35,12 +35,12 @@ spec: {{- range $pullSecrets }} - name: {{ . }} {{- end }} - {{- end }} - {{- with (include "codex-lb.nodeSelector" .) }} - nodeSelector: - {{- . | nindent 8 }} - {{- end }} - {{- if .Values.postgresql.enabled }} + {{- end }} + {{- with (include "codex-lb.nodeSelector" .) }} + nodeSelector: + {{- . | nindent 8 }} + {{- end }} + {{- if .Values.postgresql.enabled }} initContainers: - name: wait-for-db image: postgres:16-alpine From 2215aaf599e15c8ac64a5423d5a4305725a38573 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 16:22:51 +0900 Subject: [PATCH 6/9] helm: align HTTPRoute backend port and harden db-init SQL quoting --- .../codex-lb/templates/hooks/db-init-job.yaml | 27 +++++++++++++++---- deploy/helm/codex-lb/templates/httproute.yaml | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml index b6bc68c8..faefb29b 100644 --- a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml @@ -27,14 +27,31 @@ spec: PGPASSWORD="$ADMIN_PASSWORD" psql \ -h "$DB_HOST" -p "$DB_PORT" -U "$ADMIN_USER" -d postgres <<'SQL' {{- range .Values.dbInit.databases }} + {{- $dbTag := printf "db_%s" ((printf "%s" .name) | sha256sum | trunc 12) }} + {{- $userTag := printf "user_%s" ((printf "%s" .user) | sha256sum | trunc 12) }} + {{- $passTag := printf "pass_%s" ((printf "%s" .password) | sha256sum | trunc 12) }} DO $$ BEGIN - IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '{{ .user }}') THEN - CREATE ROLE {{ .user }} WITH LOGIN PASSWORD '{{ .password }}'; + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = ${{ $userTag }}${{ .user }}${{ $userTag }}$) THEN + EXECUTE format( + 'CREATE ROLE %I WITH LOGIN PASSWORD %L', + ${{ $userTag }}${{ .user }}${{ $userTag }}$, + ${{ $passTag }}${{ .password }}${{ $passTag }}$ + ); END IF; END $$; - SELECT format('CREATE DATABASE %I OWNER %I', '{{ .name }}', '{{ .user }}') - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '{{ .name }}')\gexec - GRANT ALL PRIVILEGES ON DATABASE {{ .name }} TO {{ .user }}; + SELECT format( + 'CREATE DATABASE %I OWNER %I', + ${{ $dbTag }}${{ .name }}${{ $dbTag }}$, + ${{ $userTag }}${{ .user }}${{ $userTag }}$ + ) + WHERE NOT EXISTS ( + SELECT FROM pg_database WHERE datname = ${{ $dbTag }}${{ .name }}${{ $dbTag }}$ + )\gexec + SELECT format( + 'GRANT ALL PRIVILEGES ON DATABASE %I TO %I', + ${{ $dbTag }}${{ .name }}${{ $dbTag }}$, + ${{ $userTag }}${{ .user }}${{ $userTag }}$ + )\gexec {{- end }} SQL env: diff --git a/deploy/helm/codex-lb/templates/httproute.yaml b/deploy/helm/codex-lb/templates/httproute.yaml index 68179741..13c070de 100644 --- a/deploy/helm/codex-lb/templates/httproute.yaml +++ b/deploy/helm/codex-lb/templates/httproute.yaml @@ -20,5 +20,5 @@ spec: rules: - backendRefs: - name: {{ include "codex-lb.fullname" . }} - port: 2455 + port: {{ .Values.service.port }} {{- end }} From c9578c680c936a7328a3ea006c618f33d0721a9b Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 16:33:16 +0900 Subject: [PATCH 7/9] helm: harden db-init hook security and pull secret handling --- .../codex-lb/templates/hooks/db-init-job.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml index faefb29b..09e68bdd 100644 --- a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml @@ -14,6 +14,18 @@ spec: template: spec: restartPolicy: OnFailure + automountServiceAccountToken: false + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- $pullSecrets := concat (.Values.global.imagePullSecrets | default list) (.Values.image.pullSecrets | default list) }} + {{- if $pullSecrets }} + imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} {{- with (include "codex-lb.nodeSelector" .) }} nodeSelector: {{- . | nindent 8 }} @@ -21,6 +33,11 @@ spec: containers: - name: db-init image: {{ printf "%s/bitnami/postgresql:16" (.Values.global.imageRegistry | default "docker.io") }} + imagePullPolicy: IfNotPresent + {{- with .Values.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} command: ["sh", "-ec"] args: - | From 142e8e9473587b6b48aad246ccad7a4af1be92d9 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 16:56:23 +0900 Subject: [PATCH 8/9] helm: keep app nodeSelector off hook and test pods --- deploy/helm/codex-lb/templates/_helpers.tpl | 9 +++++++++ deploy/helm/codex-lb/templates/hooks/db-init-job.yaml | 2 +- deploy/helm/codex-lb/templates/hooks/migration-job.yaml | 2 +- .../helm/codex-lb/templates/tests/test-connection.yaml | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/deploy/helm/codex-lb/templates/_helpers.tpl b/deploy/helm/codex-lb/templates/_helpers.tpl index 89971479..634338c4 100644 --- a/deploy/helm/codex-lb/templates/_helpers.tpl +++ b/deploy/helm/codex-lb/templates/_helpers.tpl @@ -149,3 +149,12 @@ Merged nodeSelector: global.nodeSelector + local nodeSelector (local wins). {{- toYaml $merged }} {{- end }} {{- end -}} + +{{/* +Global-only nodeSelector for hooks/tests so app-specific placement does not block installs. +*/}} +{{- define "codex-lb.globalNodeSelector" -}} +{{- with (.Values.global.nodeSelector | default dict) }} +{{- toYaml . }} +{{- end }} +{{- end -}} diff --git a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml index 09e68bdd..6e1c1b11 100644 --- a/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/db-init-job.yaml @@ -26,7 +26,7 @@ spec: - name: {{ . }} {{- end }} {{- end }} - {{- with (include "codex-lb.nodeSelector" .) }} + {{- with (include "codex-lb.globalNodeSelector" .) }} nodeSelector: {{- . | nindent 8 }} {{- end }} diff --git a/deploy/helm/codex-lb/templates/hooks/migration-job.yaml b/deploy/helm/codex-lb/templates/hooks/migration-job.yaml index ee238765..9cef1699 100644 --- a/deploy/helm/codex-lb/templates/hooks/migration-job.yaml +++ b/deploy/helm/codex-lb/templates/hooks/migration-job.yaml @@ -36,7 +36,7 @@ spec: - name: {{ . }} {{- end }} {{- end }} - {{- with (include "codex-lb.nodeSelector" .) }} + {{- with (include "codex-lb.globalNodeSelector" .) }} nodeSelector: {{- . | nindent 8 }} {{- end }} diff --git a/deploy/helm/codex-lb/templates/tests/test-connection.yaml b/deploy/helm/codex-lb/templates/tests/test-connection.yaml index e230f9b9..3dfeeaca 100644 --- a/deploy/helm/codex-lb/templates/tests/test-connection.yaml +++ b/deploy/helm/codex-lb/templates/tests/test-connection.yaml @@ -15,7 +15,7 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - {{- with (include "codex-lb.nodeSelector" .) }} + {{- with (include "codex-lb.globalNodeSelector" .) }} nodeSelector: {{- . | nindent 4 }} {{- end }} From 51a59f528d3ee9c9d36f13e565c0798d170ffd5d Mon Sep 17 00:00:00 2001 From: Soju06 Date: Fri, 3 Apr 2026 17:06:15 +0900 Subject: [PATCH 9/9] helm: deep-copy global nodeSelector before merge --- deploy/helm/codex-lb/templates/_helpers.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/codex-lb/templates/_helpers.tpl b/deploy/helm/codex-lb/templates/_helpers.tpl index 634338c4..833b92bb 100644 --- a/deploy/helm/codex-lb/templates/_helpers.tpl +++ b/deploy/helm/codex-lb/templates/_helpers.tpl @@ -144,7 +144,7 @@ Image string — resolves registry/repository:tag with optional digest override Merged nodeSelector: global.nodeSelector + local nodeSelector (local wins). */}} {{- define "codex-lb.nodeSelector" -}} -{{- $merged := mustMergeOverwrite (.Values.global.nodeSelector | default dict) (.Values.nodeSelector | default dict) -}} +{{- $merged := mustMergeOverwrite (deepCopy (.Values.global.nodeSelector | default dict)) (.Values.nodeSelector | default dict) -}} {{- if $merged }} {{- toYaml $merged }} {{- end }}