github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/pxc/app/statefulset/node.go (about)

     1  package statefulset
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"hash/fnv"
     7  
     8  	appsv1 "k8s.io/api/apps/v1"
     9  	corev1 "k8s.io/api/core/v1"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/types"
    12  	"sigs.k8s.io/controller-runtime/pkg/client"
    13  
    14  	api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
    15  	app "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app"
    16  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/config"
    17  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/users"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  const (
    22  	VaultSecretVolumeName = "vault-keyring-secret"
    23  )
    24  
    25  type Node struct {
    26  	sfs     *appsv1.StatefulSet
    27  	labels  map[string]string
    28  	service string
    29  }
    30  
    31  func NewNode(cr *api.PerconaXtraDBCluster) *Node {
    32  	sfs := &appsv1.StatefulSet{
    33  		TypeMeta: metav1.TypeMeta{
    34  			APIVersion: "apps/v1",
    35  			Kind:       "StatefulSet",
    36  		},
    37  		ObjectMeta: metav1.ObjectMeta{
    38  			Name:      cr.Name + "-" + app.Name,
    39  			Namespace: cr.Namespace,
    40  		},
    41  	}
    42  
    43  	labels := map[string]string{
    44  		"app.kubernetes.io/name":       "percona-xtradb-cluster",
    45  		"app.kubernetes.io/instance":   cr.Name,
    46  		"app.kubernetes.io/component":  "pxc",
    47  		"app.kubernetes.io/managed-by": "percona-xtradb-cluster-operator",
    48  		"app.kubernetes.io/part-of":    "percona-xtradb-cluster",
    49  	}
    50  
    51  	return &Node{
    52  		sfs:     sfs,
    53  		labels:  labels,
    54  		service: cr.Name + "-" + app.Name,
    55  	}
    56  }
    57  
    58  func (c *Node) Name() string {
    59  	return app.Name
    60  }
    61  
    62  func (c *Node) AppContainer(spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster, _ []corev1.Volume) (corev1.Container, error) {
    63  	redinessDelay := int32(15)
    64  	if spec.ReadinessInitialDelaySeconds != nil {
    65  		redinessDelay = *spec.ReadinessInitialDelaySeconds
    66  	}
    67  	livenessDelay := int32(300)
    68  	if spec.LivenessInitialDelaySeconds != nil {
    69  		livenessDelay = *spec.LivenessInitialDelaySeconds
    70  	}
    71  	tvar := true
    72  
    73  	serverIDHash := fnv.New32()
    74  	serverIDHash.Write([]byte(string(cr.UID)))
    75  
    76  	// we cut first 3 symbols to give a space for hostname(actially, pod number)
    77  	// which is appended to all server ids. If we do not do this, it
    78  	// can cause a int32 overflow
    79  	// P.S max value is 4294967295
    80  	serverIDHashStr := fmt.Sprint(serverIDHash.Sum32())
    81  	if len(serverIDHashStr) > 7 {
    82  		serverIDHashStr = serverIDHashStr[:7]
    83  	}
    84  
    85  	appc := corev1.Container{
    86  		Name:            app.Name,
    87  		Image:           spec.Image,
    88  		ImagePullPolicy: spec.ImagePullPolicy,
    89  		ReadinessProbe: app.Probe(&corev1.Probe{
    90  			InitialDelaySeconds: redinessDelay,
    91  			TimeoutSeconds:      15,
    92  			PeriodSeconds:       30,
    93  			FailureThreshold:    5,
    94  		}, "/var/lib/mysql/readiness-check.sh"),
    95  		LivenessProbe: app.Probe(&corev1.Probe{
    96  			InitialDelaySeconds: livenessDelay,
    97  			TimeoutSeconds:      5,
    98  			PeriodSeconds:       10,
    99  		}, "/var/lib/mysql/liveness-check.sh"),
   100  		Args:    []string{"mysqld"},
   101  		Command: []string{"/var/lib/mysql/pxc-entrypoint.sh"},
   102  		Ports: []corev1.ContainerPort{
   103  			{
   104  				ContainerPort: 3306,
   105  				Name:          "mysql",
   106  			},
   107  			{
   108  				ContainerPort: 4444,
   109  				Name:          "sst",
   110  			},
   111  			{
   112  				ContainerPort: 4567,
   113  				Name:          "write-set",
   114  			},
   115  			{
   116  				ContainerPort: 4568,
   117  				Name:          "ist",
   118  			},
   119  			{
   120  				ContainerPort: 33062,
   121  				Name:          "mysql-admin",
   122  			},
   123  			{
   124  				ContainerPort: 33060,
   125  				Name:          "mysqlx",
   126  			},
   127  		},
   128  		VolumeMounts: []corev1.VolumeMount{
   129  			{
   130  				Name:      app.DataVolumeName,
   131  				MountPath: "/var/lib/mysql",
   132  			},
   133  			{
   134  				Name:      "config",
   135  				MountPath: "/etc/percona-xtradb-cluster.conf.d",
   136  			},
   137  			{
   138  				Name:      "tmp",
   139  				MountPath: "/tmp",
   140  			},
   141  			{
   142  				Name:      "ssl",
   143  				MountPath: "/etc/mysql/ssl",
   144  			},
   145  			{
   146  				Name:      "ssl-internal",
   147  				MountPath: "/etc/mysql/ssl-internal",
   148  			},
   149  			{
   150  				Name:      "mysql-users-secret-file",
   151  				MountPath: "/etc/mysql/mysql-users-secret",
   152  			},
   153  			{
   154  				Name:      "auto-config",
   155  				MountPath: "/etc/my.cnf.d",
   156  			},
   157  			{
   158  				Name:      VaultSecretVolumeName,
   159  				MountPath: "/etc/mysql/vault-keyring-secret",
   160  			},
   161  		},
   162  		Env: []corev1.EnvVar{
   163  			{
   164  				Name:  "PXC_SERVICE",
   165  				Value: c.labels["app.kubernetes.io/instance"] + "-" + c.labels["app.kubernetes.io/component"] + "-unready",
   166  			},
   167  			{
   168  				Name:  "MONITOR_HOST",
   169  				Value: "%",
   170  			},
   171  			{
   172  				Name: "MYSQL_ROOT_PASSWORD",
   173  				ValueFrom: &corev1.EnvVarSource{
   174  					SecretKeyRef: app.SecretKeySelector(secrets, users.Root),
   175  				},
   176  			},
   177  			{
   178  				Name: "XTRABACKUP_PASSWORD",
   179  				ValueFrom: &corev1.EnvVarSource{
   180  					SecretKeyRef: app.SecretKeySelector(secrets, users.Xtrabackup),
   181  				},
   182  			},
   183  			{
   184  				Name: "MONITOR_PASSWORD",
   185  				ValueFrom: &corev1.EnvVarSource{
   186  					SecretKeyRef: app.SecretKeySelector(secrets, users.Monitor),
   187  				},
   188  			},
   189  		},
   190  		EnvFrom: []corev1.EnvFromSource{
   191  			{
   192  				SecretRef: &corev1.SecretEnvSource{
   193  					LocalObjectReference: corev1.LocalObjectReference{
   194  						Name: cr.Spec.PXC.EnvVarsSecretName,
   195  					},
   196  					Optional: &tvar,
   197  				},
   198  			},
   199  		},
   200  		SecurityContext: spec.ContainerSecurityContext,
   201  		Resources:       spec.Resources,
   202  	}
   203  
   204  	if cr.CompareVersionWith("1.11.0") >= 0 && cr.Spec.PXC != nil && cr.Spec.PXC.HookScript != "" {
   205  		appc.VolumeMounts = append(appc.VolumeMounts, corev1.VolumeMount{
   206  			Name:      "hookscript",
   207  			MountPath: "/opt/percona/hookscript",
   208  		})
   209  	}
   210  
   211  	if cr.Spec.LogCollector != nil && cr.Spec.LogCollector.Enabled {
   212  		appc.Env = append(appc.Env, []corev1.EnvVar{
   213  			{
   214  				Name:  "LOG_DATA_DIR",
   215  				Value: "/var/lib/mysql",
   216  			},
   217  			{
   218  				Name:  "IS_LOGCOLLECTOR",
   219  				Value: "yes",
   220  			},
   221  		}...)
   222  	}
   223  
   224  	appc.Env = append(appc.Env, []corev1.EnvVar{
   225  		{
   226  			Name:  "CLUSTER_HASH",
   227  			Value: serverIDHashStr,
   228  		},
   229  		{
   230  			Name: "OPERATOR_ADMIN_PASSWORD",
   231  			ValueFrom: &corev1.EnvVarSource{
   232  				SecretKeyRef: app.SecretKeySelector(secrets, users.Operator),
   233  			},
   234  		},
   235  		{
   236  			Name:  "LIVENESS_CHECK_TIMEOUT",
   237  			Value: fmt.Sprint(spec.LivenessProbes.TimeoutSeconds),
   238  		},
   239  		{
   240  			Name:  "READINESS_CHECK_TIMEOUT",
   241  			Value: fmt.Sprint(spec.ReadinessProbes.TimeoutSeconds),
   242  		},
   243  	}...)
   244  
   245  	if cr.CompareVersionWith("1.13.0") >= 0 {
   246  		plugin := "caching_sha2_password"
   247  		if cr.Spec.ProxySQLEnabled() {
   248  			plugin = "mysql_native_password"
   249  		}
   250  		appc.Env = append(appc.Env, corev1.EnvVar{
   251  			Name:  "DEFAULT_AUTHENTICATION_PLUGIN",
   252  			Value: plugin,
   253  		})
   254  	}
   255  
   256  	if cr.CompareVersionWith("1.14.0") >= 0 {
   257  		appc.VolumeMounts = append(appc.VolumeMounts, corev1.VolumeMount{
   258  			Name:      "mysql-init-file",
   259  			MountPath: "/etc/mysql/init-file",
   260  		})
   261  
   262  		appc.ReadinessProbe = app.Probe(&cr.Spec.PXC.ReadinessProbes, "/var/lib/mysql/readiness-check.sh")
   263  		appc.LivenessProbe = app.Probe(&cr.Spec.PXC.LivenessProbes, "/var/lib/mysql/liveness-check.sh")
   264  	}
   265  
   266  	if cr.Spec.PXC != nil && (cr.Spec.PXC.Lifecycle.PostStart != nil || cr.Spec.PXC.Lifecycle.PreStop != nil) {
   267  		appc.Lifecycle = &cr.Spec.PXC.Lifecycle
   268  	}
   269  
   270  	return appc, nil
   271  }
   272  
   273  func (c *Node) SidecarContainers(spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster) ([]corev1.Container, error) {
   274  	return nil, nil
   275  }
   276  
   277  func (c *Node) LogCollectorContainer(spec *api.LogCollectorSpec, logPsecrets string, logRsecrets string, cr *api.PerconaXtraDBCluster) ([]corev1.Container, error) {
   278  	logProcEnvs := []corev1.EnvVar{
   279  		{
   280  			Name:  "LOG_DATA_DIR",
   281  			Value: "/var/lib/mysql",
   282  		},
   283  		{
   284  			Name: "POD_NAMESPASE",
   285  			ValueFrom: &corev1.EnvVarSource{
   286  				FieldRef: &corev1.ObjectFieldSelector{
   287  					FieldPath: "metadata.namespace",
   288  				},
   289  			},
   290  		},
   291  		{
   292  			Name: "POD_NAME",
   293  			ValueFrom: &corev1.EnvVarSource{
   294  				FieldRef: &corev1.ObjectFieldSelector{
   295  					FieldPath: "metadata.name",
   296  				},
   297  			},
   298  		},
   299  	}
   300  
   301  	logRotEnvs := []corev1.EnvVar{
   302  		{
   303  			Name:  "SERVICE_TYPE",
   304  			Value: "mysql",
   305  		},
   306  		{
   307  			Name: "MONITOR_PASSWORD",
   308  			ValueFrom: &corev1.EnvVarSource{
   309  				SecretKeyRef: app.SecretKeySelector(logRsecrets, users.Monitor),
   310  			},
   311  		},
   312  	}
   313  
   314  	fvar := true
   315  	logProcContainer := corev1.Container{
   316  		Name:            "logs",
   317  		Image:           spec.Image,
   318  		ImagePullPolicy: spec.ImagePullPolicy,
   319  		Env:             logProcEnvs,
   320  		SecurityContext: spec.ContainerSecurityContext,
   321  		Resources:       spec.Resources,
   322  		EnvFrom: []corev1.EnvFromSource{
   323  			{
   324  				SecretRef: &corev1.SecretEnvSource{
   325  					LocalObjectReference: corev1.LocalObjectReference{
   326  						Name: logPsecrets,
   327  					},
   328  					Optional: &fvar,
   329  				},
   330  			},
   331  		},
   332  		VolumeMounts: []corev1.VolumeMount{
   333  			{
   334  				Name:      app.DataVolumeName,
   335  				MountPath: "/var/lib/mysql",
   336  			},
   337  		},
   338  	}
   339  
   340  	logRotContainer := corev1.Container{
   341  		Name:            "logrotate",
   342  		Image:           spec.Image,
   343  		ImagePullPolicy: spec.ImagePullPolicy,
   344  		Env:             logRotEnvs,
   345  		SecurityContext: spec.ContainerSecurityContext,
   346  		Resources:       spec.Resources,
   347  		Args: []string{
   348  			"logrotate",
   349  		},
   350  		VolumeMounts: []corev1.VolumeMount{
   351  			{
   352  				Name:      app.DataVolumeName,
   353  				MountPath: "/var/lib/mysql",
   354  			},
   355  		},
   356  	}
   357  
   358  	if cr.Spec.LogCollector != nil {
   359  		if cr.Spec.LogCollector.Configuration != "" {
   360  			logProcContainer.VolumeMounts = append(logProcContainer.VolumeMounts, corev1.VolumeMount{
   361  				Name:      "logcollector-config",
   362  				MountPath: "/etc/fluentbit/custom",
   363  			})
   364  		}
   365  
   366  		if cr.Spec.LogCollector.HookScript != "" {
   367  			logProcContainer.VolumeMounts = append(logProcContainer.VolumeMounts, corev1.VolumeMount{
   368  				Name:      "hookscript",
   369  				MountPath: "/opt/percona/hookscript",
   370  			})
   371  		}
   372  	}
   373  
   374  	return []corev1.Container{logProcContainer, logRotContainer}, nil
   375  }
   376  
   377  func (c *Node) PMMContainer(ctx context.Context, cl client.Client, spec *api.PMMSpec, secret *corev1.Secret, cr *api.PerconaXtraDBCluster) (*corev1.Container, error) {
   378  	envVarsSecret := &corev1.Secret{}
   379  	err := cl.Get(ctx, types.NamespacedName{Name: cr.Spec.PXC.EnvVarsSecretName, Namespace: cr.Namespace}, envVarsSecret)
   380  	if client.IgnoreNotFound(err) != nil {
   381  		return nil, errors.Wrap(err, "get env vars secret")
   382  	}
   383  
   384  	ct := app.PMMClient(cr, spec, secret, envVarsSecret)
   385  
   386  	pmmEnvs := []corev1.EnvVar{
   387  		{
   388  			Name:  "DB_TYPE",
   389  			Value: "mysql",
   390  		},
   391  		{
   392  			Name:  "DB_USER",
   393  			Value: users.Monitor,
   394  		},
   395  		{
   396  			Name: "DB_PASSWORD",
   397  			ValueFrom: &corev1.EnvVarSource{
   398  				SecretKeyRef: app.SecretKeySelector(secret.Name, users.Monitor),
   399  			},
   400  		},
   401  		{
   402  			Name:  "DB_ARGS",
   403  			Value: "--query-source=perfschema",
   404  		},
   405  	}
   406  
   407  	ct.Env = append(ct.Env, pmmEnvs...)
   408  
   409  	if cr.CompareVersionWith("1.2.0") >= 0 {
   410  		clusterEnvs := []corev1.EnvVar{
   411  			{
   412  				Name:  "DB_CLUSTER",
   413  				Value: app.Name,
   414  			},
   415  			{
   416  				Name:  "DB_HOST",
   417  				Value: "localhost",
   418  			},
   419  			{
   420  				Name:  "DB_PORT",
   421  				Value: "3306",
   422  			},
   423  		}
   424  		ct.Env = append(ct.Env, clusterEnvs...)
   425  		ct.Resources = spec.Resources
   426  	}
   427  	if cr.CompareVersionWith("1.7.0") >= 0 {
   428  		for k, v := range ct.Env {
   429  			if v.Name == "DB_PORT" {
   430  				ct.Env[k].Value = "33062"
   431  				break
   432  			}
   433  		}
   434  		PmmPxcParams := ""
   435  		if spec.PxcParams != "" {
   436  			PmmPxcParams = spec.PxcParams
   437  		}
   438  		clusterPmmEnvs := []corev1.EnvVar{
   439  			{
   440  				Name:  "CLUSTER_NAME",
   441  				Value: cr.Name,
   442  			},
   443  			{
   444  				Name:  "PMM_ADMIN_CUSTOM_PARAMS",
   445  				Value: PmmPxcParams,
   446  			},
   447  		}
   448  		ct.Env = append(ct.Env, clusterPmmEnvs...)
   449  		pmmAgentScriptEnv := app.PMMAgentScript(cr, "mysql")
   450  		ct.Env = append(ct.Env, pmmAgentScriptEnv...)
   451  	}
   452  	if cr.CompareVersionWith("1.9.0") >= 0 {
   453  		fvar := true
   454  		ct.EnvFrom = []corev1.EnvFromSource{
   455  			{
   456  				SecretRef: &corev1.SecretEnvSource{
   457  					LocalObjectReference: corev1.LocalObjectReference{
   458  						Name: cr.Spec.PXC.EnvVarsSecretName,
   459  					},
   460  					Optional: &fvar,
   461  				},
   462  			},
   463  		}
   464  
   465  	}
   466  	if cr.CompareVersionWith("1.10.0") >= 0 {
   467  		// PMM team added these flags which allows us to avoid
   468  		// container crash, but just restart pmm-agent till it recovers
   469  		// the connection.
   470  		sidecarEnvs := []corev1.EnvVar{
   471  			{
   472  				Name:  "PMM_AGENT_SIDECAR",
   473  				Value: "true",
   474  			},
   475  			{
   476  				Name:  "PMM_AGENT_SIDECAR_SLEEP",
   477  				Value: "5",
   478  			},
   479  		}
   480  		ct.Env = append(ct.Env, sidecarEnvs...)
   481  	}
   482  
   483  	if cr.CompareVersionWith("1.14.0") >= 0 {
   484  		// PMM team moved temp directory to /usr/local/percona/pmm2/tmp
   485  		// but it doesn't work on OpenShift so we set it back to /tmp
   486  		sidecarEnvs := []corev1.EnvVar{
   487  			{
   488  				Name:  "PMM_AGENT_PATHS_TEMPDIR",
   489  				Value: "/tmp",
   490  			},
   491  		}
   492  		ct.Env = append(ct.Env, sidecarEnvs...)
   493  	}
   494  
   495  	ct.VolumeMounts = []corev1.VolumeMount{
   496  		{
   497  			Name:      app.DataVolumeName,
   498  			MountPath: "/var/lib/mysql",
   499  		},
   500  	}
   501  
   502  	return &ct, nil
   503  }
   504  
   505  func (c *Node) Volumes(podSpec *api.PodSpec, cr *api.PerconaXtraDBCluster, vg api.CustomVolumeGetter) (*api.Volume, error) {
   506  	vol := app.Volumes(podSpec, app.DataVolumeName)
   507  
   508  	configVolume, err := vg(cr.Namespace, "config", config.CustomConfigMapName(cr.Name, "pxc"), true)
   509  	if err != nil {
   510  		return nil, err
   511  	}
   512  
   513  	vol.Volumes = append(
   514  		vol.Volumes,
   515  		app.GetTmpVolume("tmp"),
   516  		configVolume,
   517  		app.GetSecretVolumes("ssl-internal", podSpec.SSLInternalSecretName, true),
   518  		app.GetSecretVolumes("ssl", podSpec.SSLSecretName, cr.Spec.AllowUnsafeConfig),
   519  		app.GetConfigVolumes("auto-config", config.AutoTuneConfigMapName(cr.Name, app.Name)),
   520  		app.GetSecretVolumes(VaultSecretVolumeName, podSpec.VaultSecretName, true),
   521  		app.GetSecretVolumes("mysql-users-secret-file", "internal-"+cr.Name, false),
   522  	)
   523  
   524  	if cr.Spec.LogCollector != nil && cr.Spec.LogCollector.Configuration != "" {
   525  		vol.Volumes = append(vol.Volumes,
   526  			app.GetConfigVolumes("logcollector-config", config.CustomConfigMapName(cr.Name, "logcollector")))
   527  	}
   528  
   529  	if cr.CompareVersionWith("1.11.0") >= 0 {
   530  		if cr.Spec.PXC != nil && cr.Spec.PXC.HookScript != "" {
   531  			vol.Volumes = append(vol.Volumes,
   532  				app.GetConfigVolumes("hookscript", config.HookScriptConfigMapName(cr.Name, "pxc")))
   533  		}
   534  
   535  		if cr.Spec.LogCollector != nil && cr.Spec.LogCollector.HookScript != "" {
   536  			vol.Volumes = append(vol.Volumes,
   537  				app.GetConfigVolumes("hookscript", config.HookScriptConfigMapName(cr.Name, "logcollector")))
   538  		}
   539  	}
   540  
   541  	if cr.CompareVersionWith("1.14.0") >= 0 {
   542  		vol.Volumes = append(vol.Volumes, app.GetSecretVolumes("mysql-init-file", cr.Name+"-mysql-init", true))
   543  	}
   544  
   545  	return vol, nil
   546  }
   547  
   548  func (c *Node) StatefulSet() *appsv1.StatefulSet {
   549  	return c.sfs
   550  }
   551  
   552  func (c *Node) Labels() map[string]string {
   553  	return c.labels
   554  }
   555  
   556  func (c *Node) Service() string {
   557  	return c.service
   558  }
   559  
   560  func (c *Node) UpdateStrategy(cr *api.PerconaXtraDBCluster) appsv1.StatefulSetUpdateStrategy {
   561  	switch cr.Spec.UpdateStrategy {
   562  	case appsv1.OnDeleteStatefulSetStrategyType:
   563  		return appsv1.StatefulSetUpdateStrategy{Type: appsv1.OnDeleteStatefulSetStrategyType}
   564  	case api.SmartUpdateStatefulSetStrategyType:
   565  		return appsv1.StatefulSetUpdateStrategy{Type: appsv1.OnDeleteStatefulSetStrategyType}
   566  	default:
   567  		var zero int32 = 0
   568  		return appsv1.StatefulSetUpdateStrategy{
   569  			Type: appsv1.RollingUpdateStatefulSetStrategyType,
   570  			RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
   571  				Partition: &zero,
   572  			},
   573  		}
   574  	}
   575  }