Deploying MySQL on Kubernetes: A Comprehensive Guide

Running stateful workloads on Kubernetes was once considered risky. The platform was originally built for stateless applications, so databases didn’t fit naturally. Today, Kubernetes has evolved with tools like StatefulSets, Persistent Volumes, and Operators that make it well-suited for managing databases at scale.

This guide walks you through deploying and operating a production-ready MySQL instance on Kubernetes. Instead of stopping at a simple deployment, we’ll focus on key areas developers need to handle: high availability, security, performance tuning, and ongoing operations. The goal is to help you build a reliable and maintainable database layer within your cluster.

Unified Cloud Orchestration for Kubernetes

Manage Kubernetes at scale through a single, enterprise-ready platform.

GitOps Deployment
Secure Dashboards
Infrastructure-as-Code
Book a demo

Key takeaways:

  • Use a MySQL Operator for production automation: While StatefulSets provide stable storage and networking, a dedicated MySQL Operator is essential for managing production workloads by automating critical operations like failover, backups, and upgrades.
  • Isolate your database with network policies: A comprehensive security strategy includes RBAC and secrets management, but implementing strict Network Policies is the most critical first step to limit the database's attack surface by controlling ingress traffic.
  • Build for high availability with replication: To prevent single points of failure, deploy MySQL in a primary-replica architecture across multiple availability zones using pod anti-affinity rules, which provide the foundation for automated failover during infrastructure outages.

What Is MySQL on Kubernetes

Running MySQL on Kubernetes means deploying the database as a containerized workload inside your cluster. Kubernetes handles lifecycle tasks like deployment, scaling, and failover, while StatefulSets and Persistent Volumes ensure durability and consistency. This approach treats the database as infrastructure-as-code, making deployments reproducible and automatable. Although setting up storage, networking, and security requires extra care, the benefit is a unified control plane for both stateless and stateful workloads.

What Are Container-Based Databases

A container-based database runs inside a container, isolated from the host environment for consistency across environments. Early skepticism about running stateful workloads in containers has largely faded as Kubernetes introduced primitives like StatefulSets and Persistent Volumes. These provide stable identities and persistent storage, making containerized databases reliable. Containerization also enables you to use the same CI/CD workflows and declarative configuration practices you apply to stateless apps, reducing drift and simplifying operations.

Benefits and Challenges

Deploying MySQL on Kubernetes offers both clear advantages and notable complexities. The main advantage is consistency—databases can be managed using the same workflows and automation as applications. This includes automated deployments, scaling, and integration with CI/CD pipelines. The tradeoff is complexity in handling persistence, networking, and backups. Kubernetes wasn’t built for stateful services initially, so running MySQL effectively requires careful storage design, tuning, and monitoring to avoid performance bottlenecks or data loss.

Key Components and Architecture

A production-grade MySQL deployment on Kubernetes depends on several building blocks. StatefulSets manage pods with ordered startup and stable identities. Persistent Volumes and Persistent Volume Claims provide durable storage. Services expose consistent network access, while ConfigMaps and Secrets manage configuration and credentials securely. The recommended approach is to use a MySQL Operator, which automates lifecycle tasks like backups, upgrades, and scaling. Operator frameworks simplify day-to-day management and ensure consistency across clusters, reducing operational overhead.

How to Deploy MySQL on Kubernetes

Deploying MySQL on Kubernetes requires more than just running a container—it involves careful planning around storage, networking, security, and resource allocation. Unlike stateless services, databases need persistent storage and stable network identities. A well-structured deployment ensures MySQL remains reliable, performant, and scalable in a dynamic cluster. By following a clear process, you can avoid common pitfalls like data loss, performance bottlenecks, and misconfigured security.

Define Resource Requirements

Resource management is critical for MySQL stability. Set CPU and memory requests and limits in your pod specs to prevent resource contention and ensure consistent performance. Requests guarantee a minimum allocation, while limits cap maximum usage. If a pod exceeds its memory limit, Kubernetes may restart it, causing downtime. Properly tuned requests and limits also help the scheduler place pods more effectively.

Example Pod spec with resource requests/limits:

apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: mysql:8.0
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"
      limits:
        memory: "2Gi"
        cpu: "1"

Implement with StatefulSets

MySQL should be deployed using StatefulSets rather than Deployments. StatefulSets provide stable hostnames, predictable pod identities, and persistent storage—requirements for a reliable database.

