comet-mysql-helm

MySQL Helm Chart

Version: 1.0.7 Type: application AppVersion: 8.4.2 Artifact Hub

A simple, standalone MySQL Helm chart

Features

Prerequisites

Installation

Add the repository to your Helm configuration:

helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo update

Then install using the repository:

# Basic installation
helm install my-mysql mysql/mysql

# With custom values
helm install my-mysql mysql/mysql \
  --set auth.rootPassword=mypassword \
  --set auth.database=mydb \
  --set primary.persistence.size=50Gi

# Using a values file
helm install my-mysql mysql/mysql -f my-values.yaml

Method 2: Local Chart

Clone the repository and install directly:

git clone https://github.com/comet-ml/comet-mysql-helm.git
cd comet-mysql-helm
helm install my-mysql ./

Using as a Dependency

To use this chart as a dependency in another Helm chart, add it to your Chart.yaml:

dependencies:
  - name: mysql
    version: "1.0.7"
    repository: "https://comet-ml.github.io/comet-mysql-helm/"
    condition: mysql.enabled

Then run:

helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm dependency update
helm install my-app .

Examples

See the examples/ directory for complete example configurations:

Advanced Features

Database Initialization

The chart supports two methods for database initialization:

Method 1: Init Scripts (initdbScripts)

SQL scripts that run automatically on first initialization (Bitnami-style):

initdbScripts:
  createdb.sql: |-
    CREATE DATABASE IF NOT EXISTS myapp
      DEFAULT CHARACTER SET utf8
      DEFAULT COLLATE utf8_general_ci;
    CREATE USER IF NOT EXISTS 'myapp'@'%' IDENTIFIED BY 'myapp_password';
    GRANT ALL ON `myapp`.* TO 'myapp'@'%';
    FLUSH PRIVILEGES;

Characteristics:

Method 2: Init Job (Helm Hook)

Flexible init job that runs after MySQL is ready:

initJob:
  enabled: true
  databases:
    - name: myapp
      username: myapp
      password: myapp_password  # or use passwordSecretRef
    - name: production
      username: produser
      passwordSecretRef:
        secretName: prod-db-secret
        secretKey: password

Characteristics:

You can use both methods together - initdbScripts run first, then initJob runs after.

initJob:
  enabled: true
  databases:
    - name: myapp
      username: myapp
      password: myapp_password  # or use passwordSecretRef
    - name: production
      username: produser
      passwordSecretRef:
        secretName: prod-db-secret
        secretKey: password

MySQL Command-Line Arguments

You can pass additional command-line arguments to MySQL in two ways:

Method 1: Using primary.extraFlags (Appends to Configuration)

The extraFlags property automatically converts command-line flags to MySQL configuration format and appends them to the existing configuration (default or custom):

primary:
  extraFlags: "--max-connections=500 --max-allowed-packet=64M --log-bin-trust-function-creators=1 --thread-stack=256K"

Benefits:

Method 2: Direct Configuration (Complete Override)

You can provide a complete MySQL configuration that replaces the default:

primary:
  configuration: |-
    [mysqld]
    authentication_policy='* ,,'
    skip-name-resolve
    port=3306
    datadir=/var/lib/mysql
    max-connections=500
    max-allowed-packet=64M
    log-bin-trust-function-creators=1
    thread-stack=256K

Use cases:

Note:

See examples/extra-args-values.yaml for a complete example.

Automated Backups

Schedule regular backups to S3-compatible storage (AWS S3, MinIO, etc.):

backup:
  enabled: true
  schedule: "0 2 * * *"  # Daily at 2 AM
  storage:
    bucket: "my-backups"
    region: "us-east-1"
    prefix: "mysql-backups"
    endpoint: ""  # Empty for AWS S3, or "http://minio:9000" for MinIO
    existingSecret: "aws-s3-credentials"  # Optional - use IAM role if empty
  retention: 7
  databases: []  # Empty = all databases

Restore from Backup

One-time restore operation:

restore:
  enabled: true
  backupFile: "mysql_backup_20250130_020000.sql.gz"
  storage:
    bucket: "my-backups"
    region: "us-east-1"
    prefix: "mysql-backups"
    existingSecret: "aws-s3-credentials"

Pod Disruption Budget

Protect MySQL from voluntary disruptions (node drains, pod evictions):

podDisruptionBudget:
  enabled: true
  minAvailable: 1  # Keep at least 1 pod available (for single replica)
  # OR
  # maxUnavailable: 0  # Prevent any disruption

