Skip to content

Commit

Permalink
feat: add defaults configurable runtimeClassName
Browse files Browse the repository at this point in the history
allow the setting of Pod RuntimeClassName via defaults
  • Loading branch information
BobyMCbobs committed May 29, 2024
1 parent 4fe029f commit 400da99
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 1 deletion.
17 changes: 16 additions & 1 deletion config/core/configmaps/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ metadata:
app.kubernetes.io/component: controller
app.kubernetes.io/version: devel
annotations:
knative.dev/example-checksum: "5b64ff5c"
knative.dev/example-checksum: "a7391aec"
data:
_example: |
################################
Expand Down Expand Up @@ -104,6 +104,21 @@ data:
# specified and the system default is used.
revision-ephemeral-storage-limit: "750M" # 750 megabytes of storage
# runtime-class-name contains the selector for which runtimeClassName
# is selected to put in a revision.
# By default, it is not set by Knative.
#
# Example:
# runtime-class-name: |
# "":
# selector:
# use-default-runc: "yes"
# kata: {}
# gvisor:
# selector:
# use-gvisor: "please"
runtime-class-name: ""
# container-name-template contains a template for the default
# container name, if none is specified. This field supports
# Go templating and is supplied with the ObjectMeta of the
Expand Down
52 changes: 52 additions & 0 deletions pkg/apis/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"

"knative.dev/pkg/apis"
cm "knative.dev/pkg/configmap"
Expand Down Expand Up @@ -89,6 +90,7 @@ func defaultDefaultsConfig() *Defaults {
ContainerConcurrencyMaxLimit: DefaultMaxRevisionContainerConcurrency,
AllowContainerConcurrencyZero: DefaultAllowContainerConcurrencyZero,
EnableServiceLinks: ptr.Bool(false),
RuntimeClassName: "",
}
}

Expand Down Expand Up @@ -132,6 +134,8 @@ func NewDefaultsConfigFromMap(data map[string]string) (*Defaults, error) {
cm.AsQuantity("revision-cpu-limit", &nc.RevisionCPULimit),
cm.AsQuantity("revision-memory-limit", &nc.RevisionMemoryLimit),
cm.AsQuantity("revision-ephemeral-storage-limit", &nc.RevisionEphemeralStorageLimit),

cm.AsString("runtime-class-name", &nc.RuntimeClassName),
); err != nil {
return nil, err
}
Expand Down Expand Up @@ -216,6 +220,8 @@ type Defaults struct {
RevisionMemoryLimit *resource.Quantity
RevisionEphemeralStorageRequest *resource.Quantity
RevisionEphemeralStorageLimit *resource.Quantity

RuntimeClassName string
}

func containerNameFromTemplate(ctx context.Context, tmpl *ObjectMetaTemplate) string {
Expand All @@ -236,6 +242,52 @@ func (d Defaults) InitContainerName(ctx context.Context) string {
return containerNameFromTemplate(ctx, d.InitContainerNameTemplate)
}

func (d Defaults) PodRuntimeClassName(ctx context.Context) *string {
cfg := map[string]RuntimeClassNameLabelSelector{}
if err := yaml.Unmarshal([]byte(d.RuntimeClassName), &cfg); err != nil {
return nil
}
runtimeClassName := ""
specificity := -1
for k, v := range cfg {
if !v.Matches(apis.ParentMeta(ctx).Labels) || v.specificity() < specificity {
continue
}
if v.specificity() > specificity || strings.Compare(k, runtimeClassName) < 0 {
runtimeClassName = k
specificity = v.specificity()
}
}
if runtimeClassName == "" {
return nil
}
return ptr.String(runtimeClassName)
}

type RuntimeClassNameLabelSelector struct {
Selector map[string]string `json:"selector,omitempty"`
}

func (s *RuntimeClassNameLabelSelector) specificity() int {
if s == nil {
return 0
}
return len(s.Selector)
}

func (s *RuntimeClassNameLabelSelector) Matches(labels map[string]string) bool {
if s == nil {
return true
}
for label, expectedValue := range s.Selector {
value, ok := labels[label]
if !ok || expectedValue != value {
return false
}
}
return true
}

func asTemplate(key string, target **ObjectMetaTemplate) cm.ParseFunc {
return func(data map[string]string) error {
if raw, ok := data[key]; ok {
Expand Down
129 changes: 129 additions & 0 deletions pkg/apis/config/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"
Expand Down Expand Up @@ -237,6 +238,65 @@ func TestDefaultsConfiguration(t *testing.T) {
InitContainerNameTemplate: DefaultInitContainerNameTemplate,
UserContainerNameTemplate: DefaultUserContainerNameTemplate,
},
}, {
name: "runtime class name defaults to nothing",
wantErr: false,
data: map[string]string{},
wantDefaults: &Defaults{
RuntimeClassName: "",
RevisionTimeoutSeconds: DefaultRevisionTimeoutSeconds,
MaxRevisionTimeoutSeconds: DefaultMaxRevisionTimeoutSeconds,
RevisionResponseStartTimeoutSeconds: DefaultRevisionResponseStartTimeoutSeconds,
ContainerConcurrencyMaxLimit: DefaultMaxRevisionContainerConcurrency,
AllowContainerConcurrencyZero: DefaultAllowContainerConcurrencyZero,
EnableServiceLinks: ptr.Bool(false),
InitContainerNameTemplate: DefaultInitContainerNameTemplate,
UserContainerNameTemplate: DefaultUserContainerNameTemplate,
},
}, {
name: "runtime class name with wildcard",
wantErr: false,
wantDefaults: &Defaults{
RuntimeClassName: "gvisor: {}",
RevisionTimeoutSeconds: DefaultRevisionTimeoutSeconds,
RevisionResponseStartTimeoutSeconds: DefaultRevisionResponseStartTimeoutSeconds,
MaxRevisionTimeoutSeconds: DefaultMaxRevisionTimeoutSeconds,
ContainerConcurrencyMaxLimit: DefaultMaxRevisionContainerConcurrency,
AllowContainerConcurrencyZero: DefaultAllowContainerConcurrencyZero,
EnableServiceLinks: ptr.Bool(false),
InitContainerNameTemplate: DefaultInitContainerNameTemplate,
UserContainerNameTemplate: DefaultUserContainerNameTemplate,
},
data: map[string]string{
"runtime-class-name": "gvisor: {}",
},
}, {
name: "runtime class name with wildcard and label selectors",
wantErr: false,
wantDefaults: &Defaults{
RuntimeClassName: `---
gvisor: {}
kata:
selector:
some: value-here
`,
RevisionTimeoutSeconds: DefaultRevisionTimeoutSeconds,
RevisionResponseStartTimeoutSeconds: DefaultRevisionResponseStartTimeoutSeconds,
MaxRevisionTimeoutSeconds: DefaultMaxRevisionTimeoutSeconds,
ContainerConcurrencyMaxLimit: DefaultMaxRevisionContainerConcurrency,
AllowContainerConcurrencyZero: DefaultAllowContainerConcurrencyZero,
EnableServiceLinks: ptr.Bool(false),
InitContainerNameTemplate: DefaultInitContainerNameTemplate,
UserContainerNameTemplate: DefaultUserContainerNameTemplate,
},
data: map[string]string{
"runtime-class-name": `---
gvisor: {}
kata:
selector:
some: value-here
`,
},
}}

for _, tt := range configTests {
Expand Down Expand Up @@ -334,3 +394,72 @@ func TestTemplating(t *testing.T) {
}
})
}

