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 }