Kubernetes and Helm
Deploy Sockudo with production Helm values, safe probes, disruption controls, ingress, and node tuning.
Kubernetes is a good Sockudo platform when you need repeatable rollouts, failure-domain placement, secret injection, metrics discovery, and several replicas. It does not remove the state model: pods still need shared adapters and stores, and scale-down still disconnects their clients.
Install the chart
helm upgrade --install sockudo oci://ghcr.io/sockudo/charts/sockudo \
--version 4.7.0 \
--namespace sockudo \
--create-namespace \
--values values-production.yamlPin the chart and image version in GitOps or release automation. Do not deploy latest.
To work on the chart itself, install from a checkout instead: helm upgrade --install sockudo ./charts/sockudo.
ArgoCD
The chart is published as an OCI artifact, so ArgoCD consumes it directly without cloning this repository:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: sockudo
namespace: argocd
spec:
project: default
source:
repoURL: ghcr.io/sockudo/charts
chart: sockudo
targetRevision: 4.7.0
helm:
valuesObject:
replicaCount: 3
destination:
server: https://kubernetes.default.svc
namespace: sockudo
syncPolicy:
syncOptions:
- CreateNamespace=trueNote repoURL carries no oci:// prefix: for a Helm chart source, ArgoCD takes the bare
registry path and infers the OCI protocol from chart being set.
Production values example
The following is a regional three-pod baseline with one immutable app sourced from a Kubernetes Secret and Redis used for fanout, cache, queue, and rate limits:
replicaCount: 3
image:
repository: ghcr.io/sockudo/sockudo
tag: "5.0.0"
pullPolicy: IfNotPresent
config:
mode: production
debug: false
logOutputFormat: json
rustLog: "info,sockudo=info"
shutdownGracePeriod: 30
adapterDriver: redis
appManagerDriver: memory
cacheDriver: redis
queueDriver: redis
rateLimiterDriver: redis
rateLimiter:
enabled: true
apiMaxRequests: 1000
apiWindowSeconds: 60
apiTrustHops: 1
wsMaxRequests: 100
wsWindowSeconds: 60
wsTrustHops: 1
metrics:
enabled: true
defaultApp:
enabled: true
existingSecret: sockudo-app
maxConnections: 50000
enableClientMessages: false
enableUserAuthentication: true
redis:
host: redis.internal
port: 6379
existingSecret: sockudo-redis
extraEnv:
- name: SOCKUDO_MAX_CONNECTIONS
value: "50000"
- name: ADAPTER_FALLBACK_TO_LOCAL
value: "false"
- name: SOCKUDO_DEFAULT_APP_ALLOWED_ORIGINS
value: "https://app.example.com"
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
memory: 2Gi
readinessProbe:
httpGet:
path: /up/production
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /live
port: http
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /live
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
terminationGracePeriodSeconds: 45
pdb:
enabled: true
minAvailable: 2
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: sockudo
serviceMonitor:
enabled: true
interval: 30s
scrapeTimeout: 10s
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "180"
nginx.ingress.kubernetes.io/proxy-send-timeout: "180"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
hosts:
- host: ws.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: sockudo-tls
hosts:
- ws.example.comAdapt the topology label selector when the release name or chart labels differ. If application
records must change dynamically, select a durable app manager instead of memory and configure its
database credentials from a Secret.
Create the one-app Secret without placing values in the shell history or a checked-in manifest. The required keys are:
default-app-id
default-app-key
default-app-secretFor advanced nested configuration, use the chart's configJson value or mount your own static
file. Keep secret values out of configJson; combine it with extraEnvFrom or file-backed Secrets.
Probes
Use different endpoints for different decisions:
| Probe | Endpoint | Meaning |
|---|---|---|
| Startup | /live | The process has started. |
| Liveness | /live | The process runtime is alive. |
| Readiness | /up or /up/<app-id> | Required apps and shared dependencies are available. |
Do not use dependency-aware /up as liveness. Restarting every pod because Redis is slow creates a
reconnect storm and does not repair Redis.
Set probe timeout below its period and above normal dependency-check latency. Sockudo's
health_check_timeout_ms bounds each dependency check separately.
Resources and connection admission
Persistent connections do not map cleanly onto CPU-only autoscaling:
- memory rises with sockets, subscriptions, pending writes, and per-connection feature state
- CPU rises with handshake, auth, messages, fanout, compression, filtering, and reconnect bursts
- a pod can have low CPU while holding too many quiet sockets to remove safely
Set SOCKUDO_MAX_CONNECTIONS on every pod so a single replica cannot absorb more connections than
its measured memory and file-descriptor budget. Keep minimum replicas high enough for the peak
quiet-socket count even when CPU is low.
CPU limits can throttle a pod during the exact reconnect or fanout burst it needs to process. Requests plus a memory limit are a reasonable starting point on a dedicated node pool. Add a CPU limit only when isolation requires it and benchmark with the same quota.
Autoscaling
The chart can configure an HPA:
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 12
targetCPUUtilizationPercentage: 65
targetMemoryUtilizationPercentage: 75
behavior:
scaleUp:
stabilizationWindowSeconds: 30
scaleDown:
stabilizationWindowSeconds: 600
policies:
- type: Pods
value: 1
periodSeconds: 120CPU and memory are fallback signals, not a complete realtime scaling policy. A stronger setup also uses active connections per pod, connection admission rejections, event-loop or fanout latency, adapter backlog, and reconnect rate through an external/custom metrics adapter.
Scale up early. Scale down slowly and one pod at a time. Every removed pod makes its clients reconnect, so an aggressive HPA can oscillate and manufacture load.
Rollouts and disruptions
Set:
- a PodDisruptionBudget that leaves enough connection capacity online
- zone or host topology spread
- a termination grace period longer than
SHUTDOWN_GRACE_PERIOD - a rollout
maxUnavailablecompatible with the PDB and remaining socket capacity - load-balancer deregistration or endpoint propagation time inside the drain budget
During a rollout, watch connection count by pod. Kubernetes considers replica count, not how many sockets each replica owns. One terminating pod may hold a disproportionate share after a long uptime.
If strict connection drain is required, remove readiness first, wait for endpoint and load-balancer propagation, then terminate. Test this behavior with the actual ingress controller; it is not identical across controllers.
Ingress and load balancers
Confirm all layers:
- accept HTTP/1.1 WebSocket upgrades
- preserve
UpgradeandConnectionsemantics - have idle timeouts above the heartbeat interval with margin
- do not buffer WebSocket traffic
- expose a dependency-aware readiness path
- drain targets during pod termination
The ingress annotation example above is specific to ingress-nginx. Use the equivalent settings for AWS Load Balancer Controller, Google Cloud Load Balancing, Azure Application Gateway, Traefik, Envoy, HAProxy, or a service mesh.
Sticky sessions are not required for basic pub/sub with a shared adapter. They can reduce reconnect-to-new-node churn, but must not substitute for shared state.
When preserving client IPs with externalTrafficPolicy: Local or proxy-protocol features, retest
load distribution and health checks. Source-IP preservation can reduce the set of eligible nodes.
Pod and node sysctls
First deploy with provider defaults and inspect counters. Managed Kubernetes node images already ship with non-trivial tuning, and an unnecessary override can reduce stability.
Kubernetes classifies sysctls as safe or unsafe. Safe, namespaced values can be applied in the pod security context when the cluster version and admission policy allow them:
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
sysctls:
- name: net.ipv4.tcp_keepalive_time
value: "600"
- name: net.ipv4.tcp_keepalive_intvl
value: "60"
- name: net.ipv4.tcp_keepalive_probes
value: "5"net.core.somaxconn is commonly treated as unsafe. A pod-level value requires both:
- an exact kubelet
allowedUnsafeSysctlsentry on every eligible node - the matching
podSecurityContext.sysctlsvalue and an admission policy that permits it
See Kubernetes' sysctl documentation
and the provider-specific node-pool guide before enabling it. Do not grant a privileged container
or broad net.* permission just to set one value.
Node-level sysctl configuration and pod network namespaces are distinct. Verify the effective value inside the scheduled pod:
kubectl exec -n sockudo deploy/sockudo -- cat /proc/sys/net/core/somaxconn
kubectl exec -n sockudo deploy/sockudo -- sh -c 'ulimit -n; cat /proc/1/limits'Kubernetes has no portable pod field for POSIX rlimit values. The effective nofile comes from
the container runtime and node image. If it is too low, change the runtime/node configuration or
use a provider-supported node image customization, then verify again inside the pod.
Provider examples are in Cloud platforms.
Network capacity
High socket counts can hit infrastructure before the pod:
- load-balancer connection and target limits
- node vNIC packets-per-second or bandwidth limits
- CNI IP allocation and conntrack tables
- NAT gateway state for outbound dependencies or load generators
- cross-zone traffic and broker latency
Inbound WebSockets do not consume a Sockudo pod's local ephemeral port range. Avoid “fixing”
ip_local_port_range unless evidence points to an outbound connection or NAT bottleneck.
Verify the deployed state
helm get values sockudo -n sockudo
kubectl get pods -n sockudo -o wide
kubectl get pdb -n sockudo
kubectl exec -n sockudo deploy/sockudo -- sh -c 'ulimit -n'
kubectl port-forward -n sockudo svc/sockudo 6001:6001
curl -fsS http://127.0.0.1:6001/live
curl -fsS http://127.0.0.1:6001/up/productionThen run a node drain and a rolling restart under load. A manifest that renders successfully is not yet a tested realtime deployment.