Takes from Official MySQL Operator documentation here
Install K3s and MetalLB here K3s with LoadBalancer MetalLB
Understanding MySQL InnoDB Cluster Architecture
MySQL InnoDB Cluster is a complete high availability solution for MySQL that provides:
- Group Replication: Automatic data replication across all cluster members using a virtual synchronous replication protocol
- MySQL Router: Lightweight middleware that provides transparent routing between your application and the MySQL cluster
- MySQL Shell: Advanced client and code editor for managing the cluster
Key Components
| Component | Description | Default Port |
|---|---|---|
| MySQL Server | Database instances running Group Replication | 3306, 33060 (X Protocol) |
| MySQL Router | Connection routing and load balancing | 6446 (RW), 6447 (RO), 6448 (X RW), 6449 (X RO) |
| Group Replication | Synchronous multi-primary/single-primary replication | 33061 |
How MySQL Operator Works
The MySQL Operator for Kubernetes manages the full lifecycle of MySQL InnoDB Cluster setups:
- Provisioning: Automatically creates StatefulSets, Services, ConfigMaps, and Secrets
- Configuration: Manages Group Replication configuration and MySQL Router setup
- Monitoring: Watches cluster health and responds to failures
- Scaling: Handles adding/removing instances with data synchronization
- Upgrades: Performs rolling upgrades with minimal downtime
Deploy MySQL Operator
1kubectl apply -f https://raw.githubusercontent.com/mysql/mysql-operator/trunk/deploy/deploy-crds.yaml2kubectl apply -f https://raw.githubusercontent.com/mysql/mysql-operator/trunk/deploy/deploy-operator.yamlThe first command installs Custom Resource Definitions (CRDs) that define new Kubernetes resource types:
InnoDBCluster- Main cluster resourceMySQLBackup- Backup resourceClusterSecretRef- Secret reference for multi-cluster setups
The second command deploys the operator itself, which watches for these custom resources.
Verify the deployment
1kubectl get deployment mysql-operator --namespace mysql-operatorYou should see output similar to:
1NAME READY UP-TO-DATE AVAILABLE AGE2mysql-operator 1/1 1 1 2mAdditional Operator Verification Commands
Check operator pods are running:
1kubectl get pods --namespace mysql-operatorView operator logs for troubleshooting:
1kubectl logs -f deployment/mysql-operator --namespace mysql-operatorCreate credentials secret
The secret stores MySQL root credentials used by the cluster. The operator reads this secret during cluster initialization.
1kubectl create secret generic mypwds \2 --from-literal=rootUser=root \3 --from-literal=rootHost=% \4 --from-literal=rootPassword="123"Security Note: For production environments, use strong passwords and consider using Kubernetes Secrets management solutions like HashiCorp Vault, Sealed Secrets, or External Secrets Operator.
Secret Parameters Explained:
rootUser: MySQL root username (typically "root")rootHost: Host pattern for root access (%means any host, use specific IP/hostname for security)rootPassword: Root password (should be complex in production)
Alternative: Creating Secret from YAML
For version control and GitOps workflows:
1apiVersion: v12kind: Secret3metadata:4 name: mypwds5type: Opaque6stringData:7 rootUser: root8 rootHost: "%"9 rootPassword: "your-secure-password"Deploy MySQL InnoDB Cluster
1nano mycluster.yaml1apiVersion: mysql.oracle.com/v22kind: InnoDBCluster3metadata:4 name: mycluster5spec:6 secretName: mypwds7 tlsUseSelfSigned: true8 instances: 39 router:10 instances: 1Cluster Specification Explained:
| Field | Description |
|---|---|
secretName | Reference to the Kubernetes secret containing root credentials |
tlsUseSelfSigned | When true, the operator generates self-signed certificates for TLS encryption |
instances | Number of MySQL server instances (minimum 3 for fault tolerance) |
router.instances | Number of MySQL Router instances for load balancing |
Why 3 Instances? InnoDB Cluster uses Group Replication which requires a majority (quorum) to function. With 3 nodes, you can tolerate 1 failure. With 5 nodes, you can tolerate 2 failures.
1kubectl apply -f mycluster.yaml2kubectl get innodbcluster --watchThe cluster goes through several states during initialization:
PENDING→ Resources being createdINITIALIZING→ MySQL instances startingONLINE→ Cluster is ready
Extended Cluster Configuration
For production deployments, consider these additional options:
1apiVersion: mysql.oracle.com/v22kind: InnoDBCluster3metadata:4 name: mycluster-production5spec:6 secretName: mypwds7 tlsUseSelfSigned: true8 instances: 39 version: "9.0.1" # Specific MySQL version10 router:11 instances: 2 # Multiple routers for HA12 datadirVolumeClaimTemplate:13 accessModes:14 - ReadWriteOnce15 resources:16 requests:17 storage: 50Gi18 storageClassName: local-path # K3s default storage class19 mycnf: |20 [mysqld]21 max_connections=50022 innodb_buffer_pool_size=1G23 innodb_log_file_size=256MResource Limits Configuration
Control CPU and memory for MySQL pods:
1apiVersion: mysql.oracle.com/v22kind: InnoDBCluster3metadata:4 name: mycluster-resources5spec:6 secretName: mypwds7 tlsUseSelfSigned: true8 instances: 39 router:10 instances: 111 podSpec:12 containers:13 - name: mysql14 resources:15 requests:16 cpu: "500m"17 memory: "1Gi"18 limits:19 cpu: "2"20 memory: "4Gi"Expose MySQL InnoDB Cluster to LAN
Warning: Exposing MySQL directly to LAN without proper security measures is dangerous!
Dont do this!!!
1kubectl patch service mycluster -p '{"spec": {"type": "LoadBalancer"}}'Why Direct Exposure is Dangerous
- No authentication layer: Anyone on the network can attempt connections
- Brute force attacks: Exposed ports are targets for automated attacks
- Data exfiltration: Compromised credentials lead to data theft
- Compliance violations: Many standards (PCI-DSS, HIPAA) prohibit direct database exposure
Secure Alternatives
Option 1: Internal ClusterIP with Application Proxy
Keep MySQL internal and access through your application:
1# MySQL remains ClusterIP (default)2# Applications in the cluster connect via service name3mysql -h mycluster -P 6446 -u root -pOption 2: NodePort for Controlled Access
Expose on specific node ports with firewall rules:
1apiVersion: v12kind: Service3metadata:4 name: mycluster-nodeport5spec:6 type: NodePort7 selector:8 mysql.oracle.com/cluster: mycluster9 component: mysqlrouter10 ports:11 - name: mysql-rw12 port: 644613 targetPort: 644614 nodePort: 30446Option 3: VPN or SSH Tunnel
Access from outside via secure tunnel:
1# SSH tunnel from local machine2ssh -L 3306:mycluster.default.svc.cluster.local:6446 user@k3s-nodeNext example deploy WordPress with MySQL InnoDB Cluster here
Cluster Management
Connect Mysqlsh
1kubectl run --rm -it myshell --image=container-registry.oracle.com/mysql/community-operator -- mysqlshOnce connected, you can interact with the cluster using MySQL Shell's JavaScript or Python mode:
1// Connect to the cluster2\connect root@mycluster:64463
4// Check cluster status5var cluster = dba.getCluster()6cluster.status()Connect to specific pod in cluster
1kunectl get pods2kubectl --namespace default exec -it mycluster-2 -- bash3
4mysqlsh root@localhostMonitoring and Troubleshooting
Check Cluster Status
View overall cluster health:
1kubectl get innodbclusterExample output:
1NAME STATUS ONLINE INSTANCES ROUTERS AGE2mycluster ONLINE 3 3 1 1hView Cluster Events
Monitor events for debugging:
1kubectl describe innodbcluster myclusterCheck Individual Pod Status
1kubectl get pods -l mysql.oracle.com/cluster=myclusterView MySQL Logs
1# View logs for specific MySQL instance2kubectl logs mycluster-0 -c mysql3
4# Follow logs in real-time5kubectl logs -f mycluster-0 -c mysqlCheck Group Replication Status
Connect to MySQL and run:
1SELECT * FROM performance_schema.replication_group_members;Expected output shows all members with ONLINE state:
1+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+2| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE |3+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+4| group_replication_applier | 3b3c6a7c-1234-5678-9abc-def012345678 | mycluster-0 | 3306 | ONLINE | PRIMARY |5| group_replication_applier | 4c4d7b8d-2345-6789-abcd-ef0123456789 | mycluster-1 | 3306 | ONLINE | SECONDARY |6| group_replication_applier | 5d5e8c9e-3456-789a-bcde-f01234567890 | mycluster-2 | 3306 | ONLINE | SECONDARY |7+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+Common Issues and Solutions
| Issue | Symptom | Solution |
|---|---|---|
| Cluster stuck in PENDING | Pods not starting | Check PVC binding: kubectl get pvc |
| Split-brain scenario | Multiple primaries | Restart affected pods, check network |
| High replication lag | Slow queries | Check performance_schema.replication_group_member_stats |
| Router not connecting | Connection refused | Verify router pods: kubectl get pods -l component=mysqlrouter |
Scaling the Cluster
Add More Instances
Update the instances field:
1kubectl patch innodbcluster mycluster --type=merge -p '{"spec":{"instances":5}}'Scale Router Instances
For higher connection capacity:
1kubectl patch innodbcluster mycluster --type=merge -p '{"spec":{"router":{"instances":3}}}'Backup and Recovery
Create Backup Using MySQL Shell
1kubectl run --rm -it backup-shell --image=container-registry.oracle.com/mysql/community-operator -- mysqlsh root@mycluster:6446 -- util.dumpInstance('/backup')Backup to Persistent Volume
Create a backup job with PVC:
1apiVersion: batch/v12kind: Job3metadata:4 name: mysql-backup5spec:6 template:7 spec:8 containers:9 - name: backup10 image: container-registry.oracle.com/mysql/community-operator11 command:12 - mysqlsh13 - root@mycluster:644614 - --password=$(MYSQL_ROOT_PASSWORD)15 - -e16 - "util.dumpInstance('/backup', {threads: 4})"17 env:18 - name: MYSQL_ROOT_PASSWORD19 valueFrom:20 secretKeyRef:21 name: mypwds22 key: rootPassword23 volumeMounts:24 - name: backup-storage25 mountPath: /backup26 volumes:27 - name: backup-storage28 persistentVolumeClaim:29 claimName: mysql-backup-pvc30 restartPolicy: NeverPerformance Tuning
Key MySQL Parameters for InnoDB Cluster
Add to your cluster's mycnf section:
1[mysqld]2# Connection handling3max_connections = 5004thread_cache_size = 505
6# InnoDB settings7innodb_buffer_pool_size = 2G8innodb_buffer_pool_instances = 49innodb_log_file_size = 512M10innodb_flush_log_at_trx_commit = 111
12# Group Replication tuning13group_replication_poll_spin_loops = 014group_replication_compression_threshold = 100000015group_replication_flow_control_mode = QUOTARouter Connection Pool Settings
For high-traffic applications:
1spec:2 router:3 instances: 24 podSpec:5 containers:6 - name: router7 env:8 - name: MYSQL_ROUTER_max_total_connections9 value: "1024"