Network Policy

Restrict network traffic to/from MySQL pods:

networkPolicy:
  enabled: true
  allowExternal: true  # Allow all pods in namespace
  # OR restrict to specific namespaces/pods:
  # allowExternal: false
  # allowedNamespaces:
  #   - matchLabels:
  #       name: production
  # allowedPods:
  #   - matchLabels:
  #       app: myapp

Connecting to MySQL

From Within the Cluster

mysql -h <release-name>-mysql -u root -p

Port Forward (for local development)

kubectl port-forward svc/<release-name>-mysql 3306:3306
mysql -h 127.0.0.1 -P 3306 -u root -p

Upgrading

From Helm Repository

helm repo update
helm upgrade my-mysql mysql/mysql -f values.yaml

From Local Chart

helm upgrade my-mysql ./ -f values.yaml

Migrate from Bitnami MySQL

When migrating from Bitnami MySQL chart to this custom MySQL chart:

  1. Set dataDir to Bitnami’s path in your values:
primary:
  dataDir: /bitnami/mysql/data
  1. Delete the MySQL StatefulSet before upgrading (PVCs are preserved):
kubectl delete statefulset <release-name>-mysql --cascade=orphan

This allows Helm to recreate the StatefulSet with the new chart while reusing the existing PVC and data.

Note: The volume will be mounted at /bitnami/mysql (base folder), and MySQL will use /bitnami/mysql/data as its datadir, matching Bitnami’s structure.

Uninstalling

helm uninstall my-mysql

Note: This removes all Kubernetes components but does not delete PVCs by default. To delete PVCs:

kubectl delete pvc -l app.kubernetes.io/name=mysql

Troubleshooting

Check Pod Status

kubectl get pods -l app.kubernetes.io/name=mysql
kubectl describe pod <pod-name>
kubectl logs <pod-name>

Check PVC

kubectl get pvc
kubectl describe pvc data-<release-name>-mysql-0

Connect to MySQL Pod

kubectl exec -it <pod-name> -- /bin/bash

Architecture

┌─────────────────────────────────────┐
│         Service (ClusterIP)         │
│      <release-name>-mysql:3306      │
└─────────────────┬───────────────────┘
                  │
┌─────────────────▼───────────────────┐
│         StatefulSet (Primary)        │
│      <release-name>-mysql-0         │
│  - MySQL 8.4.2 (Official Image)     │
│  - Port 3306                         │
│  - Health Checks                     │
│  - Resource Limits                   │
└─────────────────┬───────────────────┘
                  │
┌─────────────────▼───────────────────┐
│      PersistentVolumeClaim          │
│   data-<release-name>-mysql-0       │
│  - /var/lib/mysql                    │
│  - Size: 20Gi (default)              │
│  - Storage Class: configurable       │
└─────────────────────────────────────┘

Values

