2017-02-16 05:18:16 -05:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package daemon
import (
2017-06-06 13:09:57 -04:00
"bytes"
2020-02-07 21:16:47 -05:00
"context"
2017-02-16 05:18:16 -05:00
"fmt"
2018-08-15 19:07:59 -04:00
"reflect"
2017-05-17 19:53:46 -04:00
"sort"
2017-02-16 05:18:16 -05:00
2020-04-17 15:25:06 -04:00
"k8s.io/klog/v2"
2017-06-06 14:31:04 -04:00
2018-02-14 13:35:38 -05:00
apps "k8s.io/api/apps/v1"
2021-01-27 00:20:56 -05:00
v1 "k8s.io/api/core/v1"
2017-05-17 19:53:46 -04:00
"k8s.io/apimachinery/pkg/api/errors"
2017-02-16 05:18:16 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
2017-05-17 19:53:46 -04:00
"k8s.io/apimachinery/pkg/runtime"
2021-10-28 08:24:26 -04:00
"k8s.io/apimachinery/pkg/types"
2017-05-18 18:46:20 -04:00
"k8s.io/apimachinery/pkg/util/json"
2017-04-17 13:56:40 -04:00
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
2017-05-17 19:53:46 -04:00
"k8s.io/kubernetes/pkg/controller"
2017-02-16 05:18:16 -05:00
"k8s.io/kubernetes/pkg/controller/daemon/util"
2017-05-17 19:53:46 -04:00
labelsutil "k8s.io/kubernetes/pkg/util/labels"
2017-02-16 05:18:16 -05:00
)
2021-01-27 01:09:53 -05:00
// rollingUpdate identifies the set of old pods to delete, or additional pods to create on nodes,
// remaining within the constraints imposed by the update strategy.
2021-04-22 14:14:52 -04:00
func ( dsc * DaemonSetsController ) rollingUpdate ( ctx context . Context , ds * apps . DaemonSet , nodeList [ ] * v1 . Node , hash string ) error {
2022-11-04 04:37:32 -04:00
logger := klog . FromContext ( ctx )
2023-06-27 11:56:33 -04:00
nodeToDaemonPods , err := dsc . getNodesToDaemonPods ( ctx , ds , false )
2017-03-07 18:01:11 -05:00
if err != nil {
return fmt . Errorf ( "couldn't get node to daemon pod mapping for daemon set %q: %v" , ds . Name , err )
}
2023-08-08 23:27:18 -04:00
maxSurge , maxUnavailable , desiredNumberScheduled , err := dsc . updatedDesiredNodeCounts ( ctx , ds , nodeList , nodeToDaemonPods )
2017-02-16 05:18:16 -05:00
if err != nil {
2019-07-18 13:22:52 -04:00
return fmt . Errorf ( "couldn't get unavailable numbers: %v" , err )
2017-02-16 05:18:16 -05:00
}
2021-01-27 00:20:56 -05:00
now := dsc . failedPodsBackoff . Clock . Now ( )
// When not surging, we delete just enough pods to stay under the maxUnavailable limit, if any
// are necessary, and let the core loop create new instances on those nodes.
2021-01-27 01:09:53 -05:00
//
// Assumptions:
// * Expect manage loop to allow no more than one pod per node
// * Expect manage loop will create new pods
// * Expect manage loop will handle failed pods
// * Deleted pods do not count as unavailable so that updates make progress when nodes are down
// Invariants:
// * The number of new pods that are unavailable must be less than maxUnavailable
// * A node with an available old pod is a candidate for deletion if it does not violate other invariants
//
2021-01-27 00:20:56 -05:00
if maxSurge == 0 {
2021-01-27 01:09:53 -05:00
var numUnavailable int
var allowedReplacementPods [ ] string
var candidatePodsToDelete [ ] string
for nodeName , pods := range nodeToDaemonPods {
newPod , oldPod , ok := findUpdatedPodsOnNode ( ds , pods , hash )
if ! ok {
// let the manage loop clean up this node, and treat it as an unavailable node
2022-11-04 04:37:32 -04:00
logger . V ( 3 ) . Info ( "DaemonSet has excess pods on node, skipping to allow the core loop to process" , "daemonset" , klog . KObj ( ds ) , "node" , klog . KRef ( "" , nodeName ) )
2021-01-27 01:09:53 -05:00
numUnavailable ++
2021-01-27 00:20:56 -05:00
continue
}
2021-01-27 01:09:53 -05:00
switch {
case oldPod == nil && newPod == nil , oldPod != nil && newPod != nil :
// the manage loop will handle creating or deleting the appropriate pod, consider this unavailable
numUnavailable ++
case newPod != nil :
// this pod is up to date, check its availability
if ! podutil . IsPodAvailable ( newPod , ds . Spec . MinReadySeconds , metav1 . Time { Time : now } ) {
// an unavailable new pod is counted against maxUnavailable
numUnavailable ++
}
default :
// this pod is old, it is an update candidate
switch {
case ! podutil . IsPodAvailable ( oldPod , ds . Spec . MinReadySeconds , metav1 . Time { Time : now } ) :
// the old pod isn't available, so it needs to be replaced
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet pod on node is out of date and not available, allowing replacement" , "daemonset" , klog . KObj ( ds ) , "pod" , klog . KObj ( oldPod ) , "node" , klog . KRef ( "" , nodeName ) )
2021-01-27 01:09:53 -05:00
// record the replacement
if allowedReplacementPods == nil {
allowedReplacementPods = make ( [ ] string , 0 , len ( nodeToDaemonPods ) )
}
allowedReplacementPods = append ( allowedReplacementPods , oldPod . Name )
2024-02-09 17:36:23 -05:00
numUnavailable ++
2021-01-27 01:09:53 -05:00
case numUnavailable >= maxUnavailable :
// no point considering any other candidates
continue
default :
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet pod on node is out of date, this is a candidate to replace" , "daemonset" , klog . KObj ( ds ) , "pod" , klog . KObj ( oldPod ) , "node" , klog . KRef ( "" , nodeName ) )
2021-01-27 01:09:53 -05:00
// record the candidate
if candidatePodsToDelete == nil {
candidatePodsToDelete = make ( [ ] string , 0 , maxUnavailable )
}
candidatePodsToDelete = append ( candidatePodsToDelete , oldPod . Name )
}
2021-01-27 00:20:56 -05:00
}
2021-01-27 01:09:53 -05:00
}
2021-01-27 00:20:56 -05:00
2024-07-24 03:00:25 -04:00
// use any of the candidates we can, including the allowedReplacementPods
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet allowing replacements" , "daemonset" , klog . KObj ( ds ) , "replacements" , len ( allowedReplacementPods ) , "maxUnavailable" , maxUnavailable , "numUnavailable" , numUnavailable , "candidates" , len ( candidatePodsToDelete ) )
2021-01-27 01:09:53 -05:00
remainingUnavailable := maxUnavailable - numUnavailable
if remainingUnavailable < 0 {
remainingUnavailable = 0
2021-01-27 00:20:56 -05:00
}
2021-01-27 01:09:53 -05:00
if max := len ( candidatePodsToDelete ) ; remainingUnavailable > max {
remainingUnavailable = max
}
oldPodsToDelete := append ( allowedReplacementPods , candidatePodsToDelete [ : remainingUnavailable ] ... )
2021-11-10 17:56:46 -05:00
return dsc . syncNodes ( ctx , ds , oldPodsToDelete , nil , hash )
2021-01-27 00:20:56 -05:00
}
// When surging, we create new pods whenever an old pod is unavailable, and we can create up
// to maxSurge extra pods
//
// Assumptions:
// * Expect manage loop to allow no more than two pods per node, one old, one new
// * Expect manage loop will create new pods if there are no pods on node
// * Expect manage loop will handle failed pods
// * Deleted pods do not count as unavailable so that updates make progress when nodes are down
// Invariants:
// * A node with an unavailable old pod is a candidate for immediate new pod creation
// * An old available pod is deleted if a new pod is available
// * No more than maxSurge new pods are created for old available pods at any one time
//
2023-08-08 23:27:18 -04:00
var oldPodsToDelete [ ] string // these pods are already updated or unavailable on sunsetted node
var shouldNotRunPodsToDelete [ ] string // candidate pods to be deleted on sunsetted nodes
2021-01-27 00:20:56 -05:00
var candidateNewNodes [ ] string
var allowedNewNodes [ ] string
var numSurge int
2023-08-08 23:27:18 -04:00
var numAvailable int
2021-01-27 00:20:56 -05:00
for nodeName , pods := range nodeToDaemonPods {
2021-01-27 01:09:53 -05:00
newPod , oldPod , ok := findUpdatedPodsOnNode ( ds , pods , hash )
2021-01-27 00:20:56 -05:00
if ! ok {
// let the manage loop clean up this node, and treat it as a surge node
2022-11-04 04:37:32 -04:00
logger . V ( 3 ) . Info ( "DaemonSet has excess pods on node, skipping to allow the core loop to process" , "daemonset" , klog . KObj ( ds ) , "node" , klog . KRef ( "" , nodeName ) )
2021-01-27 00:20:56 -05:00
numSurge ++
2017-02-16 05:18:16 -05:00
continue
}
2023-08-08 23:27:18 -04:00
// first count availability for all the nodes (even the ones that we are sunsetting due to scheduling constraints)
if oldPod != nil {
if podutil . IsPodAvailable ( oldPod , ds . Spec . MinReadySeconds , metav1 . Time { Time : now } ) {
numAvailable ++
}
} else if newPod != nil {
if podutil . IsPodAvailable ( newPod , ds . Spec . MinReadySeconds , metav1 . Time { Time : now } ) {
numAvailable ++
}
}
2021-01-27 00:20:56 -05:00
switch {
case oldPod == nil :
// we don't need to do anything to this node, the manage loop will handle it
case newPod == nil :
// this is a surge candidate
switch {
case ! podutil . IsPodAvailable ( oldPod , ds . Spec . MinReadySeconds , metav1 . Time { Time : now } ) :
2023-08-08 23:27:18 -04:00
node , err := dsc . nodeLister . Get ( nodeName )
if err != nil {
return fmt . Errorf ( "couldn't get node for nodeName %q: %v" , nodeName , err )
}
2025-11-10 15:42:54 -05:00
if shouldRun , _ := NodeShouldRunDaemonPod ( logger , node , ds ) ; ! shouldRun {
2023-08-08 23:27:18 -04:00
logger . V ( 5 ) . Info ( "DaemonSet pod on node is not available and does not match scheduling constraints, remove old pod" , "daemonset" , klog . KObj ( ds ) , "node" , nodeName , "oldPod" , klog . KObj ( oldPod ) )
oldPodsToDelete = append ( oldPodsToDelete , oldPod . Name )
continue
}
2021-01-27 00:20:56 -05:00
// the old pod isn't available, allow it to become a replacement
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "Pod on node is out of date and not available, allowing replacement" , "daemonset" , klog . KObj ( ds ) , "pod" , klog . KObj ( oldPod ) , "node" , klog . KRef ( "" , nodeName ) )
2021-01-27 00:20:56 -05:00
// record the replacement
if allowedNewNodes == nil {
2021-01-27 01:09:53 -05:00
allowedNewNodes = make ( [ ] string , 0 , len ( nodeToDaemonPods ) )
2021-01-27 00:20:56 -05:00
}
allowedNewNodes = append ( allowedNewNodes , nodeName )
default :
2023-08-08 23:27:18 -04:00
node , err := dsc . nodeLister . Get ( nodeName )
if err != nil {
return fmt . Errorf ( "couldn't get node for nodeName %q: %v" , nodeName , err )
}
2025-11-10 15:42:54 -05:00
if shouldRun , _ := NodeShouldRunDaemonPod ( logger , node , ds ) ; ! shouldRun {
2023-08-08 23:27:18 -04:00
shouldNotRunPodsToDelete = append ( shouldNotRunPodsToDelete , oldPod . Name )
continue
}
if numSurge >= maxSurge {
// no point considering any other candidates
continue
}
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet pod on node is out of date, this is a surge candidate" , "daemonset" , klog . KObj ( ds ) , "pod" , klog . KObj ( oldPod ) , "node" , klog . KRef ( "" , nodeName ) )
2021-01-27 00:20:56 -05:00
// record the candidate
if candidateNewNodes == nil {
candidateNewNodes = make ( [ ] string , 0 , maxSurge )
}
candidateNewNodes = append ( candidateNewNodes , nodeName )
}
default :
// we have already surged onto this node, determine our state
if ! podutil . IsPodAvailable ( newPod , ds . Spec . MinReadySeconds , metav1 . Time { Time : now } ) {
// we're waiting to go available here
numSurge ++
continue
}
// we're available, delete the old pod
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet pod on node is available, remove old pod" , "daemonset" , klog . KObj ( ds ) , "newPod" , klog . KObj ( newPod ) , "node" , nodeName , "oldPod" , klog . KObj ( oldPod ) )
2021-01-27 00:20:56 -05:00
oldPodsToDelete = append ( oldPodsToDelete , oldPod . Name )
}
2017-02-16 05:18:16 -05:00
}
2021-01-27 00:20:56 -05:00
// use any of the candidates we can, including the allowedNewNodes
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet allowing replacements" , "daemonset" , klog . KObj ( ds ) , "replacements" , len ( allowedNewNodes ) , "maxSurge" , maxSurge , "numSurge" , numSurge , "candidates" , len ( candidateNewNodes ) )
2021-01-27 00:20:56 -05:00
remainingSurge := maxSurge - numSurge
2023-08-08 23:27:18 -04:00
// With maxSurge, the application owner expects 100% availability.
// When the scheduling constraint change from node A to node B, we do not want the application to stay
// without any available pods. Only delete a pod on node A when a pod on node B becomes available.
if deletablePodsNumber := numAvailable - desiredNumberScheduled ; deletablePodsNumber > 0 {
if shouldNotRunPodsToDeleteNumber := len ( shouldNotRunPodsToDelete ) ; deletablePodsNumber > shouldNotRunPodsToDeleteNumber {
deletablePodsNumber = shouldNotRunPodsToDeleteNumber
}
for _ , podToDeleteName := range shouldNotRunPodsToDelete [ : deletablePodsNumber ] {
podToDelete , err := dsc . podLister . Pods ( ds . Namespace ) . Get ( podToDeleteName )
if err != nil {
if errors . IsNotFound ( err ) {
continue
}
return fmt . Errorf ( "couldn't get pod which should be deleted due to scheduling constraints %q: %v" , podToDeleteName , err )
}
logger . V ( 5 ) . Info ( "DaemonSet pod on node should be deleted due to scheduling constraints" , "daemonset" , klog . KObj ( ds ) , "pod" , klog . KObj ( podToDelete ) , "node" , podToDelete . Spec . NodeName )
oldPodsToDelete = append ( oldPodsToDelete , podToDeleteName )
}
}
2021-01-27 00:20:56 -05:00
if remainingSurge < 0 {
remainingSurge = 0
}
if max := len ( candidateNewNodes ) ; remainingSurge > max {
remainingSurge = max
}
newNodesToCreate := append ( allowedNewNodes , candidateNewNodes [ : remainingSurge ] ... )
2021-11-10 17:56:46 -05:00
return dsc . syncNodes ( ctx , ds , oldPodsToDelete , newNodesToCreate , hash )
2021-01-27 00:20:56 -05:00
}
2021-01-27 01:09:53 -05:00
// findUpdatedPodsOnNode looks at non-deleted pods on a given node and returns true if there
2021-01-27 00:20:56 -05:00
// is at most one of each old and new pods, or false if there are multiples. We can skip
// processing the particular node in those scenarios and let the manage loop prune the
// excess pods for our next time around.
2021-01-27 01:09:53 -05:00
func findUpdatedPodsOnNode ( ds * apps . DaemonSet , podsOnNode [ ] * v1 . Pod , hash string ) ( newPod , oldPod * v1 . Pod , ok bool ) {
2021-01-27 00:20:56 -05:00
for _ , pod := range podsOnNode {
if pod . DeletionTimestamp != nil {
continue
}
generation , err := util . GetTemplateGeneration ( ds )
if err != nil {
generation = nil
}
if util . IsPodUpdated ( pod , hash , generation ) {
if newPod != nil {
return nil , nil , false
}
newPod = pod
} else {
if oldPod != nil {
return nil , nil , false
}
oldPod = pod
2017-02-16 05:18:16 -05:00
}
}
2021-01-27 00:20:56 -05:00
return newPod , oldPod , true
2017-02-16 05:18:16 -05:00
}
2017-06-09 14:03:38 -04:00
// constructHistory finds all histories controlled by the given DaemonSet, and
// update current history revision number, or create current history if need to.
// It also deduplicates current history, and adds missing unique labels to existing histories.
2021-04-22 14:14:52 -04:00
func ( dsc * DaemonSetsController ) constructHistory ( ctx context . Context , ds * apps . DaemonSet ) ( cur * apps . ControllerRevision , old [ ] * apps . ControllerRevision , err error ) {
2017-05-17 19:53:46 -04:00
var histories [ ] * apps . ControllerRevision
var currentHistories [ ] * apps . ControllerRevision
2021-04-22 14:14:52 -04:00
histories , err = dsc . controlledHistories ( ctx , ds )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , nil , err
}
for _ , history := range histories {
// Add the unique label if it's not already added to the history
// We use history name instead of computing hash, so that we don't need to worry about hash collision
2018-02-14 13:35:38 -05:00
if _ , ok := history . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] ; ! ok {
2017-08-15 08:14:21 -04:00
toUpdate := history . DeepCopy ( )
2018-02-14 13:35:38 -05:00
toUpdate . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] = toUpdate . Name
2021-04-22 14:14:52 -04:00
history , err = dsc . kubeClient . AppsV1 ( ) . ControllerRevisions ( ds . Namespace ) . Update ( ctx , toUpdate , metav1 . UpdateOptions { } )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , nil , err
}
}
// Compare histories with ds to separate cur and old history
found := false
2017-06-06 13:09:57 -04:00
found , err = Match ( ds , history )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , nil , err
}
if found {
currentHistories = append ( currentHistories , history )
} else {
old = append ( old , history )
}
}
currRevision := maxRevision ( old ) + 1
switch len ( currentHistories ) {
case 0 :
// Create a new history if the current one isn't found
2021-04-22 14:14:52 -04:00
cur , err = dsc . snapshot ( ctx , ds , currRevision )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , nil , err
}
default :
2021-04-22 14:14:52 -04:00
cur , err = dsc . dedupCurHistories ( ctx , ds , currentHistories )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , nil , err
}
// Update revision number if necessary
if cur . Revision < currRevision {
2017-08-15 08:14:21 -04:00
toUpdate := cur . DeepCopy ( )
2017-05-17 19:53:46 -04:00
toUpdate . Revision = currRevision
2021-04-22 14:14:52 -04:00
_ , err = dsc . kubeClient . AppsV1 ( ) . ControllerRevisions ( ds . Namespace ) . Update ( ctx , toUpdate , metav1 . UpdateOptions { } )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , nil , err
}
}
}
return cur , old , err
}
2021-04-22 14:14:52 -04:00
func ( dsc * DaemonSetsController ) cleanupHistory ( ctx context . Context , ds * apps . DaemonSet , old [ ] * apps . ControllerRevision ) error {
2023-06-27 11:56:33 -04:00
// Include deleted terminal pods when maintaining history.
nodesToDaemonPods , err := dsc . getNodesToDaemonPods ( ctx , ds , true )
2017-05-17 19:53:46 -04:00
if err != nil {
return fmt . Errorf ( "couldn't get node to daemon pod mapping for daemon set %q: %v" , ds . Name , err )
}
toKeep := int ( * ds . Spec . RevisionHistoryLimit )
toKill := len ( old ) - toKeep
if toKill <= 0 {
return nil
}
// Find all hashes of live pods
liveHashes := make ( map [ string ] bool )
for _ , pods := range nodesToDaemonPods {
for _ , pod := range pods {
2018-02-14 13:35:38 -05:00
if hash := pod . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] ; len ( hash ) > 0 {
2017-05-17 19:53:46 -04:00
liveHashes [ hash ] = true
}
}
}
// Clean up old history from smallest to highest revision (from oldest to newest)
sort . Sort ( historiesByRevision ( old ) )
for _ , history := range old {
if toKill <= 0 {
break
}
2019-04-02 18:45:46 -04:00
if hash := history . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] ; liveHashes [ hash ] {
2017-05-17 19:53:46 -04:00
continue
}
// Clean up
2021-04-22 14:14:52 -04:00
err := dsc . kubeClient . AppsV1 ( ) . ControllerRevisions ( ds . Namespace ) . Delete ( ctx , history . Name , metav1 . DeleteOptions { } )
2017-05-17 19:53:46 -04:00
if err != nil {
return err
}
toKill --
}
return nil
}
// maxRevision returns the max revision number of the given list of histories
func maxRevision ( histories [ ] * apps . ControllerRevision ) int64 {
max := int64 ( 0 )
for _ , history := range histories {
if history . Revision > max {
max = history . Revision
}
}
return max
}
2021-04-22 14:14:52 -04:00
func ( dsc * DaemonSetsController ) dedupCurHistories ( ctx context . Context , ds * apps . DaemonSet , curHistories [ ] * apps . ControllerRevision ) ( * apps . ControllerRevision , error ) {
2017-05-17 19:53:46 -04:00
if len ( curHistories ) == 1 {
return curHistories [ 0 ] , nil
}
var maxRevision int64
var keepCur * apps . ControllerRevision
for _ , cur := range curHistories {
if cur . Revision >= maxRevision {
keepCur = cur
maxRevision = cur . Revision
}
}
2022-02-25 02:02:50 -05:00
// Relabel pods before dedup
pods , err := dsc . getDaemonPods ( ctx , ds )
if err != nil {
return nil , err
}
for _ , pod := range pods {
if pod . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] != keepCur . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] {
patchRaw := map [ string ] interface { } {
"metadata" : map [ string ] interface { } {
"labels" : map [ string ] interface { } {
apps . DefaultDaemonSetUniqueLabelKey : keepCur . Labels [ apps . DefaultDaemonSetUniqueLabelKey ] ,
} ,
} ,
}
patchJSON , err := json . Marshal ( patchRaw )
if err != nil {
return nil , err
}
_ , err = dsc . kubeClient . CoreV1 ( ) . Pods ( ds . Namespace ) . Patch ( ctx , pod . Name , types . MergePatchType , patchJSON , metav1 . PatchOptions { } )
if err != nil {
return nil , err
}
}
}
// Clean up duplicates
2017-05-17 19:53:46 -04:00
for _ , cur := range curHistories {
if cur . Name == keepCur . Name {
continue
}
// Remove duplicates
2021-04-22 14:14:52 -04:00
err = dsc . kubeClient . AppsV1 ( ) . ControllerRevisions ( ds . Namespace ) . Delete ( ctx , cur . Name , metav1 . DeleteOptions { } )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , err
}
}
return keepCur , nil
}
2017-06-02 21:02:01 -04:00
// controlledHistories returns all ControllerRevisions controlled by the given DaemonSet.
// This also reconciles ControllerRef by adopting/orphaning.
2017-05-17 19:53:46 -04:00
// Note that returned histories are pointers to objects in the cache.
// If you want to modify one, you need to deep-copy it first.
2021-04-22 14:14:52 -04:00
func ( dsc * DaemonSetsController ) controlledHistories ( ctx context . Context , ds * apps . DaemonSet ) ( [ ] * apps . ControllerRevision , error ) {
2017-05-17 19:53:46 -04:00
selector , err := metav1 . LabelSelectorAsSelector ( ds . Spec . Selector )
if err != nil {
return nil , err
}
2017-06-02 21:02:01 -04:00
// List all histories to include those that don't match the selector anymore
// but have a ControllerRef pointing to the controller.
2021-11-01 05:36:15 -04:00
histories , err := dsc . historyLister . ControllerRevisions ( ds . Namespace ) . List ( labels . Everything ( ) )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , err
}
2017-06-02 21:02:01 -04:00
// If any adoptions are attempted, we should first recheck for deletion with
// an uncached quorum read sometime after listing Pods (see #42639).
2021-04-22 14:14:52 -04:00
canAdoptFunc := controller . RecheckDeletionTimestamp ( func ( ctx context . Context ) ( metav1 . Object , error ) {
fresh , err := dsc . kubeClient . AppsV1 ( ) . DaemonSets ( ds . Namespace ) . Get ( ctx , ds . Name , metav1 . GetOptions { } )
2017-06-02 21:02:01 -04:00
if err != nil {
return nil , err
2017-05-17 19:53:46 -04:00
}
2017-06-02 21:02:01 -04:00
if fresh . UID != ds . UID {
return nil , fmt . Errorf ( "original DaemonSet %v/%v is gone: got uid %v, wanted %v" , ds . Namespace , ds . Name , fresh . UID , ds . UID )
}
return fresh , nil
} )
// Use ControllerRefManager to adopt/orphan as needed.
cm := controller . NewControllerRevisionControllerRefManager ( dsc . crControl , ds , selector , controllerKind , canAdoptFunc )
2021-04-22 14:14:52 -04:00
return cm . ClaimControllerRevisions ( ctx , histories )
2017-05-17 19:53:46 -04:00
}
2017-06-06 13:09:57 -04:00
// Match check if the given DaemonSet's template matches the template stored in the given history.
2018-02-14 13:35:38 -05:00
func Match ( ds * apps . DaemonSet , history * apps . ControllerRevision ) ( bool , error ) {
2017-06-06 13:09:57 -04:00
patch , err := getPatch ( ds )
if err != nil {
return false , err
}
return bytes . Equal ( patch , history . Data . Raw ) , nil
2017-05-17 19:53:46 -04:00
}
2017-06-06 13:09:57 -04:00
// getPatch returns a strategic merge patch that can be applied to restore a Daemonset to a
// previous version. If the returned error is nil the patch is valid. The current state that we save is just the
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
// recorded patches.
2018-02-14 13:35:38 -05:00
func getPatch ( ds * apps . DaemonSet ) ( [ ] byte , error ) {
2017-06-06 14:31:04 -04:00
dsBytes , err := json . Marshal ( ds )
2017-06-06 13:09:57 -04:00
if err != nil {
return nil , err
}
var raw map [ string ] interface { }
2017-06-06 14:31:04 -04:00
err = json . Unmarshal ( dsBytes , & raw )
if err != nil {
return nil , err
}
2017-06-06 13:09:57 -04:00
objCopy := make ( map [ string ] interface { } )
specCopy := make ( map [ string ] interface { } )
2017-06-06 14:31:04 -04:00
// Create a patch of the DaemonSet that replaces spec.template
2017-06-06 13:09:57 -04:00
spec := raw [ "spec" ] . ( map [ string ] interface { } )
template := spec [ "template" ] . ( map [ string ] interface { } )
specCopy [ "template" ] = template
template [ "$patch" ] = "replace"
objCopy [ "spec" ] = specCopy
patch , err := json . Marshal ( objCopy )
return patch , err
2017-05-17 19:53:46 -04:00
}
2021-04-22 14:14:52 -04:00
func ( dsc * DaemonSetsController ) snapshot ( ctx context . Context , ds * apps . DaemonSet , revision int64 ) ( * apps . ControllerRevision , error ) {
2017-06-06 13:09:57 -04:00
patch , err := getPatch ( ds )
2017-05-17 19:53:46 -04:00
if err != nil {
return nil , err
}
2018-07-11 20:01:38 -04:00
hash := controller . ComputeHash ( & ds . Spec . Template , ds . Status . CollisionCount )
name := ds . Name + "-" + hash
2017-05-17 19:53:46 -04:00
history := & apps . ControllerRevision {
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
Namespace : ds . Namespace ,
2018-02-14 13:35:38 -05:00
Labels : labelsutil . CloneAndAddLabel ( ds . Spec . Template . Labels , apps . DefaultDaemonSetUniqueLabelKey , hash ) ,
2017-05-17 19:53:46 -04:00
Annotations : ds . Annotations ,
2017-08-02 06:05:37 -04:00
OwnerReferences : [ ] metav1 . OwnerReference { * metav1 . NewControllerRef ( ds , controllerKind ) } ,
2017-05-17 19:53:46 -04:00
} ,
2017-06-06 13:09:57 -04:00
Data : runtime . RawExtension { Raw : patch } ,
2017-05-17 19:53:46 -04:00
Revision : revision ,
}
2021-04-22 14:14:52 -04:00
history , err = dsc . kubeClient . AppsV1 ( ) . ControllerRevisions ( ds . Namespace ) . Create ( ctx , history , metav1 . CreateOptions { } )
2018-07-21 00:09:23 -04:00
if outerErr := err ; errors . IsAlreadyExists ( outerErr ) {
2022-11-04 04:37:32 -04:00
logger := klog . FromContext ( ctx )
2017-05-17 19:53:46 -04:00
// TODO: Is it okay to get from historyLister?
2021-04-22 14:14:52 -04:00
existedHistory , getErr := dsc . kubeClient . AppsV1 ( ) . ControllerRevisions ( ds . Namespace ) . Get ( ctx , name , metav1 . GetOptions { } )
2017-05-17 19:53:46 -04:00
if getErr != nil {
return nil , getErr
}
// Check if we already created it
2018-07-21 00:09:23 -04:00
done , matchErr := Match ( ds , existedHistory )
if matchErr != nil {
return nil , matchErr
2017-05-17 19:53:46 -04:00
}
if done {
return existedHistory , nil
}
// Handle name collisions between different history
2018-08-15 19:07:59 -04:00
// Get the latest DaemonSet from the API server to make sure collision count is only increased when necessary
2021-04-22 14:14:52 -04:00
currDS , getErr := dsc . kubeClient . AppsV1 ( ) . DaemonSets ( ds . Namespace ) . Get ( ctx , ds . Name , metav1 . GetOptions { } )
2017-05-17 19:53:46 -04:00
if getErr != nil {
return nil , getErr
}
2018-08-15 19:07:59 -04:00
// If the collision count used to compute hash was in fact stale, there's no need to bump collision count; retry again
if ! reflect . DeepEqual ( currDS . Status . CollisionCount , ds . Status . CollisionCount ) {
return nil , fmt . Errorf ( "found a stale collision count (%d, expected %d) of DaemonSet %q while processing; will retry until it is updated" , ds . Status . CollisionCount , currDS . Status . CollisionCount , ds . Name )
}
2017-05-17 19:53:46 -04:00
if currDS . Status . CollisionCount == nil {
2017-08-13 08:10:25 -04:00
currDS . Status . CollisionCount = new ( int32 )
2017-05-17 19:53:46 -04:00
}
* currDS . Status . CollisionCount ++
2021-04-22 14:14:52 -04:00
_ , updateErr := dsc . kubeClient . AppsV1 ( ) . DaemonSets ( ds . Namespace ) . UpdateStatus ( ctx , currDS , metav1 . UpdateOptions { } )
2017-05-17 19:53:46 -04:00
if updateErr != nil {
return nil , updateErr
}
2022-11-04 04:37:32 -04:00
logger . V ( 2 ) . Info ( "Found a hash collision for DaemonSet - bumping collisionCount to resolve it" , "daemonset" , klog . KObj ( ds ) , "collisionCount" , * currDS . Status . CollisionCount )
2018-07-21 00:09:23 -04:00
return nil , outerErr
2017-05-17 19:53:46 -04:00
}
return history , err
}
2023-08-08 23:27:18 -04:00
// updatedDesiredNodeCounts calculates the true number of allowed surge, unavailable or desired scheduled pods and
2021-01-27 01:09:53 -05:00
// updates the nodeToDaemonPods array to include an empty array for every node that is not scheduled.
2023-08-08 23:27:18 -04:00
func ( dsc * DaemonSetsController ) updatedDesiredNodeCounts ( ctx context . Context , ds * apps . DaemonSet , nodeList [ ] * v1 . Node , nodeToDaemonPods map [ string ] [ ] * v1 . Pod ) ( int , int , int , error ) {
2021-01-27 01:09:53 -05:00
var desiredNumberScheduled int
2022-11-04 04:37:32 -04:00
logger := klog . FromContext ( ctx )
2017-02-16 05:18:16 -05:00
for i := range nodeList {
node := nodeList [ i ]
2025-11-10 15:42:54 -05:00
wantToRun , _ := NodeShouldRunDaemonPod ( logger , node , ds )
2017-02-16 05:18:16 -05:00
if ! wantToRun {
continue
}
desiredNumberScheduled ++
2021-01-27 01:09:53 -05:00
if _ , exists := nodeToDaemonPods [ node . Name ] ; ! exists {
nodeToDaemonPods [ node . Name ] = nil
2017-02-16 05:18:16 -05:00
}
}
2021-01-27 00:20:56 -05:00
maxUnavailable , err := util . UnavailableCount ( ds , desiredNumberScheduled )
2017-02-16 05:18:16 -05:00
if err != nil {
2023-08-08 23:27:18 -04:00
return - 1 , - 1 , - 1 , fmt . Errorf ( "invalid value for MaxUnavailable: %v" , err )
2021-01-27 00:20:56 -05:00
}
maxSurge , err := util . SurgeCount ( ds , desiredNumberScheduled )
if err != nil {
2023-08-08 23:27:18 -04:00
return - 1 , - 1 , - 1 , fmt . Errorf ( "invalid value for MaxSurge: %v" , err )
2021-01-27 00:20:56 -05:00
}
// if the daemonset returned with an impossible configuration, obey the default of unavailable=1 (in the
// event the apiserver returns 0 for both surge and unavailability)
if desiredNumberScheduled > 0 && maxUnavailable == 0 && maxSurge == 0 {
2022-11-04 04:37:32 -04:00
logger . Info ( "DaemonSet is not configured for surge or unavailability, defaulting to accepting unavailability" , "daemonset" , klog . KObj ( ds ) )
2021-01-27 00:20:56 -05:00
maxUnavailable = 1
2017-02-16 05:18:16 -05:00
}
2022-11-04 04:37:32 -04:00
logger . V ( 5 ) . Info ( "DaemonSet with maxSurge and maxUnavailable" , "daemonset" , klog . KObj ( ds ) , "maxSurge" , maxSurge , "maxUnavailable" , maxUnavailable )
2023-08-08 23:27:18 -04:00
return maxSurge , maxUnavailable , desiredNumberScheduled , nil
2017-02-16 05:18:16 -05:00
}
2017-05-17 19:53:46 -04:00
type historiesByRevision [ ] * apps . ControllerRevision
func ( h historiesByRevision ) Len ( ) int { return len ( h ) }
func ( h historiesByRevision ) Swap ( i , j int ) { h [ i ] , h [ j ] = h [ j ] , h [ i ] }
func ( h historiesByRevision ) Less ( i , j int ) bool {
return h [ i ] . Revision < h [ j ] . Revision
}