Skip to content

Placement Controller Events

Reference documentation for Kubernetes events emitted by the Placement controller. The controller emits events on key lifecycle transitions to provide observability via kubectl describe placement and kubectl get events without requiring access to controller logs.

Events complement status conditions: conditions reflect current state for programmatic consumers, while events provide a timestamped audit trail of transitions for human operators and alerting systems.

For the reconciler architecture and sub-reconciler contracts, see Placement Reconciler Architecture. For the spec and status contract the conditions belong to, see Placement CRD.


Event Conventions

All events follow these conventions:

  • Reason strings are stable PascalCase identifiers. They are part of the controller's public API and will not change without a deprecation notice.
  • Normal type indicates successful completion of a lifecycle transition.
  • Warning type indicates a failure, validation error, or unexpected condition that requires operator attention.
  • No events are emitted for in-progress/polling states (e.g. while the db-sync Job is still running). This prevents event noise from repeated requeue cycles.
  • Every event is emitted on the Placement CR (involvedObject.kind: Placement) by the recorder named placement-controller, which is what the reportingComponent field selector matches.
  • The Kubernetes API server deduplicates events by (involvedObject, reason, message, source). Repeated identical events increment a counter rather than creating new event objects.

Event Reasons Reference

Configuration

ReasonTypeTrigger ConditionExample Message
ExtraConfigOwnedKeyOverrideWarningspec.extraConfig overrides one or more operator-owned configuration keys (the per-service ownership registry)spec.extraConfig overrides operator-owned keys: [placement_database] connection (the runtime value comes from the OS_PLACEMENT_DATABASE__CONNECTION env override, so the file override is ignored)

Source: the shared RecordExtraConfigHealth in internal/common/config/ownership.go, called from reconcileConfig in reconcile_config.go

Note: The event is gated on the ExtraConfigHealthy=False condition's message. It fires once on the transition into False and once more when the overridden-key set changes, never on the steady reconcile poll. Removing the overrides transitions the condition back to ExtraConfigHealthy=True, Reason=NoOwnedKeysOverridden without a further event. The condition is informational and is not aggregated into Ready. The three keys the validating webhook refuses at admission never reach this path.

Database Sync

ReasonTypeTrigger ConditionExample Message
DatabaseSyncedNormalThe db-sync Job completes successfullyDatabase schema is up to date
DBSyncFailedWarningThe db-sync Job failsdb_sync job failed: <error>
DBSyncMetricEmissionDeferredWarningPatching the last-observed Job UID annotation fails, deferring db_sync metric emission to the next reconcilePatching last-observed db-sync Job UID failed; metric emission deferred to the next reconcile: <error>

Source: the shared ReconcileSyncJobs in internal/common/database/flow.go (DatabaseSynced / DBSyncFailed); the shared RecordJobTerminalState in internal/common/job/terminal.go, wired through recordDBJobTerminalState in db_job_metrics.go (DBSyncMetricEmissionDeferred)

Release Transitions

The release gate validates a spec.openStackRelease change against status.installedRelease before any migration Job runs. A refused transition sets DatabaseReady=False, raises one Warning event whose reason names the rule that refused it, and returns an error so the controller backs off.

ReasonTypeTrigger ConditionExample Message
VersionParseErrorWarningThe installed or the requested release is not a valid YYYY.N stringparsing requested release "latest": invalid release format "latest": expected YYYY.N
DowngradeNotSupportedWarningThe requested release is older than the installed onedowngrade from 2026.1 to 2025.2 is not supported
UpgradePathInvalidWarningThe requested jump is more than one releaseupgrade from 2024.2 to 2026.1 is not sequential; upgrade one release at a time
ImageReleaseMismatchWarningThe requested release bump leaves spec.image at the reference that migrated the installed schema, so no migration would runupgrade from 2025.2 to 2026.1 leaves spec.image unchanged (ghcr.io/c5c3/placement:2025.2), so no migration would run; bump spec.image in lockstep with spec.openStackRelease

