Skip to main content
Version: 0.4.0-beta

Cluster-Wizard Deployment

This guide covers installing Cluster-Wizard in Kubernetes using Helm.

Requirements

  • Access to https://charts.cluster-wizard.com/ from cluster.
  • Prepared Kubernetes secrets
    • cluster-wizard license
    • cluster-wizard client CA certificate
    • cluster-wizard server certificate

Getting Started - Installing Cluster-Wizard

Installation Preparation

Adding Helm Repository

Cluster-Wizard's helm charts are available at https://charts.cluster-wizard.com. A matching helm chart version for Cluster-Wizard 0.4.x versions is 0.1.0. Cluster-wizard's chart can be obtained with the following steps:

helm repo add cluster-wizard https://charts.cluster-wizard.com/  
helm repo update cluster-wizard
helm search repo cluster-wizard --versions
helm fetch cluster-wizard/cluster-wizard --version 0.1.0 --untar

Required Helm values

Below is a minimal set of helm values needed to deploy Cluster-Wizard.

  • licenseSecretName: name of the license secret
  • clusterWizardCert.clientCASecretName: name of the client CA cert secret
  • clusterWizardCert.serverSecretName: name of the server cert secrets
  • admin user information
    • adminUser
    • adminEmail
  • configuration need for postgres
    • postgres.install.install
    • postgres.password
    • postgres.cwPassword
    • postgres.host

Here we utilize a loadBalancer set by expose.type to assign an IP to our service. It is possible to change the expose setup to other methods.

Example values-override.yaml:

licenseSecretName: "cw-license"

clusterWizardCert:
clientCASecretName: "client-ca"
serverSecretName: "cluster-wizard-cert"

adminUser: "cluster-wizard-admin"
adminEmail: "cluster-wizard-admin@corespeq.com"

postgres:
install:
install: true
password: "supersecret"
cwPassword: "supersecretToo"
adminUser: "cw-admin"
host: "postgres-service"

expose:
type: loadBalancer
loadBalancer:
IP: "192.168.100.100"

Preparing Secrets for License and Certificates

Cluster-Wizard requires a license, a client CA certificate, and a server certificate. Set the license secret with:

kubectl create namespace cluster-wizard

kubectl create secret generic cw-license -n cluster-wizard \
--from-file=license=cluster-wizard-040.lic
Self Managed Certificates

If the CA cert and a server cert files are already present, deploy the secrets using the following commands:

kubectl create secret tls cluster-wizard-cert -n cluster-wizard \
--cert=server/server_cert.pem \
--key=server/server_key.pem

kubectl create secret tls client-ca -n cluster-wizard \
--cert=CA/client_ca_cert.pem \
--key=CA/client_ca_key.pem

Alternatively, it is possible to use Cert Manager to handle certificates.

Cert Manager Managed Certificates
  1. Create a Cert Manager Issuer, cluster-wizard-issuer.yaml:
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: cluster-wizard-issuer
namespace: cluster-wizard
spec:
selfSigned: {}
kubectl apply -f cluster-wizard-issuer.yaml
  1. Create Client CA, client-ca.yaml:
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: client-ca
namespace: cluster-wizard
spec:
isCA: true
commonName: CORESPEQ INC
secretName: client-ca
privateKey:
algorithm: RSA
encoding: PKCS8
size: 2048
duration: 87600h
issuerRef:
name: cluster-wizard-issuer
kind: Issuer
kubectl apply -f client-ca.yaml
  1. Create Server CA, server-ca.yaml:
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: server-ca
namespace: cluster-wizard
spec:
isCA: true
commonName: CORESPEQ INC
secretName: server-ca
privateKey:
algorithm: ECDSA
size: 521
duration: 87600h
issuerRef:
name: cluster-wizard-issuer
kind: Issuer
kubectl apply -f server-ca.yaml
  1. Create a Cert Manager Issuer that uses the Server CA, issuer-with-server-ca.yaml:
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: issuer-with-server-ca
namespace: cluster-wizard
spec:
ca:
secretName: server-ca
kubectl apply -f issuer-with-server-ca.yaml
  1. Create Certificate used by Cluster-Wizard, cluster-wizard-cert.yaml:
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cluster-wizard-cert
namespace: cluster-wizard
spec:
dnsNames:
- cluster-wizard
secretName: cluster-wizard-cert
issuerRef:
name: issuer-with-server-ca
kind: Issuer
subject:
organizations:
- CORESPEQ INC
organizationalUnits:
- Cluster Wizard Team
note

