// MaxSurge returns the maximum surge pods a rolling deployment config can take. func MaxSurge(config deployapi.DeploymentConfig) int32 { if !IsRollingConfig(&config) { return int32(0) } // Error caught by validation maxSurge, _, _ := kdeplutil.ResolveFenceposts(&config.Spec.Strategy.RollingParams.MaxSurge, &config.Spec.Strategy.RollingParams.MaxUnavailable, config.Spec.Replicas) return maxSurge }
// Update all pods for a ReplicationController (oldRc) by creating a new // controller (newRc) with 0 replicas, and synchronously scaling oldRc and // newRc until oldRc has 0 replicas and newRc has the original # of desired // replicas. Cleanup occurs based on a RollingUpdaterCleanupPolicy. // // Each interval, the updater will attempt to make progress however it can // without violating any availability constraints defined by the config. This // means the amount scaled up or down each interval will vary based on the // timeliness of readiness and the updater will always try to make progress, // even slowly. // // If an update from newRc to oldRc is already in progress, we attempt to // drive it to completion. If an error occurs at any step of the update, the // error will be returned. // // A scaling event (either up or down) is considered progress; if no progress // is made within the config.Timeout, an error is returned. // // TODO: make this handle performing a rollback of a partially completed // rollout. func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error { out := config.Out oldRc := config.OldRc scaleRetryParams := NewRetryParams(config.Interval, config.Timeout) // Find an existing controller (for continuing an interrupted update) or // create a new one if necessary. sourceId := fmt.Sprintf("%s:%s", oldRc.Name, oldRc.UID) newRc, existed, err := r.getOrCreateTargetController(config.NewRc, sourceId) if err != nil { return err } if existed { fmt.Fprintf(out, "Continuing update with existing controller %s.\n", newRc.Name) } else { fmt.Fprintf(out, "Created %s\n", newRc.Name) } // Extract the desired replica count from the controller. desiredAnnotation, err := strconv.Atoi(newRc.Annotations[desiredReplicasAnnotation]) if err != nil { return fmt.Errorf("Unable to parse annotation for %s: %s=%s", newRc.Name, desiredReplicasAnnotation, newRc.Annotations[desiredReplicasAnnotation]) } desired := int32(desiredAnnotation) // Extract the original replica count from the old controller, adding the // annotation if it doesn't yet exist. _, hasOriginalAnnotation := oldRc.Annotations[originalReplicasAnnotation] if !hasOriginalAnnotation { existing, err := r.rcClient.ReplicationControllers(oldRc.Namespace).Get(oldRc.Name) if err != nil { return err } originReplicas := strconv.Itoa(int(existing.Spec.Replicas)) applyUpdate := func(rc *api.ReplicationController) { if rc.Annotations == nil { rc.Annotations = map[string]string{} } rc.Annotations[originalReplicasAnnotation] = originReplicas } if oldRc, err = updateRcWithRetries(r.rcClient, existing.Namespace, existing, applyUpdate); err != nil { return err } } // maxSurge is the maximum scaling increment and maxUnavailable are the maximum pods // that can be unavailable during a rollout. maxSurge, maxUnavailable, err := deploymentutil.ResolveFenceposts(&config.MaxSurge, &config.MaxUnavailable, desired) if err != nil { return err } // Validate maximums. if desired > 0 && maxUnavailable == 0 && maxSurge == 0 { return fmt.Errorf("one of maxSurge or maxUnavailable must be specified") } // The minimum pods which must remain available throughout the update // calculated for internal convenience. minAvailable := int32(integer.IntMax(0, int(desired-maxUnavailable))) // If the desired new scale is 0, then the max unavailable is necessarily // the effective scale of the old RC regardless of the configuration // (equivalent to 100% maxUnavailable). if desired == 0 { maxUnavailable = oldRc.Spec.Replicas minAvailable = 0 } fmt.Fprintf(out, "Scaling up %s from %d to %d, scaling down %s from %d to 0 (keep %d pods available, don't exceed %d pods)\n", newRc.Name, newRc.Spec.Replicas, desired, oldRc.Name, oldRc.Spec.Replicas, minAvailable, desired+maxSurge) // give a caller incremental notification and allow them to exit early goal := desired - newRc.Spec.Replicas if goal < 0 { goal = -goal } progress := func(complete bool) error { if config.OnProgress == nil { return nil } progress := desired - newRc.Spec.Replicas if progress < 0 { progress = -progress } percentage := 100 if !complete && goal > 0 { percentage = int((goal - progress) * 100 / goal) } return config.OnProgress(oldRc, newRc, percentage) } // Scale newRc and oldRc until newRc has the desired number of replicas and // oldRc has 0 replicas. progressDeadline := time.Now().UnixNano() + config.Timeout.Nanoseconds() for newRc.Spec.Replicas != desired || oldRc.Spec.Replicas != 0 { // Store the existing replica counts for progress timeout tracking. newReplicas := newRc.Spec.Replicas oldReplicas := oldRc.Spec.Replicas // Scale up as much as possible. scaledRc, err := r.scaleUp(newRc, oldRc, desired, maxSurge, maxUnavailable, scaleRetryParams, config) if err != nil { return err } newRc = scaledRc // notify the caller if necessary if err := progress(false); err != nil { return err } // Wait between scaling operations for things to settle. time.Sleep(config.UpdatePeriod) // Scale down as much as possible. scaledRc, err = r.scaleDown(newRc, oldRc, desired, minAvailable, maxUnavailable, maxSurge, config) if err != nil { return err } oldRc = scaledRc // notify the caller if necessary if err := progress(false); err != nil { return err } // If we are making progress, continue to advance the progress deadline. // Otherwise, time out with an error. progressMade := (newRc.Spec.Replicas != newReplicas) || (oldRc.Spec.Replicas != oldReplicas) if progressMade { progressDeadline = time.Now().UnixNano() + config.Timeout.Nanoseconds() } else if time.Now().UnixNano() > progressDeadline { return fmt.Errorf("timed out waiting for any update progress to be made") } } // notify the caller if necessary if err := progress(true); err != nil { return err } // Housekeeping and cleanup policy execution. return r.cleanup(oldRc, newRc, config) }