github.com/spotahome/redis-operator@v1.2.4/service/k8s/statefulset.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"k8s.io/apimachinery/pkg/labels"
    10  
    11  	"github.com/spotahome/redis-operator/operator/redisfailover/util"
    12  
    13  	appsv1 "k8s.io/api/apps/v1"
    14  	corev1 "k8s.io/api/core/v1"
    15  	"k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/client-go/kubernetes"
    18  
    19  	"github.com/spotahome/redis-operator/log"
    20  	"github.com/spotahome/redis-operator/metrics"
    21  )
    22  
    23  // StatefulSet the StatefulSet service that knows how to interact with k8s to manage them
    24  type StatefulSet interface {
    25  	GetStatefulSet(namespace, name string) (*appsv1.StatefulSet, error)
    26  	GetStatefulSetPods(namespace, name string) (*corev1.PodList, error)
    27  	CreateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error
    28  	UpdateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error
    29  	CreateOrUpdateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error
    30  	DeleteStatefulSet(namespace string, name string) error
    31  	ListStatefulSets(namespace string) (*appsv1.StatefulSetList, error)
    32  }
    33  
    34  // StatefulSetService is the service account service implementation using API calls to kubernetes.
    35  type StatefulSetService struct {
    36  	kubeClient      kubernetes.Interface
    37  	logger          log.Logger
    38  	metricsRecorder metrics.Recorder
    39  }
    40  
    41  // NewStatefulSetService returns a new StatefulSet KubeService.
    42  func NewStatefulSetService(kubeClient kubernetes.Interface, logger log.Logger, metricsRecorder metrics.Recorder) *StatefulSetService {
    43  	logger = logger.With("service", "k8s.statefulSet")
    44  	return &StatefulSetService{
    45  		kubeClient:      kubeClient,
    46  		logger:          logger,
    47  		metricsRecorder: metricsRecorder,
    48  	}
    49  }
    50  
    51  // GetStatefulSet will retrieve the requested statefulset based on namespace and name
    52  func (s *StatefulSetService) GetStatefulSet(namespace, name string) (*appsv1.StatefulSet, error) {
    53  	statefulSet, err := s.kubeClient.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    54  	recordMetrics(namespace, "StatefulSet", name, "GET", err, s.metricsRecorder)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	return statefulSet, err
    59  }
    60  
    61  // GetStatefulSetPods will give a list of pods that are managed by the statefulset
    62  func (s *StatefulSetService) GetStatefulSetPods(namespace, name string) (*corev1.PodList, error) {
    63  	statefulSet, err := s.GetStatefulSet(namespace, name)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	labels := []string{}
    68  	for k, v := range statefulSet.Spec.Selector.MatchLabels {
    69  		labels = append(labels, fmt.Sprintf("%s=%s", k, v))
    70  	}
    71  	selector := strings.Join(labels, ",")
    72  	return s.kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector})
    73  }
    74  
    75  // CreateStatefulSet will create the given statefulset
    76  func (s *StatefulSetService) CreateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error {
    77  	_, err := s.kubeClient.AppsV1().StatefulSets(namespace).Create(context.TODO(), statefulSet, metav1.CreateOptions{})
    78  	recordMetrics(namespace, "StatefulSet", statefulSet.GetName(), "CREATE", err, s.metricsRecorder)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	s.logger.WithField("namespace", namespace).WithField("statefulSet", statefulSet.ObjectMeta.Name).Debugf("statefulSet created")
    83  	return err
    84  }
    85  
    86  // UpdateStatefulSet will update the given statefulset
    87  func (s *StatefulSetService) UpdateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error {
    88  	_, err := s.kubeClient.AppsV1().StatefulSets(namespace).Update(context.TODO(), statefulSet, metav1.UpdateOptions{})
    89  	recordMetrics(namespace, "StatefulSet", statefulSet.GetName(), "UPDATE", err, s.metricsRecorder)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	s.logger.WithField("namespace", namespace).WithField("statefulSet", statefulSet.ObjectMeta.Name).Debugf("statefulSet updated")
    94  	return err
    95  }
    96  
    97  // CreateOrUpdateStatefulSet will update the statefulset or create it if does not exist
    98  func (s *StatefulSetService) CreateOrUpdateStatefulSet(namespace string, statefulSet *appsv1.StatefulSet) error {
    99  	storedStatefulSet, err := s.GetStatefulSet(namespace, statefulSet.Name)
   100  	if err != nil {
   101  		// If no resource we need to create.
   102  		if errors.IsNotFound(err) {
   103  			return s.CreateStatefulSet(namespace, statefulSet)
   104  		}
   105  		return err
   106  	}
   107  
   108  	// Already exists, need to Update.
   109  	// Set the correct resource version to ensure we are on the latest version. This way the only valid
   110  	// namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency),
   111  	// we will replace the current namespace state.
   112  	statefulSet.ResourceVersion = storedStatefulSet.ResourceVersion
   113  	// resize pvc
   114  	// 1.Get the data already stored internally
   115  	// 2.Get the desired data
   116  	// 3.Start querying the pvc list when you find data inconsistencies
   117  	// 3.1 Comparison using real pvc capacity and desired data
   118  	// 3.1.1 Update if you find inconsistencies
   119  	// 3.2 Writing successful updates to internal
   120  	// 4. Set to old VolumeClaimTemplates to update.Prevent update error reporting
   121  	// 5. Set to old annotations to update
   122  	annotations := storedStatefulSet.Annotations
   123  	if annotations == nil {
   124  		annotations = map[string]string{
   125  			"storageCapacity": "0",
   126  		}
   127  	}
   128  	storedCapacity, _ := strconv.ParseInt(annotations["storageCapacity"], 0, 64)
   129  	if len(statefulSet.Spec.VolumeClaimTemplates) != 0 {
   130  		stateCapacity := statefulSet.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests.Storage().Value()
   131  		if storedCapacity != stateCapacity {
   132  			rfName := strings.TrimPrefix(storedStatefulSet.Name, "rfr-")
   133  			listOpt := metav1.ListOptions{
   134  				LabelSelector: labels.FormatLabels(
   135  					map[string]string{
   136  						"app.kubernetes.io/component": "redis",
   137  						"app.kubernetes.io/name":      strings.TrimPrefix(storedStatefulSet.Name, "rfr-"),
   138  						"app.kubernetes.io/part-of":   "redis-failover",
   139  					},
   140  				),
   141  			}
   142  			pvcs, err := s.kubeClient.CoreV1().PersistentVolumeClaims(storedStatefulSet.Namespace).List(context.Background(), listOpt)
   143  			if err != nil {
   144  				return err
   145  			}
   146  			updateFailed := false
   147  			realUpdate := false
   148  			for _, pvc := range pvcs.Items {
   149  				realCapacity := pvc.Spec.Resources.Requests.Storage().Value()
   150  				if realCapacity != stateCapacity {
   151  					realUpdate = true
   152  					pvc.Spec.Resources.Requests = statefulSet.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests
   153  					_, err = s.kubeClient.CoreV1().PersistentVolumeClaims(storedStatefulSet.Namespace).Update(context.Background(), &pvc, metav1.UpdateOptions{})
   154  					if err != nil {
   155  						updateFailed = true
   156  						s.logger.WithField("namespace", namespace).WithField("pvc", pvc.Name).Warningf("resize pvc failed:%s", err.Error())
   157  					}
   158  				}
   159  			}
   160  			if !updateFailed && len(pvcs.Items) != 0 {
   161  				annotations["storageCapacity"] = fmt.Sprintf("%d", stateCapacity)
   162  				storedStatefulSet.Annotations = annotations
   163  				if realUpdate {
   164  					s.logger.WithField("namespace", namespace).WithField("statefulSet", statefulSet.Name).Infof("resize statefulset pvcs from %d to %d Success", storedCapacity, stateCapacity)
   165  				} else {
   166  					s.logger.WithField("namespace", namespace).WithField("pvc", rfName).Warningf("set annotations,resize nothing")
   167  				}
   168  			}
   169  		}
   170  	}
   171  	// set stored.volumeClaimTemplates
   172  	statefulSet.Spec.VolumeClaimTemplates = storedStatefulSet.Spec.VolumeClaimTemplates
   173  	statefulSet.Annotations = util.MergeAnnotations(storedStatefulSet.Annotations, statefulSet.Annotations)
   174  	return s.UpdateStatefulSet(namespace, statefulSet)
   175  }
   176  
   177  // DeleteStatefulSet will delete the statefulset
   178  func (s *StatefulSetService) DeleteStatefulSet(namespace, name string) error {
   179  	propagation := metav1.DeletePropagationForeground
   180  	err := s.kubeClient.AppsV1().StatefulSets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{PropagationPolicy: &propagation})
   181  	recordMetrics(namespace, "StatefulSet", name, "DELETE", err, s.metricsRecorder)
   182  	return err
   183  }
   184  
   185  // ListStatefulSets will retrieve a list of statefulset in the given namespace
   186  func (s *StatefulSetService) ListStatefulSets(namespace string) (*appsv1.StatefulSetList, error) {
   187  	stsList, err := s.kubeClient.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{})
   188  	recordMetrics(namespace, "StatefulSet", metrics.NOT_APPLICABLE, "LIST", err, s.metricsRecorder)
   189  	return stsList, err
   190  }