The dnsNames specified in cluster-wizard-cert.yaml will need to be available via a DNS service to the Wizard-Client. If DNS is not an option consider using ipAddresses instead of dnsNames.

...
spec:
ipAddresses:
- 192.168.100.100
...
kubectl apply -f cluster-wizard-cert.yaml

Install Cluster-Wizard

helm install cluster-wizard ./cluster-wizard \
--version 0.1.0 \
--namespace cluster-wizard \
-f values-override.yaml

Verifying Installation

  • Check pod status for running state:
kubectl get pods -n cluster-wizard
NAME READY STATUS RESTARTS AGE
cluster-wizard-7f567f57f5-7f5tl 1/1 Running 0 5m05s
cluster-wizard-postgres-7b8f7b885-7b8xl 1/1 Running 0 5m05s
  • Check service exposure:
kubectl get svc -n cluster-wizard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster-wizard-svc ClusterIP 10.233.52.203 192.168.100.100 50001/TCP 5m05s
postgres-service ClusterIP 10.233.13.17 <none> 5432/TCP 5m05s
  • Check logs:
kubectl logs deployment/cluster-wizard -n cluster-wizard
2025/05/05 05:05:05 START of check license
2025/05/05 05:05:05 get current time from time server
2025/05/05 05:05:05 Starting cluster-wizard ... : 0.4.0
2025/05/05 05:05:05 CEPH_USER: CEPH_POOL: CEPH_ENABLE:false host:postgres-service
2025/05/05 05:05:05 START of check ceph status
2025/05/05 05:05:05 CEPH disabled
2025/05/05 05:05:05 Creating ServerKeyPair:
2025/05/05 05:05:05 loading: /cluster_wizard/server_cert.pem
2025/05/05 05:05:05 loading: /cluster_wizard/server_key.pem
2025/05/05 05:05:05 Creating Client Cert Pool
2025/05/05 05:05:05 loading: /cluster_wizard/client_ca_cert.pem

Next Steps

Before using any clients, the admin credentials will need to be obtained from the Kubernete's secret admin-cred created by the Cluster-Wizard. Use the following commands to get the admin certificate and key.

kubectl get secret admin-cred -n cluster-wizard -o jsonpath='{.data.cert}' | base64 -d > admin-cert.crt

kubectl get secret admin-cred -n cluster-wizard -o jsonpath='{.data.private_key}' | base64 -d > admin-private.key

Install and configure Wizard-Client or Wizard-Client Web UI.

Once Wizard-Client is installed the admin certificate and key can be used to access Cluster-Wizard. If the above basic values-override.yaml was applied, Cluster-Wizard is exposed via loadBalancer.

Obtain the service IP with the following command:

kubectl get svc cluster-wizard-svc -n cluster-wizard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster-wizard-svc ClusterIP 10.233.52.128 192.168.100.100 50001:31450/TCP 05d

A DNS entry matching the Cluster-Wizard certificate needs to be setup. For now, add an entry to the /etc/hosts that matches "cluster-wizard" from the dnsNames entry in cluster-wizard-cert to access Cluster-Wizard:

sudo vi /etc/hosts
...
192.168.100.100 cluster-wizard
...

Alternatively, if ipAddresses was used in the server certificate append -s 192.168.100.100 to the wizard-clients command.

If Cert Manager was used to setup the server CA, get the certificate with

kubectl get secret cluster-wizard-cert -n cluster-wizard -o jsonpath='{.data.ca\.crt}' | base64 -d > ca_cert.pem

Now Wizard-Client can be used to add nodes.

wizard-clients -c hosts -cert admin-cert.crt -ca ./ca_cert.pem  -pkey admin-private.key

Helm Values

Admin User

The admin account for Cluster-Wizard defined by adminUser and adminEmail. These are required values with no defaults.

Example values-override.yaml snippet showing Admin User values:

adminUser: "cluster-wizard-admin"
adminEmail: "cluster-wizard-admin@corespeq.com"

Ceph

