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 }