Skip to content

Glance Operator ​

The Glance operator deploys and manages the OpenStack Image service as a Kubernetes-native workload. It is the third service operator built on the shared scaffolding the Keystone operator established (internal/common, the operator-library Helm chart, the parameterized operator image), after Keystone and Horizon.

Glance is the first service operator that is a Keystone API consumer: 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 other distinguishing trait is state: images live in a shared object store, and each store is modeled out-of-band as a separate GlanceBackend CR rather than inline in the Glance spec, so stores can be attached and detached without editing the Glance CR.

Design decisions ​

The v1 operator resolves the onboarding decisions as follows:

  • uWSGI launch mode. The API server launches under uWSGI on every supported release through the image-shipped glance-wsgi-api shim, because glance's own WSGI module ignores sys.argv and cannot be pointed at the operator's config directories directly. See Container Images.
  • Always-rendered reserved stores. Glance registers the os_glance_staging_store and os_glance_tasks_store filesystem stores at /var/lib/glance/staging and /var/lib/glance/tasks-work on every deployment, even an all-object-store one: import staging and async task work land on local disk regardless of the image store. Both are emptyDirs bounded by a default sizeLimit of 10Gi (spec.staging.sizeLimit), so an oversized import gets the glance-api pod evicted rather than running until the node's disk is full; see StagingSpec for what that bound does and does not guarantee. enabled_import_methods is rendered as [web-download,copy-image] — glance-direct is deliberately excluded because it stages the uploaded image on the API pod's local disk, and there is no staging volume shared across replicas, so an import begun on one pod could not be finished by another. Every deployment also renders an [import_filtering_opts] group, so a web-download URI is filtered before glance fetches it: HTTPS on port 443, plus a literal host denylist covering loopback, the link-local metadata address, and the in-cluster API server. See ImportFilteringSpec.
  • Image cache: sqlite driver, bounded emptyDir, sidecar pruner.spec.imageCache turns on a per-replica cache of the image data the API has served. The driver is pinned to sqlite even though upstream deprecates it in favour of centralized_db: that default keys cache state in the Glance database per worker_self_reference_url, so with Deployment pods whose names change on every replacement it strands node_reference and cached_images rows there and turns every cache hit into a database write. sqlite keeps the metadata inside the cache directory, where it shares the volume's lifecycle. The directory is a bounded emptyDir and not a PVC, so the operator assumes no StorageClass, reuses the bounded-scratch pattern the staging volumes already establish, and avoids a bound that would be fiction on kind, whose local-path provisioner enforces no capacity. Pruning runs in a cache-maintenance sidecar because a CronJob cannot reach a pod-local volume. See ImageCacheSpec.
  • Import plugins: an opt-in block per plugin, rendered in a fixed order.spec.importPlugins selects the image-import plugins, and the presence of a sub-block is the switch for its plugin. The rendered order (image_decompression, image_conversion, inject_image_metadata) is the operator's and not an input, which is what satisfies upstream's one ordering requirement: decompression has to precede conversion, or conversion would rewrite the archive instead of the disk image inside it. image_import_plugins is rendered on every deployment, [] while the block is unset, so a missing key never leaves Glance on its own default. The plugins are stages of the interoperable import flow, so a PUT /v2/images/{id}/file upload bypasses them and copy-image skips them, which leaves web-download imports. The Glance image ships qemu-img and lhafile, the two the conversion and decompression plugins shell out to and import at run time. See ImportPluginsSpec.
  • isDefault lives on the backend CR. Exactly one attached, credential-ready GlanceBackend must be marked isDefault; that backend becomes the [glance_store] default_backend. The glance-side sub-reconciler and a sibling-uniqueness webhook both enforce the single-default invariant.
  • Keystone endpoints: two plain URL fields. spec.keystoneEndpoint renders as [keystone_authtoken] auth_url (the pod-reachable, server-side URL) and the optional spec.keystonePublicEndpoint as www_authenticate_uri (the browser/client-facing address a 401 points at). The service-user password is never rendered into config: it is delivered as the OS_KEYSTONE_AUTHTOKEN__PASSWORD environment variable, digested into a pod-template annotation so a rotation rolls the pods.
  • /healthcheck probes. Startup, readiness and liveness all GET /healthcheck, served by the oslo healthcheck middleware without touching the database or Keystone. The startup probe allows 300 seconds (30 probes 10 seconds apart, each with an 8-second timeout) before the liveness probe takes over, so a cold start slowed by a CPU limit set on the container or a contended node does not restart the container. The liveness probe then allows 10 seconds per check, the readiness probe's timeout, so a check that waits behind a request in flight no longer fails at the kubelet's 1-second default. Under uWSGI the check still needs a free worker: image transfers that hold every worker (two by default) for 50 seconds or more can fail three checks in a row, and the kubelet then restarts the container in the middle of them.
  • Expand-migrate-contract upgrades. When spec.openStackRelease advances to a new OpenStack release (with the image in lockstep), the operator drives phased database migrations while the API keeps serving. Sequential-only upgrade paths; a fresh install or a same-release image bump stays on the single-pass glance-manage db sync. See Upgrade Flow.
  • No live S3 probing by the operator. The operator never connects to an S3 endpoint to validate a backend; it only resolves the credentials Secret and renders the store section. Bucket reachability is a runtime concern of the Glance pods.

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 Glance CR named {name} the operator manages:

ResourceNamePurpose
Deployment{name}The Glance API pods (port 9292)
Service{name}ClusterIP in front of the API pods on port 9292
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 glance-api.conf / glance-api-paste.ini (3 historical retained)
Secret{name}-backends-<hash>Immutable, content-addressed backends.conf (the aggregated store sections; 3 historical retained)
Secret{name}-db-connectionDerived pymysql DSN, consumed via OS_DATABASE__CONNECTION
Job{name}-db-syncglance-manage db sync schema migration
CronJob{name}-db-purgeScheduled purge of soft-deleted task rows (image rows opt-in)

Reference pages ​