diff --git a/charts/mysql-cluster/Chart.yaml b/charts/mysql-cluster/Chart.yaml new file mode 100644 index 0000000..d181751 --- /dev/null +++ b/charts/mysql-cluster/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: mysql-cluster +version: 0.1.0 +description: Chart for a mysql cluster +keywords: + - database + - mysql +sources: + - https://dev.mysql.com/ + - https://github.com/mysql/mysql-operator + - https://github.com/mysql/mysql-operator/tree/trunk/helm/mysql-innodbcluster +maintainers: + - name: alexlebens +icon: https://avatars.githubusercontent.com/u/2452804?s=48&v=4 +appVersion: 8.3.0-2.1.2 diff --git a/charts/mysql-cluster/README.md b/charts/mysql-cluster/README.md new file mode 100644 index 0000000..6fd86e7 --- /dev/null +++ b/charts/mysql-cluster/README.md @@ -0,0 +1,17 @@ +## Introduction + +[MySQL Operator](https://dev.mysql.com/doc/mysql-operator/en/) + +MySQL Operator for Kubernetes manages MySQL InnoDB Cluster setups inside a Kubernetes Cluster. MySQL Operator for Kubernetes manages the full lifecycle with setup and maintenance including automating upgrades and backups. + +This chart bootstraps a [MySQL InnoDB](https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-innodbcluster.html) cluster on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Kubernetes +- Helm +- MySQL Operator + +## Parameters + +See the [values files](values.yaml). diff --git a/charts/mysql-cluster/templates/_backup.tpl b/charts/mysql-cluster/templates/_backup.tpl new file mode 100644 index 0000000..4360e6b --- /dev/null +++ b/charts/mysql-cluster/templates/_backup.tpl @@ -0,0 +1,72 @@ +{{- define "cluster.backup" -}} + +{{- if and .Values.backup.enabled .Values.backup.profiles }} +backupProfiles: +{{- $isDumpInstance := false }} +{{- $isSnapshot := false }} +{{- range $_, $profile := .Values.backup.profiles }} + - name: {{ $profile.name | quote }} + {{- if hasKey $profile "podAnnotations" }} + podAnnotations: + {{ toYaml $profile.podAnnotations | nindent 6 }} + {{- end }} + {{- if hasKey $profile "podLabels" }} + podLabels: + {{ toYaml $profile.podLabels | nindent 6 }} + {{- end }} + + {{- $isDumpInstance = hasKey $profile "dumpInstance" }} + {{- $isSnapshot = hasKey $profile "snapshot" }} + {{- if or $isDumpInstance $isSnapshot }} + + {{- $backupProfile := ternary $profile.dumpInstance $profile.snapshot $isDumpInstance }} + {{- if $isDumpInstance }} + dumpInstance: + {{- else if $isSnapshot }} + snapshot: + {{- else }} + {{- fail "Unsupported or unspecified backup type, must be either snapshot or dumpInstance" }} + {{ end }} + + {{- if not (hasKey $backupProfile "storage") }} + {{- fail "backup profile $profile.name has no storage section" }} + {{- else if hasKey $backupProfile.storage "s3" }} + storage: + s3: + {{- if $backupProfile.storage.s3.prefix }} + prefix: {{ $backupProfile.storage.s3.prefix }} + {{- end }} + bucketName: {{ required "bucketName is required" $backupProfile.storage.s3.bucketName }} + config: {{ required "config is required" $backupProfile.storage.s3.config }} + {{- if $backupProfile.storage.s3.profile }} + profile: {{ $backupProfile.storage.s3.profile }} + {{- end }} + {{- if $backupProfile.storage.s3.endpoint }} + endpoint: {{ $backupProfile.storage.s3.endpoint }} + {{- end }} + {{- else if hasKey $backupProfile.storage "persistentVolumeClaim" }} + storage: + persistentVolumeClaim: {{ toYaml $backupProfile.storage.persistentVolumeClaim | nindent 12}} + {{- else -}} + {{- fail "Backup profile $profile.name has empty storage section - neither s3 nor persistentVolumeClaim defined" }} + {{- end -}} + + {{- end }} +{{- end }} +{{- end }} + +{{- if .Values.backupSchedules }} +backupSchedules: +{{- range $_, $schedule := .Values.backup.schedules }} + - name: {{ $schedule.name | quote }} + enabled: {{ $schedule.enabled }} + schedule: {{ quote $schedule.schedule }} + {{- if ($schedule).timeZone }} + timeZone: {{ quote $schedule.timeZone }} + {{- end }} + deleteBackupData: {{ $schedule.deleteBackupData }} + backupProfileName: {{ $schedule.backupProfileName }} +{{- end }} +{{- end }} + +{{- end }} \ No newline at end of file diff --git a/charts/mysql-cluster/templates/_helpers.tpl b/charts/mysql-cluster/templates/_helpers.tpl new file mode 100644 index 0000000..4c981f0 --- /dev/null +++ b/charts/mysql-cluster/templates/_helpers.tpl @@ -0,0 +1,64 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cluster.name" -}} + {{- if .Values.global.nameOverride }} + {{- .Values.global.nameOverride | trunc 63 | trimSuffix "-" }} + {{- else }} + {{- printf "%s-mysql-%s" .Release.Name ((semver .Values.cluster.image.version).Major | toString) | trunc 63 | trimSuffix "-" -}} + {{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cluster.chart" -}} + {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Check for invalid versions +*/}} +{{- $minimalVersion := "8.0.27" }} +{{- $forbiddenVersions := list "8.0.29" }} +{{- $serverVersion := .Values.serverVersion | default .Chart.AppVersion }} +{{- if lt $serverVersion $minimalVersion }} + {{- $err := printf "It is not possible to use MySQL version %s . Please, use %s or above" $serverVersion $minimalVersion }} + {{- fail $err }} +{{- end }} +{{- if has $serverVersion $forbiddenVersions }} + {{- $err := printf "It is not possible to use MySQL version %s . Please, use %s or above except %v" $serverVersion $minimalVersion $forbiddenVersions }} + {{- fail $err }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cluster.labels" -}} +helm.sh/chart: {{ include "cluster.chart" . }} +{{ include "cluster.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cluster.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cluster.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/part-of: cloudnative-pg +{{- end }} + +{{/* +Create the name of the service account to use. +*/}} +{{- define "mysql.serviceAccountName" -}} +{{- if .Values.serviceAccount.enabled -}} + {{ default (include "cluster.name" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/mysql-cluster/templates/_init.tpl b/charts/mysql-cluster/templates/_init.tpl new file mode 100644 index 0000000..ee08cdb --- /dev/null +++ b/charts/mysql-cluster/templates/_init.tpl @@ -0,0 +1,47 @@ +{{- define "cluster.init" -}} + +{{- if eq .Values.mode "clone" }} +{{- with .Values.clone }} +initDB: + clone: + donorUrl: {{ required "clone donorUrl is required" .donorUrl }} + rootUser: {{ .rootUser | default "root" }} + secretKeyRef: + name: {{ required "clone credentials is required" .exisitingCredentialsSecret }} +{{- end }} +{{- end }} + +{{- if eq .Values.mode "recovery" }} +{{- with .Values.recovery }} +initDB: + dump: + {{- if .name }} + name: {{ .name | quote }} + {{- end }} + {{- if .path }} + path: {{ .path | quote }} + {{- end }} + {{- if .options }} + options: {{ toYaml .options | nindent 8 }} + {{- end }} + storage: + {{- if eq .type "s3" }} + s3: + prefix: {{ required "s3 prefix is required" .s3.prefix }} + bucketName: {{ required "s3 bucketName is required" .s3.bucketName }} + config: {{ required "s3 config is required" .s3.config }} + {{- if .s3.profile }} + profile: {{ .s3.profile }} + {{- end }} + {{- if .s3.endpoint }} + endpoint: {{ .s3.endpoint }} + {{- end }} + {{- end }} + {{- if eq .type "pvc" }} + persistentVolumeClaim: + {{ toYaml .persistentVolumeClaim | nindent 10}} + {{- end }} +{{- end }} +{{- end }} + +{{- end }} diff --git a/charts/mysql-cluster/templates/deployment.yaml b/charts/mysql-cluster/templates/deployment.yaml new file mode 100644 index 0000000..ad85d9a --- /dev/null +++ b/charts/mysql-cluster/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: mysql.oracle.com/v2 +kind: InnoDBCluster +metadata: + name: {{ include "cluster.name" . }}-cluster + namespace: {{ .Release.Namespace }} + annotations: + {{- with .Values.global.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "cluster.labels" . | nindent 4 }} + {{- include "cluster.selectorLabels" . | nindent 4 }} + {{- with .Values.global.labels }} + {{ toYaml . | nindent 4 }} + {{- end }} +spec: + instances: {{ required "serverInstances is required" .Values.cluster.serverInstances }} + baseServerId: {{ required "baseServerId is required" .Values.cluster.baseServerId }} + serviceAccountName: {{ include "mysql.serviceAccountName" . }} + imagePullPolicy : {{ .Values.cluster.image.pullPolicy }} + version: {{ .Values.cluster.image.version }} + tlsUseSelfSigned: true + secretName: {{ .Values.cluster.exisitingCredentialsSecret }} + podSpec: + {{- with .Values.cluster.podSpec }} + {{ toYaml . | nindent 4 }} + {{- end }} + podAnnotations: + {{- with .Values.cluster.podAnnotations }} + {{ toYaml . | nindent 4 }} + {{- end }} + podLabels: + {{- with .Values.cluster.podLabels }} + {{ toYaml . | nindent 4 }} + {{- end }} + router: + instances: {{ required "router.instances is required" .Values.cluster.router.instances }} + podSpec: + {{- with .Values.cluster.router.podSpec }} + {{- toYaml . | nindent 6 }} + {{- end }} + podAnnotations: + {{- with .Values.cluster.router.podAnnotations }} + {{- toYaml . | nindent 6 }} + {{- end }} + podLabels: + {{- with .Values.cluster.router.podLabels }} + {{- toYaml . | nindent 6 }} + {{- end }} + tlsSecretName: {{ include "cluster.name" . }}-router-tls + logs: + {{- with .Values.cluster.logs }} + {{ toYaml . | nindent 4 }} + {{- end }} + mycnf: | +{{ .Values.cluster.serverConfig.mycnf | indent 4 }} + {{- if .Values.cluster.datadirVolumeClaimTemplate }} + {{- with .Values.cluster.datadirVolumeClaimTemplate }} + datadirVolumeClaimTemplate: + {{- if .storageClassName }} + storageClassName: {{ .storageClassName | quote }} + {{- end}} + {{- if .accessModes }} + accessModes: [ "{{ .accessModes }}" ] + {{- end }} + {{- if .size }} + resources: + requests: + storage: "{{ .size }}" + {{- end }} + {{- end }} + {{- end }} + + {{ include "cluster.init" . | nindent 2 }} + {{ include "cluster.backup" . | nindent 2 }} diff --git a/charts/mysql-cluster/templates/service-account.yaml b/charts/mysql-cluster/templates/service-account.yaml new file mode 100644 index 0000000..bbb7472 --- /dev/null +++ b/charts/mysql-cluster/templates/service-account.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "mysql.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cluster.labels" . | nindent 4 }} + {{- include "cluster.selectorLabels" . | nindent 4 }} + {{- with .Values.global.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.serviceAccount.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.global.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.serviceAccount.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/charts/mysql-cluster/values.yaml b/charts/mysql-cluster/values.yaml new file mode 100644 index 0000000..8acb4ae --- /dev/null +++ b/charts/mysql-cluster/values.yaml @@ -0,0 +1,147 @@ +global: + nameOverride: + labels: {} + annotations: {} + +serviceAccount: + enabled: true + labels: {} + annotations: {} + name: "" + +### +# Cluster mode of operation. Available modes: +# * `standalone` - Default mode. Creates new or updates an existing cluster. +# * `recovery` - Same as standalone but creates a cluster from a backup +# * `clone` - Create database as a replica from another cluster +mode: standalone + +## +# Cluster spec +# +# Reference: https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-properties.html#mysql-operator-spec-innodbclusterspecinitdbdumpstorages3 +# +cluster: + serverInstances: 1 + baseServerId: 1000 + + # Existing secret that contains the keys "rootUser", "rootHost", and "rootPassword" + exisitingCredentialsSecret: "" + + image: + version: 8.3.0-2.1.2 + pullPolicy: IfNotPresent + + router: + instances: 1 + podSpec: {} + podAnnotations: {} + podLabels: {} + + logs: + error: + enabled: true + collect: false + general: + enabled: false + collect: false + slowQuery: + enabled: false + longQueryTime: 2.5 + + serverConfig: + mycnf: | + [mysqld] + core_file + local_infile=off + + datadirVolumeClaimTemplate: + storageClassName: "" + accessModes: "" + size: "" + + podSpec: + containers: + - name: mysql + resources: + limits: + memory: 1024Mi + cpu: 1000m + requests: + memory: 512Mi + cpu: 100m + podAnnotations: {} + podLabels: {} + +## +# Recovery database from storage +# +recovery: + + # * `s3` - Restores from s3 object store + # * `pvc` - Restores from persistent volume claim + type: + + # -- Name of the dump. Not used by the operator, but a descriptive hint for the cluster administrator + name: "" + # -- Path to the dump in the PVC. Use when specifying persistentVolumeClaim. Omit for ociObjectStorage, S3, or azure. + path: "" + # -- A dictionary of key-value pairs passed directly to MySQL Shell's loadDump() + options: {} + + s3: + # -- Path in the bucket where the dump files are stored + prefix: "" + # -- Name of a Secret with S3 configuration and credentials as contained in ~/.aws/config + config: "" + # -- Name of the S3 bucket where the dump is stored + bucketName: "" + # -- Override endpoint URL + endpoint: "" + + persistentVolumeClaim: {} + +## +# Clone database from another instance +# +clone: + donorUrl: "" + rootUser: root + exisitingCredentialsSecret: "" + +## +# Backup database to pvc or s3 +# +backup: + enabled: false + profiles: + +## -- Example profile that back ups to local pvc + +# - name: pvc-backup +# dumpInstance: +# storage: +# persistentVolumeClaim: +# claimName: backup-volume-claim + +## -- Example profile that back ups to s3 endpoint + +# - name: s3-backup +# snapshot: +# storage: +# s3: +# prefix: "" +# config: "" +# bucketName: "" +# endpoint: "" + + schedules: + +## -- Example schedule that backups daily + +# - name: schedule-daily +# enabled: true +# schedule: "0 0 0 * * *" +# timeZone: "US/Mountain" +# deleteBackupData: false +# backupProfileName: