Skip to content

Commit 0283728

Browse files
andrewd-zededaeriknordmark
authored andcommitted
Enable strict node scheduling for cluster app instances.
App wil only run if scheduling succeeds for the requested node id. Set DesignatedNodeID and AffinityType Required. With strict node scheduling the app will not failover to other cluster nodes. Signed-off-by: Andrew Durbin <andrewd@zededa.com>
1 parent 4d12ef5 commit 0283728

File tree

7 files changed

+80
-22
lines changed

7 files changed

+80
-22
lines changed

pkg/pillar/cmd/zedagent/parseconfig.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,12 @@ func parseAppInstanceConfig(getconfigCtx *getconfigContext,
838838
} else {
839839
appInstance.IsDesignatedNodeID = true
840840
}
841+
switch cfgApp.GetAffinity() {
842+
case zconfig.AffinityType_AFFINITY_TYPE_REQUIRED:
843+
appInstance.AffinityType = types.RequiredDuringScheduling
844+
default:
845+
appInstance.AffinityType = types.PreferredDuringScheduling
846+
}
841847

842848
// Verify that it fits and if not publish with error
843849
checkAndPublishAppInstanceConfig(getconfigCtx.pubAppInstanceConfig, appInstance)

pkg/pillar/cmd/zedkube/failover.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ func (z *zedkube) checkAppsFailover(wdFunc func()) {
5252
wdFunc()
5353

5454
aiconfig := item.(types.AppInstanceConfig)
55+
if aiconfig.AffinityType == types.RequiredDuringScheduling {
56+
// Don't attempt failover on apps with strict node affinity
57+
log.Noticef("checkAppsFailover: AppInstanceConfig id:%s name:%s has affinityType:required, failover is disabled",
58+
aiconfig.UUIDandVersion.UUID, aiconfig.DisplayName)
59+
continue
60+
}
61+
5562
encAppStatus := types.ENClusterAppStatus{
5663
AppUUID: aiconfig.UUIDandVersion.UUID,
5764
IsDNidNode: aiconfig.IsDesignatedNodeID,

pkg/pillar/cmd/zedmanager/handledomainmgr.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ func MaybeAddDomainConfig(ctx *zedmanagerContext,
6464
DisableLogs: aiConfig.DisableLogs,
6565
// This isDNiDnode will be set to true even if the App is not in cluster mode,
6666
// This will be set in zedagent parseConfig for the case of single node/device App case.
67-
IsDNidNode: aiConfig.IsDesignatedNodeID,
67+
IsDNidNode: aiConfig.IsDesignatedNodeID,
68+
AffinityType: aiConfig.AffinityType,
6869
// DeploymentType is set to the value of the DeploymentType of the AppInstanceConfig
6970
DeploymentType: aiConfig.DeploymentType,
7071
}

pkg/pillar/docs/failover.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,23 @@ The designated device will start the application and publishes AppInstanceStatus
120120

121121
There is also additional flag in AppInstanceStatus named NoUploadStatsToController. Zedagent looks at that flag and decides to upload stats to controller or not. The reason to have such flag is that app can move between nodes and only one node is supposed to upload stats to controller. Hence that flag will be toggled accordingly.
122122

123-
eve-api has been enhanced to add designated node id to AppInstanceConfig
123+
eve-api has been enhanced to add designated node id and affinity type to AppInstanceConfig
124124

125125
* [config/appinfo.config](https://github.com/lf-edge/eve-api/blob/main/proto/config/appconfig.proto)
126126

127127
```golang
128128
message AppInstanceConfig {
129129
....
130-
// This edge-node UUID for the Designate Node for the Application
130+
// Designated Node Id is used for cluster nodes to determine placement of
131+
// the AppInstance when an EdgeNodeCluster config is present.
132+
// See affinity below to set the desired node affinity.
133+
// eg. Preferred or Required.
131134
string designated_node_id = 26;
135+
....
136+
// Affinity is used for cluster nodes to determine
137+
// preferred or required node scheduling for app instances.
138+
// Node Id for scheduling is defined in designated_node_id.
139+
AffinityType affinity = 29;
132140
}
133141
```
134142

@@ -143,7 +151,8 @@ eve-api has been enhanced to add designated node id to AppInstanceConfig
143151
}
144152
```
145153

146-
EVE specific changes to AppInstanceConfig and AppInstanceStatus structs to carry bool IsDesignatedNodeID
154+
EVE specific changes to AppInstanceConfig and AppInstanceStatus structs to carry bool IsDesignatedNodeID.
155+
AffinityType is also added to AppInstanceConfig
147156

148157
* [types/zedmanagertypes.go](pkg/pillar/types/zedmanagertypes.go)
149158

@@ -152,6 +161,9 @@ EVE specific changes to AppInstanceConfig and AppInstanceStatus structs to carry
152161
....
153162
// Am I Cluster Designated Node Id for this app
154163
IsDesignatedNodeID bool
164+
165+
// Node Affinity for cluster IsDesignatedNodeID
166+
AffinityType Affinity
155167
}
156168
type AppInstanceStatus struct {
157169
....
@@ -181,6 +193,8 @@ There are various scenarios that can trigger the application failover. Some of t
181193

182194
We depend on the kubernetes infrastructure to detect and the trigger the failover of an application.
183195
Kubernetes scheduler makes the decision to move the app to some other existing node in a cluster.
196+
If an application is defined with AppInstanceConfig.AffinityType==RequiredDuringScheduling then
197+
the application cannot failover to another node, failover is disabled for only that application.
184198

185199
Once the application gets Scheduled on a particular node after failover. EVE code does the following;
186200

pkg/pillar/hypervisor/kubevirt.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ func (ctx kubevirtContext) CreateReplicaVMIConfig(domainName string, config type
455455
}
456456

457457
// Set the affinity to this node the VMI is preferred to run on
458-
affinity := setKubeAffinity(nodeName)
458+
affinity := setKubeAffinity(nodeName, config.AffinityType)
459459

460460
// Set tolerations to handle node conditions
461461
tolerations := setKubeToleration(int64(tolerateSec))
@@ -1337,7 +1337,7 @@ func (ctx kubevirtContext) CreateReplicaPodConfig(domainName string, config type
13371337
},
13381338
Spec: k8sv1.PodSpec{
13391339
Tolerations: setKubeToleration(int64(tolerateSec)),
1340-
Affinity: setKubeAffinity(nodeName),
1340+
Affinity: setKubeAffinity(nodeName, config.AffinityType),
13411341
Containers: []k8sv1.Container{
13421342
{
13431343
Name: kubeName,
@@ -1421,26 +1421,38 @@ func (ctx kubevirtContext) CreateReplicaPodConfig(domainName string, config type
14211421
return nil
14221422
}
14231423

1424-
func setKubeAffinity(nodeName string) *k8sv1.Affinity {
1425-
affinity := &k8sv1.Affinity{
1426-
NodeAffinity: &k8sv1.NodeAffinity{
1427-
PreferredDuringSchedulingIgnoredDuringExecution: []k8sv1.PreferredSchedulingTerm{
1424+
func setKubeAffinity(nodeName string, affinityType types.Affinity) *k8sv1.Affinity {
1425+
matchExpressions := []k8sv1.NodeSelectorRequirement{
1426+
{
1427+
Key: "kubernetes.io/hostname",
1428+
Operator: "In",
1429+
Values: []string{nodeName},
1430+
},
1431+
}
1432+
1433+
k8sAffinity := &k8sv1.Affinity{
1434+
NodeAffinity: &k8sv1.NodeAffinity{},
1435+
}
1436+
switch affinityType {
1437+
case types.PreferredDuringScheduling:
1438+
k8sAffinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution = []k8sv1.PreferredSchedulingTerm{
1439+
{
1440+
Preference: k8sv1.NodeSelectorTerm{
1441+
MatchExpressions: matchExpressions,
1442+
},
1443+
Weight: 100,
1444+
},
1445+
}
1446+
case types.RequiredDuringScheduling:
1447+
k8sAffinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &k8sv1.NodeSelector{
1448+
NodeSelectorTerms: []k8sv1.NodeSelectorTerm{
14281449
{
1429-
Preference: k8sv1.NodeSelectorTerm{
1430-
MatchExpressions: []k8sv1.NodeSelectorRequirement{
1431-
{
1432-
Key: "kubernetes.io/hostname",
1433-
Operator: "In",
1434-
Values: []string{nodeName},
1435-
},
1436-
},
1437-
},
1438-
Weight: 100,
1450+
MatchExpressions: matchExpressions,
14391451
},
14401452
},
1441-
},
1453+
}
14421454
}
1443-
return affinity
1455+
return k8sAffinity
14441456
}
14451457

14461458
func setKubeToleration(timeOutSec int64) []k8sv1.Toleration {

pkg/pillar/types/domainmgrtypes.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ type DomainConfig struct {
4646
// if this node is the DNiD of the App
4747
IsDNidNode bool
4848

49+
// Node Affinity for cluster IsDesignatedNodeID
50+
AffinityType Affinity
51+
4952
// XXX: to be deprecated, use CipherBlockStatus instead
5053
CloudInitUserData *string `json:"pubsub-large-CloudInitUserData"` // base64-encoded
5154

pkg/pillar/types/zedmanagertypes.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ type AppInstanceConfig struct {
168168
// Am I Cluster Designated Node Id for this app
169169
IsDesignatedNodeID bool
170170

171+
// Node Affinity for cluster IsDesignatedNodeID
172+
AffinityType Affinity
173+
171174
// AppRuntimeType specifies the runtime type of the application
172175
DeploymentType AppRuntimeType
173176

@@ -558,3 +561,15 @@ func GetSnapshotInstanceStatusFile(snapshotID string) string {
558561
func GetSnapshotAppInstanceConfigFile(snapshotID string) string {
559562
return filepath.Join(GetSnapshotDir(snapshotID), SnapshotAppInstanceConfigFilename)
560563
}
564+
565+
// Affinity - Cluster App Instance Node Affinity
566+
type Affinity uint8
567+
568+
const (
569+
// PreferredDuringScheduling - designated_node_id is preferred for placement but
570+
// if the node is unhealthy, app can run on other nodes.
571+
PreferredDuringScheduling Affinity = iota
572+
// RequiredDuringScheduling - designated_node_id is required to be healthy for
573+
// app instance to boot, app will not failover to other nodes.
574+
RequiredDuringScheduling
575+
)

0 commit comments

Comments
 (0)