Skip to content

Managed resources ​

The resource layer lives in internal/resources/ and produces native Kubernetes objects — Deployment, StatefulSet, Service, Secret, ConfigMap, Ingress. Anything that isn't delegated to an upstream operator lives here.

Design rules ​

  • Pure builders. Like the wiring layer, these are pure functions: take a Palena CR, return a typed Kubernetes object. No client.Client, no I/O.
  • Standard labels everywhere. Every resource gets the same base label set so kubectl get all -l operator.palena.ai/gateway=production works consistently.
  • Secrets are generated, not hard-coded. For resources that need a random password (Redis, MongoDB root, MeiliSearch master key), Palena generates a 32-char random string on first reconciliation and writes it to a Secret. The Secret is never overwritten — rotation is an explicit, separate operation.

Components ​

Redis (redis.go) ​

Managed Redis for LiteLLM rate limiting, caching, and routing.

ResourceNamePurpose
Secret<gw>-palena-redisContains host, port, password.
StatefulSet<gw>-palena-redisRedis pods with PVC per replica.
Service (headless)<gw>-palena-redisPod DNS for StatefulSet.
Service (ClusterIP)<gw>-palena-redis-svcClient-facing.
yaml
image: redis:7-alpine
command: ["redis-server", "--requirepass", "$(REDIS_PASSWORD)", "--appendonly", "yes"]
ports: [6379]
volumeClaimTemplate:
  storage: <spec.redis.managed.storageSize>
resources:
  requests: { cpu: 100m, memory: 128Mi }
  limits:   { cpu: 500m, memory: 512Mi }
readinessProbe:
  exec: { command: ["redis-cli", "-a", "$(REDIS_PASSWORD)", "ping"] }

LibreChat (librechat/) ​

Four files:

  • deployment.go — the LibreChat Deployment, with config hash annotation for rolling restarts.
  • configmap.go — the generated librechat.yaml ConfigMap.
  • service.go — ClusterIP Service on port 3080.
  • ingress.go — Ingress (or Route on OpenShift).

Deployment shape ​

yaml
image: <spec.image or "ghcr.io/danny-avila/librechat:latest">
replicas: <spec.replicas>
ports: [3080]
env:
  - MONGO_URI: from MongoDB Secret
  - MEILI_HOST: http://<ui>-palena-meilisearch:7700
  - MEILI_MASTER_KEY: from MeiliSearch Secret
volumes:
  - name: config
    configMap: { name: <ui>-palena-librechat-config }
    mountPath: /app/librechat.yaml
    subPath: librechat.yaml
  - name: branding    # if logoConfigMapRef
    configMap: { name: <logoConfigMapRef.name> }
    mountPath: /app/client/public/img/custom
podAnnotations:
  operator.palena.ai/config-hash: <sha256 of librechat.yaml>

librechat.yaml generation ​

The operator builds the full LibreChat config from CR spec + gateway status + MCP server status:

go
func BuildLibreChatConfig(ui *PalenaUI, gatewayEndpoint string, mcpEndpoints map[string]string) string {
    config := LibreChatConfig{
        Version: "1.2.6",
        Cache:   true,
        Endpoints: map[string]EndpointConfig{
            "custom": {
                APIKey:  "${LITELLM_API_KEY}",
                BaseURL: gatewayEndpoint + "/v1",
                Models:  map[string]ModelConfig{"default": {FetchModels: true}},
            },
        },
    }

    if len(mcpEndpoints) > 0 {
        config.MCPServers = map[string]MCPServerConfig{}
        for name, endpoint := range mcpEndpoints {
            config.MCPServers[name] = MCPServerConfig{Type: "sse", URL: endpoint}
        }
    }

    if ui.Spec.Branding != nil {
        config.AppTitle = ui.Spec.Branding.AppTitle
        config.AppDescription = ui.Spec.Branding.AppDescription
    }

    if ui.Spec.Auth != nil && ui.Spec.Auth.DisableSignup {
        config.Registration.SocialLogins = []string{"openid"}
    }

    return marshalYAML(config)
}

