Skip to main content

Command Palette

Search for a command to run...

Day 66: MySQL on Kubernetes - Complete Database Deployment with Secrets & Persistent Storage 🗄️

Published
14 min read
Day 66: MySQL on Kubernetes - Complete Database Deployment with Secrets & Persistent Storage 🗄️

Welcome back! 👋 Day 66 of the 100 Days Cloud DevOps Challenge, and today we're deploying MySQL database on Kubernetes! This is complete production database setup - persistent storage, secrets management, and external access. Let's deploy! 🎯

🎯 The Mission - Production MySQL Deployment

📋 TASK TICKET #DEV-8066 - MySQL Database on Kubernetes
Priority: CRITICAL | Type: Database Infrastructure
Server: Jump Host | Cluster: Kubernetes

REQUIREMENT:
- Deploy MySQL database server
- Persistent data storage
- Secure credential management
- External access capability
- Production-ready configuration

SPECIFICATIONS:
1. PersistentVolume: mysql-pv
   - Capacity: 250Mi

2. PersistentVolumeClaim: mysql-pv-claim
   - Request: 250Mi

3. Deployment: mysql-deployment
   - Image: MySQL (any version)
   - Mount: PV at /var/lib/mysql

4. Service: mysql
   - Type: NodePort
   - NodePort: 30007

5. Secrets (3 total):
   a. mysql-root-pass
      - password: YUIidhb667
   b. mysql-user-pass
      - username: kodekloud_gem
      - password: B4zNgHA7Ya
   c. mysql-db-url
      - database: kodekloud_db5

6. Environment Variables:
   - MYSQL_ROOT_PASSWORD (from mysql-root-pass)
   - MYSQL_DATABASE (from mysql-db-url)
   - MYSQL_USER (from mysql-user-pass)
   - MYSQL_PASSWORD (from mysql-user-pass)

SUCCESS CRITERIA:
- All resources created
- MySQL running with persistent storage
- Secrets properly configured
- Database accessible via NodePort

This is production database infrastructure! 🏗️

🤔 Why This Architecture?

Complete Database Stack:

✅ Production Requirements:
- Persistent Storage (survive restarts)
- Secrets Management (secure credentials)
- External Access (NodePort service)
- Environment Config (via secrets)
- Data Safety (PersistentVolume)

Architecture:
External Client (Port 30007)
        ↓
NodePort Service (mysql)
        ↓
Deployment (mysql-deployment)
        ↓
Pod (MySQL container)
    ├─ Secrets (credentials)
    └─ PV (data storage)

🛠️ Complete Implementation

Step 1: Access Jump Host

# SSH to jump host
ssh user@jump_host

# Verify kubectl
kubectl version --short
kubectl get nodes

Environment ready!

Step 2: Create Secrets

# Secret 1: mysql-root-pass
kubectl create secret generic mysql-root-pass \
  --from-literal=password=YUIidhb667

# Secret 2: mysql-user-pass
kubectl create secret generic mysql-user-pass \
  --from-literal=username=kodekloud_gem \
  --from-literal=password=B4zNgHA7Ya

# Secret 3: mysql-db-url
kubectl create secret generic mysql-db-url \
  --from-literal=database=kodekloud_db5

Expected output:

secret/mysql-root-pass created
secret/mysql-user-pass created
secret/mysql-db-url created

Secrets created! 🔐

Verify secrets:

# List secrets
kubectl get secrets | grep mysql

Expected output:

mysql-db-url       Opaque   1      30s
mysql-root-pass    Opaque   1      30s
mysql-user-pass    Opaque   2      30s

All secrets exist!

Step 3: Create PersistentVolume

# Create PersistentVolume YAML
cat > mysql-pv.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 250Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /mnt/data/mysql
  persistentVolumeReclaimPolicy: Retain
EOF

# Apply PV
kubectl apply -f mysql-pv.yaml

Expected output:

persistentvolume/mysql-pv created

Verify PV:

kubectl get pv mysql-pv

Expected output:

NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      STORAGECLASS   AGE
mysql-pv   250Mi      RWO            Retain           Available                  10s

PV created!

Step 4: Create PersistentVolumeClaim

# Create PVC YAML
cat > mysql-pvc.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 250Mi
EOF

