Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
Add soft pod anti affinity (#1115)
Browse files Browse the repository at this point in the history
  • Loading branch information
d0x2f authored Feb 21, 2020
1 parent ac91c1e commit 3eff84c
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 1 deletion.
6 changes: 5 additions & 1 deletion docs/advanced-function-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ The fields that a Function specification can contain are:

Apart from the basic parameters, it is possible to add the specification of a `Deployment`, a `Service` or an `Horizontal Pod Autoscaler` that Kubeless will use to generate them.

## Pod Anti Affinity

By default, a kubless generated `Deployment` will include a soft pod anti-affinity rule that will signal to kubernetes that it should try to deploy pods to different nodes. This behaviour can be overridden using a deployment template.

## Deploying large functions

As any Kubernetes object, function objects have a maximum size of 1.5MiB (due to the [maximum size](https://github.com/etcd-io/etcd/blob/master/Documentation/dev-guide/limit.md#request-size-limit) of an etcd entry). Because of that, it's not possible to specify in the `function` field of the YAML content that surpasses that size. To workaround this issue it's possible to specify an URL in the `function` field. This file will be downloaded at build time (extracted if necessary) and the checksum will be checked. Doing this we avoid any limitation regarding the file size. It's also possible to include the function dependencies in this file and skip the dependency installation step. Note that since the file will be downloaded in a pod the URL should be accessible from within the cluster:
Expand Down Expand Up @@ -91,7 +95,7 @@ spec:
```

Would create a function with the environment variable `FOO`, using CPU and memory limits and mounting the secret `my-secret` as a volume. Note that you can also specify a default template for a Deployment spec in the [controller configuration](/docs/function-controller-configuration).
The resource configuration in `initContainers` will be applied to all of the initial containers in the target deployment (like `provision`, `compile` etc.)
The resource configuration in `initContainers` will be applied to all of the initial containers in the target deployment (like `provision`, `compile` etc.)


## Custom Service
Expand Down
22 changes: 22 additions & 0 deletions pkg/utils/kubelessutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,28 @@ func EnsureFuncDeployment(client kubernetes.Interface, funcObj *kubelessApi.Func
}
}

// Add soft pod anti affinity
if dpm.Spec.Template.Spec.Affinity == nil {
dpm.Spec.Template.Spec.Affinity = &v1.Affinity{
PodAntiAffinity: &v1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
{
Weight: 100,
PodAffinityTerm: v1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"created-by": "kubeless",
"function": funcObj.ObjectMeta.Name,
},
},
TopologyKey: "kubernetes.io/hostname",
},
},
},
},
}
}

_, err = client.AppsV1().Deployments(funcObj.ObjectMeta.Namespace).Create(dpm)
if err != nil && k8sErrors.IsAlreadyExists(err) {
// In case the Deployment already exists we should update
Expand Down
48 changes: 48 additions & 0 deletions pkg/utils/kubelessutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,10 +636,34 @@ func TestEnsureDeployment(t *testing.T) {
},
},
}

if !reflect.DeepEqual(dpm.Spec.Template.Spec.Containers[0], expectedContainer) {
t.Errorf("Unexpected container definition. Received:\n %+v\nExpecting:\n %+v", dpm.Spec.Template.Spec.Containers[0], expectedContainer)
}

expectedAffinity := &v1.Affinity{
PodAntiAffinity: &v1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
{
Weight: 100,
PodAffinityTerm: v1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"created-by": "kubeless",
"function": f1Name,
},
},
TopologyKey: "kubernetes.io/hostname",
},
},
},
},
}

if !reflect.DeepEqual(dpm.Spec.Template.Spec.Affinity, expectedAffinity) {
t.Errorf("Unexpected pod affinity definition. Received:\n %+v\nExpecting:\n %+v", dpm.Spec.Template.Spec.Affinity, expectedAffinity)
}

secrets := dpm.Spec.Template.Spec.ImagePullSecrets
if secrets[0].Name != "creds" && secrets[1].Name != "p1" && secrets[2].Name != "p2" {
t.Errorf("Expected first secret to be 'p1' but found %v and second secret to be 'p2' and found %v", secrets[0], secrets[1])
Expand Down Expand Up @@ -847,6 +871,30 @@ func TestDeploymentWithVolumes(t *testing.T) {
}
}

func TestEnsureDeploymentWithAffinityOverridden(t *testing.T) {
funcName := "func"
clientset, or, ns, lr := prepareDeploymentTest(funcName)
// If the Image has been already provided it should not resolve it
f3 := getDefaultFunc(funcName, ns)
f3.Spec.Deployment.Spec.Template.Spec.Affinity = &v1.Affinity{}
err := EnsureFuncDeployment(clientset, f3, or, lr, "", "unzip", []v1.LocalObjectReference{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
dpm, err := clientset.AppsV1().Deployments(ns).Get(funcName, metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
expectedAffinity := &v1.Affinity{NodeAffinity: nil, PodAffinity: nil, PodAntiAffinity: nil}
if *dpm.Spec.Template.Spec.Affinity != *expectedAffinity {
t.Errorf(
"Unexpected Affinity Definition:\nExpecting: %+v\nReceived: %+v",
expectedAffinity,
dpm.Spec.Template.Spec.Affinity,
)
}
}

func doesNotContain(envs []v1.EnvVar, env v1.EnvVar) bool {
for _, e := range envs {
if e == env {
Expand Down

0 comments on commit 3eff84c

Please sign in to comment.