Skip to content

Keystone Controller Events ​

Reference documentation for Kubernetes events emitted by the Keystone controller. The controller emits events on key lifecycle transitions to provide observability via kubectl describe keystone 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 Keystone Reconciler Architecture. For the upgrade flow that drives most upgrade-related events, see Keystone Upgrade Flow. For schema drift detection events, see Keystone Schema Drift Detection.


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 a Job is still running). This prevents event noise from repeated requeue cycles.
  • 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 ​

Bootstrap ​

ReasonTypeTrigger ConditionExample Message
BootstrapCompleteNormalBootstrap Job completes successfullyKeystone bootstrap completed successfully
BootstrapFailedWarningBootstrap Job failsKeystone bootstrap job failed: <error>
AdminSecretInvalidWarningAdmin password Secret is missing, unreadable, or has an empty password valueAdmin password Secret openstack/keystone-admin is missing, unreadable, or has an empty "password" value

Source: reconcileBootstrap in reconcile_bootstrap.go

Database Sync (Non-Upgrade) ​

ReasonTypeTrigger ConditionExample Message
DatabaseSyncedNormaldb_sync and schema-check Jobs both complete successfullyDatabase schema is up to date
DBSyncFailedWarningdb_sync Job failsdb_sync job failed: <error>
SchemaDriftDetectedWarningschema-check Job fails after db_sync succeeds (schema does not match Alembic head)schema-check 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; db_sync metric emission deferred to the next reconcile: <error>

Source: reconcileDatabase in reconcile_database.go; recordDBJobTerminalState in db_job_metrics.go

Upgrade Initiation ​

ReasonTypeTrigger ConditionExample Message
UpgradeInitiatedNormalUpgrade validated and initiated with expand-migrate-contract pipelineUpgrade initiated: 2026.1 → 2026.2
VersionParseErrorWarningInstalled release or target release version string cannot be parsedFailed to parse installed release "invalid": <error>
DowngradeNotSupportedWarningTarget release is older than installed releaseDowngrade from 2026.2 to 2026.1 is not supported
UpgradePathInvalidWarningTarget release skips an intermediate version (non-sequential upgrade)Upgrade from 2026.1 to 2027.1 is not sequential
UpgradeTargetChangedWarningspec.image.tag changed while an upgrade is already in progressImage tag changed to 2027.1 during active upgrade 2026.1 → 2026.2
UpgradeAbortedNormalspec.image.tag reverted to the installed release while an upgrade was in progress; upgrade Jobs are deleted and phase/target resetUpgrade 2026.1 → 2026.2 aborted: spec.image.tag reverted to installed release 2026.1

Source: initiateUpgrade in reconcile_upgrade.go; UpgradeTargetChanged and UpgradeAborted from reconcileDatabase/abortUpgrade in reconcile_database.go

Upgrade Phases ​

ReasonTypeTrigger ConditionExample Message
ExpandCompleteNormalExpand phase Job completes successfullyExpand phase complete: 2026.1 → 2026.2
ExpandFailedWarningExpand phase Job failsExpand job <name> failed: <error>
MigrateCompleteNormalMigrate phase Job completes successfullyMigrate phase complete: 2026.1 → 2026.2
MigrateFailedWarningMigrate phase Job failsMigrate job <name> failed: <error>
UpgradeCompleteNormalContract phase Job completes, finishing the entire upgradeUpgrade complete: 2026.1 → 2026.2
ContractFailedWarningContract phase Job failsContract job <name> failed: <error>

Source: reconcileExpand, reconcileMigrate, reconcileContract, and the shared runUpgradePhase step driver in reconcile_upgrade.go

Encryption Key Generation ​

ReasonTypeTrigger ConditionExample Message
FernetKeysGeneratedNormalInitial Fernet encryption keys Secret is created (first reconcile only)Initial Fernet encryption keys have been generated
CredentialKeysGeneratedNormalInitial credential encryption keys Secret is created (first reconcile only)Initial credential encryption keys have been generated

Source: reconcileFernetKeys in reconcile_fernet.go, reconcileCredentialKeys in reconcile_credential.go

Note: These events fire only on initial Secret creation. Subsequent key rotations run as CronJobs; when the controller commits a completed rotation from the staging Secret it emits the events in Rotation Commit (Staged) below.

