celld-operator

Networking & ingress

Every fleet has two listeners with two very different audiences: the public Worker listener (:8080), which your users reach through ingress, and the internal listener (:8081), which nothing outside the fleet and the operator may ever reach. The operator wires both correctly.

Ingress modes

How spec.hostnames get routed is an operator-level choice (--ingress-mode, or operator.ingressMode in Helm):

ModeMechanismWhen to use it
httproute (default)One Gateway API HTTPRoute per app, carrying every hostname in spec.hostnames, attached to a shared platform Gateway by parentRefs (--gateway-name/--gateway-namespace, default edge/infra). Cross-namespace attachment is authorized by that Gateway's allowedRoutes; TLS is the Gateway's own. The drain-503 retry needs experimental-channel CRDs — see below.Clusters that already run Gateway API; Istio's implementation is the one this was built against.
virtualserviceClassic Istio VirtualServices bound to pre-existing networking.istio.io Gateways (--istio-gateways, comma-separated namespace/name).Clusters whose edge is an existing istio-ingressgateway.
ingressPlain networking.k8s.io/v1 Ingress; --ingress-class selects the class, and --cluster-issuer makes each app's Ingress request its own cert-manager TLS certificate. Drain-retry and WebSocket policies are expressed as ingress-nginx annotations (ignored by other controllers).ingress-nginx, Traefik, cloud ingress controllers.
noneNo routes are created; hostnames has no effect.Internal-only platforms, or ingress managed entirely outside the operator.

When Gateway API CRDs are missing in httproute mode, or a route errors, the WorkerApp reports IngressReady: False and hostnames are not routed; the fleet itself keeps running.

Internal consumers: no ingress at all

Internal-only apps need no hostnames. Consumers in the cluster reach the serving Service directly at <app>-celld.<namespace>.svc:8080. The spec.service block shapes that Service:

service:
  type: LoadBalancer       # ClusterIP (default) | LoadBalancer | NodePort
  annotations:             # cloud LB configuration, e.g. an internal/private LB
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"

Drain-aware edge policy

A draining celld node answers new requests with 503 and a closed connection, expecting the client to retry against a healthy node. At the platform edge, the gateway is that client: the operator configures explicit retry-on-503 on every route it emits (Istio's default retryOn does not cover 503 responses), which makes drains, and therefore rollouts, invisible to end users.

One caveat in httproute mode: HTTPRouteRule.retry exists only in the Gateway API experimental channel. Standard-channel CRDs accept the route and silently drop the field, so the operator checks the stored object and reports IngressReady: True with reason RouteReconciledRetryDropped and the message drain 503s are not retried at the gateway. If you see that reason, either install the experimental channel or expect clients to see 503s during rollouts.

Related wiring the operator takes care of:

The WebSocket profile

spec.websockets: true switches the app's routes and scaling to a socket-friendly profile:

Note there is no session affinity: the operator emits no DestinationRule and sets no sticky-session annotation. It does not need one — celld's signed peer tunnel serves any cell from any node — but if you want requests to land on a cell's owner for latency reasons, that is your own edge configuration to add.

Keeping proxies happy without waking cells: have clients ping periodically and answer with setWebSocketAutoResponse: celld replies to a matched message without waking the hibernated cell, and the proxy's idle clock resets.

Where TLS lives

celld terminates no TLS anywhere. Public TLS belongs to the Gateway (or Ingress + cert-manager). The pod network should be encrypted (Istio ambient mode, or a CNI with WireGuard) because celld's peer protocol relies on network confidentiality for its payloads (its HMAC auth protects integrity, not secrecy).

The internal listener's protections are covered in Security.