Configures Cluster-Wizard's ceph integration allowing for rbd backed images for VMs, snapshots and migration between hosts.

  • ceph.enabled: enabled ceph integration. Default is false
  • ceph.secretName: name of secret containing ceph.conf and ceph keyring
    • conf is the required key for ceph.conf file
    • keyring is the required key for the ceph keyring
  • ceph.pool: ceph pool name used by libvirt
  • ceph.user: ceph user used by libvirt pool

See Ceph Basics for Cluster-Wizard for more information about ceph.conf and ceph keyrings.

warning

ceph.conf must contain "keyring = /cluster_wizard/libvirt.keyring"

ceph.conf:

# minimal ceph.conf for 8108d676-8848-11aa-8e88-35b7aac3aa46
[global]
fsid = 8108d676-8848-11aa-8e88-35b7aac3aa46
mon_host = [v2:172.17.3.1:3300/0,v1:172.17.3.1:6789/0] [v2:172.17.3.2:3300/0,v1:172.17.3.2:6789/0] [v2:172.17.3.3:3300/0,v1:172.17.3.3:6789/0]
keyring = /cluster_wizard/libvirt.keyring

libvirt.keyring:

[client.libvirt]
key = CQC+j3xl/bcROhCCBd0aGSfiTIie9gRJEewYRw==

Example secret creation:

kubectl create secret generic cluster-wizard-ceph -n cluster-wizard \
--from-file=conf=ceph.conf \
--from-file=keyring=libvirt.keyring

Example values-override.yaml snippet showing ceph values:

ceph:
enabled: true
secretName: "cluster-wizard-ceph"
pool: "libvirt-pool"
user: "libvirt"

values.yaml snippet showing ceph values:

ceph:
enabled: false
secretName: ""
pool: ""
user: ""

Certificates

Cluster-Wizard requires two TLS certificate secrets. A Client CA certificate secret used for signing client certificates and Server certificate secret used for mTLS communication with Wizard-Client. These are required values with no defaults.

  • clusterWizardCert.clientCASecretName: name of secret containing client CA certificate in Kubernetes TLS format. Required secret keys:
    • tls.crt: key for the client CA certificate
    • tls.key: key for the private key
  • clusterWizardCert.serverSecretName: name of secret containing server certificate in Kubernetes TLS format. Required secret keys:
    • tls.crt: key for the public certificate
    • tls.key: key for server private key

Example secret creation using already existing certificates:

kubectl create secret tls cluster-wizard-cert -n cluster-wizard \
--cert=server/server_cert.pem \
--key=server/server_key.pem

kubectl create secret tls client-ca -n cluster-wizard \
--cert=CA/client_ca_cert.pem \
--key=CA/client_ca_key.pem

Alternatively, it is possible to use Cert Manager to manage certificates.

Example values-override.yaml snippet showing clusterWizardCert:

clusterWizardCert:
clientCASecretName: "client-ca"
serverSecretName: "cluster-wizard-cert"

Expose

Configures how cluster wizard service is exposed:

  • expose.type - controls how cluster wizard service is exposed. Set the service type as "clusterIP", "ingress", "loadBalancer", "nodePort" or "". Default is clusterIP.
    • clusterIP: Used when expose.type is "clusterIP"
      • expose.clusterIP.staticClusterIP - sets the ip address of the ClusterIP service, leave empty for acquiring dynamic ip. Default is ""
      • expose.clusterIP.ports.externalPort - port the cluster-wizard service listens on. Default is "50001"
    • ingress: Used when expose.type is "ingress"
      • expose.ingress.host: URL used for access
      • expose.ingress.controller: set to the type of ingress controller if it has specific requirements. default works for most ingress controllers. Possible values "default", "gce", "ncp", "alb", or "f5-bigip". Default is default.
      • expose.ingress.kubeVersionOverride: Allow .Capabilities.KubeVersion.Version to be overridden while creating ingress
      • expose.ingress.className: Sets the ingress class name, eg "haproxy"
      • expose.ingress.annotations: Optional annotations needed by different ingress providers.
    • loadBalancer: Used when expose.type is "loadBalancer"
      • expose.loadBalancer.IP - specifies the IP address assigned by the loadBalancer. Default is "" for automatic assignment.
      • expose.loadBalancer.ports.externalPort - port the cluster-wizard service listens on. Default is "50001"
      • expose.loadBalancer.sourceRanges - specifies which CIDR IP ranges are allowed to access the LoadBalancer’s external IP. eg: "192.168.100.0/24"
    • nodePort: Used when expose.type is "nodePort"
      • expose.nodePort.ports.externalPort.port - port the cluster-wizard service listens on. Default is "50001"
      • expose.nodePort.ports.externalPort.nodePort - sets the node port the service listens on. Default value is 30002

