Sitelet https://github.com/kubernetes/kops/commit/c2701603b7e825e89b9cd715b79ed06ee730d47b
Skip to content

Commit c270160

Browse files
authored
Merge pull request #18406 from ameukam/gvisor-runtime-support
feat: add gVisor RuntimeClass support for containerd
2 parents e6044a5 + 90d5ed3 commit c270160

27 files changed

Lines changed: 799 additions & 1 deletion

‎docs/releases/1.36-NOTES.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ kOps 1.36 adds Kubernetes 1.36 support, completes the move away from the in-tree
3939
* dns-controller: make `priorityClassName` configurable and default `Provider` when `ExternalDNS` is partially set ([#18298](https://github.com/kubernetes/kops/pull/18298), [#18302](https://github.com/kubernetes/kops/pull/18302))
4040
* Drop deprecated GCS-based CNI plugin mirrors ([#17987](https://github.com/kubernetes/kops/pull/17987), [#17976](https://github.com/kubernetes/kops/pull/17976))
4141

42+
## gVisor RuntimeClass support
43+
44+
kOps now supports running workloads under [gVisor](https://gvisor.dev/) (runsc) as a containerd runtime handler. When enabled, kOps installs the `runsc` runtime via apt, configures containerd with a `runsc` runtime handler, and creates a `gvisor` RuntimeClass. Pods targeting the RuntimeClass are automatically scheduled to nodes with gVisor enabled via the `kops.k8s.io/gvisor` node label.
45+
46+
gVisor is configured per worker instance group. Cluster-level gVisor configuration is rejected, and instance group-level configuration is accepted only for workers:
47+
48+
```yaml
49+
spec:
50+
containerd:
51+
gvisor:
52+
enabled: true
53+
platform: systrap # default; also supports kvm
54+
```
55+
56+
This feature is currently supported on Debian-family distributions only (Debian, Ubuntu).
57+
4258
## AWS
4359
* Drop the in-tree `cloud-provider-aws` dependency from kOps ([#18336](https://github.com/kubernetes/kops/pull/18336))
4460
* AWS Load Balancer Controller refresh:

‎k8s/crds/kops.k8s.io_clusters.yaml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,19 @@ spec:
954954
description: ConfigOverride is the complete containerd config
955955
file provided by the user.
956956
type: string
957+
gvisor:
958+
description: GVisor configures the gVisor (runsc) sandboxed runtime.
959+
properties:
960+
enabled:
961+
description: Enabled determines if kOps will install the gVisor
962+
runtime.
963+
type: boolean
964+
platform:
965+
description: |-
966+
Platform is the gVisor execution platform: "systrap" (default, works
967+
everywhere including VMs) or "kvm" (bare-metal with KVM support).
968+
type: string
969+
type: object
957970
installCriCtl:
958971
description: InstallCriCtl installs crictl (default "false").
959972
type: boolean

‎k8s/crds/kops.k8s.io_instancegroups.yaml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,19 @@ spec:
136136
description: ConfigOverride is the complete containerd config
137137
file provided by the user.
138138
type: string
139+
gvisor:
140+
description: GVisor configures the gVisor (runsc) sandboxed runtime.
141+
properties:
142+
enabled:
143+
description: Enabled determines if kOps will install the gVisor
144+
runtime.
145+
type: boolean
146+
platform:
147+
description: |-
148+
Platform is the gVisor execution platform: "systrap" (default, works
149+
everywhere including VMs) or "kvm" (bare-metal with KVM support).
150+
type: string
151+
type: object
139152
installCriCtl:
140153
description: InstallCriCtl installs crictl (default "false").
141154
type: boolean

‎nodeup/pkg/model/containerd.go‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ func (b *ContainerdBuilder) Build(c *fi.NodeupModelBuilderContext) error {
9393
return err
9494
}
9595

96+
// If gVisor is enabled, emit the runsc shim config file
97+
if b.InstallGVisorRuntime() {
98+
b.buildGVisorShimConfig(c)
99+
}
100+
96101
if installContainerd {
97102
if err := b.installContainerd(c); err != nil {
98103
return err
@@ -564,6 +569,12 @@ func (b *ContainerdBuilder) buildContainerdConfigV2() (string, error) {
564569
}
565570
}
566571

572+
if b.InstallGVisorRuntime() {
573+
if err := appendGVisorRuntimeConfig(config, []string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes"}); err != nil {
574+
return "", fmt.Errorf("appending gvisor runtime to v2 containerd config: %w", err)
575+
}
576+
}
577+
567578
if err := applyConfigAdditions(config, containerd.ConfigAdditions); err != nil {
568579
return "", fmt.Errorf("applying ConfigAdditions to v2 containerd config: %w", err)
569580
}
@@ -617,6 +628,12 @@ func (b *ContainerdBuilder) buildContainerdConfigV3() (string, error) {
617628
}
618629
}
619630

631+
if b.InstallGVisorRuntime() {
632+
if err := appendGVisorRuntimeConfig(config, []string{"plugins", "io.containerd.cri.v1.runtime", "containerd", "runtimes"}); err != nil {
633+
return "", fmt.Errorf("appending gvisor runtime to v3 containerd config: %w", err)
634+
}
635+
}
636+
620637
if err := applyConfigAdditions(config, containerd.ConfigAdditions); err != nil {
621638
return "", fmt.Errorf("applying ConfigAdditions to v3 containerd config: %w", err)
622639
}
@@ -691,6 +708,45 @@ func appendNvidiaGPURuntimeConfig(config *toml.Tree, runtimesPath []string) erro
691708
return nil
692709
}
693710

711+
// appendGVisorRuntimeConfig adds the "runsc" runtime entry under runtimesPath.
712+
// runtimesPath is schema-specific so the same helper can serve both v2 and v3 builders.
713+
func appendGVisorRuntimeConfig(config *toml.Tree, runtimesPath []string) error {
714+
gvisorConfig, err := toml.TreeFromMap(
715+
map[string]interface{}{
716+
"runtime_type": "io.containerd.runsc.v1",
717+
},
718+
)
719+
if err != nil {
720+
return err
721+
}
722+
723+
path := make([]string, len(runtimesPath)+1)
724+
copy(path, runtimesPath)
725+
path[len(runtimesPath)] = "runsc"
726+
config.SetPath(path, gvisorConfig)
727+
728+
return nil
729+
}
730+
731+
// buildGVisorShimConfig emits /etc/containerd/runsc.toml, the shim-level
732+
// configuration consumed by containerd-shim-runsc-v1 at container creation.
733+
// See https://gvisor.dev/docs/user_guide/containerd/configuration/
734+
func (b *ContainerdBuilder) buildGVisorShimConfig(c *fi.NodeupModelBuilderContext) {
735+
platform := b.NodeupConfig.GVisor.Platform
736+
if platform == "" {
737+
platform = "systrap"
738+
}
739+
740+
shimConfig, _ := toml.Load("")
741+
shimConfig.SetPath([]string{"runsc_config", "platform"}, platform)
742+
743+
c.AddTask(&nodetasks.File{
744+
Path: "/etc/containerd/runsc.toml",
745+
Contents: fi.NewStringResource(shimConfig.String()),
746+
Type: nodetasks.FileType_File,
747+
})
748+
}
749+
694750
// buildRegistryHosts emits one hosts.toml per RegistryMirrors entry under containerdRegistryDirPath.
695751
// The directory is referenced by registry.config_path in the main containerd config; the
696752
// emit-files-iff-mirrors-non-empty condition here must stay in sync with the registry.config_path

‎nodeup/pkg/model/context.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,14 @@ func (c *NodeupModelContext) InstallNvidiaRuntime() bool {
563563
c.GPUVendor == architectures.GPUVendorNvidia
564564
}
565565

566+
// InstallGVisorRuntime returns true if the gVisor (runsc) runtime should be installed.
567+
func (c *NodeupModelContext) InstallGVisorRuntime() bool {
568+
return c.BootConfig != nil &&
569+
c.BootConfig.InstanceGroupRole == kops.InstanceGroupRoleNode &&
570+
c.NodeupConfig.GVisor != nil &&
571+
fi.ValueOf(c.NodeupConfig.GVisor.Enabled)
572+
}
573+
566574
// CloudProvider returns the cloud provider we are running on
567575
func (c *NodeupModelContext) CloudProvider() kops.CloudProviderID {
568576
return c.BootConfig.CloudProvider

‎nodeup/pkg/model/gvisor.go‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package model
18+
19+
import (
20+
"k8s.io/kops/upup/pkg/fi"
21+
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
22+
)
23+
24+
// GVisorBuilder installs the gVisor (runsc) sandboxed runtime.
25+
// Only supported on Debian-family distributions.
26+
type GVisorBuilder struct {
27+
*NodeupModelContext
28+
}
29+
30+
var _ fi.NodeupModelBuilder = &GVisorBuilder{}
31+
32+
// Build installs gVisor packages via the upstream apt repository.
33+
func (b *GVisorBuilder) Build(c *fi.NodeupModelBuilderContext) error {
34+
if !b.InstallGVisorRuntime() {
35+
return nil
36+
}
37+
38+
// gVisor packages are only published in the upstream apt repository,
39+
// so installation is limited to Debian-family distributions (Debian, Ubuntu).
40+
if !b.Distribution.IsDebianFamily() {
41+
return nil
42+
}
43+
44+
c.AddTask(&nodetasks.AptSource{
45+
Name: "gvisor",
46+
Keyring: "https://gvisor.dev/archive.key",
47+
Sources: []string{
48+
"deb [arch=$(ARCH)] https://storage.googleapis.com/gvisor/releases release main",
49+
},
50+
})
51+
// The runsc package bundles both runsc and containerd-shim-runsc-v1.
52+
c.AddTask(&nodetasks.Package{Name: "runsc"})
53+
54+
return nil
55+
}

‎nodeup/pkg/model/gvisor_test.go‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package model
18+
19+
import (
20+
"testing"
21+
22+
"k8s.io/kops/pkg/apis/kops"
23+
"k8s.io/kops/pkg/apis/nodeup"
24+
"k8s.io/kops/upup/pkg/fi"
25+
"k8s.io/kops/util/pkg/distributions"
26+
)
27+
28+
func TestGVisorBuilderBuild(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
distribution distributions.Distribution
32+
role kops.InstanceGroupRole
33+
gvisor *kops.GVisorConfig
34+
wantTasks []string
35+
}{
36+
{
37+
name: "disabled",
38+
distribution: distributions.DistributionDebian13,
39+
role: kops.InstanceGroupRoleNode,
40+
gvisor: &kops.GVisorConfig{Enabled: fi.PtrTo(false)},
41+
},
42+
{
43+
name: "enabled debian",
44+
distribution: distributions.DistributionDebian13,
45+
role: kops.InstanceGroupRoleNode,
46+
gvisor: &kops.GVisorConfig{Enabled: fi.PtrTo(true)},
47+
wantTasks: []string{"AptSource/gvisor", "Package/runsc"},
48+
},
49+
{
50+
name: "enabled non debian",
51+
distribution: distributions.DistributionRhel9,
52+
role: kops.InstanceGroupRoleNode,
53+
gvisor: &kops.GVisorConfig{Enabled: fi.PtrTo(true)},
54+
},
55+
{
56+
name: "enabled control plane",
57+
distribution: distributions.DistributionDebian13,
58+
role: kops.InstanceGroupRoleControlPlane,
59+
gvisor: &kops.GVisorConfig{Enabled: fi.PtrTo(true)},
60+
},
61+
{
62+
name: "unset",
63+
distribution: distributions.DistributionDebian13,
64+
role: kops.InstanceGroupRoleNode,
65+
},
66+
}
67+
68+
for _, test := range tests {
69+
t.Run(test.name, func(t *testing.T) {
70+
context := &fi.NodeupModelBuilderContext{
71+
Tasks: make(map[string]fi.NodeupTask),
72+
}
73+
builder := &GVisorBuilder{
74+
NodeupModelContext: &NodeupModelContext{
75+
Distribution: test.distribution,
76+
BootConfig: &nodeup.BootConfig{
77+
InstanceGroupRole: test.role,
78+
},
79+
NodeupConfig: &nodeup.Config{
80+
GVisor: test.gvisor,
81+
},
82+
},
83+
}
84+
85+
if err := builder.Build(context); err != nil {
86+
t.Fatalf("Build returned error: %v", err)
87+
}
88+
if len(context.Tasks) != len(test.wantTasks) {
89+
t.Fatalf("got %d tasks, want %d: %v", len(context.Tasks), len(test.wantTasks), context.Tasks)
90+
}
91+
for _, key := range test.wantTasks {
92+
if _, ok := context.Tasks[key]; !ok {
93+
t.Errorf("missing task %q", key)
94+
}
95+
}
96+
})
97+
}
98+
}

‎pkg/apis/kops/containerdconfig.go‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const (
2626
NvidiaDefaultDriverPackage = "nvidia-driver-535-server"
2727
// NvidiaDevicePluginImage is the Nvidia K8s device plugin container image
2828
NvidiaDevicePluginImage = "nvcr.io/nvidia/k8s-device-plugin:v0.17.3"
29+
// GVisorDefaultPlatform is the default gVisor execution platform.
30+
// systrap uses SECCOMP_RET_TRAP/SIGSYS and works in all environments including VMs.
31+
GVisorDefaultPlatform = "systrap"
2932
)
3033

3134
// ContainerdConfig is the configuration for containerd
@@ -55,6 +58,8 @@ type ContainerdConfig struct {
5558
Version *string `json:"version,omitempty"`
5659
// NvidiaGPU configures the Nvidia GPU runtime.
5760
NvidiaGPU *NvidiaGPUConfig `json:"nvidiaGPU,omitempty"`
61+
// GVisor configures the gVisor (runsc) sandboxed runtime.
62+
GVisor *GVisorConfig `json:"gvisor,omitempty"`
5863
// Runc configures the runc runtime.
5964
Runc *Runc `json:"runc,omitempty"`
6065
// SelinuxEnabled enables SELinux support
@@ -106,3 +111,15 @@ type Runc struct {
106111
// Packages overrides the URL and hash for the packages.
107112
Packages *PackagesConfig `json:"packages,omitempty"`
108113
}
114+
115+
// GVisorConfig configures the gVisor sandboxed container runtime.
116+
// When enabled, kOps installs runsc and containerd-shim-runsc-v1,
117+
// registers the "runsc" runtime handler in containerd, and deploys
118+
// a Kubernetes RuntimeClass named "gvisor".
119+
type GVisorConfig struct {
120+
// Enabled determines if kOps will install the gVisor runtime.
121+
Enabled *bool `json:"enabled,omitempty"`
122+
// Platform is the gVisor execution platform: "systrap" (default, works
123+
// everywhere including VMs) or "kvm" (bare-metal with KVM support).
124+
Platform string `json:"platform,omitempty"`
125+
}

‎pkg/apis/kops/v1alpha2/containerdconfig.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ type ContainerdConfig struct {
4848
Version *string `json:"version,omitempty"`
4949
// NvidiaGPU configures the Nvidia GPU runtime.
5050
NvidiaGPU *NvidiaGPUConfig `json:"nvidiaGPU,omitempty"`
51+
// GVisor configures the gVisor (runsc) sandboxed runtime.
52+
GVisor *GVisorConfig `json:"gvisor,omitempty"`
5153
// Runc configures the runc runtime.
5254
Runc *Runc `json:"runc,omitempty"`
5355
// SelinuxEnabled enables SELinux support
@@ -99,3 +101,15 @@ type Runc struct {
99101
// Packages overrides the URL and hash for the packages.
100102
Packages *PackagesConfig `json:"packages,omitempty"`
101103
}
104+
105+
// GVisorConfig configures the gVisor sandboxed container runtime.
106+
// When enabled, kOps installs runsc and containerd-shim-runsc-v1,
107+
// registers the "runsc" runtime handler in containerd, and deploys
108+
// a Kubernetes RuntimeClass named "gvisor".
109+
type GVisorConfig struct {
110+
// Enabled determines if kOps will install the gVisor runtime.
111+
Enabled *bool `json:"enabled,omitempty"`
112+
// Platform is the gVisor execution platform: "systrap" (default, works
113+
// everywhere including VMs) or "kvm" (bare-metal with KVM support).
114+
Platform string `json:"platform,omitempty"`
115+
}

0 commit comments

Comments
 (0)