Example StatefulSet for MySQL:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
  volumeClaimTemplates:
  - metadata:
      name: mysql-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

Follow Configuration Best Practices

Store sensitive data like root passwords in Kubernetes Secrets, not in plain YAML or images.

Example Secret for MySQL credentials:

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
data:
  root-password: bXlzcWxfcGFzc3dvcmQ=  # base64 encoded value

ConfigMaps can manage non-sensitive settings such as MySQL configuration files.

Example ConfigMap for custom my.cnf:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=1G

Set Up Health Checks and Monitoring

Health checks ensure MySQL stays available.

Example health probes for MySQL:

livenessProbe:
  exec:
    command:
    - sh
    - -c
    - "mysqladmin ping -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD"
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  exec:
    command:
    - sh
    - -c
    - "mysql -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD -e 'SELECT 1;'"
  initialDelaySeconds: 10
  periodSeconds: 5

These probes let Kubernetes automatically restart failed pods and only route traffic to ready instances.

Tools for Managing MySQL on Kubernetes

Managing MySQL on Kubernetes requires more than the built-in primitives like StatefulSets and PersistentVolumes. While these provide the foundation, specialized tools handle automation, observability, backups, and upgrades. The right tooling abstracts away operational complexity, making it easier to run MySQL reliably in production. A complete setup usually combines Operators for lifecycle management, Helm charts for deployment, monitoring stacks for observability, and backup tools for disaster recovery.

Comparing MySQL Operators

A Kubernetes Operator extends the API to manage complex stateful workloads. For MySQL, an Operator can handle setup, scaling, failover, upgrades, and backups automatically.

  • Oracle MySQL Operator – manages MySQL InnoDB Cluster with high availability and built-in lifecycle automation.
  • Percona Operator for MySQL – widely used for production workloads, supports clustering, monitoring integration, and backups.
  • MariaDB Operator – supports MariaDB deployments with automation for scaling and upgrades.

When evaluating, look at feature set, stability, and community support. An Operator reduces manual intervention and makes production-grade MySQL management more repeatable.

Example Operator installation with Helm:

helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm install mysql-operator mysql-operator/mysql-operator

Use Helm Charts and Templates

Helm bundles all Kubernetes manifests for MySQL—StatefulSets, Services, ConfigMaps, and Secrets—into a single chart, making deployments reproducible and configurable. Configuration is managed through a values.yaml file.

Example MySQL installation with Helm:

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-mysql bitnami/mysql --set auth.rootPassword=my-secret-pw

This enables quick setup while still allowing customizations such as storage size, resource limits, and replication. Helm also integrates naturally into GitOps workflows, ensuring deployments remain consistent across clusters.

Choose Your Monitoring Solution

Monitoring is critical for both the MySQL engine and Kubernetes resources. A typical stack is:

  • Prometheus – collects metrics.
  • Grafana – visualizes metrics with dashboards.
  • Alertmanager – triggers alerts on anomalies.

Example Prometheus scrape config for MySQL exporter:

scrape_configs:
  - job_name: 'mysql'
    static_configs:
      - targets: ['mysql-exporter:9104']

This setup tracks key metrics like query latency, connections, CPU, memory, and disk I/O, giving developers visibility into database performance and cluster resource usage.

Implement Backup Tools and Strategies

A backup strategy must protect both database data and Kubernetes resources. Many Operators support scheduled backups to object storage (S3, GCS, Azure Blob).

Example Velero backup command:

velero backup create mysql-backup --include-namespaces=mysql --storage-location=default

Velero is a general-purpose tool that backs up persistent volumes along with cluster objects. Regardless of the tool, always validate restore procedures to ensure reliability in real incidents.

How to Ensure High Availability

MySQL on Kubernetes requires more than pod restarts to achieve true high availability. A resilient setup must handle node failures, zone outages, and even data corruption without compromising data integrity. This involves replication for redundancy, automated failover to minimize downtime, backups to protect against data loss, and disaster recovery for large-scale failures. Kubernetes primitives like StatefulSets and PersistentVolumes provide the foundation, while MySQL Operators and supporting tools automate the critical workflows. Managing these components across clusters can add operational overhead, which platforms like Plural reduce by providing a single pane of glass for consistent deployments and lifecycle management.

Design a Replication Architecture

High availability starts with replication. A common approach is a primary–replica cluster with at least three MySQL pods: one primary for writes and two or more replicas for reads.

