Skip to content

Commit 066adcf

Browse files
committed
HYPERFLEET-937 - feat: add initSidecars support for Kubernetes native sidecar containers
Adds initSidecars list for injecting init containers with restartPolicy: Always (Kubernetes 1.28+). These start before db-migrate, solving the deadlock where database proxies in regular sidecars aren't available during migrations.
1 parent ac6d35e commit 066adcf

4 files changed

Lines changed: 129 additions & 37 deletions

File tree

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,27 @@ test-helm: ## Test Helm charts (lint, template, validate)
348348
--set-json 'sidecars=[{"name":"test-sidecar","image":"busybox:1.36","command":["sleep","infinity"]}]' > /dev/null
349349
@echo "Sidecar injection config template OK"
350350
@echo ""
351+
@echo "Testing template with native init sidecar injection..."
352+
helm template test-release charts/ \
353+
--set image.registry=quay.io \
354+
--set image.repository=openshift-hyperfleet/hyperfleet-api \
355+
--set image.tag=test \
356+
--set 'adapters.cluster=["validation"]' \
357+
--set 'adapters.nodepool=["validation"]' \
358+
--set-json 'initSidecars=[{"name":"cloud-sql-proxy","restartPolicy":"Always","image":"gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.3","args":["--structured-logs","--port=5432","project:region:instance"]}]' > /dev/null
359+
@echo "Init sidecar injection config template OK"
360+
@echo ""
361+
@echo "Testing template with init sidecars and no database..."
362+
helm template test-release charts/ \
363+
--set image.registry=quay.io \
364+
--set image.repository=openshift-hyperfleet/hyperfleet-api \
365+
--set image.tag=test \
366+
--set 'adapters.cluster=["validation"]' \
367+
--set 'adapters.nodepool=["validation"]' \
368+
--set database.postgresql.enabled=false \
369+
--set-json 'initSidecars=[{"name":"test-proxy","restartPolicy":"Always","image":"busybox:1.36","command":["sleep","infinity"]}]' > /dev/null
370+
@echo "Init sidecar without database config template OK"
371+
@echo ""
351372
@echo "Testing template with full adapter config..."
352373
helm template test-release charts/ \
353374
--set image.registry=quay.io \

charts/templates/deployment.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ spec:
3939
serviceAccountName: {{ include "hyperfleet-api.serviceAccountName" . }}
4040
securityContext:
4141
{{- toYaml .Values.podSecurityContext | nindent 8 }}
42-
{{- if or .Values.database.postgresql.enabled .Values.database.external.enabled }}
42+
{{- if or .Values.initSidecars (or .Values.database.postgresql.enabled .Values.database.external.enabled) }}
4343
initContainers:
44+
{{- range .Values.initSidecars }}
45+
- {{- toYaml . | nindent 8 }}
46+
{{- end }}
4447
{{- if .Values.database.postgresql.enabled }}
4548
- name: wait-for-db
4649
image: busybox:1.36

charts/values.yaml

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,44 @@ tracing:
278278
samplerArg: "1.0"
279279
propagators: "tracecontext,baggage"
280280

