From 71601fe5bdcbb2c6d56e97a793949d5d295dfc5a Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Tue, 26 May 2020 17:42:00 +0200 Subject: [PATCH 01/11] METAL-1890 proxySQL --- helm/db-instances/templates/dbinstance.yaml | 40 ++++ helm/db-operator/templates/config.yaml | 5 + helm/db-operator/templates/crd.yaml | 124 ++++++----- helm/db-operator/values.yaml | 87 ++++++++ pkg/apis/kci/v1alpha1/dbinstance_types.go | 59 +++-- .../kci/v1alpha1/zz_generated.deepcopy.go | 51 ++++- pkg/utils/proxy/cloudproxy.go | 79 +++---- pkg/utils/proxy/create.go | 15 +- pkg/utils/proxy/helper.go | 31 +++ pkg/utils/proxy/proxysql.go | 204 ++++++++++++++++++ pkg/utils/proxy/types.go | 12 +- 11 files changed, 577 insertions(+), 130 deletions(-) create mode 100644 pkg/utils/proxy/helper.go create mode 100644 pkg/utils/proxy/proxysql.go diff --git a/helm/db-instances/templates/dbinstance.yaml b/helm/db-instances/templates/dbinstance.yaml index c7b19162..a7941fa7 100644 --- a/helm/db-instances/templates/dbinstance.yaml +++ b/helm/db-instances/templates/dbinstance.yaml @@ -52,6 +52,18 @@ spec: backupHost: {{ $value.generic.backupHost }} {{- end }} {{- end }} + {{- if $value.percona }} + percona: + servers: + {{- range $server := $value.percona.servers }} + - {{ $server }} + {{- end }} + port: {{ $value.percona.port }} + maxConn: {{ $value.percona.maxConn }} + monitorUserSecretRef: + Name: {{ $name }}-monitoruser-secret + Namespace: {{ $operatorNs }} + {{- end }} {{- if not $value.existingAdminSecret }} --- apiVersion: v1 @@ -83,5 +95,33 @@ data: config: | {{ $value.google.configMap.data | indent 4 }} {{- end }} +{{- if $value.percona }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }}-monitoruser-secret + namespace: {{ $operatorNs }} + labels: + chart: {{ $chart }} + release: {{ $release.Name }} + heritage: {{ $heritage }} +type: Opaque +data: + user: {{ $value.percona.monitoruser.name | b64enc }} + password: {{ $value.percona.monitoruser.password | b64enc }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $name }}-proxysql-config-template + namespace: {{ $operatorNs }} + labels: + chart: {{ $chart }} + release: {{ $release.Name }} + heritage: {{ $heritage }} +data: + config: | +{{ $value.percona.proxyConfig.data | indent 4 }} +{{- end }} {{- end }} {{- end }} \ No newline at end of file diff --git a/helm/db-operator/templates/config.yaml b/helm/db-operator/templates/config.yaml index 16a39437..140c04a0 100644 --- a/helm/db-operator/templates/config.yaml +++ b/helm/db-operator/templates/config.yaml @@ -17,6 +17,11 @@ data: {{ toYaml .Values.config.instance.google.proxy.nodeSelector | indent 10 }} image: {{ .Values.config.instance.google.proxy.image }} generic: {} + percona: + proxy: + image: {{ .Values.config.instance.percona.proxy.image }} + configTemplate: +{{ toYaml .Values.config.instance.percona.proxy.configTemplate | indent 12 }} backup: nodeSelector: {{ toYaml .Values.config.backup.nodeSelector | indent 8 }} diff --git a/helm/db-operator/templates/crd.yaml b/helm/db-operator/templates/crd.yaml index 18e9f7ba..07d583cf 100644 --- a/helm/db-operator/templates/crd.yaml +++ b/helm/db-operator/templates/crd.yaml @@ -68,60 +68,76 @@ spec: subresources: status: {} preserveUnknownFields: true - # validation: - # openAPIV3Schema: - # description: DbInstance is the Schema for the dbinstances API - # properties: - # apiVersion: - # description: 'APIVersion defines the versioned schema of this representation - # of an object. Servers should convert recognized schemas to the latest - # internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' - # type: string - # kind: - # description: 'Kind is a string value representing the REST resource this - # object represents. Servers may infer this from the endpoint the client - # submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' - # type: string - # metadata: - # type: object - # spec: - # description: DbInstanceSpec defines the desired state of DbInstance - # type: object - # properties: - # engine: - # type: string - # adminSecretRef: - # type: map - # properties: - # Namespace: - # type: string - # Name: - # type: string - # backup: - # type: object - # generic: - # type: object - # properties: - # host: - # type: string - # port: - # type: integer - # google: - # type: object - # properties: - # instance: - # type: string - # configmapRef: - # type: map - # properties: - # Namespace: - # type: string - # Name: - # type: string - # status: - # description: DbInstanceStatus defines the observed state of DbInstance - # type: object - # type: object + validation: + openAPIV3Schema: + description: DbInstance is the Schema for the dbinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: DbInstanceSpec defines the desired state of DbInstance + type: object + required: + - engine + - adminSecretRef + properties: + engine: + type: string + adminSecretRef: + type: object + properties: + Namespace: + type: string + Name: + type: string + backup: + type: object + generic: + type: object + properties: + host: + type: string + port: + type: integer + google: + type: object + properties: + instance: + type: string + configmapRef: + type: object + properties: + Namespace: + type: string + Name: + type: string + percona: + type: object + required: + - servers + - port + - monitorUserSecretRef + properties: + servers: + type: array + port: + type: integer + monitorUserSecretRef: + type: object + status: + description: DbInstanceStatus defines the observed state of DbInstance + type: object + type: object version: v1alpha1 versions: - name: v1alpha1 diff --git a/helm/db-operator/values.yaml b/helm/db-operator/values.yaml index 338e05bb..39a181d5 100644 --- a/helm/db-operator/values.yaml +++ b/helm/db-operator/values.yaml @@ -33,6 +33,93 @@ config: nodeSelector: {} image: gcr.io/cloudsql-docker/gce-proxy:1.16 generic: {} + percona: + proxy: + image: severalnines/proxysql:2.0 + configTemplate: |- + datadir="/var/lib/proxysql" + + admin_variables= + { + mysql_ifaces="0.0.0.0:6032" + refresh_interval=2000 + web_enabled=false + web_port=6080 + stats_credentials="stats:admin" + } + + mysql_variables= + { + threads=4 + max_connections=2048 + default_query_delay=0 + default_query_timeout=36000000 + have_compress=true + poll_timeout=2000 + interfaces="0.0.0.0:6033;/tmp/proxysql.sock" + default_schema="information_schema" + stacksize=1048576 + server_version="5.7.28" + connect_timeout_server=10000 + monitor_history=60000 + monitor_connect_interval=200000 + monitor_ping_interval=200000 + ping_interval_server_msec=10000 + ping_timeout_server=200 + commands_stats=true + sessions_sort=true + monitor_username="$MONITOR_USERNAME" + monitor_password="$MONITOR_PASSWORD" + monitor_galera_healthcheck_interval=2000 + monitor_galera_healthcheck_timeout=800 + } + + mysql_galera_hostgroups = + ( + { + writer_hostgroup=10 + backup_writer_hostgroup=20 + reader_hostgroup=30 + offline_hostgroup=9999 + max_writers=2 + writer_is_also_reader=2 + max_transactions_behind=30 + active=1 + } + ) + + mysql_servers = + ( + {{- range . }} + { address="{{.Host}}", port={{.Port}}, hostgroup=10, max_connections={{.MaxConn}} }, + {{- end }} + ) + + mysql_query_rules = + ( + { + rule_id=100 + active=1 + match_pattern="^SELECT .* FOR UPDATE" + destination_hostgroup=10 + apply=1 + }, + { + rule_id=200 + active=1 + match_pattern="^SELECT .*" + destination_hostgroup=20 + apply=1 + }, + { + rule_id=300 + active=1 + match_pattern=".*" + destination_hostgroup=10 + apply=1 + } + ) + # do not remove this empty line above backup: nodeSelector: {} postgres: diff --git a/pkg/apis/kci/v1alpha1/dbinstance_types.go b/pkg/apis/kci/v1alpha1/dbinstance_types.go index 2c7278cb..f5db297e 100644 --- a/pkg/apis/kci/v1alpha1/dbinstance_types.go +++ b/pkg/apis/kci/v1alpha1/dbinstance_types.go @@ -13,12 +13,17 @@ import ( // +k8s:openapi-gen=true type DbInstanceSpec struct { // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file - Engine string `json:"engine"` - AdminUserSecret types.NamespacedName `json:"adminSecretRef"` - Backup DbInstanceBackup `json:"backup"` - Monitoring DbInstanceMonitoring `json:"monitoring"` - Google *GoogleInstance `json:"google,omitempty"` - Generic *GenericInstance `json:"generic,omitempty"` + Engine string `json:"engine"` + AdminUserSecret types.NamespacedName `json:"adminSecretRef"` + Backup DbInstanceBackup `json:"backup"` + Monitoring DbInstanceMonitoring `json:"monitoring"` + DbInstanceSource `json:",inline"` +} + +type DbInstanceSource struct { + Google *GoogleInstance `json:"google,omitempty" protobuf:"bytes,1,opt,name=google"` + Generic *GenericInstance `json:"generic,omitempty" protobuf:"bytes,2,opt,name=generic"` + Percona *PerconaCluster `json:"percona,omitempty" protobuf:"bytes,3,opt,name=percona"` } // DbInstanceStatus defines the observed state of DbInstance @@ -38,6 +43,13 @@ type GoogleInstance struct { ConfigmapName types.NamespacedName `json:"configmapRef"` } +type PerconaCluster struct { + ServerList []string `json:"servers"` // hostgroup: host address + Port int32 `json:"port"` + MaxConnection int16 `json:"maxConn"` + MonitorUserSecret types.NamespacedName `json:"monitorUserSecretRef"` +} + // GenericInstance is used when instance type is generic // and describes necessary informations to use instance // generic instance can be any backend, it must be reachable by described address and port @@ -101,14 +113,30 @@ func (dbin *DbInstance) ValidateEngine() error { // returns error when more than one backend types are defined // or when no backend type is defined func (dbin *DbInstance) ValidateBackend() error { - if (dbin.Spec.Google != nil) && (dbin.Spec.Generic != nil) { - return errors.New("more than one instance type defined") - } + source := dbin.Spec.DbInstanceSource - if (dbin.Spec.Google == nil) && (dbin.Spec.Generic == nil) { + if (source.Google == nil) && (source.Generic == nil) && (source.Percona == nil) { return errors.New("no instance type defined") } + numVolumes := 0 + + if source.Google != nil { + numVolumes++ + } + + if source.Generic != nil { + numVolumes++ + } + + if source.Percona != nil { + numVolumes++ + } + + if numVolumes > 1 { + return errors.New("may not specify more than 1 instance type") + } + return nil } @@ -119,14 +147,21 @@ func (dbin *DbInstance) GetBackendType() (string, error) { if err != nil { return "", err } - if dbin.Spec.Google != nil { + + source := dbin.Spec.DbInstanceSource + + if source.Google != nil { return "google", nil } - if dbin.Spec.Generic != nil { + if source.Generic != nil { return "generic", nil } + if source.Percona != nil { + return "percona", nil + } + return "", errors.New("no backend type defined") } diff --git a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go index 4ca54bfd..20ee9f3f 100644 --- a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go @@ -222,11 +222,8 @@ func (in *DbInstanceMonitoring) DeepCopy() *DbInstanceMonitoring { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DbInstanceSpec) DeepCopyInto(out *DbInstanceSpec) { +func (in *DbInstanceSource) DeepCopyInto(out *DbInstanceSource) { *out = *in - out.AdminUserSecret = in.AdminUserSecret - out.Backup = in.Backup - out.Monitoring = in.Monitoring if in.Google != nil { in, out := &in.Google, &out.Google *out = new(GoogleInstance) @@ -237,6 +234,31 @@ func (in *DbInstanceSpec) DeepCopyInto(out *DbInstanceSpec) { *out = new(GenericInstance) **out = **in } + if in.Percona != nil { + in, out := &in.Percona, &out.Percona + *out = new(PerconaCluster) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DbInstanceSource. +func (in *DbInstanceSource) DeepCopy() *DbInstanceSource { + if in == nil { + return nil + } + out := new(DbInstanceSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DbInstanceSpec) DeepCopyInto(out *DbInstanceSpec) { + *out = *in + out.AdminUserSecret = in.AdminUserSecret + out.Backup = in.Backup + out.Monitoring = in.Monitoring + in.DbInstanceSource.DeepCopyInto(&out.DbInstanceSource) return } @@ -312,3 +334,24 @@ func (in *GoogleInstance) DeepCopy() *GoogleInstance { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PerconaCluster) DeepCopyInto(out *PerconaCluster) { + *out = *in + if in.ServerList != nil { + in, out := &in.ServerList, &out.ServerList + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PerconaCluster. +func (in *PerconaCluster) DeepCopy() *PerconaCluster { + if in == nil { + return nil + } + out := new(PerconaCluster) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/utils/proxy/cloudproxy.go b/pkg/utils/proxy/cloudproxy.go index 4b8673a6..f2b06e57 100644 --- a/pkg/utils/proxy/cloudproxy.go +++ b/pkg/utils/proxy/cloudproxy.go @@ -11,6 +11,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// CloudProxy for google sql instance +type CloudProxy struct { + NamePrefix string + Namespace string + InstanceConnectionName string + AccessSecretName string + Engine string + Port int32 + Labels map[string]string +} + var conf = config.Config{} const instanceAccessSecretVolumeName string = "gcloud-secret" @@ -40,7 +51,7 @@ func (cp *CloudProxy) buildService() (*v1.Service, error) { } func (cp *CloudProxy) buildDeployment() (*v1apps.Deployment, error) { - spec, err := deploymentSpec(cp.InstanceConnectionName, cp.Port, cp.Labels, cp.AccessSecretName) + spec, err := cp.deploymentSpec() if err != nil { return nil, err } @@ -57,27 +68,35 @@ func (cp *CloudProxy) buildDeployment() (*v1apps.Deployment, error) { }, Spec: spec, }, nil - } -func deploymentSpec(conn string, port int32, labels map[string]string, instanceAccessSecret string) (v1apps.DeploymentSpec, error) { +func (cp *CloudProxy) deploymentSpec() (v1apps.DeploymentSpec, error) { var replicas int32 = 2 - container, err := container(conn, port) + container, err := cp.container() if err != nil { return v1apps.DeploymentSpec{}, err } - volumes := buildVolumes(instanceAccessSecret) + volumes := []v1.Volume{ + v1.Volume{ + Name: instanceAccessSecretVolumeName, + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: cp.AccessSecretName, + }, + }, + }, + } return v1apps.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ - MatchLabels: labels, + MatchLabels: cp.Labels, }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: labels, + Labels: cp.Labels, }, Spec: v1.PodSpec{ Containers: []v1.Container{container}, @@ -85,17 +104,17 @@ func deploymentSpec(conn string, port int32, labels map[string]string, instanceA RestartPolicy: v1.RestartPolicyAlways, Volumes: volumes, Affinity: &v1.Affinity{ - PodAntiAffinity: podAntiAffinity(labels), + PodAntiAffinity: podAntiAffinity(cp.Labels), }, }, }, }, nil } -func container(conn string, port int32) (v1.Container, error) { +func (cp *CloudProxy) container() (v1.Container, error) { RunAsUser := int64(2) AllowPrivilegeEscalation := false - instanceArg := fmt.Sprintf("-instances=%s=tcp:0.0.0.0:%s", conn, strconv.FormatInt(int64(port), 10)) + instanceArg := fmt.Sprintf("-instances=%s=tcp:0.0.0.0:%s", cp.InstanceConnectionName, strconv.FormatInt(int64(cp.Port), 10)) return v1.Container{ Name: "cloudsql-proxy", @@ -110,7 +129,7 @@ func container(conn string, port int32) (v1.Container, error) { Ports: []v1.ContainerPort{ v1.ContainerPort{ Name: "sqlport", - ContainerPort: port, + ContainerPort: cp.Port, Protocol: v1.ProtocolTCP, }, }, @@ -123,40 +142,6 @@ func container(conn string, port int32) (v1.Container, error) { }, nil } -func buildVolumes(instanceAccessSecretName string) []v1.Volume { - return []v1.Volume{ - v1.Volume{ - Name: instanceAccessSecretVolumeName, - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: instanceAccessSecretName, - }, - }, - }, - } -} - -func podAntiAffinity(labelSelector map[string]string) *v1.PodAntiAffinity { - var weight int32 = 1 - return &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - v1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: labelSelector, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ - v1.WeightedPodAffinityTerm{ - PodAffinityTerm: v1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: labelSelector, - }, - TopologyKey: "failure-domain.beta.kubernetes.io/zone", - }, - Weight: weight, - }, - }, - } +func (cp *CloudProxy) buildConfigMap() (v1.ConfigMap, error) { + return v1.ConfigMap{}, nil } diff --git a/pkg/utils/proxy/create.go b/pkg/utils/proxy/create.go index 76d2fa2c..78b1d17c 100644 --- a/pkg/utils/proxy/create.go +++ b/pkg/utils/proxy/create.go @@ -10,7 +10,7 @@ import ( func BuildDeployment(proxy Proxy) (*v1apps.Deployment, error) { deploy, err := proxy.buildDeployment() if err != nil { - logrus.Error("failed building cloudsql proxy deployment") + logrus.Error("failed building proxy deployment") return nil, err } @@ -21,9 +21,20 @@ func BuildDeployment(proxy Proxy) (*v1apps.Deployment, error) { func BuildService(proxy Proxy) (*v1.Service, error) { svc, err := proxy.buildService() if err != nil { - logrus.Error("failed building cloudsql proxy service") + logrus.Error("failed building proxy service") return nil, err } return svc, nil } + +// BuildConfigmap builds kubernetes configmap object used by proxy container of the database +func BuildConfigmap(proxy Proxy) (*v1.ConfigMap, error) { + cm, err := proxy.buildConfigMap() + if err != nil { + logrus.Error("failed building proxy configmap") + return nil, err + } + + return &cm, nil +} diff --git a/pkg/utils/proxy/helper.go b/pkg/utils/proxy/helper.go new file mode 100644 index 00000000..c20a84b6 --- /dev/null +++ b/pkg/utils/proxy/helper.go @@ -0,0 +1,31 @@ +package proxy + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func podAntiAffinity(labelSelector map[string]string) *v1.PodAntiAffinity { + var weight int32 = 1 + return &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + v1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: labelSelector, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ + v1.WeightedPodAffinityTerm{ + PodAffinityTerm: v1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: labelSelector, + }, + TopologyKey: "failure-domain.beta.kubernetes.io/zone", + }, + Weight: weight, + }, + }, + } +} diff --git a/pkg/utils/proxy/proxysql.go b/pkg/utils/proxy/proxysql.go new file mode 100644 index 00000000..bd1e11b2 --- /dev/null +++ b/pkg/utils/proxy/proxysql.go @@ -0,0 +1,204 @@ +package proxy + +import ( + "bytes" + "html/template" + "log" + "strconv" + + v1apps "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ProxySQL for percona cluster +type ProxySQL struct { + NamePrefix string + Namespace string + Servers []string + MaxConn int16 + MonitorUserSecretName string + Engine string + Port int32 + Labels map[string]string +} + +func (ps *ProxySQL) buildService() (*v1.Service, error) { + return &v1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ps.NamePrefix + "-svc", + Namespace: ps.Namespace, + Labels: ps.Labels, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + v1.ServicePort{ + Name: ps.Engine, + Protocol: v1.ProtocolTCP, + Port: ps.Port, + }, + }, + Selector: ps.Labels, + }, + }, nil +} + +func (ps *ProxySQL) buildDeployment() (*v1apps.Deployment, error) { + spec, err := ps.deploymentSpec() + if err != nil { + return nil, err + } + + return &v1apps.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: "extensions/apps", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ps.NamePrefix + "-proxysql", + Namespace: ps.Namespace, + Labels: ps.Labels, + }, + Spec: spec, + }, nil +} + +func (ps *ProxySQL) deploymentSpec() (v1apps.DeploymentSpec, error) { + var replicas int32 = 2 + + proxyContainer, err := ps.proxyContainer() + if err != nil { + return v1apps.DeploymentSpec{}, err + } + + volumes := []v1.Volume{ + v1.Volume{ + Name: "name", //TODO change + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: ps.MonitorUserSecretName, + }, + }, + }, + v1.Volume{ + Name: "proxysql-config", + VolumeSource: v1.VolumeSource{ + ConfigMap: &v1.ConfigMapVolumeSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: ps.NamePrefix + "-proxysql-config", + }, + }, + }, + }, + } + + return v1apps.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: ps.Labels, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: ps.Labels, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{proxyContainer}, + NodeSelector: conf.Instances.Percona.ProxyConfig.NodeSelector, + RestartPolicy: v1.RestartPolicyAlways, + Volumes: volumes, + Affinity: &v1.Affinity{ + PodAntiAffinity: podAntiAffinity(ps.Labels), + }, + }, + }, + }, nil +} + +func (ps *ProxySQL) initContainer() (v1.Container, error) { + return v1.Container{ + Name: "config-generator", + Image: "alpine", + ImagePullPolicy: v1.PullIfNotPresent, + VolumeMounts: []v1.VolumeMount{ + v1.VolumeMount{ + Name: "proxysql-config-template", + MountPath: "/tmp/proxysql.cnf.tmpl", + SubPath: "proxysql.cnf.tmpl", + }, + v1.VolumeMount{ + Name: "shared-data", + MountPath: "/mnt", + } + }, + }, nil +} + +func (ps *ProxySQL) proxyContainer() (v1.Container, error) { + RunAsUser := int64(2) + AllowPrivilegeEscalation := false + + return v1.Container{ + Name: "proxysql", + Image: conf.Instances.Percona.ProxyConfig.Image, + SecurityContext: &v1.SecurityContext{ + RunAsUser: &RunAsUser, + AllowPrivilegeEscalation: &AllowPrivilegeEscalation, + }, + ImagePullPolicy: v1.PullIfNotPresent, + Ports: []v1.ContainerPort{ + v1.ContainerPort{ + Name: "sqlport", + ContainerPort: ps.Port, + Protocol: v1.ProtocolTCP, + }, + }, + VolumeMounts: []v1.VolumeMount{ + v1.VolumeMount{ + Name: "proxysql-config", + MountPath: "/etc/proxysql.cnf", // TODO path + }, + }, + }, nil +} + +func (ps *ProxySQL) buildConfigMap() (v1.ConfigMap, error) { + configTmpl := conf.Instances.Percona.ProxyConfig.ConfigTemplate + "\n" + t := template.Must(template.New("config").Parse(configTmpl)) + + type backendInfo struct { + Host, Port, MaxConn string + } + + var infos []backendInfo + for _, s := range ps.Servers { + info := backendInfo{s, strconv.FormatInt(int64(ps.Port), 10), strconv.FormatInt(int64(ps.MaxConn), 10)} + infos = append(infos, info) + } + + var outputBuf bytes.Buffer + if err := t.Execute(&outputBuf, infos); err != nil { + log.Fatal(err) + } + + data := map[string]string{ + "proxysql.cnf.tmpl": outputBuf.String(), + } + + return v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ps.Namespace, + Name: ps.NamePrefix + "-proxysql-config-template", + Labels: ps.Labels, + }, + Data: data, + }, nil +} + diff --git a/pkg/utils/proxy/types.go b/pkg/utils/proxy/types.go index 3b5f62dd..d9a31d3b 100644 --- a/pkg/utils/proxy/types.go +++ b/pkg/utils/proxy/types.go @@ -5,19 +5,9 @@ import ( v1 "k8s.io/api/core/v1" ) -// CloudProxy for google sql instance -type CloudProxy struct { - NamePrefix string - Namespace string - InstanceConnectionName string - AccessSecretName string - Engine string - Port int32 - Labels map[string]string -} - // Proxy for database type Proxy interface { buildDeployment() (*v1apps.Deployment, error) buildService() (*v1.Service, error) + buildConfigMap() (v1.ConfigMap, error) } From b38e6eddbd41153c89697f39a2e0bf160e201340 Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Wed, 27 May 2020 15:30:03 +0200 Subject: [PATCH 02/11] rollback config type change and hard coded templates for proxysql --- helm/db-operator/templates/config.yaml | 2 - helm/db-operator/values.yaml | 84 ------------ pkg/apis/kci/v1alpha1/database_types.go | 7 +- .../kci/v1alpha1/zz_generated.deepcopy.go | 1 + pkg/config/test/config_ok.yaml | 3 + pkg/config/types.go | 5 + pkg/utils/proxy/proxysql.go | 128 ++++++++++++------ pkg/utils/proxy/proxysql/template.go | 95 +++++++++++++ 8 files changed, 198 insertions(+), 127 deletions(-) create mode 100644 pkg/utils/proxy/proxysql/template.go diff --git a/helm/db-operator/templates/config.yaml b/helm/db-operator/templates/config.yaml index 140c04a0..186f1cb6 100644 --- a/helm/db-operator/templates/config.yaml +++ b/helm/db-operator/templates/config.yaml @@ -20,8 +20,6 @@ data: percona: proxy: image: {{ .Values.config.instance.percona.proxy.image }} - configTemplate: -{{ toYaml .Values.config.instance.percona.proxy.configTemplate | indent 12 }} backup: nodeSelector: {{ toYaml .Values.config.backup.nodeSelector | indent 8 }} diff --git a/helm/db-operator/values.yaml b/helm/db-operator/values.yaml index 39a181d5..fb63c96a 100644 --- a/helm/db-operator/values.yaml +++ b/helm/db-operator/values.yaml @@ -36,90 +36,6 @@ config: percona: proxy: image: severalnines/proxysql:2.0 - configTemplate: |- - datadir="/var/lib/proxysql" - - admin_variables= - { - mysql_ifaces="0.0.0.0:6032" - refresh_interval=2000 - web_enabled=false - web_port=6080 - stats_credentials="stats:admin" - } - - mysql_variables= - { - threads=4 - max_connections=2048 - default_query_delay=0 - default_query_timeout=36000000 - have_compress=true - poll_timeout=2000 - interfaces="0.0.0.0:6033;/tmp/proxysql.sock" - default_schema="information_schema" - stacksize=1048576 - server_version="5.7.28" - connect_timeout_server=10000 - monitor_history=60000 - monitor_connect_interval=200000 - monitor_ping_interval=200000 - ping_interval_server_msec=10000 - ping_timeout_server=200 - commands_stats=true - sessions_sort=true - monitor_username="$MONITOR_USERNAME" - monitor_password="$MONITOR_PASSWORD" - monitor_galera_healthcheck_interval=2000 - monitor_galera_healthcheck_timeout=800 - } - - mysql_galera_hostgroups = - ( - { - writer_hostgroup=10 - backup_writer_hostgroup=20 - reader_hostgroup=30 - offline_hostgroup=9999 - max_writers=2 - writer_is_also_reader=2 - max_transactions_behind=30 - active=1 - } - ) - - mysql_servers = - ( - {{- range . }} - { address="{{.Host}}", port={{.Port}}, hostgroup=10, max_connections={{.MaxConn}} }, - {{- end }} - ) - - mysql_query_rules = - ( - { - rule_id=100 - active=1 - match_pattern="^SELECT .* FOR UPDATE" - destination_hostgroup=10 - apply=1 - }, - { - rule_id=200 - active=1 - match_pattern="^SELECT .*" - destination_hostgroup=20 - apply=1 - }, - { - rule_id=300 - active=1 - match_pattern=".*" - destination_hostgroup=10 - apply=1 - } - ) - # do not remove this empty line above backup: nodeSelector: {} postgres: diff --git a/pkg/apis/kci/v1alpha1/database_types.go b/pkg/apis/kci/v1alpha1/database_types.go index ff297d91..0ba1808b 100644 --- a/pkg/apis/kci/v1alpha1/database_types.go +++ b/pkg/apis/kci/v1alpha1/database_types.go @@ -25,9 +25,10 @@ type DatabaseSpec struct { type DatabaseStatus struct { // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html - Phase string `json:"phase"` - Status bool `json:"status"` - InstanceRef *DbInstance `json:"instanceRef"` + Phase string `json:"phase"` + Status bool `json:"status"` + InstanceRef *DbInstance `json:"instanceRef"` + MonitorUserSecretName string `json:"monitorUserSecret,omitempty"` } // DatabaseBackup defines the desired state of backup and schedule diff --git a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go index 20ee9f3f..3be43117 100644 --- a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go @@ -343,6 +343,7 @@ func (in *PerconaCluster) DeepCopyInto(out *PerconaCluster) { *out = make([]string, len(*in)) copy(*out, *in) } + out.MonitorUserSecret = in.MonitorUserSecret return } diff --git a/pkg/config/test/config_ok.yaml b/pkg/config/test/config_ok.yaml index b3995bdb..78e255ab 100644 --- a/pkg/config/test/config_ok.yaml +++ b/pkg/config/test/config_ok.yaml @@ -5,6 +5,9 @@ instance: nodeSelector: {} image: gcr.io/cloudsql-docker/gce-proxy:1.11 generic: {} + percona: + proxy: + image: severalnines/proxysql:2.0 backup: nodeSelector: {} postgres: diff --git a/pkg/config/types.go b/pkg/config/types.go index fde95c55..a6c7db2b 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -10,6 +10,7 @@ type Config struct { type instanceConfig struct { Google googleInstanceConfig `yaml:"google"` Generic genericInstanceConfig `yaml:"generic"` + Percona perconaClusterConfig `yaml:"percona"` } type googleInstanceConfig struct { @@ -21,6 +22,10 @@ type genericInstanceConfig struct { // TODO } +type perconaClusterConfig struct { + ProxyConfig proxyConfig `yaml:"proxy"` +} + type proxyConfig struct { NodeSelector map[string]string `yaml:"nodeSelector"` Image string `yaml:"image"` diff --git a/pkg/utils/proxy/proxysql.go b/pkg/utils/proxy/proxysql.go index bd1e11b2..05e2c158 100644 --- a/pkg/utils/proxy/proxysql.go +++ b/pkg/utils/proxy/proxysql.go @@ -6,6 +6,8 @@ import ( "log" "strconv" + proxysql "github.com/kloeckner-i/db-operator/pkg/utils/proxy/proxysql" + v1apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -23,6 +25,17 @@ type ProxySQL struct { Labels map[string]string } +const sqlPort = 6033 +const adminPort = 6032 + +func (ps *ProxySQL) configMapName() string { + return ps.NamePrefix + "-proxysql-config-template" +} + +func (ps *ProxySQL) serviceName() string { + return ps.NamePrefix + "-proxysql" +} + func (ps *ProxySQL) buildService() (*v1.Service, error) { return &v1.Service{ TypeMeta: metav1.TypeMeta{ @@ -30,7 +43,7 @@ func (ps *ProxySQL) buildService() (*v1.Service, error) { APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: ps.NamePrefix + "-svc", + Name: ps.serviceName(), Namespace: ps.Namespace, Labels: ps.Labels, }, @@ -39,7 +52,7 @@ func (ps *ProxySQL) buildService() (*v1.Service, error) { v1.ServicePort{ Name: ps.Engine, Protocol: v1.ProtocolTCP, - Port: ps.Port, + Port: sqlPort, }, }, Selector: ps.Labels, @@ -70,6 +83,11 @@ func (ps *ProxySQL) buildDeployment() (*v1apps.Deployment, error) { func (ps *ProxySQL) deploymentSpec() (v1apps.DeploymentSpec, error) { var replicas int32 = 2 + configGenContainer, err := ps.configGeneratorContainer() + if err != nil { + return v1apps.DeploymentSpec{}, err + } + proxyContainer, err := ps.proxyContainer() if err != nil { return v1apps.DeploymentSpec{}, err @@ -77,19 +95,31 @@ func (ps *ProxySQL) deploymentSpec() (v1apps.DeploymentSpec, error) { volumes := []v1.Volume{ v1.Volume{ - Name: "name", //TODO change + Name: "shared-data", VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: ps.MonitorUserSecretName, - }, + EmptyDir: &v1.EmptyDirVolumeSource{}, }, }, v1.Volume{ - Name: "proxysql-config", + Name: "proxysql-config-template", VolumeSource: v1.VolumeSource{ ConfigMap: &v1.ConfigMapVolumeSource{ LocalObjectReference: v1.LocalObjectReference{ - Name: ps.NamePrefix + "-proxysql-config", + Name: ps.configMapName(), + }, + }, + }, + }, + v1.Volume{ + Name: "monitoruser-secret", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: ps.MonitorUserSecretName, + Items: []v1.KeyToPath{ + v1.KeyToPath{ + Key: "password", + Path: "monitoruser-password", + }, }, }, }, @@ -106,10 +136,11 @@ func (ps *ProxySQL) deploymentSpec() (v1apps.DeploymentSpec, error) { Labels: ps.Labels, }, Spec: v1.PodSpec{ - Containers: []v1.Container{proxyContainer}, - NodeSelector: conf.Instances.Percona.ProxyConfig.NodeSelector, - RestartPolicy: v1.RestartPolicyAlways, - Volumes: volumes, + InitContainers: []v1.Container{configGenContainer}, + Containers: []v1.Container{proxyContainer}, + NodeSelector: conf.Instances.Percona.ProxyConfig.NodeSelector, + RestartPolicy: v1.RestartPolicyAlways, + Volumes: volumes, Affinity: &v1.Affinity{ PodAntiAffinity: podAntiAffinity(ps.Labels), }, @@ -118,11 +149,22 @@ func (ps *ProxySQL) deploymentSpec() (v1apps.DeploymentSpec, error) { }, nil } -func (ps *ProxySQL) initContainer() (v1.Container, error) { +func (ps *ProxySQL) configGeneratorContainer() (v1.Container, error) { return v1.Container{ Name: "config-generator", Image: "alpine", ImagePullPolicy: v1.PullIfNotPresent, + Command: []string{"sh", "-c", "apk add --update gettext && MONITOR_PASSWORD=$(cat /run/secrets/monitoruser-password) envsubst < /tmp/proxysql.cnf.tmpl > /mnt/proxysql.cnf"}, + Env: []v1.EnvVar{ + v1.EnvVar{ + Name: "MONITOR_USERNAME", ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{Name: ps.MonitorUserSecretName}, + Key: "user", + }, + }, + }, + }, VolumeMounts: []v1.VolumeMount{ v1.VolumeMount{ Name: "proxysql-config-template", @@ -130,57 +172,68 @@ func (ps *ProxySQL) initContainer() (v1.Container, error) { SubPath: "proxysql.cnf.tmpl", }, v1.VolumeMount{ - Name: "shared-data", + Name: "shared-data", MountPath: "/mnt", - } + }, + v1.VolumeMount{ + Name: "monitoruser-secret", + MountPath: "/run/secrets/monitoruser-password", + SubPath: "monitoruser-password", + ReadOnly: true, + }, }, }, nil } func (ps *ProxySQL) proxyContainer() (v1.Container, error) { - RunAsUser := int64(2) - AllowPrivilegeEscalation := false - return v1.Container{ - Name: "proxysql", - Image: conf.Instances.Percona.ProxyConfig.Image, - SecurityContext: &v1.SecurityContext{ - RunAsUser: &RunAsUser, - AllowPrivilegeEscalation: &AllowPrivilegeEscalation, - }, + Name: "proxysql", + Image: conf.Instances.Percona.ProxyConfig.Image, ImagePullPolicy: v1.PullIfNotPresent, Ports: []v1.ContainerPort{ v1.ContainerPort{ - Name: "sqlport", - ContainerPort: ps.Port, + Name: "sql", + ContainerPort: sqlPort, + Protocol: v1.ProtocolTCP, + }, + v1.ContainerPort{ + Name: "admin", + ContainerPort: adminPort, Protocol: v1.ProtocolTCP, }, }, VolumeMounts: []v1.VolumeMount{ v1.VolumeMount{ - Name: "proxysql-config", - MountPath: "/etc/proxysql.cnf", // TODO path + Name: "shared-data", + MountPath: "/etc/proxysql.cnf", + SubPath: "proxysql.cnf", }, }, }, nil } func (ps *ProxySQL) buildConfigMap() (v1.ConfigMap, error) { - configTmpl := conf.Instances.Percona.ProxyConfig.ConfigTemplate + "\n" + configTmpl := proxysql.PerconaMysqlConfigTemplate t := template.Must(template.New("config").Parse(configTmpl)) - type backendInfo struct { - Host, Port, MaxConn string + var backends []proxysql.Backend + for _, s := range ps.Servers { + backend := proxysql.Backend{ + Host: s, + Port: strconv.FormatInt(int64(ps.Port), 10), + MaxConn: strconv.FormatInt(int64(ps.MaxConn), 10), + } + backends = append(backends, backend) } - var infos []backendInfo - for _, s := range ps.Servers { - info := backendInfo{s, strconv.FormatInt(int64(ps.Port), 10), strconv.FormatInt(int64(ps.MaxConn), 10)} - infos = append(infos, info) + config := proxysql.Config{ + AdminPort: strconv.FormatInt(int64(adminPort), 10), + SQLPort: strconv.FormatInt(int64(sqlPort), 10), + Backends: backends, } var outputBuf bytes.Buffer - if err := t.Execute(&outputBuf, infos); err != nil { + if err := t.Execute(&outputBuf, config); err != nil { log.Fatal(err) } @@ -195,10 +248,9 @@ func (ps *ProxySQL) buildConfigMap() (v1.ConfigMap, error) { }, ObjectMeta: metav1.ObjectMeta{ Namespace: ps.Namespace, - Name: ps.NamePrefix + "-proxysql-config-template", + Name: ps.configMapName(), Labels: ps.Labels, }, Data: data, }, nil } - diff --git a/pkg/utils/proxy/proxysql/template.go b/pkg/utils/proxy/proxysql/template.go new file mode 100644 index 00000000..1c4a26ce --- /dev/null +++ b/pkg/utils/proxy/proxysql/template.go @@ -0,0 +1,95 @@ +package proxysql + +type Config struct { + AdminPort string + SQLPort string + Backends []Backend +} +type Backend struct { + Host, Port, MaxConn string +} + +const PerconaMysqlConfigTemplate = ` +datadir="/var/lib/proxysql" + +admin_variables= +{ + mysql_ifaces="0.0.0.0:{{.AdminPort}}" + refresh_interval=2000 + web_enabled=false + web_port=6080 + stats_credentials="stats:admin" +} + +mysql_variables= +{ + threads=4 + max_connections=2048 + default_query_delay=0 + default_query_timeout=36000000 + have_compress=true + poll_timeout=2000 + interfaces="0.0.0.0:{{.SQLPort}};/tmp/proxysql.sock" + default_schema="information_schema" + stacksize=1048576 + server_version="5.7.28" + connect_timeout_server=10000 + monitor_history=60000 + monitor_connect_interval=200000 + monitor_ping_interval=200000 + ping_interval_server_msec=10000 + ping_timeout_server=200 + commands_stats=true + sessions_sort=true + monitor_username="$MONITOR_USERNAME" + monitor_password="$MONITOR_PASSWORD" + monitor_galera_healthcheck_interval=2000 + monitor_galera_healthcheck_timeout=800 +} + +mysql_galera_hostgroups = +( + { + writer_hostgroup=10 + backup_writer_hostgroup=20 + reader_hostgroup=30 + offline_hostgroup=9999 + max_writers=2 + writer_is_also_reader=2 + max_transactions_behind=30 + active=1 + } +) + +mysql_servers = +( + {{- range .Backends }} + { address="{{.Host}}", port={{.Port}}, hostgroup=10, max_connections={{.MaxConn}} }, + {{- end }} +) + +mysql_query_rules = +( + { + rule_id=100 + active=1 + match_pattern="^SELECT .* FOR UPDATE" + destination_hostgroup=10 + apply=1 + }, + { + rule_id=200 + active=1 + match_pattern="^SELECT .*" + destination_hostgroup=20 + apply=1 + }, + { + rule_id=300 + active=1 + match_pattern=".*" + destination_hostgroup=10 + apply=1 + } +) +` From d79d64dc06d8b1146700be4ce3af1506aa8acfbf Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Wed, 27 May 2020 15:55:16 +0200 Subject: [PATCH 03/11] proxySQL support for percona cluster --- pkg/controller/database/helper_test.go | 16 ++-- pkg/controller/database/proxyHelper.go | 30 ++++++-- pkg/controller/database/reconcileProxy.go | 74 ++++++++++++++++++- pkg/controller/dbinstance/proxyHelper.go | 12 ++- .../dbinstance/reconcileDbInstance.go | 8 ++ pkg/utils/proxy/cloudproxy.go | 4 +- pkg/utils/proxy/create.go | 2 +- pkg/utils/proxy/proxysql.go | 4 +- pkg/utils/proxy/types.go | 2 +- 9 files changed, 129 insertions(+), 23 deletions(-) diff --git a/pkg/controller/database/helper_test.go b/pkg/controller/database/helper_test.go index a8fc433f..e725e400 100644 --- a/pkg/controller/database/helper_test.go +++ b/pkg/controller/database/helper_test.go @@ -17,9 +17,11 @@ func newPostgresTestDbInstanceCr() kciv1alpha1.DbInstance { return kciv1alpha1.DbInstance{ Spec: kciv1alpha1.DbInstanceSpec{ Engine: "postgres", - Generic: &kciv1alpha1.GenericInstance{ - Host: test.GetPostgresHost(), - Port: test.GetPostgresPort(), + DbInstanceSource: kciv1alpha1.DbInstanceSource{ + Generic: &kciv1alpha1.GenericInstance{ + Host: test.GetPostgresHost(), + Port: test.GetPostgresPort(), + }, }, }, Status: kciv1alpha1.DbInstanceStatus{Info: info}, @@ -55,9 +57,11 @@ func newMysqlTestDbCr() *kciv1alpha1.Database { InstanceRef: &kciv1alpha1.DbInstance{ Spec: kciv1alpha1.DbInstanceSpec{ Engine: "mysql", - Generic: &kciv1alpha1.GenericInstance{ - Host: test.GetMysqlHost(), - Port: test.GetMysqlPort(), + DbInstanceSource: kciv1alpha1.DbInstanceSource{ + Generic: &kciv1alpha1.GenericInstance{ + Host: test.GetMysqlHost(), + Port: test.GetMysqlPort(), + }, }, }, Status: kciv1alpha1.DbInstanceStatus{Info: info}, diff --git a/pkg/controller/database/proxyHelper.go b/pkg/controller/database/proxyHelper.go index 6b172e75..dc24541d 100644 --- a/pkg/controller/database/proxyHelper.go +++ b/pkg/controller/database/proxyHelper.go @@ -31,15 +31,15 @@ func determinProxyType(dbcr *kciv1alpha1.Database) (proxy.Proxy, error) { return nil, err } + portString := instance.Status.Info["DB_PORT"] + port, err := strconv.Atoi(portString) + if err != nil { + logrus.Errorf("can not convert DB_PORT to int - %s", err) + return nil, err + } + switch backend { case "google": - portString := instance.Status.Info["DB_PORT"] - port, err := strconv.Atoi(portString) - if err != nil { - logrus.Errorf("can not convert DB_PORT to int - %s", err) - return nil, err - } - labels := map[string]string{ "app": "cloudproxy", "db-name": dbcr.Name, @@ -54,6 +54,22 @@ func determinProxyType(dbcr *kciv1alpha1.Database) (proxy.Proxy, error) { Port: int32(port), Labels: kci.LabelBuilder(labels), }, nil + case "percona": + labels := map[string]string{ + "app": "proxysql", + "db-name": dbcr.Name, + } + + return &proxy.ProxySQL{ + NamePrefix: "db-" + dbcr.Name, + Namespace: dbcr.Namespace, + Servers: instance.Spec.Percona.ServerList, + MaxConn: instance.Spec.Percona.MaxConnection, + MonitorUserSecretName: dbcr.Status.MonitorUserSecretName, + Engine: engine, + Port: int32(port), + Labels: kci.LabelBuilder(labels), + }, nil default: err := errors.New("not supported backend type") return nil, err diff --git a/pkg/controller/database/reconcileProxy.go b/pkg/controller/database/reconcileProxy.go index 16d49cc4..5354e7ba 100644 --- a/pkg/controller/database/reconcileProxy.go +++ b/pkg/controller/database/reconcileProxy.go @@ -2,24 +2,59 @@ package database import ( "context" + kciv1alpha1 "github.com/kloeckner-i/db-operator/pkg/apis/kci/v1alpha1" + "github.com/kloeckner-i/db-operator/pkg/utils/kci" proxy "github.com/kloeckner-i/db-operator/pkg/utils/proxy" "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" ) func (r *ReconcileDatabase) createProxy(dbcr *kciv1alpha1.Database) error { - if backend, _ := dbcr.GetBackendType(); backend != "google" { + backend, _ := dbcr.GetBackendType() + if backend == "generic" { logrus.Infof("DB: namespace=%s, name=%s %s proxy creation is not yet implemented skipping...", dbcr.Namespace, dbcr.Name, backend) return nil } + if backend == "percona" { + err := r.replicateMonitorUserSecret(dbcr) + if err != nil { + return err + } + } + proxyInterface, err := determinProxyType(dbcr) if err != nil { return err } + + // create proxy configmap + cm, err := proxy.BuildConfigmap(proxyInterface) + if err != nil { + return err + } + if cm != nil { // if configmap is not null + err = r.client.Create(context.TODO(), cm) + if err != nil { + if k8serrors.IsAlreadyExists(err) { + // if resource already exists, update + err = r.client.Update(context.TODO(), cm) + if err != nil { + logrus.Errorf("DB: namespace=%s, name=%s failed updating proxy configmap", dbcr.Namespace, dbcr.Name) + return err + } + } else { + // failed creating configmap + logrus.Errorf("DB: namespace=%s, name=%s failed updating proxy configmap", dbcr.Namespace, dbcr.Name) + return err + } + } + } + // create proxy deployment deploy, err := proxy.BuildDeployment(proxyInterface) if err != nil { @@ -65,3 +100,40 @@ func (r *ReconcileDatabase) createProxy(dbcr *kciv1alpha1.Database) error { logrus.Infof("DB: namespace=%s, name=%s proxy created", dbcr.Namespace, dbcr.Name) return nil } + +func (r *ReconcileDatabase) replicateMonitorUserSecret(dbcr *kciv1alpha1.Database) error { + dbin, err := dbcr.GetInstanceRef() + if err != nil { + return err + } + source := dbin.Spec.DbInstanceSource + + key := source.Percona.MonitorUserSecret + monitorUserSecret := &v1.Secret{} + + err = r.client.Get(context.TODO(), key, monitorUserSecret) + if err != nil { + logrus.Errorf("DB: namespace=%s, name=%s couldn't get monitor user secret - %s", dbcr.Namespace, dbcr.Name, err) + return err + } + + newSecret := kci.SecretBuilder(dbcr.Name+"-proxysql-monitoruser", dbcr.Namespace, monitorUserSecret.Data) + err = r.client.Create(context.TODO(), newSecret) + if err != nil { + if k8serrors.IsAlreadyExists(err) { + // if resource already exists, update + err = r.client.Update(context.TODO(), monitorUserSecret) + if err != nil { + logrus.Errorf("DB: namespace=%s, name=%s failed replicating monitor user secret", dbcr.Namespace, dbcr.Name) + return err + } + } else { + // failed to create deployment + logrus.Errorf("DB: namespace=%s, name=%s failed replicating monitor user secret", dbcr.Namespace, dbcr.Name) + return err + } + } + + dbcr.Status.MonitorUserSecretName = dbcr.Name + "-proxysql-monitoruser" + return nil +} diff --git a/pkg/controller/dbinstance/proxyHelper.go b/pkg/controller/dbinstance/proxyHelper.go index 47f5b723..24cdacf8 100644 --- a/pkg/controller/dbinstance/proxyHelper.go +++ b/pkg/controller/dbinstance/proxyHelper.go @@ -28,7 +28,13 @@ func determinProxyType(dbin *kciv1alpha1.DbInstance) (proxy.Proxy, error) { return nil, err } - if dbin.Spec.Google != nil { + backend, err := dbin.GetBackendType() + if err != nil { + return nil, err + } + + switch backend { + case "google": portString := dbin.Status.Info["DB_PORT"] port, err := strconv.Atoi(portString) if err != nil { @@ -50,7 +56,7 @@ func determinProxyType(dbin *kciv1alpha1.DbInstance) (proxy.Proxy, error) { Port: int32(port), Labels: kci.LabelBuilder(labels), }, nil + default: + return nil, ErrNoProxySupport } - - return nil, ErrNoProxySupport } diff --git a/pkg/controller/dbinstance/reconcileDbInstance.go b/pkg/controller/dbinstance/reconcileDbInstance.go index 86e8b9cf..fa5c801c 100644 --- a/pkg/controller/dbinstance/reconcileDbInstance.go +++ b/pkg/controller/dbinstance/reconcileDbInstance.go @@ -52,6 +52,14 @@ func (r *ReconcileDbInstance) create(dbin *kciv1alpha1.DbInstance) error { User: cred.Username, Password: cred.Password, } + case "percona": + instance = &dbinstance.Generic{ + Host: dbin.Spec.Percona.ServerList[0], + Port: dbin.Spec.Percona.Port, + Engine: dbin.Spec.Engine, + User: cred.Username, + Password: cred.Password, + } default: return errors.New("not supported backend type") } diff --git a/pkg/utils/proxy/cloudproxy.go b/pkg/utils/proxy/cloudproxy.go index f2b06e57..1582792c 100644 --- a/pkg/utils/proxy/cloudproxy.go +++ b/pkg/utils/proxy/cloudproxy.go @@ -142,6 +142,6 @@ func (cp *CloudProxy) container() (v1.Container, error) { }, nil } -func (cp *CloudProxy) buildConfigMap() (v1.ConfigMap, error) { - return v1.ConfigMap{}, nil +func (cp *CloudProxy) buildConfigMap() (*v1.ConfigMap, error) { + return nil, nil } diff --git a/pkg/utils/proxy/create.go b/pkg/utils/proxy/create.go index 78b1d17c..3a6f500f 100644 --- a/pkg/utils/proxy/create.go +++ b/pkg/utils/proxy/create.go @@ -36,5 +36,5 @@ func BuildConfigmap(proxy Proxy) (*v1.ConfigMap, error) { return nil, err } - return &cm, nil + return cm, nil } diff --git a/pkg/utils/proxy/proxysql.go b/pkg/utils/proxy/proxysql.go index 05e2c158..4ab60fb2 100644 --- a/pkg/utils/proxy/proxysql.go +++ b/pkg/utils/proxy/proxysql.go @@ -212,7 +212,7 @@ func (ps *ProxySQL) proxyContainer() (v1.Container, error) { }, nil } -func (ps *ProxySQL) buildConfigMap() (v1.ConfigMap, error) { +func (ps *ProxySQL) buildConfigMap() (*v1.ConfigMap, error) { configTmpl := proxysql.PerconaMysqlConfigTemplate t := template.Must(template.New("config").Parse(configTmpl)) @@ -241,7 +241,7 @@ func (ps *ProxySQL) buildConfigMap() (v1.ConfigMap, error) { "proxysql.cnf.tmpl": outputBuf.String(), } - return v1.ConfigMap{ + return &v1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", diff --git a/pkg/utils/proxy/types.go b/pkg/utils/proxy/types.go index d9a31d3b..deed6011 100644 --- a/pkg/utils/proxy/types.go +++ b/pkg/utils/proxy/types.go @@ -9,5 +9,5 @@ import ( type Proxy interface { buildDeployment() (*v1apps.Deployment, error) buildService() (*v1.Service, error) - buildConfigMap() (v1.ConfigMap, error) + buildConfigMap() (*v1.ConfigMap, error) } From d77c0a58f05d789fb2eed169687864db81ffd2d1 Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Wed, 27 May 2020 16:05:22 +0200 Subject: [PATCH 04/11] cleanup --- helm/db-instances/templates/dbinstance.yaml | 13 ------------- pkg/config/types.go | 4 ---- 2 files changed, 17 deletions(-) diff --git a/helm/db-instances/templates/dbinstance.yaml b/helm/db-instances/templates/dbinstance.yaml index a7941fa7..1b89c192 100644 --- a/helm/db-instances/templates/dbinstance.yaml +++ b/helm/db-instances/templates/dbinstance.yaml @@ -109,19 +109,6 @@ type: Opaque data: user: {{ $value.percona.monitoruser.name | b64enc }} password: {{ $value.percona.monitoruser.password | b64enc }} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ $name }}-proxysql-config-template - namespace: {{ $operatorNs }} - labels: - chart: {{ $chart }} - release: {{ $release.Name }} - heritage: {{ $heritage }} -data: - config: | -{{ $value.percona.proxyConfig.data | indent 4 }} {{- end }} {{- end }} {{- end }} \ No newline at end of file diff --git a/pkg/config/types.go b/pkg/config/types.go index a6c7db2b..8309c6fb 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -22,10 +22,6 @@ type genericInstanceConfig struct { // TODO } -type perconaClusterConfig struct { - ProxyConfig proxyConfig `yaml:"proxy"` -} - type proxyConfig struct { NodeSelector map[string]string `yaml:"nodeSelector"` Image string `yaml:"image"` From f4b64675b28c4a39b2ae5bf5a48e678523830823 Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Wed, 27 May 2020 16:07:59 +0200 Subject: [PATCH 05/11] rollback cleanup --- pkg/config/types.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/config/types.go b/pkg/config/types.go index 8309c6fb..a6c7db2b 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -22,6 +22,10 @@ type genericInstanceConfig struct { // TODO } +type perconaClusterConfig struct { + ProxyConfig proxyConfig `yaml:"proxy"` +} + type proxyConfig struct { NodeSelector map[string]string `yaml:"nodeSelector"` Image string `yaml:"image"` From c3c665b50b9a385422462843a0feda2954cb090d Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Wed, 27 May 2020 16:55:46 +0200 Subject: [PATCH 06/11] fix lint warnings --- .travis.yml | 8 +++++--- pkg/apis/kci/v1alpha1/dbinstance_types.go | 3 +++ pkg/utils/proxy/proxysql/template.go | 6 ++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 73eacc8e..2ffbbe93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,9 @@ cache: - $GOPATH/pkg/mod jobs: - # allow_failures: - # - env: - # - ALLOW_FAIL=true + allow_failures: + - env: + - ALLOW_FAIL=true include: - stage: test name: "Unit Tests" @@ -37,6 +37,8 @@ jobs: - find . -type f -name "*\.go" | grep -v "zz_generated" | while read line; do golint -set_exit_status $line || exit 1; done + env: + - ALLOW_FAIL=true - stage: test name: "Go Vet" script: diff --git a/pkg/apis/kci/v1alpha1/dbinstance_types.go b/pkg/apis/kci/v1alpha1/dbinstance_types.go index f5db297e..790bc625 100644 --- a/pkg/apis/kci/v1alpha1/dbinstance_types.go +++ b/pkg/apis/kci/v1alpha1/dbinstance_types.go @@ -20,6 +20,8 @@ type DbInstanceSpec struct { DbInstanceSource `json:",inline"` } +// DbInstanceSource represents the source of a instance. +// Only one of its members may be specified. type DbInstanceSource struct { Google *GoogleInstance `json:"google,omitempty" protobuf:"bytes,1,opt,name=google"` Generic *GenericInstance `json:"generic,omitempty" protobuf:"bytes,2,opt,name=generic"` @@ -43,6 +45,7 @@ type GoogleInstance struct { ConfigmapName types.NamespacedName `json:"configmapRef"` } +// PerconaCluster is used when instance type is percona cluster type PerconaCluster struct { ServerList []string `json:"servers"` // hostgroup: host address Port int32 `json:"port"` diff --git a/pkg/utils/proxy/proxysql/template.go b/pkg/utils/proxy/proxysql/template.go index 1c4a26ce..0a6713db 100644 --- a/pkg/utils/proxy/proxysql/template.go +++ b/pkg/utils/proxy/proxysql/template.go @@ -1,14 +1,20 @@ package proxysql +// Config defines list of configurable items +// Items will be applied in config by templating type Config struct { AdminPort string SQLPort string Backends []Backend } + +// Backend defines list of servers behind of proxysql type Backend struct { Host, Port, MaxConn string } +// PerconaMysqlConfigTemplate defines proxysql config template for mysql percona cluster +// Later this could be moved out completely to outside so that it's more configurable. const PerconaMysqlConfigTemplate = ` datadir="/var/lib/proxysql" From 35db161f0183e20affacb4ab1931593818646aa4 Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Thu, 28 May 2020 08:00:21 +0200 Subject: [PATCH 07/11] add lint for local test --- Makefile | 3 +++ go.mod | 2 ++ go.sum | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/Makefile b/Makefile index 1aec93fe..03a5ce4c 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,9 @@ test: @docker stop postgres @docker stop mysql +lint: + @golint ./... + vet: @go vet ./... diff --git a/go.mod b/go.mod index 15a772fd..25bf8594 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,9 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.4.0 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/tools v0.0.0-20200526224456-8b020aee10d2 // indirect google.golang.org/api v0.9.0 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 gopkg.in/yaml.v2 v2.2.4 diff --git a/go.sum b/go.sum index b9d6b903..8644e3ee 100644 --- a/go.sum +++ b/go.sum @@ -598,6 +598,7 @@ github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -633,6 +634,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -644,8 +647,12 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -676,6 +683,8 @@ golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQ golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -753,8 +762,15 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a h1:UuQ+70Pi/ZdWHuP4v457pkXeOynTdgd/4enxeIO/98k= golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200526224456-8b020aee10d2 h1:21BqcH/onxtGHn1A2GDOJjZnbt4Nlez629S3eaR+eYs= +golang.org/x/tools v0.0.0-20200526224456-8b020aee10d2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= From fd10525a024ae549442f068aee2700e29dd79147 Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Thu, 28 May 2020 18:09:01 +0200 Subject: [PATCH 08/11] add proxy address in database info configmap --- .../{mysql => mysql-generic}/Chart.yaml | 2 +- .../scripts/test_read.sh | 0 .../scripts/test_write.sh | 0 integration/mysql-generic/templates/db.yaml | 11 +++ .../templates/instance.yaml | 6 +- .../templates/mysql-server.yaml | 0 .../templates/test/test.yaml | 18 ++-- .../templates/testscript.yaml | 2 +- integration/mysql-generic/values.yaml | 11 +++ integration/mysql-percona/.gitignore | 2 + integration/mysql-percona/Chart.yaml | 5 + integration/mysql-percona/requirements.yaml | 5 + .../mysql-percona/scripts/test_read.sh | 26 +++++ .../mysql-percona/scripts/test_write.sh | 23 +++++ integration/mysql-percona/templates/db.yaml | 11 +++ .../mysql-percona/templates/instance.yaml | 36 +++++++ .../mysql-percona/templates/mysql-server.yaml | 40 ++++++++ .../mysql-percona/templates/test/test.yaml | 95 +++++++++++++++++++ .../mysql-percona/templates/testscript.yaml | 10 ++ integration/mysql-percona/values.yaml | 11 +++ integration/mysql/templates/db.yaml | 11 --- integration/mysql/values.yaml | 5 - integration/test.sh | 8 +- pkg/apis/kci/v1alpha1/database_types.go | 17 +++- pkg/apis/kci/v1alpha1/dbinstance_types.go | 16 ++-- .../kci/v1alpha1/zz_generated.deepcopy.go | 17 ++++ pkg/controller/database/controller.go | 8 +- pkg/controller/database/databaseHelper.go | 4 +- pkg/controller/database/proxyHelper.go | 3 +- pkg/controller/database/reconcileDatabase.go | 33 ------- .../database/reconcileInfoConfigMap.go | 59 ++++++++++++ pkg/controller/database/reconcileProxy.go | 10 ++ .../dbinstance/reconcileDbInstance.go | 5 + pkg/test/helper.go | 8 +- pkg/utils/database/mysql.go | 2 +- pkg/utils/database/postgres.go | 2 +- pkg/utils/dbinstance/generic.go | 2 +- pkg/utils/proxy/proxysql.go | 44 ++++++++- pkg/utils/proxy/proxysql/template.go | 5 + 39 files changed, 480 insertions(+), 93 deletions(-) rename integration/{mysql => mysql-generic}/Chart.yaml (72%) rename integration/{mysql => mysql-generic}/scripts/test_read.sh (100%) rename integration/{mysql => mysql-generic}/scripts/test_write.sh (100%) create mode 100644 integration/mysql-generic/templates/db.yaml rename integration/{mysql => mysql-generic}/templates/instance.yaml (77%) rename integration/{mysql => mysql-generic}/templates/mysql-server.yaml (100%) rename integration/{mysql => mysql-generic}/templates/test/test.yaml (79%) rename integration/{mysql => mysql-generic}/templates/testscript.yaml (80%) create mode 100644 integration/mysql-generic/values.yaml create mode 100644 integration/mysql-percona/.gitignore create mode 100644 integration/mysql-percona/Chart.yaml create mode 100644 integration/mysql-percona/requirements.yaml create mode 100644 integration/mysql-percona/scripts/test_read.sh create mode 100755 integration/mysql-percona/scripts/test_write.sh create mode 100644 integration/mysql-percona/templates/db.yaml create mode 100644 integration/mysql-percona/templates/instance.yaml create mode 100644 integration/mysql-percona/templates/mysql-server.yaml create mode 100644 integration/mysql-percona/templates/test/test.yaml create mode 100644 integration/mysql-percona/templates/testscript.yaml create mode 100644 integration/mysql-percona/values.yaml delete mode 100644 integration/mysql/templates/db.yaml delete mode 100644 integration/mysql/values.yaml create mode 100644 pkg/controller/database/reconcileInfoConfigMap.go diff --git a/integration/mysql/Chart.yaml b/integration/mysql-generic/Chart.yaml similarity index 72% rename from integration/mysql/Chart.yaml rename to integration/mysql-generic/Chart.yaml index c637a6a1..0a3ba27c 100644 --- a/integration/mysql/Chart.yaml +++ b/integration/mysql-generic/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 appVersion: "1.0" description: Integration test for db operator mysql -name: test-mysql-db-operator +name: test-mysql-generic-db-operator version: 0.1.0 diff --git a/integration/mysql/scripts/test_read.sh b/integration/mysql-generic/scripts/test_read.sh similarity index 100% rename from integration/mysql/scripts/test_read.sh rename to integration/mysql-generic/scripts/test_read.sh diff --git a/integration/mysql/scripts/test_write.sh b/integration/mysql-generic/scripts/test_write.sh similarity index 100% rename from integration/mysql/scripts/test_write.sh rename to integration/mysql-generic/scripts/test_write.sh diff --git a/integration/mysql-generic/templates/db.yaml b/integration/mysql-generic/templates/db.yaml new file mode 100644 index 00000000..efcb0765 --- /dev/null +++ b/integration/mysql-generic/templates/db.yaml @@ -0,0 +1,11 @@ +apiVersion: "kci.rocks/v1alpha1" +kind: "Database" +metadata: + name: {{ .Values.db.name }} + labels: + env: test +spec: + secretName: {{ .Values.db.name }}-credentials # where to save db name user, password for application + instance: {{ .Values.instance.name }} + backup: + enable: false diff --git a/integration/mysql/templates/instance.yaml b/integration/mysql-generic/templates/instance.yaml similarity index 77% rename from integration/mysql/templates/instance.yaml rename to integration/mysql-generic/templates/instance.yaml index 7b2ff9ee..61913191 100644 --- a/integration/mysql/templates/instance.yaml +++ b/integration/mysql-generic/templates/instance.yaml @@ -1,11 +1,11 @@ apiVersion: kci.rocks/v1alpha1 kind: DbInstance metadata: - name: my-local-test + name: {{ .Values.instance.name }} spec: adminSecretRef: Namespace: {{ .Release.Namespace }} - Name: my-local-db-password + Name: {{ .Values.instance.name }}-admin-password engine: mysql generic: host: {{ .Values.mysql.serviceName }}.{{ .Release.Namespace }} @@ -16,7 +16,7 @@ spec: apiVersion: v1 kind: Secret metadata: - name: my-local-db-password + name: {{ .Values.instance.name }}-admin-password type: Opaque data: password: {{ .Values.mysql.adminPassword | b64enc }} diff --git a/integration/mysql/templates/mysql-server.yaml b/integration/mysql-generic/templates/mysql-server.yaml similarity index 100% rename from integration/mysql/templates/mysql-server.yaml rename to integration/mysql-generic/templates/mysql-server.yaml diff --git a/integration/mysql/templates/test/test.yaml b/integration/mysql-generic/templates/test/test.yaml similarity index 79% rename from integration/mysql/templates/test/test.yaml rename to integration/mysql-generic/templates/test/test.yaml index f2cb30d1..0a0c1a63 100644 --- a/integration/mysql/templates/test/test.yaml +++ b/integration/mysql-generic/templates/test/test.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: mysql-tester-app-{{ randAlphaNum 5 | lower }} + name: {{ .Values.db.name }}-tester-app-{{ randAlphaNum 5 | lower }} annotations: "helm.sh/hook": test-success labels: @@ -21,17 +21,17 @@ spec: - name: MYSQL_USERNAME valueFrom: secretKeyRef: - name: my-db-test-credentials + name: {{ .Values.db.name }}-credentials key: USER - name: MYSQL_DB valueFrom: secretKeyRef: - name: my-db-test-credentials + name: {{ .Values.db.name }}-credentials key: DB - name: MYSQL_HOST valueFrom: configMapKeyRef: - name: my-db-test-credentials + name: {{ .Values.db.name }}-credentials key: DB_CONN volumeMounts: - name: db-secret @@ -58,17 +58,17 @@ spec: - name: MYSQL_USERNAME valueFrom: secretKeyRef: - name: my-db-test-credentials + name: {{ .Values.db.name }}-credentials key: USER - name: MYSQL_DB valueFrom: secretKeyRef: - name: my-db-test-credentials + name: {{ .Values.db.name }}-credentials key: DB - name: MYSQL_HOST valueFrom: configMapKeyRef: - name: my-db-test-credentials + name: {{ .Values.db.name }}-credentials key: DB_CONN volumeMounts: - name: db-secret @@ -87,9 +87,9 @@ spec: volumes: - name: db-secret secret: - secretName: my-db-test-credentials + secretName: {{ .Values.db.name }}-credentials - name: script configMap: - name: mysql-test-script + name: {{ .Values.db.name }}-test-script - name: shared-data emptyDir: {} \ No newline at end of file diff --git a/integration/mysql/templates/testscript.yaml b/integration/mysql-generic/templates/testscript.yaml similarity index 80% rename from integration/mysql/templates/testscript.yaml rename to integration/mysql-generic/templates/testscript.yaml index 26f56cf6..c58e6996 100644 --- a/integration/mysql/templates/testscript.yaml +++ b/integration/mysql-generic/templates/testscript.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: mysql-test-script + name: {{ .Values.db.name }}-test-script data: write.sh: | {{ .Files.Get "scripts/test_write.sh" | indent 4}} diff --git a/integration/mysql-generic/values.yaml b/integration/mysql-generic/values.yaml new file mode 100644 index 00000000..739e8614 --- /dev/null +++ b/integration/mysql-generic/values.yaml @@ -0,0 +1,11 @@ +mysql: + serviceName: my-generic-server + adminUser: root + adminPassword: test1234 + image: mysql:5.7 + +instance: + name: my-generic-instance + +db: + name: my-generic-db \ No newline at end of file diff --git a/integration/mysql-percona/.gitignore b/integration/mysql-percona/.gitignore new file mode 100644 index 00000000..eb3b48f0 --- /dev/null +++ b/integration/mysql-percona/.gitignore @@ -0,0 +1,2 @@ +charts/ +requirements.lock \ No newline at end of file diff --git a/integration/mysql-percona/Chart.yaml b/integration/mysql-percona/Chart.yaml new file mode 100644 index 00000000..9aaddfab --- /dev/null +++ b/integration/mysql-percona/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: Integration test for db operator mysql percona instance type +name: test-mysql-percona-db-operator +version: 0.1.0 \ No newline at end of file diff --git a/integration/mysql-percona/requirements.yaml b/integration/mysql-percona/requirements.yaml new file mode 100644 index 00000000..335e1f86 --- /dev/null +++ b/integration/mysql-percona/requirements.yaml @@ -0,0 +1,5 @@ +dependencies: +- name: percona-xtradb-cluster + repository: "@stable" + version: 1.0.3 + alias: percona \ No newline at end of file diff --git a/integration/mysql-percona/scripts/test_read.sh b/integration/mysql-percona/scripts/test_read.sh new file mode 100644 index 00000000..2a2efc88 --- /dev/null +++ b/integration/mysql-percona/scripts/test_read.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +retry=3 +interval=5 + +for i in `seq 1 $retry` +do + sleep $interval + if [ ! -f "${MYSQL_PASSWORD_FILE}" ]; then + echo "Password file does not exists" + exit 1; + else + MYSQL_PASSWORD=$(cat ${MYSQL_PASSWORD_FILE}) + fi + + TESTDATA="$(cat /tmp/checkdata)" + + echo "reading data from mysql..." + FOUNDDATA=$(mysql \ + -h ${MYSQL_HOST} \ + -u ${MYSQL_USERNAME} \ + -p${MYSQL_PASSWORD} ${MYSQL_DB} \ + -e "SELECT data FROM test WHERE data = '${TESTDATA}';") + + echo "$FOUNDDATA" | grep "$TESTDATA" && break; +done \ No newline at end of file diff --git a/integration/mysql-percona/scripts/test_write.sh b/integration/mysql-percona/scripts/test_write.sh new file mode 100755 index 00000000..47a0961b --- /dev/null +++ b/integration/mysql-percona/scripts/test_write.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +TESTDATA="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)" +echo ${TESTDATA} > /tmp/checkdata + +retry=3 +interval=5 + +for i in `seq 1 $retry` +do + sleep $interval + if [ ! -f "${MYSQL_PASSWORD_FILE}" ]; then + echo "Password file does not exists" + exit 1; + else + MYSQL_PASSWORD=$(cat ${MYSQL_PASSWORD_FILE}) + fi + + echo "writing data into mysql database..." + mysql -h ${MYSQL_HOST} -u ${MYSQL_USERNAME} -p${MYSQL_PASSWORD} ${MYSQL_DB} \ + -e "CREATE TABLE IF NOT EXISTS test (no INT NOT NULL AUTO_INCREMENT PRIMARY KEY, data VARCHAR(100)); INSERT INTO test (data) VALUES('${TESTDATA}');"\ + && break +done \ No newline at end of file diff --git a/integration/mysql-percona/templates/db.yaml b/integration/mysql-percona/templates/db.yaml new file mode 100644 index 00000000..efcb0765 --- /dev/null +++ b/integration/mysql-percona/templates/db.yaml @@ -0,0 +1,11 @@ +apiVersion: "kci.rocks/v1alpha1" +kind: "Database" +metadata: + name: {{ .Values.db.name }} + labels: + env: test +spec: + secretName: {{ .Values.db.name }}-credentials # where to save db name user, password for application + instance: {{ .Values.instance.name }} + backup: + enable: false diff --git a/integration/mysql-percona/templates/instance.yaml b/integration/mysql-percona/templates/instance.yaml new file mode 100644 index 00000000..c679cfc5 --- /dev/null +++ b/integration/mysql-percona/templates/instance.yaml @@ -0,0 +1,36 @@ +apiVersion: kci.rocks/v1alpha1 +kind: DbInstance +metadata: + name: {{ .Values.instance.name }} +spec: + adminSecretRef: + Namespace: {{ .Release.Namespace }} + Name: {{ .Values.instance.name }}-admin-password + engine: mysql + percona: + servers: + - {{ .Release.Name }}-pxc-0.{{ .Release.Name }}-pxc.{{ .Release.Namespace }} + - {{ .Release.Name }}-pxc-1.{{ .Release.Name }}-pxc.{{ .Release.Namespace }} + port: 3306 + maxConn: 100 + monitorUserSecretRef: + Namespace: {{ .Release.Namespace }} + Name: {{ .Values.instance.name }}-monitoruser-secret +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.instance.name }}-admin-password +type: Opaque +data: + password: {{ .Values.percona.mysqlRootPassword | b64enc }} + user: {{ print "root" | b64enc }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.instance.name }}-monitoruser-secret +type: Opaque +data: + password: {{ .Values.percona.mysqlPassword | b64enc }} + user: {{ .Values.percona.mysqlUser | b64enc }} \ No newline at end of file diff --git a/integration/mysql-percona/templates/mysql-server.yaml b/integration/mysql-percona/templates/mysql-server.yaml new file mode 100644 index 00000000..d59e0db6 --- /dev/null +++ b/integration/mysql-percona/templates/mysql-server.yaml @@ -0,0 +1,40 @@ +{{- if .Values.mysql }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.mysql.serviceName }} +spec: + ports: + - port: 3306 + selector: + app: mysql + role: test + type: ClusterIP +--- +apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 +kind: Deployment +metadata: + name: {{ .Values.mysql.serviceName }} +spec: + replicas: 1 + selector: + matchLabels: + app: mysql + role: test + strategy: + type: Recreate + template: + metadata: + labels: + app: mysql + role: test + spec: + containers: + - image: {{ .Values.mysql.image }} + name: mysql + env: + # Use secret in real usage + - name: MYSQL_ROOT_PASSWORD + value: {{ .Values.mysql.adminPassword }} +{{- end }} \ No newline at end of file diff --git a/integration/mysql-percona/templates/test/test.yaml b/integration/mysql-percona/templates/test/test.yaml new file mode 100644 index 00000000..9a758bfc --- /dev/null +++ b/integration/mysql-percona/templates/test/test.yaml @@ -0,0 +1,95 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{ .Values.db.name }}-tester-app-{{ randAlphaNum 5 | lower }} + annotations: + "helm.sh/hook": test-success + labels: + app: mysql + role: tester +spec: + containers: + - name: mysql-writer + image: imega/mysql-client + command: + - sh + - -c + - exec sh /app/write.sh + env: + - name: MYSQL_PASSWORD_FILE + value: /run/secrets/mysql/PASSWORD + - name: MYSQL_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.db.name }}-credentials + key: USER + - name: MYSQL_DB + valueFrom: + secretKeyRef: + name: {{ .Values.db.name }}-credentials + key: DB + - name: MYSQL_HOST + valueFrom: + configMapKeyRef: + name: {{ .Values.db.name }}-credentials + key: DB_CONN + volumeMounts: + - name: db-secret + mountPath: /run/secrets/mysql/ + readOnly: true + - name: script + mountPath: /app/ + - name: shared-data + mountPath: /tmp + imagePullPolicy: IfNotPresent + resources: + limits: + cpu: 100m + memory: 128Mi + - name: mysql-reader + image: imega/mysql-client + command: + - sh + - -c + - exec sh /app/read.sh + env: + - name: MYSQL_PASSWORD_FILE + value: /run/secrets/mysql/PASSWORD + - name: MYSQL_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.db.name }}-credentials + key: USER + - name: MYSQL_DB + valueFrom: + secretKeyRef: + name: {{ .Values.db.name }}-credentials + key: DB + - name: MYSQL_HOST + valueFrom: + configMapKeyRef: + name: {{ .Values.db.name }}-credentials + key: DB_HOST + volumeMounts: + - name: db-secret + mountPath: /run/secrets/mysql/ + readOnly: true + - name: script + mountPath: /app/ + - name: shared-data + mountPath: /tmp + imagePullPolicy: IfNotPresent + resources: + limits: + cpu: 100m + memory: 128Mi + restartPolicy: Never + volumes: + - name: db-secret + secret: + secretName: {{ .Values.db.name }}-credentials + - name: script + configMap: + name: {{ .Values.db.name }}-test-script + - name: shared-data + emptyDir: {} \ No newline at end of file diff --git a/integration/mysql-percona/templates/testscript.yaml b/integration/mysql-percona/templates/testscript.yaml new file mode 100644 index 00000000..c58e6996 --- /dev/null +++ b/integration/mysql-percona/templates/testscript.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.db.name }}-test-script +data: + write.sh: | +{{ .Files.Get "scripts/test_write.sh" | indent 4}} + read.sh: | +{{ .Files.Get "scripts/test_read.sh" | indent 4}} \ No newline at end of file diff --git a/integration/mysql-percona/values.yaml b/integration/mysql-percona/values.yaml new file mode 100644 index 00000000..f87f7f30 --- /dev/null +++ b/integration/mysql-percona/values.yaml @@ -0,0 +1,11 @@ +instance: + name: my-percona-instance + +db: + name: my-percona-db + +percona: + mysqlRootPassword: "test1234" + mysqlUser: "monitor" + mysqlPassword: "test4321" + replicas: 2 diff --git a/integration/mysql/templates/db.yaml b/integration/mysql/templates/db.yaml deleted file mode 100644 index b3951689..00000000 --- a/integration/mysql/templates/db.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: "kci.rocks/v1alpha1" -kind: "Database" -metadata: - name: "my-db-test" - labels: - env: test -spec: - secretName: my-db-test-credentials # where to save db name user, password for application - instance: my-local-test - backup: - enable: false \ No newline at end of file diff --git a/integration/mysql/values.yaml b/integration/mysql/values.yaml deleted file mode 100644 index 64b82135..00000000 --- a/integration/mysql/values.yaml +++ /dev/null @@ -1,5 +0,0 @@ -mysql: - serviceName: my-test-db - adminUser: root - adminPassword: test1234 - image: mysql:5.7 \ No newline at end of file diff --git a/integration/test.sh b/integration/test.sh index d26266a3..44fd32e6 100755 --- a/integration/test.sh +++ b/integration/test.sh @@ -49,7 +49,9 @@ check_instance_status() { create_test_resources() { echo "[Test] creating" - $HELM_CMD upgrade --install --namespace ${TEST_NAMESPACE} test-mysql integration/mysql \ + $HELM_CMD upgrade --install --namespace ${TEST_NAMESPACE} test-mysql-generic integration/mysql-generic \ + && $HELM_CMD dependency build integration/mysql-percona \ + && $HELM_CMD upgrade --install --namespace ${TEST_NAMESPACE} test-mysql-percona integration/mysql-percona \ && $HELM_CMD upgrade --install --namespace ${TEST_NAMESPACE} test-pg integration/postgres \ && echo "[Test] created" if [ $? -ne 0 ]; then @@ -86,13 +88,13 @@ check_databases_status() { run_test() { echo "[Test] testing read write to database" - $HELM_CMD test test-mysql && $HELM_CMD test test-pg \ + $HELM_CMD test test-mysql-generic && $HELM_CMD test test-mysql-percona && $HELM_CMD test test-pg \ && echo "[Test] OK!" } delete_databases() { echo "[Database] deleting" - $KUBECTL_CMD delete db my-db-test -n ${TEST_NAMESPACE} && $KUBECTL_CMD delete db pg-db-test -n ${TEST_NAMESPACE} \ + $KUBECTL_CMD delete db -n ${TEST_NAMESPACE} --all \ && echo "[Database] deleted!" } diff --git a/pkg/apis/kci/v1alpha1/database_types.go b/pkg/apis/kci/v1alpha1/database_types.go index 0ba1808b..9b49dc63 100644 --- a/pkg/apis/kci/v1alpha1/database_types.go +++ b/pkg/apis/kci/v1alpha1/database_types.go @@ -25,10 +25,19 @@ type DatabaseSpec struct { type DatabaseStatus struct { // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html - Phase string `json:"phase"` - Status bool `json:"status"` - InstanceRef *DbInstance `json:"instanceRef"` - MonitorUserSecretName string `json:"monitorUserSecret,omitempty"` + Phase string `json:"phase"` + Status bool `json:"status"` + InstanceRef *DbInstance `json:"instanceRef"` + MonitorUserSecretName string `json:"monitorUserSecret,omitempty"` + ProxyStatus DatabaseProxyStatus `json:"proxyStatus,omitempty"` +} + +// DatabaseProxyStatus defines whether proxy for database is enabled or not +// if so, provide information +type DatabaseProxyStatus struct { + Status bool `json:"status"` + ServiceName string `json:"serviceName"` + SQLPort int32 `json:"sqlPort"` } // DatabaseBackup defines the desired state of backup and schedule diff --git a/pkg/apis/kci/v1alpha1/dbinstance_types.go b/pkg/apis/kci/v1alpha1/dbinstance_types.go index 790bc625..dae9bff3 100644 --- a/pkg/apis/kci/v1alpha1/dbinstance_types.go +++ b/pkg/apis/kci/v1alpha1/dbinstance_types.go @@ -48,8 +48,8 @@ type GoogleInstance struct { // PerconaCluster is used when instance type is percona cluster type PerconaCluster struct { ServerList []string `json:"servers"` // hostgroup: host address - Port int32 `json:"port"` - MaxConnection int16 `json:"maxConn"` + Port uint16 `json:"port"` + MaxConnection uint8 `json:"maxConn"` MonitorUserSecret types.NamespacedName `json:"monitorUserSecretRef"` } @@ -58,7 +58,7 @@ type PerconaCluster struct { // generic instance can be any backend, it must be reachable by described address and port type GenericInstance struct { Host string `json:"host"` - Port int32 `json:"port"` + Port uint16 `json:"port"` PublicIP string `json:"publicIp,omitempty"` // BackupHost address will be used for dumping database for backup // Usually slave address for master-slave setup or cluster lb address @@ -122,21 +122,21 @@ func (dbin *DbInstance) ValidateBackend() error { return errors.New("no instance type defined") } - numVolumes := 0 + numSources := 0 if source.Google != nil { - numVolumes++ + numSources++ } if source.Generic != nil { - numVolumes++ + numSources++ } if source.Percona != nil { - numVolumes++ + numSources++ } - if numVolumes > 1 { + if numSources > 1 { return errors.New("may not specify more than 1 instance type") } diff --git a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go index 3be43117..57b73644 100644 --- a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go @@ -85,6 +85,22 @@ func (in *DatabaseList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseProxyStatus) DeepCopyInto(out *DatabaseProxyStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseProxyStatus. +func (in *DatabaseProxyStatus) DeepCopy() *DatabaseProxyStatus { + if in == nil { + return nil + } + out := new(DatabaseProxyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) { *out = *in @@ -115,6 +131,7 @@ func (in *DatabaseStatus) DeepCopyInto(out *DatabaseStatus) { *out = new(DbInstance) (*in).DeepCopyInto(*out) } + out.ProxyStatus = in.ProxyStatus return } diff --git a/pkg/controller/database/controller.go b/pkg/controller/database/controller.go index d7ab830a..896eaec5 100644 --- a/pkg/controller/database/controller.go +++ b/pkg/controller/database/controller.go @@ -87,9 +87,9 @@ type ReconcileDatabase struct { var ( phaseCreate = "Creating" - phaseConfigMap = "InfoConfigMapCreating" phaseInstanceAccessSecret = "InstanceAccessSecretCreating" phaseProxy = "ProxyCreating" + phaseConfigMap = "InfoConfigMapCreating" phaseMonitoring = "MonitoringCreating" phaseBackupJob = "BackupJobCreating" phaseFinish = "Finishing" @@ -225,6 +225,12 @@ func (r *ReconcileDatabase) Reconcile(request reconcile.Request) (reconcile.Resu if err != nil { return r.manageError(dbcr, err, true) } + dbcr.Status.Phase = phaseConfigMap + case phaseConfigMap: + err := r.createInfoConfigMap(dbcr) + if err != nil { + return r.manageError(dbcr, err, true) + } dbcr.Status.Phase = phaseBackupJob case phaseBackupJob: err := r.createBackupJob(dbcr) diff --git a/pkg/controller/database/databaseHelper.go b/pkg/controller/database/databaseHelper.go index 90636132..aa6c512a 100644 --- a/pkg/controller/database/databaseHelper.go +++ b/pkg/controller/database/databaseHelper.go @@ -53,7 +53,7 @@ func determinDatabaseType(dbcr *kciv1alpha1.Database, dbCred database.Credential db := database.Postgres{ Backend: backend, Host: host, - Port: int32(port), + Port: uint16(port), Database: dbCred.Name, User: dbCred.Username, Password: dbCred.Password, @@ -66,7 +66,7 @@ func determinDatabaseType(dbcr *kciv1alpha1.Database, dbCred database.Credential db := database.Mysql{ Backend: backend, Host: host, - Port: int32(port), + Port: uint16(port), Database: dbCred.Name, User: dbCred.Username, Password: dbCred.Password, diff --git a/pkg/controller/database/proxyHelper.go b/pkg/controller/database/proxyHelper.go index dc24541d..83a423cf 100644 --- a/pkg/controller/database/proxyHelper.go +++ b/pkg/controller/database/proxyHelper.go @@ -65,9 +65,10 @@ func determinProxyType(dbcr *kciv1alpha1.Database) (proxy.Proxy, error) { Namespace: dbcr.Namespace, Servers: instance.Spec.Percona.ServerList, MaxConn: instance.Spec.Percona.MaxConnection, + UserSecretName: dbcr.Spec.SecretName, MonitorUserSecretName: dbcr.Status.MonitorUserSecretName, Engine: engine, - Port: int32(port), + Port: uint16(port), Labels: kci.LabelBuilder(labels), }, nil default: diff --git a/pkg/controller/database/reconcileDatabase.go b/pkg/controller/database/reconcileDatabase.go index c64c207e..f173009e 100644 --- a/pkg/controller/database/reconcileDatabase.go +++ b/pkg/controller/database/reconcileDatabase.go @@ -114,39 +114,6 @@ func (r *ReconcileDatabase) createDatabase(dbcr *kciv1alpha1.Database) error { return err } - instance, err := dbcr.GetInstanceRef() - if err != nil { - return err - } - - databaseConfigResource := &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: dbcr.Namespace, - Name: dbcr.Spec.SecretName, - Labels: kci.BaseLabelBuilder(), - }, - Data: instance.Status.Info, - } - - err = r.client.Create(context.TODO(), databaseConfigResource) - if err != nil { - if k8serrors.IsAlreadyExists(err) { - // if configmap resource already exists, update - err = r.client.Update(context.TODO(), databaseConfigResource) - if err != nil { - logrus.Errorf("DB: namespace=%s, name=%s failed updating database info configmap", dbcr.Namespace, dbcr.Name) - return err - } - } else { - logrus.Errorf("DB: namespace=%s, name=%s failed creating database info configmap", dbcr.Namespace, dbcr.Name) - return err - } - } - logrus.Infof("DB: namespace=%s, name=%s successfully created", dbcr.Namespace, dbcr.Name) return nil } diff --git a/pkg/controller/database/reconcileInfoConfigMap.go b/pkg/controller/database/reconcileInfoConfigMap.go new file mode 100644 index 00000000..2933478a --- /dev/null +++ b/pkg/controller/database/reconcileInfoConfigMap.go @@ -0,0 +1,59 @@ +package database + +import ( + "context" + "strconv" + + kciv1alpha1 "github.com/kloeckner-i/db-operator/pkg/apis/kci/v1alpha1" + "github.com/kloeckner-i/db-operator/pkg/utils/kci" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (r *ReconcileDatabase) createInfoConfigMap(dbcr *kciv1alpha1.Database) error { + instance, err := dbcr.GetInstanceRef() + if err != nil { + return err + } + + info := instance.Status.DeepCopy().Info + proxyStatus := dbcr.Status.ProxyStatus + + if proxyStatus.Status == true { + info["DB_HOST"] = proxyStatus.ServiceName + info["DB_PORT"] = strconv.FormatInt(int64(proxyStatus.SQLPort), 10) + } + + databaseConfigResource := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: dbcr.Namespace, + Name: dbcr.Spec.SecretName, + Labels: kci.BaseLabelBuilder(), + }, + Data: info, + } + + err = r.client.Create(context.TODO(), databaseConfigResource) + if err != nil { + if k8serrors.IsAlreadyExists(err) { + // if configmap resource already exists, update + err = r.client.Update(context.TODO(), databaseConfigResource) + if err != nil { + logrus.Errorf("DB: namespace=%s, name=%s failed updating database info configmap", dbcr.Namespace, dbcr.Name) + return err + } + } else { + logrus.Errorf("DB: namespace=%s, name=%s failed creating database info configmap", dbcr.Namespace, dbcr.Name) + return err + } + } + + logrus.Infof("DB: namespace=%s, name=%s database info configmap created", dbcr.Namespace, dbcr.Name) + return nil +} diff --git a/pkg/controller/database/reconcileProxy.go b/pkg/controller/database/reconcileProxy.go index 5354e7ba..851c1edb 100644 --- a/pkg/controller/database/reconcileProxy.go +++ b/pkg/controller/database/reconcileProxy.go @@ -97,6 +97,16 @@ func (r *ReconcileDatabase) createProxy(dbcr *kciv1alpha1.Database) error { return err } } + + engine, _ := dbcr.GetEngineType() + dbcr.Status.ProxyStatus.ServiceName = svc.Name + for _, svcPort := range svc.Spec.Ports { + if svcPort.Name == engine { + dbcr.Status.ProxyStatus.SQLPort = svcPort.Port + } + } + dbcr.Status.ProxyStatus.Status = true + logrus.Infof("DB: namespace=%s, name=%s proxy created", dbcr.Namespace, dbcr.Name) return nil } diff --git a/pkg/controller/dbinstance/reconcileDbInstance.go b/pkg/controller/dbinstance/reconcileDbInstance.go index fa5c801c..d9c0689f 100644 --- a/pkg/controller/dbinstance/reconcileDbInstance.go +++ b/pkg/controller/dbinstance/reconcileDbInstance.go @@ -53,6 +53,11 @@ func (r *ReconcileDbInstance) create(dbin *kciv1alpha1.DbInstance) error { Password: cred.Password, } case "percona": + if dbin.Spec.Engine != "mysql" { + logrus.Errorf("Instance: name=%s - non mysql percona instance not supported", dbin.Name) + return errors.New("non mysql percona instance not supported") + } + instance = &dbinstance.Generic{ Host: dbin.Spec.Percona.ServerList[0], Port: dbin.Spec.Percona.Port, diff --git a/pkg/test/helper.go b/pkg/test/helper.go index 4822b0f7..2ef182ff 100644 --- a/pkg/test/helper.go +++ b/pkg/test/helper.go @@ -16,13 +16,13 @@ func GetMysqlHost() string { } // GetMysqlPort set mysql port which used by unit test -func GetMysqlPort() int32 { +func GetMysqlPort() uint16 { if value, ok := os.LookupEnv("MYSQL_PORT"); ok { port, err := strconv.Atoi(value) if err != nil { logrus.Fatal(err) } - return int32(port) + return uint16(port) } return 3306 } @@ -44,13 +44,13 @@ func GetPostgresHost() string { } // GetPostgresPort set postgres port which used by unit test -func GetPostgresPort() int32 { +func GetPostgresPort() uint16 { if value, ok := os.LookupEnv("POSTGRES_PORT"); ok { port, err := strconv.Atoi(value) if err != nil { logrus.Fatal(err) } - return int32(port) + return uint16(port) } return 5432 } diff --git a/pkg/utils/database/mysql.go b/pkg/utils/database/mysql.go index c6165e45..3419048f 100644 --- a/pkg/utils/database/mysql.go +++ b/pkg/utils/database/mysql.go @@ -21,7 +21,7 @@ import ( type Mysql struct { Backend string Host string - Port int32 + Port uint16 Database string User string Password string diff --git a/pkg/utils/database/postgres.go b/pkg/utils/database/postgres.go index 6e0ab6d8..0f3acd0a 100644 --- a/pkg/utils/database/postgres.go +++ b/pkg/utils/database/postgres.go @@ -23,7 +23,7 @@ import ( type Postgres struct { Backend string Host string - Port int32 + Port uint16 Database string User string Password string diff --git a/pkg/utils/dbinstance/generic.go b/pkg/utils/dbinstance/generic.go index 6360c324..9ebeb1c2 100644 --- a/pkg/utils/dbinstance/generic.go +++ b/pkg/utils/dbinstance/generic.go @@ -12,7 +12,7 @@ import ( // Generic represents database instance which can be connected by address and port type Generic struct { Host string - Port int32 + Port uint16 Engine string User string Password string diff --git a/pkg/utils/proxy/proxysql.go b/pkg/utils/proxy/proxysql.go index 4ab60fb2..2e288626 100644 --- a/pkg/utils/proxy/proxysql.go +++ b/pkg/utils/proxy/proxysql.go @@ -2,14 +2,15 @@ package proxy import ( "bytes" - "html/template" "log" "strconv" + "text/template" proxysql "github.com/kloeckner-i/db-operator/pkg/utils/proxy/proxysql" v1apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -18,10 +19,11 @@ type ProxySQL struct { NamePrefix string Namespace string Servers []string - MaxConn int16 + MaxConn uint8 + UserSecretName string MonitorUserSecretName string Engine string - Port int32 + Port uint16 Labels map[string]string } @@ -124,6 +126,20 @@ func (ps *ProxySQL) deploymentSpec() (v1apps.DeploymentSpec, error) { }, }, }, + v1.Volume{ + Name: "user-secret", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: ps.UserSecretName, + Items: []v1.KeyToPath{ + v1.KeyToPath{ + Key: "PASSWORD", + Path: "user-password", + }, + }, + }, + }, + }, } return v1apps.DeploymentSpec{ @@ -154,7 +170,7 @@ func (ps *ProxySQL) configGeneratorContainer() (v1.Container, error) { Name: "config-generator", Image: "alpine", ImagePullPolicy: v1.PullIfNotPresent, - Command: []string{"sh", "-c", "apk add --update gettext && MONITOR_PASSWORD=$(cat /run/secrets/monitoruser-password) envsubst < /tmp/proxysql.cnf.tmpl > /mnt/proxysql.cnf"}, + Command: []string{"sh", "-c", "apk add --update gettext && MONITOR_PASSWORD=$(cat /run/secrets/monitoruser-password) DB_PASSWORD=$(cat /run/secrets/user-password) envsubst < /tmp/proxysql.cnf.tmpl > /mnt/proxysql.cnf"}, Env: []v1.EnvVar{ v1.EnvVar{ Name: "MONITOR_USERNAME", ValueFrom: &v1.EnvVarSource{ @@ -164,6 +180,14 @@ func (ps *ProxySQL) configGeneratorContainer() (v1.Container, error) { }, }, }, + v1.EnvVar{ + Name: "DB_USERNAME", ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{Name: ps.UserSecretName}, + Key: "USER", + }, + }, + }, }, VolumeMounts: []v1.VolumeMount{ v1.VolumeMount{ @@ -181,6 +205,12 @@ func (ps *ProxySQL) configGeneratorContainer() (v1.Container, error) { SubPath: "monitoruser-password", ReadOnly: true, }, + v1.VolumeMount{ + Name: "user-secret", + MountPath: "/run/secrets/user-password", + SubPath: "user-password", + ReadOnly: true, + }, }, }, nil } @@ -209,6 +239,12 @@ func (ps *ProxySQL) proxyContainer() (v1.Container, error) { SubPath: "proxysql.cnf", }, }, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("50m"), + v1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, }, nil } diff --git a/pkg/utils/proxy/proxysql/template.go b/pkg/utils/proxy/proxysql/template.go index 0a6713db..ecc5aaa8 100644 --- a/pkg/utils/proxy/proxysql/template.go +++ b/pkg/utils/proxy/proxysql/template.go @@ -74,6 +74,11 @@ mysql_servers = {{- end }} ) +mysql_users = +( + { username = "$DB_USERNAME", password = "$DB_PASSWORD", default_hostgroup = 10, transaction_persistent = 0, active = 1 } +) + mysql_query_rules = ( { From f61c4dbc8da16c1ea1c1b194573f3f3ad17117e8 Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Fri, 29 May 2020 09:43:24 +0200 Subject: [PATCH 09/11] percona-xtradb-cluster helm stateful api version outdated. build local chart --- .../percona-xtradb-cluster/.helmignore | 21 ++ .../percona-xtradb-cluster/Chart.yaml | 23 ++ .../percona-xtradb-cluster/OWNERS | 8 + .../percona-xtradb-cluster/README.md | 198 ++++++++++++++++ .../files/entrypoint.sh | 38 ++++ .../percona-xtradb-cluster/files/functions.sh | 117 ++++++++++ .../percona-xtradb-cluster/files/node.cnf | 13 ++ .../templates/NOTES.txt | 35 +++ .../templates/_helpers.tpl | 25 +++ .../templates/config-map_mysql-config.yaml | 20 ++ .../templates/config-map_startup-scripts.yaml | 14 ++ .../percona-xtradb-cluster/templates/pdb.yaml | 22 ++ .../templates/prometheusrule.yaml | 61 +++++ .../templates/secrets.yaml | 47 ++++ .../templates/service-metrics.yaml | 20 ++ .../templates/service-percona.yaml | 20 ++ .../templates/service-repl.yaml | 21 ++ .../templates/servicemonitor.yaml | 27 +++ .../templates/statefulset.yaml | 211 ++++++++++++++++++ .../templates/tests/pxc-test-cm.yaml | 23 ++ .../templates/tests/pxc-test.yaml | 26 +++ .../percona-xtradb-cluster/values.yaml | 169 ++++++++++++++ integration/mysql-percona/requirements.yaml | 2 +- 23 files changed, 1160 insertions(+), 1 deletion(-) create mode 100755 integration/mysql-percona/percona-xtradb-cluster/.helmignore create mode 100755 integration/mysql-percona/percona-xtradb-cluster/Chart.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/OWNERS create mode 100755 integration/mysql-percona/percona-xtradb-cluster/README.md create mode 100755 integration/mysql-percona/percona-xtradb-cluster/files/entrypoint.sh create mode 100755 integration/mysql-percona/percona-xtradb-cluster/files/functions.sh create mode 100755 integration/mysql-percona/percona-xtradb-cluster/files/node.cnf create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/NOTES.txt create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/_helpers.tpl create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/config-map_mysql-config.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/config-map_startup-scripts.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/pdb.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/prometheusrule.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/secrets.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/service-metrics.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/service-percona.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/service-repl.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/servicemonitor.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/statefulset.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test-cm.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test.yaml create mode 100755 integration/mysql-percona/percona-xtradb-cluster/values.yaml diff --git a/integration/mysql-percona/percona-xtradb-cluster/.helmignore b/integration/mysql-percona/percona-xtradb-cluster/.helmignore new file mode 100755 index 00000000..f0c13194 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/.helmignore @@ -0,0 +1,21 @@ +# 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 +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/integration/mysql-percona/percona-xtradb-cluster/Chart.yaml b/integration/mysql-percona/percona-xtradb-cluster/Chart.yaml new file mode 100755 index 00000000..9923ab64 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +appVersion: 5.7.19 +description: free, fully compatible, enhanced, open source drop-in replacement for + MySQL with Galera Replication (xtradb) +engine: gotpl +home: https://www.percona.com/ +icon: https://www.percona.com/sites/all/themes/percona2015/logo.png +keywords: +- mysql +- percona +- database +- sql +- xtradb +- galera +- wsrep +maintainers: +- email: username.taken@gmail.com + name: paulczar +name: percona-xtradb-cluster +sources: +- https://github.com/kubernetes/charts +- https://github.com/percona-lab/percona-docker/ +version: 1.0.3 diff --git a/integration/mysql-percona/percona-xtradb-cluster/OWNERS b/integration/mysql-percona/percona-xtradb-cluster/OWNERS new file mode 100755 index 00000000..4abdb65b --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/OWNERS @@ -0,0 +1,8 @@ +approvers: +- paulczar +- maver1ck +reviewers: +- paulczar +- maver1ck + + diff --git a/integration/mysql-percona/percona-xtradb-cluster/README.md b/integration/mysql-percona/percona-xtradb-cluster/README.md new file mode 100755 index 00000000..c70f64e0 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/README.md @@ -0,0 +1,198 @@ +# Percona XtraDB Cluster + +[Percona Server](https://MySQL.org) for MySQL® is a free, fully compatible, enhanced, open source drop-in replacement for MySQL that provides superior performance, scalability and instrumentation. With over 3,000,000 downloads, Percona Server for MySQL's self-tuning algorithms and support for extremely high-performance hardware delivers excellent performance and reliability. + +Notable users include Netflix, Amazon Web Services, Alcatel-Lucent, and Smug Mug. + +## Introduction + +This chart, based off of the Percona chart (which in turn is based off the MySQL chart), bootstraps a multi-node Percona XtraDB Cluster deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +The chart exploits the deterministic nature of StatefulSet and KubeDNS to ensure the cluster bootstrap is performed in the correct order. + +## Prerequisites + +- Kubernetes 1.8+ with Beta APIs enabled +- PV provisioner support in the underlying infrastructure + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```bash +$ helm install --name my-release stable/percona-xtradb-cluster +``` + +The command deploys a Percona XtraDB Cluster on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. + +The root password can only be used inside each `pod`. You should set a default `mysqlDatabase`, `mysqlUser` and `mysqlPassword` in the values.yaml file. + +By default an **insecure** password will be generated for the root and replication users. If you'd like to set your own password change the `mysqlRootPassword` or `xtraBackupPassword` respectively +in the values.yaml. + +You can retrieve your root password (usable only via localhost in each pod) by running the following command. Make sure to replace [YOUR_RELEASE_NAME]: + + printf $(printf '\%o' `kubectl get secret [YOUR_RELEASE_NAME]-percona -o jsonpath="{.data.mysql-root-password[*]}"`) + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```bash +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Configuration + +The following table lists the configurable parameters of the Percona chart and their default values. + +| Parameter | Description | Default | +| ----------------------- | ---------------------------------- | ---------------------------------------------------------- | +| `image.repository` | `percona-xtradb-cluster` image Repo. | 5.7.19 release | +| `image.tag` | `percona-xtradb-cluster` image tag. | `percona/percona-xtradb-cluster` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `replicas` | Number of pods to join the Percona XtraDB Cluster | 3 | +| `allowRootFrom` | Remote hosts to allow root access, set to `127.0.0.1` to disable remote root | `%` | +| `mysqlRootPassword` | Password for the `root` user. | `not-a-secure-password` | +| `xtraBackupPassword` | Password for the `xtrabackup` user. | `replicate-my-data` | +| `pxc_strict_mode` | Setting for `pxc_strict_mode`. | ENFORCING | +| `mysqlUser` | Username of new user to create. | `nil` | +| `mysqlPassword` | Password for the new user. | `nil` | +| `mysqlDatabase` | Name for new database to create. | `nil` | +| `persistence.enabled` | Create a volume to store data | false | +| `persistence.size` | Size of persistent volume claim | 8Gi RW | +| `persistence.storageClass` | Type of persistent volume claim | nil (uses alpha storage class annotation) | +| `persistence.accessMode` | ReadWriteOnce or ReadOnly | ReadWriteOnce | +| `tolerations` | Node labels for pod assignment | `[]` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `podAnnotations` | Pod annotations | `{}` | +| `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` | +| `configFiles` | files to write to /etc/mysql/conf.d | see values.yaml | +| `ssl.enabled` | Setup and use SSL for MySQL connections | `false` | +| `ssl.secret` | Name of the secret containing the SSL certificates | mysql-ssl-certs | +| `ssl.certificates[0].name` | Name of the secret containing the SSL certificates | `nil` | +| `ssl.certificates[0].ca` | CA certificate | `nil` | +| `ssl.certificates[0].cert` | Server certificate (public key) | `nil` | +| `ssl.certificates[0].key` | Server key (private key) | `nil` | +| `logTail` | if set to true runs a container to tail /var/log/mysqld.log in the pod | true | +| `metricsExporter.enabled` | if set to true runs a [mysql metrics exporter](https://github.com/prometheus/mysqld_exporter) container in the pod | false | +| `metricsExporter.commandOverrides` | Overrides default docker command for metrics exporter | `[]` | +| `metricsExporter.argsOverrides` | Overrides default docker args for metrics exporter | `[]` | +| `metricsExporter.tag` | Specify a docker image tag for `prom/mysqld-exporter` metrics exporter docker image | `nil` | +| `prometheus.operator.enabled` | Setting to true will create Prometheus-Operator specific resources | `false` | +| `prometheus.operator.prometheusRule.enabled` | Create default alerting rules | `true` | +| `prometheus.operator.prometheusRule.labels` | Labels to add to alerts | `{}` | +| `prometheus.operator.prometheusRule.namespace` | Namespace which Prometheus is installed in | `nil` | +| `prometheus.operator.prometheusRule.selector` | Label Selector for Prometheus to find ServiceMonitors | `nil` | +| `prometheus.operator.serviceMonitor.interval` | Interval at which Prometheus will scrape metrics exporter | `10s` | +| `prometheus.operator.serviceMonitor.namespace` | Namespace which Prometheus is installed in | `nil` | +| `prometheus.operator.serviceMonitor.selector` | Label Selector for Prometheus to find ServiceMonitors | `nil` | +| `podDisruptionBudget` | Pod disruption budget | `{enabled: false, maxUnavailable: 1}` | +| `service.percona.headless` | if set to true makes the percona service [headless](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) | false | + + +Some of the parameters above map to the env variables defined in the [Percona XtraDB Cluster DockerHub image](https://hub.docker.com/r/percona/percona-xtradb-cluster/). + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install --name my-release \ + --set mysqlRootPassword=secretpassword,mysqlUser=my-user,mysqlPassword=my-password,mysqlDatabase=my-database \ + stable/percona-xtradb-cluster +``` + +The above command sets the MySQL `root` account password to `secretpassword`. Additionally it creates a standard database user named `my-user`, with the password `my-password`, who has access to a database named `my-database`. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +$ helm install --name my-release -f values.yaml stable/percona-xtradb-cluster +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Persistence + +The [Percona XtraDB Cluster DockerHub image](https://hub.docker.com/r/percona/percona-xtradb-cluster/) stores the MySQL data and configurations at the `/var/lib/mysql` path of the container. + +By default, an emptyDir volume is mounted at that location. + +> *"An emptyDir volume is first created when a Pod is assigned to a Node, and exists as long as that Pod is running on that node. When a Pod is removed from a node for any reason, the data in the emptyDir is deleted forever."* + +You can change the values.yaml to enable persistence and use a PersistentVolumeClaim instead. + +## SSL + +This chart supports configuring MySQL to use [encrypted connections](https://dev.mysql.com/doc/refman/5.7/en/encrypted-connections.html) with TLS/SSL certificates provided by the user. This is accomplished by storing the required Certificate Authority file, the server public key certificate, and the server private key as a Kubernetes secret. The SSL options for this chart support the following use cases: + +* Manage certificate secrets with helm +* Manage certificate secrets outside of helm + +## Manage certificate secrets with helm + +Include your certificate data in the `ssl.certificates` section. For example: + +``` +ssl: + enabled: false + secret: mysql-ssl-certs + certificates: + - name: mysql-ssl-certs + ca: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + cert: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + key: |- + -----BEGIN RSA PRIVATE KEY----- + ... + -----END RSA PRIVATE KEY----- +``` + +> **Note**: Make sure your certificate data has the correct formatting in the values file. + +## Manage certificate secrets outside of helm + +1. Ensure the certificate secret exist before installation of this chart. +2. Set the name of the certificate secret in `ssl.secret`. +3. Make sure there are no entries underneath `ssl.certificates`. + +To manually create the certificate secret from local files you can execute: +``` +kubectl create secret generic mysql-ssl-certs \ + --from-file=ca.pem=./ssl/certificate-authority.pem \ + --from-file=server-cert.pem=./ssl/server-public-key.pem \ + --from-file=server-key.pem=./ssl/server-private-key.pem +``` +> **Note**: `ca.pem`, `server-cert.pem`, and `server-key.pem` **must** be used as the key names in this generic secret. + +If you are using a certificate your configurationFiles must include the three ssl lines under [mysqld] + +``` +[mysqld] + ssl-ca=/ssl/ca.pem + ssl-cert=/ssl/server-cert.pem + ssl-key=/ssl/server-key.pem +``` + +## PXC Strict Mode + +PXC Strict Mode is designed to avoid the use of experimental and unsupported features in Percona XtraDB Cluster. It performs a number of validations at startup and during runtime. + +Depending on the actual mode you select, upon encountering a failed validation, the server will either throw an error (halting startup or denying the operation), or log a warning and continue running as normal. The following modes are available: + +* DISABLED: Do not perform strict mode validations and run as normal. +* PERMISSIVE: If a vaidation fails, log a warning and continue running as normal. +* ENFORCING: If a validation fails during startup, halt the server and throw an error. If a validation fails during runtime, deny the operation and throw an error. +* MASTER: The same as ENFORCING except that the validation of explicit table locking is not performed. This mode can be used with clusters in which write operations are isolated to a single node. + +By default, PXC Strict Mode is set to ENFORCING, except if the node is acting as a standalone server or the node is bootstrapping, then PXC Strict Mode defaults to DISABLED. + +Source: https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html diff --git a/integration/mysql-percona/percona-xtradb-cluster/files/entrypoint.sh b/integration/mysql-percona/percona-xtradb-cluster/files/entrypoint.sh new file mode 100755 index 00000000..91b0fca5 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/files/entrypoint.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +if [[ -n "${DEBUG}" ]]; then + set -x +fi + +. /startup-scripts/functions.sh + +ipaddr=$(hostname -i | awk ' { print $1 } ') +hostname=$(hostname) +echo "I AM $hostname - $ipaddr" + +# if command starts with an option, prepend mysqld +if [ "${1:0:1}" = '-' ]; then + CMDARG="$@" +fi + +cluster_join=$(resolveip -s "${K8S_SERVICE_NAME}" || echo "") +if [[ -z "${cluster_join}" ]]; then + echo "I am the Primary Node" + init_mysql + write_password_file + exec mysqld --user=mysql --wsrep_cluster_name=$SHORT_CLUSTER_NAME --wsrep_node_name=$hostname \ + --wsrep_cluster_address=gcomm:// --wsrep_sst_method=xtrabackup-v2 \ + --wsrep_sst_auth="xtrabackup:$XTRABACKUP_PASSWORD" \ + --wsrep_node_address="$ipaddr" --pxc_strict_mode="$PXC_STRICT_MODE" $CMDARG +else + echo "I am not the Primary Node" + chown -R mysql:mysql /var/lib/mysql || true # default is root:root 777 + touch /var/log/mysqld.log + chown mysql:mysql /var/log/mysqld.log + write_password_file + exec mysqld --user=mysql --wsrep_cluster_name=$SHORT_CLUSTER_NAME --wsrep_node_name=$hostname \ + --wsrep_cluster_address="gcomm://$cluster_join" --wsrep_sst_method=xtrabackup-v2 \ + --wsrep_sst_auth="xtrabackup:$XTRABACKUP_PASSWORD" \ + --wsrep_node_address="$ipaddr" --pxc_strict_mode="$PXC_STRICT_MODE" $CMDARG +fi diff --git a/integration/mysql-percona/percona-xtradb-cluster/files/functions.sh b/integration/mysql-percona/percona-xtradb-cluster/files/functions.sh new file mode 100755 index 00000000..83f71ada --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/files/functions.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +write_password_file() { + if [[ -n "${MYSQL_ROOT_PASSWORD}" ]]; then + cat < /root/.my.cnf + [client] + user=root + password=${MYSQL_ROOT_PASSWORD} +EOF + fi +} + +init_mysql() { + DATADIR=/var/lib/mysql + # if we have CLUSTER_JOIN - then we do not need to perform datadir initialize + # the data will be copied from another node + if [ ! -e "$DATADIR/mysql" ]; then + if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" -a -z "$MYSQL_ROOT_PASSWORD_FILE" ]; then + echo >&2 'error: database is uninitialized and password option is not specified ' + echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ROOT_PASSWORD_FILE, MYSQL_ALLOW_EMPTY_PASSWORD or MYSQL_RANDOM_ROOT_PASSWORD' + exit 1 + fi + + if [ ! -z "$MYSQL_ROOT_PASSWORD_FILE" -a -z "$MYSQL_ROOT_PASSWORD" ]; then + MYSQL_ROOT_PASSWORD=$(cat $MYSQL_ROOT_PASSWORD_FILE) + fi + mkdir -p "$DATADIR" + + echo "Running --initialize-insecure on $DATADIR" + ls -lah $DATADIR + if [ "$PERCONA_MAJOR" = "5.6" ]; then + mysql_install_db --user=mysql --datadir="$DATADIR" + else + mysqld --user=mysql --datadir="$DATADIR" --initialize-insecure + fi + chown -R mysql:mysql "$DATADIR" || true # default is root:root 777 + if [ -f /var/log/mysqld.log ]; then + chown mysql:mysql /var/log/mysqld.log + fi + echo 'Finished --initialize-insecure' + + mysqld --user=mysql --datadir="$DATADIR" --skip-networking & + pid="$!" + + mysql=( mysql --protocol=socket -uroot ) + + for i in {30..0}; do + if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then + break + fi + echo 'MySQL init process in progress...' + sleep 1 + done + + if [ "$i" = 0 ]; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + # sed is for https://bugs.mysql.com/bug.php?id=20545 + mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql + "${mysql[@]}" <<-EOSQL + -- What's done in this file shouldn't be replicated + -- or products like mysql-fabric won't work + SET @@SESSION.SQL_LOG_BIN=0; + CREATE USER 'root'@'${ALLOW_ROOT_FROM}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; + GRANT ALL ON *.* TO 'root'@'${ALLOW_ROOT_FROM}' WITH GRANT OPTION ; + GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ; + CREATE USER 'xtrabackup'@'localhost' IDENTIFIED BY '$XTRABACKUP_PASSWORD'; + GRANT RELOAD,PROCESS,LOCK TABLES,REPLICATION CLIENT ON *.* TO 'xtrabackup'@'localhost'; + GRANT REPLICATION CLIENT ON *.* TO monitor@'%' IDENTIFIED BY 'monitor'; + GRANT PROCESS ON *.* TO monitor@localhost IDENTIFIED BY 'monitor'; + CREATE USER 'mysql'@'localhost' IDENTIFIED BY '' ; + DROP DATABASE IF EXISTS test ; + FLUSH PRIVILEGES ; +EOSQL + + if [ "$PERCONA_MAJOR" = "5.6" ]; then + echo "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('${MYSQL_ROOT_PASSWORD}'); FLUSH PRIVILEGES;" | "${mysql[@]}" + else + echo "ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}'; FLUSH PRIVILEGES;" | "${mysql[@]}" + fi + + if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then + mysql+=( -p"${MYSQL_ROOT_PASSWORD}" ) + fi + + if [ "$MYSQL_DATABASE" ]; then + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}" + mysql+=( "$MYSQL_DATABASE" ) + fi + + if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then + echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" | "${mysql[@]}" + + if [ "$MYSQL_DATABASE" ]; then + echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" | "${mysql[@]}" + fi + + echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}" + fi + + if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then + "${mysql[@]}" <<-EOSQL + ALTER USER 'root'@'%' PASSWORD EXPIRE; +EOSQL + fi + if ! kill -s TERM "$pid" || ! wait "$pid"; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + echo + echo 'MySQL init process done. Ready for start up.' + echo + fi +} diff --git a/integration/mysql-percona/percona-xtradb-cluster/files/node.cnf b/integration/mysql-percona/percona-xtradb-cluster/files/node.cnf new file mode 100755 index 00000000..952ebacd --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/files/node.cnf @@ -0,0 +1,13 @@ +[mysqld] +datadir=/var/lib/mysql +default_storage_engine=InnoDB +binlog_format=ROW +innodb_flush_log_at_trx_commit = 0 +innodb_flush_method = O_DIRECT +innodb_file_per_table = 1 +innodb_autoinc_lock_mode=2 +bind_address = 0.0.0.0 +wsrep_slave_threads=2 +wsrep_cluster_address=gcomm:// +wsrep_provider=/usr/lib/galera3/libgalera_smm.so +wsrep_sst_method=xtrabackup-v2 \ No newline at end of file diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/NOTES.txt b/integration/mysql-percona/percona-xtradb-cluster/templates/NOTES.txt new file mode 100755 index 00000000..7867dba6 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/NOTES.txt @@ -0,0 +1,35 @@ +Percona can be accessed via port 3306 on the following DNS name from within your cluster: +{{ template "percona-xtradb-cluster.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local + +To get your root password run (this password can only be used from inside the container): + + $ kubectl get secret --namespace {{ .Release.Namespace }} {{ template "percona-xtradb-cluster.fullname" . }} -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo + +To get your xtradb backup password run: + + $ kubectl get secret --namespace {{ .Release.Namespace }} {{ template "percona-xtradb-cluster.fullname" . }} -o jsonpath="{.data.xtrabackup-password}" | base64 --decode; echo + +To check the size of the xtradb cluster: + + $ kubectl exec --namespace {{ .Release.Namespace }} -ti {{ template "percona-xtradb-cluster.fullname" . }}-0 -c database -- mysql -e "SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size'" + +To connect to your database: + +1. Run a command in the first pod in the StatefulSet: + + $ kubectl exec --namespace {{ .Release.Namespace }} -ti {{ template "percona-xtradb-cluster.fullname" . }}-0 -c database -- mysql + +{{- if .Values.mysqlUser }}{{ if .Values.mysqlPassword }}{{ if .Values.mysqlDatabase }} + +2. Run a percona pod that you can use as a client: + + $ kubectl run -i --tty --rm percona-client --image=percona:{{ .Values.image.tag }} --restart=Never -- mysql -h {{ template "percona-xtradb-cluster.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local -u{{ .Values.mysqlUser }} \ + -p$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "percona-xtradb-cluster.fullname" . }} -o jsonpath="{.data.mysql-password}" | base64 --decode; echo) \ + {{ .Values.mysqlDatabase }} +{{ end }}{{ end }}{{ end }} + +{{- if .Values.logTail }} +To view your Percona XtraDB Cluster logs run: + + $ kubectl logs -f {{ template "percona-xtradb-cluster.fullname" . }}-0 logs +{{ end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/_helpers.tpl b/integration/mysql-percona/percona-xtradb-cluster/templates/_helpers.tpl new file mode 100755 index 00000000..ac633908 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/_helpers.tpl @@ -0,0 +1,25 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "percona-xtradb-cluster.name" -}} +{{- default "pxc" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "percona-xtradb-cluster.fullname" -}} +{{- $name := default "pxc" .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{/* +Create a short cluster name in order to fulfill Percona's `wsrep_cluster_name` variable max size. +https://www.percona.com/doc/percona-xtradb-cluster/LATEST/wsrep-system-index.html#wsrep_cluster_name +*/}} +{{- define "percona-xtradb-cluster.shortname" -}} +{{- $name := default "pxc" .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 32 | trimSuffix "-" -}} +{{- end -}} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/config-map_mysql-config.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/config-map_mysql-config.yaml new file mode 100755 index 00000000..89f48dc8 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/config-map_mysql-config.yaml @@ -0,0 +1,20 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }}-config-files + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: +{{- if .Values.configFiles }} +{{ toYaml .Values.configFiles | indent 2 }} +{{ if not (index .Values.configFiles "node.cnf") }} + node.cnf: | +{{ .Files.Get "files/node.cnf" | indent 4 }} +{{ end }} +{{ else }} + node.cnf: | +{{ .Files.Get "files/node.cnf" | indent 4 }} +{{ end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/config-map_startup-scripts.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/config-map_startup-scripts.yaml new file mode 100755 index 00000000..111f16be --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/config-map_startup-scripts.yaml @@ -0,0 +1,14 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }}-startup-scripts + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: + entrypoint.sh: | +{{ .Files.Get "files/entrypoint.sh" | indent 4 }} + functions.sh: | +{{ .Files.Get "files/functions.sh" | indent 4 }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/pdb.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/pdb.yaml new file mode 100755 index 00000000..01bd17ec --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/pdb.yaml @@ -0,0 +1,22 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "percona-xtradb-cluster.fullname" . }}-pdb +spec: +{{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} +{{- end }} +{{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} +{{- end }} + selector: + matchLabels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/prometheusrule.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/prometheusrule.yaml new file mode 100755 index 00000000..fe07a7e3 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/prometheusrule.yaml @@ -0,0 +1,61 @@ +{{ if and .Values.metricsExporter.enabled .Values.prometheus.operator.enabled .Values.prometheus.operator.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }} +{{- if .Values.prometheus.operator.prometheusRule.namespace }} + namespace: {{ .Values.prometheus.operator.prometheusRule.namespace }} +{{- end }} + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + heritage: "{{ .Release.Service }}" + release: "{{ .Release.Name }}" +{{- if .Values.prometheus.operator.prometheusRule.selector }} +{{ toYaml .Values.prometheus.operator.prometheusRule.selector | indent 4 }} +{{- end }} +spec: + groups: + - name: {{ template "percona-xtradb-cluster.fullname" . }}-alerts + rules: + - alert: MySQLGaleraNotReady + annotations: + message: Galera cluster node not ready + expr: mysql_global_status_wsrep_ready{service="{{ template "percona-xtradb-cluster.fullname" . }}-metrics"} != 1 + for: 5m + labels: + severity: critical +{{- if .Values.prometheus.operator.prometheusRule.labels }} +{{ toYaml .Values.prometheus.operator.prometheusRule.labels | indent 8 }} +{{- end }} + - alert: MySQLGaleraOutOfSync + annotations: + message: Galera cluster node out of sync + expr: (mysql_global_status_wsrep_local_state{service="{{ template "percona-xtradb-cluster.fullname" . }}-metrics"} != 4 and mysql_global_variables_wsrep_desync{service="{{ template "percona-xtradb-cluster.fullname" . }}-metrics"} == 0) + for: 5m + labels: + severity: critical +{{- if .Values.prometheus.operator.prometheusRule.labels }} +{{ toYaml .Values.prometheus.operator.prometheusRule.labels | indent 8 }} +{{- end }} + - alert: MySQLGaleraDonorFallingBehind + annotations: + message: xtradb cluster donor node falling behind + expr: (mysql_global_status_wsrep_local_state{service="{{ template "percona-xtradb-cluster.fullname" . }}-metrics"} == 2 and mysql_global_status_wsrep_local_recv_queue{service="{{ template "percona-xtradb-cluster.fullname" . }}-metrics"} > 100) + for: 5m + labels: + severity: warning +{{- if .Values.prometheus.operator.prometheusRule.labels }} +{{ toYaml .Values.prometheus.operator.prometheusRule.labels | indent 8 }} +{{- end }} + - alert: MySQLInnoDBLogWaits + annotations: + message: MySQL innodb log writes stalling + expr: rate(mysql_global_status_innodb_log_waits{service="{{ template "percona-xtradb-cluster.fullname" . }}-metrics"}[15m]) > 10 + for: 5m + labels: + severity: warning +{{- if .Values.prometheus.operator.prometheusRule.labels }} +{{ toYaml .Values.prometheus.operator.prometheusRule.labels | indent 8 }} +{{- end }} +{{ end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/secrets.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/secrets.yaml new file mode 100755 index 00000000..0d150ed6 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/secrets.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }} + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +type: Opaque +data: + {{ if .Values.mysqlRootPassword }} + mysql-root-password: {{ .Values.mysqlRootPassword | b64enc | quote }} + {{ else }} + mysql-root-password: {{ randAlphaNum 10 | b64enc | quote }} + {{ end }} + {{ if .Values.mysqlPassword }} + mysql-password: {{ .Values.mysqlPassword | b64enc | quote }} + {{ else }} + mysql-password: {{ randAlphaNum 10 | b64enc | quote }} + {{ end }} + {{ if .Values.xtraBackupPassword }} + xtrabackup-password: {{ .Values.xtraBackupPassword | b64enc | quote }} + {{ else }} + xtrabackup-password: {{ randAlphaNum 10 | b64enc | quote }} + {{ end }} +{{- if .Values.ssl.enabled }} +{{ if .Values.ssl.certificates }} +{{- range .Values.ssl.certificates }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .name }} + labels: + app: {{ template "percona-xtradb-cluster.fullname" $ }} + chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}" + release: "{{ $.Release.Name }}" + heritage: "{{ $.Release.Service }}" +type: Opaque +data: + ca.pem: {{ .ca | b64enc }} + server-cert.pem: {{ .cert | b64enc }} + server-key.pem: {{ .key | b64enc }} +{{- end }} +{{- end }} +{{- end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/service-metrics.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/service-metrics.yaml new file mode 100755 index 00000000..5bcc5541 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/service-metrics.yaml @@ -0,0 +1,20 @@ +{{ if .Values.metricsExporter.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "percona-xtradb-cluster.fullname" . }}-metrics" + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + clusterIP: None + ports: + - name: metrics + port: 9104 + selector: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: "{{ .Release.Name }}" +{{ end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/service-percona.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/service-percona.yaml new file mode 100755 index 00000000..659fc573 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/service-percona.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }} + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + {{- if .Values.service.percona.headless }} + clusterIP: None + {{- end }} + ports: + - name: mysql + port: 3306 + targetPort: mysql + selector: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: "{{ .Release.Name }}" diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/service-repl.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/service-repl.yaml new file mode 100755 index 00000000..e488492c --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/service-repl.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "percona-xtradb-cluster.fullname" . }}-repl" + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + clusterIP: None + ports: + - name: galera + port: 4567 + - name: state-xfer + port: 4568 + - name: state-snap + port: 4444 + selector: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: "{{ .Release.Name }}" diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/servicemonitor.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/servicemonitor.yaml new file mode 100755 index 00000000..7b721682 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/servicemonitor.yaml @@ -0,0 +1,27 @@ +{{ if and .Values.metricsExporter.enabled .Values.prometheus.operator.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }} +{{- if .Values.prometheus.operator.serviceMonitor.namespace }} + namespace: {{ .Values.prometheus.operator.serviceMonitor.namespace }} +{{- end }} + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + heritage: "{{ .Release.Service }}" + release: "{{ .Release.Name }}" +{{- if .Values.prometheus.operator.serviceMonitor.selector }} +{{ toYaml .Values.prometheus.operator.serviceMonitor.selector | indent 4 }} +{{- end }} +spec: + selector: + matchLabels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + interval: {{ .Values.prometheus.operator.serviceMonitor.interval }} + namespaceSelector: + any: true +{{ end }} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/statefulset.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/statefulset.yaml new file mode 100755 index 00000000..0055d6c9 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/statefulset.yaml @@ -0,0 +1,211 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }} + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + release: "{{ .Release.Name }}" + serviceName: {{ template "percona-xtradb-cluster.fullname" . }} + template: + metadata: + labels: + app: {{ template "percona-xtradb-cluster.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.podAnnotations }} + annotations: +{{ toYaml .Values.podAnnotations | indent 8 }} + {{- end }} + spec: + initContainers: + - name: "remove-lost-found" + image: "busybox:1.25.0" + imagePullPolicy: IfNotPresent + command: + - "rm" + - "-fr" + - "/var/lib/mysql/lost+found" + volumeMounts: + - name: mysql-data + mountPath: /var/lib/mysql + containers: + - name: database + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + command: + - "/bin/bash" + - "/startup-scripts/entrypoint.sh" + resources: +{{ toYaml .Values.resources | indent 10 }} + env: + {{- if .Values.mysqlAllowEmptyPassword }} + - name: MYSQL_ALLOW_EMPTY_PASSWORD + value: "true" + {{- else }} + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "percona-xtradb-cluster.fullname" . }} + key: mysql-root-password + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "percona-xtradb-cluster.fullname" . }} + key: mysql-password + {{- end }} + - name: XTRABACKUP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "percona-xtradb-cluster.fullname" . }} + key: xtrabackup-password + - name: MYSQL_USER + value: {{ default "" .Values.mysqlUser | quote }} + - name: MYSQL_DATABASE + value: {{ default "" .Values.mysqlDatabase | quote }} + - name: ALLOW_ROOT_FROM + value: {{ .Values.allowRootFrom | quote }} + - name: CLUSTER_NAME + value: {{ template "percona-xtradb-cluster.fullname" . }} + - name: SHORT_CLUSTER_NAME + value: {{ template "percona-xtradb-cluster.shortname" . }} + - name: K8S_SERVICE_NAME + value: {{ template "percona-xtradb-cluster.fullname" . }}-repl + - name: PXC_STRICT_MODE + value: {{ default "ENFORCING" .Values.pxc_strict_mode | quote }} + - name: DEBUG + value: "true" + ports: + - name: mysql + containerPort: 3306 + - name: galera-repl + containerPort: 4567 + - name: state-transfer + containerPort: 4568 + - name: state-snapshot + containerPort: 4444 + livenessProbe: + exec: + command: + - "/bin/bash" + - "-c" + - "mysqladmin ping || test -e /var/lib/mysql/sst_in_progress" + initialDelaySeconds: 30 + timeoutSeconds: 2 + readinessProbe: + exec: + command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"] + initialDelaySeconds: 30 + timeoutSeconds: 2 + volumeMounts: + - name: mysql-data + mountPath: /var/lib/mysql + - name: mysql-startup-scripts + mountPath: /startup-scripts + - name: mysql-config-files + mountPath: /etc/mysql/conf.d + - name: slash-root + mountPath: /root + - name: var-log + mountPath: /var/log + {{- if .Values.ssl.enabled }} + - name: certificates + mountPath: /ssl + {{- end }} + {{ if .Values.logTail }} + - name: "logs" + image: "busybox:1.25.0" + imagePullPolicy: IfNotPresent + command: + - "tail" + - "-f" + - "/var/log/mysqld.log" + volumeMounts: + - name: var-log + mountPath: /var/log + {{ end }} + {{ if .Values.metricsExporter.enabled }} + - name: metrics + {{- if .Values.metricsExporter.tag }} + image: prom/mysqld-exporter:{{ .Values.metricsExporter.tag }} + {{- else }} + image: prom/mysqld-exporter + {{- end }} + imagePullPolicy: IfNotPresent +{{- if .Values.metricsExporter.commandOverrides }} + command: +{{ toYaml .Values.metricsExporter.commandOverrides | indent 8 }} +{{- end }} +{{- if .Values.metricsExporter.argsOverrides }} + args: +{{ toYaml .Values.metricsExporter.argsOverrides | indent 8 }} +{{- end }} + ports: + - name: metrics + containerPort: 9104 + volumeMounts: + - name: slash-root + mountPath: /root + livenessProbe: + exec: + command: ["wget","-q","-O","-","localhost:9104"] + initialDelaySeconds: 30 + timeoutSeconds: 2 + readinessProbe: + exec: + command: ["wget","-q","-O","-","localhost:9104"] + initialDelaySeconds: 30 + timeoutSeconds: 2 + {{ end }} + volumes: + - name: slash-root + emptyDir: {} + - name: var-log + emptyDir: {} + - name: mysql-config-files + configMap: + name: {{ template "percona-xtradb-cluster.fullname" . }}-config-files + - name: mysql-startup-scripts + configMap: + name: {{ template "percona-xtradb-cluster.fullname" . }}-startup-scripts + {{- if not .Values.persistence.enabled }} + - name: mysql-data + emptyDir: {} + {{- end -}} + {{- if .Values.ssl.enabled }} + - name: certificates + secret: + secretName: {{ .Values.ssl.secret }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} + {{- end -}} + {{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- end -}} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: mysql-data + spec: + accessModes: [{{ .Values.persistence.accessMode | quote }}] + {{- if .Values.persistence.storageClass }} + {{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- end -}} diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test-cm.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test-cm.yaml new file mode 100755 index 00000000..98f83470 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test-cm.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "percona-xtradb-cluster.fullname" . }}-tests +data: + run.sh: |- + echo "Testing Percona XtraDB Cluster" + creds="-uroot -p${MYSQL_ROOT_PASSWORD} -h {{ template "percona-xtradb-cluster.fullname" . }}" + echo "==> basic mysql ping" + mysqladmin $creds ping + [[ $? != "0" ]] && exit $? + echo "==> test basic query" + mysql $creds -e "select now() \G" + [[ $? != "0" ]] && exit $? + echo "==> checking if cluster is ready" + mysql $creds -e "SHOW GLOBAL STATUS LIKE 'wsrep_ready' \G" | grep Value | awk '{ print $2}' | grep ON + [[ $? != "0" ]] && exit $? + echo "==> checking if cluster size matches replica count" + cluster_size=$(mysql $creds -e "SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size' \G" | grep Value | awk '{ print $2}') + [[ $? != "0" ]] && exit $? + [[ "${cluster_size}" == "{{ .Values.replicas }}" ]] || exit 1 + echo "SUCCESS" + exit 0 diff --git a/integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test.yaml b/integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test.yaml new file mode 100755 index 00000000..8e415c49 --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/templates/tests/pxc-test.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-test" + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: pxc-test + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + command: ["bash", "/tests/run.sh"] + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "percona-xtradb-cluster.fullname" . }} + key: mysql-root-password + volumeMounts: + - mountPath: /tests + name: tests + volumes: + - name: tests + configMap: + name: {{ template "percona-xtradb-cluster.fullname" . }}-tests + restartPolicy: Never diff --git a/integration/mysql-percona/percona-xtradb-cluster/values.yaml b/integration/mysql-percona/percona-xtradb-cluster/values.yaml new file mode 100755 index 00000000..4d10525c --- /dev/null +++ b/integration/mysql-percona/percona-xtradb-cluster/values.yaml @@ -0,0 +1,169 @@ +# Default values for Percona XtraDB Cluster + +## percona image and version +## ref: https://hub.docker.com/r/percona/percona-xtradb-cluster/tags/ +image: + repository: "percona/percona-xtradb-cluster" + tag: "5.7.19" + pullPolicy: IfNotPresent + +# Desired number of members of xtradb cluster +replicas: 3 + +## Specify password for root user +## +# mysqlRootPassword: not-a-secure-password + +## Specify password for xtradb backup user +## +# xtraBackupPassword: replicate-my-data + +## Uncomment to create a database user +## +# mysqlUser: test +# mysqlPassword: test + +## Allow unauthenticated access, uncomment to enable +## +# mysqlAllowEmptyPassword: true + +## Uncomment to Create a database +## +# mysqlDatabase: test + +## Configure pxc_strict_mode +## ref: https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html +## pxc_strict_mode: ENFORCING + +## hosts to allow root user access from +# set to "127.0.0.1" to deny remote root. +allowRootFrom: "%" + +## Persist data to a persistent volume +persistence: + enabled: false + ## percona data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessMode: ReadWriteOnce + size: 8Gi + +## Node labels for pod assignment +## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Pod annotations +## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} + +## Tolerations labels for pod assignment +## Allow the scheduling on tainted nodes (requires Kubernetes >= 1.6) +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +# resources: +# requests: +# memory: 256Mi +# cpu: 100m + +configFiles: + node.cnf: |+ + [mysqld] + datadir=/var/lib/mysql + default_storage_engine=InnoDB + binlog_format=ROW + innodb_flush_log_at_trx_commit = 0 + innodb_flush_method = O_DIRECT + innodb_file_per_table = 1 + innodb_autoinc_lock_mode=2 + bind_address = 0.0.0.0 + wsrep_slave_threads=2 + wsrep_cluster_address=gcomm:// + wsrep_provider=/usr/lib/galera3/libgalera_smm.so + wsrep_cluster_name=galera + wsrep_sst_method=xtrabackup-v2 + +## When set to true will create sidecar for `prom/mysqld-exporter` +## metrics exporting +metricsExporter: + enabled: false + commandOverrides: [] + argsOverrides: [] + +prometheus: + ## Are you using [Prometheus Operator](https://coreos.com/operators/prometheus/docs/latest/user-guides/getting-started.html)? + operator: + ## Setting to true will create Prometheus-Operator specific resources like ServiceMonitors + enabled: false + + ## Configures alerts for Prometheus to pick up + prometheusRule: + enabled: true + + ## Labels to add to alerts + labels: {} + + ## Namespace which Prometheus is installed in + # namespace: monitoring + + ## Label Selector for Prometheus to find alert rules + # selector: + # prometheus: kube-prometheus + + ## Configures targets for Prometheus to pick up + serviceMonitor: + ## Interval at which Prometheus will scrape metrics exporter + interval: 10s + + ## Namespace which Prometheus is installed in + # namespace: monitoring + + ## Label Selector for Prometheus to find ServiceMonitors + # selector: + # prometheus: kube-prometheus + +## When set to true will create sidecar to tail mysql log +logTail: true + +ssl: + enabled: false + secret: mysql-ssl-certs + certificates: +# - name: mysql-ssl-certs +# ca: |- +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- +# cert: |- +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- +# key: |- +# -----BEGIN RSA PRIVATE KEY----- +# ... +# -----END RSA PRIVATE KEY----- + +## Configure PodDisruptionBudget +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +# +podDisruptionBudget: + enabled: false + # minAvailable: 1 + maxUnavailable: 1 + +## Set the percona kubernetes service headless (no load-balancing) +## ref: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services +service: + percona: + headless: false diff --git a/integration/mysql-percona/requirements.yaml b/integration/mysql-percona/requirements.yaml index 335e1f86..bc50365a 100644 --- a/integration/mysql-percona/requirements.yaml +++ b/integration/mysql-percona/requirements.yaml @@ -1,5 +1,5 @@ dependencies: - name: percona-xtradb-cluster - repository: "@stable" + repository: "file://./percona-xtradb-cluster" version: 1.0.3 alias: percona \ No newline at end of file From 7954bda447d695a134550a833c16046de5b58fde Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Thu, 4 Jun 2020 11:02:29 +0200 Subject: [PATCH 10/11] METAL-1890_proxySQL support backend percona server as array and readonly option --- helm/db-instances/templates/dbinstance.yaml | 9 +- helm/db-operator/templates/crd.yaml | 30 +++- .../mysql-percona/templates/instance.yaml | 10 +- .../mysql-percona/templates/mysql-server.yaml | 40 ------ pkg/apis/kci/v1alpha1/dbinstance_types.go | 12 +- .../kci/v1alpha1/zz_generated.deepcopy.go | 18 ++- pkg/controller/database/proxyHelper.go | 16 ++- .../dbinstance/reconcileDbInstance.go | 4 +- pkg/utils/proxy/proxysql.go | 18 +-- pkg/utils/proxy/proxysql/template.go | 128 +++++++++--------- 10 files changed, 149 insertions(+), 136 deletions(-) delete mode 100644 integration/mysql-percona/templates/mysql-server.yaml diff --git a/helm/db-instances/templates/dbinstance.yaml b/helm/db-instances/templates/dbinstance.yaml index 1b89c192..d5ee8783 100644 --- a/helm/db-instances/templates/dbinstance.yaml +++ b/helm/db-instances/templates/dbinstance.yaml @@ -56,10 +56,13 @@ spec: percona: servers: {{- range $server := $value.percona.servers }} - - {{ $server }} + - host: {{ $server.host }} + port: {{ $server.port }} + maxConn: {{ $server.maxConn }} + {{- if $server.readonly }} + readonly: {{ $server.readonly }} + {{- end }} {{- end }} - port: {{ $value.percona.port }} - maxConn: {{ $value.percona.maxConn }} monitorUserSecretRef: Name: {{ $name }}-monitoruser-secret Namespace: {{ $operatorNs }} diff --git a/helm/db-operator/templates/crd.yaml b/helm/db-operator/templates/crd.yaml index 07d583cf..d69036aa 100644 --- a/helm/db-operator/templates/crd.yaml +++ b/helm/db-operator/templates/crd.yaml @@ -90,6 +90,13 @@ spec: required: - engine - adminSecretRef + oneOf: + - required: + - percona + - required: + - google + - required: + - generic properties: engine: type: string @@ -104,6 +111,9 @@ spec: type: object generic: type: object + required: + - host + - port properties: host: type: string @@ -111,6 +121,8 @@ spec: type: integer google: type: object + required: + - instance properties: instance: type: string @@ -125,13 +137,25 @@ spec: type: object required: - servers - - port - monitorUserSecretRef properties: servers: type: array - port: - type: integer + items: + required: + - host + - port + - maxConn + properties: + host: + type: string + port: + type: integer + maxConn: + type: integer + minimum: 1 + readonly: + type: boolean monitorUserSecretRef: type: object status: diff --git a/integration/mysql-percona/templates/instance.yaml b/integration/mysql-percona/templates/instance.yaml index c679cfc5..07804eca 100644 --- a/integration/mysql-percona/templates/instance.yaml +++ b/integration/mysql-percona/templates/instance.yaml @@ -9,10 +9,12 @@ spec: engine: mysql percona: servers: - - {{ .Release.Name }}-pxc-0.{{ .Release.Name }}-pxc.{{ .Release.Namespace }} - - {{ .Release.Name }}-pxc-1.{{ .Release.Name }}-pxc.{{ .Release.Namespace }} - port: 3306 - maxConn: 100 + - host: {{ .Release.Name }}-pxc-0.{{ .Release.Name }}-pxc.{{ .Release.Namespace }} + port: 3306 + maxConn: 100 + - host: {{ .Release.Name }}-pxc-1.{{ .Release.Name }}-pxc.{{ .Release.Namespace }} + port: 3306 + maxConn: 100 monitorUserSecretRef: Namespace: {{ .Release.Namespace }} Name: {{ .Values.instance.name }}-monitoruser-secret diff --git a/integration/mysql-percona/templates/mysql-server.yaml b/integration/mysql-percona/templates/mysql-server.yaml deleted file mode 100644 index d59e0db6..00000000 --- a/integration/mysql-percona/templates/mysql-server.yaml +++ /dev/null @@ -1,40 +0,0 @@ -{{- if .Values.mysql }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.mysql.serviceName }} -spec: - ports: - - port: 3306 - selector: - app: mysql - role: test - type: ClusterIP ---- -apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 -kind: Deployment -metadata: - name: {{ .Values.mysql.serviceName }} -spec: - replicas: 1 - selector: - matchLabels: - app: mysql - role: test - strategy: - type: Recreate - template: - metadata: - labels: - app: mysql - role: test - spec: - containers: - - image: {{ .Values.mysql.image }} - name: mysql - env: - # Use secret in real usage - - name: MYSQL_ROOT_PASSWORD - value: {{ .Values.mysql.adminPassword }} -{{- end }} \ No newline at end of file diff --git a/pkg/apis/kci/v1alpha1/dbinstance_types.go b/pkg/apis/kci/v1alpha1/dbinstance_types.go index dae9bff3..7bf1ef5e 100644 --- a/pkg/apis/kci/v1alpha1/dbinstance_types.go +++ b/pkg/apis/kci/v1alpha1/dbinstance_types.go @@ -47,12 +47,18 @@ type GoogleInstance struct { // PerconaCluster is used when instance type is percona cluster type PerconaCluster struct { - ServerList []string `json:"servers"` // hostgroup: host address - Port uint16 `json:"port"` - MaxConnection uint8 `json:"maxConn"` + ServerList []BackendServer `json:"servers"` // hostgroup: host address MonitorUserSecret types.NamespacedName `json:"monitorUserSecretRef"` } +// BackendServer defines backend database server +type BackendServer struct { + Host string `json:"host"` + Port uint16 `json:"port"` + MaxConnection uint8 `json:"maxConn"` + ReadOnly bool `json:"readonly,omitempty"` +} + // GenericInstance is used when instance type is generic // and describes necessary informations to use instance // generic instance can be any backend, it must be reachable by described address and port diff --git a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go index 57b73644..d60118f7 100644 --- a/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kci/v1alpha1/zz_generated.deepcopy.go @@ -8,6 +8,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendServer) DeepCopyInto(out *BackendServer) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendServer. +func (in *BackendServer) DeepCopy() *BackendServer { + if in == nil { + return nil + } + out := new(BackendServer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Database) DeepCopyInto(out *Database) { *out = *in @@ -357,7 +373,7 @@ func (in *PerconaCluster) DeepCopyInto(out *PerconaCluster) { *out = *in if in.ServerList != nil { in, out := &in.ServerList, &out.ServerList - *out = make([]string, len(*in)) + *out = make([]BackendServer, len(*in)) copy(*out, *in) } out.MonitorUserSecret = in.MonitorUserSecret diff --git a/pkg/controller/database/proxyHelper.go b/pkg/controller/database/proxyHelper.go index 83a423cf..bc84b4eb 100644 --- a/pkg/controller/database/proxyHelper.go +++ b/pkg/controller/database/proxyHelper.go @@ -7,6 +7,7 @@ import ( kciv1alpha1 "github.com/kloeckner-i/db-operator/pkg/apis/kci/v1alpha1" "github.com/kloeckner-i/db-operator/pkg/utils/kci" proxy "github.com/kloeckner-i/db-operator/pkg/utils/proxy" + "github.com/kloeckner-i/db-operator/pkg/utils/proxy/proxysql" "github.com/sirupsen/logrus" ) @@ -60,15 +61,24 @@ func determinProxyType(dbcr *kciv1alpha1.Database) (proxy.Proxy, error) { "db-name": dbcr.Name, } + var backends []proxysql.Backend + for _, s := range instance.Spec.Percona.ServerList { + backend := proxysql.Backend{ + Host: s.Host, + Port: strconv.FormatInt(int64(s.Port), 10), + MaxConn: strconv.FormatInt(int64(s.MaxConnection), 10), + ReadOnly: s.ReadOnly, + } + backends = append(backends, backend) + } + return &proxy.ProxySQL{ NamePrefix: "db-" + dbcr.Name, Namespace: dbcr.Namespace, - Servers: instance.Spec.Percona.ServerList, - MaxConn: instance.Spec.Percona.MaxConnection, + Servers: backends, UserSecretName: dbcr.Spec.SecretName, MonitorUserSecretName: dbcr.Status.MonitorUserSecretName, Engine: engine, - Port: uint16(port), Labels: kci.LabelBuilder(labels), }, nil default: diff --git a/pkg/controller/dbinstance/reconcileDbInstance.go b/pkg/controller/dbinstance/reconcileDbInstance.go index d9c0689f..6c1c99d3 100644 --- a/pkg/controller/dbinstance/reconcileDbInstance.go +++ b/pkg/controller/dbinstance/reconcileDbInstance.go @@ -59,8 +59,8 @@ func (r *ReconcileDbInstance) create(dbin *kciv1alpha1.DbInstance) error { } instance = &dbinstance.Generic{ - Host: dbin.Spec.Percona.ServerList[0], - Port: dbin.Spec.Percona.Port, + Host: dbin.Spec.Percona.ServerList[0].Host, + Port: dbin.Spec.Percona.ServerList[0].Port, Engine: dbin.Spec.Engine, User: cred.Username, Password: cred.Password, diff --git a/pkg/utils/proxy/proxysql.go b/pkg/utils/proxy/proxysql.go index 2e288626..59ea5eb0 100644 --- a/pkg/utils/proxy/proxysql.go +++ b/pkg/utils/proxy/proxysql.go @@ -18,12 +18,10 @@ import ( type ProxySQL struct { NamePrefix string Namespace string - Servers []string - MaxConn uint8 + Servers []proxysql.Backend UserSecretName string MonitorUserSecretName string Engine string - Port uint16 Labels map[string]string } @@ -71,7 +69,7 @@ func (ps *ProxySQL) buildDeployment() (*v1apps.Deployment, error) { return &v1apps.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", - APIVersion: "extensions/apps", + APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: ps.NamePrefix + "-proxysql", @@ -252,20 +250,10 @@ func (ps *ProxySQL) buildConfigMap() (*v1.ConfigMap, error) { configTmpl := proxysql.PerconaMysqlConfigTemplate t := template.Must(template.New("config").Parse(configTmpl)) - var backends []proxysql.Backend - for _, s := range ps.Servers { - backend := proxysql.Backend{ - Host: s, - Port: strconv.FormatInt(int64(ps.Port), 10), - MaxConn: strconv.FormatInt(int64(ps.MaxConn), 10), - } - backends = append(backends, backend) - } - config := proxysql.Config{ AdminPort: strconv.FormatInt(int64(adminPort), 10), SQLPort: strconv.FormatInt(int64(sqlPort), 10), - Backends: backends, + Backends: ps.Servers, } var outputBuf bytes.Buffer diff --git a/pkg/utils/proxy/proxysql/template.go b/pkg/utils/proxy/proxysql/template.go index ecc5aaa8..04c2e7f9 100644 --- a/pkg/utils/proxy/proxysql/template.go +++ b/pkg/utils/proxy/proxysql/template.go @@ -11,96 +11,100 @@ type Config struct { // Backend defines list of servers behind of proxysql type Backend struct { Host, Port, MaxConn string + ReadOnly bool } // PerconaMysqlConfigTemplate defines proxysql config template for mysql percona cluster // Later this could be moved out completely to outside so that it's more configurable. -const PerconaMysqlConfigTemplate = ` -datadir="/var/lib/proxysql" +const PerconaMysqlConfigTemplate = `datadir="/var/lib/proxysql" admin_variables= { - mysql_ifaces="0.0.0.0:{{.AdminPort}}" - refresh_interval=2000 - web_enabled=false - web_port=6080 - stats_credentials="stats:admin" + mysql_ifaces="0.0.0.0:{{.AdminPort}}" + refresh_interval=2000 + web_enabled=false + web_port=6080 + stats_credentials="stats:admin" } mysql_variables= { - threads=4 - max_connections=2048 - default_query_delay=0 - default_query_timeout=36000000 - have_compress=true - poll_timeout=2000 - interfaces="0.0.0.0:{{.SQLPort}};/tmp/proxysql.sock" - default_schema="information_schema" - stacksize=1048576 - server_version="5.7.28" - connect_timeout_server=10000 - monitor_history=60000 - monitor_connect_interval=200000 - monitor_ping_interval=200000 - ping_interval_server_msec=10000 - ping_timeout_server=200 - commands_stats=true - sessions_sort=true - monitor_username="$MONITOR_USERNAME" - monitor_password="$MONITOR_PASSWORD" - monitor_galera_healthcheck_interval=2000 - monitor_galera_healthcheck_timeout=800 + threads=4 + max_connections=2048 + default_query_delay=0 + default_query_timeout=36000000 + have_compress=true + poll_timeout=2000 + interfaces="0.0.0.0:{{.SQLPort}};/tmp/proxysql.sock" + default_schema="information_schema" + stacksize=1048576 + server_version="5.7.28" + connect_timeout_server=10000 + monitor_history=60000 + monitor_connect_interval=200000 + monitor_ping_interval=200000 + ping_interval_server_msec=10000 + ping_timeout_server=200 + commands_stats=true + sessions_sort=true + monitor_username="$MONITOR_USERNAME" + monitor_password="$MONITOR_PASSWORD" + monitor_galera_healthcheck_interval=2000 + monitor_galera_healthcheck_timeout=800 } mysql_galera_hostgroups = ( - { - writer_hostgroup=10 - backup_writer_hostgroup=20 - reader_hostgroup=30 - offline_hostgroup=9999 - max_writers=2 - writer_is_also_reader=2 - max_transactions_behind=30 - active=1 - } + { + writer_hostgroup=10 + backup_writer_hostgroup=20 + reader_hostgroup=30 + offline_hostgroup=9999 + max_writers=2 + writer_is_also_reader=2 + max_transactions_behind=30 + active=1 + } ) mysql_servers = ( {{- range .Backends }} - { address="{{.Host}}", port={{.Port}}, hostgroup=10, max_connections={{.MaxConn}} }, + {{- if .ReadOnly }} + { address="{{.Host}}", port={{.Port}}, hostgroup=20, max_connections={{.MaxConn}} }, + {{- else }} + { address="{{.Host}}", port={{.Port}}, hostgroup=10, max_connections={{.MaxConn}} }, + {{- end }} {{- end }} ) mysql_users = ( - { username = "$DB_USERNAME", password = "$DB_PASSWORD", default_hostgroup = 10, transaction_persistent = 0, active = 1 } + { username = "$DB_USERNAME", password = "$DB_PASSWORD", default_hostgroup = 10, transaction_persistent = 0, active = 1 } ) mysql_query_rules = ( - { - rule_id=100 - active=1 - match_pattern="^SELECT .* FOR UPDATE" - destination_hostgroup=10 - apply=1 - }, - { - rule_id=200 - active=1 - match_pattern="^SELECT .*" - destination_hostgroup=20 - apply=1 - }, - { - rule_id=300 - active=1 - match_pattern=".*" - destination_hostgroup=10 - apply=1 - } + { + rule_id=100 + active=1 + match_pattern="^SELECT .* FOR UPDATE" + destination_hostgroup=10 + apply=1 + }, + { + rule_id=200 + active=1 + match_pattern="^SELECT .*" + destination_hostgroup=20 + apply=1 + }, + { + rule_id=300 + active=1 + match_pattern=".*" + destination_hostgroup=10 + apply=1 + } ) ` From 0acd3fd808ce3470087388bf19ecfb83f8a9a2da Mon Sep 17 00:00:00 2001 From: Soohyun Kim Date: Thu, 4 Jun 2020 11:04:47 +0200 Subject: [PATCH 11/11] METAL-1890_proxySQL helm chart release --- helm/db-instances-0.4.0.tgz | Bin 0 -> 3458 bytes helm/db-instances/Chart.yaml | 2 +- helm/db-operator-0.3.0.tgz | Bin 0 -> 5910 bytes helm/db-operator/Chart.yaml | 2 +- helm/index.yaml | 34 ++++++++++++++++++++++++++-------- 5 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 helm/db-instances-0.4.0.tgz create mode 100644 helm/db-operator-0.3.0.tgz diff --git a/helm/db-instances-0.4.0.tgz b/helm/db-instances-0.4.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6524575cbfa0312bc1211948b8810574f7b1bce8 GIT binary patch literal 3458 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 literal 0 HcmV?d00001 diff --git a/helm/db-instances/Chart.yaml b/helm/db-instances/Chart.yaml index bf93caa1..ae92f808 100644 --- a/helm/db-instances/Chart.yaml +++ b/helm/db-instances/Chart.yaml @@ -2,4 +2,4 @@ apiVersion: v1 appVersion: "1.0" description: Database Instances for db operator name: db-instances -version: 0.3.1 +version: 0.4.0 diff --git a/helm/db-operator-0.3.0.tgz b/helm/db-operator-0.3.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0ab88ff867ca891f9e12de4d60b930b8fb686065 GIT binary patch literal 5910 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