Skip to content

Kubernetes Deployment ​

Ratchet runs on Kubernetes as a standard Jakarta EE 10/11 application. This guide covers single-replica Deployments, clustered StatefulSets, ConfigMaps for configuration, and health probe setup.

Single-node deployment ​

For non-clustered workloads, a standard Deployment is sufficient:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ratchet-app
  labels:
    app: ratchet-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ratchet-app
  template:
    metadata:
      labels:
        app: ratchet-app
    spec:
      containers:
      - name: app
        image: myapp:latest
        ports:
        - containerPort: 8080
          name: http
        envFrom:
        - configMapRef:
            name: ratchet-config
        - secretRef:
            name: ratchet-db-credentials
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "2"
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 2
          failureThreshold: 1
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 30
        startupProbe:
          httpGet:
            path: /health/ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          failureThreshold: 30

Clustered StatefulSet ​

For multi-node Ratchet deployments where each node needs a stable identity (used by NodeIdentityProvider), use a StatefulSet:

yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: ratchet-scheduler
spec:
  serviceName: ratchet-scheduler
  replicas: 3
  selector:
    matchLabels:
      app: ratchet-scheduler
  template:
    metadata:
      labels:
        app: ratchet-scheduler
    spec:
      containers:
      - name: app
        image: myapp:latest
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: NODE_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        envFrom:
        - configMapRef:
            name: ratchet-config
        - secretRef:
            name: ratchet-db-credentials
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "2"
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 2
          failureThreshold: 1
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 30

StatefulSet pods get stable names (ratchet-scheduler-0, ratchet-scheduler-1, etc.) that serve as natural node identifiers. The metadata.name field is injected via the Downward API and can be read by a NodeIdentityProvider implementation:

There is no RATCHET_CLUSTER_ENABLED flag. Multiple replicas become a Ratchet cluster when they share the same store. One-shot claims, recurring scans, and destructive startup cleanup are already coordinated through store-backed locking; bake in a real ClusterCoordinator only if you want cross-node wakeups.

java
@ApplicationScoped
public class KubernetesNodeProvider implements NodeIdentityProvider {

  @Override
  public String getNodeId() {
    String nodeId = System.getenv("NODE_ID");
    return nodeId != null ? nodeId : System.getenv("HOSTNAME");
  }
}

Headless Service ​

A headless Service is required for StatefulSet DNS resolution:

yaml
apiVersion: v1
kind: Service
metadata:
  name: ratchet-scheduler
  labels:
    app: ratchet-scheduler
spec:
  clusterIP: None
  selector:
    app: ratchet-scheduler
  ports:
  - port: 8080
    name: http

This allows pods to discover each other at ratchet-scheduler-0.ratchet-scheduler.default.svc.cluster.local.

External-Facing Service ​

Expose the application to traffic with a standard Service:

yaml
apiVersion: v1
kind: Service
metadata:
  name: ratchet-app
  labels:
    app: ratchet-scheduler
spec:
  type: ClusterIP
  selector:
    app: ratchet-scheduler
  ports:
  - port: 80
    targetPort: 8080
    name: http

Ingress ​

Route external traffic to the application:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ratchet-app
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
spec:
  rules:
  - host: ratchet.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ratchet-app
            port:
              number: 80

ConfigMaps ​

Store non-sensitive Ratchet configuration in a ConfigMap:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ratchet-config
data:
  RATCHET_THREAD_POOL_SIZE_SINGLE: "16"
  RATCHET_THREAD_POOL_SIZE_RECURRING: "5"
  RATCHET_POLLER_MIN_DELAY_MS: "2000"
  RATCHET_POLLER_MAX_DELAY_MS: "10000"
  RATCHET_POLLER_BATCH_SIZE: "100"
  RATCHET_JOB_RETENTION_DAYS: "30"
  RATCHET_NODE_HEARTBEAT_INTERVAL_SECONDS: "10"

Secrets ​

Store database credentials in a Secret:

yaml
apiVersion: v1
kind: Secret
metadata:
  name: ratchet-db-credentials
type: Opaque
stringData:
  DB_URL: jdbc:postgresql://postgres-service:5432/ratchet
  DB_USERNAME: ratchet
  DB_PASSWORD: your-secure-password

In production, use an external secret manager (AWS Secrets Manager, HashiCorp Vault, etc.) with an operator like External Secrets to sync credentials into Kubernetes Secrets.

Health probes ​

Ratchet applications should expose MicroProfile Health endpoints. WildFly and other Jakarta EE runtimes serve these automatically when the microprofile-health subsystem is enabled.

Startup probe ​

The startup probe prevents the readiness and liveness probes from running until the application has fully initialized. Jakarta EE applications can take 30-60 seconds to start, especially when connecting to databases and initializing CDI contexts.

yaml
startupProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 30    # 10 + (5 * 30) = up to 160 seconds to start

Readiness probe ​

The readiness probe controls whether the pod receives request traffic. It should combine an application-owned traffic-acceptance signal with any dependencies required to serve requests, such as the Ratchet store. Ratchet does not ship a MicroProfile Health check or a public Poller-status API.

yaml
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  periodSeconds: 2
  failureThreshold: 1

Liveness probe ​

The liveness probe restarts the pod if it becomes unresponsive. Use a longer interval and higher failure threshold to avoid unnecessary restarts during garbage collection pauses or temporary database hiccups:

yaml
livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  periodSeconds: 30
  failureThreshold: 5

Custom health check ​

This example uses a file as the application's traffic-acceptance signal. The path is an application convention, not a file created or read by Ratchet. Use a writable in-container path (for example, an emptyDir mount) or expose an equivalent application-owned endpoint when the image has a read-only filesystem:

java
@Readiness
@ApplicationScoped
public class RatchetReadinessCheck implements HealthCheck {

  private static final Path NOT_READY = Path.of("/tmp/ratchet-not-ready");

  @Resource(lookup = "java:/RatchetDS")
  DataSource dataSource;

  @Override
  public HealthCheckResponse call() {
    if (Files.exists(NOT_READY)) {
      return HealthCheckResponse.down("ratchet-application")
          .withData("reason", "terminating")
          .build();
    }

    try (Connection conn = dataSource.getConnection()) {
      return conn.isValid(2)
          ? HealthCheckResponse.up("ratchet-application").build()
          : HealthCheckResponse.down("ratchet-application")
              .withData("reason", "store validation failed")
              .build();
    } catch (Exception e) {
      return HealthCheckResponse.down("ratchet-application")
          .withData("error", e.getMessage())
          .build();
    }
  }
}

Rolling termination ​

DrainController belongs to the reference implementation, is not exported as a supported application API, and has no built-in MicroProfile Health adapter. Its state changes as CDI shutdown begins, so it cannot be the signal that a preStop hook uses to remove request traffic first.

Use a preStop hook to flip the same application-owned signal checked above, then wait long enough for readiness and any ingress or service-mesh routing state to converge. Kubernetes already marks a terminating Service endpoint as not ready; the explicit flag keeps the application's own health state consistent and also works for traffic paths that consult the health endpoint directly. See the Kubernetes documentation on readiness probes and container lifecycle hooks.

yaml
spec:
  terminationGracePeriodSeconds: 30
  containers:
  - name: app
    image: myapp:latest
    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sh
          - -c
          - touch /tmp/ratchet-not-ready && sleep 10
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 8080
      periodSeconds: 2
      failureThreshold: 1

The file flag does not switch Ratchet's internal drain mode. It only stops new request traffic. After preStop returns, Kubernetes sends the container its termination signal. The Jakarta runtime then destroys the application, and Ratchet's lifecycle stops new claims, stops its background services, and waits for accepted work within the shutdown timeout. It then releases all remaining claims to PENDING without a crash charge and interrupts any worker that is still active. The claim fence prevents that worker from recording a stale outcome, so the interrupt does not use up a retry attempt. Job code must remain safe for at-least-once execution. A SIGKILL before teardown completes uses orphan recovery and consumes the crash budget. ratchet.node.max-crash-redeliveries defaults to 3; the next crash sends the job to the DLQ.

The termination grace period includes the preStop hook. Set it long enough for the routing delay and normal application-server teardown, but do not use it as a promise that arbitrary job runtimes will finish before shutdown.

Database on Kubernetes ​

For production, use a managed database service (RDS, Cloud SQL, Azure Database) rather than running the database in Kubernetes. Point the DB_URL to the managed instance:

yaml
# In the Secret
stringData:
  DB_URL: jdbc:postgresql://ratchet-db.abc123.us-east-1.rds.amazonaws.com:5432/ratchet

PostgreSQL in Kubernetes ​

For development or when a managed service is not available:

yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: ratchet
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: ratchet-db-credentials
              key: DB_USERNAME
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: ratchet-db-credentials
              key: DB_PASSWORD
        volumeMounts:
        - name: pgdata
          mountPath: /var/lib/postgresql/data
        - name: init-scripts
          mountPath: /docker-entrypoint-initdb.d
  volumeClaimTemplates:
  - metadata:
      name: pgdata
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-init
data:
  01-schema.sql: |
    -- Paste contents of postgresql-schema.sql here,
    -- or mount from a volume containing the DDL file

Schema initialization ​

Apply the Ratchet schema as a Kubernetes Job that runs before the application starts:

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: ratchet-schema-init
spec:
  template:
    spec:
      containers:
      - name: schema-init
        image: postgres:15
        command:
        - sh
        - -c
        - |
          until pg_isready -h postgres-service -U ratchet; do
            echo "Waiting for PostgreSQL..."
            sleep 2
          done
          psql -h postgres-service -U ratchet -d ratchet -f /schema/postgresql-schema.sql
        env:
        - name: PGPASSWORD
          valueFrom:
            secretKeyRef:
              name: ratchet-db-credentials
              key: DB_PASSWORD
        volumeMounts:
        - name: schema
          mountPath: /schema
      volumes:
      - name: schema
        configMap:
          name: ratchet-schema
      restartPolicy: OnFailure
  backoffLimit: 5

Resource recommendations ​

Sizing guidelines based on workload:

WorkloadReplicasCPU RequestMemory RequestPolling Interval
Light (< 100 jobs/hr)1250m256Mi10s
Medium (100-1,000 jobs/hr)2500m512Mi5s
Heavy (1,000-10,000 jobs/hr)311Gi3s
Extreme (> 10,000 jobs/hr)5+22Gi1s

Pod disruption budget ​

Prevent Kubernetes from evicting too many Ratchet pods during node maintenance:

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: ratchet-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: ratchet-scheduler

See also ​