Skip to content

comet-ml/comet-mysql-helm

Repository files navigation

MySQL Helm Chart

Version: 1.0.6 Type: application AppVersion: 8.4.2 Artifact Hub

A simple, standalone MySQL Helm chart

Features

  • Official MySQL Images: Uses docker.io/mysql:8.4.2
  • Standalone Mode: Simple single-instance deployment
  • Persistent Storage: StatefulSet with PVC support (default 20Gi)
  • Configurable: Customizable MySQL configuration
  • Secure: Kubernetes secrets for passwords
  • Production Ready: Resource limits, health checks, security contexts
  • Database Initialization: Automated database and user creation (optional)
  • Automated Backups: Scheduled backups to S3-compatible storage (optional)
  • Restore Operations: One-time restore from S3-compatible storage (optional)
  • Global Labels: Common labels support for all resources

Prerequisites

  • Kubernetes 1.19+
  • Helm 3.0+
  • PV provisioner support (if persistence is enabled)

Installation

Method 1: Helm Repository (Recommended)

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.6"
    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:

  • Basic: simple-values.yaml
  • Persistence: existing-pvc-values.yaml
  • Configuration: extra-args-values.yaml (command-line arguments)
  • Initialization: initdb-scripts-values.yaml (SQL scripts), init-job-mixed-values.yaml, init-job-secretref-values.yaml
  • Availability: pdb-values.yaml (Pod Disruption Budget)
  • Security: network-policy-values.yaml (Network Policy), tls-values.yaml (TLS/SSL)
  • Backups: backup-aws-s3-values.yaml, backup-aws-iam-role-values.yaml, backup-minio-values.yaml
  • Restore: restore-aws-s3-values.yaml, restore-aws-iam-role-values.yaml, restore-minio-values.yaml
  • Complete Setup: complete-setup-values.yaml
  • Labels: common-labels-values.yaml

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:

  • Runs automatically on first initialization only
  • Scripts execute in alphabetical order
  • Mounted to /docker-entrypoint-initdb.d (MySQL standard)
  • Only runs when data directory is empty

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:

  • Runs as Helm post-install/post-upgrade hook
  • Supports both plain text passwords and secret references
  • Runs every time (can be idempotent with IF NOT EXISTS)
  • More flexible for dynamic configurations

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:

  • Flags are automatically converted to my.cnf format
  • Appends to the default configuration (or your custom primary.configuration)

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:

  • Complete control over the MySQL configuration
  • When you need to override the entire default configuration
  • Custom configurations that differ significantly from defaults

Note:

  • primary.extraFlags appends flags to the existing configuration (default or custom)
  • primary.configuration completely replaces the default configuration
  • You can use both together: primary.configuration provides the base config, and primary.extraFlags adds additional flags on top

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 {}
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

About

Comet helm chart for mysql

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages