Add support for placement preferences docker-compose v3.3+ (#1425)

This commit is contained in:
ichx
2021-11-04 08:28:45 +08:00
committed by GitHub
parent c921643705
commit 8cb1b0599e
12 changed files with 220 additions and 37 deletions
+1
View File
@@ -534,6 +534,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.Spec.Volumes = append(template.Spec.Volumes, volumes...)
}
template.Spec.Affinity = ConfigAffinity(service)
template.Spec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service)
// Configure the HealthCheck
template.Spec.Containers[0].LivenessProbe = configProbe(service.HealthChecks.Liveness)
template.Spec.Containers[0].ReadinessProbe = configProbe(service.HealthChecks.Readiness)
+42 -12
View File
@@ -29,8 +29,6 @@ import (
"strconv"
"strings"
"golang.org/x/tools/godoc/util"
"github.com/fatih/structs"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose"
@@ -40,6 +38,7 @@ import (
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cast"
"golang.org/x/tools/godoc/util"
appsv1 "k8s.io/api/apps/v1"
api "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
@@ -1073,22 +1072,52 @@ func ConfigEnvs(service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]ap
// ConfigAffinity configures the Affinity.
func ConfigAffinity(service kobject.ServiceConfig) *api.Affinity {
var affinity *api.Affinity
// Config constraints
// Convert constraints to requiredDuringSchedulingIgnoredDuringExecution
positiveConstraints := configConstrains(service.Placement.PositiveConstraints, api.NodeSelectorOpIn)
negativeConstraints := configConstrains(service.Placement.NegativeConstraints, api.NodeSelectorOpNotIn)
if len(positiveConstraints) == 0 && len(negativeConstraints) == 0 {
return nil
}
return &api.Affinity{
NodeAffinity: &api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: append(positiveConstraints, negativeConstraints...),
if len(positiveConstraints) != 0 || len(negativeConstraints) != 0 {
affinity = &api.Affinity{
NodeAffinity: &api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: append(positiveConstraints, negativeConstraints...),
},
},
},
},
},
}
}
return affinity
}
// ConfigTopologySpreadConstraints configures the TopologySpreadConstraints.
func ConfigTopologySpreadConstraints(service kobject.ServiceConfig) []api.TopologySpreadConstraint {
preferencesLen := len(service.Placement.Preferences)
constraints := make([]api.TopologySpreadConstraint, 0, preferencesLen)
// Placement preferences are ignored for global services
if service.DeployMode == "global" {
log.Warnf("Ignore placement preferences for global service %s", service.Name)
return constraints
}
for i, p := range service.Placement.Preferences {
constraints = append(constraints, api.TopologySpreadConstraint{
// According to the order of preferences, the MaxSkew decreases in order
// The minimum value is 1
MaxSkew: int32(preferencesLen - i),
TopologyKey: p,
WhenUnsatisfiable: api.ScheduleAnyway,
LabelSelector: &metav1.LabelSelector{
MatchLabels: transformer.ConfigLabels(service.Name),
},
})
}
return constraints
}
func configConstrains(constrains map[string]string, operator api.NodeSelectorOperator) []api.NodeSelectorRequirement {
@@ -1404,6 +1433,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
ResourcesLimits(service),
ResourcesRequests(service),
TerminationGracePeriodSeconds(name, service),
TopologySpreadConstraints(service),
)
if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
@@ -668,6 +668,51 @@ func TestConfigAffinity(t *testing.T) {
}
}
func TestConfigTopologySpreadConstraints(t *testing.T) {
serviceName := "app"
testCases := map[string]struct {
service kobject.ServiceConfig
result []api.TopologySpreadConstraint
}{
"ConfigTopologySpreadConstraint": {
service: kobject.ServiceConfig{
Name: serviceName,
Placement: kobject.Placement{
Preferences: []string{
"zone", "ssd",
},
},
},
result: []api.TopologySpreadConstraint{
{
MaxSkew: 2,
TopologyKey: "zone",
WhenUnsatisfiable: api.ScheduleAnyway,
LabelSelector: &metav1.LabelSelector{
MatchLabels: transformer.ConfigLabels(serviceName),
},
},
{
MaxSkew: 1,
TopologyKey: "ssd",
WhenUnsatisfiable: api.ScheduleAnyway,
LabelSelector: &metav1.LabelSelector{
MatchLabels: transformer.ConfigLabels(serviceName),
},
},
},
},
}
for name, test := range testCases {
t.Log("Test case:", name)
result := ConfigTopologySpreadConstraints(test.service)
if !reflect.DeepEqual(result, test.result) {
t.Errorf("Not expected result for ConfigTopologySpreadConstraints")
}
}
}
func TestMultipleContainersInPod(t *testing.T) {
groupName := "pod_group"
+6
View File
@@ -306,6 +306,12 @@ func ServiceAccountName(serviceAccountName string) PodSpecOption {
}
}
func TopologySpreadConstraints(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
podSpec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service)
}
}
func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
for _, option := range ops {
option(podSpec)