func TestPodRuntimeClassName(t *testing.T) {
ts := []struct {
name string
serviceLabels map[string]string
runtimeClassNames string
want *string
}{{
name: "empty",
serviceLabels: map[string]string{},
runtimeClassNames: "",
want: nil,
}, {
name: "wildcard set",
serviceLabels: map[string]string{},
runtimeClassNames: `gvisor: {}`,
want: ptr.String("gvisor"),
}, {
name: "set via label",
serviceLabels: map[string]string{
"very-cool": "indeed",
},
runtimeClassNames: `---
gvisor: {}
kata:
selector:
very-cool: indeed
`,
want: ptr.String("kata"),
}, {
name: "no default only labels with set labels",
serviceLabels: map[string]string{
"very-cool": "indeed",
},
runtimeClassNames: `---
"": {}
kata:
selector:
very-cool: indeed
`,
want: ptr.String("kata"),
}, {
name: "no default only labels with set no labels",
serviceLabels: map[string]string{},
runtimeClassNames: `---
"": {}
kata:
selector:
very-cool: indeed
`,
want: nil,
}}

for _, tt := range ts {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctx := apis.WithinParent(context.Background(), metav1.ObjectMeta{
Labels: tt.serviceLabels,
})
defaults := defaultDefaultsConfig()
defaults.RuntimeClassName = tt.runtimeClassNames
got, want := defaults.PodRuntimeClassName(ctx), tt.want

if !equality.Semantic.DeepEqual(got, want) {
t.Errorf("PodRuntimeClassName() = %v, wanted %v", got, want)
}
})
}
}
23 changes: 23 additions & 0 deletions pkg/apis/config/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/apis/serving/v1/revision_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func (rs *RevisionSpec) SetDefaults(ctx context.Context) {
rs.ContainerConcurrency = ptr.Int64(cfg.Defaults.ContainerConcurrency)
}

// Default PodRuntimeClassName based on our configmap.
if rs.PodSpec.RuntimeClassName == nil {
rs.PodSpec.RuntimeClassName = cfg.Defaults.PodRuntimeClassName(ctx)
}

// Avoid clashes with user-supplied names when generating defaults.
containerNames := make(sets.Set[string], len(rs.PodSpec.Containers)+len(rs.PodSpec.InitContainers))
for idx := range rs.PodSpec.Containers {
Expand Down
39 changes: 39 additions & 0 deletions pkg/apis/serving/v1/revision_defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,45 @@ func TestRevisionDefaulting(t *testing.T) {
ContainerConcurrency: ptr.Int64(config.DefaultContainerConcurrency),
},
},
}, {
name: "with runtimeClassName set",
in: &Revision{Spec: RevisionSpec{PodSpec: corev1.PodSpec{Containers: []corev1.Container{{}}}}},
wc: func(ctx context.Context) context.Context {
s := config.NewStore(logger)
s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
s.OnConfigChanged(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName},
Data: map[string]string{
"kubernetes.podspec-runtimeclassname": "enabled",
},
})
s.OnConfigChanged(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: config.DefaultsConfigName,
},
Data: map[string]string{
"runtime-class-name": `---
gvisor: {}`,
},
})

return apis.WithinCreate(s.ToContext(ctx))
},
want: &Revision{
Spec: RevisionSpec{
ContainerConcurrency: ptr.Int64(config.DefaultContainerConcurrency),
TimeoutSeconds: ptr.Int64(config.DefaultRevisionTimeoutSeconds),
PodSpec: corev1.PodSpec{
RuntimeClassName: ptr.String("gvisor"),
Containers: []corev1.Container{{
Name: config.DefaultUserContainerName,
Resources: defaultResources,
ReadinessProbe: defaultProbe,
}},
EnableServiceLinks: ptr.Bool(false),
},
},
},
}, {
name: "with context",
in: &Revision{Spec: RevisionSpec{PodSpec: corev1.PodSpec{Containers: []corev1.Container{{}}}}},
Expand Down

0 comments on commit 400da99

Please sign in to comment.