Skip to content

Minimal Environment ​

This guide walks you through deploying the smallest viable CobaltCore environment: one control plane node running the Kubernetes control plane and OpenStack services, and one hypervisor node running KVM virtual machines.

This is not a production configuration - it has no high availability and no redundant storage. It is suitable for exploration, development, and learning how the components fit together.

What you will deploy ​

┌─────────────────────────────────┐
│  Control Plane Node             │
│  Kubernetes · OpenStack (Helm)  │
│  Ceph (single node)             │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│  Hypervisor Node                │
│  GardenLinux · KVM · libvirt    │
│  Hypervisor Operator agents     │
└─────────────────────────────────┘

Prerequisites ​

  • Two machines (physical or virtual), each with:
    • 8+ CPUs, 32 GB RAM, 200 GB disk
    • Linux OS (Ubuntu 22.04 or equivalent)
    • Network connectivity between them
  • kubectl installed on your workstation
  • helm v3 installed
  • Basic familiarity with Kubernetes

Step 1: Bootstrap the Kubernetes cluster ​

CobaltCore uses Gardener for cluster lifecycle management in production, but for a minimal setup you can use any conformant Kubernetes distribution (k3s, kubeadm, kind with enough resources).

The control plane must be reachable from the hypervisor node.

Step 2: Install the Hypervisor Operator ​

The Hypervisor Operator manages hypervisor node lifecycle. Install it into your cluster:

bash
helm repo add cobaltcore https://cobaltcore-dev.github.io/charts
helm repo update
helm install hypervisor-operator cobaltcore/hypervisor-operator \
  --namespace cobaltcore-system \
  --create-namespace

See Compute - Hypervisor for configuration options.

Step 3: Register the hypervisor node ​

Create a Hypervisor resource to register your compute node:

yaml
apiVersion: kvm.cloud.sap/v1
kind: Hypervisor
metadata:
  name: hypervisor-01
spec:
  lifecycleEnabled: true
  highAvailability: false
  version: latest

Apply it and verify the node is discovered:

bash
kubectl apply -f hypervisor.yaml
kubectl get hypervisors

Step 4: Deploy storage (single-node Ceph) ​

For a minimal setup, deploy Ceph in single-replica mode via Rook:

bash
helm repo add rook-release https://charts.rook.io/release
helm repo update

helm install rook-ceph rook-release/rook-ceph \
  --namespace rook-ceph \
  --create-namespace \
  --version v1.20.7 \
  --wait

helm repo add ceph-csi-operator https://ceph.github.io/ceph-csi-operator
helm repo update
helm install ceph-csi-drivers ceph-csi-operator/ceph-csi-drivers \
  --namespace rook-ceph \
  --version 1.0.4 \
  --wait \
  -f https://raw.githubusercontent.com/rook/rook/v1.20.7/deploy/charts/ceph-csi-drivers/values.yaml

Create minimal-ceph-cluster.yaml, replacing k3s-node and sdb with the Kubernetes node name and the dedicated raw device in your lab:

yaml
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  dataDirHostPath: /var/lib/rook
  cephVersion:
    image: quay.io/ceph/ceph:v20.2.4
  mon:
    count: 1
    allowMultiplePerNode: true
  mgr:
    count: 1
    allowMultiplePerNode: true
  storage:
    useAllNodes: false
    useAllDevices: false
    nodes:
    - name: k3s-node
      devices:
      - name: sdb
  cephConfig:
    global:
      osd_pool_default_size: "1"
      mon_warn_on_pool_no_redundancy: "false"
---
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  replicated:
    size: 1
    requireSafeReplicaSize: false
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
  clusterID: rook-ceph
  pool: replicapool
  imageFormat: "2"
  imageFeatures: layering
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-publish-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-publish-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
  csi.storage.k8s.io/fstype: ext4
allowVolumeExpansion: true
reclaimPolicy: Delete

The device must be unformatted, unmounted, and dedicated to this disposable test cluster. Apply the manifest and wait for Ceph to become ready:

bash
kubectl apply -f minimal-ceph-cluster.yaml
kubectl -n rook-ceph wait --for=condition=Ready cephcluster/rook-ceph --timeout=15m

This single-node configuration is for testing only. See Storage - Rook for the production three-node cluster configuration.

Step 5: Deploy OpenStack ​

OpenStack services are deployed via Helm. A minimal configuration enables Nova, Neutron, Cinder, Keystone, and Glance:

bash
helm install openstack cobaltcore/openstack \
  --namespace openstack \
  --create-namespace \
  --values minimal-values.yaml

See OpenStack for service-level configuration.

Step 6: Verify ​

Check that all components are running:

bash
kubectl get pods -n cobaltcore-system
kubectl get pods -n rook-ceph
kubectl get pods -n openstack
kubectl get hypervisors

Access the OpenStack API endpoint and create a test VM to confirm end-to-end functionality.

Next steps ​

EU and German government funding logos

Funded by the European Union – NextGenerationEU.

The views and opinions expressed are solely those of the author(s) and do not necessarily reflect the views of the European Union or the European Commission. Neither the European Union nor the European Commission can be held responsible for them.