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

     1  package statefulset
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/pkg/errors"
     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/users"
    17  )
    18  
    19  const (
    20  	haproxyName           = "haproxy"
    21  	haproxyDataVolumeName = "haproxydata"
    22  )
    23  
    24  type HAProxy struct {
    25  	sfs     *appsv1.StatefulSet
    26  	labels  map[string]string
    27  	service string
    28  }
    29  
    30  func NewHAProxy(cr *api.PerconaXtraDBCluster) *HAProxy {
    31  	sfs := &appsv1.StatefulSet{
    32  		TypeMeta: metav1.TypeMeta{
    33  			APIVersion: "apps/v1",
    34  			Kind:       "StatefulSet",
    35  		},
    36  		ObjectMeta: metav1.ObjectMeta{
    37  			Name:      cr.Name + "-" + haproxyName,
    38  			Namespace: cr.Namespace,
    39  		},
    40  		Spec: appsv1.StatefulSetSpec{
    41  			PodManagementPolicy: "OrderedReady",
    42  		},
    43  	}
    44  
    45  	labels := map[string]string{
    46  		"app.kubernetes.io/name":       "percona-xtradb-cluster",
    47  		"app.kubernetes.io/instance":   cr.Name,
    48  		"app.kubernetes.io/component":  haproxyName,
    49  		"app.kubernetes.io/managed-by": "percona-xtradb-cluster-operator",
    50  		"app.kubernetes.io/part-of":    "percona-xtradb-cluster",
    51  	}
    52  
    53  	return &HAProxy{
    54  		sfs:     sfs,
    55  		labels:  labels,
    56  		service: cr.Name + "-" + haproxyName,
    57  	}
    58  }
    59  
    60  func (c *HAProxy) Name() string {
    61  	return haproxyName
    62  }
    63  
    64  func (c *HAProxy) AppContainer(spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster,
    65  	_ []corev1.Volume,
    66  ) (corev1.Container, error) {
    67  	appc := corev1.Container{
    68  		Name:            haproxyName,
    69  		Image:           spec.Image,
    70  		ImagePullPolicy: spec.ImagePullPolicy,
    71  		Ports: []corev1.ContainerPort{
    72  			{
    73  				ContainerPort: 3306,
    74  				Name:          "mysql",
    75  			},
    76  			{
    77  				ContainerPort: 3307,
    78  				Name:          "mysql-replicas",
    79  			},
    80  			{
    81  				ContainerPort: 3309,
    82  				Name:          "proxy-protocol",
    83  			},
    84  		},
    85  		VolumeMounts: []corev1.VolumeMount{
    86  			{
    87  				Name:      "haproxy-custom",
    88  				MountPath: "/etc/haproxy-custom/",
    89  			},
    90  			{
    91  				Name:      "haproxy-auto",
    92  				MountPath: "/etc/haproxy/pxc",
    93  			},
    94  		},
    95  		Env: []corev1.EnvVar{
    96  			{
    97  				Name:  "PXC_SERVICE",
    98  				Value: c.labels["app.kubernetes.io/instance"] + "-" + "pxc",
    99  			},
   100  		},
   101  		SecurityContext: spec.ContainerSecurityContext,
   102  		Resources:       spec.Resources,
   103  	}
   104  
   105  	if cr.CompareVersionWith("1.7.0") < 0 {
   106  		appc.Env = append(appc.Env, corev1.EnvVar{
   107  			Name: "MONITOR_PASSWORD",
   108  			ValueFrom: &corev1.EnvVarSource{
   109  				SecretKeyRef: app.SecretKeySelector(secrets, users.Monitor),
   110  			},
   111  		})
   112  	}
   113  
   114  	if cr.CompareVersionWith("1.6.0") >= 0 {
   115  		redinessDelay := int32(15)
   116  		if spec.ReadinessInitialDelaySeconds != nil {
   117  			redinessDelay = *spec.ReadinessInitialDelaySeconds
   118  		}
   119  		appc.ReadinessProbe = app.Probe(&corev1.Probe{
   120  			InitialDelaySeconds: redinessDelay,
   121  			TimeoutSeconds:      1,
   122  			PeriodSeconds:       5,
   123  			FailureThreshold:    3,
   124  		}, "/usr/local/bin/readiness-check.sh")
   125  
   126  		appc.Ports = append(
   127  			appc.Ports,
   128  			corev1.ContainerPort{
   129  				ContainerPort: 33062,
   130  				Name:          "mysql-admin",
   131  			},
   132  		)
   133  	}
   134  
   135  	if cr.CompareVersionWith("1.7.0") >= 0 {
   136  		appc.VolumeMounts = append(appc.VolumeMounts, corev1.VolumeMount{
   137  			Name:      "mysql-users-secret-file",
   138  			MountPath: "/etc/mysql/mysql-users-secret",
   139  		})
   140  
   141  		livenessDelay := int32(60)
   142  		if spec.LivenessInitialDelaySeconds != nil {
   143  			livenessDelay = *spec.LivenessInitialDelaySeconds
   144  		}
   145  		appc.LivenessProbe = app.Probe(&corev1.Probe{
   146  			InitialDelaySeconds: livenessDelay,
   147  			TimeoutSeconds:      5,
   148  			PeriodSeconds:       30,
   149  			FailureThreshold:    4,
   150  		}, "/usr/local/bin/readiness-check.sh")
   151  	}
   152  	if cr.CompareVersionWith("1.9.0") >= 0 {
   153  		fvar := true
   154  		appc.EnvFrom = []corev1.EnvFromSource{
   155  			{
   156  				SecretRef: &corev1.SecretEnvSource{
   157  					LocalObjectReference: corev1.LocalObjectReference{
   158  						Name: cr.Spec.HAProxy.EnvVarsSecretName,
   159  					},
   160  					Optional: &fvar,
   161  				},
   162  			},
   163  		}
   164  		appc.VolumeMounts = append(appc.VolumeMounts, corev1.VolumeMount{
   165  			Name:      cr.Spec.HAProxy.EnvVarsSecretName,
   166  			MountPath: "/etc/mysql/haproxy-env-secret",
   167  		})
   168  
   169  		appc.Ports = append(
   170  			appc.Ports,
   171  			corev1.ContainerPort{
   172  				ContainerPort: 33060,
   173  				Name:          "mysqlx",
   174  			},
   175  		)
   176  
   177  		appc.LivenessProbe = &cr.Spec.HAProxy.LivenessProbes
   178  		appc.ReadinessProbe = &cr.Spec.HAProxy.ReadinessProbes
   179  		appc.ReadinessProbe.Exec = &corev1.ExecAction{
   180  			Command: []string{"/usr/local/bin/readiness-check.sh"},
   181  		}
   182  		appc.LivenessProbe.Exec = &corev1.ExecAction{
   183  			Command: []string{"/usr/local/bin/liveness-check.sh"},
   184  		}
   185  		probsEnvs := []corev1.EnvVar{
   186  			{
   187  				Name:  "LIVENESS_CHECK_TIMEOUT",
   188  				Value: fmt.Sprint(cr.Spec.HAProxy.LivenessProbes.TimeoutSeconds),
   189  			},
   190  			{
   191  				Name:  "READINESS_CHECK_TIMEOUT",
   192  				Value: fmt.Sprint(cr.Spec.HAProxy.ReadinessProbes.TimeoutSeconds),
   193  			},
   194  		}
   195  		appc.Env = append(appc.Env, probsEnvs...)
   196  	}
   197  	if cr.CompareVersionWith("1.11.0") >= 0 && cr.Spec.HAProxy != nil && cr.Spec.HAProxy.HookScript != "" {
   198  		appc.VolumeMounts = append(appc.VolumeMounts, corev1.VolumeMount{
   199  			Name:      "hookscript",
   200  			MountPath: "/opt/percona/hookscript",
   201  		})
   202  	}
   203  	hasKey, err := cr.ConfigHasKey("mysqld", "proxy_protocol_networks")
   204  	if err != nil {
   205  		return appc, errors.Wrap(err, "check if congfig has proxy_protocol_networks key")
   206  	}
   207  	if hasKey {
   208  		appc.Env = append(appc.Env, corev1.EnvVar{
   209  			Name:  "IS_PROXY_PROTOCOL",
   210  			Value: "yes",
   211  		})
   212  	}
   213  
   214  	if cr.Spec.HAProxy != nil && (cr.Spec.HAProxy.Lifecycle.PostStart != nil || cr.Spec.HAProxy.Lifecycle.PreStop != nil) {
   215  		appc.Lifecycle = &cr.Spec.HAProxy.Lifecycle
   216  	}
   217  
   218  	return appc, nil
   219  }
   220  
   221  func (c *HAProxy) SidecarContainers(spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster) ([]corev1.Container, error) {
   222  	container := corev1.Container{
   223  		Name:            "pxc-monit",
   224  		Image:           spec.Image,
   225  		ImagePullPolicy: spec.ImagePullPolicy,
   226  		Args: []string{
   227  			"/usr/bin/peer-list",
   228  			"-on-change=/usr/bin/add_pxc_nodes.sh",
   229  			"-service=$(PXC_SERVICE)",
   230  		},
   231  		Env: []corev1.EnvVar{
   232  			{
   233  				Name:  "PXC_SERVICE",
   234  				Value: c.labels["app.kubernetes.io/instance"] + "-" + "pxc",
   235  			},
   236  		},
   237  		Resources: spec.SidecarResources,
   238  		VolumeMounts: []corev1.VolumeMount{
   239  			{
   240  				Name:      "haproxy-custom",
   241  				MountPath: "/etc/haproxy-custom/",
   242  			},
   243  			{
   244  				Name:      "haproxy-auto",
   245  				MountPath: "/etc/haproxy/pxc",
   246  			},
   247  		},
   248  		SecurityContext: spec.ContainerSecurityContext,
   249  	}
   250  
   251  	hasKey, err := cr.ConfigHasKey("mysqld", "proxy_protocol_networks")
   252  	if err != nil {
   253  		return nil, errors.Wrap(err, "check if congfig has proxy_protocol_networks key")
   254  	}
   255  	if hasKey {
   256  		container.Env = append(container.Env, corev1.EnvVar{
   257  			Name:  "IS_PROXY_PROTOCOL",
   258  			Value: "yes",
   259  		})
   260  	}
   261  	if cr.CompareVersionWith("1.7.0") < 0 {
   262  		container.Env = append(container.Env, corev1.EnvVar{
   263  			Name: "MONITOR_PASSWORD",
   264  			ValueFrom: &corev1.EnvVarSource{
   265  				SecretKeyRef: app.SecretKeySelector(secrets, users.Monitor),
   266  			},
   267  		})
   268  	}
   269  	if cr.CompareVersionWith("1.7.0") >= 0 {
   270  		container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
   271  			Name:      "mysql-users-secret-file",
   272  			MountPath: "/etc/mysql/mysql-users-secret",
   273  		})
   274  	}
   275  	if cr.CompareVersionWith("1.9.0") >= 0 {
   276  		fvar := true
   277  		container.EnvFrom = []corev1.EnvFromSource{
   278  			{
   279  				SecretRef: &corev1.SecretEnvSource{
   280  					LocalObjectReference: corev1.LocalObjectReference{
   281  						Name: cr.Spec.HAProxy.EnvVarsSecretName,
   282  					},
   283  					Optional: &fvar,
   284  				},
   285  			},
   286  		}
   287  		container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
   288  			Name:      cr.Spec.HAProxy.EnvVarsSecretName,
   289  			MountPath: "/etc/mysql/haproxy-env-secret",
   290  		})
   291  	}
   292  
   293  	return []corev1.Container{container}, nil
   294  }
   295  
   296  func (c *HAProxy) LogCollectorContainer(_ *api.LogCollectorSpec, _ string, _ string, _ *api.PerconaXtraDBCluster) ([]corev1.Container, error) {
   297  	return nil, nil
   298  }
   299  
   300  func (c *HAProxy) PMMContainer(ctx context.Context, cl client.Client, spec *api.PMMSpec, secret *corev1.Secret, cr *api.PerconaXtraDBCluster) (*corev1.Container, error) {
   301  	if cr.CompareVersionWith("1.9.0") < 0 {
   302  		return nil, nil
   303  	}
   304  
   305  	envVarsSecret := &corev1.Secret{}
   306  	err := cl.Get(ctx, types.NamespacedName{Name: cr.Spec.HAProxy.EnvVarsSecretName, Namespace: cr.Namespace}, envVarsSecret)
   307  	if client.IgnoreNotFound(err) != nil {
   308  		return nil, errors.Wrap(err, "get env vars secret")
   309  	}
   310  
   311  	ct := app.PMMClient(cr, spec, secret, envVarsSecret)
   312  
   313  	pmmEnvs := []corev1.EnvVar{
   314  		{
   315  			Name:  "DB_TYPE",
   316  			Value: "haproxy",
   317  		},
   318  		{
   319  			Name:  "MONITOR_USER",
   320  			Value: users.Monitor,
   321  		},
   322  		{
   323  			Name: "MONITOR_PASSWORD",
   324  			ValueFrom: &corev1.EnvVarSource{
   325  				SecretKeyRef: app.SecretKeySelector(secret.Name, users.Monitor),
   326  			},
   327  		},
   328  		{
   329  			Name:  "DB_USER",
   330  			Value: users.Monitor,
   331  		},
   332  		{
   333  			Name: "DB_PASSWORD",
   334  			ValueFrom: &corev1.EnvVarSource{
   335  				SecretKeyRef: app.SecretKeySelector(secret.Name, users.Monitor),
   336  			},
   337  		},
   338  		{
   339  			Name:  "DB_CLUSTER",
   340  			Value: app.Name,
   341  		},
   342  		{
   343  			Name:  "DB_HOST",
   344  			Value: "localhost",
   345  		},
   346  		{
   347  			Name:  "DB_PORT",
   348  			Value: "3306",
   349  		},
   350  		{
   351  			Name:  "CLUSTER_NAME",
   352  			Value: cr.Name,
   353  		},
   354  		{
   355  			Name:  "PMM_ADMIN_CUSTOM_PARAMS",
   356  			Value: "--listen-port=8404",
   357  		},
   358  	}
   359  	ct.Env = append(ct.Env, pmmEnvs...)
   360  
   361  	pmmAgentScriptEnv := app.PMMAgentScript(cr, "haproxy")
   362  	ct.Env = append(ct.Env, pmmAgentScriptEnv...)
   363  
   364  	if cr.CompareVersionWith("1.10.0") >= 0 {
   365  		// PMM team added these flags which allows us to avoid
   366  		// container crash, but just restart pmm-agent till it recovers
   367  		// the connection.
   368  		sidecarEnvs := []corev1.EnvVar{
   369  			{
   370  				Name:  "PMM_AGENT_SIDECAR",
   371  				Value: "true",
   372  			},
   373  			{
   374  				Name:  "PMM_AGENT_SIDECAR_SLEEP",
   375  				Value: "5",
   376  			},
   377  		}
   378  		ct.Env = append(ct.Env, sidecarEnvs...)
   379  	}
   380  
   381  	if cr.CompareVersionWith("1.14.0") >= 0 {
   382  		// PMM team moved temp directory to /usr/local/percona/pmm2/tmp
   383  		// but it doesn't work on OpenShift so we set it back to /tmp
   384  		sidecarEnvs := []corev1.EnvVar{
   385  			{
   386  				Name:  "PMM_AGENT_PATHS_TEMPDIR",
   387  				Value: "/tmp",
   388  			},
   389  		}
   390  		ct.Env = append(ct.Env, sidecarEnvs...)
   391  
   392  		fvar := true
   393  		ct.EnvFrom = []corev1.EnvFromSource{
   394  			{
   395  				SecretRef: &corev1.SecretEnvSource{
   396  					LocalObjectReference: corev1.LocalObjectReference{
   397  						Name: cr.Spec.HAProxy.EnvVarsSecretName,
   398  					},
   399  					Optional: &fvar,
   400  				},
   401  			},
   402  		}
   403  	}
   404  
   405  	ct.Resources = spec.Resources
   406  
   407  	return &ct, nil
   408  }
   409  
   410  func (c *HAProxy) Volumes(podSpec *api.PodSpec, cr *api.PerconaXtraDBCluster, vg api.CustomVolumeGetter) (*api.Volume, error) {
   411  	vol := app.Volumes(podSpec, haproxyDataVolumeName)
   412  	configVolume, err := vg(cr.Namespace, "haproxy-custom", c.labels["app.kubernetes.io/instance"]+"-haproxy", true)
   413  	if err != nil {
   414  		return nil, err
   415  	}
   416  	vol.Volumes = append(
   417  		vol.Volumes,
   418  		configVolume,
   419  		app.GetTmpVolume("haproxy-auto"),
   420  	)
   421  	if cr.CompareVersionWith("1.7.0") >= 0 {
   422  		vol.Volumes = append(vol.Volumes, app.GetSecretVolumes("mysql-users-secret-file", "internal-"+cr.Name, false))
   423  	}
   424  	if cr.CompareVersionWith("1.9.0") >= 0 {
   425  		vol.Volumes = append(vol.Volumes, app.GetSecretVolumes(cr.Spec.HAProxy.EnvVarsSecretName, cr.Spec.HAProxy.EnvVarsSecretName, true))
   426  	}
   427  	if cr.CompareVersionWith("1.11.0") >= 0 && cr.Spec.HAProxy != nil && cr.Spec.HAProxy.HookScript != "" {
   428  		vol.Volumes = append(vol.Volumes,
   429  			app.GetConfigVolumes("hookscript", c.labels["app.kubernetes.io/instance"]+"-"+c.labels["app.kubernetes.io/component"]+"-hookscript"))
   430  	}
   431  	if cr.CompareVersionWith("1.13.0") >= 0 {
   432  		vol.Volumes = append(vol.Volumes,
   433  			corev1.Volume{
   434  				Name: app.BinVolumeName,
   435  				VolumeSource: corev1.VolumeSource{
   436  					EmptyDir: &corev1.EmptyDirVolumeSource{},
   437  				},
   438  			},
   439  		)
   440  	}
   441  	return vol, nil
   442  }
   443  
   444  func (c *HAProxy) StatefulSet() *appsv1.StatefulSet {
   445  	return c.sfs
   446  }
   447  
   448  func (c *HAProxy) Labels() map[string]string {
   449  	return c.labels
   450  }
   451  
   452  func (c *HAProxy) Service() string {
   453  	return c.service
   454  }
   455  
   456  func (c *HAProxy) UpdateStrategy(cr *api.PerconaXtraDBCluster) appsv1.StatefulSetUpdateStrategy {
   457  	switch cr.Spec.UpdateStrategy {
   458  	case appsv1.OnDeleteStatefulSetStrategyType:
   459  		return appsv1.StatefulSetUpdateStrategy{Type: appsv1.OnDeleteStatefulSetStrategyType}
   460  	default:
   461  		var zero int32 = 0
   462  		return appsv1.StatefulSetUpdateStrategy{
   463  			Type: appsv1.RollingUpdateStatefulSetStrategyType,
   464  			RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
   465  				Partition: &zero,
   466  			},
   467  		}
   468  	}
   469  }