Skip to content

[Feature]: Surface provisioning failures (quota, admission, scheduling) to Cluster.status.conditions #10733

Closed as not planned

Description

@chobostar

Is there an existing issue already for this feature request/idea?

  • I have searched for an existing issue, and could not find anything. I believe this is a new feature request to be evaluated.

What problem is this feature going to solve? Why should it be added?

Problem

When a Cluster gets stuck in phase "Setting up primary", the actual cause is invisible in the Cluster object. CNPG creates a Job to bootstrap the primary instance, but if Kubernetes cannot create the Job's Pod (ResourceQuota exceeded, admission webhook rejection, node scheduling constraints), the failure is recorded only as Warning / FailedCreate events on the Job object by the job-controller. CNPG does not observe these failures and the Cluster stays in "Setting up primary" indefinitely with a generic reason.

sequenceDiagram
    participant CNPG as CNPG Operator
    participant Cluster as Cluster Object
    participant Job as Job *-initdb
    participant JC as job-controller
    participant K8s as Admission / Scheduler

    CNPG->>Cluster: phase = "Setting up primary"
    CNPG->>Job: create Job ✅
    Note over CNPG: no further observation<br/>of Job's pod creation

    loop every ~10s backoff
        JC->>K8s: create Pod
        K8s-->>JC: 403 exceeded quota / Unschedulable
        JC->>Job: Warning FailedCreate
    end

    Note over Cluster: phase stays "Setting up primary"<br/>phaseReason: "Creating primary instance X"<br/>no indication of the real problem
Loading

Observed in the wild:

$ kubectl вescribe cluster my-cluster
...
Status:
  Phase:         Setting up primary
  Phase Reason:  Creating primary instance my-cluster-1
Events:
  Normal  CreatingInstance  cloudnative-pg  Primary instance (initdb)
  # ← nothing about the actual error

$ kubectl get events
Warning  FailedCreate  job/my-cluster-1-initdb  job-controller
  Error creating: pods "..." is forbidden: exceeded quota: quota-requests-cpu,
  requested: requests.cpu=4, used: requests.cpu=30m, limited: requests.cpu=1
  # ← error is here, on a different object

Impact

  • Users and platform operators have no way to determine why provisioning is blocked without kubectl get events or kubectl describe job — neither accessible through higher-level platform abstractions (e.g., Helm releases, Crossplane Compositions, GitOps pipelines) that mirror cluster.status
  • Support load increases as users report "cluster stuck with no error"
  • No programmatic way to detect and alert on stuck provisioning vs normal provisioning

Describe the solution you'd like

Proposed Solution

Add a Provisioning condition to cluster.status.conditions that reflects the actual state of the provisioning lifecycle, including failure reasons surfaced from owned resources.

Target state:

status:
  phase: Setting up primary
  conditions:
    - type: Provisioning
      status: "False"
      reason: QuotaExceeded       # enum: InProgress | QuotaExceeded | AdmissionDenied
                                   #       Unschedulable | PVCProvisioningFailed | Succeeded
      message: "exceeded quota: quota-requests-cpu, requested: requests.cpu=4, limited: requests.cpu=1"
      lastTransitionTime: "2024-01-01T00:00:00Z"

The reason field as an enum allows consumers to act programmatically without string parsing. The message contains the human-readable detail from Kubernetes.

Implementation Approaches

I intentionally leave the implementation choice to maintainers. Two approaches are viable:

A — Proactive (preferred, fully level-triggered):
Before or shortly after Job creation, read ResourceQuota.status in the cluster namespace and compare against the cluster's resource requests. If quota would be exceeded, set Provisioning=False/QuotaExceeded immediately — without relying on Events.

B — Reactive (covers all admission cases):
When the owned Job has status.active=0, status.succeeded=0 for N consecutive reconcile cycles, read Events on the Job with reason=FailedCreate and copy the most recent message to the Cluster condition. This generalises to admission webhooks, LimitRanges, and other pod creation blockers beyond quota.

Note: approach A is more robust (level-triggered, no dependence on Event TTL of ~1h) but approach B covers a broader set of admission failures. A combination may be optimal.

Failure Classes Covered

Failure Source of truth Approach A Approach B
ResourceQuota exceeded ResourceQuota.status / Job Events ✅ ✅
Admission webhook rejection (Kyverno, OPA) Job Events ❌ ✅
LimitRange violation Job Events ❌ ✅
Pod Unschedulable (no nodes) pod.status.conditions ❌ ✅ via Pod Events

The existing IsPodUnschedulable() utility in pkg/utils/pod_conditions.go already reads pod.status.conditions[PodScheduled] for the scheduling case — extending its usage to update a Cluster condition would be a small addition.

Describe alternatives you've considered

Alternatives Considered

  • Update phaseReason only: Already exists as a string field, but it is overwritten on every phase transition and is not structured (no reason enum, no lastTransitionTime). Not machine-readable.
  • Emit Warning Event on Cluster only: Events are ephemeral (default TTL 1h). Useful as a complement but insufficient as the primary status mechanism.
  • Document the current behavior: We are happy to contribute a documentation PR describing where to look for errors during provisioning. However, documentation does not eliminate the operational gap for automated systems consuming cluster.status.

Additional context

Version

  • CloudNative-PG: 1.28.1
  • Kubernetes: 1.31
  • Reproduction: create namespace with requests.cpu=1 ResourceQuota, create Cluster with resources.requests.cpu: 4

Backport?

N/A

Are you willing to actively contribute to this feature?

No

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

  1. adribr53 commented on Jun 25, 2026

    @adribr53

    Hi @gbartolini
    I took a run at this for onboarding cnpg and have a working branch with tests (build + unit + controller integration all green locally). See https://github.com/adribr53/cloudnative-pg/tree/jobStatusSurfacing

    I implemented it as a new Provisioning condition on cluster.status.conditions, distinct from Initialized: Provisioning oscillates with the current attempt and is meaningful for any provisioning Job.

    It surfaces the actual root causes the issue mentions:

    • ProvisioningJobFailed — the Job's JobFailed condition is True.
    • ProvisioningJobStuck — derived from the underlying Pods/events: the most recent FailedCreate event on the Job (exhausted ResourceQuota, admission-webhook rejection, LimitRange) or a PodScheduled=False/Unschedulable reason, falling back to a time threshold when no Pod ever started.
    • ProvisioningHealthy / ProvisioningIdle on the success paths.

    The message carries the human-readable Kubernetes reason, plus a Warning event on the Cluster. This needs a small RBAC addition (get;list;watch on events/pods).

    Since the issue is still in triage and assigned to you, I wanted to check the direction before opening a PR. Does this approach work for you, and is it OK for me to be assigned? Happy to adjust if you'd prefer a different shape.

  2. adribr53 commented on Jun 25, 2026

    @adribr53

    Thanks for the pointer at #11035

    #11035 keys off JobFailed. But provisioning jobs set no ActiveDeadlineSeconds, so that only fires after a pod runs and fail AFAIK.
    That leaves out some cases where Job doesn't fail then (quota unmet, admission webhook failed, unschedulable, ...)

    I see 2 options:

    • have the operator detect such cases (-> what I explored in my run)
    • set ActiveDeadlineSeconds so the Job fails on its own. Assuming that's even acceptable, this doesn't surface why the job failed.
  3. github-actions commented on Aug 25, 2026

    @github-actions
    Contributor

    This issue is stale because it has been open for 60 days with no activity.

  4. github-actions commented on Oct 11, 2026

    @github-actions
    Contributor

    This issue was closed because it has been inactive for 14 days since being marked as stale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions