Skip to content

Commit 076a4b8

Browse files
aramasehelayoty
andauthored
feat: implement filter and score (#23)
* feat: implement filter and score Signed-off-by: Anish Ramasekar <[email protected]> * feat: use prefilter and prescore extension point for computing Signed-off-by: Anish Ramasekar <[email protected]> * chore: return framework.Success instead of nil Signed-off-by: Anish Ramasekar <[email protected]> * helm: add prescore and prefilter to charts Signed-off-by: Anish Ramasekar <[email protected]> * refactor: make HasMatchingLabels util Signed-off-by: Anish Ramasekar <[email protected]> * fix integration test by use handle.kubeconfig * comment checking pod Co-authored-by: Heba Elayoty <[email protected]>
1 parent b7aafaa commit 076a4b8

File tree

17 files changed

+852
-34
lines changed

17 files changed

+852
-34
lines changed

Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Build the manager binary
2+
FROM golang:1.17 as builder
3+
4+
WORKDIR /workspace
5+
# Copy the Go Modules manifests
6+
COPY go.mod go.mod
7+
COPY go.sum go.sum
8+
# cache deps before building and copying source so that we don't need to re-download as much
9+
# and so that source changes don't invalidate our downloaded layer
10+
RUN go mod download
11+
12+
# Copy the go source
13+
COPY cmd/scheduler/main.go main.go
14+
COPY apis/ apis/
15+
COPY pkg/ pkg/
16+
17+
# Build
18+
ARG TARGETARCH
19+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -o manager main.go
20+
21+
# Use distroless as minimal base image to package the manager binary
22+
# Refer to https://github.com/GoogleContainerTools/distroless for more details
23+
FROM --platform=${TARGETPLATFORM:-linux/amd64} gcr.io/distroless/static:nonroot
24+
WORKDIR /
25+
COPY --from=builder /workspace/manager .
26+
27+
ENTRYPOINT ["/manager"]

Makefile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# TODO(aramase) use the github registry to publish the image
2+
REGISTRY ?= aramase
3+
IMAGE_NAME := placement-policy
4+
IMAGE_VERSION ?= v0.1.0
5+
16
# Directories
27
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
38
BIN_DIR := $(abspath $(ROOT_DIR)/bin)
@@ -118,3 +123,28 @@ integration-test: install-etcd autogen manager manifests
118123
.PHONY: e2e-test
119124
e2e-test:
120125
go test -tags=e2e -v ./test/e2e
126+
127+
## --------------------------------------
128+
## Images
129+
## --------------------------------------
130+
131+
OUTPUT_TYPE ?= type=registry
132+
BUILDX_BUILDER_NAME ?= img-builder
133+
QEMU_VERSION ?= 5.2.0-2
134+
135+
.PHONY: docker-buildx-builder
136+
docker-buildx-builder:
137+
@if ! docker buildx ls | grep $(BUILDX_BUILDER_NAME); then \
138+
docker run --rm --privileged multiarch/qemu-user-static:$(QEMU_VERSION) --reset -p yes; \
139+
docker buildx create --name $(BUILDX_BUILDER_NAME) --use; \
140+
docker buildx inspect $(BUILDX_BUILDER_NAME) --bootstrap; \
141+
fi
142+
143+
.PHONY: docker-build
144+
docker-build: docker-buildx-builder
145+
docker buildx build \
146+
--file Dockerfile \
147+
--output=$(OUTPUT_TYPE) \
148+
--platform="linux/amd64" \
149+
--pull \
150+
--tag $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_VERSION) .

apis/v1alpha1/placementpolicy_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const (
2222
ActionMust Action = "Must"
2323
// ActionMustNot means the pods must not be placed on the node
2424
ActionMustNot Action = "MustNot"
25+
26+
// PlacementPolicyAnnotationKey is the annotation key for placement policy
27+
PlacementPolicyAnnotationKey = "placement-policy.x-k8s.io/policy-name"
28+
// PlacementPolicyPreferenceAnnotationKey is the annotation key for placement policy node preference
29+
PlacementPolicyPreferenceAnnotationKey = "placement-policy.x-k8s.io/node-preference-matching-labels"
2530
)
2631

