Distributed MariaDB Galera Cluster Overview
Introduction
KubeDB enables distributed MariaDB deployments using the Galera clustering technology across multiple Kubernetes clusters, providing a scalable, highly available, and resilient database solution. By integrating Open Cluster Management (OCM) for multi-cluster orchestration and KubeSlice for seamless pod-to-pod network connectivity, KubeDB simplifies the deployment and management of MariaDB instances across clusters. A PodPlacementPolicy ensures precise control over pod scheduling, allowing you to distribute MariaDB pods across clusters for optimal resource utilization and fault tolerance.
This guide provides a step-by-step process to deploy a distributed MariaDB Galera cluster, including prerequisites, configuration, and verification steps. It assumes familiarity with Kubernetes and basic database concepts.
New to KubeDB? Start with the KubeDB documentation for an introduction.
Understanding OCM Hub and Spoke Clusters
In an Open Cluster Management (OCM) setup, clusters are categorized as:
- Hub Cluster: The central control plane where policies, applications, and resources are defined and managed. It orchestrates the lifecycle of applications deployed across spoke clusters.
- Spoke Cluster: Managed clusters registered with the hub, running the actual workloads (e.g., MariaDB pods).
When a spoke cluster (e.g., demo-worker
) is joined to the hub using the clusteradm join
command, OCM creates a namespace on the hub cluster matching the spoke cluster’s name (e.g., demo-worker
). This namespace is used to manage resources specific to the spoke cluster from the hub.
Prerequisites
Before deploying a distributed MariaDB Galera cluster, ensure the following:
- Kubernetes Clusters: Multiple Kubernetes clusters (version 1.26 or higher) configured and accessible.
- Node Requirements: Each Kubernetes node should have at least 4 vCPUs and 16GB of RAM.
- Open Cluster Management (OCM): Install
clusteradm
as per the OCM Quick Start Guide. - kubectl: Installed and configured to interact with all clusters.
- Helm: Installed for deploying the KubeDB Operator and KubeSlice components.
- Persistent Storage: A storage class (e.g.,
local-path
or cloud provider-specific) configured for persistent volumes.
Configuration Steps
Follow these steps to deploy a distributed MariaDB Galera cluster across multiple Kubernetes clusters.
Step 1: Set Up Open Cluster Management (OCM)
Configure KUBECONFIG: Ensure your
KUBECONFIG
is set up to switch between clusters. This guide uses two clusters:demo-controller
(hub and spoke) anddemo-worker
(spoke).kubectl config get-contexts
Output:
CURRENT NAME CLUSTER AUTHINFO NAMESPACE * demo-controller demo-controller demo-controller demo-worker demo-worker demo-worker
Initialize the OCM Hub: On the
demo-controller
cluster, initialize the OCM hub.kubectl config use-context demo-controller clusteradm init --wait --feature-gates=ManifestWorkReplicaSet=true
Verify Hub Deployment: Check the pods in the
open-cluster-management-hub
namespace to ensure all components are running.kubectl get pods -n open-cluster-management-hub
Output:
NAME READY STATUS RESTARTS AGE cluster-manager-addon-manager-controller-5f99f56896-qpzj8 1/1 Running 0 7m2s cluster-manager-placement-controller-597d5ff644-wqjq2 1/1 Running 0 7m2s cluster-manager-registration-controller-6d79d7dcc6-b8h9p 1/1 Running 0 7m2s cluster-manager-registration-webhook-5d88cf97c7-2sq5m 1/1 Running 0 7m2s cluster-manager-work-controller-7468bf4dc-5qn6q 1/1 Running 0 7m2s cluster-manager-work-webhook-c5875947-d272b 1/1 Running 0 7m2s
All pods should be in the
Running
state with1/1
readiness and no restarts, indicating a successful hub deployment.Register Spoke Cluster (
demo-worker
): Obtain the join token from the hub cluster.clusteradm get token
Output:
token=<Your_Clusteradm_Join_Token> please log on spoke and run: clusteradm join --hub-token <Your_Clusteradm_Join_Token> --hub-apiserver https://10.2.0.56:6443 --cluster-name <cluster_name>
On the
demo-worker
cluster, join it to the hub, including theRawFeedbackJsonString
feature gate for resource feedback.kubectl config use-context demo-worker clusteradm join --hub-token <Your_Clusteradm_Join_Token> --hub-apiserver https://10.2.0.56:6443 --cluster-name demo-worker --feature-gates=RawFeedbackJsonString=true
Accept Spoke Cluster: On the
demo-controller
cluster, accept thedemo-worker
cluster.kubectl config use-context demo-controller clusteradm accept --clusters demo-worker
Note: It may take a few attempts (e.g., retry every 10 seconds) if the cluster is not immediately available.
Output (on success):
Starting approve csrs for the cluster demo-worker CSR demo-worker-2p2pb approved set hubAcceptsClient to true for managed cluster demo-worker Your managed cluster demo-worker has joined the Hub successfully.
Verify Namespace Creation: Confirm that a namespace for
demo-worker
was created on the hub cluster.kubectl get ns
Output:
NAME STATUS AGE default Active 99m demo-worker Active 58s kube-node-lease Active 99m kube-public Active 99m kube-system Active 99m open-cluster-management Active 6m7s open-cluster-management-hub Active 5m32s
Register
demo-controller
as a Spoke Cluster: Repeat the join and accept process fordemo-controller
to also act as a spoke cluster.kubectl config use-context demo-controller clusteradm join --hub-token <Your_Clusteradm_Join_Token> --hub-apiserver https://10.2.0.56:6443 --cluster-name demo-controller --feature-gates=RawFeedbackJsonString=true clusteradm accept --clusters demo-controller
Verify the namespace for
demo-controller
.kubectl get ns
Output:
NAME STATUS AGE default Active 104m demo-controller Active 3s demo-worker Active 6m7s kube-node-lease Active 104m kube-public Active 104m kube-system Active 104m open-cluster-management Active 11m open-cluster-management-agent Active 37s open-cluster-management-agent-addon Active 34s open-cluster-management-hub Active 10m
Step 2: Configure OCM WorkConfiguration (Optional)
If you did not follow the provided OCM installation steps, update the klusterlet
resource to enable feedback retrieval.
kubectl edit klusterlet klusterlet
Add the following under the spec
field:
workConfiguration:
featureGates:
- feature: RawFeedbackJsonString
mode: Enable
hubKubeAPIBurst: 100
hubKubeAPIQPS: 50
kubeAPIBurst: 100
kubeAPIQPS: 50
Verify the configuration:
kubectl get klusterlet klusterlet -oyaml
Sample Output (abridged):
apiVersion: operator.open-cluster-management.io/v1
kind: Klusterlet
metadata:
name: klusterlet
spec:
clusterName: demo-worker
workConfiguration:
featureGates:
- feature: RawFeedbackJsonString
mode: Enable
hubKubeAPIBurst: 100
hubKubeAPIQPS: 50
kubeAPIBurst: 100
kubeAPIQPS: 50
Step 3: Configure KubeSlice for Network Connectivity
KubeSlice enables pod-to-pod communication across clusters. Install the KubeSlice Controller on the demo-controller
cluster and the KubeSlice Worker on both demo-controller
and demo-worker
clusters.
Install KubeSlice Controller: On
demo-controller
, create acontroller.yaml
file:kubeslice: controller: loglevel: info rbacResourcePrefix: kubeslice-rbac projectnsPrefix: kubeslice endpoint: https://10.2.0.56:6443
Deploy the controller using Helm:
helm upgrade -i kubeslice-controller oci://ghcr.io/appscode-charts/kubeslice-controller \ --version v2025.7.31 \ -f controller.yaml \ --namespace kubeslice-controller \ --create-namespace \ --wait --burst-limit=10000 --debug
Verify the installation:
kubectl get pods -n kubeslice-controller
Output:
NAME READY STATUS RESTARTS AGE kubeslice-controller-manager-7fd756fff6-5kddd 2/2 Running 0 98s
Create a KubeSlice Project: Create a
project.yaml
file:apiVersion: controller.kubeslice.io/v1alpha1 kind: Project metadata: name: demo-distributed-mariadb namespace: kubeslice-controller spec: serviceAccount: readWrite: - admin
Apply the project:
kubectl apply -f project.yaml
Verify:
kubectl get project -n kubeslice-controller
Output:
NAME AGE demo-distributed-mariadb 31s
Check service accounts:
kubectl get sa -n kubeslice-demo-distributed-mariadb
Output:
NAME SECRETS AGE default 0 69s kubeslice-rbac-rw-admin 1 68s
Label Nodes for KubeSlice: Assign the
kubeslice.io/node-type=gateway
label to node(where worker operator will deploy) in both clusters.On
demo-controller
:kubectl get nodes kubectl label node demo-master kubeslice.io/node-type=gateway
On
demo-worker
:kubectl config use-context demo-worker kubectl get nodes kubectl label node demo-worker kubeslice.io/node-type=gateway
Register Clusters with KubeSlice: Identify the network interface for both clusters by running the command on the node.
ip route get 8.8.8.8 | awk '{ print $5 }'
Output (example):
enp1s0
Create a
registration.yaml
file:apiVersion: controller.kubeslice.io/v1alpha1 kind: Cluster metadata: name: demo-controller namespace: kubeslice-demo-distributed-mariadb spec: networkInterface: enp1s0 clusterProperty: {} --- apiVersion: controller.kubeslice.io/v1alpha1 kind: Cluster metadata: name: demo-worker namespace: kubeslice-demo-distributed-mariadb spec: networkInterface: enp1s0 clusterProperty: {}
Apply on
demo-controller
:kubectl apply -f registration.yaml
Verify:
kubectl get clusters -n kubeslice-demo-distributed-mariadb
Output:
NAME AGE demo-controller 9s demo-worker 9s
Register KubeSlice Worker Clusters: Create a
secrets.sh
script to generate worker configuration:
# The script returns a kubeconfig for the service account given
# you need to have kubectl on PATH with the context set to the cluster you want to create the config for
# Cosmetics for the created config
firstWorkerSecretName=$1
# cluster name what you given in clusters registration
clusterName=$2
# the Namespace and ServiceAccount name that is used for the config
namespace=$3
# Need to give correct network interface value like ens160, eth0 etc
networkInterface=$4
# kubectl cluster-info of respective worker-cluster
worker_endpoint=$5
######################
# actual script starts
set -o errexit
### Fetch Worker cluster Secrets ###
PROJECT_NAMESPACE=$(kubectl get secrets $firstWorkerSecretName -n $namespace -o jsonpath={.data.namespace})
CONTROLLER_ENDPOINT=$(kubectl get secrets $firstWorkerSecretName -n $namespace -o jsonpath={.data.controllerEndpoint})
CA_CRT=$(kubectl get secrets $firstWorkerSecretName -n $namespace -o jsonpath='{.data.ca\.crt}')
TOKEN=$(kubectl get secrets $firstWorkerSecretName -n $namespace -o jsonpath={.data.token})
echo "
---
## Base64 encoded secret values from controller cluster
controllerSecret:
namespace: ${PROJECT_NAMESPACE}
endpoint: ${CONTROLLER_ENDPOINT}
ca.crt: ${CA_CRT}
token: ${TOKEN}
cluster:
name: ${clusterName}
endpoint: ${worker_endpoint}
netop:
networkInterface: ${networkInterface}
"
Command to generate the worker configuration:
sh secrets.sh <worker-secret-name> <worker-cluster-name> <kubeslice-projectname> <network-interface> <worker-api-endpoint>
Get secrets for the project namespace:
kubectl get secrets -n kubeslice-demo-distributed-mariadb
Output:
NAME TYPE DATA AGE
kubeslice-rbac-rw-admin kubernetes.io/service-account-token 3 17m
kubeslice-rbac-worker-demo-controller kubernetes.io/service-account-token 5 5m8s
kubeslice-rbac-worker-demo-worker kubernetes.io/service-account-token 5 5m8s
Get the demo-worker
cluster endpoint:
kubectl cluster-info --context demo-worker --kubeconfig $HOME/.kube/config
Output:
Kubernetes control plane is running at https://10.2.0.60:6443
Run the script for demo-worker
:
sh secrets.sh kubeslice-rbac-worker-demo-worker demo-worker kubeslice-demo-distributed-mariadb enp1s0 https://10.2.0.60:6443 > sliceoperator-worker.yaml
Install the KubeSlice worker on demo-worker
:
kubectl config use-context demo-worker
helm upgrade -i kubeslice-worker oci://ghcr.io/appscode-charts/kubeslice-worker \
--version v2025.7.31 \
-f sliceoperator-worker.yaml \
--namespace kubeslice-system \
--create-namespace \
--wait --burst-limit=10000 --debug
Repeat for demo-controller
:
kubectl config use-context demo-controller
sh secrets.sh kubeslice-rbac-worker-demo-controller demo-controller kubeslice-demo-distributed-mariadb enp1s0 https://10.2.0.56:6443 > sliceoperator-controller.yaml
helm upgrade -i kubeslice-worker oci://ghcr.io/appscode-charts/kubeslice-worker \
--version v2025.7.31 \
-f sliceoperator-controller.yaml \
--namespace kubeslice-system \
--create-namespace \
--wait --burst-limit=10000 --debug
Verify the worker installation:
kubectl get pods -n kubeslice-system
Output:
NAME READY STATUS RESTARTS AGE
forwarder-kernel-bw5l4 1/1 Running 0 4m43s
kubeslice-dns-6bd9749f4d-pvh7g 1/1 Running 0 4m43s
kubeslice-install-crds-szhvc 0/1 Completed 0 4m56s
kubeslice-netop-g4dfn 1/1 Running 0 4m43s
kubeslice-operator-949b7d6f7-9wj7h 2/2 Running 0 4m43s
kubeslice-postdelete-job-ctlzt 0/1 Completed 0 20m
nsm-delete-webhooks-ndksl 0/1 Completed 0 20m
nsm-install-crds-5z4j9 0/1 Completed 0 4m53s
nsmgr-zzwgh 2/2 Running 0 4m43s
registry-k8s-979455d6d-q2j8x 1/1 Running 0 4m43s
spire-install-clusterid-cr-qwqlr 0/1 Completed 0 4m47s
spire-install-crds-cnbjh 0/1 Completed 0 4m50s
Step 4: Install the KubeDB Operator
Install the KubeDB Operator on the demo-controller
cluster to manage the MariaDB instance.
Get a Free License
Download a FREE license from AppsCode License Server. Get the license for demo-controller
cluster.
helm upgrade -i kubedb oci://ghcr.io/appscode-charts/kubedb \
--version v2025.7.31 \
--namespace kubedb --create-namespace \
--set-file global.license=$HOME/Downloads/kubedb-license-cd548cce-5141-4ed3-9276-6d9578707f12.txt \
--set petset.features.ocm.enabled=true \
--wait --burst-limit=10000 --debug
Note: --set petset.features.ocm.enabled=true
must be set to enable MariaDB Distributed feature.
Follow the KubeDB Installation Guide for additional details.
Step 5: Define a PodPlacementPolicy
Create a PodPlacementPolicy
to control pod distribution across clusters. Create a pod-placement-policy.yaml
file:
apiVersion: apps.k8s.appscode.com/v1
kind: PlacementPolicy
metadata:
labels:
app.kubernetes.io/managed-by: Helm
name: distributed-mariadb
spec:
nodeSpreadConstraint:
maxSkew: 1
whenUnsatisfiable: ScheduleAnyway
ocm:
distributionRules:
- clusterName: demo-controller
replicas:
- 0
- 2
- clusterName: demo-worker
replicas:
- 1
sliceName: demo-slice
zoneSpreadConstraint:
maxSkew: 1
whenUnsatisfiable: ScheduleAnyway
This policy schedules:
mariadb-0
andmariadb-2
ondemo-controller
.mariadb-1
ondemo-worker
.
Apply the policy on demo-controller
:
kubectl apply -f pod-placement-policy.yaml --context demo-controller --kubeconfig $HOME/.kube/config
Step 6: Onboard Application Namespace
Create a SliceConfig
to onboard the demo
(application) and kubedb
(operator) namespaces for network connectivity. Create a sliceconfig.yaml
file:
apiVersion: controller.kubeslice.io/v1alpha1
kind: SliceConfig
metadata:
name: demo-slice
namespace: kubeslice-demo-distributed-mariadb
spec:
sliceSubnet: 10.1.0.0/16
maxClusters: 16
sliceType: Application
sliceGatewayProvider:
sliceGatewayType: Wireguard
sliceCaType: Local
sliceIpamType: Local
rotationInterval: 60
vpnConfig:
cipher: AES-128-CBC
clusters:
- demo-controller
- demo-worker
qosProfileDetails:
queueType: HTB
priority: 1
tcType: BANDWIDTH_CONTROL
bandwidthCeilingKbps: 5120
bandwidthGuaranteedKbps: 2560
dscpClass: AF11
namespaceIsolationProfile:
applicationNamespaces:
- namespace: demo
clusters:
- '*'
- namespace: kubedb
clusters:
- '*'
isolationEnabled: false
allowedNamespaces:
- namespace: kube-system
clusters:
- '*'
Apply the SliceConfig
:
kubectl apply -f sliceconfig.yaml
Restart all pods in the kubedb
namespace to inject KubeSlice sidecar containers:
kubectl delete -n kubedb pod --all
Verify the pods restart and are running:
kubectl get pods -n kubedb
Output:
NAME READY STATUS RESTARTS AGE
kubedb-kubedb-autoscaler-0 1/2 Running 0 44s
kubedb-kubedb-ops-manager-0 1/2 Running 0 43s
kubedb-kubedb-provisioner-0 1/2 Running 0 43s
kubedb-kubedb-webhook-server-df667cd85-tjdp9 2/2 Running 0 44s
kubedb-petset-cf9f5b6f4-d9558 2/2 Running 0 44s
kubedb-sidekick-5dbf7bcf64-4b8cw 2/2 Running 0 44s
Step 7: Create a Distributed MariaDB Instance
Define a MariaDB custom resource with spec.distributed
set to true
and reference the PodPlacementPolicy
. Create a mariadb.yaml
file:
apiVersion: kubedb.com/v1
kind: MariaDB
metadata:
name: mariadb
namespace: demo
spec:
distributed: true
deletionPolicy: WipeOut
replicas: 3
storage:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageType: Durable
version: 11.5.2
podTemplate:
spec:
podPlacementPolicy:
name: distributed-mariadb
Apply the resource on demo-controller
:
kubectl apply -f mariadb.yaml --context demo-controller --kubeconfig $HOME/.kube/config
Step 8: Verify the Deployment
Check MariaDB Resource and Pods on
demo-controller
:kubectl get md,pods,secret -n demo --context demo-controller --kubeconfig $HOME/.kube/config
Output:
NAME VERSION STATUS AGE mariadb.kubedb.com/mariadb 11.5.2 Ready 99s NAME READY STATUS RESTARTS AGE pod/mariadb-0 3/3 Running 0 95s pod/mariadb-2 3/3 Running 0 95s NAME TYPE DATA AGE secret/mariadb-auth kubernetes.io/basic-auth 2 95s
Check Pods and Secrets on
demo-worker
:kubectl get pods,secrets -n demo --context demo-worker --kubeconfig $HOME/.kube/config
Output:
NAME READY STATUS RESTARTS AGE mariadb-1 3/3 Running 0 95s NAME TYPE DATA AGE secret/mariadb-auth kubernetes.io/basic-auth 2 95s
Verify Galera Cluster Status: Connect to a MariaDB pod and check the Galera cluster status:
kubectl exec -it -n demo pod/mariadb-0 --context demo-controller -- bash mariadb -uroot -p$MYSQL_ROOT_PASSWORD
Run the following query:
SHOW STATUS LIKE 'wsrep_cluster_status';
Output:
+----------------------+---------+ | Variable_name | Value | +----------------------+---------+ | wsrep_cluster_status | Primary | +----------------------+---------+ 1 row in set (0.001 sec)
Check additional Galera status variables:
SHOW STATUS LIKE 'wsrep%';
Key Indicators:
wsrep_cluster_status: Primary
: The cluster is fully operational.wsrep_cluster_size: 3
: All three nodes are part of the cluster.wsrep_connected: ON
: The node is connected to the cluster.wsrep_ready: ON
: The node is ready to accept queries.wsrep_incoming_addresses
: Lists the IP addresses of all nodes (e.g.,10.1.0.3:0,10.1.0.4:0,10.1.16.4:0
).
Troubleshooting Tips
- Pods Not Running: Check pod logs (
kubectl logs -n demo mariadb-0
) for errors related to storage, networking, or configuration. - Cluster Not Joining: Ensure the
RawFeedbackJsonString
feature gate is enabled and verify network connectivity between clusters. - KubeSlice Issues: Confirm that the network interface (
enp1s0
) matches your cluster’s configuration and that sidecar containers are injected. - MariaDB Not Synced: Check
wsrep_local_state_comment
(should beSynced
) and ensure all nodes have the samewsrep_cluster_state_uuid
.
Next Steps
- Accessing the Database: Use the
mariadb-auth
secret to retrieve credentials and connect to the MariaDB instance. - Scaling: Adjust the
PodPlacementPolicy
to add or remove replicas across clusters. - Monitoring: Integrate KubeDB with monitoring tools like Prometheus for cluster health insights.
For further details, refer to the KubeDB Documentation