Key practices:

  • Spread pods across zones with anti-affinity rules to avoid a single point of failure.
  • Back each pod with a PersistentVolume to ensure data durability.
  • Use replicas to offload read traffic and prepare for failover scenarios.

Example anti-affinity rule in a StatefulSet:

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values:
          - mysql
      topologyKey: "topology.kubernetes.io/zone"

Plural’s centralized policies ensure that anti-affinity and storage configurations are applied consistently across all environments, reducing the risk of misconfiguration.

Set Up Automated Failover

Replication is only useful if failover is seamless. Manual promotion of replicas is error-prone and slow. Operators like Percona XtraDB Cluster Operator or Oracle MySQL Operator automate failover as part of their lifecycle management.

How it works:

  • Operator monitors primary instance health.
  • On failure, it promotes the best replica.
  • Replicas reconfigure to follow the new primary.
  • Services are updated so apps connect without changes.

Example of deploying Percona XtraDB Operator with Helm:

helm repo add percona https://percona.github.io/percona-helm-charts/
helm install my-mysql-operator percona/pxc-operator

Plural integrates Operators into its GitOps pipeline, ensuring they are deployed and upgraded consistently across your fleet.

Establish Backup and Recovery Procedures

Replication won’t protect against accidental deletes or corruption. Backups provide a safety net.

Best practices:

  • Schedule regular full and incremental backups.
  • Store backups in durable storage like S3 or GCS.
  • Enable point-in-time recovery (PITR) for precision restores.
  • Test recovery procedures regularly.

Example Percona Operator backup CRD:

apiVersion: pxc.percona.com/v1-9-0
kind: PerconaXtraDBClusterBackup
metadata:
  name: daily-backup
spec:
  pxcCluster: my-mysql-cluster
  storageName: s3-backup

With Plural Stacks, you can declaratively manage backup tools and policies, guaranteeing consistency across clusters and simplifying compliance.

Plan for Disaster Recovery

For resilience against full-region failures, implement a multi-region DR plan.

  • Run a standby MySQL cluster in another region.
  • Use asynchronous replication to sync data between regions.
  • Define a controlled failover process to promote the standby cluster.

This ensures business continuity in worst-case scenarios. Managing multi-cluster, multi-region deployments introduces significant complexity, which Plural addresses by offering a unified workflow to deploy and manage infrastructure across any number of clusters and environments.

Secure Your MySQL Deployment

Running a stateful service like MySQL on Kubernetes requires a deliberate security strategy. A breach could expose sensitive application data, so security must be a top priority. Kubernetes provides tools to lock down workloads—from controlling API access to isolating network traffic. Properly securing your MySQL deployment involves:

  • Configuring RBAC for strict access control.
  • Implementing NetworkPolicies to isolate traffic.
  • Enabling encryption in transit and at rest.
  • Managing secrets securely.

Platforms like Plural make this easier by applying GitOps workflows and fleet-wide policies, ensuring consistency across clusters.

Configure RBAC

Role-Based Access Control (RBAC) governs access to the Kubernetes API. For MySQL, you should restrict who can interact with database pods, services, and volumes.

  • Use least-privilege roles for applications and monitoring tools.
  • Bind Roles/ClusterRoles to specific service accounts, not broad groups.
  • Regularly audit RBAC bindings.

Example: Read-only Role for monitoring:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: mysql-monitor-role
  namespace: database
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: mysql-monitor-binding
  namespace: database
subjects:
- kind: ServiceAccount
  name: monitor-sa
  namespace: database
roleRef:
  kind: Role
  name: mysql-monitor-role
  apiGroup: rbac.authorization.k8s.io

With Plural, RBAC policies can be defined once in Git and synced via GlobalService across your fleet, preventing drift between clusters.

Implement Network Policies

By default, Kubernetes pods can talk to each other freely—an unnecessary risk for MySQL. NetworkPolicies let you explicitly allow or deny traffic.

  • Allow ingress only from application pods that require MySQL access.
  • Block all other traffic.
  • Restrict egress to prevent the database from making external calls.

Example: Allow ingress only on port 3306 from app pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: mysql
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: myapp
    ports:
    - protocol: TCP
      port: 3306
  policyTypes:
  - Ingress

Plural ensures NetworkPolicies are managed as code and consistently applied to all clusters, reducing the chance of a misconfigured firewall rule.

Encrypt Your Data