2732
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

examples/pod.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ metadata:
55
labels:
66
app: nginx
77
spec:
8-
replicas: 5
8+
replicas: 10
99
selector:
1010
matchLabels:
1111
app: nginx
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ metadata:
44
name: pp-one
55
spec:
66
weight: 100
7-
enforcementMode: Strict #BestEffort
7+
enforcementMode: Strict
88
podSelector:
99
matchLabels:
1010
app: nginx

manifest_staging/charts/placement-policy-scheduler-plugins/templates/configmap.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@ data:
1414
profiles:
1515
- schedulerName: placement-policy-plugins-scheduler
1616
plugins:
17+
preScore:
18+
enabled:
19+
- name: placementpolicy
1720
score:
1821
enabled:
1922
- name: placementpolicy
23+
preFilter:
24+
enabled:
25+
- name: placementpolicy
2026
filter:
2127
enabled:
2228
- name: placementpolicy

manifest_staging/deploy/kube-scheduler-configuration.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,15 @@ data:
195195
profiles:
196196
- schedulerName: placement-policy-plugins-scheduler
197197
plugins:
198+
preScore:
199+
enabled:
200+
- name: placementpolicy
198201
score:
199202
enabled:
200203
- name: placementpolicy
204+
preFilter:
205+
enabled:
206+
- name: placementpolicy
201207
filter:
202208
enabled:
203209
- name: placementpolicy
@@ -307,7 +313,7 @@ spec:
307313
- --config=/etc/schedulerconfig/scheduler-config.yaml
308314
- --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
309315
- --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
310-
image: helayoty/placement-policy:v0.1.0
316+
image: aramase/placement-policy:v0.1.0
311317
name: pp-plugins-scheduler
312318
securityContext:
313319
privileged: true
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package core
2+
3+
import (
4+
"context"
5+
"sort"
6+
7+
"github.com/Azure/placement-policy-scheduler-plugins/apis/v1alpha1"
8+
ppclientset "github.com/Azure/placement-policy-scheduler-plugins/pkg/client/clientset/versioned"
9+
ppinformers "github.com/Azure/placement-policy-scheduler-plugins/pkg/client/informers/externalversions/apis/v1alpha1"
10+
pplisters "github.com/Azure/placement-policy-scheduler-plugins/pkg/client/listers/apis/v1alpha1"
11+
"github.com/Azure/placement-policy-scheduler-plugins/pkg/utils"
12+
13+
corev1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/labels"
16+
"k8s.io/client-go/kubernetes"
17+
corelisters "k8s.io/client-go/listers/core/v1"
18+
"k8s.io/kubernetes/pkg/scheduler/framework"
19+
)
20+
21+
// Manager defines the interfaces for PlacementPolicy management.
22+
type Manager interface {
23+
GetPlacementPolicyForPod(context.Context, *corev1.Pod) (*v1alpha1.PlacementPolicy, error)
24+
GetPodsWithLabels(context.Context, map[string]string) ([]*corev1.Pod, error)
25+
AnnotatePod(context.Context, *corev1.Pod, *v1alpha1.PlacementPolicy, bool) (*corev1.Pod, error)
26+
GetPlacementPolicy(context.Context, string, string) (*v1alpha1.PlacementPolicy, error)
27+
}
28+
29+
type PlacementPolicyManager struct {
30+
// client is a clientset for the kube API server.
31+
client kubernetes.Interface
32+
// client is a placementPolicy client
33+
ppClient ppclientset.Interface
34+
// podLister is pod lister
35+
podLister corelisters.PodLister
36+
// snapshotSharedLister is pod shared list
37+
snapshotSharedLister framework.SharedLister
38+
// ppLister is placementPolicy lister
39+
ppLister pplisters.PlacementPolicyLister
40+
}
41+
42+
func NewPlacementPolicyManager(
43+
client kubernetes.Interface,
44+
ppClient ppclientset.Interface,
45+
snapshotSharedLister framework.SharedLister,
46+
ppInformer ppinformers.PlacementPolicyInformer,
47+
podLister corelisters.PodLister) *PlacementPolicyManager {
48+
return &PlacementPolicyManager{
49+
client: client,
50+
ppClient: ppClient,
51+
snapshotSharedLister: snapshotSharedLister,
52+
ppLister: ppInformer.Lister(),
53+
podLister: podLister,
54+
}
55+
}
56+
57+
// GetPlacementPolicyForPod returns the placement policy for the given pod
58+
func (m *PlacementPolicyManager) GetPlacementPolicyForPod(ctx context.Context, pod *corev1.Pod) (*v1alpha1.PlacementPolicy, error) {
59+
ppList, err := m.ppLister.PlacementPolicies(pod.Namespace).List(labels.Everything())
60+
if err != nil {
61+
return nil, err
62+
}
63+
// filter the placement policy list based on the pod's labels
64+
ppList = m.filterPlacementPolicyList(ppList, pod)
65+
if len(ppList) == 0 {
66+
return nil, nil
67+
}
68+
if len(ppList) > 1 {
69+
// if there are multiple placement policies, sort them by weight and return the first one
70+
sort.Sort(sort.Reverse(ByWeight(ppList)))
71+
}
72+
73+
return ppList[0], nil
74+
}
75+
76+
func (m *PlacementPolicyManager) GetPodsWithLabels(ctx context.Context, podLabels map[string]string) ([]*corev1.Pod, error) {
77+
return m.podLister.List(labels.Set(podLabels).AsSelector())
78+
}
79+
80+
// AnnotatePod annotates the pod with the placement policy.
81+
func (m *PlacementPolicyManager) AnnotatePod(ctx context.Context, pod *corev1.Pod, pp *v1alpha1.PlacementPolicy, preferredNodeWithMatchingLabels bool) (*corev1.Pod, error) {
82+
annotations := map[string]string{}
83+
if pod.Annotations != nil {
84+
annotations = pod.Annotations
85+
}
86+
87+
preference := "false"
88+
if preferredNodeWithMatchingLabels {
89+
preference = "true"
90+
}
91+
annotations[v1alpha1.PlacementPolicyAnnotationKey] = pp.Name
92+
annotations[v1alpha1.PlacementPolicyPreferenceAnnotationKey] = preference
93+
pod.Annotations = annotations
94+
return m.client.CoreV1().Pods(pod.Namespace).Update(ctx, pod, metav1.UpdateOptions{})
95+
}
96+
97+
func (m *PlacementPolicyManager) GetPlacementPolicy(ctx context.Context, namespace, name string) (*v1alpha1.PlacementPolicy, error) {
98+
return m.ppLister.PlacementPolicies(namespace).Get(name)
99+
}
100+
101+
func (m *PlacementPolicyManager) filterPlacementPolicyList(ppList []*v1alpha1.PlacementPolicy, pod *corev1.Pod) []*v1alpha1.PlacementPolicy {
102+
var filteredPPList []*v1alpha1.PlacementPolicy
103+
for _, pp := range ppList {
104+
labels := pp.Spec.PodSelector.MatchLabels
105+
if utils.HasMatchingLabels(pod.Labels, labels) {
106+
filteredPPList = append(filteredPPList, pp)
107+
}
108+
}
109+
return filteredPPList
110+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package core
2+
3+
import "github.com/Azure/placement-policy-scheduler-plugins/apis/v1alpha1"
4+
5+
type ByWeight []*v1alpha1.PlacementPolicy
6+
7+
func (a ByWeight) Len() int { return len(a) }
8+
9+
func (a ByWeight) Swap(i, j int) {
10+
a[i], a[j] = a[j], a[i]
11+
}
12+
13+
func (a ByWeight) Less(i, j int) bool {
14+
return a[i].Spec.Weight > a[j].Spec.Weight
15+
}

0 commit comments

Comments
 (0)