# Apply PVC
kubectl apply -f mysql-pvc.yaml

Expected output:

persistentvolumeclaim/mysql-pv-claim created

Verify PVC:

kubectl get pvc mysql-pv-claim

Expected output:

NAME             STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-pv-claim   Bound    mysql-pv   250Mi      RWO                           10s

PVC bound to PV!

Step 5: Create MySQL Deployment

# Create Deployment YAML
cat > mysql-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deployment
  labels:
    app: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        ports:
        - containerPort: 3306
          name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-root-pass
              key: password
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              name: mysql-db-url
              key: database
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: mysql-user-pass
              key: username
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-user-pass
              key: password
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim
EOF

# Apply Deployment
kubectl apply -f mysql-deployment.yaml

Expected output:

deployment.apps/mysql-deployment created

Deployment created! 🎉

Watch deployment:

kubectl get pods -l app=mysql --watch

Expected progression:

NAME                                READY   STATUS              RESTARTS   AGE
mysql-deployment-7d6c9f8b9c-x7k2m   0/1     Pending             0          0s
mysql-deployment-7d6c9f8b9c-x7k2m   0/1     ContainerCreating   0          2s
mysql-deployment-7d6c9f8b9c-x7k2m   1/1     Running             0          30s

Press Ctrl+C when Running.

Step 6: Create NodePort Service

# Create Service YAML
cat > mysql-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: NodePort
  selector:
    app: mysql
  ports:
  - protocol: TCP
    port: 3306
    targetPort: 3306
    nodePort: 30007
EOF

# Apply Service
kubectl apply -f mysql-service.yaml

Expected output:

service/mysql created

Service created! 🎉

Verify service:

kubectl get service mysql

Expected output:

NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
mysql   NodePort   10.96.100.50    <none>        3306:30007/TCP   10s

Service exposing MySQL!

Step 7: Verify Complete Deployment

# Check all resources
kubectl get deployment mysql-deployment
kubectl get pods -l app=mysql
kubectl get service mysql
kubectl get pvc mysql-pv-claim
kubectl get pv mysql-pv
kubectl get secrets | grep mysql

Expected output:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
mysql-deployment   1/1     1            1           2m

NAME                                READY   STATUS    RESTARTS   AGE
mysql-deployment-7d6c9f8b9c-x7k2m   1/1     Running   0          2m

NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
mysql   NodePort   10.96.100.50    <none>        3306:30007/TCP   1m

NAME             STATUS   VOLUME     CAPACITY   ACCESS MODES   AGE
mysql-pv-claim   Bound    mysql-pv   250Mi      RWO            3m

NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   AGE
mysql-pv   250Mi      RWO            Retain           Bound    4m

mysql-db-url       Opaque   1      5m
mysql-root-pass    Opaque   1      5m
mysql-user-pass    Opaque   2      5m

All resources created!

Step 8: Verify MySQL Functionality

# Check MySQL logs
kubectl logs -l app=mysql --tail=20

Expected output:

2025-01-11T10:00:00.123456Z 0 [Note] mysqld: ready for connections.
Version: '5.7.44'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)

MySQL ready!

Test MySQL connection:

# Get pod name
POD_NAME=$(kubectl get pod -l app=mysql -o jsonpath='{.items[0].metadata.name}')

# Test root login
kubectl exec -it $POD_NAME -- mysql -u root -pYUIidhb667 -e "SHOW DATABASES;"

Expected output:

+--------------------+
| Database           |
+--------------------+
| information_schema |
| kodekloud_db5      |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

Database created!

Test user login:

# Test user credentials
kubectl exec -it $POD_NAME -- mysql -u kodekloud_gem -pB4zNgHA7Ya -e "SELECT DATABASE();"

Expected output:

+------------+
| DATABASE() |
+------------+
| NULL       |
+------------+

User authenticated!

Verify environment variables:

# Check env vars in pod
kubectl exec $POD_NAME -- env | grep MYSQL

Expected output:

MYSQL_ROOT_PASSWORD=YUIidhb667
MYSQL_DATABASE=kodekloud_db5
MYSQL_USER=kodekloud_gem
MYSQL_PASSWORD=B4zNgHA7Ya

All environment variables set!

Test database access:

# Use the database
kubectl exec -it $POD_NAME -- mysql -u kodekloud_gem -pB4zNgHA7Ya kodekloud_db5 -e "CREATE TABLE test (id INT); INSERT INTO test VALUES (1); SELECT * FROM test;"

Expected output:

+------+
| id   |
+------+
|    1 |
+------+

Database fully functional!

Verify persistent storage:

# Check mounted volume
kubectl exec $POD_NAME -- df -h /var/lib/mysql

Expected output:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        20G  500M   19G   3% /var/lib/mysql

Check data directory:

kubectl exec $POD_NAME -- ls -lh /var/lib/mysql/

Expected output:

total 188M
-rw-r----- 1 mysql mysql   56 Jan 11 10:00 auto.cnf
-rw------- 1 mysql mysql 1.7K Jan 11 10:00 ca-key.pem
-rw-r--r-- 1 mysql mysql 1.1K Jan 11 10:00 ca.pem
drwxr-x--- 2 mysql mysql 4.0K Jan 11 10:00 kodekloud_db5
-rw-r----- 1 mysql mysql  56M Jan 11 10:00 ib_logfile0
-rw-r----- 1 mysql mysql  56M Jan 11 10:00 ib_logfile1
-rw-r----- 1 mysql mysql  76M Jan 11 10:00 ibdata1
drwxr-x--- 2 mysql mysql 4.0K Jan 11 10:00 mysql
drwxr-x--- 2 mysql mysql 4.0K Jan 11 10:00 performance_schema
drwxr-x--- 2 mysql mysql 4.0K Jan 11 10:00 sys

Data persisted!

Step 9: Test External Access

# Get node IP
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
echo "MySQL accessible at: $NODE_IP:30007"

# Test connection from jump host (if mysql client installed)
mysql -h $NODE_IP -P 30007 -u kodekloud_gem -pB4zNgHA7Ya -e "SHOW DATABASES;"

If mysql client not installed:

# Test with pod
kubectl run mysql-client --image=mysql:5.7 --rm -it --restart=Never -- mysql -h $NODE_IP -P 30007 -u kodekloud_gem -pB4zNgHA7Ya -e "SHOW DATABASES;"

External access working!

Step 10: Complete Verification

# Comprehensive verification script
cat > verify-mysql.sh << 'EOF'
#!/bin/bash
echo "=== MySQL Kubernetes Deployment Verification ==="
echo ""
echo "1. Secrets Status:"
kubectl get secrets | grep mysql | awk '{print "   " $1 ": " $2 " (" $3 " keys)"}'
echo ""
echo "2. PersistentVolume Status:"
kubectl get pv mysql-pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,STATUS:.status.phase,CLAIM:.spec.claimRef.name
echo ""
echo "3. PersistentVolumeClaim Status:"
kubectl get pvc mysql-pv-claim -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,VOLUME:.spec.volumeName,CAPACITY:.status.capacity.storage
echo ""
echo "4. Deployment Status:"
kubectl get deployment mysql-deployment
echo ""
echo "5. Pod Status:"
kubectl get pods -l app=mysql
echo ""
echo "6. Service Status:"
kubectl get service mysql
echo ""
echo "7. Environment Variables Check:"
POD_NAME=$(kubectl get pod -l app=mysql -o jsonpath='{.items[0].metadata.name}')
echo "   Pod: $POD_NAME"
kubectl exec $POD_NAME -- env | grep MYSQL_ | sed 's/^/   /'
echo ""
echo "8. Database Verification:"
DB_CHECK=$(kubectl exec $POD_NAME -- mysql -u root -pYUIidhb667 -e "SHOW DATABASES;" 2>/dev/null | grep kodekloud_db5)
if [ -n "$DB_CHECK" ]; then
    echo "   ✓ Database 'kodekloud_db5' exists"
else
    echo "   ✗ Database 'kodekloud_db5' not found"
fi
echo ""
echo "9. User Authentication:"
USER_CHECK=$(kubectl exec $POD_NAME -- mysql -u kodekloud_gem -pB4zNgHA7Ya -e "SELECT 1;" 2>/dev/null)
if [ -n "$USER_CHECK" ]; then
    echo "   ✓ User 'kodekloud_gem' can authenticate"
