How to Deploy MySQL with StatefulSet
Deploy a production-ready MySQL database on Kubernetes using StatefulSet. Learn persistent storage, headless services, and backup strategies.
The Problem
You need to run MySQL on Kubernetes with persistent storage, stable network identity, and ordered deployment/scaling.
The Solution
Use a StatefulSet with PersistentVolumeClaims to deploy MySQL with stable storage and predictable pod names.
Understanding StatefulSets
StatefulSets provide:
- Stable, unique network identifiers
- Stable, persistent storage
- Ordered deployment and scaling
- Ordered, automated rolling updates
Step 1: Create a Secret for MySQL Password
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
stringData:
mysql-root-password: "YourSecurePassword123!"
mysql-password: "AppPassword456!"Apply it:
kubectl apply -f mysql-secret.yamlStep 2: Create a ConfigMap for MySQL Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
default-authentication-plugin=mysql_native_password
max_connections=200
innodb_buffer_pool_size=256M
innodb_log_file_size=64M
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow.log
long_query_time=2Step 3: Create a Headless Service
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
clusterIP: None # Headless service
selector:
app: mysqlStep 4: Create the StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
terminationGracePeriodSeconds: 30
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: mysql-root-password
- name: MYSQL_DATABASE
value: "myapp"
- name: MYSQL_USER
value: "appuser"
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: mysql-password
volumeMounts:
- name: data
mountPath: /var/lib/mysql
- name: config
mountPath: /etc/mysql/conf.d
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1"
livenessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
- -u
- root
- -p${MYSQL_ROOT_PASSWORD}
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
- mysql
- -h
- localhost
- -u
- root
- -p${MYSQL_ROOT_PASSWORD}
- -e
- "SELECT 1"
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
volumes:
- name: config
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard" # Use your StorageClass
resources:
requests:
storage: 10GiStep 5: Create a Service for External Access
For applications to connect:
apiVersion: v1
kind: Service
metadata:
name: mysql-external
spec:
type: ClusterIP
ports:
- port: 3306
targetPort: 3306
selector:
app: mysqlConnecting to MySQL
From Within the Cluster
DNS name: mysql-0.mysql.<namespace>.svc.cluster.local
mysql -h mysql-0.mysql.default.svc.cluster.local -u root -pUsing kubectl
kubectl exec -it mysql-0 -- mysql -u root -pFrom Your Application
env:
- name: DATABASE_HOST
value: "mysql-0.mysql"
- name: DATABASE_PORT
value: "3306"
- name: DATABASE_NAME
value: "myapp"Backup Strategy
Manual Backup
kubectl exec mysql-0 -- mysqldump -u root -p${MYSQL_ROOT_PASSWORD} --all-databases > backup.sqlCronJob for Automated Backups
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mysql:8.0
command:
- /bin/sh
- -c
- |
mysqldump -h mysql-0.mysql -u root -p${MYSQL_ROOT_PASSWORD} \
--all-databases | gzip > /backup/mysql-$(date +%Y%m%d).sql.gz
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: mysql-root-password
volumeMounts:
- name: backup
mountPath: /backup
restartPolicy: OnFailure
volumes:
- name: backup
persistentVolumeClaim:
claimName: mysql-backup-pvcScaling Considerations
Single Instance
For simple applications, one replica is sufficient.
MySQL Replication
For high availability, consider:
- MySQL Group Replication
- Percona XtraDB Cluster
- MySQL Operator
Monitoring MySQL
Add Prometheus exporter:
- name: exporter
image: prom/mysqld-exporter:latest
ports:
- containerPort: 9104
name: metrics
env:
- name: DATA_SOURCE_NAME
value: "exporter:exporterpassword@(localhost:3306)/"Troubleshooting
Check Pod Status
kubectl get pods -l app=mysql
kubectl describe pod mysql-0View Logs
kubectl logs mysql-0Check PVC
kubectl get pvc
kubectl describe pvc data-mysql-0Access MySQL Shell
kubectl exec -it mysql-0 -- mysql -u root -pBest Practices
- Use Secrets for passwords
- Set resource limits to prevent resource starvation
- Configure backups before going to production
- Use PodDisruptionBudget for maintenance windows
- Monitor disk usage to avoid running out of space
Key Takeaways
- StatefulSets provide stable identity and storage
- Headless services enable direct pod DNS
- PersistentVolumeClaims retain data across restarts
- Always implement backup strategies
- Consider operators for production deployments
π Go Further with Kubernetes Recipes
Love this recipe? Thereβs so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.
Inside the book, youβll master:
- β Production-ready deployment strategies
- β Advanced networking and security patterns
- β Observability, monitoring, and troubleshooting
- β Real-world best practices from industry experts
βThe practical, recipe-based approach made complex Kubernetes concepts finally click for me.β
π Get Your Copy Now β Start building production-grade Kubernetes skills today!
π Get All 100+ Recipes in One Book
Stop searching β get every production-ready pattern with detailed explanations, best practices, and copy-paste YAML.
Want More Kubernetes Recipes?
This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.