Source: gateReleaseTransition and rejectReleaseTransition in reconcile_database.go

Note: ImageReleaseMismatch has a second trigger that raises no event. When a tag-pinned spec.image names a different OpenStack release than spec.openStackRelease, the reconcile sets DatabaseReady=False with that reason and requeues without recording anything, because the check runs on every pass and would otherwise re-fire for as long as the two fields disagree. Read that case off the condition, not the event stream.

Finalization

Emitted while the finalizer tears a deleted Placement CR down. FinalizingDatabase is gated on live MariaDB cleanup work remaining, so brownfield CRs (no MariaDB CRs) and repeated requeue polls do not produce noise.

ReasonTypeTrigger ConditionExample Message
FinalizingDatabaseNormalDeletion begins while MariaDB Database/User/Grant CRs are still liveCleaning up MariaDB Database, User, and Grant before removing Placement
DatabaseFinalizedNormalMariaDB resources marked for deletion; finalizer releasedMariaDB Database, User, and Grant marked for deletion; releasing finalizer
RemoteChildrenAbandonedWarningDeletion begins while the target cluster the CR named no longer resolves; the finalizer is released without touching what was written thereTarget cluster is no longer registered; releasing the finalizer without deleting the MariaDB Database, User, and Grant on it

Source: reconcileDelete in placement_controller.go

Reasons that never fire for Placement

ReasonWhy it cannot occur
SchemaDriftDetectedThe Job set carries no schema-check command. placement-manage db sync applies every pending migration in one idempotent pass, and the placement-status upgrade check inside the same Job already validates the result
UpgradeInitiated, ExpandComplete, MigrateComplete, DeploymentRolloutComplete, UpgradeComplete, UpgradeAborted, UpgradeTargetChanged, ExpandFailed, MigrateFailed, ContractFailedThese belong to the shared expand-migrate-contract flow. Placement runs no phase machine and its CR carries no upgradePhase; a release bump takes the same single db-sync Job a fresh install takes

Alerting Configuration

Event reason strings are designed to be stable identifiers for alerting rules. Use kubectl get events --field-selector to filter by reason:

bash
# Watch for db-sync failures
kubectl get events --field-selector reason=DBSyncFailed -w

# Watch for a rejected release upgrade path
kubectl get events --field-selector reason=UpgradePathInvalid -w

# Watch for an extraConfig override of an operator-owned key
kubectl get events --field-selector reason=ExtraConfigOwnedKeyOverride -w

# Watch for all Warning events from the placement-controller
kubectl get events --field-selector type=Warning,reportingComponent=placement-controller -w

Prometheus Alertmanager Example

When using kube-state-metrics with event metrics enabled, you can alert on specific event reasons:

yaml
groups:
  - name: placement-events
    rules:
      - alert: PlacementDBSyncFailed
        expr: |
          increase(kube_event_count{
            reason="DBSyncFailed",
            involved_object_kind="Placement"
          }[5m]) > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "Placement db-sync failed"
          description: "The Placement db-sync Job has failed. Check the Job logs for details."

Event Flow

text
PlacementReconciler.Reconcile()

  ├── reconcileDelete() (deletionTimestamp set)
  │     ├─ MariaDB CRs still live  → Normal  FinalizingDatabase
  │     └─ MariaDB cleanup done    → Normal  DatabaseFinalized

  ├── reconcileConfig()
  │     └─ spec.extraConfig overrides operator-owned keys → Warning ExtraConfigOwnedKeyOverride
  │       (gated on transition into ExtraConfigHealthy=False, Reason=OwnedKeysOverridden)

  └── reconcileDatabase()
        ├─ tag/release mismatch      → (condition only, no event)
        ├─ release gate refuses      → Warning VersionParseError / DowngradeNotSupported /
        │                                      UpgradePathInvalid / ImageReleaseMismatch
        ├─ db_sync fails             → Warning DBSyncFailed
        ├─ db_sync succeeds          → Normal  DatabaseSynced
        └─ Job-UID patch fails       → Warning DBSyncMetricEmissionDeferred