else
    echo "   ✗ User authentication failed"
fi
echo ""
echo "10. Persistent Storage:"
MOUNT_CHECK=$(kubectl exec $POD_NAME -- df -h /var/lib/mysql | tail -1 | awk '{print $6}')
if [ "$MOUNT_CHECK" = "/var/lib/mysql" ]; then
    echo "   ✓ PersistentVolume mounted at /var/lib/mysql"
else
    echo "   ✗ Volume mount issue"
fi
echo ""
echo "11. Service Connectivity:"
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
echo "   External Access: mysql -h $NODE_IP -P 30007 -u kodekloud_gem -pB4zNgHA7Ya"
echo ""
echo "12. Verification Summary:"
DEPLOY_READY=$(kubectl get deployment mysql-deployment -o jsonpath='{.status.readyReplicas}')
POD_STATUS=$(kubectl get pod -l app=mysql -o jsonpath='{.items[0].status.phase}')
SVC_PORT=$(kubectl get service mysql -o jsonpath='{.spec.ports[0].nodePort}')
PVC_STATUS=$(kubectl get pvc mysql-pv-claim -o jsonpath='{.status.phase}')

if [ "$DEPLOY_READY" = "1" ] && [ "$POD_STATUS" = "Running" ] && [ "$SVC_PORT" = "30007" ] && [ "$PVC_STATUS" = "Bound" ]; then
    echo "    ✓ Deployment ready (1/1)"
    echo "    ✓ Pod running successfully"
    echo "    ✓ Service exposed on NodePort 30007"
    echo "    ✓ PersistentVolume bound"
    echo "    ✓ All secrets configured"
    echo "    ✓ Database accessible"
    echo "    ✓ ALL CHECKS PASSED"
else
    echo "    ✗ Some checks failed"
fi
EOF

chmod +x verify-mysql.sh
./verify-mysql.sh

Expected output:

=== MySQL Kubernetes Deployment Verification ===

1. Secrets Status:
   mysql-db-url: Opaque (1 keys)
   mysql-root-pass: Opaque (1 keys)
   mysql-user-pass: Opaque (2 keys)

2. PersistentVolume Status:
NAME       CAPACITY   STATUS   CLAIM
mysql-pv   250Mi      Bound    mysql-pv-claim

3. PersistentVolumeClaim Status:
NAME             STATUS   VOLUME     CAPACITY
mysql-pv-claim   Bound    mysql-pv   250Mi

4. Deployment Status:
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
mysql-deployment   1/1     1            1           5m

5. Pod Status:
NAME                                READY   STATUS    RESTARTS   AGE
mysql-deployment-7d6c9f8b9c-x7k2m   1/1     Running   0          5m

6. Service Status:
NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
mysql   NodePort   10.96.100.50    <none>        3306:30007/TCP   4m

7. Environment Variables Check:
   Pod: mysql-deployment-7d6c9f8b9c-x7k2m
   MYSQL_ROOT_PASSWORD=YUIidhb667
   MYSQL_DATABASE=kodekloud_db5
   MYSQL_USER=kodekloud_gem
   MYSQL_PASSWORD=B4zNgHA7Ya

8. Database Verification:
   ✓ Database 'kodekloud_db5' exists

9. User Authentication:
   ✓ User 'kodekloud_gem' can authenticate

10. Persistent Storage:
   ✓ PersistentVolume mounted at /var/lib/mysql

11. Service Connectivity:
   External Access: mysql -h 172.17.0.2 -P 30007 -u kodekloud_gem -pB4zNgHA7Ya

12. Verification Summary:
    ✓ Deployment ready (1/1)
    ✓ Pod running successfully
    ✓ Service exposed on NodePort 30007
    ✓ PersistentVolume bound
    ✓ All secrets configured
    ✓ Database accessible
    ✓ ALL CHECKS PASSED

All systems operational! 🎊

🔍 Understanding the Complete Architecture

Component Interaction

Complete MySQL Stack:

External Client
      ↓ Port 30007
NodePort Service (mysql)
      ↓ Port 3306
Deployment (mysql-deployment)
      ↓
