Warning: Don't use this configuration in any production environment. This is for testing purposes only in local and Home LAN environments. For production deployments, consider implementing proper security measures including strong passwords, network policies, TLS encryption, and backup strategies.
Overview
This guide walks you through deploying a MariaDB cluster with primary-secondary replication architecture alongside WordPress on MicroK8s using Helm charts. The setup demonstrates how to leverage Kubernetes for database high availability and horizontal read scaling.
Architecture Explained
The replication architecture consists of:
- Primary Node: Handles all write operations (INSERT, UPDATE, DELETE) and propagates changes to secondaries
- Secondary Nodes (Replicas): Receive replicated data from the primary and can serve read queries, improving read performance
- WordPress Frontend: Connects to the primary MariaDB node for database operations
This topology is ideal for read-heavy workloads where you can distribute SELECT queries across multiple replicas while maintaining a single source of truth for writes.
Prerequisites
Before starting, ensure you have the following components installed and configured:
| Component | Minimum Version | Purpose |
|---|---|---|
| MicroK8s | 1.28+ | Kubernetes distribution |
| Helm | 3.12+ | Package manager for Kubernetes |
| kubectl | 1.28+ | Kubernetes CLI tool |
| Storage Class | - | For persistent volume provisioning |
Enable required MicroK8s addons:
1microk8s enable dns storage helm3 metallbUnderstanding MariaDB Replication
MariaDB replication uses a binary log (binlog) based mechanism where:
- The primary server records all data modifications in its binary log
- Secondary servers connect to the primary and read the binary log
- Each secondary applies the logged events to maintain data consistency
Key replication parameters in the configuration:
replicationPassword: Credentials for the replication user that secondaries use to connect to the primaryreplicaCount: Number of secondary read replicas to deploy
MariaDB Cluster Configuration
Values for MariaDB Cluster
Create the MariaDB Helm values file that defines the replication topology:
1nano mariadb.yaml1architecture: "replication"2auth:3 user: "user"4 rootPassword: "123"5 password: "123"6 replicationPassword: "123"7mariadb:8secondary:9 replicaCount: 410tls:11 enabled: true12networkPolicy:13 enabled: falseConfiguration Parameters Breakdown
| Parameter | Value | Description |
|---|---|---|
architecture | replication | Enables primary-secondary replication mode (alternatives: standalone) |
auth.rootPassword | - | Root user password for administrative access |
auth.replicationPassword | - | Password for the replication user connecting secondaries to primary |
secondary.replicaCount | 4 | Number of read replicas to create |
tls.enabled | true | Enables TLS encryption for client connections |
networkPolicy.enabled | false | Disables Kubernetes NetworkPolicy (enable in production) |
Tip: For production environments, use strong randomly-generated passwords.
You can generate secure passwords using: openssl rand -base64 32
WordPress Configuration
Values for WordPress
The WordPress deployment uses a Kubernetes manifest that defines the Service, PersistentVolumeClaim, and Deployment resources:
1nano wordpress.yaml1apiVersion: v12kind: Service3metadata:4 name: wordpress5 labels:6 app: wordpress7spec:8 ports:9 - port: 8010 selector:11 app: wordpress12 tier: frontend13 type: LoadBalancer14---15apiVersion: v116kind: PersistentVolumeClaim17metadata:18 name: wp-pv-claim19 labels:20 app: wordpress21spec:22 accessModes:23 - ReadWriteOnce24 resources:25 requests:26 storage: 20Gi27---28apiVersion: apps/v129kind: Deployment30metadata:31 name: wordpress32 labels:33 app: wordpress34spec:35 selector:36 matchLabels:37 app: wordpress38 tier: frontend39 strategy:40 type: Recreate41 template:42 metadata:43 labels:44 app: wordpress45 tier: frontend46 spec:47 containers:48 - image: wordpress:6.2.1-apache49 name: wordpress50 env:51 - name: WORDPRESS_DB_HOST52 value: wordpress-mariadb53 - name: WORDPRESS_DB_PASSWORD54 valueFrom:55 secretKeyRef:56 name: mysql-pass57 key: password58 - name: WORDPRESS_DB_USER59 value: wordpress60 ports:61 - containerPort: 8062 name: wordpress63 volumeMounts:64 - name: wordpress-persistent-storage65 mountPath: /var/www/html66 volumes:67 - name: wordpress-persistent-storage68 persistentVolumeClaim:69 claimName: wp-pv-claimWordPress Manifest Components
Service (LoadBalancer): Exposes WordPress externally on port 80, automatically obtaining an external IP from MetalLB or your cloud provider's load balancer.
PersistentVolumeClaim: Requests 20Gi of persistent storage for WordPress files, themes, and plugins. Uses ReadWriteOnce access mode suitable for single-pod deployments.
Deployment Strategy: Uses Recreate strategy which terminates the old pod before creating a new one during updates. This prevents multiple pods accessing the same PersistentVolume simultaneously.
Environment Variables:
WORDPRESS_DB_HOST: Points to the MariaDB primary serviceWORDPRESS_DB_PASSWORD: Securely retrieved from a Kubernetes SecretWORDPRESS_DB_USER: Database username for WordPress
Deployment
Deploy it
Add the Bitnami Helm repository and deploy both MariaDB and WordPress:
1helm repo add bitnami https://charts.bitnami.com/bitnami2
3microk8s helm install maria oci://registry-1.docker.io/bitnamicharts/mariadb -f mariadb.yaml4microk8s helm install wordpress oci://registry-1.docker.io/bitnamicharts/wordpress -f wordpress.yamlCreate the Database Password Secret
Before the WordPress deployment can start, create the Secret containing the database password:
1kubectl create secret generic mysql-pass --from-literal=password='123'Verify Deployment Status
Check the status of all deployed resources:
1# Check MariaDB pods (should show 1 primary + 4 secondaries)2kubectl get pods -l app.kubernetes.io/name=mariadb3
4# Check WordPress pod5kubectl get pods -l app=wordpress6
7# View services and their external IPs8kubectl get svc9
10# Check persistent volume claims11kubectl get pvcExpected output for MariaDB pods:
1NAME READY STATUS RESTARTS AGE2maria-mariadb-primary-0 1/1 Running 0 5m3maria-mariadb-secondary-0 1/1 Running 0 5m4maria-mariadb-secondary-1 1/1 Running 0 4m5maria-mariadb-secondary-2 1/1 Running 0 3m6maria-mariadb-secondary-3 1/1 Running 0 2mVerifying Replication
Connect to Primary and Check Replication Status
Connect to the MariaDB primary pod and verify replication is working:
1# Get a shell into the primary pod2kubectl exec -it maria-mariadb-primary-0 -- bash3
4# Connect to MariaDB as root5mysql -u root -p6
7# Check replication status on primary8SHOW MASTER STATUS\G9
10# View connected replicas11SHOW SLAVE HOSTS;Test Replication
Create a test database and verify it replicates to secondaries:
1# On the primary2CREATE DATABASE test_replication;3USE test_replication;4CREATE TABLE test_table (id INT PRIMARY KEY, data VARCHAR(100));5INSERT INTO test_table VALUES (1, 'Hello from primary');Then connect to a secondary and verify:
1# Connect to a secondary pod2kubectl exec -it maria-mariadb-secondary-0 -- bash3
4# Connect and verify data5mysql -u root -p -e "SELECT * FROM test_replication.test_table;"Scaling Operations
Scale Read Replicas
Increase or decrease the number of secondary replicas:
1# Scale to 6 replicas2microk8s helm upgrade maria oci://registry-1.docker.io/bitnamicharts/mariadb \3 -f mariadb.yaml \4 --set secondary.replicaCount=65
6# Or scale down to 2 replicas7microk8s helm upgrade maria oci://registry-1.docker.io/bitnamicharts/mariadb \8 -f mariadb.yaml \9 --set secondary.replicaCount=2Monitor Resource Usage
Check resource consumption of MariaDB pods:
1kubectl top pods -l app.kubernetes.io/name=mariadbTroubleshooting
Common Issues
| Issue | Possible Cause | Solution |
|---|---|---|
| Secondary pods in CrashLoopBackOff | Incorrect replication password | Verify replicationPassword matches primary configuration |
| WordPress can't connect to DB | Wrong WORDPRESS_DB_HOST value | Ensure it matches the MariaDB service name |
| PVC stuck in Pending | No StorageClass available | Enable storage addon: microk8s enable storage |
| Replication lag increasing | High write load or network issues | Check pod resources and network latency |
View MariaDB Logs
1# Primary logs2kubectl logs maria-mariadb-primary-03
4# Secondary logs5kubectl logs maria-mariadb-secondary-06
7# Follow logs in real-time8kubectl logs -f maria-mariadb-primary-0Check Replication Lag
Connect to a secondary and check the replication delay:
1kubectl exec -it maria-mariadb-secondary-0 -- \2 mysql -u root -p -e "SHOW SLAVE STATUS\G" | grep -E "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"Cleanup
Remove all deployed resources:
1# Uninstall Helm releases2microk8s helm uninstall maria3microk8s helm uninstall wordpress4
5# Delete PVCs (WARNING: This deletes all data!)6kubectl delete pvc --all7
8# Delete the password secret9kubectl delete secret mysql-passAdvanced Configuration Examples
Enable Prometheus Metrics
Add metrics monitoring to your MariaDB cluster for observability:
1# Additional values for mariadb.yaml2metrics:3 enabled: true4 image:5 registry: docker.io6 repository: bitnami/mysqld-exporter7 serviceMonitor:8 enabled: true9 namespace: monitoring10 labels:11 release: prometheus12 resources:13 limits:14 cpu: 100m15 memory: 128Mi16 requests:17 cpu: 50m18 memory: 64MiConfigure Resource Limits
Set resource requests and limits for production workloads:
1# Resource configuration for primary2primary:3 resources:4 limits:5 cpu: "2"6 memory: 4Gi7 requests:8 cpu: "500m"9 memory: 1Gi10 persistence:11 size: 50Gi12 storageClass: "fast-storage"13
14# Resource configuration for secondaries15secondary:16 resources:17 limits:18 cpu: "1"19 memory: 2Gi20 requests:21 cpu: "250m"22 memory: 512Mi23 persistence:24 size: 50GiCustom MariaDB Configuration
Override MariaDB server configuration with custom parameters:
1# Primary node configuration2primary:3 configuration: |-4 [mysqld]5 skip-name-resolve6 explicit_defaults_for_timestamp7 basedir=/opt/bitnami/mariadb8 datadir=/bitnami/mariadb/data9 port=330610 socket=/opt/bitnami/mariadb/tmp/mysql.sock11 tmpdir=/opt/bitnami/mariadb/tmp12 max_allowed_packet=64M13 bind-address=0.0.0.014 pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid15 log-error=/opt/bitnami/mariadb/logs/mysqld.log16 character-set-server=UTF817 collation-server=utf8_general_ci18 slow_query_log=119 long_query_time=10.020 max_connections=20021 innodb_buffer_pool_size=1G22 innodb_log_file_size=256M23 innodb_flush_log_at_trx_commit=224
25# Secondary nodes configuration26secondary:27 configuration: |-28 [mysqld]29 skip-name-resolve30 explicit_defaults_for_timestamp31 basedir=/opt/bitnami/mariadb32 datadir=/bitnami/mariadb/data33 port=330634 socket=/opt/bitnami/mariadb/tmp/mysql.sock35 tmpdir=/opt/bitnami/mariadb/tmp36 max_allowed_packet=64M37 bind-address=0.0.0.038 pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid39 log-error=/opt/bitnami/mariadb/logs/mysqld.log40 character-set-server=UTF841 collation-server=utf8_general_ci42 read_only=143 max_connections=20044 innodb_buffer_pool_size=512MInitialize Database with Scripts
Pre-populate your database with initial schema and data:
1initdbScripts:2 init-wordpress-db.sql: |3 -- Create WordPress database and user4 CREATE DATABASE IF NOT EXISTS wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;5 CREATE USER IF NOT EXISTS 'wordpress'@'%' IDENTIFIED BY 'your_secure_password';6 GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%';7 FLUSH PRIVILEGES;8
9 health-check-script.sh: |10 #!/bin/sh11 if [[ $(hostname) == *primary* ]]; then12 echo "Primary node initialized successfully"13 mysql -P 3306 -uroot -p${MARIADB_ROOT_PASSWORD} -e "SELECT 1";14 fiProduction Considerations
Security Hardening
For production deployments, implement the following security measures:
- Strong Passwords: Use randomly generated passwords with at least 32 characters
- Network Policies: Enable and configure Kubernetes NetworkPolicy to restrict pod-to-pod communication
- TLS Encryption: Enable TLS for both client connections and replication traffic
- Pod Security Standards: Apply restrictive security contexts
- Secrets Management: Use external secrets managers like HashiCorp Vault or AWS Secrets Manager
High Availability Considerations
| Consideration | Recommendation |
|---|---|
| Pod Anti-Affinity | Distribute replicas across different nodes |
| Pod Disruption Budget | Ensure minimum available replicas during maintenance |
| Backup Strategy | Implement regular automated backups using Velero or mysqldump |
| Monitoring & Alerting | Set up Prometheus alerts for replication lag and resource usage |
| Failover Testing | Regularly test failover procedures |
Backup Configuration Example
Configure automated backups using a CronJob:
1apiVersion: batch/v12kind: CronJob3metadata:4 name: mariadb-backup5spec:6 schedule: "0 2 * * *" # Daily at 2 AM7 jobTemplate:8 spec:9 template:10 spec:11 containers:12 - name: backup13 image: bitnami/mariadb:latest14 command:15 - /bin/sh16 - -c17 - |18 mysqldump -h maria-mariadb-primary \19 -u root -p${MARIADB_ROOT_PASSWORD} \20 --all-databases --single-transaction \21 --routines --triggers \22 | gzip > /backup/backup-$(date +%Y%m%d-%H%M%S).sql.gz23 env:24 - name: MARIADB_ROOT_PASSWORD25 valueFrom:26 secretKeyRef:27 name: maria-mariadb28 key: mariadb-root-password29 volumeMounts:30 - name: backup-volume31 mountPath: /backup32 restartPolicy: OnFailure33 volumes:34 - name: backup-volume35 persistentVolumeClaim:36 claimName: mariadb-backup-pvcComparison: Replication vs Galera Cluster
Understanding when to use each architecture:
| Feature | Primary-Secondary Replication | Galera Cluster |
|---|---|---|
| Write Nodes | Single (primary only) | Multi-master (all nodes) |
| Read Scaling | Excellent | Good |
| Write Scaling | Limited | Better distribution |
| Consistency | Eventual (asynchronous) | Synchronous (strong) |
| Failover | Manual or requires automation | Automatic |
| Network Sensitivity | Tolerant | Requires low latency |
| Complexity | Lower | Higher |
| Use Case | Read-heavy workloads | Write-heavy, HA requirements |
For WordPress specifically, primary-secondary replication is often preferred due to the read-heavy nature of most WordPress sites (displaying content) versus the relatively low write activity (publishing posts, comments).