@@ -21,9 +21,15 @@ import (
21
21
"errors"
22
22
"fmt"
23
23
"net/url"
24
+ goruntime "runtime"
25
+ "strconv"
24
26
"time"
25
27
28
+ introspectionapi "github.com/containerd/containerd/v2/api/services/introspection/v1"
29
+ apitypes "github.com/containerd/containerd/v2/api/types"
30
+ "github.com/containerd/containerd/v2/protobuf"
26
31
"github.com/containerd/log"
32
+ "github.com/containerd/typeurl/v2"
27
33
"github.com/pelletier/go-toml/v2"
28
34
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
29
35
"k8s.io/kubelet/pkg/cri/streaming"
@@ -34,8 +40,16 @@ import (
34
40
"github.com/containerd/containerd/v2/pkg/deprecation"
35
41
runtimeoptions "github.com/containerd/containerd/v2/pkg/runtimeoptions/v1"
36
42
"github.com/containerd/containerd/v2/plugins"
43
+ "github.com/opencontainers/image-spec/specs-go"
44
+ "github.com/opencontainers/runtime-spec/specs-go/features"
37
45
)
38
46
47
+ func init () {
48
+ const prefix = "types.containerd.io"
49
+ major := strconv .Itoa (specs .VersionMajor )
50
+ typeurl .Register (& features.Features {}, prefix , "opencontainers/runtime-spec" , major , "features" , "Features" )
51
+ }
52
+
39
53
const (
40
54
// defaultImagePullProgressTimeoutDuration is the default value of imagePullProgressTimeout.
41
55
//
@@ -73,6 +87,17 @@ const (
73
87
DefaultSandboxImage = "registry.k8s.io/pause:3.9"
74
88
)
75
89
90
+ // Ternary represents a ternary value.
91
+ // Ternary is needed because TOML does not accept "null" for boolean values.
92
+ type Ternary = string
93
+
94
+ const (
95
+ TernaryEmpty Ternary = "" // alias for IfPossible
96
+ TernaryEnabled Ternary = "Enabled"
97
+ TernaryIfPossible Ternary = "IfPossible"
98
+ TernaryDisabled Ternary = "Disabled"
99
+ )
100
+
76
101
// Runtime struct to contain the type(ID), engine, and root variables for a default runtime
77
102
// and a runtime for untrusted workload.
78
103
type Runtime struct {
@@ -116,6 +141,15 @@ type Runtime struct {
116
141
// shim - means use whatever Controller implementation provided by shim (e.g. use RemoteController).
117
142
// podsandbox - means use Controller implementation from sbserver podsandbox package.
118
143
Sandboxer string `toml:"sandboxer" json:"sandboxer"`
144
+
145
+ // TreatRoMountsAsRro ("Enabled"|"IfPossible"|"Disabled")
146
+ // treats read-only mounts as recursive read-only mounts.
147
+ // An empty string means "IfPossible".
148
+ // "Enabled" requires Linux kernel v5.12 or later.
149
+ // Introduced in containerd v2.0.
150
+ // This configuration does not apply to non-volume mounts such as "/sys/fs/cgroup".
151
+ TreatRoMountsAsRro Ternary `toml:"treat_ro_mount_as_rro" json:"treatRoMountsAsRro"`
152
+ TreatRoMountsAsRroResolved bool `toml:"-" json:"-"` // Do not set manually
119
153
}
120
154
121
155
// ContainerdConfig contains toml config related to containerd
@@ -499,8 +533,120 @@ func ValidateImageConfig(ctx context.Context, c *ImageConfig) ([]deprecation.War
499
533
return warnings , nil
500
534
}
501
535
536
+ func introspectRuntimeFeatures (ctx context.Context , introspectionClient introspectionapi.IntrospectionClient , r Runtime ) (* features.Features , error ) {
537
+ if introspectionClient == nil { // happens for unit tests
538
+ return nil , errors .New ("introspectionClient is nil" )
539
+ }
540
+ infoReq := & introspectionapi.PluginInfoRequest {
541
+ Type : string (plugins .RuntimePluginV2 ),
542
+ ID : "task" ,
543
+ }
544
+ rr := & apitypes.RuntimeRequest {
545
+ RuntimePath : r .Type ,
546
+ }
547
+ if r .Path != "" {
548
+ rr .RuntimePath = r .Path
549
+ }
550
+ options , err := GenerateRuntimeOptions (r )
551
+ if err != nil {
552
+ return nil , err
553
+ }
554
+ rr .Options , err = protobuf .MarshalAnyToProto (options )
555
+ if err != nil {
556
+ return nil , fmt .Errorf ("failed to marshal %T: %w" , options , err )
557
+ }
558
+ infoReq .Options , err = protobuf .MarshalAnyToProto (rr )
559
+ if err != nil {
560
+ return nil , fmt .Errorf ("failed to marshal %T: %w" , rr , err )
561
+ }
562
+ infoResp , err := introspectionClient .PluginInfo (ctx , infoReq )
563
+ if err != nil {
564
+ return nil , fmt .Errorf ("failed to call PluginInfo: %w" , err )
565
+ }
566
+ var info apitypes.RuntimeInfo
567
+ if err := typeurl .UnmarshalTo (infoResp .Extra , & info ); err != nil {
568
+ return nil , fmt .Errorf ("failed to get runtime info from plugin info: %w" , err )
569
+ }
570
+ featuresX , err := typeurl .UnmarshalAny (info .Features )
571
+ if err != nil {
572
+ return nil , fmt .Errorf ("failed to unmarshal Features (%T): %w" , info .Features , err )
573
+ }
574
+ features , ok := featuresX .(* features.Features )
575
+ if ! ok {
576
+ return nil , fmt .Errorf ("unknown features type %T" , featuresX )
577
+ }
578
+ return features , nil
579
+ }
580
+
581
+ // resolveTreatRoMountsAsRro resolves r.TreatRoMountsAsRro string into a boolean.
582
+ func resolveTreatRoMountsAsRro (ctx context.Context , introspectionClient introspectionapi.IntrospectionClient , r Runtime ) (bool , error ) {
583
+ debugPrefix := "treat_ro_mounts_as_rro"
584
+ if r .Type != "" {
585
+ debugPrefix += fmt .Sprintf ("[%s]" , r .Type )
586
+ }
587
+ if binaryName := r .Options ["BinaryName" ]; binaryName != "" {
588
+ debugPrefix += fmt .Sprintf ("[%v]" , binaryName )
589
+ }
590
+ debugPrefix += ": "
591
+
592
+ var runtimeSupportsRro bool
593
+ if r .Type == plugins .RuntimeRuncV2 {
594
+ features , err := introspectRuntimeFeatures (ctx , introspectionClient , r )
595
+ if err != nil {
596
+ log .G (ctx ).WithError (err ).Warnf (debugPrefix + "failed to introspect runtime features (binary is not compatible with runc v1.1?)" )
597
+ } else {
598
+ log .G (ctx ).Debugf (debugPrefix + "Features: %+v" , features )
599
+ for _ , s := range features .MountOptions {
600
+ if s == "rro" {
601
+ runtimeSupportsRro = true
602
+ break
603
+ }
604
+ }
605
+ }
606
+ }
607
+
608
+ switch r .TreatRoMountsAsRro {
609
+ case TernaryDisabled :
610
+ log .G (ctx ).Debug (debugPrefix + "rro mounts are explicitly disabled" )
611
+ return false , nil
612
+ case TernaryEnabled :
613
+ log .G (ctx ).Debug (debugPrefix + "rro mounts are explicitly enabled" )
614
+ if ! kernelSupportsRro {
615
+ return true , fmt .Errorf ("invalid `treat_ro_mounts_as_rro`: %q: needs Linux kernel v5.12 or later" , TernaryEnabled )
616
+ }
617
+ if ! runtimeSupportsRro {
618
+ return true , fmt .Errorf ("invalid `treat_ro_mounts_as_rro`: %q: needs a runtime that is compatible with runc v1.1" , TernaryEnabled )
619
+ }
620
+ return true , nil
621
+ case TernaryEmpty , TernaryIfPossible :
622
+ if r .Type != plugins .RuntimeRuncV2 {
623
+ log .G (ctx ).Debugf (debugPrefix + "rro mounts are not supported by runtime %q, disabling rro mounts" , r .Type )
624
+ return false , nil
625
+ }
626
+ if ! kernelSupportsRro {
627
+ msg := debugPrefix + "rro mounts are not supported by kernel, disabling rro mounts"
628
+ if goruntime .GOOS == "linux" {
629
+ msg += " (Hint: upgrade the kernel to v5.12 or later)"
630
+ log .G (ctx ).Warn (msg )
631
+ } else {
632
+ log .G (ctx ).Debug (msg )
633
+ }
634
+ return false , nil
635
+ }
636
+ if ! runtimeSupportsRro {
637
+ log .G (ctx ).Warn (debugPrefix + "rro mounts are not supported by runtime, disabling rro mounts (Hint: use a runtime that is compatible with runc v1.1)" )
638
+ return false , nil
639
+ }
640
+ log .G (ctx ).Debug (debugPrefix + "rro mounts are implicitly enabled" )
641
+ return true , nil
642
+ default :
643
+ return false , fmt .Errorf ("invalid `treat_ro_mounts_as_rro`: %q (must be %q, %q, or %q)" ,
644
+ r .TreatRoMountsAsRro , TernaryDisabled , TernaryEnabled , TernaryIfPossible )
645
+ }
646
+ }
647
+
502
648
// ValidateRuntimeConfig validates the given runtime configuration.
503
- func ValidateRuntimeConfig (ctx context.Context , c * RuntimeConfig ) ([]deprecation.Warning , error ) {
649
+ func ValidateRuntimeConfig (ctx context.Context , c * RuntimeConfig , introspectionClient introspectionapi. IntrospectionClient ) ([]deprecation.Warning , error ) {
504
650
var warnings []deprecation.Warning
505
651
if c .ContainerdConfig .Runtimes == nil {
506
652
c .ContainerdConfig .Runtimes = make (map [string ]Runtime )
@@ -521,8 +667,15 @@ func ValidateRuntimeConfig(ctx context.Context, c *RuntimeConfig) ([]deprecation
521
667
// If empty, use default podSandbox mode
522
668
if len (r .Sandboxer ) == 0 {
523
669
r .Sandboxer = string (ModePodSandbox )
524
- c .ContainerdConfig .Runtimes [k ] = r
525
670
}
671
+
672
+ // Resolve r.TreatRoMountsAsRro (string; empty value must not be ignored) into r.TreatRoMountsAsRroResolved (bool)
673
+ var err error
674
+ r .TreatRoMountsAsRroResolved , err = resolveTreatRoMountsAsRro (ctx , introspectionClient , r )
675
+ if err != nil {
676
+ return warnings , err
677
+ }
678
+ c .ContainerdConfig .Runtimes [k ] = r
526
679
}
527
680
528
681
// Validation for drain_exec_sync_io_timeout
0 commit comments