The SHA-256 of the marshaled YAML is attached to the pod template so that any config change (new model, new MCP endpoint, new branding) triggers a rolling restart automatically.

OIDC env vars ​

When auth.oidc.enabled, the operator adds:

OPENID_ISSUER=<spec.auth.oidc.issuer>
OPENID_CLIENT_ID=<from secret>
OPENID_CLIENT_SECRET=<from secret>
OPENID_CALLBACK_URL=https://<ingress.host>/oauth/openid/callback
OPENID_SCOPE=openid profile email

MongoDB (mongodb.go) ​

ResourceName
Secret<ui>-palena-mongodb (root password, connection URI)
StatefulSet<ui>-palena-mongodb
Service<ui>-palena-mongodb
yaml
image: mongo:7
ports: [27017]
env:
  - MONGO_INITDB_ROOT_USERNAME: admin
  - MONGO_INITDB_ROOT_PASSWORD: <from Secret>
volumeClaimTemplate:
  storage: <spec.mongodb.managed.storageSize>
readinessProbe:
  exec: { command: ["mongosh", "--eval", "db.adminCommand('ping')"] }

Connection URI stored in the Secret:

mongodb://admin:<pw>@<ui>-palena-mongodb.<ns>.svc.cluster.local:27017/librechat?authSource=admin

MeiliSearch (meilisearch.go) ​

ResourceName
Secret<ui>-palena-meilisearch (master key)
Deployment<ui>-palena-meilisearch
Service<ui>-palena-meilisearch
PVC<ui>-palena-meilisearch-data
yaml
image: getmeili/meilisearch:latest
ports: [7700]
env:
  - MEILI_MASTER_KEY: <from Secret>
  - MEILI_DB_PATH: /meili_data
readinessProbe:
  httpGet: { path: /health, port: 7700 }

Websearch MCP (websearch/) ​

The websearch MCP stack is a small bundle of cooperating deployments. Each file is an independent builder:

FileWhat it builds
deployment.goThe MCP server Deployment + Service (<mcp>-palena-websearch)
searxng.goSearXNG Deployment + Service + settings.yml ConfigMap
chromium.goOptional Chromium scraper Deployment + Service
presidio.goOptional Presidio analyzer + anonymizer Deployments
flashrank.goOptional FlashRank reranker Deployment
configmap.goWebsearch config ConfigMap pointing at enabled sidecars
common.goDefault image constants, port constants, labels

MCP server deployment ​

yaml
image: <spec.image or "ghcr.io/palenaai/palena-websearch-mcp:latest">
replicas: <spec.replicas>
ports: [8080]
env:
  - CONFIG_PATH: /etc/palena/config.yaml
volumeMounts:
  - name: config
    configMap: { name: <mcp>-config }
    mountPath: /etc/palena
readinessProbe:
  httpGet: { path: /health, port: 8080 }

SearXNG ​

yaml
image: searxng/searxng:latest
replicas: <spec.websearch.searxng.replicas>
ports: [8080]
volumeMounts:
  - name: settings
    configMap: { name: <mcp>-searxng-settings }
    mountPath: /etc/searxng/settings.yml
    subPath: settings.yml
env:
  - SEARXNG_BASE_URL: "http://localhost:8080"

settings.yml is generated from spec.websearch.searxng.engines.

Chromium scraper ​

yaml
image: ghcr.io/browserless/chromium:latest
replicas: <spec.websearch.scraper.chromiumReplicas>
ports: [3000]
env:
  - MAX_CONCURRENT_SESSIONS: <spec.websearch.scraper.maxConcurrency>
  - CONNECTION_TIMEOUT: 30000
resources:
  requests: { cpu: 500m, memory: 1Gi }
  limits:   { cpu: "2",  memory: "2Gi" }

Presidio (PII) ​

yaml
# Analyzer
image: mcr.microsoft.com/presidio-analyzer:latest
ports: [5002]

# Anonymizer
image: mcr.microsoft.com/presidio-anonymizer:latest
ports: [5001]

