# OpenStack Infra Provider Use the Cluster API [OpenStack Infra Provider (CAPO)](https://github.com/kubernetes-sigs/cluster-api-provider-openstack) with the Cluster API [Kamaji Control Plane Provider](https://github.com/clastix/cluster-api-control-plane-provider-kamaji) to create Kubernetes clusters. !!! warning "Important Notes" This walkthrough uses an advanced `externalClusterReference` setup. `kind` cluster is used as the Cluster API management cluster for ease of PoC, and it assumes you start with no existing Kubernetes infrastructure, only a working OpenStack environment. Because a local `kind` cluster is typically not reachable from OpenStack instances, Kamaji is installed on a kubeadm-based Kubernetes cluster running inside OpenStack (the "control plane cluster"). The tenant control plane is then deployed there using `KamajiControlPlane.spec.deployment.externalClusterReference`. If you already have a Kubernetes cluster reachable from your OpenStack nodes (for example, a Magnum-bootstrapped cluster, a kubeadm cluster in OpenStack, or any managed Kubernetes service), you can run both Cluster API controllers and Kamaji on that single management cluster and avoid `externalClusterReference`. ## Topology ```text +--------------------+ | management cluster | +---------+----------+ / \ / \ v v +-----------------------+ +-------------------------+ | control plane cluster | | workload tenant cluster | +-----------+-----------+ +------------+------------+ \ / +--------------------------+ ``` - The management cluster is a local `kind` cluster and runs Cluster API core, Kubeadm providers, CAPO, CAPH (Cluster API Helm add-on provider), and the Kamaji Cluster API control plane provider, and it reconciles both OpenStack `Cluster` resources. - The control plane cluster is a kubeadm-based cluster on OpenStack that runs Kamaji and add-ons such as CCM, CNI, and CSI. - The workload tenant cluster runs worker nodes on OpenStack, while its tenant control plane is hosted by Kamaji on the control plane cluster. ## Prerequisites - `kind`, `kubectl`, `clusterctl`, `openstack` CLI, `openssl`, and `base64` - Host images built with [image-builder](https://image-builder.sigs.k8s.io/) (or equivalent) and configured with `cloud-init` using the [OpenStack datasource](https://docs.cloud-init.io/en/latest/reference/datasources/openstack.html) - An existing OpenStack environment with network connectivity, flavors, host images, and sufficient quotas for control plane and worker nodes - OpenStack load balancer support (Octavia) for Service type `LoadBalancer` and API endpoint/public IP management for the kubeadm-based control plane cluster !!! warning "ProviderID and cloud-init behavior" CAPI host images must have the OpenStack cloud-init datasource configured. Otherwise, kubelet `provider-id` injection can resolve to a Nova-style non-UUID value and Cluster API reconciliation can stall. In this configuration, `kubeletExtraArgs` does not set `provider-id`; OpenStack CCM sets `providerID` after cluster start. See [cloud-init OpenStack datasource docs](https://docs.cloud-init.io/en/latest/reference/datasources/openstack.html) and [CAPO external cloud provider notes](https://cluster-api-openstack.sigs.k8s.io/topics/external-cloud-provider). ## Setup management cluster ```bash kind create cluster --name management-cluster kubectl cluster-info --context kind-management-cluster ``` ### Install providers Install ORC before initializing Cluster API providers: ```bash export ORC_VERSION=v2.0.3 kubectl apply -f "https://github.com/k-orc/openstack-resource-controller/releases/download/${ORC_VERSION}/install.yaml" ``` Initialize Cluster API providers: ```bash clusterctl init \ --core cluster-api \ --bootstrap kubeadm \ --control-plane kubeadm \ --infrastructure openstack \ --addon helm \ --control-plane kamaji ``` Enable feature gates required by `KamajiControlPlane.spec.deployment.externalClusterReference`: ```bash kubectl -n kamaji-system patch deployment capi-kamaji-controller-manager \ --type='json' \ -p='[ { "op": "replace", "path": "/spec/template/spec/containers/0/args/1", "value": "--feature-gates=ExternalClusterReference=true,ExternalClusterReferenceCrossNamespace=true" } ]' ``` ### Prepare OpenStack application credentials Application credentials are used instead of username/password. !!! warning "Credential Scope" The same application credential is reused for both tenant clusters. In production, separate credentials per cluster or environment are preferred. !!! note "Project selection" Application credentials are created in the currently scoped project. Set `TARGET_PROJECT_ID` explicitly so credential creation is project-scoped. Set and review variables: ```bash export OPENSTACK_CLOUD_NAME=example-openstack export TARGET_PROJECT_ID= export OPENSTACK_APP_CREDENTIAL_NAME=capi-kamaji export OPENSTACK_APP_CREDENTIAL_SECRET="$(openssl rand -hex 24)" ``` Create the application credential: ```bash openstack --os-cloud "$OPENSTACK_CLOUD_NAME" application credential create \ --os-project-id "$TARGET_PROJECT_ID" \ --secret "$OPENSTACK_APP_CREDENTIAL_SECRET" \ "$OPENSTACK_APP_CREDENTIAL_NAME" ``` Store the generated credential ID: ```bash export OPENSTACK_APP_CREDENTIAL_ID="$(openstack --os-cloud "$OPENSTACK_CLOUD_NAME" --os-project-id "$TARGET_PROJECT_ID" application credential show "$OPENSTACK_APP_CREDENTIAL_NAME" -f value -c id)" ``` ### Prepare `clouds.yaml` and `cloud.conf` Set OpenStack connection variables: ```bash export OPENSTACK_AUTH_URL=https://openstack.example.com:5000/v3 export OPENSTACK_REGION_NAME=RegionOne export OPENSTACK_INTERFACE=public export OPENSTACK_IDENTITY_API_VERSION=3 export OPENSTACK_TLS_INSECURE=false ``` Build `clouds.yaml` and `cloud.conf` content in environment variables: ```bash export CLOUDS_YAML_CONTENT="$(cat < ~/.kube/${CONTROL_PLANE_CLUSTER_NAME}.kubeconfig KUBECONFIG=~/.kube/${CONTROL_PLANE_CLUSTER_NAME}.kubeconfig kubectl get nodes ``` ## Bootstrap the tenant cluster with Kamaji control plane ### Set cross-cluster reference Set the reference to the first cluster kubeconfig Secret: ```bash export CONTROL_PLANE_CLUSTER_KUBECONFIG_SECRET_NAME="${CONTROL_PLANE_CLUSTER_NAME}-kubeconfig" ``` ### Apply tenant cluster resources ```bash kubectl apply -f - < ~/.kube/${WORKLOAD_TENANT_CLUSTER_NAME}.kubeconfig KUBECONFIG=~/.kube/${WORKLOAD_TENANT_CLUSTER_NAME}.kubeconfig kubectl get nodes ``` ## Clean up ```bash kubectl delete cluster "$WORKLOAD_TENANT_CLUSTER_NAME" -n "$CLUSTER_NAMESPACE" kubectl delete cluster "$CONTROL_PLANE_CLUSTER_NAME" -n "$CLUSTER_NAMESPACE" kind delete cluster --name management-cluster ```