Skip to content

Commit ac7fd32

Browse files
committed
K8SPG-882 determine patroni version without using the special patroni version check pod
1 parent d8248fe commit ac7fd32

File tree

4 files changed

+575
-458
lines changed

4 files changed

+575
-458
lines changed

percona/controller/pgcluster/controller.go

Lines changed: 1 addition & 233 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package pgcluster
22

33
import (
4-
"bytes"
54
"context"
65
"crypto/md5"
76
"fmt"
@@ -11,21 +10,17 @@ import (
1110
"strings"
1211
"time"
1312

14-
gover "github.com/hashicorp/go-version"
1513
"github.com/pkg/errors"
1614
"go.opentelemetry.io/otel/trace"
1715
batchv1 "k8s.io/api/batch/v1"
1816
corev1 "k8s.io/api/core/v1"
1917
k8serrors "k8s.io/apimachinery/pkg/api/errors"
20-
"k8s.io/apimachinery/pkg/api/resource"
2118
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2219
"k8s.io/apimachinery/pkg/labels"
2320
"k8s.io/apimachinery/pkg/types"
24-
"k8s.io/apimachinery/pkg/util/wait"
2521
"k8s.io/client-go/tools/record"
2622
"k8s.io/client-go/util/retry"
2723
"k8s.io/client-go/util/workqueue"
28-
"k8s.io/utils/ptr"
2924
ctrl "sigs.k8s.io/controller-runtime"
3025
"sigs.k8s.io/controller-runtime/pkg/builder"
3126
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -39,11 +34,9 @@ import (
3934
"sigs.k8s.io/controller-runtime/pkg/source"
4035

4136
"github.com/percona/percona-postgresql-operator/v2/internal/controller/runtime"
42-
"github.com/percona/percona-postgresql-operator/v2/internal/initialize"
4337
"github.com/percona/percona-postgresql-operator/v2/internal/logging"
4438
"github.com/percona/percona-postgresql-operator/v2/internal/naming"
4539
"github.com/percona/percona-postgresql-operator/v2/internal/postgres"
46-
"github.com/percona/percona-postgresql-operator/v2/percona/clientcmd"
4740
perconaController "github.com/percona/percona-postgresql-operator/v2/percona/controller"
4841
"github.com/percona/percona-postgresql-operator/v2/percona/extensions"
4942
"github.com/percona/percona-postgresql-operator/v2/percona/k8s"
@@ -272,7 +265,7 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
272265
return reconcile.Result{}, errors.Wrap(err, "ensure finalizers")
273266
}
274267

275-
if err := r.reconcilePatroniVersionCheck(ctx, cr); err != nil {
268+
if err := r.reconcilePatroniVersion(ctx, cr); err != nil {
276269
if errors.Is(err, errPatroniVersionCheckWait) {
277270
return reconcile.Result{
278271
RequeueAfter: 5 * time.Second,
@@ -366,231 +359,6 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
366359
return ctrl.Result{}, nil
367360
}
368361

369-
var errPatroniVersionCheckWait = errors.New("waiting for pod to initialize")
370-
371-
func (r *PGClusterReconciler) reconcilePatroniVersionCheck(ctx context.Context, cr *v2.PerconaPGCluster) error {
372-
if cr.Annotations == nil {
373-
cr.Annotations = make(map[string]string)
374-
}
375-
376-
if patroniVersion, ok := cr.Annotations[pNaming.AnnotationCustomPatroniVersion]; ok {
377-
patroniVersionUpdateFunc := func() error {
378-
cluster := &v2.PerconaPGCluster{}
379-
if err := r.Client.Get(ctx, types.NamespacedName{
380-
Name: cr.Name,
381-
Namespace: cr.Namespace,
382-
}, cluster); err != nil {
383-
return errors.Wrap(err, "get PerconaPGCluster")
384-
}
385-
386-
orig := cluster.DeepCopy()
387-
388-
cluster.Status.Patroni.Version = patroniVersion
389-
cluster.Status.PatroniVersion = patroniVersion
390-
391-
if err := r.Client.Status().Patch(ctx, cluster.DeepCopy(), client.MergeFrom(orig)); err != nil {
392-
return errors.Wrap(err, "failed to patch patroni version")
393-
}
394-
395-
err := r.patchPatroniVersionAnnotation(ctx, cr, patroniVersion)
396-
if err != nil {
397-
return errors.Wrap(err, "failed to patch patroni version annotation")
398-
}
399-
400-
return nil
401-
}
402-
403-
// To ensure that the update was done given that conflicts can be caused by
404-
// other code making unrelated updates to the same resource at the same time.
405-
if err := retry.RetryOnConflict(retry.DefaultRetry, patroniVersionUpdateFunc); err != nil {
406-
return errors.Wrap(err, "failed to patch patroni version")
407-
}
408-
return nil
409-
}
410-
411-
imageIDs, err := r.instanceImageIDs(ctx, cr)
412-
if err != nil {
413-
return errors.Wrap(err, "get image IDs")
414-
}
415-
416-
// If the imageIDs slice contains the imageID from the status, we skip checking the Patroni version.
417-
// This ensures that the Patroni version is only checked after all pods have been updated.
418-
if cr.CompareVersion("2.8.0") >= 0 {
419-
if (len(imageIDs) == 0 || slices.Contains(imageIDs, cr.Status.Postgres.ImageID)) && cr.Status.Patroni.Version != "" {
420-
err = r.patchPatroniVersionAnnotation(ctx, cr, cr.Status.Patroni.Version)
421-
if err != nil {
422-
return errors.Wrap(err, "failed to patch patroni version annotation")
423-
}
424-
return nil
425-
}
426-
} else {
427-
if (len(imageIDs) == 0 || slices.Contains(imageIDs, cr.Status.Postgres.ImageID)) && cr.Status.PatroniVersion != "" {
428-
err = r.patchPatroniVersionAnnotation(ctx, cr, cr.Status.PatroniVersion)
429-
if err != nil {
430-
return errors.Wrap(err, "failed to patch patroni version annotation")
431-
}
432-
return nil
433-
}
434-
}
435-
436-
meta := metav1.ObjectMeta{
437-
Name: cr.Name + "-patroni-version-check",
438-
Namespace: cr.Namespace,
439-
}
440-
441-
p := &corev1.Pod{
442-
ObjectMeta: meta,
443-
}
444-
445-
err = r.Client.Get(ctx, client.ObjectKeyFromObject(p), p)
446-
if client.IgnoreNotFound(err) != nil {
447-
return errors.Wrap(err, "failed to get patroni version check pod")
448-
}
449-
if k8serrors.IsNotFound(err) {
450-
if len(cr.Spec.InstanceSets) == 0 {
451-
return errors.New(".spec.instances is a required value") // shouldn't happen as the value is required in the crd.yaml
452-
}
453-
454-
// Using minimal resources since the patroni version check pod is performing a very simple
455-
// operation i.e. "patronictl version"
456-
resources := corev1.ResourceRequirements{
457-
Limits: corev1.ResourceList{
458-
corev1.ResourceCPU: resource.MustParse("100m"),
459-
corev1.ResourceMemory: resource.MustParse("64Mi"),
460-
},
461-
Requests: corev1.ResourceList{
462-
corev1.ResourceCPU: resource.MustParse("50m"),
463-
corev1.ResourceMemory: resource.MustParse("32Mi"),
464-
},
465-
}
466-
467-
p = &corev1.Pod{
468-
ObjectMeta: meta,
469-
Spec: corev1.PodSpec{
470-
Containers: []corev1.Container{
471-
{
472-
Name: pNaming.ContainerPatroniVersionCheck,
473-
Image: cr.PostgresImage(),
474-
Command: []string{
475-
"bash",
476-
},
477-
Args: []string{
478-
"-c", "sleep 60",
479-
},
480-
Resources: resources,
481-
SecurityContext: initialize.RestrictedSecurityContext(cr.CompareVersion("2.8.0") >= 0),
482-
},
483-
},
484-
SecurityContext: cr.Spec.InstanceSets[0].SecurityContext,
485-
Affinity: cr.Spec.InstanceSets[0].Affinity,
486-
TerminationGracePeriodSeconds: ptr.To(int64(5)),
487-
ImagePullSecrets: cr.Spec.ImagePullSecrets,
488-
Resources: &resources,
489-
},
490-
}
491-
492-
if err := controllerutil.SetControllerReference(cr, p, r.Client.Scheme()); err != nil {
493-
return errors.Wrap(err, "set controller reference")
494-
}
495-
if err := r.Client.Create(ctx, p); client.IgnoreAlreadyExists(err) != nil {
496-
return errors.Wrap(err, "failed to create pod to check patroni version")
497-
}
498-
499-
return errPatroniVersionCheckWait
500-
}
501-
502-
if p.Status.Phase != corev1.PodRunning {
503-
return errPatroniVersionCheckWait
504-
}
505-
506-
var stdout, stderr bytes.Buffer
507-
execCli, err := clientcmd.NewClient()
508-
if err != nil {
509-
return errors.Wrap(err, "failed to create exec client")
510-
}
511-
b := wait.Backoff{
512-
Duration: 5 * time.Second,
513-
Factor: 1.0,
514-
Steps: 12,
515-
Cap: time.Minute,
516-
}
517-
if err := retry.OnError(b, func(err error) bool { return err != nil && strings.Contains(err.Error(), "container not found") }, func() error {
518-
return execCli.Exec(ctx, p, pNaming.ContainerPatroniVersionCheck, nil, &stdout, &stderr, "patronictl", "version")
519-
}); err != nil {
520-
return errors.Wrap(err, "exec")
521-
}
522-
523-
patroniVersion := strings.TrimSpace(strings.TrimPrefix(stdout.String(), "patronictl version "))
524-
525-
if _, err := gover.NewVersion(patroniVersion); err != nil {
526-
return errors.Wrap(err, "failed to validate patroni version")
527-
}
528-
529-
orig := cr.DeepCopy()
530-
531-
cr.Status.Patroni.Version = patroniVersion
532-
cr.Status.PatroniVersion = patroniVersion
533-
cr.Status.Postgres.Version = cr.Spec.PostgresVersion
534-
cr.Status.Postgres.ImageID = getImageIDFromPod(p, pNaming.ContainerPatroniVersionCheck)
535-
536-
if err := r.Client.Status().Patch(ctx, cr.DeepCopy(), client.MergeFrom(orig)); err != nil {
537-
return errors.Wrap(err, "failed to patch patroni version")
538-
}
539-
540-
err = r.patchPatroniVersionAnnotation(ctx, cr, patroniVersion)
541-
if err != nil {
542-
return errors.Wrap(err, "failed to patch patroni version annotation")
543-
}
544-
545-
if err := r.Client.Delete(ctx, p); err != nil {
546-
return errors.Wrap(err, "failed to delete patroni version check pod")
547-
}
548-
549-
return nil
550-
}
551-
552-
func (r *PGClusterReconciler) patchPatroniVersionAnnotation(ctx context.Context, cr *v2.PerconaPGCluster, patroniVersion string) error {
553-
orig := cr.DeepCopy()
554-
cr.Annotations[pNaming.AnnotationPatroniVersion] = patroniVersion
555-
if err := r.Client.Patch(ctx, cr.DeepCopy(), client.MergeFrom(orig)); err != nil {
556-
return errors.Wrap(err, "failed to patch the pg cluster")
557-
}
558-
return nil
559-
}
560-
561-
func (r *PGClusterReconciler) instanceImageIDs(ctx context.Context, cr *v2.PerconaPGCluster) ([]string, error) {
562-
pods := new(corev1.PodList)
563-
instances, err := naming.AsSelector(naming.ClusterInstances(cr.Name))
564-
if err != nil {
565-
return nil, errors.Wrap(err, "failed to create a selector for instance pods")
566-
}
567-
if err = r.Client.List(ctx, pods, client.InNamespace(cr.Namespace), client.MatchingLabelsSelector{Selector: instances}); err != nil {
568-
return nil, errors.Wrap(err, "failed to list instances")
569-
}
570-
571-
// Collecting all image IDs from instance pods. Under normal conditions, this slice will contain a single image ID, as all pods typically use the same image.
572-
// During an image update, it may contain multiple different image IDs as the update progresses.
573-
var imageIDs []string
574-
for _, pod := range pods.Items {
575-
imageID := getImageIDFromPod(&pod, naming.ContainerDatabase)
576-
if imageID != "" && !slices.Contains(imageIDs, imageID) {
577-
imageIDs = append(imageIDs, imageID)
578-
}
579-
}
580-
581-
return imageIDs, nil
582-
}
583-
584-
func getImageIDFromPod(pod *corev1.Pod, containerName string) string {
585-
idx := slices.IndexFunc(pod.Status.ContainerStatuses, func(s corev1.ContainerStatus) bool {
586-
return s.Name == containerName
587-
})
588-
if idx == -1 {
589-
return ""
590-
}
591-
return pod.Status.ContainerStatuses[idx].ImageID
592-
}
593-
594362
func (r *PGClusterReconciler) reconcileTLS(ctx context.Context, cr *v2.PerconaPGCluster) error {
595363
if err := r.validateTLS(ctx, cr); err != nil {
596364
return errors.Wrap(err, "validate TLS")

0 commit comments

Comments
 (0)