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
- 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
- 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
- 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
- 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
- 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
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.
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
- clusterIP: Used when expose.type is "clusterIP"
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
- clusterIP: used when postgres.install.expose.type is "clusterIP"
-
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.