Encryption protects MySQL data from interception or unauthorized access.

  • In transit: Enable TLS between apps and MySQL. Store TLS certs in Kubernetes Secrets.
  • At rest: Use encrypted PersistentVolumes by configuring the StorageClass.

Example: Encrypted storage class (AWS EBS):

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-gp2
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  encrypted: "true"

Plural Stacks ensure that encryption defaults are enforced across your infrastructure so you don’t rely on manual setup.

Manage Secrets Securely

Never hardcode MySQL credentials in manifests or images. Use Kubernetes Secrets or external managers.

Example: MySQL Secret in Kubernetes:

apiVersion: v1
kind: Secret
metadata:
  name: mysql-root-password
  namespace: database
type: Opaque
data:
  password: bXlzcWxfc2VjdXJlX3Bhc3M=   # base64 encoded

For stronger security:

  • Use external secrets managers (HashiCorp Vault, AWS Secrets Manager, etc.).
  • Mount them into pods with Secrets Store CSI driver.

Plural’s marketplace makes it simple to deploy and manage Vault or other secret management tools across all environments.

How to Optimize Performance

Deploying MySQL on Kubernetes is only the starting point; ensuring it runs efficiently under production workloads requires ongoing tuning. Performance optimization involves monitoring and adjusting infrastructure, storage, networking, and database configurations.

A well-optimized MySQL instance should:

  • Handle concurrent connections reliably.
  • Respond quickly to queries.
  • Scale predictably as demand grows.

Neglecting performance leads to slow application responses, resource contention, and instability. The four key areas to focus on are resource management, storage, connection pooling, and query tuning.

Manage Resources Effectively

MySQL pods need predictable CPU and memory resources. Without resource requests and limits, they risk OOM kills or CPU throttling.

  • Requests guarantee minimum resources.
  • Limits prevent a pod from over-consuming.
  • Monitor and adjust based on real usage.

Example: MySQL pod with requests and limits:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        resources:
          requests:
            memory: "2Gi"
            cpu: "500m"
          limits:
            memory: "4Gi"
            cpu: "2"

Plural’s embedded Kubernetes dashboard gives a fleet-wide view of resource consumption, making it easier to fine-tune based on observed workloads.

Configure Storage for Performance

MySQL is I/O intensive, and storage is often the biggest bottleneck. Use SSD-backed PersistentVolumes to achieve the low-latency, high-IOPS throughput transactional workloads demand.

  • Always use a StorageClass with SSDs.
  • Avoid spinning disks for production MySQL.
  • Ensure volumes are provisioned with low-latency backends.

Example: SSD-backed storage class (GKE):

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: mysql-ssd
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
reclaimPolicy: Retain

Plural Stacks enforce consistent storage class definitions across clusters, preventing misconfigurations that can cripple performance.

Implement Connection Pooling

Opening new database connections is expensive. Connection pooling improves throughput and reduces latency by reusing existing connections.

  • Deploy a connection pooler (e.g., ProxySQL, Proxy sidecar).
  • Use it as a middle layer between applications and MySQL.
  • Reduce CPU overhead on the MySQL server.

Example: ProxySQL deployment in Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: proxysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: proxysql
  template:
    metadata:
      labels:
        app: proxysql
    spec:
      containers:
      - name: proxysql
        image: proxysql/proxysql:2.5.5
        ports:
        - containerPort: 6033

This deployment can be fronted by a Service to route all MySQL traffic through ProxySQL.

Tune Query Performance

Even with optimized infrastructure, slow queries can bring down performance.

  • Use indexes to avoid full table scans.
  • Analyze queries with EXPLAIN to spot inefficiencies.
  • Enable slow query logs to catch problems in production.

Example: Enable slow query log in MySQL config:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    slow_query_log = 1
    slow_query_log_file = /var/log/mysql/slow-query.log
    long_query_time = 1

Plural integrates with observability stacks so you can monitor query performance, resource usage, and latency metrics across your entire fleet.

How to Troubleshoot MySQL on Kubernetes

Running MySQL on Kubernetes introduces unique troubleshooting challenges. Problems can arise at multiple layers—Kubernetes scheduling, persistent storage, networking, or MySQL itself. A systematic debugging approach is essential to identify root causes quickly.

While MySQL Operators automate many lifecycle tasks, they can also hide underlying failures. Sometimes you’ll need to go beyond the Operator’s abstraction and directly inspect pods, logs, and configuration.