Pod (MySQL 5.7)
  ├─ Secrets (Environment Variables)
  │   ├─ MYSQL_ROOT_PASSWORD → mysql-root-pass
  │   ├─ MYSQL_DATABASE → mysql-db-url
  │   ├─ MYSQL_USER → mysql-user-pass
  │   └─ MYSQL_PASSWORD → mysql-user-pass
  │
  └─ PersistentVolume (Data Storage)
      ├─ PVC: mysql-pv-claim (250Mi)
      ├─ PV: mysql-pv (250Mi)
      └─ Mount: /var/lib/mysql

Secret Management

Three secrets with specific purposes:

# Secret 1: Root password (admin access)
mysql-root-pass:
  password: YUIidhb667

# Secret 2: Application user (app access)
mysql-user-pass:
  username: kodekloud_gem
  password: B4zNgHA7Ya

# Secret 3: Database name (config)
mysql-db-url:
  database: kodekloud_db5

Environment variable injection:

env:
- name: MYSQL_ROOT_PASSWORD
  valueFrom:
    secretKeyRef:
      name: mysql-root-pass   # Secret name
      key: password           # Key within secret

# MySQL reads env vars on startup:
# 1. Creates root user with MYSQL_ROOT_PASSWORD
# 2. Creates database MYSQL_DATABASE
# 3. Creates user MYSQL_USER
# 4. Grants permissions with MYSQL_PASSWORD

Storage Architecture

PersistentVolume flow:

1. PersistentVolume (mysql-pv)
   - Capacity: 250Mi
   - Storage class: (default)
   - Path: /mnt/data/mysql (hostPath)
   - Status: Available → Bound

2. PersistentVolumeClaim (mysql-pv-claim)
   - Requests: 250Mi
   - Access mode: ReadWriteOnce
   - Binds to: mysql-pv
   - Status: Bound

3. Pod Volume Mount
   - PVC: mysql-pv-claim
   - Mount path: /var/lib/mysql
   - MySQL data files stored here

Data persistence:
Pod restart → Data survives ✓
Pod delete/recreate → Data survives ✓
Node reboot → Data survives ✓ (if PV uses network storage)

Service Exposure

NodePort service:

Service: mysql
Type: NodePort
Selector: app=mysql

Port mapping:
- ClusterIP: 10.96.100.50:3306 (internal)
- NodePort: <any-node-ip>:30007 (external)
- TargetPort: 3306 (container)

Access methods:
1. From within cluster:
   mysql.default.svc.cluster.local:3306

2. From external (any node):
   <node-ip>:30007

3. From pods:
   mysql:3306

💡 Key Takeaways

PersistentVolume ensures data survives pod restarts
Secrets securely store database credentials
Environment variables configure MySQL at startup
NodePort enables external database access
Deployment manages pod lifecycle
Volume binding connects PV to PVC to Pod
Complete stack production-ready database
Security credentials never in plain text

🎓 Quick Interview Questions

Q: Why use PersistentVolume instead of emptyDir for MySQL? A: emptyDir data lost on pod restart/delete. MySQL database needs persistence. PersistentVolume survives pod lifecycle, stores data permanently, enables database backups, disaster recovery.

Q: What's difference between PV and PVC? A: PV is storage resource (admin creates, cluster-wide). PVC is storage request (user creates, namespace-scoped). PVC binds to PV. Like: PV=available apartment, PVC=rental agreement.

Q: Why separate secrets for root and user passwords? A: Principle of least privilege. Root for admin tasks (rarely used). User for application (regular access). Separate secrets enable different RBAC policies, rotation schedules, audit trails.

Q: How does MySQL know to create database and user on startup? A: MySQL official image checks environment variables on first run: MYSQL_DATABASE creates DB, MYSQL_USER + MYSQL_PASSWORD creates user, MYSQL_ROOT_PASSWORD sets root password. Only runs if /var/lib/mysql empty.

