Aller au contenu
Français

Kubernetes deployment

Ce contenu n’est pas encore disponible dans votre langue.

Kubernetes is the most common deployment target. The framework plays well with K8s once you get a few things right: stable identity (for stateful actors), seed discovery (so nodes find each other), and a clean shutdown path (so rolling updates don’t drop traffic).

This page is a working recipe — copy, adapt, deploy.

apiVersion: v1
kind: ServiceAccount
metadata:
name: actor-ts
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: actor-ts-pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: actor-ts
subjects:
- kind: ServiceAccount
name: actor-ts
roleRef:
kind: Role
name: actor-ts-pod-reader
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Service
metadata:
name: actor-ts-cluster
spec:
clusterIP: None # headless — DNS returns pod IPs
selector:
app: actor-ts
ports:
- name: cluster
port: 2552
targetPort: 2552
---
apiVersion: v1
kind: Service
metadata:
name: actor-ts
spec:
selector:
app: actor-ts
ports:
- name: http
port: 80
targetPort: 8080
- name: management
port: 8558
targetPort: 8558
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: actor-ts
spec:
serviceName: actor-ts-cluster
replicas: 3
selector:
matchLabels:
app: actor-ts
template:
metadata:
labels:
app: actor-ts
spec:
serviceAccountName: actor-ts
terminationGracePeriodSeconds: 30
containers:
- name: app
image: ghcr.io/your-org/your-app:1.2.3
ports:
- name: cluster
containerPort: 2552
- name: http
containerPort: 8080
- name: management
containerPort: 8558
env:
- name: ACTOR_TS_HOSTNAME
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: ACTOR_TS_PORT
value: "2552"
- name: K8S_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: K8S_LABEL_SELECTOR
value: "app=actor-ts"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: actor-ts-secrets
key: db-password
readinessProbe:
httpGet:
path: /health/ready
port: management
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health/alive
port: management
initialDelaySeconds: 15
periodSeconds: 10
lifecycle:
preStop:
exec:
# Drain LB before SIGTERM hits the app
command: ["/bin/sh", "-c", "sleep 10"]
ServiceAccount: actor-ts
Role: actor-ts-pod-reader # get + list pods
RoleBinding: binds them

The K8s API seed provider needs to list pods matching a label selector to discover peers. Without this RBAC grant, the seed provider gets 403 and the cluster never forms.

clusterIP: None

A headless service returns the pod IPs directly via DNS (no ClusterIP virtual address). Useful when nodes need stable, direct peer identities — the failure detector’s heartbeats target specific pod IPs, not a load-balanced abstraction.

clusterIP: <default>

A normal Service for HTTP and the management endpoint — these benefit from load-balancing. Cluster traffic goes through the headless service, app traffic through this one.

UseWhen
StatefulSetStable pod names (actor-ts-0, actor-ts-1, …). Useful when you want predictable identity for entity placement, or when persistent volumes are mounted per-pod.
DeploymentPod names are random. Fine if your app is stateless (no per-pod identity required) and persistence is external (Cassandra journal, shared S3 snapshot store).

For sharded actors with remember-entities = true on persistent volumes per pod, StatefulSet is the right choice. For externally-persisted state (cluster talking to a shared Postgres/Cassandra), Deployment is fine and simpler.

terminationGracePeriodSeconds: 30

K8s sends SIGTERM, then waits this long before SIGKILL. Sized based on:

  • HTTP drain — typically 5-10 s.
  • Cluster leave gossip — 5-15 s for convergence.
  • Journal flush — depends on the journal.

30 s is a reasonable default. Bump it if your cluster is large or the failure-detector window is long.

preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]

Critical for clean rolling updates. The flow:

  1. K8s marks the pod terminating and starts the preStop hook in parallel with the load-balancer-deregistration.
  2. sleep 10 — gives the load balancer time to stop sending new traffic to this pod.
  3. After the sleep, K8s sends SIGTERM.
  4. The app’s coordinated-shutdown hooks drain in-flight requests, leave the cluster, etc.

Without the sleep, SIGTERM races with LB deregistration — in-flight requests can see “draining” responses.

readinessProbe: /health/ready
livenessProbe: /health/alive

The framework’s HttpManagement extension exposes these endpoints.

  • /health/ready — “should this pod receive traffic?” Returns 503 during shutdown so the LB drops it before SIGTERM. Use to gate per-app health checks (DB reachable, dependencies warm, etc.).
  • /health/alive — “is this pod fundamentally broken?” Failing means K8s restarts the pod. Reserve for genuinely unrecoverable states — actor-system not running, OOM-like conditions.
import { ActorSystem, Cluster, CoordinatedShutdownId, HttpManagement } from 'actor-ts';
import { KubernetesApiSeedProvider } from 'actor-ts/discovery';
const system = ActorSystem.create('my-app');
const cs = system.extension(CoordinatedShutdownId);
// 1. Cluster join with K8s API seed discovery
const seeds = await new KubernetesApiSeedProvider({
namespace: process.env.K8S_NAMESPACE!,
labelSelector: process.env.K8S_LABEL_SELECTOR!,
containerPort: 2552,
}).discover();
const cluster = await Cluster.join(system, {
host: process.env.ACTOR_TS_HOSTNAME!,
port: parseInt(process.env.ACTOR_TS_PORT!),
seeds,
roles: ['compute'],
});
// 2. Management endpoints
const management = await HttpManagement.start(system, {
port: 8558,
cluster,
});
// 3. App HTTP server
const http = system.extension(HttpExtensionId);
await http.newServerAt('0.0.0.0', 8080).bind(routes);
// 4. SIGTERM → coordinated shutdown
cs.installProcessHooks();
// 5. Keep the process alive
await new Promise(() => {});

Five pieces in order:

  1. Cluster join with seed discovery — the K8s API seed provider queries pods matching app=actor-ts and uses their IPs as seeds. On the first pod, the seed list is just itself (the auto-promote-to-leader path).
  2. Management endpoints/health/* for K8s probes, /cluster/members for debugging.
  3. App HTTP — your routes, separate port from management.
  4. SIGTERM hooks — coordinated-shutdown installs hooks that fire on SIGTERM to gracefully drain.
  5. Keep alive — the process hangs until SIGTERM arrives.

See Discovery — Kubernetes API for the seed provider’s full options.

Terminal window
kubectl rollout restart statefulset/actor-ts

For each pod, in order (StatefulSet) or arbitrary (Deployment):

  1. K8s marks the pod terminating + starts preStop.
  2. 10-second LB drain.
  3. SIGTERM lands.
  4. Coordinated-shutdown runs:
    • Stop accepting new HTTP requests.
    • Drain in-flight requests.
    • Issue cluster.leave().
    • Wait for cluster to acknowledge leave.
    • Terminate the actor system.
  5. Process exits cleanly.
  6. K8s starts a new pod from the new image.
  7. New pod joins the cluster via the seed provider.

For sharded entities, rebalancing happens automatically — the leaving node’s shards are reallocated; new entities re-spawn on the new pod from the journal.