Plural simplifies this process by giving you a single-pane-of-glass dashboard across your clusters. You can view pod health, logs, and resource metrics in one place, helping you correlate events and speed up root cause analysis. Still, a working knowledge of common MySQL-on-Kubernetes failure modes is critical.

This section walks through deployment, configuration, performance, and recovery issues—and how to solve them.

Solving Common Deployment Issues

Most deployment problems stem from misconfigured storage, networking, or security.

  • Pods stuck in Pending often indicate missing or misconfigured PersistentVolumes.
  • Pods in CrashLoopBackOff typically point to container misconfigurations or missing secrets.
  • MySQL Operator-managed clusters may fail if the CRD or Operator lacks proper permissions.

Troubleshooting commands:

# Inspect events for a failing pod
kubectl describe pod <pod-name>

# Check logs for container errors
kubectl logs <pod-name> --previous

With Plural, you can see Pending/CrashLoopBackOff pods across all clusters in one dashboard instead of manually running kubectl on each cluster.

Fixing Configuration Problems

Incorrect environment variables, secrets, or Services often block apps from connecting to MySQL.

Steps to debug:

  1. Verify secrets are mounted correctly:
kubectl describe secret mysql-root-password
kubectl exec -it <pod-name> -- printenv | grep MYSQL
  1. Test Service connectivity from inside the cluster:
kubectl exec -it <app-pod> -- nc -zv mysql 3306
  1. Check if NetworkPolicies are blocking traffic:
kubectl describe networkpolicy

Plural’s GitOps-based RBAC and policy management ensures that secrets and network rules are applied consistently across your fleet.

Debugging Performance Bottlenecks

Slow responses or timeouts usually trace back to resource limits, disk I/O, or inefficient queries.

Use Kubernetes metrics:

kubectl top pod -n <namespace>

Enable MySQL slow query log:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

Check IOPS by reviewing your StorageClass—ensure it’s SSD-backed for production workloads.

Plural’s observability tools correlate pod metrics, node resource usage, and database performance, helping you distinguish between infrastructure and SQL-level bottlenecks.

Executing Recovery Procedures

Backup and restore workflows often fail due to permissions, corrupted files, or PersistentVolume issues.

Inspect backup tool logs (e.g., Velero):

kubectl logs deploy/velero -n velero

Restore a backup and attach a new PVC:

velero restore create --from-backup <backup-name>

Always test Point-In-Time Recovery (PITR) scenarios before production outages.

Plural Stacks help enforce consistent deployment of backup tools across environments, so you don’t discover gaps in recovery during a live incident.

Follow Production Best Practices

Moving MySQL deployments from development to production on Kubernetes requires a focus on reliability, scalability, and observability. A basic StatefulSet is a solid foundation, but production workloads demand a more robust architecture. Following best practices ensures your databases remain stable, performant, and resilient under load. This means planning carefully for how applications discover and connect to databases, how traffic is managed across replicas, how you monitor system health, and how you scale to meet changing demand. Adopting these practices is key to successfully running stateful applications like MySQL on Kubernetes.

Set Up Service Discovery

Applications need a stable and reliable way to connect to MySQL databases, regardless of which node a pod is running on. Kubernetes Services provide this stable endpoint. For StatefulSets, you typically create a Headless Service to give each pod a unique DNS name (e.g., mysql-0.mysql, mysql-1.mysql). You should also create a separate ClusterIP Service that points specifically to the primary pod for write operations.

Headless Service example:

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Primary Service example (writes):

apiVersion: v1
kind: Service
metadata:
  name: mysql-primary
spec:
  type: ClusterIP
  selector:
    statefulset.kubernetes.io/pod-name: mysql-0
  ports:
  - port: 3306
    name: mysql

Replica Service example (reads):

apiVersion: v1
kind: Service
metadata:
  name: mysql-replicas
spec:
  type: ClusterIP
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Many MySQL operators automate the creation of these services, simplifying the setup process.

Configure Load Balancing

Load balancing is critical in a primary-replica MySQL architecture. A standard Service performs round-robin load balancing, which isn’t suitable for write traffic. Instead, use two distinct Services:

  • mysql-primary → directs writes to the primary
  • mysql-replicas → distributes reads across replicas

For more advanced routing, deploy ProxySQL or HAProxy. These proxies can split read/write traffic from a single endpoint, making application configuration simpler.