281+
# ============================================================
282+
# Init Sidecar Containers (Kubernetes Native Sidecars)
283+
# ============================================================
284+
# Native sidecars are init containers with restartPolicy: Always (Kubernetes 1.28+).
285+
# They start BEFORE other init containers and keep running throughout the pod lifecycle.
286+
# Use this for database proxies that must be available during db-migrate.
287+
# Each entry is a full Kubernetes container spec — you MUST include restartPolicy: Always.
288+
initSidecars: []
289+
# Example: Cloud SQL Auth Proxy as native sidecar
290+
# - name: cloud-sql-proxy
291+
# restartPolicy: Always
292+
# image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.3
293+
# args:
294+
# - "--structured-logs"
295+
# - "--port=5432"
296+
# - "PROJECT:REGION:INSTANCE"
297+
# securityContext:
298+
# allowPrivilegeEscalation: false
299+
# capabilities:
300+
# drop: [ALL]
301+
# readOnlyRootFilesystem: true
302+
# runAsNonRoot: true
303+
# seccompProfile:
304+
# type: RuntimeDefault
305+
# resources:
306+
# requests:
307+
# cpu: 100m
308+
# memory: 64Mi
309+
# limits:
310+
# cpu: 200m
311+
# memory: 128Mi
312+
281313
# ============================================================
282314
# Sidecar Containers
283315
# ============================================================
284-
# Generic sidecar injection — each entry is a full Kubernetes container spec.
316+
# Regular sidecar injection — each entry is a full Kubernetes container spec.
317+
# These start AFTER init containers complete. Use initSidecars above for
318+
# containers that must be available during init (e.g., database proxies).
285319
# Use extraVolumes/extraVolumeMounts for sidecar volume needs.
286320
# Operators SHOULD enforce container security via admission policies (PodSecurity Standards, Kyverno, etc.).
287321
sidecars: []
@@ -324,20 +358,6 @@ sidecars: []
324358
# requests:
325359
# cpu: 50m
326360
# memory: 64Mi
327-
#
328-
# Example: Cloud SQL Auth Proxy (GCP)
329-
# - name: cloud-sql-proxy
330-
# image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.3
331-
# args:
332-
# - "--structured-logs"
333-
# - "--port=5432"
334-
# - "PROJECT:REGION:INSTANCE"
335-
# securityContext:
336-
# runAsNonRoot: true
337-
# resources:
338-
# requests:
339-
# cpu: 100m
340-
# memory: 64Mi
341361

342362
# ============================================================
343363
# Advanced Overrides (Escape Hatch)

docs/database.md

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,55 @@ The readiness probe (`/readyz`) pings the database with a separate timeout contr
184184

185185
## Sidecar Containers
186186

187-
The Helm chart supports generic sidecar injection via the `sidecars` list in `values.yaml`. Each entry is a full Kubernetes container spec injected into the deployment pod. This can be used for any purpose — database proxies, log shippers, monitoring agents, etc.
187+
The Helm chart supports two sidecar injection mechanisms:
188188

189-
A common use case is database proxy sidecars (PgBouncer, Cloud SQL Auth Proxy). The example below shows a PgBouncer configuration.
189+
| List | Where it renders | Starts when | Use case |
190+
|------|------------------|-------------|----------|
191+
| `initSidecars` | `initContainers` (with `restartPolicy: Always`) | Before all other init containers | Database proxies that must be available during `db-migrate` |
192+
| `sidecars` | `containers` | After all init containers complete | Log shippers, monitoring agents, connection poolers that don't need to run during init |
190193

191-
### Example: PgBouncer Sidecar
194+
Each entry in either list is a full Kubernetes container spec injected as-is into the deployment pod.
195+
196+
### Native Sidecars (`initSidecars`)
197+
198+
Kubernetes 1.28+ supports [native sidecar containers](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/) — init containers declared with `restartPolicy: Always`. They start before other init containers and keep running throughout the pod lifecycle.
199+
200+
Use `initSidecars` for database proxies (Cloud SQL Auth Proxy, AlloyDB Auth Proxy) that must be reachable when the `db-migrate` init container runs. Without this, the migration deadlocks: the init container waits for the proxy, but regular sidecars only start after all init containers finish.
201+
202+
### Example: Cloud SQL Auth Proxy (Native Sidecar)
203+
204+
```yaml
205+
# values.yaml
206+
initSidecars:
207+
- name: cloud-sql-proxy
208+
restartPolicy: Always
209+
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.3
210+
args:
211+
- "--structured-logs"
212+
- "--port=5432"
213+
- "PROJECT:REGION:INSTANCE"
214+
securityContext:
215+
allowPrivilegeEscalation: false
216+
capabilities:
217+
drop: [ALL]
218+
readOnlyRootFilesystem: true
219+
runAsNonRoot: true
220+
seccompProfile:
221+
type: RuntimeDefault
222+
resources:
223+
requests:
224+
cpu: 100m
225+
memory: 64Mi
226+
limits:
227+
cpu: 200m
228+
memory: 128Mi
229+
```
230+
231+
With this setup, both the `db-migrate` init container and the runtime API container connect through the proxy — no `extraEnv` override needed since the proxy listens on `localhost:5432`.
232+
233+
### Regular Sidecars (`sidecars`)
234+
235+
Use the `sidecars` list for containers that don't need to be available during init. The example below shows a PgBouncer connection pooler:
192236