Rotation Commit (Staged) ​

The Fernet, credential-key, and admin-password rotation CronJobs write their output to a staging Secret. The controller validates and commits the staged payload onto the production Secret and reports the outcome as events:

ReasonTypeTrigger ConditionExample Message
FernetKeysRotatedNormalStaged Fernet rotation validated and applied to the main keys Secretrotation applied from staging secret keystone-fernet-keys-rotation (3 active keys)
CredentialKeysRotatedNormalStaged credential-key rotation validated and applied to the main keys Secretrotation applied from staging secret keystone-credential-keys-rotation (2 active keys)
RotationAnnotationInvalidWarningStaging Secret's cobaltcore.c5c3.io/rotation-completed-at annotation is not valid RFC3339 (Fernet/credential)staging secret <name> has malformed cobaltcore.c5c3.io/rotation-completed-at annotation: <error>
RotationRejectedWarningStaged Fernet/credential key set fails validation (key count outside [min, max]); the staged data is clearedstaging secret <name> rejected: <error>
AdminPasswordRotatedNormalStaged admin-password rotation validated and applied to the push-source Secretadmin password rotation applied from staging secret <name>
AdminPasswordRotationAnnotationInvalidWarningAdmin-password staging Secret's completion annotation is malformedstaging secret <name> has malformed cobaltcore.c5c3.io/rotation-completed-at annotation: <error>
AdminPasswordRotationRejectedWarningStaged admin password fails validation (e.g. below minimum length); the rejected password is retained for inspectionstaging secret <name> rejected: <error>

Source: shared commitStagedRotation in rotation_staging.go, invoked from reconcile_fernet.go, reconcile_credential.go, and reconcile_passwordrotation.go

Trust Flush ​

ReasonTypeTrigger ConditionExample Message
TrustFlushBypassWarningspec.trustFlush is nil on a CR that predates webhook defaulting; the existing trust-flush CronJob is deletedTrust flush legacy bypass: spec.trustFlush is nil (webhook defaulting did not run); existing CronJob deleted

Source: reconcileTrustFlush in reconcile_trustflush.go

Identity Backends ​

Domain-lifecycle events are emitted on the KeystoneIdentityBackend CR by its dedicated controller; the projection warning is emitted on the Keystone CR by the keystone-side sub-reconciler:

ReasonTypeTrigger ConditionExample Message
DomainCreatedNormalManage mode provisions the domain through the identity APICreated domain "corp" (id <id>)
DomainAdoptedNormalAdopt mode resolves the pre-existing domain by name (first observation only)Adopted pre-existing domain "corp" (id <id>)
DomainDisabledNormaldeletionPolicy Delete disables the domain before deleting it (keystone forbids deleting an enabled domain)Disabled domain "corp" before deletion
DomainDeletedNormaldeletionPolicy Delete removed the domainDeleted domain "corp" (id <id>)
DomainDeleteFailedWarningDisabling/deleting the domain failed (retried on a bounded poll), or the admin credential vanished mid-teardown (fail-open: the domain is retained and the finalizer released)Deleting domain "corp" failed: <error>
IdentityProviderCreatedNormalAn OIDC backend's keystone identity provider is registeredCreated identity provider "keycloak-cobaltcore" (remote ID <issuer>)
MappingCreatedNormalThe federation mapping is created from the typed rulesCreated federation mapping "keycloak-cobaltcore-mapping" (2 rules)
MappingUpdatedNormalRules drift converged back with a single updateUpdated federation mapping "keycloak-cobaltcore-mapping" (2 rules)
ProtocolCreatedNormalThe federation protocol is bound to the mappingCreated protocol "openid" on identity provider "keycloak-cobaltcore" (mapping keycloak-cobaltcore-mapping)
FederationGroupCreatedNormalA declarative target group is created in the backend's domainCreated group "federated-users" in domain <id>
FederationObjectsDeletedNormalFinalizer teardown removed protocol, mapping, and identity provider (reverse dependency order)Deleted protocol "openid", mapping "keycloak-cobaltcore-mapping", and identity provider "keycloak-cobaltcore"
FederationTeardownFailedWarningA federation-object delete failed (retried on a bounded poll), or the admin credential vanished mid-teardown (fail-open)Deleting mapping "keycloak-cobaltcore-mapping" failed: <error>
IdentityBackendSkippedWarningA backend's bind/client Secret is missing or lacks its fixed data key, the provider metadata is unreachable or issuer-mismatched, or a rendered value carries a control character; the backend is skipped while healthy siblings keep projecting (emitted on the Keystone CR)Skipping identity backend corp-ldap: <error>
FederationProxyImageMissingWarningAt least one federation backend (OIDC or SAML) is attached but spec.federation.proxyImage is unset; every federation backend stays pending (emitted on the Keystone CR, once per reconcile pass)Identity backend keycloak-cobaltcore is pending: spec.federation.proxyImage is not set — configure the federation sidecar image before attaching federation backends
FederationMetadataStaleWarningA discovery-based backend's provider metadata endpoint was unreachable on a cache miss (e.g. an operator restart); the operator reused the last-known-good discovery document so federation stays up until the IdP recovers (emitted on the Keystone CR)Identity backend keycloak-cobaltcore: provider metadata unavailable (<error>); reusing the last-known-good discovery document so federation stays up

