Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions helm/codeapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ verifier through environment variables on the api component, e.g.:

```yaml
api:
extraEnv:
- name: CODEAPI_AUTH_PROVIDER
value: librechat-jwt
- name: CODEAPI_JWT_PUBLIC_KEY # single PEM/base64-DER verifier key
valueFrom:
secretKeyRef:
name: codeapi-jwt-verifier
key: public-key
- name: CODEAPI_JWT_KID
value: my-key-id
extraEnv:
- name: CODEAPI_AUTH_PROVIDER
value: librechat-jwt
- name: CODEAPI_JWT_PUBLIC_KEY # single PEM/base64-DER verifier key
valueFrom:
secretKeyRef:
name: codeapi-jwt-verifier
key: public-key
- name: CODEAPI_JWT_KID
value: my-key-id
```

`CODEAPI_JWT_PUBLIC_KEYS_DIR` (a mounted directory of PEM files) and
Expand All @@ -78,14 +78,40 @@ For development only, `LOCAL_MODE=true` bypasses authentication β€” see
**TLS to Redis.** Set `REDIS_TLS=true` via `extraEnv` on each component when
your Redis (e.g. a managed cache) requires TLS.

**Redis Cluster mode (GCP Memorystore cluster, AWS ElastiCache cluster).** Set
`redis.enabled=false` (so the bundled Redis subchart doesn't take precedence)
and `redis.cluster.enabled=true`, and provide the comma-separated startup nodes
in `redis.cluster.nodes`. The chart will set `USE_REDIS_CLUSTER=true` and
populate `REDIS_HOST` with the node list on every component. For TLS with a CA
certificate (recommended for GCP Memorystore), also set:

```yaml
redis:
enabled: false # required β€” otherwise the bundled subchart is used instead
cluster:
enabled: true
nodes: 'node-1:6379,node-2:6379,node-3:6379'
tls:
enabled: true
caSecretName: my-memorystore-secret # Secret that holds the CA cert
caKey: ca # Key inside the Secret
caMountPath: /etc/redis-tls/ca.crt # Mount path in each pod
useAlternativeDnsLookup: true # Required for GCP Memorystore cluster
```

The CA certificate is mounted from the Secret into every component pod and
passed to ioredis via `REDIS_CA`. No sidecar is needed.

## Quick Start (Local Development)

### 1. Start Minikube

```bash
minikube start --cpus=4 --memory=8192
```

### 2. Build Images Inside Minikube

```bash
# Point docker to minikube's daemon
eval $(minikube docker-env)
Expand All @@ -100,6 +126,7 @@ docker build -t codeapi-package-init:latest -f docker/Dockerfile.package-init .
```

### 3. Install Dependencies & Deploy

```bash
cd helm/codeapi

Expand Down Expand Up @@ -151,6 +178,7 @@ kubectl rollout restart deployment/codeapi-sandbox-runner
```

### 5. Access the API

```bash
# Port forward (in another terminal)
kubectl port-forward svc/codeapi-api 3112:3112
Expand All @@ -164,6 +192,7 @@ curl http://localhost:3112/v1/health
## Commands Reference

### Startup

```bash
# Start minikube
minikube start
Expand All @@ -176,6 +205,7 @@ kubectl port-forward svc/codeapi-api 3112:3112
```

### Check Status

```bash
# View all pods
kubectl get pods
Expand All @@ -190,6 +220,7 @@ kubectl describe pod <pod-name>
```

### Scaling

```bash
# Scale the sandbox execution tier
kubectl scale deployment/codeapi-sandbox-runner --replicas=10
Expand All @@ -200,6 +231,7 @@ helm upgrade codeapi ./helm/codeapi -f ./helm/codeapi/values-local.yaml \
```

### Update After Code Changes

```bash
# Rebuild images (must be in minikube docker env)
eval $(minikube docker-env)
Expand All @@ -212,6 +244,7 @@ kubectl rollout restart deployment/codeapi-sandbox-runner
```

### Teardown

```bash
# Uninstall the Helm release (removes all K8s resources)
helm uninstall codeapi
Expand All @@ -228,12 +261,14 @@ minikube delete
## Testing

### Health Check

```bash
curl http://localhost:3112/v1/health
# Expected: OK
```

### Execute Python Code

```bash
curl -X POST http://localhost:3112/v1/exec \
-H "Content-Type: application/json" \
Expand All @@ -242,6 +277,7 @@ curl -X POST http://localhost:3112/v1/exec \
```

### Verify Horizontal Scaling

```bash
# Check which service-worker processed the job
kubectl logs deployment/codeapi-service-worker --tail=5
Expand Down Expand Up @@ -299,6 +335,7 @@ kubectl logs deployment/codeapi-service-worker --tail=5
## Troubleshooting

### Pod stuck in `ErrImageNeverPull`

```bash
# Images must be built inside minikube's docker
eval $(minikube docker-env)
Expand All @@ -307,13 +344,15 @@ kubectl rollout restart deployment/<deployment-name>
```

### Pod stuck in `CrashLoopBackOff`

```bash
# Check logs
kubectl logs <pod-name> --previous
kubectl describe pod <pod-name>
```

### "runtime is unknown" error

```bash
# Language packages PVC is empty. Check if the init job ran:
kubectl get jobs -l app.kubernetes.io/component=package-init
Expand All @@ -327,12 +366,14 @@ kubectl rollout restart deployment/codeapi-sandbox-runner
```

### Connection refused on port 3112

```bash
# Make sure port-forward is running
kubectl port-forward svc/codeapi-api 3112:3112
```

### MinIO `ImagePullBackOff` (production values)

```bash
# The Bitnami MinIO chart may reference unavailable image tags.
# For local dev, values-local.yaml uses minio.useSimple=true which
Expand Down
115 changes: 114 additions & 1 deletion helm/codeapi/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,15 @@ app.kubernetes.io/component: tool-call-server
{{- end }}

{{/*
Redis host - either from subchart or external
Redis host - either from subchart or external.
In cluster mode this renders the comma-separated node list from redis.cluster.nodes
(falling back to redis.external.host for single-node external deployments).
*/}}
{{- define "codeapi.redis.host" -}}
{{- if .Values.redis.enabled }}
{{- printf "%s-redis-master" .Release.Name }}
{{- else if and .Values.redis.cluster.enabled .Values.redis.cluster.nodes }}
{{- .Values.redis.cluster.nodes }}
Comment thread
pedrojreis marked this conversation as resolved.
{{- else }}
{{- .Values.redis.external.host }}
{{- end }}
Expand All @@ -173,6 +177,115 @@ Redis port
{{- end }}
{{- end }}

{{/*
Redis host used ONLY for the wait-for-redis readiness probe. In cluster mode
codeapi.redis.host renders the full comma-separated startup-node list
("n1:6379,n2:6379,..."), which `nc -z` cannot resolve as a single
[destination] [port] pair. This always resolves to one reachable host β€”
the first cluster startup node when in cluster mode, otherwise the same
value as codeapi.redis.host.
*/}}
{{- define "codeapi.redis.probeHost" -}}
{{- if .Values.redis.enabled }}
{{- printf "%s-redis-master" .Release.Name }}
{{- else if and .Values.redis.cluster.enabled .Values.redis.cluster.nodes }}
{{- $firstNode := .Values.redis.cluster.nodes | splitList "," | first }}
{{- $firstNode | splitList ":" | first }}
{{- else }}
{{- .Values.redis.external.host }}
{{- end }}
{{- end }}

{{/*
Port companion to codeapi.redis.probeHost.
*/}}
{{- define "codeapi.redis.probePort" -}}
{{- if .Values.redis.enabled }}
{{- "6379" }}
{{- else if and .Values.redis.cluster.enabled .Values.redis.cluster.nodes }}
{{- $firstNode := .Values.redis.cluster.nodes | splitList "," | first }}
{{- $parts := $firstNode | splitList ":" }}
{{- if gt (len $parts) 1 }}
{{- index $parts 1 }}
{{- else }}
{{- "6379" }}
{{- end }}
{{- else }}
{{- .Values.redis.external.port | default "6379" }}
{{- end }}
{{- end }}

{{/*
USE_REDIS_CLUSTER value – "true" when redis.cluster.enabled or when
redis.cluster.nodes contains a comma (auto-detect multiple nodes).
*/}}
{{- define "codeapi.redis.clusterEnabled" -}}
{{- if .Values.redis.cluster.enabled }}
{{- "true" }}
{{- else if and .Values.redis.cluster.nodes (contains "," .Values.redis.cluster.nodes) }}
{{- "true" }}
{{- else }}
{{- "false" }}
{{- end }}
{{- end }}

{{/*
Emit the Redis TLS + CA environment variables and volume mount for each
component that needs it. These only apply to an external managed Redis, so
they render nothing while the bundled subchart is enabled (redis.enabled=true
always uses plain TCP to the in-cluster Bitnami Redis) or when redis.tls.enabled
is false.
Usage: {{ include "codeapi.redis.tlsEnv" . }}
*/}}
{{- define "codeapi.redis.tlsEnv" -}}
{{- if not .Values.redis.enabled }}
{{- if .Values.redis.tls.enabled }}
- name: REDIS_TLS
value: "true"
Comment thread
pedrojreis marked this conversation as resolved.
{{- if .Values.redis.tls.caSecretName }}
- name: REDIS_CA
value: {{ .Values.redis.tls.caMountPath | quote }}
{{- end }}
{{- end }}
{{- if .Values.redis.useAlternativeDnsLookup }}
- name: REDIS_USE_ALTERNATIVE_DNS_LOOKUP
value: "true"
{{- end }}
{{- end }}
{{- end }}

{{/*
Volume definition for the Redis CA certificate secret.
Renders nothing when redis.tls.caSecretName is empty, or the bundled Redis
subchart is enabled (it never speaks TLS).
Usage: {{ include "codeapi.redis.caVolume" . }}
*/}}
{{- define "codeapi.redis.caVolume" -}}
{{- if and (not .Values.redis.enabled) .Values.redis.tls.enabled .Values.redis.tls.caSecretName }}
- name: redis-ca
secret:
secretName: {{ .Values.redis.tls.caSecretName }}
items:
- key: {{ .Values.redis.tls.caKey }}
path: ca.crt
{{- end }}
{{- end }}

{{/*
VolumeMount for the Redis CA certificate inside a container.
Renders nothing when redis.tls.caSecretName is empty, or the bundled Redis
subchart is enabled (it never speaks TLS).
Usage: {{ include "codeapi.redis.caVolumeMount" . }}
*/}}
{{- define "codeapi.redis.caVolumeMount" -}}
{{- if and (not .Values.redis.enabled) .Values.redis.tls.enabled .Values.redis.tls.caSecretName }}
- name: redis-ca
mountPath: {{ .Values.redis.tls.caMountPath | quote }}
subPath: ca.crt
readOnly: true
{{- end }}
{{- end }}

{{/*
Redis NetworkPolicy egress. Kubernetes NetworkPolicy cannot match DNS names,
so external Redis can be scoped with CIDRs when available; otherwise the chart
Expand Down
12 changes: 11 additions & 1 deletion helm/codeapi/templates/api-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ spec:
secretKeyRef:
name: {{ include "codeapi.fullname" . }}-secrets
key: redis-password
# Service URLs
- name: USE_REDIS_CLUSTER
value: {{ include "codeapi.redis.clusterEnabled" . | quote }}
{{- include "codeapi.redis.tlsEnv" . | nindent 12 }}
- name: FILE_SERVER_URL
value: "http://{{ include "codeapi.fullname" . }}-file-server:{{ .Values.fileServer.service.port }}"
- name: TOOL_CALL_SERVER_URL
Expand Down Expand Up @@ -87,6 +89,14 @@ spec:
periodSeconds: 10
resources:
{{- toYaml .Values.api.resources | nindent 12 }}
{{- with (include "codeapi.redis.caVolumeMount" . | trim) }}
volumeMounts:
{{- . | nindent 12 }}
{{- end }}
{{- with (include "codeapi.redis.caVolume" . | trim) }}
volumes:
{{- . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
11 changes: 11 additions & 0 deletions helm/codeapi/templates/egress-gateway-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ spec:
secretKeyRef:
name: {{ include "codeapi.fullname" . }}-secrets
key: redis-password
- name: USE_REDIS_CLUSTER
value: {{ include "codeapi.redis.clusterEnabled" . | quote }}
{{- include "codeapi.redis.tlsEnv" . | nindent 12 }}
livenessProbe:
httpGet:
path: /live
Expand All @@ -83,6 +86,14 @@ spec:
periodSeconds: 10
resources:
{{- toYaml .Values.egressGateway.resources | nindent 12 }}
{{- with (include "codeapi.redis.caVolumeMount" . | trim) }}
volumeMounts:
{{- . | nindent 12 }}
{{- end }}
{{- with (include "codeapi.redis.caVolume" . | trim) }}
volumes:
{{- . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
11 changes: 11 additions & 0 deletions helm/codeapi/templates/file-server-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ spec:
secretKeyRef:
name: {{ include "codeapi.fullname" . }}-secrets
key: redis-password
- name: USE_REDIS_CLUSTER
value: {{ include "codeapi.redis.clusterEnabled" . | quote }}
{{- include "codeapi.redis.tlsEnv" . | nindent 12 }}
- name: CODEAPI_INTERNAL_SERVICE_TOKEN
valueFrom:
secretKeyRef:
Expand All @@ -135,6 +138,14 @@ spec:
timeoutSeconds: 5
resources:
{{- toYaml .Values.fileServer.resources | nindent 12 }}
{{- with (include "codeapi.redis.caVolumeMount" . | trim) }}
volumeMounts:
{{- . | nindent 12 }}
{{- end }}
{{- with (include "codeapi.redis.caVolume" . | trim) }}
volumes:
{{- . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
Loading