feat: udev rules support

Brings udev rules as first class citizens in the machine config

Allows for customizing how udev devices are presented in the system. My case in particular, I need to change the group and permissions for the Intel QuickSync renderer device to enable non-root containers to make use of the hardware transcoding capabilities of the Intel GPU.

Signed-off-by: Branden Cash <ammmze@gmail.com>
Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
Branden Cash 2021-08-03 21:41:49 -07:00 committed by Andrey Smirnov
parent 5237fdc957
commit 41299cae99
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
10 changed files with 181 additions and 1 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
bin
_out
.vscode
.idea
*.code-workspace
init.yaml
controlplane.yaml

View File

@ -232,6 +232,9 @@ func (*Sequencer) Boot(r runtime.Runtime) []runtime.Phase {
r.State().Platform().Mode() != runtime.ModeContainer,
"overlay",
MountOverlayFilesystems,
).Append(
"udevSetup",
WriteUdevRules,
).AppendWhen(
r.State().Platform().Mode() != runtime.ModeContainer,
"udevd",

View File

@ -589,6 +589,30 @@ func StartContainerd(seq runtime.Sequence, data interface{}) (runtime.TaskExecut
}, "startContainerd"
}
// WriteUdevRules is the task that writes udev rules to a udev rules file.
func WriteUdevRules(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
rules := r.Config().Machine().Udev().Rules()
if len(rules) == 0 {
return nil
}
var content strings.Builder
for _, rule := range rules {
content.WriteString(strings.ReplaceAll(rule, "\n", "\\\n"))
content.WriteByte('\n')
}
if err = ioutil.WriteFile(constants.UdevRulesPath, []byte(content.String()), 0o644); err != nil {
return fmt.Errorf("failed writing custom udev rules: %w", err)
}
return nil
}, "writeUdevRules"
}
// StartUdevd represents the task to start udevd.
func StartUdevd(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {

View File

@ -49,6 +49,7 @@ type MachineConfig interface {
Registries() Registries
SystemDiskEncryption() SystemDiskEncryption
Features() Features
Udev() UdevConfig
}
// Disk represents the options available for partitioning, formatting, and
@ -478,3 +479,8 @@ type ServiceRegistry interface {
Enabled() bool
Endpoint() string
}
// UdevConfig describes configuration for udev.
type UdevConfig interface {
Rules() []string
}

View File

@ -285,6 +285,15 @@ func (m *MachineConfig) Features() config.Features {
return m.MachineFeatures
}
// Udev implements the config.MachineConfig interface.
func (m *MachineConfig) Udev() config.UdevConfig {
if m.MachineUdev == nil {
return &UdevConfig{}
}
return m.MachineUdev
}
// Image implements the config.Provider interface.
func (k *KubeletConfig) Image() string {
image := k.KubeletImage
@ -1202,3 +1211,8 @@ func (v VolumeMountConfig) Name() string {
func (v VolumeMountConfig) ReadOnly() bool {
return v.VolumeReadOnly
}
// Rules implements config.Udev interface.
func (u *UdevConfig) Rules() []string {
return u.UdevRules
}

View File

@ -250,6 +250,10 @@ var (
RBAC: pointer.ToBool(true),
}
machineUdevExample = &UdevConfig{
UdevRules: []string{"SUBSYSTEM==\"drm\", KERNEL==\"renderD*\", GROUP=\"44\", MODE=\"0660\""},
}
clusterConfigExample = struct {
ControlPlane *ControlPlaneConfig `yaml:"controlPlane"`
ClusterName string `yaml:"clusterName"`
@ -651,6 +655,11 @@ type MachineConfig struct {
// examples:
// - value: machineFeaturesExample
MachineFeatures *FeaturesConfig `yaml:"features,omitempty"`
// description: |
// Configures the udev system.
// examples:
// - value: machineUdevExample
MachineUdev *UdevConfig `yaml:"udev,omitempty"`
}
// ClusterConfig represents the cluster-wide config values.
@ -2006,3 +2015,10 @@ type RegistryServiceConfig struct {
// - value: constants.DefaultDiscoveryServiceEndpoint
RegistryEndpoint string `yaml:"endpoint,omitempty"`
}
// UdevConfig describes how the udev system should be configured.
type UdevConfig struct {
// description: |
// List of udev rules to apply to the udev system
UdevRules []string `yaml:"rules,omitempty"`
}

View File

@ -67,6 +67,7 @@ var (
DiscoveryRegistriesConfigDoc encoder.Doc
RegistryKubernetesConfigDoc encoder.Doc
RegistryServiceConfigDoc encoder.Doc
UdevConfigDoc encoder.Doc
)
func init() {
@ -128,7 +129,7 @@ func init() {
FieldName: "machine",
},
}
MachineConfigDoc.Fields = make([]encoder.Doc, 15)
MachineConfigDoc.Fields = make([]encoder.Doc, 16)
MachineConfigDoc.Fields[0].Name = "type"
MachineConfigDoc.Fields[0].Type = "string"
MachineConfigDoc.Fields[0].Note = ""
@ -248,6 +249,13 @@ func init() {
MachineConfigDoc.Fields[14].Comments[encoder.LineComment] = "Features describe individual Talos features that can be switched on or off."
MachineConfigDoc.Fields[14].AddExample("", machineFeaturesExample)
MachineConfigDoc.Fields[15].Name = "udev"
MachineConfigDoc.Fields[15].Type = "UdevConfig"
MachineConfigDoc.Fields[15].Note = ""
MachineConfigDoc.Fields[15].Description = "Configures the udev system."
MachineConfigDoc.Fields[15].Comments[encoder.LineComment] = "Configures the udev system."
MachineConfigDoc.Fields[15].AddExample("", machineUdevExample)
ClusterConfigDoc.Type = "ClusterConfig"
ClusterConfigDoc.Comments[encoder.LineComment] = "ClusterConfig represents the cluster-wide config values."
@ -2136,6 +2144,24 @@ func init() {
RegistryServiceConfigDoc.Fields[1].Comments[encoder.LineComment] = "External service endpoint."
RegistryServiceConfigDoc.Fields[1].AddExample("", constants.DefaultDiscoveryServiceEndpoint)
UdevConfigDoc.Type = "UdevConfig"
UdevConfigDoc.Comments[encoder.LineComment] = "UdevConfig describes how the udev system should be configured."
UdevConfigDoc.Description = "UdevConfig describes how the udev system should be configured."
UdevConfigDoc.AddExample("", machineUdevExample)
UdevConfigDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "MachineConfig",
FieldName: "udev",
},
}
UdevConfigDoc.Fields = make([]encoder.Doc, 1)
UdevConfigDoc.Fields[0].Name = "rules"
UdevConfigDoc.Fields[0].Type = "[]string"
UdevConfigDoc.Fields[0].Note = ""
UdevConfigDoc.Fields[0].Description = "List of udev rules to apply to the udev system"
UdevConfigDoc.Fields[0].Comments[encoder.LineComment] = "List of udev rules to apply to the udev system"
}
func (_ Config) Doc() *encoder.Doc {
@ -2358,6 +2384,10 @@ func (_ RegistryServiceConfig) Doc() *encoder.Doc {
return &RegistryServiceConfigDoc
}
func (_ UdevConfig) Doc() *encoder.Doc {
return &UdevConfigDoc
}
// GetConfigurationDoc returns documentation for the file ./v1alpha1_types_doc.go.
func GetConfigurationDoc() *encoder.FileDoc {
return &encoder.FileDoc{
@ -2419,6 +2449,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
&DiscoveryRegistriesConfigDoc,
&RegistryKubernetesConfigDoc,
&RegistryServiceConfigDoc,
&UdevConfigDoc,
},
}
}

View File

@ -974,6 +974,11 @@ func (in *MachineConfig) DeepCopyInto(out *MachineConfig) {
*out = new(FeaturesConfig)
(*in).DeepCopyInto(*out)
}
if in.MachineUdev != nil {
in, out := &in.MachineUdev, &out.MachineUdev
*out = new(UdevConfig)
(*in).DeepCopyInto(*out)
}
return
}
@ -1386,6 +1391,27 @@ func (in *TimeConfig) DeepCopy() *TimeConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UdevConfig) DeepCopyInto(out *UdevConfig) {
*out = *in
if in.UdevRules != nil {
in, out := &in.UdevRules, &out.UdevRules
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UdevConfig.
func (in *UdevConfig) DeepCopy() *UdevConfig {
if in == nil {
return nil
}
out := new(UdevConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VIPEquinixMetalConfig) DeepCopyInto(out *VIPEquinixMetalConfig) {
*out = *in

View File

@ -512,6 +512,9 @@ const (
// KubeSpanLinkName is the link name for the KubeSpan Wireguard interface.
KubeSpanLinkName = "kubespan"
// UdevRulesPath rules file path.
UdevRulesPath = "/usr/etc/udev/rules.d/99-talos.rules"
)
// See https://linux.die.net/man/3/klogctl

View File

@ -735,6 +735,31 @@ features:
```
</div>
<hr />
<div class="dd">
<code>udev</code> <i><a href="#udevconfig">UdevConfig</a></i>
</div>
<div class="dt">
Configures the udev system.
Examples:
``` yaml
udev:
# List of udev rules to apply to the udev system
rules:
- SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="44", MODE="0660"
```
</div>
<hr />
@ -5531,3 +5556,34 @@ endpoint: https://discovery.talos.dev/
<hr />
## UdevConfig
UdevConfig describes how the udev system should be configured.
Appears in:
- <code><a href="#machineconfig">MachineConfig</a>.udev</code>
``` yaml
# List of udev rules to apply to the udev system
rules:
- SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="44", MODE="0660"
```
<hr />
<div class="dd">
<code>rules</code> <i>[]string</i>
</div>
<div class="dt">
List of udev rules to apply to the udev system
</div>
<hr />