Q: What happens if PVC requests more storage than PV provides? A: PVC stays Pending (can't bind). PV must have ≥ requested capacity. Solution: increase PV size or reduce PVC request. Dynamic provisioning auto-creates matching PV.

🚀 Production Enhancements

1. StatefulSet for HA

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    # ... pod spec
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

2. Add Resource Limits

resources:
  requests:
    memory: "1Gi"
    cpu: "500m"
  limits:
    memory: "2Gi"
    cpu: "1000m"

3. Health Checks

livenessProbe:
  exec:
    command:
    - mysqladmin
    - ping
    - -h
    - localhost
  initialDelaySeconds: 30
  periodSeconds: 10
readinessProbe:
  exec:
    command:
    - mysql
    - -h
    - localhost
    - -u
    - root
    - -p$(MYSQL_ROOT_PASSWORD)
    - -e
    - "SELECT 1"
  initialDelaySeconds: 5
  periodSeconds: 5

4. ConfigMap for MySQL Configuration

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

5. Backup CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-backup
spec:
  schedule: "0 2 * * *"  # 2 AM daily
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: mysql:5.7
            command:
            - /bin/sh
            - -c
            - mysqldump -h mysql -u root -p$MYSQL_ROOT_PASSWORD --all-databases > /backup/backup-$(date +%Y%m%d).sql
            env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-root-pass
                  key: password
            volumeMounts:
            - name: backup
              mountPath: /backup
          volumes:
          - name: backup
            persistentVolumeClaim:
              claimName: backup-pvc
          restartPolicy: OnFailure

6. StorageClass for Dynamic Provisioning

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: mysql-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  fsType: ext4

🔧 Troubleshooting

Issue 1: PVC Pending

# Check PVC status
kubectl describe pvc mysql-pv-claim

# Common causes:
# - No PV available with sufficient capacity
# - Access mode mismatch
# - StorageClass mismatch

# Solution: Check PV
kubectl get pv
kubectl describe pv mysql-pv

Issue 2: MySQL Won't Start

# Check pod logs
kubectl logs -l app=mysql

# Common errors:
# - Permission denied on /var/lib/mysql
# - Invalid root password
# - Port already in use

# Check events
kubectl describe pod -l app=mysql

Issue 3: Can't Connect Externally

# Verify service
kubectl get service mysql

# Check NodePort
kubectl describe service mysql | grep NodePort

# Test connectivity
telnet <node-ip> 30007

# Check firewall
sudo firewall-cmd --list-ports
sudo firewall-cmd --add-port=30007/tcp --permanent

Issue 4: Data Not Persisting

# Check volume mount
kubectl exec -l app=mysql -- df -h /var/lib/mysql

# Verify PVC bound
kubectl get pvc mysql-pv-claim

# Check PV
kubectl get pv mysql-pv

📊 MySQL Monitoring

Check MySQL status:

# MySQL version
kubectl exec -l app=mysql -- mysql --version

# Database size
kubectl exec -l app=mysql -- mysql -u root -pYUIidhb667 -e "SELECT table_schema AS 'Database', ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)' FROM information_schema.tables GROUP BY table_schema;"

#Active connections
kubectl exec -l app=mysql -- mysql -u root -pYUIidhb667 -e "SHOW PROCESSLIST;"
Server status
kubectl exec -l app=mysql -- mysql -u root -pYUIidhb667 -e "SHOW STATUS LIKE '%thread%';"

🎉 Final Thoughts

You've successfully deployed a complete MySQL database on Kubernetes! This is production-grade infrastructure.

What you accomplished: ✅ Created 3 secrets for credential management
✅ Set up persistent storage with PV/PVC
✅ Deployed MySQL with environment variables
✅ Exposed database via NodePort service
✅ Verified full database functionality

Real-world impact:

  • Data Persistence: Survives pod restarts

  • Security: Credentials in secrets, not code

  • Accessibility: External access via NodePort

  • Production Ready: Complete database stack

  • Scalability: Foundation for StatefulSet HA

Skills acquired:

  • PersistentVolume/PVC management

  • Secrets creation and injection

  • Database deployment patterns

  • Service exposure strategies

  • Complete stack orchestration

This is production database infrastructure! 💪


Day: 66/100
Challenge: KodeKloud Cloud DevOps
Date: January 11, 2025
Topic: MySQL on Kubernetes

How do you manage databases in Kubernetes? Share your data persistence strategies! 🗄️

More from this blog

🚀 DevOps Challenge- KodeKloud Solutions

73 posts