Example ProxySQL ConfigMap snippet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: proxysql-config
data:
  proxysql.cnf: |
    mysql_servers =
    (
      { address="mysql-0.mysql", port=3306, hostgroup=10 }, # Primary
      { address="mysql-1.mysql", port=3306, hostgroup=20 }, # Replica
      { address="mysql-2.mysql", port=3306, hostgroup=20 }  # Replica
    )
    mysql_query_rules =
    (
      { rule_id=1, active=1, match_pattern="^SELECT", destination_hostgroup=20 },
      { rule_id=2, active=1, match_pattern=".*", destination_hostgroup=10 }
    )

Integrate with Monitoring Tools

Databases in production must be observable. A common approach is to deploy the MySQL Exporter sidecar to expose Prometheus metrics.

Exporter sidecar example:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
      - name: mysql-exporter
        image: prom/mysqld-exporter
        env:
        - name: DATA_SOURCE_NAME
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: dsn

Metrics such as replication lag, query latency, and active connections can then be scraped by Prometheus and visualized in Grafana. With Plural, these dashboards can be unified across multiple clusters for centralized observability.

Develop Scaling Strategies

MySQL deployments must handle fluctuating traffic. Kubernetes supports two approaches:

  • Vertical scaling: Increase CPU/memory resources in pod specs.
  • Horizontal scaling: Add replicas for read-heavy workloads.

Scaling StatefulSet replicas (read scaling):

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  replicas: 3   # Increase read replicas
  template:
    spec:
      containers:
      - name: mysql
        resources:
          requests:
            cpu: "1"
            memory: "2Gi"
          limits:
            cpu: "2"
            memory: "4Gi"

Using GitOps with Plural CD ensures that scaling changes in your Git repository (replica count, resource requests/limits) are automatically propagated to your fleet of clusters, keeping deployments auditable and consistent.

Unified Cloud Orchestration for Kubernetes

Manage Kubernetes at scale through a single, enterprise-ready platform.

GitOps Deployment
Secure Dashboards
Infrastructure-as-Code
Book a demo

Frequently Asked Questions

Is running a database like MySQL on Kubernetes actually a good idea? Yes, it is, provided you approach it correctly. While Kubernetes was initially designed for stateless applications, its capabilities have matured significantly. Using core features like StatefulSets and Persistent Volumes provides the stable identity and durable storage that databases require. The primary benefit is creating a unified management plane for all your applications, both stateless and stateful. This simplifies operations, automates deployments, and ensures consistency, but it does require a deliberate strategy for storage, networking, and high availability.

What's the single biggest mistake to avoid when first deploying MySQL on Kubernetes? The most common and damaging mistake is using a standard Deployment instead of a StatefulSet. A Deployment is designed for stateless applications and doesn't provide the stable network identifiers or persistent storage guarantees that a database needs. If a pod managed by a Deployment restarts, it can come back with a new name and lose its connection to its data volume. A StatefulSet is purpose-built for stateful workloads like MySQL, ensuring each pod maintains a persistent identity and is always reconnected to its correct storage volume, which is essential for data integrity.

Do I really need to use a MySQL Operator, or can I manage it myself with a StatefulSet? You can certainly manage MySQL with a vanilla StatefulSet, but you'll be taking on significant operational responsibility. An Operator automates complex, day-two operations like setting up replication, handling automated failover, managing backups, and performing version upgrades. Without an Operator, these tasks become manual, scripted processes that are complex and error-prone. For any serious production deployment, an Operator is highly recommended as it acts as an automated database administrator, ensuring your cluster is resilient and properly maintained.

How should my applications connect to the database if the pod IPs can change? You should never have your applications connect directly to a pod's IP address. Instead, you use a Kubernetes Service, which provides a stable DNS endpoint that abstracts away the underlying pods. For a typical primary-replica setup, you would create two Services: one that points exclusively to the primary pod for write operations, and another that load balances across all the read replicas. This ensures your application can reliably send queries to the correct instances without needing to know their specific locations.

My database is running, but it's slow. Where should I start looking for performance issues? Start by checking the infrastructure layer before diving into query tuning. First, verify that your MySQL pods have adequate CPU and memory resources and aren't being throttled; you can check this with kubectl top pod. Next, investigate your storage performance, as slow disk I/O is a common bottleneck. Ensure your Persistent Volumes are provisioned on fast, SSD-backed storage. If those areas look healthy, then it's time to enable MySQL's slow query log to identify and optimize inefficient queries. Using a platform like Plural gives you a single dashboard to monitor resource utilization across your fleet, making it much easier to spot infrastructure-level bottlenecks.