Skip to content

Commit 0304299

Browse files
author
Lars Maier
authored
Merge pull request #368 from arangodb/feature/pod-priority
Feature/pod priority
2 parents fd91700 + 48f774c commit 0304299

File tree

10 files changed

+179
-7
lines changed

10 files changed

+179
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [0.3.10](---) (XXXX-XX-XX)
44
- Added Pod Disruption Budgets for all server groups in production mode.
5+
- Added Priority Class Name to be specified per server group.
56

67
## [0.3.9](https://github.com/arangodb/kube-arangodb/tree/0.3.9) (2019-02-28)
78
[Full Changelog](https://github.com/arangodb/kube-arangodb/compare/0.3.8...0.3.9)

docs/Manual/Deployment/Kubernetes/DeploymentResource.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ for each server of this group.
413413
This setting is not available for group `coordinators`, `syncmasters` & `syncworkers`
414414
because servers in these groups do not need persistent storage.
415415

416+
### `spec.<group>.priorityClassName: string`
417+
418+
Priority class name for pods of this group. Will be forwarded to the pod spec. [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/)
419+
416420
### `spec.<group>.probes.livenessProbeDisabled: bool`
417421

418422
If set to true, the operator does not generate a liveness probe for new pods belonging to this group.

manifests/templates/test/rbac.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ rules:
1515
- apiGroups: ["apps"]
1616
resources: ["daemonsets", "deployments"]
1717
verbs: ["*"]
18+
- apiGroups: ["scheduling.k8s.io"]
19+
resources: ["priorityclasses"]
20+
verbs: ["*"]
1821

1922
---
2023

pkg/apis/deployment/v1alpha/deployment_status_members.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,23 @@ func (ds DeploymentStatusMembers) AllMembersReady(mode DeploymentMode, syncEnabl
243243
return false
244244
}
245245
}
246+
247+
// MembersOfGroup returns the member list of the given group
248+
func (ds DeploymentStatusMembers) MembersOfGroup(group ServerGroup) MemberStatusList {
249+
switch group {
250+
case ServerGroupSingle:
251+
return ds.Single
252+
case ServerGroupAgents:
253+
return ds.Agents
254+
case ServerGroupDBServers:
255+
return ds.DBServers
256+
case ServerGroupCoordinators:
257+
return ds.Coordinators
258+
case ServerGroupSyncMasters:
259+
return ds.SyncMasters
260+
case ServerGroupSyncWorkers:
261+
return ds.SyncWorkers
262+
default:
263+
return MemberStatusList{}
264+
}
265+
}

pkg/apis/deployment/v1alpha/server_group_spec.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type ServerGroupSpec struct {
5858
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
5959
// Probes specifies additional behaviour for probes
6060
Probes *ServerGroupProbesSpec `json:"probes,omitempty"`
61+
// PriorityClassName specifies a priority class name
62+
PriorityClassName string `json:"priorityClassName,omitempty"`
6163
}
6264

6365
// ServerGroupProbesSpec contains specification for probes for pods of the server group

pkg/deployment/images.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (ib *imagesBuilder) fetchArangoDBImageIDAndVersion(ctx context.Context, ima
198198
}
199199
}
200200
if err := k8sutil.CreateArangodPod(ib.KubeCli, true, ib.APIObject, role, id, podName, "", image, "", "", ib.Spec.GetImagePullPolicy(), "", false, terminationGracePeriod, args, env, nil, nil, nil,
201-
tolerations, serviceAccountName, "", "", "", nil, v1.ResourceRequirements{}); err != nil {
201+
tolerations, serviceAccountName, "", "", "", nil, "", v1.ResourceRequirements{}); err != nil {
202202
log.Debug().Err(err).Msg("Failed to create image ID pod")
203203
return true, maskAny(err)
204204
}

pkg/deployment/reconcile/plan_builder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,15 @@ func podNeedsRotation(log zerolog.Logger, p v1.Pod, apiObject metav1.Object, spe
345345
return true, "ServiceAccountName changed"
346346
}
347347

348+
// Check priorities
349+
if groupSpec.PriorityClassName != p.Spec.PriorityClassName {
350+
return true, "Pod priority changed"
351+
}
352+
348353
// Check resource requirements
349354
if resourcesRequireRotation(spec.GetServerGroupSpec(group).Resources, k8sutil.GetArangoDBContainerFromPod(&p).Resources) {
350355
return true, "Resource Requirements changed"
356+
351357
}
352358

