github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/scheduler/nodeinfo.go (about) 1 package scheduler 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/docker/swarmkit/api" 8 "github.com/docker/swarmkit/api/genericresource" 9 "github.com/docker/swarmkit/log" 10 ) 11 12 // hostPortSpec specifies a used host port. 13 type hostPortSpec struct { 14 protocol api.PortConfig_Protocol 15 publishedPort uint32 16 } 17 18 // versionedService defines a tuple that contains a service ID and a spec 19 // version, so that failures can be tracked per spec version. Note that if the 20 // task predates spec versioning, specVersion will contain the zero value, and 21 // this will still work correctly. 22 type versionedService struct { 23 serviceID string 24 specVersion api.Version 25 } 26 27 // NodeInfo contains a node and some additional metadata. 28 type NodeInfo struct { 29 *api.Node 30 Tasks map[string]*api.Task 31 ActiveTasksCount int 32 ActiveTasksCountByService map[string]int 33 AvailableResources *api.Resources 34 usedHostPorts map[hostPortSpec]struct{} 35 36 // recentFailures is a map from service ID/version to the timestamps of 37 // the most recent failures the node has experienced from replicas of 38 // that service. 39 recentFailures map[versionedService][]time.Time 40 41 // lastCleanup is the last time recentFailures was cleaned up. This is 42 // done periodically to avoid recentFailures growing without any limit. 43 lastCleanup time.Time 44 } 45 46 func newNodeInfo(n *api.Node, tasks map[string]*api.Task, availableResources api.Resources) NodeInfo { 47 nodeInfo := NodeInfo{ 48 Node: n, 49 Tasks: make(map[string]*api.Task), 50 ActiveTasksCountByService: make(map[string]int), 51 AvailableResources: availableResources.Copy(), 52 usedHostPorts: make(map[hostPortSpec]struct{}), 53 recentFailures: make(map[versionedService][]time.Time), 54 lastCleanup: time.Now(), 55 } 56 57 for _, t := range tasks { 58 nodeInfo.addTask(t) 59 } 60 61 return nodeInfo 62 } 63 64 // removeTask removes a task from nodeInfo if it's tracked there, and returns true 65 // if nodeInfo was modified. 66 func (nodeInfo *NodeInfo) removeTask(t *api.Task) bool { 67 oldTask, ok := nodeInfo.Tasks[t.ID] 68 if !ok { 69 return false 70 } 71 72 delete(nodeInfo.Tasks, t.ID) 73 if oldTask.DesiredState <= api.TaskStateCompleted { 74 nodeInfo.ActiveTasksCount-- 75 nodeInfo.ActiveTasksCountByService[t.ServiceID]-- 76 } 77 78 if t.Endpoint != nil { 79 for _, port := range t.Endpoint.Ports { 80 if port.PublishMode == api.PublishModeHost && port.PublishedPort != 0 { 81 portSpec := hostPortSpec{protocol: port.Protocol, publishedPort: port.PublishedPort} 82 delete(nodeInfo.usedHostPorts, portSpec) 83 } 84 } 85 } 86 87 reservations := taskReservations(t.Spec) 88 resources := nodeInfo.AvailableResources 89 90 resources.MemoryBytes += reservations.MemoryBytes 91 resources.NanoCPUs += reservations.NanoCPUs 92 93 if nodeInfo.Description == nil || nodeInfo.Description.Resources == nil || 94 nodeInfo.Description.Resources.Generic == nil { 95 return true 96 } 97 98 taskAssigned := t.AssignedGenericResources 99 nodeAvailableResources := &resources.Generic 100 nodeRes := nodeInfo.Description.Resources.Generic 101 genericresource.Reclaim(nodeAvailableResources, taskAssigned, nodeRes) 102 103 return true 104 } 105 106 // addTask adds or updates a task on nodeInfo, and returns true if nodeInfo was 107 // modified. 108 func (nodeInfo *NodeInfo) addTask(t *api.Task) bool { 109 oldTask, ok := nodeInfo.Tasks[t.ID] 110 if ok { 111 if t.DesiredState <= api.TaskStateCompleted && oldTask.DesiredState > api.TaskStateCompleted { 112 nodeInfo.Tasks[t.ID] = t 113 nodeInfo.ActiveTasksCount++ 114 nodeInfo.ActiveTasksCountByService[t.ServiceID]++ 115 return true 116 } else if t.DesiredState > api.TaskStateCompleted && oldTask.DesiredState <= api.TaskStateCompleted { 117 nodeInfo.Tasks[t.ID] = t 118 nodeInfo.ActiveTasksCount-- 119 nodeInfo.ActiveTasksCountByService[t.ServiceID]-- 120 return true 121 } 122 return false 123 } 124 125 nodeInfo.Tasks[t.ID] = t 126 127 reservations := taskReservations(t.Spec) 128 resources := nodeInfo.AvailableResources 129 130 resources.MemoryBytes -= reservations.MemoryBytes 131 resources.NanoCPUs -= reservations.NanoCPUs 132 133 // minimum size required 134 t.AssignedGenericResources = make([]*api.GenericResource, 0, len(resources.Generic)) 135 taskAssigned := &t.AssignedGenericResources 136 137 genericresource.Claim(&resources.Generic, taskAssigned, reservations.Generic) 138 139 if t.Endpoint != nil { 140 for _, port := range t.Endpoint.Ports { 141 if port.PublishMode == api.PublishModeHost && port.PublishedPort != 0 { 142 portSpec := hostPortSpec{protocol: port.Protocol, publishedPort: port.PublishedPort} 143 nodeInfo.usedHostPorts[portSpec] = struct{}{} 144 } 145 } 146 } 147 148 if t.DesiredState <= api.TaskStateCompleted { 149 nodeInfo.ActiveTasksCount++ 150 nodeInfo.ActiveTasksCountByService[t.ServiceID]++ 151 } 152 153 return true 154 } 155 156 func taskReservations(spec api.TaskSpec) (reservations api.Resources) { 157 if spec.Resources != nil && spec.Resources.Reservations != nil { 158 reservations = *spec.Resources.Reservations 159 } 160 return 161 } 162 163 func (nodeInfo *NodeInfo) cleanupFailures(now time.Time) { 164 entriesLoop: 165 for key, failuresEntry := range nodeInfo.recentFailures { 166 for _, timestamp := range failuresEntry { 167 if now.Sub(timestamp) < monitorFailures { 168 continue entriesLoop 169 } 170 } 171 delete(nodeInfo.recentFailures, key) 172 } 173 nodeInfo.lastCleanup = now 174 } 175 176 // taskFailed records a task failure from a given service. 177 func (nodeInfo *NodeInfo) taskFailed(ctx context.Context, t *api.Task) { 178 expired := 0 179 now := time.Now() 180 181 if now.Sub(nodeInfo.lastCleanup) >= monitorFailures { 182 nodeInfo.cleanupFailures(now) 183 } 184 185 versionedService := versionedService{serviceID: t.ServiceID} 186 if t.SpecVersion != nil { 187 versionedService.specVersion = *t.SpecVersion 188 } 189 190 for _, timestamp := range nodeInfo.recentFailures[versionedService] { 191 if now.Sub(timestamp) < monitorFailures { 192 break 193 } 194 expired++ 195 } 196 197 if len(nodeInfo.recentFailures[versionedService])-expired == maxFailures-1 { 198 log.G(ctx).Warnf("underweighting node %s for service %s because it experienced %d failures or rejections within %s", nodeInfo.ID, t.ServiceID, maxFailures, monitorFailures.String()) 199 } 200 201 nodeInfo.recentFailures[versionedService] = append(nodeInfo.recentFailures[versionedService][expired:], now) 202 } 203 204 // countRecentFailures returns the number of times the service has failed on 205 // this node within the lookback window monitorFailures. 206 func (nodeInfo *NodeInfo) countRecentFailures(now time.Time, t *api.Task) int { 207 versionedService := versionedService{serviceID: t.ServiceID} 208 if t.SpecVersion != nil { 209 versionedService.specVersion = *t.SpecVersion 210 } 211 212 recentFailureCount := len(nodeInfo.recentFailures[versionedService]) 213 for i := recentFailureCount - 1; i >= 0; i-- { 214 if now.Sub(nodeInfo.recentFailures[versionedService][i]) > monitorFailures { 215 recentFailureCount -= i + 1 216 break 217 } 218 } 219 220 return recentFailureCount 221 }