weebo-forgejo

High availability & sizing

How the replicas share the work, what keeps the webhook up, and how to size the operator.

How the replicas share the work

The operator runs as a Deployment of identical replicas (2 by default), each doing two jobs:

JobRuns onWhy it matters
Admission webhook (/validate, HTTPS)Every replica, behind the weebo-forgejo ServiceWith failurePolicy: Fail, namespaced CRs can't be created or updated while no replica answers
Reconcilers (every kind)Only the leaderOne writer per Forgejo object, no conflicting updates

The leader holds a coordination.k8s.io Lease in the release namespace, weebo-forgejo-operator-leader (leaderElection.leaseName). It renews it every 5 s; the Lease expires 15 s after the last renewal, and another replica takes over. Every Lease write is guarded by its resourceVersion, so two standbys racing for an expired Lease never both win, and expiry is judged on each replica's own monotonic clock, so skewed node clocks don't matter. A leader that can't renew in time exits before the Lease can expire, so two leaders never reconcile at once.

More replicas therefore add webhook availability, not reconcile throughput. Two is the right number for almost every cluster; three spreads the webhook over three nodes or zones.

What keeps it available

MechanismDefaultNotes
replicaCount2One replica means no webhook while it restarts
PodDisruptionBudgetminAvailable: 1Rendered with 2+ replicas; a drain never removes the last webhook replica
Topology spreadsoft, per nodetopologySpreadConstraints replaces it, e.g. per zone with DoNotSchedule
priorityClassNamenoneGive it a class above ordinary workloads: the webhook gates writes
Startup probe/readyz, up to 120 sCovers the certificate load before the other probes start
Readiness probe/readyz every 10 sA replica that can't answer admissions leaves the Service
Liveness probe/livez every 20 s, 3 failuresFails (503) when the leader's controllers stop making progress: the wedged leader is restarted and another replica takes over

/healthz remains an alias of the readiness check for older tooling.

# values.yaml: three replicas over three zones, high priority.
replicaCount: 3
priorityClassName: system-cluster-critical   # or your own class
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: ScheduleAnyway

The chart adds the pod selector to each constraint's labelSelector when you leave it out.

Graceful shutdown

On SIGTERM (rollout, drain, eviction) a replica:

  1. keeps serving the webhook for shutdown.drainSeconds (5 s), while the endpoint removal reaches every API server;
  2. stops its reconcilers, letting in-flight reconciles finish, and releases the Lease so another replica takes over at once.

terminationGracePeriodSeconds is 960 s because one reconcile can be a long one: a ForgejoRepository migration (clone of a large mirror) can take up to 15 minutes, and interrupting it leaves a half-imported repository. Lower it if you don't migrate repositories.

If your load balancers or service mesh need more time to stop sending traffic, set shutdown.preStopSleepSeconds. The chart uses the native preStop: sleep action (Kubernetes 1.30+): the images have no shell, so an exec hook running sleep would not work.

Sizing

The operator keeps an in-memory cache of every CR it watches (all eleven kinds, cluster-wide). It never lists or watches Secrets, only reads them by name, so their number doesn't matter.

SizeCRsSuggested resources
Smallup to ~500the defaults: requests 50m / 64Mi, limit 256Mi
Medium~500 to 5 000requests 100m / 128Mi, limit 512Mi
Largemoremeasure, starting from 200m / 256Mi, limit 1Gi

These are starting points, not measurements: check the real usage with kubectl top pod -n weebo-forgejo (or the container_memory_working_set_bytes metric) after the first full resync and adjust. Only the leader's memory grows with the reconcile work; the followers mostly hold the cache.

There is deliberately no CPU limit: CFS throttling would slow admission answers, and a slow webhook (webhook.timeoutSeconds, 5 s) fails writes.

The operator's load on Forgejo is driven by the number of CRs and their periodic resync (every ~5 min per CR, jittered ±20% so CRs created together don't stay in lockstep). Two knobs bound it:

ValueDefaultEffect
reconcileConcurrency4Reconciles running at once, per kind
forgejoMaxInflight8Forgejo API calls in flight at once, per ForgejoInstance, whatever the number of reconciles

The startup sweep (every CR reconciled once) and a ForgejoNamespacePolicy change (which re-checks every namespaced CR) are spread by these limits instead of hitting Forgejo all at once. If Forgejo still rate-limits the operator (HTTP 429), CRs report InstanceUnreachable and retry; raise Forgejo's limits for the operator's token rather than adding replicas. Failures only a change can clear (InvalidSpec, NamespaceNotAllowed, DisruptiveUpdateRefused, …) are retried every ~5 min, not every 30 s.

Next

Security model.

On this page