Source: keystoneidentitybackend_controller.go (domain lifecycle); reconcile_federation_objects.go (federation objects); reconcileIdentityBackends in reconcile_identitybackends.go (IdentityBackendSkipped, FederationProxyImageMissing); renderOIDCBackend in reconcile_federation.go (FederationMetadataStale)

Finalization ​

Emitted while the two finalizers tear a deleted Keystone CR down. The "Finalizing" events are gated on live 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 Keystone
DatabaseFinalizedNormalMariaDB resources marked for deletion; database finalizer releasedMariaDB Database, User, and Grant marked for deletion; releasing finalizer
FinalizingOpenBaoSecretsNormalDeletion begins while OpenBao backup PushSecrets are still liveCleaning up OpenBao backup PushSecrets before removing Keystone
OpenBaoSecretsFinalizedNormalBackup PushSecrets deleted; OpenBao finalizer releasedOpenBao backup PushSecrets deleted; releasing openbao-finalizer
RemoteChildrenAbandonedWarningDeletion begins while the target cluster the CR named no longer resolves; the finalizers are 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
ESOAdoptionTimedOutWarningA backup PushSecret was not adopted by ESO within the bounded wait after deletion; the controller force-deletes it to release the finalizerPushSecret "<name>" not adopted by ESO within <timeout> of deletion; force-deleting to release the openbao-finalizer (the OpenBao kv-v2 path may be orphaned only if ESO is not running)
OpenBaoCleanupStalledWarningA backup PushSecret stayed Terminating behind ESO's cleanup finalizer past the bounded gone-wait — ESO can no longer purge its kv-v2 path, typically because a namespace deletion reaped the SecretStore the purge authenticates through; the controller force-removes the PushSecret finalizers so the CR (and its namespace) can finish deletingESO could not delete the OpenBao data behind PushSecret(s) [<names>] within <timeout> of deletion; the kv-v2 path(s) [<paths>] may still hold the backed-up keys — delete them by hand

Source: finalizer handlers in keystone_controller.go; ESOAdoptionTimedOut and OpenBaoCleanupStalled from the adoption-wait and gone-wait passes in reconcile_secrets.go

Deployment Rollout ​

ReasonTypeTrigger ConditionExample Message
DeploymentRolloutCompleteNormalDeployment becomes ready during the UpgradePhaseRollingUpdate phase of an upgradeDeployment rollout complete during upgrade 2026.1 → 2026.2

Source: reconcileDeployment in reconcile_deployment.go

Note: This event fires only during an active upgrade's rolling update phase. Normal steady-state Deployment readiness does not emit an event.

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: [DEFAULT] use_stderr (container logs will no longer reach kubectl logs)

Source: 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.


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 any bootstrap failure
kubectl get events --field-selector reason=BootstrapFailed -w

# Watch for upgrade-related warnings
kubectl get events --field-selector reason=ExpandFailed -w
kubectl get events --field-selector reason=MigrateFailed -w
kubectl get events --field-selector reason=ContractFailed -w

