Quick Start (ControlPlane): C5C3 + K-ORC on Kind
This guide takes a single c5c3 ControlPlane CR from git clone to an authenticated Keystone API call. Compared with the Quick Start, the c5c3-operator now provisions the MariaDB, Memcached, Keystone, Horizon, Glance, Placement, and Barbican children, mints the admin application credential through K-ORC, mirrors it to OpenBao, and registers the identity catalog.
Prerequisites
Same toolchain as the Quick Start, plus:
makeonPATHforinstall-test-deps,deploy-infra, andteardown-infra- The OpenStack CLI (
python-openstackclient) onPATHfor the auth check in Step 6, plus two plugins for the other checks in that step:osc-placementfor the placement call andpython-barbicanclientfor theopenstack secretsubcommands - A stable internet connection while
make deploy-infraclones K-ORC from GitHub - Roughly 8 GB RAM, 2 CPU cores, and 10 GB of free disk for a laptop-sized kind cluster
yqv4.x onPATHfor theKIND_HOST_PORT=8443override path in Step 2
Docker Desktop and Podman are both valid kind providers. When using Podman, ensure its machine is already running and select it explicitly before running Step 2:
export KIND_EXPERIMENTAL_PROVIDER=podmanThe bundled kind
ControlPlaneCR pins its backing services to a single instance (spec.infrastructure.database.replicas: 1,cache.replicas: 1) so the fresh-create chain fits a single-node kind cluster.database.replicas: 1yields a single-instance, non-Galera MariaDB andcache.replicas: 1a single Memcached pod. The CRD default for both is3, which matches the production baseline but OOM-kills a laptop-sized kind.On a bigger box, set
CONTROLPLANE_DB_REPLICAS=3and/orCONTROLPLANE_CACHE_REPLICAS=Nfor Step 2.2is rejected for the database — Galera needs a quorum.database.replicasis immutable after the CR is created, so change it on a fresh environment (make teardown-infrafirst).The bundled CR also pins the MariaDB volume to a test size (
spec.infrastructure.database.storageSize: 512Mi).The CRD default is
100Gi, which a kind/CI run never fills, so the managed MariaDB requests a small volume instead.To mirror the production volume on a bigger box, set
CONTROLPLANE_DB_STORAGE=100Gifor Step 2. Any Kubernetes quantity inMi/Gi/Tiis accepted.
make install-test-deps
export PATH="${HOME}/.local/bin:${PATH}"Step 1 — Clone
git clone https://github.com/c5c3/cobaltcore.git
cd cobaltcoreStep 2 — Cluster + ControlPlane stack
KIND_HOST_PORT=8443 WITH_CONTROLPLANE=true make deploy-infraWITH_CONTROLPLANE=true brings up the shared infrastructure and then the ControlPlane operator stack (keystone-operator, horizon-operator, glance-operator, placement-operator, barbican-operator, K-ORC, c5c3-operator) from the published charts — but not the ControlPlane CR itself; you create and apply that in Step 3. In this mode the ControlPlane provisions its own MariaDB/Memcached (managed mode), so deploy-infra does not create the shared ones. KIND_HOST_PORT=8443 maps the Gateway to a non-privileged host port for macOS; on Linux with rootful Docker drop the override and use port 443. Expect 5–10 minutes.
If a download or image pull fails, run make teardown-infra and repeat Step 2.
Fresh operator images after a merge
The operator images are published under the mutable :latest tag, so deploy-infra pins them to the digest current at deploy time (per-operator image-digest ConfigMaps consumed by the HelmReleases via valuesFrom). After a feature merges to main, run make refresh-operator-digests against the running cluster: it re-resolves the digests, updates the ConfigMaps, and requests a Flux reconcile so the operators roll to the freshly built images — no redeploy needed. The helper prefers docker buildx, but falls back to curl if Docker is unavailable.
Step 3 — Create the ControlPlane CR
Apply a ControlPlane CR. You only supply openStackRelease and the services.keystone block; the defaulting webhook fills the infrastructure and admin-credential references with their well-known names:
openstack-dbandopenstack-memcachedfor the managed infrastructurekeystone-dbandkeystone-admin/passwordas placeholders that the operator replaces with per-ControlPlane Secrets in managed modek-orc-clouds-yamlwith theadmincloud entry
The c5c3-operator seeds the K-ORC bootstrap clouds.yaml per CR, deriving the in-cluster Keystone auth URL from the CR name. To use a different name, pass CONTROLPLANE_NAME=foo to Step 2; it renames the bundled CR and seeds the matching admin password. The defaulting only fills the names and references; the operator still consumes the pre-seeded Secret content and materialises the bootstrap clouds.yaml itself.
# controlplane.yaml
apiVersion: c5c3.io/v1alpha1
kind: ControlPlane
metadata:
name: controlplane
namespace: openstack
spec:
openStackRelease: "2025.2"
# Single-node backing services for kind. Omit these and both default to 3 (a
# 3-node Galera MariaDB plus three Memcached pods), which OOM-kills a small kind.
infrastructure:
database:
replicas: 1 # single-instance, non-Galera MariaDB (Galera = replicas > 1)
storageSize: 512Mi # test-sized volume; omit to default to 100Gi (production)
cache:
replicas: 1 # single Memcached pod
services:
keystone:
replicas: 1
# Drop publicEndpoint on the default port 443 — the operator then derives
# https://keystone.127-0-0-1.nip.io/v3 from the gateway hostname.
publicEndpoint: https://keystone.127-0-0-1.nip.io:8443/v3
gateway:
parentRef:
name: openstack-gw
hostname: keystone.127-0-0-1.nip.io
path: /
horizon:
replicas: 1
# Exposed through the same shared Envoy Gateway as Keystone, via the
# second HTTPS listener the kind overlay adds for horizon.127-0-0-1.nip.io.
gateway:
parentRef:
name: openstack-gw
hostname: horizon.127-0-0-1.nip.io
glance:
replicas: 1
# Drop publicEndpoint on the default port 443 — the operator then derives
# https://glance.127-0-0-1.nip.io from the gateway hostname.
publicEndpoint: https://glance.127-0-0-1.nip.io:8443
# Exposed through the same shared Envoy Gateway, via the third HTTPS
# listener the kind overlay adds for glance.127-0-0-1.nip.io.
gateway:
parentRef:
name: openstack-gw
hostname: glance.127-0-0-1.nip.io
# One curated S3 image store on the in-cluster Garage object store; the
# Step 2 stack ships the Garage cluster and the glance-images bucket.
backends:
- name: default
type: S3
isDefault: true
s3:
endpoint: http://garage.shared-services.svc.cluster.local:3900
bucket: glance-images
region: garage
credentialsSecretRef:
name: garage-s3-credentials
placement:
replicas: 1
# Drop publicEndpoint on the default port 443 — the operator then derives
# https://placement.127-0-0-1.nip.io from the gateway hostname.
publicEndpoint: https://placement.127-0-0-1.nip.io:8443
# Exposed through the same shared Envoy Gateway, via the sixth HTTPS
# listener the kind overlay adds for placement.127-0-0-1.nip.io.
gateway:
parentRef:
name: openstack-gw
hostname: placement.127-0-0-1.nip.io
barbican:
replicas: 1
# An OpenBao instance this ControlPlane provisions and owns; its name, its
# KV mount, and its AppRole are derived by convention, so the block is empty.
secretStore:
dedicated: {}
# Drop publicEndpoint on the default port 443; the operator then derives
# https://barbican.127-0-0-1.nip.io from the gateway hostname.
publicEndpoint: https://barbican.127-0-0-1.nip.io:8443
# Exposed through the same shared Envoy Gateway, via the seventh HTTPS
# listener the kind overlay adds for barbican.127-0-0-1.nip.io.
gateway:
parentRef:
name: openstack-gw
hostname: barbican.127-0-0-1.nip.iokubectl apply -f controlplane.yamlThe horizon block makes the reconciler project the OpenStack Dashboard once its Keystone child is Ready. Everything else is derived: the image tag from spec.openStackRelease, the Memcached wiring from spec.infrastructure.cache, and the Keystone endpoint from the Keystone child's naming convention. The Django SECRET_KEY defaults to the kind-only horizon-secret-key Secret (seeded per the default ControlPlane identity); a second ControlPlane must set services.horizon.secretKeyRef to its own Secret. A HorizonReady condition joins the chain (after KeystoneReady) and status.services gains a second entry.
The glance block makes the reconciler project the OpenStack Image service and one GlanceBackend child per backends entry — here a single S3 store on the in-cluster Garage object store. Garage runs in shared-services; the Step 2 stack also syncs a garage-s3-credentials Secret into openstack, which is where the Glance child resolves credentialsSecretRef. The gateway block exposes the image API through the same shared Envoy Gateway as Keystone and Horizon, on the third HTTPS listener the kind overlay adds for glance.127-0-0-1.nip.io, and publicEndpoint makes the public image catalog row advertise the actually reachable host URL — the :8443 host port — instead of the default-443 form the operator would otherwise derive from the gateway hostname. The operator projects a KeystoneService registration controlplane-glance carrying the image catalog entry and the glance service account (user glance, project service-glance, role service), so Glance can validate the Keystone tokens it receives; its database and cache derive from spec.infrastructure, exactly like Keystone's. On the managed shared database its DB credential is engine-issued and auto-rotated exactly like Keystone's — short-lived leases from the OpenBao database engine, and the Step 4 onboarding provisions the engine tenant for all four database services (keystone, glance, placement, and barbican). A GlanceReady condition joins the chain — gating on KeystoneReady plus that registration having provisioned the account — and status.services gains a third entry.
The placement block projects a Placement child, controlplane-placement: the API deployment, its own logical schema on the shared MariaDB, and a Keystone service user. Its own KeystoneService registration controlplane-placement carries the placement catalog entry and the placement account (user placement, role service) with a project of its own, service-placement; each registration creates its project, so two naming one project would collide. Database and cache derive from spec.infrastructure the same way Glance's do, and on the managed shared database the DB credential is engine-issued too, from the tenant Step 4 onboards. The gateway block puts the API on the sixth HTTPS listener the kind overlay adds, placement.127-0-0-1.nip.io, and publicEndpoint carries the :8443 host port into the public placement catalog row. A PlacementReady condition joins the chain next to GlanceReady, gated the same way, and status.services gains a fourth entry.
The barbican block adds the Key Manager service. secretStore.dedicated asks for an OpenBao instance this ControlPlane owns, so the operator projects three CRs: the Barbican child controlplane-barbican, the instance controlplane-barbican-bao, and the store controlplane-barbican-store that attaches the two. A KeystoneService registration controlplane-barbican carries the key-manager catalog entry and the barbican account (user barbican, role service) with a project of its own, service-barbican. Its database credential is engine-issued from the tenant Step 4 onboards, like Glance's and Placement's. The gateway block puts the key-manager API on the seventh HTTPS listener, barbican.127-0-0-1.nip.io, and publicEndpoint carries the :8443 host port into the public key-manager catalog row. A BarbicanReady condition joins the chain beside GlanceReady and PlacementReady, gated the same way, and status.services gains a fifth entry. The projected instance is proving-grade: one replica, no PodDisruptionBudget, sealed by a static key in a plain Secret beside its volume. Run Barbican on a Dedicated OpenBao covers the same service step by step, including the external-server alternative.
Applying the CR is not the end of the manual work: a hand-applied ControlPlane needs the one-time OpenBao onboarding in Step 4 before the chain can progress past its database credentials.
Equivalent fully-expanded form (what the webhook defaults to)
# controlplane.yaml
apiVersion: c5c3.io/v1alpha1
kind: ControlPlane
metadata:
name: controlplane
namespace: openstack
spec:
openStackRelease: "2025.2"
region: RegionOne
infrastructure:
database:
clusterRef:
name: openstack-db # MariaDB the operator provisions (managed mode)
database: keystone
secretRef:
name: keystone-db # placeholder default — the operator replaces it
# with {name}-keystone-db-credentials (managed mode)
replicas: 1 # single-instance, non-Galera; omit to default to 3 (Galera)
storageSize: 512Mi # test-sized volume; omit to default to 100Gi (production)
cache:
clusterRef:
name: openstack-memcached
backend: dogpile.cache.pymemcache
replicas: 1 # single Memcached pod; omit to default to 3
services:
keystone:
replicas: 1
publicEndpoint: https://keystone.127-0-0-1.nip.io:8443/v3
gateway:
parentRef:
name: openstack-gw
hostname: keystone.127-0-0-1.nip.io
path: /
horizon:
replicas: 1
gateway:
parentRef:
name: openstack-gw # same Gateway as Keystone; second listener
hostname: horizon.127-0-0-1.nip.io
secretKeyRef:
name: horizon-secret-key # default-identity kind shim Secret
key: secret-key
glance:
replicas: 1
publicEndpoint: https://glance.127-0-0-1.nip.io:8443
gateway:
parentRef:
name: openstack-gw # same Gateway; third listener
hostname: glance.127-0-0-1.nip.io
backends:
- name: default
type: S3
isDefault: true
s3:
endpoint: http://garage.shared-services.svc.cluster.local:3900
bucket: glance-images
region: garage
credentialsSecretRef:
name: garage-s3-credentials
placement:
replicas: 1
publicEndpoint: https://placement.127-0-0-1.nip.io:8443
gateway:
parentRef:
name: openstack-gw # same Gateway; sixth listener
hostname: placement.127-0-0-1.nip.io
barbican:
replicas: 1
secretStore:
dedicated: {} # OpenBao instance projected beside the child
publicEndpoint: https://barbican.127-0-0-1.nip.io:8443
gateway:
parentRef:
name: openstack-gw # same Gateway; seventh listener
hostname: barbican.127-0-0-1.nip.io
korc:
adminCredential:
cloudCredentialsRef:
cloudName: admin # entry in the operator-materialised k-orc-clouds-yaml Secret
secretName: k-orc-clouds-yaml
passwordSecretRef:
name: keystone-admin # spec-level/brownfield default — in managed mode the
# operator projects {name}-keystone-admin-credentials
# and points the Keystone child at it instead
key: password
applicationCredential:
rotation:
mode: PasswordDrivenStep 4 — Onboard the OpenBao database-engine tenant
In managed mode the ControlPlane defaults to engine-issued (Dynamic) Keystone DB credentials: ESO draws short-lived MySQL users from the OpenBao database engine at database/mariadb/creds/keystone-<namespace>. The c5c3-operator only reads from that path — the engine connection and the per-tenant role are provisioned out-of-band, once per ControlPlane, by deploy/openbao/bootstrap/setup-database-tenant.sh.
Here <namespace> is the Keystone service namespace — the ControlPlane's own namespace (openstack) in this quick start, and only different when spec.services.keystone.namespace places the Keystone service in a namespace of its own. The onboarding script resolves it from the live ControlPlane spec, so the two arguments below always name the ControlPlane, wherever its Keystone lands.
Run it after the kubectl apply from Step 3, as soon as the projected MariaDB is Ready (the script configures the engine's database connection, so it needs a reachable database):
kubectl wait mariadb/openstack-db -n openstack --for=condition=Ready --timeout=10m
export BAO_TOKEN=$(kubectl get secret openbao-init-keys -n shared-services \
-o jsonpath='{.data.init-output}' | base64 -d | jq -r '.root_token')
deploy/openbao/bootstrap/setup-database-tenant.sh openstack controlplane
unset BAO_TOKENThe two arguments are the ControlPlane namespace and name (openstack controlplane here; adjust the second one if you renamed the CR via CONTROLPLANE_NAME in Step 2). BAO_TOKEN is read from the openbao-init-keys Secret where deploy-infra stores the root token — kind-only plumbing; against a production OpenBao use a token with write access to database/mariadb/*. The script is idempotent: re-running it refreshes the connection and role in place.
Skip this step only when:
- Step 2 ran with
WITH_CONTROLPLANE_CR=true— deploy-infra then onboards the bundled ControlPlane automatically, or - the ControlPlane opts out of Dynamic credentials with
spec.infrastructure.database.credentialsMode: Static(see Migrate Keystone DB to Dynamic Credentials).
If you skip it
The reconcile chain stalls before any Keystone or Horizon child is created: the ControlPlane reports DBCredentialsReady=False (reason WaitingForDBCredentialSecret), the controlplane-keystone-db-credentials ExternalSecret sits in SecretSyncedError, and the external-secrets controller logs unknown role: keystone-<namespace>. Nothing is lost — run the onboarding script and ESO syncs the credential on its next retry.
Optional: a per-tenant OpenBao identity
By default this ControlPlane reaches OpenBao through the shared cluster store openbao-cluster-store. To give it its own OpenBao identity (so OpenBao itself enforces isolation from other tenants), run deploy/openbao/bootstrap/setup-eso-tenant.sh openstack, wait for the openbao-tenant-store SecretStore to be Ready, then set spec.secretStoreRef: {kind: SecretStore, name: openbao-tenant-store} on the ControlPlane. See the multi-tenant deployment guide.
Step 5 — Watch the chain reconcile
The aggregate Ready flips to True once all 15 sub-conditions are met, in dependency order (HorizonReady gates on KeystoneReady; GlanceReady, PlacementReady, and BarbicanReady gate on KeystoneReady plus the KeystoneService registration each service projects for itself; ServiceAccountsReady then folds those three registrations, so it comes after them; the K-ORC branch runs alongside):
NamespacesReady → InfrastructureReady → ESOTenantStoreReady → DBCredentialsReady → AdminPasswordReady → KeystoneReady → HorizonReady → KORCReady → AdminCredentialReady → CatalogReady → GlanceReady → PlacementReady → BarbicanReady → ServiceAccountsReady → RegistrationTenantStoresReadyRegistrationTenantStoresReady closes the chain and reads True/NoRegistrationNamespaces on this devstack: it provisions secret stores for namespaces outside this ControlPlane's own that register services against it, and the quick start declares none.
kubectl get controlplane controlplane -n openstack \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'Wait for the aggregate condition:
kubectl wait controlplane/controlplane -n openstack \
--for=condition=Ready --timeout=15mStep 6 — Verify
The ControlPlane exposes the projected Keystone through the shared Envoy Gateway at https://keystone.127-0-0-1.nip.io:8443/v3 — the same path as the per-service Quick Start, no port-forward.
curl -k https://keystone.127-0-0-1.nip.io:8443/v3If your host cannot resolve *.nip.io
On some local setups that filter or block this specific DNS pattern, the following command fails:
curl -k https://keystone.127-0-0-1.nip.io:8443/v3The command returns the following error:
curl: (6) Could not resolve host: keystone.127-0-0-1.nip.ionip.io deliberately resolves hostnames like keystone.127-0-0-1.nip.io to the loopback address 127.0.0.1. Some routers ship DNS rebind protection, a security feature that silently drops any DNS response resolving a public hostname to a private or loopback address, which is this pattern. When your machine's resolver does this, nip.io never resolves. Confirm the cause by comparing your router against a public resolver:
dig +short keystone.127-0-0-1.nip.io @<your-router-ip> # empty: blocked
dig +short keystone.127-0-0-1.nip.io @1.1.1.1 # 127.0.0.1: as expectedThe most reliable fix is host-local: add loopback entries to /etc/hosts:
sudo sh -c 'cat >> /etc/hosts <<EOF
127.0.0.1 keystone.127-0-0-1.nip.io
127.0.0.1 horizon.127-0-0-1.nip.io
127.0.0.1 glance.127-0-0-1.nip.io
127.0.0.1 placement.127-0-0-1.nip.io
127.0.0.1 barbican.127-0-0-1.nip.io
EOF'For a one-off curl check without touching /etc/hosts, use --resolve to override DNS for a single host/port pair — curl still sends the original hostname in the request and TLS handshake, but connects directly to 127.0.0.1:
curl -k --resolve keystone.127-0-0-1.nip.io:8443:127.0.0.1 \
https://keystone.127-0-0-1.nip.io:8443/v3If you'd rather fix it at the network level, most routers let you exempt specific domains from rebind protection, or you can add a public resolver (e.g. 1.1.1.1) ahead of the router in your machine's network settings.
Then issue a token with the admin password:
export OS_AUTH_URL=https://keystone.127-0-0-1.nip.io:8443/v3
export OS_USERNAME=admin
export OS_PASSWORD=$(kubectl get secret controlplane-keystone-admin-credentials -n openstack -o jsonpath='{.data.password}' | base64 -d)
export OS_PROJECT_NAME=admin
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
openstack --insecure token issueThe admin password is read from the operator-owned per-ControlPlane Secret
controlplane-keystone-admin-credentials(named{ControlPlane name}-keystone-admin-credentials). In managed mode the c5c3-operator always projects this Secret, so the command holds for any identity — if you setCONTROLPLANE_NAME=fooin Step 2, readfoo-keystone-admin-credentialsinstead.
With the default
KIND_HOST_PORT=443usehttps://keystone.127-0-0-1.nip.io/v3and drop all fourpublicEndpointlines (keystone, glance, placement, and barbican) from the CR in Step 3.
Upload a first image
With the OS_* variables from the token-issue step still exported, confirm the Image service reached the catalog:
openstack --insecure catalog listAn image row proves Glance registered its endpoints. The public image catalog row now carries the gateway URL (https://glance.127-0-0-1.nip.io:8443, the publicEndpoint from Step 3), and the openstack CLI resolves the public interface by default, so the upload runs directly from the host through the shared Gateway — no in-cluster pod needed. --insecure accepts the listener's self-signed certificate, as with the Keystone calls above.
Create a throwaway 1 KiB image, upload it through the gateway, wait for it to reach active, and delete it again:
dd if=/dev/urandom of=/tmp/first.img bs=1024 count=1
openstack --insecure image create --disk-format raw --container-format bare \
--file /tmp/first.img first-image
for _ in $(seq 30); do
[ "$(openstack --insecure image show first-image -f value -c status)" = active ] && break
sleep 2
done
test "$(openstack --insecure image show first-image -f value -c status)" = active \
&& echo "OK: image uploaded through the gateway and reached active"
openstack --insecure image delete first-imageThe poll loop matters because image create returns as soon as Glance accepts the upload, while the store write completes asynchronously — a cold S3 connection or a contended Garage backend can leave the image in saving for a few seconds.
A run aborted before the final delete leaves first-image behind; delete it (openstack --insecure image delete first-image) before retrying, or the next image create fails on a name collision.
Leave OS_REGION_NAME unset
Do not add OS_REGION_NAME to the host exports: the projected K-ORC catalog rows carry no region, image, placement, and key-manager alike, so a region-scoped lookup finds no endpoint. The upload fails with public endpoint for image service in RegionOne region not found, the placement and secret calls with the same message under their own service names. Keystone's own bootstrap registers RegionOne identity rows, so identity is unaffected. Only the projected rows need the filter left clear.
List placement resource classes
With the same OS_* variables still exported, confirm the Placement service reached the catalog:
openstack --insecure catalog listA placement row proves the ControlPlane registered both endpoints: the in-cluster one at http://controlplane-placement.openstack.svc:8778 and the public one at https://placement.127-0-0-1.nip.io:8443, the publicEndpoint from Step 3. Then ask the API for its resource classes:
openstack --insecure resource class listThe call is read-only and Placement answers it only for an authenticated request, so a listing of the standard classes (VCPU, MEMORY_MB and DISK_GB among them) covers the catalog row, the gateway listener, and the service user's token validation in one command. It needs the osc-placement plugin from the prerequisites; without it the openstack CLI rejects resource class list as an unknown command. OS_REGION_NAME has to stay unset here as well, for the reason above.
Store and retrieve a first secret
With the same OS_* variables still exported, confirm the Key Manager service reached the catalog:
openstack --insecure catalog listA key-manager row proves the ControlPlane registered both endpoints: the in-cluster one at http://controlplane-barbican.openstack.svc:9311 and the public one at https://barbican.127-0-0-1.nip.io:8443, the publicEndpoint from Step 3. Store a secret through that public endpoint, read the payload back, and delete it again:
PAYLOAD='cobaltcore-quick-start-payload'
HREF=$(openstack --insecure secret store --name first-secret \
--payload "$PAYLOAD" -f value -c 'Secret href')
test "$(openstack --insecure secret get -p "$HREF" -f value -c Payload)" = "$PAYLOAD" \
&& echo "OK: payload came back unchanged from the key manager"
openstack --insecure secret delete "$HREF"A payload that survives the round-trip covers the whole key-manager chain in two calls: the catalog row resolves the endpoint, Barbican validates the admin token against Keystone, and the payload travels through castellan's vault plugin into the dedicated controlplane-barbican-bao instance and out again. The openstack secret subcommands come from the python-barbicanclient plugin in the prerequisites; without it the CLI rejects secret store as an unknown command. OS_REGION_NAME stays unset here too: the key-manager rows carry no region either, and with it set the store call fails with public endpoint for key-manager service in RegionOne region not found.
Do not substitute real key material into --payload
The literal above is a throwaway, and this snippet is written for a devstack. A value passed to --payload sits in the process argument vector, where any local user reads it out of ps or /proc/<pid>/cmdline for the life of the call, and typing it directly rather than through a variable also leaves it in your shell history. Feed real material in from a file or from standard input instead. --insecure belongs to this devstack for the same reason, and it reaches further than the payload: the flag disables certificate verification for the whole invocation, including the Keystone call that sends OS_PASSWORD. Anything that answers on the way collects the admin credential. Drop it anywhere the gateway presents a certificate you trust.
Open the Horizon dashboard
The dashboard is exposed through the same shared Envoy Gateway as Keystone, on its own horizon.127-0-0-1.nip.io listener:
open https://horizon.127-0-0-1.nip.io:8443/Your browser will warn that the certificate is not trusted — expected for a kind cluster (the listener terminates with a self-signed certificate). Log in with admin / the password from the controlplane-keystone-admin-credentials Secret above (domain Default).
After login the dashboard redirects to /project/, which reports "Unauthorized" — the default landing page needs Compute/Network services this control plane does not serve yet. Open the Identity panel instead:
open https://horizon.127-0-0-1.nip.io:8443/identity/With the default
KIND_HOST_PORT=443drop the:8443and openhttps://horizon.127-0-0-1.nip.io/.
Teardown
make teardown-infraRelated references
- ControlPlane CRD API Reference — every
spec.*field, the webhooks, and the status conditions. - ControlPlane Reconciler — the sub-reconciler ordering and gating semantics.
- Glance Operator — the projected Image service, its
GlanceBackendstores, and the reconciler chain. - Placement Operator — the projected Placement service, its CRD surface, and the reconciler chain.
- Barbican Operator — the projected Key Manager service, its
BarbicanSecretStoreattachment, and the reconciler chain. - Quick Start — the compact per-service Keystone path.