Placement Operator
The Placement operator deploys and manages the OpenStack Placement service as a Kubernetes-native workload. It is the fourth service operator built on the shared scaffolding the Keystone operator established (internal/common, the operator-library Helm chart, the parameterized operator image), after Keystone, Horizon, and Glance.
Placement is the second Keystone API consumer after Glance: the API server runs with a [keystone_authtoken] service user and validates a Keystone token on every request, so a plain spec.keystoneEndpoint URL points the pods at the auth endpoint they reach server-side. Its profile is the smallest of the database-backed services so far. An API, a MariaDB schema, and a service user are the whole surface: no stores, no message bus, no satellite CRD.
Design decisions
The v1 operator resolves the onboarding decisions as follows:
- One uWSGI launch mode for every release. Placement never shipped an eventlet server, so it carries no worker count outside uWSGI. The command loads the application through
--wsgi-file /var/lib/openstack/bin/placement-api, the WSGI entry file the operator's Placement image writes, and the config location travels in theOS_PLACEMENT_CONFIG_DIR=/etc/placementenvironment variable. No--pyargvis emitted: the entry callsinit_application()with no arguments and placement's_get_config_filesnever readssys.argv, so a value passed there would reach nothing. See Container Images. - Probes target
GET /. Startup, readiness and liveness all GET/, the version-discovery document, which placement answers without a token and without opening the database. The startup probe allows 300 seconds (30 probes 10 seconds apart, each with an 8-second timeout) before the liveness probe takes over. A cold start therefore does not restart the container, even when a CPU limit set on the container or a contended node slows the imports or the boot-time sync of traits and resource classes against the database runs long. The liveness probe then allows 10 seconds per check, the readiness probe's timeout, so one slow request on a single-worker API does not restart the container. The oslo healthcheck middleware Glance probes at/healthcheckhas nowhere to be wired here: placement composes its middleware stack in code and reads no paste pipeline, so the operator renders noapi-paste.iniand the CRD carries nomiddlewarefield. - No upgrade phase machine. The CR has no
upgradePhasestatus field. A release bump runs the one{name}-db-syncJob, which chainsplacement-manage db sync,placement-manage db online_data_migrations, andplacement-status upgrade check. The upgrade check exits 1 for warnings, which the Job tolerates, and 2 for errors, which fails it. Upgrade paths stay sequential and a downgrade is refused before any Job runs. - No recurring maintenance task.
placement-managecarries four subcommands (db sync,db version,db stamp,db online_data_migrations) and none of them purges, archives, or cleans up. The placement schema does not soft-delete either, so no backlog of dead rows accumulates to reclaim. The operator projects no maintenance CronJob, and the validating webhook carries nometadata.namelength bound. Glance needs one because Kubernetes caps a CronJob name at 52 characters, which itsdb-purgesuffix spends down to 43 for the CR name; every placement-owned object fits inside the 63 characters Kubernetes already allows a CR name. - Service-user and DSN delivery.
[keystone_authtoken]renders without the password. The service-user password arrives as theOS_KEYSTONE_AUTHTOKEN__PASSWORDenvironment variable and the assembled DSN asOS_PLACEMENT_DATABASE__CONNECTION, both sourced from Secrets, so no credential material enters the namespace-readable ConfigMap. Each value is digested into a pod-template annotation, so a rotation at the source rolls the pods. [placement_database], not[database]. Placement readsconnection,max_retries, andconnection_recycle_timefrom its own[placement_database]section, andauth_strategyfrom[api], where upstream moved the option away from the deprecated[DEFAULT]spelling. Both spellings ofauth_strategyare registered as operator-owned and refused inspec.extraConfig, becauseextraConfighas the last word in the merge andnoauth2would put the API on the no-auth middleware.
Owned resources
The figure under Owned Resources of the Keystone operator draws the baseline this list follows: serving objects, config and Secrets, Jobs and CronJobs.
For a Placement CR named {name} the operator manages:
| Resource | Name | Purpose |
|---|---|---|
| Deployment | {name} | The Placement API pods (port 8778) |
| Service | {name} | ClusterIP in front of the API pods on port 8778 |
| PodDisruptionBudget | {name} | minAvailable: 1 (or maxUnavailable: 1 at a lower replica bound of one, which is the HPA's minReplicas while spec.autoscaling is set) |
| HorizontalPodAutoscaler | {name} | Only when spec.autoscaling is set |
| NetworkPolicy | {name} | Only when spec.networkPolicy is set |
| HTTPRoute | {name} | Only when spec.gateway is set |
| ConfigMap | {name}-config-<hash> | Immutable, content-addressed placement.conf (plus policy.yaml / logging.conf when applicable; 3 historical retained) |
| Secret | {name}-db-connection | Derived pymysql DSN, consumed via OS_PLACEMENT_DATABASE__CONNECTION |
| Job | {name}-db-sync | Schema migration, online data migrations, and the upgrade check |
MariaDB Database / User / Grant | {name} | Managed mode only (spec.database.clusterRef); a brownfield database is left alone |
Reference pages
- Placement CRD — the full
spec/statuscontract - Controller Events — the Kubernetes events the controller emits
- Reconciler Architecture — the sub-reconciler pipeline, conditions, and requeue semantics