Example values-override.yaml snippet showing expose values with loadBalancer configuration with automatic IP assignment:

expose:
type: loadBalancer

Example values-override.yaml snippet showing expose values for nodeport:

expose:
type: nodePort
nodePort:
ports:
externalPort:
port: 50001
nodePort: 30002

Example values-override.yaml snippet for ingress using haproxy:

expose:
type: ingress
ingress:
host: cluster-wizard
className: "haproxy"
annotations:
haproxy.org/ssl-passthrough: "true"

values.yaml snippet showing all expose values:

expose:
type: clusterIP
ingress:
host: cluster-wizard
controller: default
kubeVersionOverride: ""
className: ""
annotations:
haproxy.org/ssl-passthrough: "true"
clusterIP:
staticClusterIP: ""
ports:
externalPort: 50001
nodePort:
ports:
externalPort:
port: 50001
nodePort: 30002
loadBalancer:
IP: ""
ports:
externalPort: 50001
sourceRanges: []

Image

Configures the image repository, tag and pull policy for the cluster-wizard deployment.

  • image.repository: sets the repository used for pulling the image. Default is "clusterwizard/cluster-wizard"
  • image.pullPolicy: controls how the container image is pulled from the repository when starting a pod. Possible values: Always, IfNotPresent, Never. Default is IfNotPresent.
  • image.tag: container tag used when cluster-wizard is installed. Default is "" and will pull Cluster Wizard Version 0.4.0.

values.yaml snippet show image values:

image:
repository: clusterwizard/cluster-wizard
pullPolicy: IfNotPresent
tag: ""

License

Cluster-Wizard requires a valid license to operate. This is a required value with no default.

  • licenseSecretName: name of secret containing the cluster-wizard license. Required secret key:
    • license key containing valid license

Example secret creation:

kubectl create secret generic cw-license -n cluster-wizard \
--from-file=license=cluster-wizard-040.lic

Example values-override.yaml snippet showing licenseSecretName:

licenseSecretName: "cw-license" 

Name Overrides

Configures cluster-wizard's installation names

  • nameOverride:
  • fullnameOverride:

values.yaml snippet showing name overrides:

nameOverride: ""
fullnameOverride: ""

OPA

Configures cluster-wizard's authorization rules

  • opa.policy: rego policy file to override default authorization rules
  • opa.roles : json file to override default roles

values.yaml snippet show OPA values:

opa:
policy: ""
role: ""

Postgres

Cluster-Wizard requires access to a postgres database. If an external postgres database is not available, Cluster-Wizard helm charts can deploy one.

Required values for postgres:

  • postgres.password: password of postgres user
  • postgres.cwPassword: password for the postgres.cwUser database account to be created.
  • postgres.host: where postgres can be accessed.

Example values-override.yaml snippet showing postgres values for an existing postgres database:

postgres:
password: "securePostgresPassword"
cwPassword: "secureWizardClientPassword"
adminUser: "cw-admin"
host: "postgres-service"

Example values-override.yaml snippet showing postgres values for creating a new postgres database:

postgres:
install:
install: true
password: "supersecret"
cwPassword: "supersecretToo"
adminUser: "cw-admin"
host: "postgres-service"

Optional values for postgres:

SSL:

  • postgres.ssl - sets whether or not the postgres server requires ssl for client connections

