From 3e12798cfe94698d11f0dd80806a42c0f47290ba Mon Sep 17 00:00:00 2001 From: Shanshan Date: Thu, 26 Mar 2026 21:02:43 +0800 Subject: [PATCH 1/3] feat: add Dolt SQL database addon Add Dolt (MySQL-compatible database with Git versioning) addon supporting: - Standalone mode (single-node deployment) - Replication mode (primary/standby with switchover support) - MySQL replica mode (versioned replica consuming MySQL binlog) - Add Grafana dashboard for Dolt server metrics - Add PodMonitor Includes Helm charts, ComponentDefinitions, configuration templates, lifecycle scripts, Prometheus monitoring, and usage examples. --- addons-cluster/dolt/Chart.yaml | 33 + addons-cluster/dolt/templates/_helpers.tpl | 21 + addons-cluster/dolt/templates/cluster.yaml | 17 + addons-cluster/dolt/values.schema.json | 102 + addons-cluster/dolt/values.yaml | 22 + addons/dolt/.helmignore | 23 + addons/dolt/Chart.yaml | 24 + addons/dolt/config/dolt-server.yaml.tpl | 25 + addons/dolt/config/dolt-standalone.yaml.tpl | 17 + addons/dolt/dashboards/dolt-server.json | 2059 +++++++++++++++++ addons/dolt/scripts/docker-entrypoint.sh | 441 ++++ .../dolt/scripts/setup-mysql-replication.sh | 30 + addons/dolt/scripts/start-standalone.sh | 31 + addons/dolt/scripts/start.sh | 43 + addons/dolt/scripts/switchover.sh | 67 + addons/dolt/templates/NOTES.txt | 0 addons/dolt/templates/_helpers.tpl | 106 + addons/dolt/templates/clusterdefinition.yaml | 19 + addons/dolt/templates/cmpd-repl.yaml | 117 + addons/dolt/templates/cmpd-standalone.yaml | 108 + addons/dolt/templates/cmpv.yaml | 30 + .../templates/config-template-standalone.yaml | 11 + addons/dolt/templates/config-template.yaml | 11 + addons/dolt/templates/script-template.yaml | 12 + addons/dolt/values.yaml | 16 + examples/dolt/README.md | 168 ++ examples/dolt/cluster-mysql-replica.yaml | 102 + examples/dolt/cluster-replication.yaml | 32 + examples/dolt/cluster-standalone.yaml | 30 + examples/dolt/podMonitor.yaml | 25 + .../dolt/switchover-specified-instance.yaml | 15 + 31 files changed, 3757 insertions(+) create mode 100644 addons-cluster/dolt/Chart.yaml create mode 100644 addons-cluster/dolt/templates/_helpers.tpl create mode 100644 addons-cluster/dolt/templates/cluster.yaml create mode 100644 addons-cluster/dolt/values.schema.json create mode 100644 addons-cluster/dolt/values.yaml create mode 100644 addons/dolt/.helmignore create mode 100644 addons/dolt/Chart.yaml create mode 100644 addons/dolt/config/dolt-server.yaml.tpl create mode 100644 addons/dolt/config/dolt-standalone.yaml.tpl create mode 100644 addons/dolt/dashboards/dolt-server.json create mode 100644 addons/dolt/scripts/docker-entrypoint.sh create mode 100644 addons/dolt/scripts/setup-mysql-replication.sh create mode 100644 addons/dolt/scripts/start-standalone.sh create mode 100644 addons/dolt/scripts/start.sh create mode 100644 addons/dolt/scripts/switchover.sh create mode 100644 addons/dolt/templates/NOTES.txt create mode 100644 addons/dolt/templates/_helpers.tpl create mode 100644 addons/dolt/templates/clusterdefinition.yaml create mode 100644 addons/dolt/templates/cmpd-repl.yaml create mode 100644 addons/dolt/templates/cmpd-standalone.yaml create mode 100644 addons/dolt/templates/cmpv.yaml create mode 100644 addons/dolt/templates/config-template-standalone.yaml create mode 100644 addons/dolt/templates/config-template.yaml create mode 100644 addons/dolt/templates/script-template.yaml create mode 100644 addons/dolt/values.yaml create mode 100644 examples/dolt/README.md create mode 100644 examples/dolt/cluster-mysql-replica.yaml create mode 100644 examples/dolt/cluster-replication.yaml create mode 100644 examples/dolt/cluster-standalone.yaml create mode 100644 examples/dolt/podMonitor.yaml create mode 100644 examples/dolt/switchover-specified-instance.yaml diff --git a/addons-cluster/dolt/Chart.yaml b/addons-cluster/dolt/Chart.yaml new file mode 100644 index 000000000..43b0d444d --- /dev/null +++ b/addons-cluster/dolt/Chart.yaml @@ -0,0 +1,33 @@ +apiVersion: v2 +name: dolt-cluster +type: application +version: 1.0.0 +description: A Dolt cluster Helm chart for KubeBlocks. + +dependencies: + - name: kblib + version: 0.1.2 + repository: file://../kblib + alias: extra + +appVersion: "1.84.0" + +keywords: + - dolt + - database + - sql + - replication + +home: https://github.com/apecloud/kubeblocks-addons +icon: https://kubeblocks.io/img/logo.png + +maintainers: + - name: ApeCloud + url: https://kubeblocks.io/ + +sources: + - https://github.com/apecloud/kubeblocks/ + - https://github.com/dolthub/dolt + +annotations: + category: Database diff --git a/addons-cluster/dolt/templates/_helpers.tpl b/addons-cluster/dolt/templates/_helpers.tpl new file mode 100644 index 000000000..05b6914db --- /dev/null +++ b/addons-cluster/dolt/templates/_helpers.tpl @@ -0,0 +1,21 @@ +{{/* +ComponentDefinition from mode (must match addons/dolt: dolt-replication | dolt-standalone). +*/}} +{{- define "dolt-cluster.componentDef" -}} +{{- if eq .Values.mode "standalone" -}} +dolt-standalone +{{- else -}} +dolt-replication +{{- end -}} +{{- end }} + +{{/* +Replica count: standalone is always 1; otherwise use .Values.replicas. +*/}} +{{- define "dolt-cluster.replicas" -}} +{{- if eq .Values.mode "standalone" -}} +1 +{{- else -}} +{{ .Values.replicas }} +{{- end -}} +{{- end }} diff --git a/addons-cluster/dolt/templates/cluster.yaml b/addons-cluster/dolt/templates/cluster.yaml new file mode 100644 index 000000000..725c1366f --- /dev/null +++ b/addons-cluster/dolt/templates/cluster.yaml @@ -0,0 +1,17 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: Cluster +metadata: + name: {{ include "kblib.clusterName" . }} + namespace: {{ .Release.Namespace }} + labels: {{ include "kblib.clusterLabels" . | nindent 4 }} +spec: + terminationPolicy: {{ .Values.extra.terminationPolicy }} + clusterDef: dolt + topology: {{ .Values.mode }} + componentSpecs: + - name: dolt + serviceVersion: {{ .Values.version | quote }} + replicas: {{ include "dolt-cluster.replicas" . }} + {{- include "kblib.componentResources" . | indent 6 }} + {{- include "kblib.componentStorages" . | indent 6 }} + {{- include "kblib.componentMonitor" . | indent 6 }} diff --git a/addons-cluster/dolt/values.schema.json b/addons-cluster/dolt/values.schema.json new file mode 100644 index 000000000..6e9d09ae0 --- /dev/null +++ b/addons-cluster/dolt/values.schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "title": "Dolt cluster", + "description": "Values for the dolt-cluster Helm chart (KubeBlocks).", + "properties": { + "mode": { + "title": "Mode", + "description": "Topology: replication (primary/standby) or standalone (single node).", + "type": "string", + "default": "replication", + "enum": ["replication", "standalone"] + }, + "version": { + "title": "Service version", + "description": "Dolt serviceVersion; must match a release in ComponentVersion `dolt`.", + "type": "string", + "default": "1.84.0" + }, + "replicas": { + "title": "Replicas", + "description": "Replica count when mode is replication (standalone always uses 1).", + "type": "integer", + "default": 2, + "minimum": 1, + "maximum": 32 + }, + "cpu": { + "title": "CPU", + "description": "CPU cores per pod.", + "type": ["number", "string"], + "default": 0.5, + "minimum": 0.5, + "maximum": 64, + "multipleOf": 0.5 + }, + "memory": { + "title": "Memory (Gi)", + "description": "Memory limit/request in Gi.", + "type": ["number", "string"], + "default": 0.5, + "minimum": 0.5, + "maximum": 1000 + }, + "storage": { + "title": "Storage (Gi)", + "description": "Persistent volume size in Gi.", + "type": ["number", "string"], + "default": 10, + "minimum": 1, + "maximum": 10000 + }, + "extra": { + "type": "object", + "description": "KubeBlocks shared options (kblib).", + "properties": { + "terminationPolicy": { + "title": "Termination policy", + "description": "Cluster deletion behavior.", + "type": "string", + "default": "Delete", + "enum": ["DoNotTerminate", "Delete", "WipeOut"] + } + }, + "additionalProperties": true + }, + "prometheus": { + "type": "object", + "description": "Prometheus Operator PodMonitor options.", + "properties": { + "enabled": { + "type": "boolean", + "default": false, + "description": "If true, render a PodMonitor when the PodMonitor CRD exists." + }, + "metricsPath": { + "type": "string", + "default": "/metrics", + "description": "HTTP path for metrics scrape." + }, + "interval": { + "type": "string", + "default": "", + "description": "Optional scrape interval (empty uses Prometheus default)." + }, + "scrapeTimeout": { + "type": "string", + "default": "", + "description": "Optional per-scrape timeout (empty uses Prometheus default)." + }, + "additionalLabels": { + "type": "object", + "default": {}, + "description": "Extra labels on the PodMonitor resource.", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true +} diff --git a/addons-cluster/dolt/values.yaml b/addons-cluster/dolt/values.yaml new file mode 100644 index 000000000..d0df79008 --- /dev/null +++ b/addons-cluster/dolt/values.yaml @@ -0,0 +1,22 @@ +# Default values for dolt-cluster. + +## @param mode Dolt topology: `replication` (primary/standby) or `standalone` (single node). +mode: replication + +## @param version serviceVersion in ComponentVersion. +version: 1.84.0 + +## @param replicas number of dolt nodes when `mode` is `replication` (ignored when `mode` is `standalone`, which is always one replica). +replicas: 2 + +## @param cpu CPU cores. +cpu: 0.5 + +## @param memory Memory in Gi. +memory: 0.5 + +## @param storage storage size in Gi. +storage: 10 + +extra: + terminationPolicy: Delete \ No newline at end of file diff --git a/addons/dolt/.helmignore b/addons/dolt/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/addons/dolt/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/addons/dolt/Chart.yaml b/addons/dolt/Chart.yaml new file mode 100644 index 000000000..8469c2767 --- /dev/null +++ b/addons/dolt/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: dolt +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.84.0" diff --git a/addons/dolt/config/dolt-server.yaml.tpl b/addons/dolt/config/dolt-server.yaml.tpl new file mode 100644 index 000000000..5f4a280ba --- /dev/null +++ b/addons/dolt/config/dolt-server.yaml.tpl @@ -0,0 +1,25 @@ +log_level: info +data_dir: ${DATA_DIR} + +listener: + host: 0.0.0.0 + port: 3306 + +behavior: + read_only: false + +metrics: + labels: {} + host: 0.0.0.0 + port: 11228 + +cluster: + bootstrap_role: ${BOOTSTRAP_ROLE} + bootstrap_epoch: 1 + + remotesapi: + port: ${REMOTES_API_PORT} + + standby_remotes: + - name: standby + remote_url_template: http://${STANDBY_HOST}.${HEADLESS_SERVICE_NAME}:${REMOTES_API_PORT}/{database} diff --git a/addons/dolt/config/dolt-standalone.yaml.tpl b/addons/dolt/config/dolt-standalone.yaml.tpl new file mode 100644 index 000000000..0ad86267b --- /dev/null +++ b/addons/dolt/config/dolt-standalone.yaml.tpl @@ -0,0 +1,17 @@ +log_level: info +data_dir: ${DATA_DIR} +cfg_dir: ${DATA_DIR}/.doltcfg +privilege_file: ${DATA_DIR}/.doltcfg/privileges.db +branch_control_file: ${DATA_DIR}/.doltcfg/branch_control.db + +listener: + host: 0.0.0.0 + port: 3306 + +behavior: + read_only: false + +metrics: + labels: {} + host: 0.0.0.0 + port: 11228 \ No newline at end of file diff --git a/addons/dolt/dashboards/dolt-server.json b/addons/dolt/dashboards/dolt-server.json new file mode 100644 index 000000000..a0c968d20 --- /dev/null +++ b/addons/dolt/dashboards/dolt-server.json @@ -0,0 +1,2059 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Dolt SQL Server metrics from the Prometheus /metrics endpoint (dss_*, process_*, go_*, sys_*).", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Dolt SQL Server", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Clients connected to this Dolt SQL server instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(dss_concurrent_connections{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"})", + "legendFormat": "connections", + "range": true, + "refId": "A" + } + ], + "title": "Concurrent connections", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Queries currently executing.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 50 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(dss_concurrent_queries{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"})", + "legendFormat": "queries", + "range": true, + "refId": "A" + } + ], + "title": "Concurrent queries", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Encoded Dolt version as exposed by dss_dolt_version (implementation detail of the binary).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "value_and_name" + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(dss_dolt_version{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"})", + "legendFormat": "dss_dolt_version", + "range": true, + "refId": "A" + } + ], + "title": "Dolt version (metric)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Approximate process uptime (wall clock).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(time() - process_start_time_seconds{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"})", + "legendFormat": "uptime", + "range": true, + "refId": "A" + } + ], + "title": "Process uptime", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "dss_concurrent_connections{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "{{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "dss_concurrent_queries{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "{{pod}} queries", + "range": true, + "refId": "B" + } + ], + "title": "Connections & concurrent queries", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 7, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(dss_connects{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])", + "legendFormat": "{{pod}} connects/s", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(dss_disconnects{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])", + "legendFormat": "{{pod}} disconnects/s", + "range": true, + "refId": "B" + } + ], + "title": "Connect / disconnect rate", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 8, + "panels": [], + "title": "Query duration", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Estimated query latency from dss_query_duration histogram.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 9, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(dss_query_duration_bucket{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])) by (le, pod))", + "legendFormat": "p50 {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.90, sum(rate(dss_query_duration_bucket{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])) by (le, pod))", + "legendFormat": "p90 {{pod}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(dss_query_duration_bucket{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])) by (le, pod))", + "legendFormat": "p99 {{pod}}", + "range": true, + "refId": "C" + } + ], + "title": "Query duration (histogram_quantile)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(dss_query_duration_bucket{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])) by (le)", + "legendFormat": "le={{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Query duration histogram (rate by bucket)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 11, + "panels": [], + "title": "Process", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 31 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(process_cpu_seconds_total{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])", + "legendFormat": "{{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "CPU usage (cores)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 31 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "process_resident_memory_bytes{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "RSS {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "process_virtual_memory_bytes{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "VSZ {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "Process memory", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 31 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(process_network_receive_bytes_total{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])", + "legendFormat": "rx {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(process_network_transmit_bytes_total{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])", + "legendFormat": "tx {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "Process network throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/max/" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 39 + }, + "id": 15, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "process_open_fds{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "open {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "process_max_fds{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "max {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "Open file descriptors", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 16, + "panels": [], + "title": "Go runtime", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 47 + }, + "id": 17, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_goroutines{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "goroutines {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_threads{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "threads {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "Goroutines & OS threads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 47 + }, + "id": 18, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_memstats_heap_inuse_bytes{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "heap inuse {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_memstats_stack_inuse_bytes{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "stack {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "Go heap & stack", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "description": "Summary quantiles are exposed as quantile=1 or 1.0 depending on the client; some scrapers omit the top quantile. Average STW pause uses _sum/_count and is always present.", + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 55 + }, + "id": 19, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_gc_duration_seconds{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\",quantile=\"1\"} or go_gc_duration_seconds{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\",quantile=\"1.0\"}", + "legendFormat": "GC max (summary q≈1) {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(go_gc_duration_seconds_sum{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval]) / rate(go_gc_duration_seconds_count{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])", + "legendFormat": "avg STW pause {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "GC pause (summary vs avg)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 55 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_gc_gogc_percent{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "GOGC % {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "go_sched_gomaxprocs_threads{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "GOMAXPROCS {{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "GOGC & GOMAXPROCS", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 62 + }, + "id": 21, + "panels": [], + "title": "System (pod)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Host-level utilization as reported by Dolt (0–100 scale).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 70 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 63 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sys_cpu_usage{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "CPU % {{pod}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sys_mem_usage{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "Memory % {{pod}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sys_disk_usage{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}", + "legendFormat": "Disk % {{pod}}", + "range": true, + "refId": "C" + } + ], + "title": "System CPU / memory / disk usage", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 71 + }, + "id": 23, + "panels": [], + "title": "Prometheus scrape (/metrics)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 16, + "x": 0, + "y": 72 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(promhttp_metric_handler_requests_total{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"}[$__rate_interval])) by (code, pod)", + "legendFormat": "{{pod}} {{code}}", + "range": true, + "refId": "A" + } + ], + "title": "Scrape requests by HTTP code", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 72 + }, + "id": 25, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(promhttp_metric_handler_requests_in_flight{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\",pod=~\"$instance\"})", + "legendFormat": "in flight", + "range": true, + "refId": "A" + } + ], + "title": "Metric handler in-flight scrapes", + "type": "stat" + } + ], + "refresh": "30s", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "dolt", + "kubeblocks", + "database" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(dss_concurrent_connections, namespace)", + "hide": 0, + "includeAll": true, + "label": "Namespace", + "multi": true, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(dss_concurrent_connections, namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(dss_concurrent_connections{namespace=~\"$namespace\"}, app_kubernetes_io_instance)", + "hide": 0, + "includeAll": true, + "label": "Cluster", + "multi": true, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(dss_concurrent_connections{namespace=~\"$namespace\"}, app_kubernetes_io_instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(dss_concurrent_connections{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\"}, pod)", + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "instance", + "options": [], + "query": { + "query": "label_values(dss_concurrent_connections{namespace=~\"$namespace\",app_kubernetes_io_instance=~\"$cluster\"}, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Dolt SQL Server", + "uid": "dolt-sql-server", + "version": 1, + "weekStart": "" +} diff --git a/addons/dolt/scripts/docker-entrypoint.sh b/addons/dolt/scripts/docker-entrypoint.sh new file mode 100644 index 000000000..9e4b76157 --- /dev/null +++ b/addons/dolt/scripts/docker-entrypoint.sh @@ -0,0 +1,441 @@ +#!/bin/bash +set -eo pipefail + +# mysql_log prints a timestamped (ISO 8601), color-coded structured log message. +# The message includes a log level and the message itself. +# If is omitted, it reads from stdin, allowing multi-line input. +# +# Arguments: +# $1 - : Log level (e.g., Warn, Error, Debug) +# $2 - : Message to log; if omitted, the function reads from stdin +# +# Usage: +# mysql_log [MESSAGE] +# mysql_log Warn "Disk space low" +# echo "Database connection lost" | mysql_log Error +# +# Output: +# 2025-10-16T12:34:56+00:00 [Warn] [Entrypoint] Disk space low +# 2025-10-16T12:35:01+00:00 [Error] [Entrypoint] Database connection lost +_mysql_log() { + local level="$1"; shift + + local dt + dt="$(date --rfc-3339=seconds)" + + local color_reset="\033[0m" + local color="" + case "$level" in + Warn) color="\033[1;33m" ;; # yellow + Error) color="\033[1;31m" ;; # red + Debug) color="\033[1;34m" ;; # blue + esac + + local msg="$*" + if [ "$#" -eq 0 ]; then + msg="$(cat)" + fi + + printf '%b%s [%s] [Entrypoint] %s%b\n' "$color" "$dt" "$level" "$msg" "$color_reset" +} + + +# _dbg logs a message of type 'Debug' using mysql_log. +_dbg() { + _mysql_log Debug "$@" +} + +# mysql_note logs a message of type 'Note' using mysql_log. +mysql_note() { + _mysql_log Note "$@" +} + +# mysql_warn logs a message of type 'Warning' using mysql_log and writes to stderr. +mysql_warn() { + _mysql_log Warn "$@" >&2 +} + +# mysql_error logs a message of type 'ERROR' using mysql_log, writes to stderr, prints a container removal hint, and +# exits with status 1. +mysql_error() { + _mysql_log Error "$@" >&2 + mysql_note "Remove this container with 'docker rm -f ' before retrying" + exit 1 +} + +# exec_mysql executes a SQL query using Dolt, retrying until success or timeout. Ensures reliability during slow +# container or resource startup. On timeout, it prints the provided error prefix followed by filtered Dolt output. +# Errors are parsed to remove blank lines and extract only relevant error text. Use --show-result to display successful +# query results. +# +# Usage: +# exec_mysql [--show-result] "" "" +# exec_mysql [--show-result] "" < /docker-entrypoint-initdb.d/init.sql +# cat /docker-entrypoint-initdb.d/init.sql | exec_mysql [--show-result] "" +# +# Output: +# Prints query output only if --show-result is specified. +exec_mysql() { + local show_result=0 + if [ "$1" = "--show-result" ]; then + show_result=1 + shift + fi + + local error_message="$1" + local query="${2:-}" + local timeout="${DOLT_SERVER_TIMEOUT:-300}" + local start_time now output status + + start_time=$(date +%s) + + while true; do + if [ -n "$query" ]; then + output=$(dolt sql -q "$query" 2>&1) + status=$? + else + set +e # tmp disabled to initdb.d/ file err + output=$(dolt sql < /dev/stdin 2>&1) + status=$? + set -e + fi + + if [ "$status" -eq 0 ]; then + [ "$show_result" -eq 1 ] && echo "$output" | grep -v "^$" || true + return 0 + fi + + if echo "$output" | grep -qiE "Error [0-9]+ \([A-Z0-9]+\)"; then + mysql_error "$error_message$(echo "$output" | grep -iE "Error|error")" + fi + + if [ "$timeout" -ne 0 ]; then + now=$(date +%s) + if [ $((now - start_time)) -ge "$timeout" ]; then + mysql_error "$error_message$(echo "$output" | grep -iE "Error|error" || true)" + fi + fi + + sleep 1 + done +} + +CONTAINER_DATA_DIR="/var/lib/dolt" +INIT_COMPLETED="$CONTAINER_DATA_DIR/.init_completed" +DOLT_CONFIG_DIR="/etc/dolt/doltcfg.d" +SERVER_CONFIG_DIR="/etc/dolt/servercfg.d" +DOLT_ROOT_PATH="/.dolt" +SERVER_PID=-1 + +# check_for_dolt_binary verifies that the dolt binary is present and executable in the system PATH. +# If not found or not executable, it logs an error and exits. +check_for_dolt_binary() { + local dolt_bin + dolt_bin=$(which dolt) + if [ ! -x "$dolt_bin" ]; then + mysql_error "dolt binary executable not found" + fi +} + +# get_env_var returns the value of an environment variable, preferring DOLT_* over MYSQL_*. +# Arguments: +# $1 - The base variable name (e.g., "USER" for MYSQL_USER or DOLT_USER) +# Output: +# Prints the value of the first set variable, or an empty string if neither is set. +get_env_var() { + local var_name="$1" + local dolt_var="DOLT_${var_name}" + local mysql_var="MYSQL_${var_name}" + + if [ -n "${!dolt_var}" ]; then + echo "${!dolt_var}" + elif [ -n "${!mysql_var}" ]; then + echo "${!mysql_var}" + else + echo "" + fi +} + +# get_env_var_name returns the name of the environment variable that is set, preferring DOLT_* over MYSQL_*. +# Arguments: +# $1 - The base variable name (e.g., "USER" for MYSQL_USER or DOLT_USER) +# Output: +# Prints the name of the first set variable, or both names if neither is set. +get_env_var_name() { + local var_name="$1" + local dolt_var="DOLT_${var_name}" + local mysql_var="MYSQL_${var_name}" + + if [ -n "${!dolt_var}" ]; then + echo "DOLT_${var_name}" + elif [ -n "${!mysql_var}" ]; then + echo "MYSQL_${var_name}" + else + echo "MYSQL_${var_name}/DOLT_${var_name}" + fi +} + +# get_config_file_path_if_exists checks for config files of a given type in a directory. +# Arguments: +# $1 - Directory to search in +# $2 - File type/extension to search for (e.g., 'json', 'yaml') +# Output: +# Sets CONFIG_PROVIDED to the path of the config file if exactly one is found, or empty otherwise. +# Logs a warning if multiple config files are found and uses the default config. +get_config_file_path_if_exists() { + CONFIG_PROVIDED= + local CONFIG_DIR=$1 + local FILE_TYPE=$2 + if [ -d "$CONFIG_DIR" ]; then + mysql_note "Checking for config provided in $CONFIG_DIR" + local number_of_files_found + number_of_files_found=$(find "$CONFIG_DIR" -type f -name "*.$FILE_TYPE" | wc -l) + if [ "$number_of_files_found" -gt 1 ]; then + CONFIG_PROVIDED= + mysql_warn "Multiple config files found in $CONFIG_DIR, using default config" + elif [ "$number_of_files_found" -eq 1 ]; then + local files_found + files_found=$(ls "$CONFIG_DIR"/*."$FILE_TYPE") + mysql_note "$files_found file is found" + CONFIG_PROVIDED=$files_found + else + CONFIG_PROVIDED= + fi + fi +} + +# docker_process_init_files Runs files found in /docker-entrypoint-initdb.d before the server is started. +# Taken from https://github.com/docker-library/mysql/blob/master/8.0/docker-entrypoint.sh +# Usage: +# docker_process_init_files [file [file ...]] +# e.g., docker_process_init_files /always-initdb.d/* +# Processes initializer files based on file extensions. +docker_process_init_files() { + local f + echo + for f; do + case "$f" in + *.sh) + if [ -x "$f" ]; then + mysql_note "$0: running $f" + if ! "$f"; then + mysql_error "Failed to execute $f: " + fi + else + mysql_note "$0: sourcing $f" + if ! . "$f"; then + mysql_error "Failed to execute $f: " + fi + fi + ;; + *.sql) + mysql_note "$0: running $f" + exec_mysql --show-result "Failed to execute $f: " < "$f" + ;; + *.sql.bz2) + mysql_note "$0: running $f" + bunzip2 -c "$f" | exec_mysql --show-result "Failed to execute $f: " + ;; + *.sql.gz) + mysql_note "$0: running $f" + gunzip -c "$f" | exec_mysql --show-result "Failed to execute $f: " + ;; + *.sql.xz) + mysql_note "$0: running $f" + xzcat "$f" | exec_mysql --show-result "Failed to execute $f: " + ;; + *.sql.zst) + mysql_note "$0: running $f" + zstd -dc "$f" | exec_mysql --show-result "Failed to execute $f: " + ;; + *) + mysql_warn "$0: ignoring $f" + ;; + esac + echo + done +} + +# set_dolt_config_if_defined checks for a user-provided Dolt config file in $DOLT_CONFIG_DIR. +# If a single JSON config file is found, it copies it to $HOME/$DOLT_ROOT_PATH/config_global.json, +# overwriting the default config. Logs an error and exits if the copy fails. +set_dolt_config_if_defined() { + get_config_file_path_if_exists "$DOLT_CONFIG_DIR" "json" + if [ ! -z "$CONFIG_PROVIDED" ]; then + if ! /bin/cp -rf "$CONFIG_PROVIDED" "$HOME/$DOLT_ROOT_PATH/config_global.json" 2>&1; then + mysql_error "Failed to copy config file from '$CONFIG_PROVIDED' to '$HOME/$DOLT_ROOT_PATH/config_global.json'. Check file permissions and paths." + fi + fi +} + +# create_database_from_env creates a database if the DATABASE environment variable is set. +# It retrieves the database name from environment variables (preferring DOLT_DATABASE over MYSQL_DATABASE) +# and attempts to create the database using exec_mysql. +create_database_from_env() { + local database + database=$(get_env_var "DATABASE") + + if [ -n "$database" ]; then + mysql_note "Creating database '${database}'" + exec_mysql "Failed to create database '$database': " "CREATE DATABASE IF NOT EXISTS \`$database\`;" + fi +} + +# create_user_from_env creates a new database user from environment variables. +# It prefers DOLT_USER/PASSWORD over MYSQL_USER/PASSWORD, and optionally grants access to a database. +# Requires both USER and PASSWORD to be set; if only the password is set, it logs a warning and does nothing. +# It does not allow creating a 'root' user via these environment variables. +create_user_from_env() { + local user + local password + local database + + user=$(get_env_var "USER") + password=$(get_env_var "PASSWORD") + database=$(get_env_var "DATABASE") + + if [ "$user" = 'root' ]; then + mysql_error "$(get_env_var_name "USER")="root", $(get_env_var_name "USER") and $(get_env_var_name "PASSWORD") are for configuring the regular user and cannot be used for the root user." + fi + + if [ -n "$user" ] && [ -z "$password" ]; then + mysql_error "$(get_env_var_name "USER") specified, but missing $(get_env_var_name "PASSWORD"); user creation requires a password." + elif [ -z "$user" ] && [ -n "$password" ]; then + mysql_warn "$(get_env_var_name "PASSWORD") specified, but missing $(get_env_var_name "USER"); password will be ignored" + return + fi + + if [ -n "$user" ]; then + local user_host + user_host=$(get_env_var "USER_HOST") + user_host="${user_host:-${DOLT_ROOT_HOST:-localhost}}" + + mysql_note "Creating user '${user}@${user_host}'" + exec_mysql "Failed to create user '$user': " "CREATE USER IF NOT EXISTS '$user'@'$user_host' IDENTIFIED BY '$password';" + exec_mysql "Failed to grant server access to user '$user': " "GRANT USAGE ON *.* TO '$user'@'$user_host';" + + if [ -n "$database" ]; then + exec_mysql "Failed to grant permissions to user '$user' on database '$database': " "GRANT ALL ON \`$database\`.* TO '$user'@'$user_host';" + fi + fi +} + +# is_port_open checks if a TCP port is open on a given host. +# Arguments: +# $1 - Host (IP or hostname) +# $2 - Port number +# Returns: +# 0 if the port is open, non-zero otherwise. +is_port_open() { + local host="$1" + local port="$2" + timeout 1 bash -c "cat < /dev/null > /dev/tcp/$host/$port" &>/dev/null + return $? +} + +# dolt_server_initializer starts the Dolt SQL server in the background and waits until it is ready to accept connections. +# It manages the server process, restarts it if necessary, and checks for readiness by probing the configured port. +# The function retries until the server is available or a timeout is reached, handling process management and logging. +# Arguments: +# $@ - Additional arguments to pass to `dolt sql-server` +# Returns: +# 0 if the server starts successfully and is ready to accept connections; exits with error otherwise. +dolt_server_initializer() { + local timeout="${DOLT_SERVER_TIMEOUT:-300}" + local start_time + start_time=$(date +%s) + + SERVER_PID=-1 + + trap 'mysql_note "Caught Ctrl+C, shutting down Dolt server..."; [ $SERVER_PID -ne -1 ] && kill "$SERVER_PID"; exit 1' INT TERM + + while true; do + if [ "$SERVER_PID" -eq -1 ] || ! kill -0 "$SERVER_PID" 2>/dev/null; then + [ "$SERVER_PID" -ne -1 ] && wait "$SERVER_PID" 2>/dev/null || true + SERVER_PID=-1 + dolt sql-server --host=0.0.0.0 --port=3306 "$@" 2>&1 & + SERVER_PID=$! + + fi + + if is_port_open "0.0.0.0" 3306; then + mysql_note "Dolt server started." + return 0 + fi + + local now elapsed + now=$(date +%s) + elapsed=$((now - start_time)) + if [ "$elapsed" -ge "$timeout" ]; then + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true + SERVER_PID=-1 + mysql_error "Dolt server failed to start within $timeout seconds" + fi + + sleep 1 + done +} + +# _main is the main entrypoint for the Dolt Docker container initialization. +_main() { + check_for_dolt_binary + + local dolt_version + dolt_version=$(dolt version | grep 'dolt version' | cut -f3 -d " ") + mysql_note "Entrypoint script for Dolt Server $dolt_version starting..." + + declare -g CONFIG_PROVIDED + + # dolt config will be set if user provided a single json file in /etc/dolt/doltcfg.d directory. + # It will overwrite config_global.json file in $HOME/.dolt + set_dolt_config_if_defined + CONFIG_PROVIDED= + + # if there is a single yaml provided in /etc/dolt/servercfg.d directory, + # it will be used to start the server with --config flag. + get_config_file_path_if_exists "$SERVER_CONFIG_DIR" "yaml" + if [ -n "$CONFIG_PROVIDED" ]; then + set -- "$@" --config="$CONFIG_PROVIDED" + fi + + mysql_note "Starting Dolt server" + # Attempt to configure the root user directly through the sql-server using built-in environment variable support + # The user creation queries with `dolt sql` can interfere with this process so we run them after the server is started + DOLT_ROOT_HOST="${DOLT_ROOT_HOST:-localhost}" + + # `dolt sql` can hold locks that prevent the server from starting during system hangs without this func + dolt_server_initializer "$@" + # Ran in a subshell to avoid exiting the main script, and so, we can use fallback below + local has_correct_host + has_correct_host=$(exec_mysql --show-result "Could not check root host: " \ + "SELECT User, Host FROM mysql.user WHERE User='root' AND Host='${DOLT_ROOT_HOST}' LIMIT 1;" | \ + grep -c "$DOLT_ROOT_HOST" || true) + + # args or system hangs may conflict with sql-server root env vars support + if [ "$has_correct_host" -eq 0 ]; then + mysql_warn "Environment variables failed to initialize 'root@${DOLT_ROOT_HOST}'; docker-entrypoint-initdb.d scripts queries may have conflicted. Overriding root user..." + exec_mysql "Could not create root user: " "CREATE USER IF NOT EXISTS 'root'@'${DOLT_ROOT_HOST}' IDENTIFIED WITH mysql_native_password BY '${DOLT_ROOT_PASSWORD}';" # override password + exec_mysql "Could not set root privileges: " "GRANT ALL PRIVILEGES ON *.* TO 'root'@'${DOLT_ROOT_HOST}' WITH GRANT OPTION;" + fi + + create_database_from_env + + create_user_from_env + + exec_mysql --show-result "Could not list users: " "SELECT User, Host FROM mysql.user;" + + if [[ ! -f $INIT_COMPLETED ]]; then + if ls /docker-entrypoint-initdb.d/* >/dev/null 2>&1; then + docker_process_init_files /docker-entrypoint-initdb.d/* + else + mysql_warn "No files found in /docker-entrypoint-initdb.d/ to process" + fi + touch "$INIT_COMPLETED" + fi + + mysql_note "Dolt init process done. Ready for connections." + wait "$SERVER_PID" +} + +_main "$@" \ No newline at end of file diff --git a/addons/dolt/scripts/setup-mysql-replication.sh b/addons/dolt/scripts/setup-mysql-replication.sh new file mode 100644 index 000000000..83e05c867 --- /dev/null +++ b/addons/dolt/scripts/setup-mysql-replication.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -eo pipefail + +# Configures Dolt as a MySQL binlog replica. +# Expects env vars: MYSQL_SOURCE_HOST, MYSQL_SOURCE_PORT +# Optional env vars: MYSQL_SOURCE_USER, MYSQL_SOURCE_PASSWORD, DOLT_REPLICA_SERVER_ID + +SOURCE_HOST="${MYSQL_SOURCE_HOST}" +SOURCE_PORT="${MYSQL_SOURCE_PORT:-3306}" +SOURCE_USER="${MYSQL_SOURCE_USER:-root}" +SOURCE_PASSWORD="${MYSQL_SOURCE_PASSWORD:-}" +SERVER_ID="${DOLT_REPLICA_SERVER_ID:-10}" + +dolt_sql() { + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q "$1" +} + +echo "Configuring Dolt as MySQL binlog replica of ${SOURCE_HOST}:${SOURCE_PORT}..." + +dolt_sql "SET @@PERSIST.server_id = ${SERVER_ID};" + +if [ -n "${SOURCE_PASSWORD}" ]; then + dolt_sql "CHANGE REPLICATION SOURCE TO SOURCE_HOST='${SOURCE_HOST}', SOURCE_USER='${SOURCE_USER}', SOURCE_PASSWORD='${SOURCE_PASSWORD}', SOURCE_PORT=${SOURCE_PORT};" +else + dolt_sql "CHANGE REPLICATION SOURCE TO SOURCE_HOST='${SOURCE_HOST}', SOURCE_USER='${SOURCE_USER}', SOURCE_PORT=${SOURCE_PORT};" +fi + +dolt_sql "START REPLICA;" + +echo "MySQL binlog replication started. Source: ${SOURCE_HOST}:${SOURCE_PORT}" diff --git a/addons/dolt/scripts/start-standalone.sh b/addons/dolt/scripts/start-standalone.sh new file mode 100644 index 000000000..0609e0f4c --- /dev/null +++ b/addons/dolt/scripts/start-standalone.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -eo pipefail + +DATA_DIR="${DATA_DIR:-/var/lib/dolt}" +SERVER_CONFIG="${DATA_DIR}/.server-config.yaml" + +mkdir -p /etc/dolt/servercfg.d/config.yaml +mkdir -p "${DATA_DIR}/.doltcfg" + +sed \ + -e "s|\${DATA_DIR}|${DATA_DIR}|g" \ + /config/config.yaml > "${SERVER_CONFIG}" + +# MySQL source is configured — start the entrypoint in background, then +# wait for the server to be ready and configure binlog replication. +/scripts/docker-entrypoint.sh --config="${SERVER_CONFIG}" & +EP_PID=$! + +if [ -n "${MYSQL_SOURCE_HOST:-}" ]; then + until dolt --host 127.0.0.1 --port 3306 --no-tls sql -q "SELECT 1" >/dev/null 2>&1; do + if ! kill -0 "$EP_PID" 2>/dev/null; then + echo "Entrypoint exited before server became ready" + exit 1 + fi + sleep 2 + done + + /scripts/setup-mysql-replication.sh +fi + +wait "$EP_PID" diff --git a/addons/dolt/scripts/start.sh b/addons/dolt/scripts/start.sh new file mode 100644 index 000000000..62ef96f3c --- /dev/null +++ b/addons/dolt/scripts/start.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -eu + +DATA_DIR="${DATA_DIR:-/var/lib/dolt}" +INIT_COMPLETED="${DATA_DIR}/.init_completed" +SERVER_CONFIG="${DATA_DIR}/.server-config.yaml" +REMOTES_API_PORT="${REMOTES_API_PORT:-50051}" + +if [ -f "${INIT_COMPLETED}" ]; then + # Data dir already initialized: reuse persisted server config (roles/remotes are in data). + : +else + POD_NAME="${CURRENT_POD_NAME:-}" + if [ -z "${POD_NAME}" ]; then + echo "CURRENT_POD_NAME is required on first start" >&2 + exit 1 + fi + + POD_BASENAME="${POD_NAME%-*}" + POD_ORDINAL="${POD_NAME##*-}" + + if [ "${POD_ORDINAL}" = "0" ]; then + BOOTSTRAP_ROLE="primary" + STANDBY_HOST="${POD_BASENAME}-1" + else + BOOTSTRAP_ROLE="standby" + STANDBY_HOST="${POD_BASENAME}-0" + fi + + HEADLESS_SERVICE_NAME="${POD_BASENAME}-headless" + + sed \ + -e "s|\${BOOTSTRAP_ROLE}|${BOOTSTRAP_ROLE}|g" \ + -e "s|\${STANDBY_HOST}|${STANDBY_HOST}|g" \ + -e "s|\${HEADLESS_SERVICE_NAME}|${HEADLESS_SERVICE_NAME}|g" \ + -e "s|\${REMOTES_API_PORT}|${REMOTES_API_PORT}|g" \ + -e "s|\${DATA_DIR}|${DATA_DIR}|g" \ + /config/config.yaml > "${SERVER_CONFIG}" +fi + +# Same as start-standalone.sh: official entrypoint handles server readiness, root host, +# optional env users/db, initdb.d, .init_completed, signals, and wait. +exec /scripts/docker-entrypoint.sh --config="${SERVER_CONFIG}" diff --git a/addons/dolt/scripts/switchover.sh b/addons/dolt/scripts/switchover.sh new file mode 100644 index 000000000..0bf58a661 --- /dev/null +++ b/addons/dolt/scripts/switchover.sh @@ -0,0 +1,67 @@ +#!/bin/sh +set -eu + +# KubeBlocks invokes this on the current primary only. We demote this instance +# then promote the candidate using the same configuration epoch (current + 1). + +if [ "${KB_SWITCHOVER_ROLE:-}" != "primary" ]; then + echo "switchover not triggered for primary, nothing to do, exit 0." + exit 0 +fi + +if [ -z "${KB_SWITCHOVER_CANDIDATE_FQDN:-}" ] && [ -z "${KB_SWITCHOVER_CANDIDATE_NAME:-}" ]; then + echo "KB_SWITCHOVER_CANDIDATE_FQDN or KB_SWITCHOVER_CANDIDATE_NAME is required for Dolt switchover" >&2 + exit 1 +fi + +CANDIDATE_HOST="${KB_SWITCHOVER_CANDIDATE_FQDN:-${KB_SWITCHOVER_CANDIDATE_NAME}}" + +echo "KB_SWITCHOVER_CANDIDATE_FQDN: ${KB_SWITCHOVER_CANDIDATE_FQDN}" +echo "KB_SWITCHOVER_CANDIDATE_NAME: ${KB_SWITCHOVER_CANDIDATE_NAME}" +echo "CANDIDATE_HOST: ${CANDIDATE_HOST}" + +dolt_local_q() { + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q "$1" +} + +dolt_remote_q() { + dolt --host "$1" --port 3306 --no-tls sql -q "$2" +} + +extract_scalar() { + _hdr=$1 + _out=$2 + printf '%s\n' "$_out" | awk -v hdr="$_hdr" '/^\|/{ + gsub(/^[ \t]*\|[ \t]*/, ""); + gsub(/[ \t]*\|[ \t]*$/, ""); + if ($1 != hdr && $1 != "") { print $1; exit } + }' +} + +epoch_out=$(dolt_local_q "select @@GLOBAL.dolt_cluster_role_epoch;" 2>/dev/null || true) +current_epoch=$(extract_scalar '@@GLOBAL.dolt_cluster_role_epoch' "$epoch_out") + +case "$current_epoch" in + '' | *[!0-9]*) + echo "Could not read @@GLOBAL.dolt_cluster_role_epoch from local instance" >&2 + exit 1 + ;; +esac + +new_epoch=$((current_epoch + 1)) + +role_out=$(dolt_local_q "select @@GLOBAL.dolt_cluster_role;" 2>/dev/null || true) +current_role=$(extract_scalar '@@GLOBAL.dolt_cluster_role' "$role_out") + +if [ "$current_role" != "primary" ]; then + echo "Local instance role is '${current_role}', expected primary; aborting" >&2 + exit 0 +fi + +echo "Demoting local primary to standby with epoch ${new_epoch}" +dolt_local_q "CALL dolt_assume_cluster_role('standby', ${new_epoch});" + +echo "Promoting candidate ${CANDIDATE_HOST} to primary with epoch ${new_epoch}" +dolt_remote_q "$CANDIDATE_HOST" "CALL dolt_assume_cluster_role('primary', ${new_epoch});" + +echo "Dolt switchover completed." diff --git a/addons/dolt/templates/NOTES.txt b/addons/dolt/templates/NOTES.txt new file mode 100644 index 000000000..e69de29bb diff --git a/addons/dolt/templates/_helpers.tpl b/addons/dolt/templates/_helpers.tpl new file mode 100644 index 000000000..bf652f652 --- /dev/null +++ b/addons/dolt/templates/_helpers.tpl @@ -0,0 +1,106 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "dolt.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "dolt.selectorLabels" -}} +app.kubernetes.io/name: {{ include "dolt.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "dolt.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "dolt.labels" -}} +helm.sh/chart: {{ include "dolt.chart" . }} +{{ include "dolt.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Common annotations. +*/}} +{{- define "dolt.annotations" -}} +{{ include "dolt.apiVersion" . }} +{{- end }} + +{{/* +API version annotation. +*/}} +{{- define "dolt.apiVersion" -}} +kubeblocks.io/crd-api-version: apps.kubeblocks.io/v1 +{{- end }} + +{{/* +ComponentDefinition name (replication / primary-standby) with chart version as suffix. +*/}} +{{- define "dolt.cmpdName" -}} +dolt-replication-{{ .Chart.Version }} +{{- end -}} + +{{/* +ComponentDefinition regexp for replication cmpd. +*/}} +{{- define "dolt.cmpdRegexpPattern" -}} +^dolt-replication$ +{{- end -}} + +{{/* +Config template name. +*/}} +{{- define "dolt.configTemplate" -}} +dolt-config-template-{{ .Chart.Version }} +{{- end }} + +{{/* +Script template name. +*/}} +{{- define "dolt.scriptTemplate" -}} +dolt-script-template-{{ .Chart.Version }} +{{- end }} + +{{/* +Standalone ComponentDefinition name. +*/}} +{{- define "dolt.standaloneCmpdName" -}} +dolt-standalone-{{ .Chart.Version }} +{{- end -}} + +{{/* +Standalone ComponentDefinition regexp. +*/}} +{{- define "dolt.standaloneCmpdRegexpPattern" -}} +^dolt-standalone$ +{{- end -}} + +{{/* +Standalone config template name. +*/}} +{{- define "dolt.standaloneConfigTemplate" -}} +dolt-standalone-config-template-{{ .Chart.Version }} +{{- end }} + +{{/* +Generate scripts configmap. +*/}} +{{- define "dolt.extend.scripts" -}} +{{- range $path, $_ := $.Files.Glob "scripts/**" }} +{{ $path | base }}: |- +{{- $.Files.Get $path | nindent 2 }} +{{- end }} +{{- end }} diff --git a/addons/dolt/templates/clusterdefinition.yaml b/addons/dolt/templates/clusterdefinition.yaml new file mode 100644 index 000000000..af4024e94 --- /dev/null +++ b/addons/dolt/templates/clusterdefinition.yaml @@ -0,0 +1,19 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: ClusterDefinition +metadata: + name: dolt + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.annotations" . | nindent 4 }} +spec: + topologies: + - name: standalone + default: true + components: + - name: dolt + compDef: {{ include "dolt.standaloneCmpdRegexpPattern" . }} + - name: replication + components: + - name: dolt + compDef: {{ include "dolt.cmpdRegexpPattern" . }} diff --git a/addons/dolt/templates/cmpd-repl.yaml b/addons/dolt/templates/cmpd-repl.yaml new file mode 100644 index 000000000..65ecb52b8 --- /dev/null +++ b/addons/dolt/templates/cmpd-repl.yaml @@ -0,0 +1,117 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: ComponentDefinition +metadata: + name: {{ include "dolt.cmpdName" . }} + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.annotations" . | nindent 4 }} +spec: + provider: community + description: {{ .Chart.Description }} + serviceKind: {{ .Chart.Name }} + serviceVersion: {{ .Chart.AppVersion }} + updateStrategy: Parallel + podManagementPolicy: Parallel + replicasLimit: + minReplicas: 2 + maxReplicas: 2 + runtime: + containers: + - name: dolt + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/sh + - -c + - /scripts/start.sh + ports: + - containerPort: 3306 + name: mysql + - containerPort: {{ .Values.remotesApiPort }} + name: remotesapi + - containerPort: {{ .Values.metricsPort }} + name: metrics + env: + - name: CURRENT_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: REMOTES_API_PORT + value: {{ .Values.remotesApiPort | quote }} + - name: DOLT_ROOT_HOST + value: {{ .Values.auth.rootHost | quote}} + livenessProbe: + exec: + command: ["dolt", "--host", "127.0.0.1", "--port", "3306", "--no-tls", "sql", "-q", "select current_timestamp();"] + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + exec: + command: ["dolt", "--host", "127.0.0.1", "--port", "3306", "--no-tls", "sql", "-q", "select current_timestamp();"] + initialDelaySeconds: 40 + periodSeconds: 10 + volumeMounts: + - name: data + mountPath: {{ .Values.dataDir }} + - name: scripts + mountPath: /scripts + - name: config + mountPath: /config + volumes: + - name: data + needSnapshot: true + services: + - name: mysql + serviceName: dolt + spec: + ports: + - name: mysql + port: 3306 + targetPort: mysql + - name: remotesapi + port: {{ .Values.remotesApiPort }} + targetPort: remotesapi + - name: metrics + port: {{ .Values.metricsPort }} + targetPort: metrics + disableAutoProvision: false + configs: + - name: config + template: {{ include "dolt.configTemplate" . }} + namespace: {{ .Release.Namespace }} + volumeName: config + defaultMode: 0644 + restartOnFileChange: true + scripts: + - name: scripts + template: {{ include "dolt.scriptTemplate" . }} + namespace: {{ .Release.Namespace }} + volumeName: scripts + defaultMode: 0555 + roles: + - name: primary + updatePriority: 2 + participatesInQuorum: false + isExclusive: true + - name: standby + updatePriority: 1 + participatesInQuorum: false + lifecycleActions: + roleProbe: + exec: + container: dolt + command: + - /bin/sh + - -c + - | + role=$(dolt --host 127.0.0.1 --no-tls sql -q "select @@GLOBAL.dolt_cluster_role;" 2>/dev/null \ + | awk '/^\|/{gsub(/^[ \t]*\|[ \t]*/,""); gsub(/[ \t]*\|[ \t]*$/,""); if($1 != "@@GLOBAL.dolt_cluster_role" && $1 != "") {print $1; exit}}') + echo -n "$role" + switchover: + exec: + container: dolt + command: + - /bin/sh + - -c + - /scripts/switchover.sh > /tmp/switchover.log 2>&1 diff --git a/addons/dolt/templates/cmpd-standalone.yaml b/addons/dolt/templates/cmpd-standalone.yaml new file mode 100644 index 000000000..6c49b7c33 --- /dev/null +++ b/addons/dolt/templates/cmpd-standalone.yaml @@ -0,0 +1,108 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: ComponentDefinition +metadata: + name: {{ include "dolt.standaloneCmpdName" . }} + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.annotations" . | nindent 4 }} +spec: + provider: community + description: {{ .Chart.Description }} + serviceKind: {{ .Chart.Name }} + serviceVersion: {{ .Chart.AppVersion }} + replicasLimit: + minReplicas: 1 + maxReplicas: 1 + serviceRefDeclarations: + - name: mysql-source + serviceRefDeclarationSpecs: + - serviceKind: mysql + serviceVersion: "^*" + optional: true + vars: + - name: MYSQL_SOURCE_HOST + valueFrom: + serviceRefVarRef: + name: mysql-source + optional: true + host: Required + - name: MYSQL_SOURCE_PORT + valueFrom: + serviceRefVarRef: + name: mysql-source + optional: true + port: Required + - name: MYSQL_SOURCE_USER + valueFrom: + serviceRefVarRef: + name: mysql-source + optional: true + username: Optional + - name: MYSQL_SOURCE_PASSWORD + valueFrom: + serviceRefVarRef: + name: mysql-source + optional: true + password: Optional + runtime: + containers: + - name: dolt + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/sh + - -c + - /scripts/start-standalone.sh + ports: + - containerPort: 3306 + name: mysql + - containerPort: {{ .Values.metricsPort }} + name: metrics + env: + - name: DOLT_ROOT_HOST + value: {{ .Values.auth.rootHost | quote}} + livenessProbe: + exec: + command: ["dolt", "--host", "127.0.0.1", "--port", "3306", "--no-tls", "sql", "-q", "select current_timestamp();"] + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + exec: + command: ["dolt", "--host", "127.0.0.1", "--port", "3306", "--no-tls", "sql", "-q", "select current_timestamp();"] + initialDelaySeconds: 40 + periodSeconds: 10 + volumeMounts: + - name: data + mountPath: {{ .Values.dataDir }} + - name: scripts + mountPath: /scripts + - name: config + mountPath: /config + volumes: + - name: data + needSnapshot: true + services: + - name: mysql + serviceName: dolt + spec: + ports: + - name: mysql + port: 3306 + targetPort: mysql + - name: metrics + port: {{ .Values.metricsPort }} + targetPort: metrics + disableAutoProvision: false + configs: + - name: config + template: {{ include "dolt.standaloneConfigTemplate" . }} + namespace: {{ .Release.Namespace }} + volumeName: config + defaultMode: 0644 + restartOnFileChange: true + scripts: + - name: scripts + template: {{ include "dolt.scriptTemplate" . }} + namespace: {{ .Release.Namespace }} + volumeName: scripts + defaultMode: 0555 diff --git a/addons/dolt/templates/cmpv.yaml b/addons/dolt/templates/cmpv.yaml new file mode 100644 index 000000000..daa005559 --- /dev/null +++ b/addons/dolt/templates/cmpv.yaml @@ -0,0 +1,30 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: ComponentVersion +metadata: + name: dolt + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.apiVersion" . | nindent 4 }} +spec: + compatibilityRules: + - compDefs: + - {{ include "dolt.cmpdRegexpPattern" . }} + - {{ include "dolt.standaloneCmpdRegexpPattern" . }} + releases: + - replica-1.84.0 + - compDefs: + - {{ include "dolt.standaloneCmpdRegexpPattern" . }} + releases: + - standalone-1.84.0 + releases: + - name: replica-1.84.0 + serviceVersion: 1.84.0 + images: + dolt: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + roleProbe: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + switchover: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + - name: standalone-1.84.0 + serviceVersion: 1.84.0 + images: + dolt: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} \ No newline at end of file diff --git a/addons/dolt/templates/config-template-standalone.yaml b/addons/dolt/templates/config-template-standalone.yaml new file mode 100644 index 000000000..eb0e321be --- /dev/null +++ b/addons/dolt/templates/config-template-standalone.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "dolt.standaloneConfigTemplate" . }} + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.annotations" . | nindent 4 }} +data: + config.yaml: | + {{- .Files.Get "config/dolt-standalone.yaml.tpl" | nindent 4 }} diff --git a/addons/dolt/templates/config-template.yaml b/addons/dolt/templates/config-template.yaml new file mode 100644 index 000000000..eec8562a1 --- /dev/null +++ b/addons/dolt/templates/config-template.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "dolt.configTemplate" . }} + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.annotations" . | nindent 4 }} +data: + config.yaml: | + {{- .Files.Get "config/dolt-server.yaml.tpl" | nindent 4 }} diff --git a/addons/dolt/templates/script-template.yaml b/addons/dolt/templates/script-template.yaml new file mode 100644 index 000000000..99f08cbcb --- /dev/null +++ b/addons/dolt/templates/script-template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "dolt.scriptTemplate" . }} + labels: + {{- include "dolt.labels" . | nindent 4 }} + annotations: + {{- include "dolt.annotations" . | nindent 4 }} +data: + {{- with include "dolt.extend.scripts" . }} + {{- . | nindent 2 }} + {{- end }} diff --git a/addons/dolt/values.yaml b/addons/dolt/values.yaml new file mode 100644 index 000000000..8d527cc9d --- /dev/null +++ b/addons/dolt/values.yaml @@ -0,0 +1,16 @@ +nameOverride: "" + +image: + registry: docker.io + repository: dolthub/dolt-sql-server + tag: 1.84.0 + pullPolicy: IfNotPresent + +dataDir: /var/lib/dolt + +remotesApiPort: 50051 + +metricsPort: 11228 + +auth: + rootHost: "%" \ No newline at end of file diff --git a/examples/dolt/README.md b/examples/dolt/README.md new file mode 100644 index 000000000..403608743 --- /dev/null +++ b/examples/dolt/README.md @@ -0,0 +1,168 @@ +# Dolt + +[Dolt](https://github.com/dolthub/dolt) is a MySQL-compatible SQL database with Git-style versioning. This addon runs `dolt sql-server` in replication (primary / standby) or standalone mode. + +## Prerequisites + +- Kubernetes cluster >= v1.21 +- `kubectl` installed, refer to [K8s Install Tools](https://kubernetes.io/docs/tasks/tools/) +- Helm, refer to [Installing Helm](https://helm.sh/docs/intro/install/) +- KubeBlocks installed and running, refer to [Install Kubeblocks](../docs/prerequisites.md) +- Dolt Addon Enabled, refer to [Install Addons](../docs/install-addon.md) +- Create K8s Namespace `demo`, to keep resources created in this tutorial isolated: + + ```bash + kubectl create ns demo + ``` + +## Install addon + +```bash +helm install dolt ./addons/dolt -n kb-system +``` + +Adjust `kb-system` to the namespace where your KubeBlocks addons are installed. + +## Examples + +### Create a primary / standby cluster + +[`cluster-replication.yaml`](cluster-replication.yaml) creates a two-replica Dolt replication cluster (Dolt primary + standby). + +```bash +kubectl apply -f examples/dolt/cluster-replication.yaml +``` + +Check supported versions: + +```bash +kubectl get cmpv dolt +``` + +Match `spec.componentSpecs[].componentDef` to the addon (`dolt-replication` or `dolt-standalone`), and `serviceVersion` to a release in that ComponentVersion (for example `1.84.0`). + +#### Switchover + +After the cluster is healthy, trigger a planned switchover so another replica becomes primary. + +**With explicit candidate** — [`switchover-specified-instance.yaml`](switchover-specified-instance.yaml): + +```bash +kubectl apply -f examples/dolt/switchover-specified-instance.yaml +``` + +Edit `instanceName` / `candidateName` to match your pod names (`-dolt-`). Get pods: + +```bash +kubectl get pods -n demo -l app.kubernetes.io/instance=dolt-repl -L kubeblocks.io/role +``` + +### Create a single-node (standalone) cluster + +[`cluster-standalone.yaml`](cluster-standalone.yaml) uses the standalone ComponentDefinition (exactly one replica). + +```bash +kubectl apply -f examples/dolt/cluster-standalone.yaml +``` + +Switchover does not apply to standalone topology (single replica). + +### Create a Dolt replica of a MySQL cluster + +Dolt can act as a [versioned MySQL replica](https://docs.dolthub.com/introduction/getting-started/versioned-mysql-replica) — it consumes MySQL binlog events and automatically creates Dolt commits, giving you time travel, diff, and rollback on every write that happens on the MySQL primary. + +[`cluster-mysql-replica.yaml`](cluster-mysql-replica.yaml) deploys two clusters: + +1. **mysql-source** — a standalone MySQL 8.0 cluster (the replication primary) +2. **dolt-mysql-replica** — a standalone Dolt cluster that references the MySQL cluster via `serviceRefs` + +```bash +kubectl apply -f examples/dolt/cluster-mysql-replica.yaml +``` + +Wait for both clusters to become ready: + +```bash +kubectl -n demo get cluster mysql-source dolt-mysql-replica +``` + +#### Verify replication + +Connect to the **MySQL primary** and create some data: + +```bash +# get the mysql root password +MYSQL_ROOT_PASSWORD=$(kubectl -n demo get secret mysql-source-mysql-account-root \ + -o jsonpath='{.data.password}' | base64 -d) + +# connect to MySQL +kubectl -n demo exec -it mysql-source-mysql-0 -- \ + mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e " + CREATE DATABASE foo; + USE foo; + CREATE TABLE t (c1 INT PRIMARY KEY, c2 INT); + INSERT INTO t VALUES (1, 100), (2, 200), (3, 300); + " +``` + +Connect to the **Dolt replica** and confirm the data has replicated: + +```bash +kubectl -n demo exec -it dolt-mysql-replica-dolt-0 -- \ + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q "USE foo; SELECT * FROM t;" +``` + +You should see the three rows from the MySQL primary. + +#### Inspect the Dolt commit log + +Every replicated transaction becomes a Dolt commit. Query the version history: + +```bash +kubectl -n demo exec -it dolt-mysql-replica-dolt-0 -- \ + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q "USE foo; SELECT * FROM dolt_log;" +``` + +#### Inspect diffs + +See exactly what changed in the last commit using the `dolt_diff()` table function: + +```bash +kubectl -n demo exec -it dolt-mysql-replica-dolt-0 -- \ + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q " + USE foo; + SELECT * FROM dolt_diff('HEAD^', 'HEAD', 't'); + " +``` + +#### Find and revert a bad change + +Make a bad change on the MySQL primary: + +```bash +kubectl -n demo exec -it mysql-source-mysql-0 -- \ + mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e " + USE foo; + UPDATE t SET c2 = 0 WHERE c1 = 2; + " +``` + +On the Dolt replica, identify the bad commit and generate a revert patch: + +```bash +kubectl -n demo exec -it dolt-mysql-replica-dolt-0 -- \ + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q " + USE foo; + SELECT * FROM dolt_diff('HEAD^', 'HEAD', 't'); + SELECT statement FROM dolt_patch('HEAD', 'HEAD^'); + " +``` + +The `dolt_patch()` output gives you the exact SQL statements to run on the MySQL primary to revert the change. Apply them to the primary to restore the original data. + +#### Check replication status + +```bash +kubectl -n demo exec -it dolt-mysql-replica-dolt-0 -- \ + dolt --host 127.0.0.1 --port 3306 --no-tls sql -q "SHOW REPLICA STATUS" +``` diff --git a/examples/dolt/cluster-mysql-replica.yaml b/examples/dolt/cluster-mysql-replica.yaml new file mode 100644 index 000000000..f54a79fa4 --- /dev/null +++ b/examples/dolt/cluster-mysql-replica.yaml @@ -0,0 +1,102 @@ +--- +# Step 1: Create a standalone MySQL cluster as the replication source. +# Ensure GTID mode is enabled (default in MySQL 8.0+). +apiVersion: apps.kubeblocks.io/v1 +kind: Cluster +metadata: + name: mysql-source + namespace: demo +spec: + terminationPolicy: Delete + clusterDef: mysql + topology: semisync + componentSpecs: + - name: mysql + serviceVersion: "8.0.35" + disableExporter: true + replicas: 1 + resources: + limits: + cpu: "0.5" + memory: 0.5Gi + requests: + cpu: "0.5" + memory: 0.5Gi + volumeClaimTemplates: + - name: data + spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi +--- +# Step 2: Set BINLOG_FORMAT to ROW when cluster is running +apiVersion: operations.kubeblocks.io/v1alpha1 +kind: OpsRequest +metadata: + name: mysql-reconfiguring + namespace: demo +spec: + # Specifies the name of the Cluster resource that this operation is targeting. + clusterName: mysql-source + force: false + reconfigures: + - componentName: mysql + parameters: + # Represents the name of the parameter that is to be updated. + - key: binlog_format + value: 'ROW' + # wait up to 2 min till cluster is running + preConditionDeadlineSeconds: 120 + type: Reconfiguring +--- +# Step 3: When MySQL is running, create a standalone Dolt cluster configured as a MySQL binlog replica. +# The serviceRef "mysql-source" binds to the MySQL cluster above, injecting +# MYSQL_SOURCE_HOST, MYSQL_SOURCE_PORT, MYSQL_SOURCE_USER, MYSQL_SOURCE_PASSWORD +# into the Dolt container. The start script detects these and runs: +# SET @@PERSIST.server_id = 2; +# CHANGE REPLICATION SOURCE TO SOURCE_HOST=..., SOURCE_USER=..., SOURCE_PORT=...; +# START REPLICA; +apiVersion: apps.kubeblocks.io/v1 +kind: Cluster +metadata: + name: dolt-mysql-replica + namespace: demo +spec: + terminationPolicy: Delete + clusterDef: dolt + topology: standalone + componentSpecs: + - name: dolt + serviceVersion: "1.84.0" + disableExporter: true + replicas: 1 + resources: + limits: + cpu: "0.5" + memory: 0.5Gi + requests: + cpu: "0.5" + memory: 0.5Gi + volumeClaimTemplates: + - name: data + spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + serviceRefs: + - name: mysql-source + namespace: demo + clusterServiceSelector: + cluster: mysql-source + credential: + name: root + component: mysql + service: + service: "" + component: mysql diff --git a/examples/dolt/cluster-replication.yaml b/examples/dolt/cluster-replication.yaml new file mode 100644 index 000000000..e3f6340f8 --- /dev/null +++ b/examples/dolt/cluster-replication.yaml @@ -0,0 +1,32 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: Cluster +metadata: + name: dolt-repl + namespace: demo +spec: + # Valid options are: [DoNotTerminate, Delete, WipeOut] + terminationPolicy: Delete + clusterDef: dolt + topology: replication + componentSpecs: + - name: dolt + # Must match a release in ComponentVersion `dolt` (e.g. replica-1.84.0). + serviceVersion: "1.84.0" + disableExporter: false + replicas: 2 + resources: + limits: + cpu: "0.5" + memory: 0.5Gi + requests: + cpu: "0.5" + memory: 0.5Gi + volumeClaimTemplates: + - name: data + spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi diff --git a/examples/dolt/cluster-standalone.yaml b/examples/dolt/cluster-standalone.yaml new file mode 100644 index 000000000..25eddae35 --- /dev/null +++ b/examples/dolt/cluster-standalone.yaml @@ -0,0 +1,30 @@ +apiVersion: apps.kubeblocks.io/v1 +kind: Cluster +metadata: + name: dolt-standalone + namespace: demo +spec: + terminationPolicy: Delete + clusterDef: dolt + topology: standalone + componentSpecs: + - name: dolt + serviceVersion: "1.84.0" + disableExporter: false + replicas: 1 + resources: + limits: + cpu: "0.5" + memory: 0.5Gi + requests: + cpu: "0.5" + memory: 0.5Gi + volumeClaimTemplates: + - name: data + spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi diff --git a/examples/dolt/podMonitor.yaml b/examples/dolt/podMonitor.yaml new file mode 100644 index 000000000..82159ffc3 --- /dev/null +++ b/examples/dolt/podMonitor.yaml @@ -0,0 +1,25 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + labels: + release: prometheus + name: dolt-metrics + namespace: demo +spec: + jobLabel: app.kubernetes.io/managed-by + namespaceSelector: + matchNames: + - demo + podMetricsEndpoints: + - path: /metrics + port: metrics + scheme: http + podTargetLabels: + - app.kubernetes.io/instance + - app.kubernetes.io/managed-by + - apps.kubeblocks.io/component-name + - apps.kubeblocks.io/pod-name + selector: + matchLabels: + app.kubernetes.io/managed-by: kubeblocks + apps.kubeblocks.io/component-name: dolt \ No newline at end of file diff --git a/examples/dolt/switchover-specified-instance.yaml b/examples/dolt/switchover-specified-instance.yaml new file mode 100644 index 000000000..e2e606404 --- /dev/null +++ b/examples/dolt/switchover-specified-instance.yaml @@ -0,0 +1,15 @@ +apiVersion: operations.kubeblocks.io/v1alpha1 +kind: OpsRequest +metadata: + name: dolt-switchover + namespace: demo +spec: + clusterName: dolt-repl + type: Switchover + switchover: + - componentName: dolt + # Instance whose role is transferred (typically the current primary). + instanceName: dolt-repl-dolt-0 + # Optional: explicitly choose which pod becomes the new primary. + # The name must match a pod in the `dolt` component. + candidateName: dolt-repl-dolt-1 From 9ecad681cb3b8dbd58368351729667dcaf96d4f3 Mon Sep 17 00:00:00 2001 From: Shanshan Date: Wed, 8 Apr 2026 11:17:52 +0800 Subject: [PATCH 2/3] chore: update dolt description --- addons/dolt/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dolt/Chart.yaml b/addons/dolt/Chart.yaml index 8469c2767..9fb0478b2 100644 --- a/addons/dolt/Chart.yaml +++ b/addons/dolt/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: dolt -description: A Helm chart for Kubernetes +description: Dolt is a MySQL-compatible SQL database with Git-style versioning. # A chart can be either an 'application' or a 'library' chart. # From b16fb8b4e6467b5efbcf0e87c9572b7e4eeb400b Mon Sep 17 00:00:00 2001 From: shanshanying Date: Wed, 8 Apr 2026 06:37:18 +0000 Subject: [PATCH 3/3] chore: auto generated files --- .github/CODEOWNERS | 3 +++ README.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cd9db5dc2..3efa878c8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,6 +13,9 @@ addons-cluster/apecloud-mysql/ @xuriwuyun @leon-inf @apecloud/kb-reviewers @apec addons/clickhouse/ @leon-inf @apecloud/kb-reviewers @apecloud/kb-addon-reviewers addons-cluster/clickhouse/ @leon-inf @apecloud/kb-reviewers @apecloud/kb-addon-reviewers +addons/dolt/ @leon-inf @apecloud/kb-reviewers @apecloud/kb-addon-reviewers +addons-cluster/dolt/ @leon-inf @apecloud/kb-reviewers @apecloud/kb-addon-reviewers + addons/elasticsearch/ @iziang @vipshop @leon-inf @apecloud/kb-reviewers @apecloud/kb-addon-reviewers addons-cluster/elasticsearch/ @iziang @vipshop @leon-inf @apecloud/kb-reviewers @apecloud/kb-addon-reviewers diff --git a/README.md b/README.md index 139a9a672..6a59415e0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ KubeBlocks add-ons. | ---- | ---- | ----------- | ----------- | | apecloud-mysql | apecloud-mysql-8.0.30
wescale-0.2.7 | ApeCloud MySQL is a database that is compatible with MySQL syntax and achieves high availability through the utilization of the RAFT consensus protocol. | xuriwuyun | | clickhouse | clickhouse-22.3.18
clickhouse-22.3.20
clickhouse-22.6.1
clickhouse-22.8.21
clickhouse-24.8.3
clickhouse-25.4.4
clickhouse-25.9.7 | ClickHouse is an open-source column-oriented OLAP database management system. Use it to boost your database performance while providing linear scalability and hardware efficiency. | ApeCloud | +| dolt | dolt-1.84.0 | Dolt is a MySQL-compatible SQL database with Git-style versioning. | | | elasticsearch | elasticsearch-6.8.23
elasticsearch-7.10.1
elasticsearch-7.10.2
elasticsearch-7.7.1
elasticsearch-7.8.1
elasticsearch-8.1.3
elasticsearch-8.15.5
elasticsearch-8.8.2
kibana-6.8.23
kibana-7.10.1
kibana-7.10.2
kibana-7.7.1
kibana-7.8.1
kibana-8.1.3
kibana-8.15.5
kibana-8.8.2
kibana-8.9.1 | Elasticsearch is a distributed, RESTful search engine optimized for speed and relevance on production-scale workloads. | iziang vipshop | | etcd | etcd-3.5.15
etcd-3.5.6
etcd-3.6.1 | Etcd is a strongly consistent, distributed key-value store that provides a reliable way to store data that needs to be accessed by a distributed system or cluster of machines. | ApeCloud | | falkordb | falkordb-4.12.5
falkordb-cluster-4.12.5
falkordb-sent-4.12.5 | FalkorDB is an in-memory graph database based on Redis. | ApeCloud dudizimber |