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 }