Installation:

  • postgres.install.install - controls whether or not postgres is installed by the helm chart. Default is false.

    Image Pull Policy:

    • postgres.install.pullPolicy - controls how the container image is pulled from the repository when starting a pod. Possible values: Always, IfNotPresent, Never. Default is IfNotPresent.

    PVC:

    • postgres.install.pvc.accessMode - sets the accessMode value on the PVC created for postgres. Possible values: ReadWriteOnce, ReadOnlyMany, ReadWriteMany. Provisioner dependent. Default is ReadWriteOnce.
    • postgres.install.pvc.size - sets the size of the PVC created for postgres. Default is 5Gi.
    • postgres.install.pvc.storageClass - set the storage class used for the pvc. Provisioner dependent. Default is "".

    Expose Options:

    • postgres.install.expose.type - controls how cluster wizard postgres service is exposed. Set the service type as "clusterIP", "loadBalancer", "nodePort" or "". Default is clusterIP.
      • clusterIP: used when postgres.install.expose.type is "clusterIP"
        • postgres.install.expose.clusterIP.staticClusterIP - sets the ip address of the ClusterIP service, leave empty for acquiring dynamic ip. Default is ""
      • loadBalancer: used when postgres.install.expose.type is "loadBalancer"
        • postgres.install.expose.loadBalancer.IP - specifies the IP address assigned by the loadBalancer. Default is "" for automatic assignment.
        • postgres.install.expose.loadBalancer.sourceRanges - specifies which CIDR IP ranges are allowed to access the LoadBalancer’s external IP. eg: "192.168.100.0/24"
      • nodePort: used when postgres.install.expose.type is "nodePort"
        • postgres.install.expose.nodePort.ports.externalPort.nodePort - sets the node port the service listens on. Default value is 30002 Postgres Version
  • postgres.tag - container tag used to define which version of postgres is installed. Default is 17.5

Database Populate:

  • postgres.populateDB - controls if cluster wizard populate database with initial values. Default is true

Database User Account

  • postgres.cwUser - Name of the database account used by client. Default is wizard_clients
  • postgres.cwDBName - Name of database. Default is cluster_management

values.yaml snippet showing all postgres values:

postgres:
install:
install: false
pullPolicy: "IfNotPresent"
pvc:
accessMode: "ReadWriteOnce"
size: "5Gi"
storageClass: ""
expose:
type: clusterIP
clusterIP:
staticClusterIP: ""
nodePort:
ports:
externalPort:
nodePort: 30002
loadBalancer:
IP: ""
sourceRanges: [ ]
tag: "17.5"
populateDB: true
password: ""
cwPassword: ""
adminUser: ""
host: "postgres-service"

Troubleshooting

Helm Output:

Error: INSTALLATION FAILED: failed post-install: 1 error occurred:
* timed out waiting for the condition

Container fails to create:

kubectl get pods -n cluster-wizard
NAME READY STATUS RESTARTS AGE
cluster-wizard-7f5676d657-9cbvp 0/1 ContainerCreating 0 5m05s
cluster-wizard-postgres-7b8f44985-xncpx 1/1 Running 0 5m05s
create-secret-job-4v4tz 0/1 ContainerCreating 0 5m05s

Pods status:

kubectl describe pod cluster-wizard-7f5676d657-9cbvp -n cluster-wizard
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m05s default-scheduler Successfully assigned cluster-wizard/cluster-wizard-7f5676d657-9cbvp to worker01
Warning FailedMount 5s (x11 over 5m5s) kubelet MountVolume.SetUp failed for volume "secret-volume" : [secret "client-ca" not found, secret "cluster-wizard-cert" not found, secret "cw-license" not found]

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m05s default-scheduler Successfully assigned cluster-wizard/cluster-wizard-7f5676d657-9cbvp to worker01
Warning FailedMount 5s (x11 over 5m5s) kubelet MountVolume.SetUp failed for volume "secret-volume" : secret "cw-license" not found

Cause: The secrets reference by clusterWizardCert.clientCASecretName, clusterWizardCert.serverSecretName or licenseSecretName were not created.

Helm Output:

Error: INSTALLATION FAILED: failed post-install: 1 error occurred:
* job create-secret-job failed: BackoffLimitExceeded

Pod in Error state:

kubectl get pods -n cluster-wizard
NAME READY STATUS RESTARTS AGE
cluster-wizard-7f5676d657-9cbvp 0/1 Error 6 (5m05s ago) 5m05s
cluster-wizard-postgres-7b8f44985-xncpx 1/1 Running 0 5m05s
create-secret-job-gm466 0/1 Error 0 5m05s
create-secret-job-hfrtl 0/1 Error 0 5m05s
create-secret-job-qpmps 0/1 Error 0 5m05s
create-secret-job-wzf5l 0/1 Error 0 5m05s

Pod Log file:

kubectl logs cluster-wizard-7f5676d657-9cbvp -n cluster-wizard
2025/05/05 05:05:05 START of check license
2025/05/05 05:05:05 main.go:273: [ERROR] : Failed to check license - invalid character 'i' looking for beginning of value

Cause: The secret pointed to by licenseSecretName contains an invalid license.