Key Type Default Description
architecture.mode string "standalone"  
auth.database string "my_database"  
auth.existingSecret string ""  
auth.password string ""  
auth.rootPassword string "changeme"  
auth.username string ""  
backup.databases list []  
backup.enabled bool false  
backup.nodeSelector object {}  
backup.resources.limits.cpu string "500m"  
backup.resources.limits.memory string "512Mi"  
backup.resources.requests.cpu string "250m"  
backup.resources.requests.memory string "256Mi"  
backup.retention int 7  
backup.schedule string "0 2 * * *"  
backup.serviceAccountName string ""  
backup.storage.bucket string ""  
backup.storage.endpoint string ""  
backup.storage.existingSecret string ""  
backup.storage.prefix string "mysql-backups"  
backup.storage.region string "us-east-1"  
backup.tolerations list []  
containerSecurityContext.enabled bool true  
containerSecurityContext.runAsNonRoot bool false  
containerSecurityContext.runAsUser string ""  
fullnameOverride string ""  
global.commonLabels object {}  
global.imageRegistry string ""  
global.storageClass string ""  
image.pullPolicy string "IfNotPresent"  
image.pullSecrets list []  
image.registry string "docker.io"  
image.repository string "mysql"  
image.tag string "8.4.2"  
initJob.annotations.”helm.sh/hook” string "post-install,post-upgrade"  
initJob.annotations.”helm.sh/hook-delete-policy” string "before-hook-creation,hook-succeeded"  
initJob.annotations.”helm.sh/hook-weight” string "5"  
initJob.databases list []  
initJob.enabled bool false  
initJob.resources.limits.cpu string "500m"  
initJob.resources.limits.memory string "256Mi"  
initJob.resources.requests.cpu string "100m"  
initJob.resources.requests.memory string "128Mi"  
initdbScripts object {}  
metrics.containerPort int 9104  
metrics.enabled bool false  
metrics.extraArgs list []  
metrics.image.pullPolicy string "IfNotPresent"  
metrics.image.registry string "docker.io"  
metrics.image.repository string "prom/mysqld-exporter"  
metrics.image.tag string "v0.16.0"  
metrics.prometheusRule.enabled bool false  
metrics.prometheusRule.labels object {}  
metrics.prometheusRule.namespace string ""  
metrics.prometheusRule.rules list []  
metrics.resources.limits.cpu string "100m"  
metrics.resources.limits.memory string "128Mi"  
metrics.resources.requests.cpu string "50m"  
metrics.resources.requests.memory string "64Mi"  
metrics.serviceMonitor.annotations object {}  
metrics.serviceMonitor.enabled bool false  
metrics.serviceMonitor.interval string "30s"  
metrics.serviceMonitor.labels object {}  
metrics.serviceMonitor.metricRelabelings list []  
metrics.serviceMonitor.namespace string ""  
metrics.serviceMonitor.relabelings list []  
metrics.serviceMonitor.scrapeTimeout string "10s"  
nameOverride string ""  
networkPolicy.allowExternal bool false  
networkPolicy.allowedNamespaces list []  
networkPolicy.allowedPods list []  
networkPolicy.enabled bool false  
networkPolicy.extraEgress list []  
networkPolicy.extraIngress list []  
podDisruptionBudget.enabled bool false  
podDisruptionBudget.maxUnavailable int 1  
podDisruptionBudget.minAvailable string ""  
podSecurityContext.enabled bool true  
podSecurityContext.fsGroup int 999  
primary.affinity object {}  
primary.configuration string "[mysqld]\nauthentication_policy='* ,,'\nskip-name-resolve\nexplicit_defaults_for_timestamp\nport=3306\ndatadir=/var/lib/mysql\nsocket=/var/run/mysqld/mysqld.sock\npid-file=/var/run/mysqld/mysqld.pid\nmax_allowed_packet=16M\nbind-address=0.0.0.0\ncharacter-set-server=utf8mb4\ncollation-server=utf8mb4_unicode_ci\nslow_query_log=0\nlong_query_time=10.0"  
primary.dataDir string "/var/lib/mysql"  
primary.existingConfigmap string ""  
primary.extraEnvVars list []  
primary.extraFlags string ""  
primary.nodeSelector object {}  
primary.persistence.accessModes[0] string "ReadWriteOnce"  
primary.persistence.annotations object {}  
primary.persistence.enabled bool true  
primary.persistence.existingClaim string ""  
primary.persistence.selector object {}  
primary.persistence.size string "20Gi"  
primary.persistence.storageClass string ""  
primary.podAnnotations object {}  
primary.podLabels object {}  
primary.resources.limits.cpu string "2000m"  
primary.resources.limits.memory string "2Gi"  
primary.resources.requests.cpu string "500m"  
primary.resources.requests.memory string "512Mi"  
primary.tolerations list []  
restore.backupFile string ""  
restore.enabled bool false  
restore.nodeSelector object {}  
restore.resources.limits.cpu string "1000m"  
restore.resources.limits.memory string "1Gi"  
restore.resources.requests.cpu string "500m"  
restore.resources.requests.memory string "512Mi"  
restore.serviceAccountName string ""  
restore.storage.bucket string ""  
restore.storage.endpoint string ""  
restore.storage.existingSecret string ""  
restore.storage.prefix string "mysql-backups"  
restore.storage.region string "us-east-1"  
restore.tolerations list []  
service.annotations object {}  
service.clusterIP string ""  
service.loadBalancerIP string ""  
service.loadBalancerSourceRanges list []  
service.nodePort string ""  
service.port int 3306  
service.type string "ClusterIP"  
serviceAccount.annotations object {}  
serviceAccount.create bool true  
serviceAccount.name string ""  
tls.certCAFilename string "ca.crt"  
tls.certFilename string "tls.crt"  
tls.certKeyFilename string "tls.key"  
tls.enabled bool false  
tls.existingSecret string ""  
tls.requireSecureTransport bool false  

License

This chart is provided as-is.


Autogenerated from chart metadata using helm-docs v1.14.2