# Watch for schema drift
kubectl get events --field-selector reason=SchemaDriftDetected -w

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

Prometheus Alertmanager Example ​

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

yaml
groups:
  - name: keystone-events
    rules:
      - alert: KeystoneBootstrapFailed
        expr: |
          increase(kube_event_count{
            reason="BootstrapFailed",
            involved_object_kind="Keystone"
          }[5m]) > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "Keystone bootstrap failed"
          description: "The Keystone bootstrap Job has failed. Check the Job logs for details."

      - alert: KeystoneSchemaDrift
        expr: |
          increase(kube_event_count{
            reason="SchemaDriftDetected",
            involved_object_kind="Keystone"
          }[5m]) > 0
        for: 0m
        labels:
          severity: warning
        annotations:
          summary: "Keystone schema drift detected"
          description: "The database schema does not match the expected Alembic migration head."

      - alert: KeystoneUpgradePhaseFailed
        expr: |
          increase(kube_event_count{
            reason=~"ExpandFailed|MigrateFailed|ContractFailed",
            involved_object_kind="Keystone"
          }[5m]) > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "Keystone upgrade phase failed"
          description: "An upgrade phase Job has failed. The upgrade is stalled and requires investigation."

Event Flow ​

The event flow of the Keystone operator: the sub-reconcilers that emit events, and for each the condition and the event it emits. KeystoneReconciler.Reconcile(): finalizers (deletionTimestamp set) emits Normal FinalizingDatabase when MariaDB CRs still live; Normal DatabaseFinalized when MariaDB cleanup done; Normal FinalizingOpenBaoSecrets when Backup PushSecrets still live; Warning ESOAdoptionTimedOut when ESO adoption wait exceeded; Warning OpenBaoCleanupStalled when ESO purge gone-wait exceeded; Normal OpenBaoSecretsFinalized when PushSecrets deleted; reconcileBootstrap() emits Warning AdminSecretInvalid when Admin Secret missing/invalid; Normal BootstrapComplete when Job succeeds; Warning BootstrapFailed when Job fails; reconcileDatabase(): Non-upgrade path emits Warning DBSyncFailed when db_sync fails; Warning SchemaDriftDetected when schema-check fails; Normal DatabaseSynced when Both succeed; Warning DBSyncMetricEmissionDeferred when Job-UID patch fails; Upgrade path emits Warning UpgradeTargetChanged when Tag changed mid-upgrade; Normal UpgradeAborted when Tag reverted mid-upgrade; Warning VersionParseError when Version parse error; Warning DowngradeNotSupported when Downgrade attempted; Warning UpgradePathInvalid when Non-sequential upgrade; Normal UpgradeInitiated when Upgrade validated; Normal ExpandComplete when Expand succeeds; Warning ExpandFailed when Expand fails; Normal MigrateComplete when Migrate succeeds; Warning MigrateFailed when Migrate fails; Normal UpgradeComplete when Contract succeeds; Warning ContractFailed when Contract fails; reconcileFernetKeys() emits Normal FernetKeysGenerated when Initial Secret created; Normal FernetKeysRotated when Staged rotation applied; Warning RotationAnnotationInvalid when Malformed completion annotation; Warning RotationRejected when Staged key set rejected; reconcileCredentialKeys() emits Normal CredentialKeysGenerated when Initial Secret created; Normal CredentialKeysRotated when Staged rotation applied; Warning RotationAnnotationInvalid when Malformed completion annotation; Warning RotationRejected when Staged key set rejected; reconcilePasswordRotation() emits Normal AdminPasswordRotated when Staged rotation applied; Warning AdminPasswordRotationAnnotationInvalid when Malformed completion annotation; Warning AdminPasswordRotationRejected when Staged password rejected; reconcileConfig() emits Warning ExtraConfigOwnedKeyOverride when spec.extraConfig overrides operator-owned keys (gated on transition into ExtraConfigHealthy=False, Reason=OwnedKeysOverridden); reconcileTrustFlush() emits Warning TrustFlushBypass when Legacy nil spec.trustFlush bypass; reconcileDeployment() emits Normal DeploymentRolloutComplete when Ready during upgrade rollout;

The figure draws which sub-reconciler emits which event, and on what. The Event Reasons Reference describes every reason.