Skip to content

Commit 6d7758e

Browse files
authored
Merge pull request #878 from payall4u/feature/pod-initializer
Optimize qos initializer.
2 parents 98ac477 + 74132af commit 6d7758e

9 files changed

Lines changed: 237 additions & 43 deletions

File tree

pkg/ensurance/analyzer/analyzer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
ecache "github.com/gocrane/crane/pkg/ensurance/cache"
2828
"github.com/gocrane/crane/pkg/ensurance/executor"
2929
"github.com/gocrane/crane/pkg/ensurance/executor/podinfo"
30+
"github.com/gocrane/crane/pkg/ensurance/util"
3031
"github.com/gocrane/crane/pkg/known"
3132
"github.com/gocrane/crane/pkg/metrics"
3233
"github.com/gocrane/crane/pkg/utils"
@@ -490,7 +491,7 @@ func (s *AnomalyAnalyzer) filterPodQOSMatches(pods []*v1.Pod, actionName string)
490491
}
491492
for _, qos := range podQOSList {
492493
for _, pod := range pods {
493-
if !match(pod, qos) {
494+
if !util.MatchPodAndPodQOS(pod, qos) {
494495
klog.V(4).Infof("Pod %s/%s does not match PodQOS %s", pod.Namespace, pod.Name, qos.Name)
495496
continue
496497

pkg/ensurance/collector/cadvisor/cadvisor_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func NewCadvisorManager(cgroupDriver string) Manager {
7878
sysfs := csysfs.NewRealSysFs()
7979
maxHousekeepingConfig := cmanager.HouskeepingConfig{Interval: &maxHousekeepingInterval, AllowDynamic: &allowDynamic}
8080

81-
m, err := cmanager.New(memCache, sysfs, maxHousekeepingConfig, includedMetrics, http.DefaultClient, []string{"/" + utils.CgroupKubePods}, nil /* containerEnvMetadataWhiteList */, "" /* perfEventsFile */, time.Duration(0) /*resctrlInterval*/)
81+
m, err := cmanager.New(memCache, sysfs, maxHousekeepingConfig, includedMetrics, http.DefaultClient, []string{"/" + utils.CgroupKubePods}, nil /* containerEnvMetadataWhiteList */, "" /* perfEventsFile */, time.Duration(0) /*resctrlInterval*/)
8282
if err != nil {
8383
klog.Errorf("Failed to create cadvisor manager start: %v", err)
8484
return nil
Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
package analyzer
1+
package util
22

33
import (
44
"fmt"
55
"reflect"
6+
"sort"
67
"strconv"
78
"strings"
89

@@ -69,7 +70,41 @@ func labelMatch(labelSelector metav1.LabelSelector, matchLabels map[string]strin
6970
return true
7071
}
7172

72-
func match(pod *v1.Pod, podQOS *ensuranceapi.PodQOS) bool {
73+
func sortQOSSlice(qosSlice []*ensuranceapi.PodQOS) {
74+
sort.Slice(qosSlice, func(i, j int) bool {
75+
if len(qosSlice[i].Spec.LabelSelector.MatchLabels) != len(qosSlice[j].Spec.LabelSelector.MatchLabels) {
76+
return len(qosSlice[i].Spec.LabelSelector.MatchLabels) > len(qosSlice[j].Spec.LabelSelector.MatchLabels)
77+
}
78+
79+
if qosSlice[i].Spec.ScopeSelector == nil && qosSlice[j].Spec.ScopeSelector == nil {
80+
return true
81+
}
82+
83+
if qosSlice[i].Spec.ScopeSelector == nil {
84+
return false
85+
}
86+
87+
if qosSlice[j].Spec.ScopeSelector == nil {
88+
return true
89+
}
90+
91+
return len(qosSlice[i].Spec.ScopeSelector.MatchExpressions) > len(qosSlice[j].Spec.ScopeSelector.MatchExpressions)
92+
})
93+
}
94+
95+
func MatchPodAndPodQOSSlice(pod *v1.Pod, qosSlice []*ensuranceapi.PodQOS) (res *ensuranceapi.PodQOS) {
96+
newSlice := make([]*ensuranceapi.PodQOS, len(qosSlice), len(qosSlice))
97+
copy(newSlice, qosSlice)
98+
sortQOSSlice(newSlice)
99+
for _, qos := range newSlice {
100+
if MatchPodAndPodQOS(pod, qos) {
101+
return qos
102+
}
103+
}
104+
return nil
105+
}
106+
107+
func MatchPodAndPodQOS(pod *v1.Pod, podQOS *ensuranceapi.PodQOS) bool {
73108

74109
if podQOS.Spec.ScopeSelector == nil &&
75110
podQOS.Spec.LabelSelector.MatchLabels == nil &&

pkg/providers/grpc/pb/provider.pb.go

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/providers/grpc/pb/provider_grpc.pb.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/webhooks/pod/mutating.go

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,33 @@ import (
44
"context"
55
"fmt"
66

7+
"github.com/pkg/errors"
78
corev1 "k8s.io/api/core/v1"
89
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
910
"k8s.io/apimachinery/pkg/labels"
1011
"k8s.io/apimachinery/pkg/runtime"
1112
"k8s.io/klog/v2"
1213

14+
"github.com/gocrane/api/ensurance/v1alpha1"
15+
1316
"github.com/gocrane/crane/pkg/ensurance/config"
17+
"github.com/gocrane/crane/pkg/ensurance/util"
1418
)
1519

1620
var (
1721
SystemNamespaces = map[string]interface{}{"kube-system": nil, "crane-system": nil}
1822
)
1923

2024
type MutatingAdmission struct {
21-
Config *config.QOSConfig
25+
Config *config.QOSConfig
26+
listPodQOS func() ([]*v1alpha1.PodQOS, error)
27+
}
28+
29+
func NewMutatingAdmission(config *config.QOSConfig, listPodQOS func() ([]*v1alpha1.PodQOS, error)) *MutatingAdmission {
30+
return &MutatingAdmission{
31+
Config: config,
32+
listPodQOS: listPodQOS,
33+
}
2234
}
2335

2436
// Default implements webhook.Defaulter so a webhook will be registered for the type
@@ -28,36 +40,77 @@ func (m *MutatingAdmission) Default(ctx context.Context, obj runtime.Object) err
2840
return fmt.Errorf("expected a Pod but got a %T", obj)
2941
}
3042

31-
klog.Infof("Into Pod injection %s/%s", pod.Namespace, pod.Name)
43+
klog.V(2).Infof("Mutating started for pod %s/%s", pod.Namespace, pod.Name)
3244

3345
if _, exist := SystemNamespaces[pod.Namespace]; exist {
3446
return nil
3547
}
3648

37-
if m.Config == nil || !m.Config.QOSInitializer.Enable {
49+
if !m.available() {
3850
return nil
3951
}
4052

41-
if pod.Labels == nil {
53+
ls, err := metav1.LabelSelectorAsSelector(m.Config.QOSInitializer.Selector)
54+
if err != nil {
55+
return err
56+
}
57+
58+
if !ls.Matches(labels.Set(pod.Labels)) {
59+
klog.V(2).Infof("Injection skipped: webhook is not interested in the pod")
4260
return nil
4361
}
4462

45-
ls, err := metav1.LabelSelectorAsSelector(m.Config.QOSInitializer.Selector)
63+
qosSlice, err := m.listPodQOS()
4664
if err != nil {
47-
return err
65+
return errors.WithMessage(err, "list PodQOS failed")
66+
}
67+
68+
/****************************************************************
69+
* Check whether the pod has a low CPUPriority (CPUPriority > 0)
70+
****************************************************************/
71+
qos := util.MatchPodAndPodQOSSlice(pod, qosSlice)
72+
if qos == nil {
73+
klog.V(2).Infof("Injection skipped: no podqos matched")
74+
return nil
75+
}
76+
77+
if qos.Spec.ResourceQOS.CPUQOS == nil ||
78+
qos.Spec.ResourceQOS.CPUQOS.CPUPriority == nil ||
79+
*qos.Spec.ResourceQOS.CPUQOS.CPUPriority == 0 {
80+
klog.V(2).Infof("Injection skipped: not a low CPUPriority pod, qos %s", qos.Name)
81+
return nil
4882
}
4983

50-
if ls.Matches(labels.Set(pod.Labels)) {
51-
if m.Config.QOSInitializer.InitContainerTemplate != nil {
52-
pod.Spec.InitContainers = append(pod.Spec.InitContainers, *m.Config.QOSInitializer.InitContainerTemplate)
84+
for _, container := range pod.Spec.InitContainers {
85+
if container.Name == m.Config.QOSInitializer.InitContainerTemplate.Name {
86+
klog.V(2).Infof("Injection skipped: pod has initializerContainer already")
87+
return nil
5388
}
89+
}
5490

55-
if m.Config.QOSInitializer.VolumeTemplate != nil {
56-
pod.Spec.Volumes = append(pod.Spec.Volumes, *m.Config.QOSInitializer.VolumeTemplate)
91+
for _, volume := range pod.Spec.Volumes {
92+
if volume.Name == m.Config.QOSInitializer.VolumeTemplate.Name {
93+
klog.V(2).Infof("Injection skipped: pod has initializerVolume already")
94+
return nil
5795
}
96+
}
97+
98+
if m.Config.QOSInitializer.InitContainerTemplate != nil {
99+
pod.Spec.InitContainers = append(pod.Spec.InitContainers, *m.Config.QOSInitializer.InitContainerTemplate)
100+
}
58101

59-
klog.Infof("Injected QOSInitializer for Pod %s/%s", pod.Namespace, pod.Name)
102+
if m.Config.QOSInitializer.VolumeTemplate != nil {
103+
pod.Spec.Volumes = append(pod.Spec.Volumes, *m.Config.QOSInitializer.VolumeTemplate)
60104
}
61105

106+
klog.V(2).Infof("Mutating completed for pod %s/%s", pod.Namespace, pod.Name)
107+
62108
return nil
63109
}
110+
111+
func (m *MutatingAdmission) available() bool {
112+
return m.Config != nil &&
113+
m.Config.QOSInitializer.Enable &&
114+
m.Config.QOSInitializer.InitContainerTemplate != nil &&
115+
m.Config.QOSInitializer.VolumeTemplate != nil
116+
}

pkg/webhooks/pod/mutating_test.go

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import (
44
"context"
55
"testing"
66

7+
"github.com/stretchr/testify/assert"
78
v1 "k8s.io/api/core/v1"
89
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/utils/pointer"
911
"sigs.k8s.io/yaml"
1012

13+
"github.com/gocrane/api/ensurance/v1alpha1"
14+
1115
"github.com/gocrane/crane/pkg/ensurance/config"
1216
)
1317

@@ -20,23 +24,102 @@ func TestDefaultingPodQOSInitializer(t *testing.T) {
2024
t.Errorf("unmarshal config failed:%v", err)
2125
}
2226
m := MutatingAdmission{
23-
Config: config,
27+
Config: config,
28+
listPodQOS: MockListPodQOSFunc,
29+
}
30+
31+
type Case struct {
32+
Pod *v1.Pod
33+
Inject bool
34+
}
35+
36+
for _, tc := range []Case{
37+
{Pod: MockPod("offline", "offline", "enable", "app", "nginx"), Inject: true},
38+
{Pod: MockPod("offline-not-interested", "offline", "enable"), Inject: false},
39+
{Pod: MockPod("online", "offline", "disable", "app", "nginx"), Inject: false},
40+
{Pod: MockPod("online-not-interested", "offline", "disable"), Inject: false},
41+
{Pod: MockPod("default"), Inject: false},
42+
} {
43+
assert.NoError(t, m.Default(context.Background(), tc.Pod))
44+
t.Log(tc.Pod.Name)
45+
assert.Equal(t, len(tc.Pod.Spec.InitContainers) == 1, tc.Inject)
46+
assert.Equal(t, len(tc.Pod.Spec.Volumes) == 1, tc.Inject)
47+
}
48+
}
49+
50+
func TestPrecheck(t *testing.T) {
51+
configYaml := "apiVersion: ensurance.crane.io/v1alpha1\nkind: QOSConfig\nqosInitializer:\n enable: true\n selector: \n matchLabels:\n app: nginx\n"
52+
53+
config := &config.QOSConfig{}
54+
err := yaml.Unmarshal([]byte(configYaml), config)
55+
if err != nil {
56+
t.Errorf("unmarshal config failed:%v", err)
57+
}
58+
m := MutatingAdmission{
59+
Config: config,
60+
listPodQOS: MockListPodQOSFunc,
2461
}
62+
assert.False(t, m.available())
63+
}
2564

65+
func MockListPodQOSFunc() ([]*v1alpha1.PodQOS, error) {
66+
return []*v1alpha1.PodQOS{
67+
{
68+
TypeMeta: metav1.TypeMeta{},
69+
ObjectMeta: metav1.ObjectMeta{},
70+
Spec: v1alpha1.PodQOSSpec{
71+
LabelSelector: metav1.LabelSelector{
72+
MatchLabels: map[string]string{"offline": "enable"},
73+
},
74+
ResourceQOS: v1alpha1.ResourceQOS{
75+
CPUQOS: &v1alpha1.CPUQOS{
76+
CPUPriority: pointer.Int32(7),
77+
},
78+
},
79+
},
80+
}, {
81+
TypeMeta: metav1.TypeMeta{},
82+
ObjectMeta: metav1.ObjectMeta{},
83+
Spec: v1alpha1.PodQOSSpec{
84+
LabelSelector: metav1.LabelSelector{
85+
MatchLabels: map[string]string{"offline": "disable"},
86+
},
87+
ResourceQOS: v1alpha1.ResourceQOS{
88+
CPUQOS: &v1alpha1.CPUQOS{
89+
CPUPriority: pointer.Int32(0),
90+
},
91+
},
92+
},
93+
}, {
94+
TypeMeta: metav1.TypeMeta{},
95+
ObjectMeta: metav1.ObjectMeta{},
96+
Spec: v1alpha1.PodQOSSpec{
97+
ResourceQOS: v1alpha1.ResourceQOS{
98+
CPUQOS: &v1alpha1.CPUQOS{
99+
CPUPriority: pointer.Int32(7),
100+
},
101+
},
102+
},
103+
},
104+
}, nil
105+
}
106+
107+
func MockPod(name string, labels ...string) *v1.Pod {
26108
pod := &v1.Pod{
27109
ObjectMeta: metav1.ObjectMeta{
28-
Name: "pod1",
29-
Labels: map[string]string{
30-
"app": "nginx",
31-
"type": "offline",
32-
},
110+
Name: name,
111+
Labels: nil,
33112
},
34113
}
35-
err = m.Default(context.TODO(), pod)
36-
if err != nil {
37-
t.Fatalf("inject pod failed: %v", err)
114+
115+
if len(labels) < 2 {
116+
return pod
38117
}
39-
if len(pod.Spec.InitContainers) == 0 {
40-
t.Fatalf("should inject containers")
118+
119+
labelmap := map[string]string{}
120+
for i := 0; i < len(labels)-1; i += 2 {
121+
labelmap[labels[i]] = labels[i+1]
41122
}
123+
pod.Labels = labelmap
124+
return pod
42125
}

0 commit comments

Comments
 (0)