FlashRank ​

yaml
image: ghcr.io/palenaai/palena-flashrank:latest
replicas: 1
ports: [8000]
resources:
  requests: { cpu: 500m, memory: 512Mi }
  limits:   { cpu: "1",  memory: "1Gi" }
readinessProbe:
  httpGet: { path: /health, port: 8000 }

Websearch config generation ​

go
func BuildWebsearchConfig(mcp *PalenaMCPServer) string {
    ws := mcp.Spec.Websearch
    config := WebsearchConfig{
        Search: SearchConfig{
            SearXNG: SearXNGConfig{
                Endpoint: fmt.Sprintf("http://%s-searxng.%s.svc:8080", mcp.Name, mcp.Namespace),
            },
        },
    }
    if ws.Scraper != nil && ws.Scraper.ChromiumEnabled {
        config.Scraper.Chromium.Endpoint = fmt.Sprintf("http://%s-chromium.%s.svc:3000", mcp.Name, mcp.Namespace)
        config.Scraper.Chromium.Enabled = true
    }
    if ws.Presidio != nil && ws.Presidio.Enabled {
        config.PII.Enabled = true
        config.PII.Mode = ws.Presidio.Mode
        config.PII.AnalyzerEndpoint   = fmt.Sprintf("http://%s-presidio-analyzer.%s.svc:5002",  mcp.Name, mcp.Namespace)
        config.PII.AnonymizerEndpoint = fmt.Sprintf("http://%s-presidio-anonymizer.%s.svc:5001", mcp.Name, mcp.Namespace)
    }
    if ws.Reranker != nil {
        config.Reranker.Provider = ws.Reranker.Provider
        if ws.Reranker.Provider == "flashrank" {
            config.Reranker.FlashRank.Endpoint = fmt.Sprintf("http://%s-flashrank.%s.svc:8000", mcp.Name, mcp.Namespace)
        }
    }
    return marshalYAML(config)
}

NetworkPolicies (networkpolicy.go) ​

Generated for PalenaGateway when security.networkPolicies.enabled.

Default deny ​

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: <gw>-palena-default-deny
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/part-of: palena
      operator.palena.ai/gateway: <gw>
  policyTypes: [Ingress, Egress]
  ingress: []
  egress: []

Per-path allow rules ​

One NetworkPolicy per allowed communication path:

  • <gw>-allow-litellm-to-pg — LiteLLM → PostgreSQL (5432)
  • <gw>-allow-litellm-to-redis — LiteLLM → Redis (6379)
  • <gw>-allow-ingress-to-litellm — Ingress → LiteLLM (4000)

The UI controller adds:

  • <ui>-allow-librechat-to-litellm — LibreChat → LiteLLM (4000)
  • <ui>-allow-librechat-to-mongodb — LibreChat → MongoDB (27017)
  • <ui>-allow-ingress-to-librechat — Ingress → LibreChat (3080)

Common helpers ​

Standard labels ​

go
func StandardLabels(component, instance, role, gatewayName string) map[string]string {
    labels := map[string]string{
        "app.kubernetes.io/name":       component,
        "app.kubernetes.io/instance":   instance,
        "app.kubernetes.io/component":  role,
        "app.kubernetes.io/part-of":    "palena",
        "app.kubernetes.io/managed-by": "palena-operator",
    }
    if gatewayName != "" {
        labels["operator.palena.ai/gateway"] = gatewayName
    }
    return labels
}

Resource naming ​

go
func ResourceName(crName, suffix string) string {
    return fmt.Sprintf("%s-palena-%s", crName, suffix)
}

Random string (for generated Secrets) ​

go
func generateRandomString(length int) string {
    const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
    b := make([]byte, length)
    for i := range b {
        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
        b[i] = chars[n.Int64()]
    }
    return string(b)
}

Uses crypto/rand, not math/rand — Palena generates credentials for production workloads, so the entropy matters.

Released under the Apache 2.0 License. "Palena" is a trademark of bitkaio LLC.