Purpose: For platform engineers and operators, shows how to generate the GitOps repository, provision OpenStack infrastructure with OpenTofu, and bring up an openCenter cluster that reconciles from Git.
This page covers the default Kubespray OpenStack flow. For Talos on OpenStack, see create-openstack-cluster.md[Create a Talos Cluster on OpenStack].
Prerequisites
-
openCenter CLI installed and on
PATH -
git,kubectl,openstack,opentofuinstalled -
fluxinstalled if you want to use the verification commands in the last section -
OpenStack application credentials with permission to create or manage networking, instances, volumes, security groups, and load balancer resources
-
Existing OpenStack network, subnet, image, and external network IDs for the target region
-
Enough quota for the default footprint: 1 bastion, 3 control planes, and 2 workers
-
A Git remote for the generated GitOps repository
The current OpenStack bootstrap path reads application credentials from the v2 cluster config. Do not rely on username/password-only examples from older docs.
Verify local tooling and OpenStack access before you start:
opencenter version
git --version
kubectl version --client
openstack --version
opentofu version
openstack token issue
Useful discovery commands:
openstack image list --name Ubuntu
openstack network list
openstack subnet list
openstack network list --external
Two Paths: Guided or Manual
openCenter supports two ways to configure an OpenStack cluster:
-
Guided (
cluster configure --guided): An interactive workflow that discovers your OpenStack resources (images, flavors, networks, availability zones) and walks you through each required field with prompts and validation. This is the recommended path for new clusters. -
Manual (
cluster init+cluster edit): Creates a config with placeholder defaults, then you edit the YAML by hand. Useful when you already know all the values or want to script the process.
Both paths produce the same v2 configuration file. You can start with guided mode and refine with cluster edit afterward.
Bootstrap Flow
The sequence diagram below shows the current OpenStack bootstrap path implemented by opencenter cluster deploy.
sequenceDiagram
autonumber
participant User as User (host)
participant CLI as opencenter CLI
participant BS as BootstrapService
participant OSP as OpenStackBootstrapProvider
participant Creds as Credentials Extractor
participant Tofu as opentofu CLI
participant OS as OpenStack API
participant K8s as kubectl / cluster API
User->>CLI: opencenter cluster deploy prod-cluster
CLI->>BS: Bootstrap(ctx, opts)
BS->>BS: load config, resolve paths, open log, load saved state
BS->>OSP: BuildSteps(cfg, clusterPaths, opts)
OSP->>Creds: ExtractOpenStack()
Creds-->>OSP: auth_url, region, app creds, project, network data
OSP-->>BS: [openstack-preflight, opentofu-init, opentofu-apply, openstack-normalize-kubeconfig, openstack-install-network-plugin]
Note over BS: Step execution is resumable via saved state
rect rgb(230, 245, 255)
Note over BS,OSP: Step 1: openstack-preflight
BS->>OSP: validateOpenStackBootstrap(creds)
OSP-->>BS: auth_url and credentials present
end
rect rgb(232, 245, 233)
Note over BS,Tofu: Step 2: opentofu-init
BS->>Tofu: opentofu init
Tofu-->>BS: backend/plugins initialized
end
rect rgb(232, 245, 233)
Note over BS,OS: Step 3: opentofu-apply
BS->>Tofu: opentofu apply -auto-approve
Tofu->>OS: create/update network, instances, volumes, LB, Kubespray inputs
Tofu-->>BS: cluster artifacts rendered in infrastructure/clusters/prod-cluster/
end
rect rgb(255, 243, 224)
Note over BS,BS: Step 4: openstack-normalize-kubeconfig
BS->>BS: copy discovered kubeconfig to cluster-owned path
Note over BS: infrastructure/clusters/prod-cluster/kubeconfig.yaml
end
rect rgb(255, 243, 224)
Note over BS,K8s: Step 5: openstack-install-network-plugin
BS->>K8s: install selected CNI with Helm or Kustomize+Helm
K8s-->>BS: Calico, Cilium, or Kube-OVN pods ready
end
BS->>K8s: kubectl cluster-info --kubeconfig ...
K8s-->>BS: endpoint reachable
BS-->>CLI: bootstrap complete + log path
Unlike the Kind flow, the OpenStack bootstrap path does not run flux bootstrap git from the host. The host-side bootstrap sequence is OpenTofu-driven, and the command returns once the Kubernetes API is reachable. Flux and platform services continue reconciling afterward.
Steps
1. Initialize the cluster configuration
You have two options here. Choose one.
Option A: Guided configuration (recommended)
The guided workflow creates the cluster config, authenticates to OpenStack, discovers available resources, and walks you through every required field interactively:
opencenter cluster configure prod-cluster --guided --org my-company --type openstack
The guided flow runs in phases:
-
Provider authentication — prompts for auth URL, region, project ID, project name, domain, application credential ID and secret, and optional TLS settings (insecure flag, CA bundle path). Once credentials are accepted, the CLI authenticates to Keystone and discovers available resources.
-
Infrastructure selection — presents discovered images, flavors, networks, subnets, external networks, and availability zones as selectable lists. If discovery fails, falls back to manual text input.
-
Compute sizing — prompts for control plane count (default: 3) and worker count (default: 2).
-
Capability flows — runs additional prompts for:
-
Git authentication — Git remote URL, SSH key paths, and branch.
-
DNS — DNS provider and cert-manager configuration (when cert-manager is enabled).
-
Object storage — Swift or S3 endpoint and credentials for Loki and Tempo backends (when those services are enabled).
-
-
Review — displays a summary of all changes grouped by category (Provider, Git Auth, DNS, Storage). You confirm or cancel before anything is written.
After confirmation, the CLI generates SSH and SOPS Age keys, writes managed files (such as clouds.yaml), validates the config, and saves it.
If the cluster already exists, guided mode loads the existing config and only prompts for fields that still have placeholder values or are missing.
# Re-run guided mode on an existing cluster to fill in remaining fields
opencenter cluster configure my-company/prod-cluster --guided
Option B: Manual initialization
opencenter cluster init prod-cluster --org my-company --type openstack
This creates a v2 config and organization-aware directory structure under:
-
~/.config/opencenter/clusters/my-company/infrastructure/clusters/prod-cluster/ -
~/.config/opencenter/clusters/my-company/secrets/
It also:
-
writes the cluster config file to
infrastructure/clusters/prod-cluster/.prod-cluster-config.yaml -
generates SSH keys for Git and node access
-
generates SOPS Age keys
-
enables OpenTofu with a local backend by default
-
sets
opencenter.gitops.repository.local_dirto the organization root (~/.config/opencenter/clusters/my-company) unless you override it explicitly
Additional cluster init flags:
| Flag | Description |
| --- | --- |
| --org | Organization name (defaults to opencenter) |
| --type | Cluster type: openstack, baremetal, kind, vmware |
| --force | Overwrite existing config file |
| --no-keygen | Skip automatic SSH and SOPS key generation |
| --no-sops-keygen | Skip only SOPS key generation |
| --regenerate-keys | Regenerate keys even if they already exist |
| --full-schema | Generate config with all available fields (useful as a reference) |
| --kind-disable-default-cni | Disable Kind’s default CNI so cluster networking is managed by openCenter (Kind provider only) |
| --server-pool | Additional server pool configuration (repeatable) |
You can also override any config value at init time using dotted flag notation:
opencenter cluster init prod-cluster --org my-company --type openstack \
opencenter.infrastructure.compute.master_count=5 \
opencenter.infrastructure.compute.worker_count=3
Confirm the resolved paths:
opencenter cluster describe my-company/prod-cluster
2. Set OpenStack, GitOps, and cluster identity values (manual path only)
Skip this step if you used cluster configure --guided — the guided flow already collected these values.
Open the generated config:
opencenter cluster edit my-company/prod-cluster
Update the OpenStack and GitOps sections with real values. This fragment matches the fields used by the current v2 loader and render path:
opencenter:
meta:
env: production
region: sjc3
cluster:
cluster_fqdn: "prod-cluster.sjc3.k8s.example.com"
admin_email: "platform@example.com"
gitops:
repository:
url: "git@github.com:my-company/prod-cluster-gitops.git"
branch: main
# Optional: move the GitOps working tree out of ~/.config/opencenter/clusters/<org>
# local_dir: "/Users/you/src/prod-cluster-gitops"
infrastructure:
cloud:
openstack:
auth_url: "https://identity.api.your-cloud.com/v3"
region: sjc3
project_id: "your-project-id"
project_name: "your-project-name"
tenant_name: "your-project-name"
application_credential_id: "your-app-credential-id"
application_credential_secret: "your-app-credential-secret"
user_domain_name: "Default"
project_domain_name: "Default"
image_id: "799dcf97-3656-4361-8187-13ab1b295e33"
availability_zone: "az1"
network_id: "your-network-id"
subnet_id: "your-subnet-id"
floating_ip_pool: "PUBLICNET"
router_external_network_id: "your-external-network-id"
networking:
network_id: "your-network-id"
subnet_id: "your-subnet-id"
floating_ip_pool: "PUBLICNET"
router_external_network_id: "your-external-network-id"
k8s_api_port_acl:
- "203.0.113.0/24"
Notes:
-
Set both
project_nameandtenant_nameto the same project value. Some generated OpenStack and service templates still readtenant_name. -
Keep the top-level OpenStack network fields and the nested
openstack.networkingblock in sync. Current validation reads the top-level fields, while some rendered OpenTofu templates still consume the nested block. The guided flow handles this automatically. -
Replace the default
repository.urlplaceholder beforecluster generateorcluster deploy.
3. Tune compute, storage, and networking
The OpenStack defaults are usable, but you should review them before provisioning. The current v2 defaults start with 3 control planes, 2 workers, Kubespray v2.31.0, Kubernetes 1.33.5, Calico v3.32.0 installed from bundled eBPF manifests after kubeconfig normalization, and a local OpenTofu state backend.
Edit these sections as needed:
opencenter:
infrastructure:
compute:
master_count: 3
worker_count: 3
flavor_bastion: "gp.0.2.2"
flavor_master: "gp.0.4.8"
flavor_worker: "gp.0.4.16"
storage:
default_storage_class: "csi-cinder-sc-delete"
worker_volume_size: 40
worker_volume_type: "HA-Standard"
master_volume_size: 40
master_volume_type: "HA-Standard"
networking:
subnet_nodes: "10.2.128.0/22"
allocation_pool_start: "10.2.128.10"
allocation_pool_end: "10.2.131.250"
vrrp_ip: "10.2.128.5"
vrrp_enabled: true
use_octavia: false
loadbalancer_provider: "ovn"
dns_zone_name: "prod-cluster.sjc3.k8s.example.com"
dns_nameservers:
- "8.8.8.8"
- "8.8.4.4"
ntp_servers:
- "time.sjc3.rackspace.com"
- "time2.sjc3.rackspace.com"
If you enable use_octavia, review vrrp_enabled at the same time. The rendered infrastructure templates assume you are not trying to use both HA modes at once.
4. Review default services
cluster init --type openstack includes a set of platform services in the configuration. Some are enabled by default and others are present but disabled. The tables below reflect the defaults defined in internal/config/v2/defaults.go (function defaultServiceMap) combined with the OpenStack provider behavior defaults (function applyProviderBehaviorDefaults).
Enabled by default:
| Service | Category | Description | | --- | --- | --- | | calico | Networking | CNI plugin for pod networking and network policy | | gateway-api | Networking | Gateway API CRDs for modern ingress routing | | gateway | Networking | Gateway API implementation (Envoy-based) | | cert-manager | Security | Automated TLS certificate management via Let’s Encrypt | | keycloak | Security | Identity and access management (OIDC provider) | | rbac-manager | Security | Declarative RBAC configuration (required by keycloak) | | olm | Management | Operator Lifecycle Manager (required by keycloak) | | postgres-operator | Management | PostgreSQL cluster management operator (required by keycloak) | | headlamp | Management | Kubernetes dashboard with OIDC login | | fluxcd | GitOps | GitOps continuous delivery controllers | | sources | GitOps | FluxCD GitRepository source definitions |
Disabled by default (present in config, set enabled: true to activate):
| Service | Why disabled |
| --- | --- |
| kube-prometheus-stack | Observability stack (Prometheus, Grafana, Alertmanager) — enable when you need monitoring |
| loki | Log aggregation — requires object storage backend configuration |
| tempo | Distributed tracing — requires object storage backend configuration |
| harbor | Optional container registry, not needed for every cluster |
| velero | Cluster backup and disaster recovery — enable when backup strategy is defined |
| metallb | Bare-metal load balancing — OpenStack clusters use Octavia or OVN |
| openstack-ccm | OpenStack Cloud Controller Manager — enable for cloud-aware node management |
| openstack-csi | Cinder CSI driver — the storage plugin (cinder_csi) is enabled separately in kubernetes.storage_plugin |
| external-snapshotter | Volume snapshot controller — enable when you need volume snapshots |
| kyverno | Kubernetes policy enforcement engine |
| kafka-cluster | Workload-specific, enable when your applications need it |
| longhorn | Distributed storage — OpenStack clusters use Cinder CSI instead |
| mimir | Long-term metrics storage — default stack uses Prometheus local storage |
| opentelemetry-kube-stack | Alternative telemetry pipeline, overlaps with Prometheus + Loki + Tempo |
| sealed-secrets | Alternative to SOPS for in-cluster secret management |
| vsphere-csi | VMware-only CSI driver |
| weave-gitops | Optional FluxCD web UI |
To toggle a service, set enabled: true or enabled: false under opencenter.services.<name> in your cluster config:
opencenter:
services:
harbor:
enabled: true
loki:
enabled: true
Run opencenter cluster validate after changing services to catch missing required fields before setup.
For the full configuration options per service, see the ../reference/platform-services.md[Platform Services Reference].
5. Run preflight checks and validate the config
opencenter cluster doctor prod-cluster
opencenter cluster validate prod-cluster
Current behavior is worth knowing:
-
cluster doctorchecks thatgitandkubectlare onPATH. For OpenStack clusters, it also checks for theopenstackCLI and warns ifauth_urlis empty. Talos deployment support uses native Go APIs instead oftalosctl. -
cluster validatevalidates the v2 config shape and required provider fields such asproject_id,image_id, andnetwork_id. It performs schema validation, required field validation, and cross-field dependency validation.
Additional cluster validate flags:
| Flag | Description |
| --- | --- |
| --validation online | Run provider connectivity, provider discovery, and Git remote checks |
| --output json | Output validation results as JSON (for CI/CD pipelines) |
| -v, --verbose | Verbose output |
| --generate-debug-config | Generate a complete config for debugging |
| --output-dir | Directory to save debug config (defaults to current directory) |
cluster doctor does not authenticate to Keystone, so keep using the OpenStack CLI for the real connectivity check:
openstack token issue
6. Generate the GitOps repository
opencenter cluster generate prod-cluster
cluster generate does more than just render files. It currently:
-
copies the base GitOps skeleton into
git_dir -
renders the application overlay into
applications/overlays/prod-cluster/ -
renders the infrastructure templates into
infrastructure/clusters/prod-cluster/ -
writes
provider.tffor the configured OpenTofu backend -
initializes a git repository if
.git/is missing -
creates the first commit automatically
Additional cluster generate flags:
| Flag | Description |
| --- | --- |
| --force | Overwrite existing GitOps repository |
| --dry-run | Show what would be generated without writing files |
| --skip-validation | Skip configuration validation before setup |
For iterative development, cluster generate --render-only is an alternative that renders templates with safety checks and timestamped backups, but does not perform Git operations:
# Re-render all services and infrastructure
opencenter cluster generate prod-cluster --render-only --force
Verify the generated infrastructure directory:
GITOPS_DIR=$(opencenter cluster describe prod-cluster 2>/dev/null | grep "git_dir:" | awk '{print $2}')
ls "$GITOPS_DIR/infrastructure/clusters/prod-cluster"
git -C "$GITOPS_DIR" log --oneline -1
You should see files such as main.tf, variables.tf, provider.tf, and Makefile, plus an initial git commit.
7. Configure the remote and push the initial commit
cluster generate creates the commit, but it does not add origin for you. Push the generated GitOps tree before bootstrapping:
GITOPS_DIR=$(opencenter cluster describe prod-cluster 2>/dev/null | grep "git_dir:" | awk '{print $2}')
git -C "$GITOPS_DIR" remote add origin git@github.com:my-company/prod-cluster-gitops.git
git -C "$GITOPS_DIR" branch -M main
git -C "$GITOPS_DIR" push -u origin main
If the repository already has an origin, update it instead:
git -C "$GITOPS_DIR" remote set-url origin git@github.com:my-company/prod-cluster-gitops.git
cluster deploy checks that the local origin matches opencenter.gitops.repository.url, so keep those values aligned.
8. Bootstrap the cluster
opencenter cluster deploy prod-cluster
The OpenStack bootstrap provider runs these step IDs (defined in internal/cluster/openstack_bootstrap_provider.go):
-
openstack-preflight— validates OpenStack credentials and bootstrap prerequisites -
opentofu-init— runsopentofu initin the cluster infrastructure directory -
opentofu-apply— runsopentofu apply -auto-approveto provision infrastructure -
openstack-normalize-kubeconfig— copies the discovered kubeconfig to the cluster-owned path -
openstack-install-network-plugin— installs the selected CNI; Calico uses bundledv3.32.0eBPF manifests, while Cilium and Kube-OVN use Helm-backed flows
Additional runtime behavior to be aware of:
-
OpenStack deploy does not use Kubespray to install CNIs. Set exactly one
network_plugintoenabled: true; supported OpenStackinstall_methodvalues arehelmandkustomize-helm. -
OpenStack Calico does not download install manifests at deploy time. The CLI bundles native
projectcalico.org/v3CRDs, the Tigera operator, andcustom-resources-bpf.yaml; the target cluster still needs access to required container images or a mirrored registry. -
Bootstrap acquires a cluster-level lock to prevent concurrent operations. If a lock exists from a previous run, you are prompted to break it.
-
Bootstrap writes a log under the openCenter state directory, by default
~/.local/state/opencenter/logs/bootstrap/<org>/<cluster>/. -
Bootstrap keeps resumable state in
~/.local/state/opencenter/bootstrap/<org>/<cluster>/state.json. -
If the GitOps working tree has uncommitted changes, bootstrap auto-commits them with
chore: auto-commit before bootstrap. -
Use
--confirm-commitif you want a prompt before that auto-commit happens. -
If a step fails, re-run with
--from-step <step-id>or--restart.
Bootstrap flags:
| Flag | Description |
| --- | --- |
| --dry-run | Show planned actions without executing |
| --restart | Rerun all bootstrap steps and ignore saved state |
| --step <id> | Run a single bootstrap step by ID |
| --from-step <id> | Restart bootstrap from the specified step ID |
| --confirm-commit | Prompt for confirmation before auto-committing uncommitted changes |
| --kubeconfig | Path to kubeconfig (defaults to the cluster-owned kubeconfig path) |
| --log | Log file path (defaults to <state_dir>/logs/bootstrap/<org>/<name>/bootstrap-<timestamp>.log) |
| --container-runtime | Container runtime for Kind clusters: docker or podman (Kind provider only) |
Examples:
# Resume from the OpenTofu apply phase
opencenter cluster deploy prod-cluster --from-step opentofu-apply
# Run only the preflight step
opencenter cluster deploy prod-cluster --step openstack-preflight
# Re-run only CNI installation after fixing Helm values or chart access
opencenter cluster deploy prod-cluster --step openstack-install-network-plugin
# Throw away saved state and rerun the full sequence
opencenter cluster deploy prod-cluster --restart
Verification
Bootstrap returns when kubectl cluster-info succeeds against the cluster-owned kubeconfig. Flux controllers and platform services may still be reconciling after that point.
GITOPS_DIR=$(opencenter cluster describe prod-cluster 2>/dev/null | grep "git_dir:" | awk '{print $2}')
export KUBECONFIG="$GITOPS_DIR/infrastructure/clusters/prod-cluster/kubeconfig.yaml"
kubectl cluster-info
kubectl get nodes -o wide
kubectl get pods -n flux-system
flux get sources git -n flux-system
flux get kustomizations -n flux-system
Expected state:
-
kubectl cluster-inforeturns the cluster API endpoint -
control plane and worker nodes are
Ready -
Flux controllers are running in
flux-system -
Flux sources and kustomizations converge to
READY=True
Cleanup
Destroy the cluster infrastructure:
opencenter cluster destroy prod-cluster --force
By default, cluster destroy --force destroys cloud infrastructure (via OpenTofu) but preserves local configuration and GitOps files for inspection or recovery. The --force flag skips the interactive confirmation prompt.
To also remove local files (GitOps directory, cluster config directory, applications overlay, and the config file):
opencenter cluster destroy prod-cluster --force --remove-files
To skip infrastructure destruction and only remove local files:
opencenter cluster destroy prod-cluster --force --skip-infrastructure --remove-files
cluster destroy acquires a lock before running. If the destroyed cluster was the active cluster, the active marker is cleared. The Git remote is not deleted automatically — remove it manually if you no longer need it.
Confirm the OpenStack resources are gone:
openstack server list --name prod-cluster
openstack volume list --long | grep prod-cluster
Troubleshooting
cluster validate passes, but bootstrap fails immediately with OpenStack auth errors
cluster validate only checks the config shape. The runtime bootstrap path needs working application credentials.
Verify:
openstack token issue
Then confirm these fields are set in the cluster config:
-
opencenter.infrastructure.cloud.openstack.auth_url -
opencenter.infrastructure.cloud.openstack.application_credential_id -
opencenter.infrastructure.cloud.openstack.application_credential_secret -
opencenter.infrastructure.cloud.openstack.project_id
The rendered OpenTofu files have the wrong project or network values
Keep these fields synchronized:
-
project_nameandtenant_name -
top-level OpenStack
network_id/subnet_id -
nested
openstack.networking.network_id/openstack.networking.subnet_id
The guided configure flow (cluster configure --guided) keeps these fields in sync automatically. If you edit the config manually, you need to update both locations.
Bootstrap fails during opentofu-apply
Open the bootstrap log printed by the command. By default it is written under:
~/.local/state/opencenter/logs/bootstrap/<org>/<cluster>/
Then inspect the generated infrastructure directory:
GITOPS_DIR=$(opencenter cluster describe prod-cluster 2>/dev/null | grep "git_dir:" | awk '{print $2}')
cd "$GITOPS_DIR/infrastructure/clusters/prod-cluster"
opentofu init
opentofu plan