353359
return false, ""

pkg/deployment/resources/pod_creator.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ func (r *Resources) createPodForMember(spec api.DeploymentSpec, memberID string,
607607
finalizers := r.createPodFinalizers(group)
608608
if err := k8sutil.CreateArangodPod(kubecli, spec.IsDevelopment(), apiObject, role, m.ID, m.PodName, m.PersistentVolumeClaimName, imageInfo.ImageID, lifecycleImage, alpineImage, spec.GetImagePullPolicy(),
609609
engine, requireUUID, terminationGracePeriod, args, env, finalizers, livenessProbe, readinessProbe, tolerations, serviceAccountName, tlsKeyfileSecretName, rocksdbEncryptionSecretName,
610-
clusterJWTSecretName, groupSpec.GetNodeSelector(), groupSpec.Resources); err != nil {
610+
clusterJWTSecretName, groupSpec.GetNodeSelector(), groupSpec.PriorityClassName, groupSpec.Resources); err != nil {
611611
return maskAny(err)
612612
}
613613
log.Debug().Str("pod-name", m.PodName).Msg("Created pod")
@@ -689,7 +689,8 @@ func (r *Resources) createPodForMember(spec api.DeploymentSpec, memberID string,
689689
affinityWithRole = api.ServerGroupDBServers.AsRole()
690690
}
691691
if err := k8sutil.CreateArangoSyncPod(kubecli, spec.IsDevelopment(), apiObject, role, m.ID, m.PodName, imageID, lifecycleImage, spec.GetImagePullPolicy(), terminationGracePeriod, args, env,
692-
livenessProbe, tolerations, serviceAccountName, tlsKeyfileSecretName, clientAuthCASecretName, masterJWTSecretName, clusterJWTSecretName, affinityWithRole, groupSpec.GetNodeSelector()); err != nil {
692+
livenessProbe, tolerations, serviceAccountName, tlsKeyfileSecretName, clientAuthCASecretName, masterJWTSecretName, clusterJWTSecretName, affinityWithRole, groupSpec.GetNodeSelector(),
693+
groupSpec.PriorityClassName, groupSpec.Resources); err != nil {
693694
return maskAny(err)
694695
}
695696
log.Debug().Str("pod-name", m.PodName).Msg("Created pod")

pkg/util/k8sutil/pods.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func arangodContainer(image string, imagePullPolicy v1.PullPolicy, args []string
321321

322322
// arangosyncContainer creates a container configured to run `arangosync`.
323323
func arangosyncContainer(image string, imagePullPolicy v1.PullPolicy, args []string, env map[string]EnvValue, livenessProbe *HTTPProbeConfig,
324-
lifecycle *v1.Lifecycle, lifecycleEnvVars []v1.EnvVar) v1.Container {
324+
lifecycle *v1.Lifecycle, lifecycleEnvVars []v1.EnvVar, resources v1.ResourceRequirements) v1.Container {
325325
c := v1.Container{
326326
Command: append([]string{"/usr/sbin/arangosync"}, args...),
327327
Name: ServerContainerName,
@@ -335,6 +335,7 @@ func arangosyncContainer(image string, imagePullPolicy v1.PullPolicy, args []str
335335
Protocol: v1.ProtocolTCP,
336336
},
337337
},
338+
Resources: filterStorageResourceRequirement(resources), // Storage is handled via pvcs
338339
}
339340
for k, v := range env {
340341
c.Env = append(c.Env, v.CreateEnvVar(k))
@@ -456,7 +457,8 @@ func CreateArangodPod(kubecli kubernetes.Interface, developmentMode bool, deploy
456457
args []string, env map[string]EnvValue, finalizers []string,
457458
livenessProbe *HTTPProbeConfig, readinessProbe *HTTPProbeConfig, tolerations []v1.Toleration, serviceAccountName string,
458459
tlsKeyfileSecretName, rocksdbEncryptionSecretName string, clusterJWTSecretName string, nodeSelector map[string]string,
459-
resources v1.ResourceRequirements) error {
460+
podPriorityClassName string, resources v1.ResourceRequirements) error {
461+
460462
// Prepare basic pod
461463
p := newPod(deployment.GetName(), deployment.GetNamespace(), role, id, podName, finalizers, tolerations, serviceAccountName, nodeSelector)
462464
terminationGracePeriodSeconds := int64(math.Ceil(terminationGracePeriod.Seconds()))
@@ -491,6 +493,9 @@ func CreateArangodPod(kubecli kubernetes.Interface, developmentMode bool, deploy
491493
}
492494
p.Spec.Containers = append(p.Spec.Containers, c)
493495

496+
// Add priorityClassName
497+
p.Spec.PriorityClassName = podPriorityClassName
498+
494499
// Add UUID init container
495500
if alpineImage != "" {
496501
p.Spec.InitContainers = append(p.Spec.InitContainers, arangodInitContainer("uuid", id, engine, alpineImage, requireUUID))
@@ -575,7 +580,8 @@ func CreateArangodPod(kubecli kubernetes.Interface, developmentMode bool, deploy
575580
// If another error occurs, that error is returned.
576581
func CreateArangoSyncPod(kubecli kubernetes.Interface, developmentMode bool, deployment APIObject, role, id, podName, image, lifecycleImage string, imagePullPolicy v1.PullPolicy,
577582
terminationGracePeriod time.Duration, args []string, env map[string]EnvValue, livenessProbe *HTTPProbeConfig, tolerations []v1.Toleration, serviceAccountName string,
578-
tlsKeyfileSecretName, clientAuthCASecretName, masterJWTSecretName, clusterJWTSecretName, affinityWithRole string, nodeSelector map[string]string) error {
583+
tlsKeyfileSecretName, clientAuthCASecretName, masterJWTSecretName, clusterJWTSecretName, affinityWithRole string, nodeSelector map[string]string,
584+
podPriorityClassName string, resources v1.ResourceRequirements) error {
579585
// Prepare basic pod
580586
p := newPod(deployment.GetName(), deployment.GetNamespace(), role, id, podName, nil, tolerations, serviceAccountName, nodeSelector)
581587
terminationGracePeriodSeconds := int64(math.Ceil(terminationGracePeriod.Seconds()))
@@ -601,7 +607,7 @@ func CreateArangoSyncPod(kubecli kubernetes.Interface, developmentMode bool, dep
601607
p.Spec.Volumes = append(p.Spec.Volumes, lifecycleVolumes...)
602608

603609
// Add arangosync container
604-
c := arangosyncContainer(image, imagePullPolicy, args, env, livenessProbe, lifecycle, lifecycleEnvVars)
610+
c := arangosyncContainer(image, imagePullPolicy, args, env, livenessProbe, lifecycle, lifecycleEnvVars, resources)
605611
if tlsKeyfileSecretName != "" {
606612
c.VolumeMounts = append(c.VolumeMounts, tlsKeyfileVolumeMounts()...)
607613
}
@@ -616,6 +622,9 @@ func CreateArangoSyncPod(kubecli kubernetes.Interface, developmentMode bool, dep
616622
}
617623
p.Spec.Containers = append(p.Spec.Containers, c)
618624

625+
// Add priorityClassName
626+
p.Spec.PriorityClassName = podPriorityClassName
627+
619628
// TLS keyfile secret mount (if any)
620629
if tlsKeyfileSecretName != "" {
621630
vol := v1.Volume{

tests/pc_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package tests
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha"
9+
"github.com/arangodb/kube-arangodb/pkg/client"
10+
"github.com/arangodb/kube-arangodb/pkg/util"
11+
"github.com/arangodb/kube-arangodb/pkg/util/retry"
12+
"github.com/dchest/uniuri"
13+
v1beta1 "k8s.io/api/scheduling/v1beta1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/client-go/kubernetes"
16+
17+
"github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
18+
)
19+
20+
func waitForPriorityOfServerGroup(kube kubernetes.Interface, c versioned.Interface, depl, ns string, group api.ServerGroup, priority int32) error {
21+
return retry.Retry(func() error {
22+
23+
apiObject, err := c.DatabaseV1alpha().ArangoDeployments(ns).Get(depl, metav1.GetOptions{})
24+
if err != nil {
25+
return err
26+
}
27+
28+
for _, m := range apiObject.Status.Members.MembersOfGroup(group) {
29+
pod, err := kube.CoreV1().Pods(apiObject.Namespace).Get(m.PodName, metav1.GetOptions{})
30+
if err != nil {
31+
return err
32+
}
33+
34+
if pod.Spec.Priority == nil {
35+
return fmt.Errorf("No pod priority set")
36+
}
37+
38+
if *pod.Spec.Priority != priority {
39+
return fmt.Errorf("Wrong pod priority, expected %d, found %d", priority, pod.Spec.Priority)
40+
}
41+
}
42+
43+
return nil
44+
}, 5*time.Minute)
45+
}
46+
47+
// TestPriorityClasses creates a PriorityClass and associates coordinators with that class.
48+
// Then check if the pods have the desired priority. Then change the class and check that the pods are rotated.
49+
func TestPriorityClasses(t *testing.T) {
50+
longOrSkip(t)
51+
c := client.MustNewInCluster()
52+
kubecli := mustNewKubeClient(t)
53+
ns := getNamespace(t)
54+
55+
lowClassName := "test-low-class"
56+
lowClassValue := int32(1000)
57+
58+
highClassName := "test-high-class"
59+
highClassValue := int32(2000)
60+
61+
// Create two priority classes
62+
if _, err := kubecli.SchedulingV1beta1().PriorityClasses().Create(&v1beta1.PriorityClass{
63+
Value: lowClassValue,
64+
GlobalDefault: false,
65+
Description: "Low priority test class",
66+
ObjectMeta: metav1.ObjectMeta{
67+
Name: lowClassName,
68+
},
69+
}); err != nil {
70+
t.Fatalf("Could not create PC: %v", err)
71+
}
72+
defer kubecli.SchedulingV1beta1().PriorityClasses().Delete(lowClassName, &metav1.DeleteOptions{})
73+
74+
if _, err := kubecli.SchedulingV1beta1().PriorityClasses().Create(&v1beta1.PriorityClass{
75+
Value: highClassValue,
76+
GlobalDefault: false,
77+
Description: "Low priority test class",
78+
ObjectMeta: metav1.ObjectMeta{
79+
Name: highClassName,
80+
},
81+
}); err != nil {
82+
t.Fatalf("Could not create PC: %v", err)
83+
}
84+
defer kubecli.SchedulingV1beta1().PriorityClasses().Delete(highClassName, &metav1.DeleteOptions{})
85+
86+
// Prepare deployment config
87+
depl := newDeployment("test-pc-" + uniuri.NewLen(4))
88+
depl.Spec.Mode = api.NewMode(api.DeploymentModeCluster)
89+
depl.Spec.TLS = api.TLSSpec{CASecretName: util.NewString("None")}
90+
depl.Spec.DBServers.Count = util.NewInt(2)
91+
depl.Spec.Coordinators.Count = util.NewInt(2)
92+
depl.Spec.Coordinators.PriorityClassName = lowClassName
93+
depl.Spec.SetDefaults(depl.GetName()) // this must be last
94+
defer deferedCleanupDeployment(c, depl.GetName(), ns)
95+
96+
// Create deployment
97+
_, err := c.DatabaseV1alpha().ArangoDeployments(ns).Create(depl)
98+
if err != nil {
99+
t.Fatalf("Create deployment failed: %v", err)
100+
}
101+
defer deferedCleanupDeployment(c, depl.GetName(), ns)
102+
103+
// Wait for deployment to be ready
104+
_, err = waitUntilDeployment(c, depl.GetName(), ns, deploymentIsReady())
105+
if err != nil {
106+
t.Fatalf("Deployment not running in time: %v", err)
107+
}
108+
109+
if err := waitForPriorityOfServerGroup(kubecli, c, depl.GetName(), ns, api.ServerGroupCoordinators, lowClassValue); err != nil {
110+
t.Errorf("PDBs not as expected: %v", err)
111+
}
112+
113+
_, err = updateDeployment(c, depl.GetName(), ns, func(spec *api.DeploymentSpec) {
114+
spec.Coordinators.PriorityClassName = highClassName
115+
})
116+
if err != nil {
117+
t.Fatalf("Failed to update deployment: %v", err)
118+
}
119+
120+
// Check if priority class is updated
121+
if err := waitForPriorityOfServerGroup(kubecli, c, depl.GetName(), ns, api.ServerGroupCoordinators, highClassValue); err != nil {
122+
t.Errorf("Priority not as expected: %v", err)
123+
}
124+
125+
removeDeployment(c, depl.GetName(), ns)
126+
}

0 commit comments

Comments
 (0)