193237
```yaml
194238
# values.yaml
@@ -233,7 +277,7 @@ sidecars:
233277
memory: 64Mi
234278
```
235279

236-
When using a proxy sidecar, the API container must connect to the proxy instead of PostgreSQL directly. The `database.external.secretName` secret must contain the **direct** database endpoint (host/port) so the `db-migrate` init container can run migrations before sidecars start. To route runtime traffic through the proxy, use `extraEnv` overrides in the main container:
280+
When using a regular sidecar proxy, the `db-migrate` init container connects directly to the database (regular sidecars aren't running yet). Route runtime traffic through the proxy with `extraEnv`:
237281

238282
```yaml
239283
extraEnv:
@@ -245,29 +289,33 @@ extraEnv:
245289

246290
Use `extraVolumes` and `extraVolumeMounts` for any volumes the sidecar requires (e.g., temp dirs, config dirs).
247291

248-
### Proxy Architecture
292+
### Architecture
249293

250294
```text
251-
┌─────────────────────────────────────────┐
252-
│ Pod │
253-
│ │
254-
│ ┌──────────────┐ ┌──────────┐ │
255-
│ │ hyperfleet │────▶│ proxy │─────┼──▶ Database
256-
│ │ API │ │ sidecar │ │
257-
│ │ (localhost) │ └──────────┘ │
258-
│ └──────────────┘ │
259-
│ │
260-
│ Init containers (migrate) ─────────────┼──▶ Database
261-
│ (direct connection, bypasses proxy) │
262-
└─────────────────────────────────────────┘
295+
┌────────────────────────────────────────────────────────┐
296+
│ Pod │
297+
│ │
298+
│ initSidecars (restartPolicy: Always) │
299+
│ ┌──────────────────┐ │
300+
│ │ cloud-sql-proxy │◄─── starts first, stays running │
301+
│ └────────┬─────────┘ │
302+
│ │ │
303+
│ initContainers │
304+
│ ┌──────────────────┐ │
305+
│ │ db-migrate │──▶ proxy ──▶ Database │
306+
│ └──────────────────┘ │
307+
│ │
308+
│ containers │
309+
│ ┌──────────────────┐ │
310+
│ │ hyperfleet-api │──▶ proxy ──▶ Database │
311+
│ └──────────────────┘ │
312+
└────────────────────────────────────────────────────────┘
263313
```
264314

265-
- Init containers (migrations) should connect directly to the database — they run before sidecars start, and DDL operations may not be compatible with connection pooling.
266-
267315
### Common Proxy Choices
268316

269-
- **PgBouncer**: Lightweight connection pooler. Use `transaction` pool mode for stateless APIs. See commented example in `values.yaml`.
270-
- **Cloud SQL Auth Proxy**: Required for GCP Cloud SQL access without complex network/IP setup. See commented example in `values.yaml`.
317+
- **Cloud SQL Auth Proxy**: Required for GCP Cloud SQL. Use `initSidecars` so migrations can reach the database through the proxy.
318+
- **PgBouncer**: Lightweight connection pooler. Use `transaction` pool mode for stateless APIs. Can go in either `initSidecars` or `sidecars` depending on whether migrations need it.
271319

272320
## Related Documentation
273321

0 commit comments

Comments
 (0)