udscsi

package
v0.0.0-...-bb1d162 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package udscsi is the csi.aether.io node plugin (proposal 039 Phase 1): the CSI driver that gives each UDS-served workload a mesh-owned socket directory.

For every pod that declares an inline `csi: {driver: csi.aether.io}` volume the kubelet calls NodePublishVolume, and the plugin:

  1. mounts a FRESH tmpfs at <root>/<pod-uid> (default /run/aether/uds/<uid>) with nosuid,nodev,noexec,nosymfollow, a size cap, an inode cap, and `mode=2770,uid=0,gid=<fsGroup>` — so only the pod's fsGroup (and root, i.e. the node proxy) can reach it, and files created in it inherit that group;
  2. bind-mounts it onto the kubelet's target_path, which is what the pod's containers see.

The per-pod directory is a pure function of the pod UID, which the kubelet supplies (podInfoOnMount). Nothing the workload controls — not the volume name, not a volume attribute — ever reaches a host path. The plugin is stateless: everything it needs to undo a publish is in the unpublish request's target_path and in the host's mount table.

Identity + Node services only, ephemeral inline volumes only; there is no Controller service and no staging.

Index

Constants

View Source
const (
	// DriverName is the CSI driver name: the CSIDriver object's name, the
	// `driver:` a pod's inline volume names, and what the plugin registers with
	// the kubelet as.
	DriverName = "csi.aether.io"
	// DefaultRoot is where the per-pod tmpfs mounts live on the host. /run is
	// tmpfs on every supported distro, so a reboot starts it empty and the
	// kubelet's republish rebuilds it.
	DefaultRoot = "/run/aether/uds"
	// DefaultKubeletRoot is the kubelet's --root-dir default (Talos too).
	DefaultKubeletRoot = "/var/lib/kubelet"

	// MinInodes is the smallest accepted per-pod inode cap. The tmpfs root
	// directory takes one and the socket another; much below a handful, an app
	// that also writes a pid or lock file next to its socket fails to start.
	MinInodes = 8
)
View Source
const RegistrationSocketName = DriverName + "-reg.sock"

RegistrationSocketName is the file the kubelet's plugin watcher looks for under <kubelet-root>/plugins_registry.

Variables

This section is empty.

Functions

func DefaultCSISocket

func DefaultCSISocket(kubeletRoot string) string

DefaultCSISocket is the CSI endpoint under a kubelet root.

func DefaultRegistrationSocket

func DefaultRegistrationSocket(kubeletRoot string) string

DefaultRegistrationSocket is the registration endpoint under a kubelet root.

func ParseSize

func ParseSize(s string) (int64, error)

ParseSize reads a tmpfs size cap in the Kubernetes binary-quantity spelling the chart uses ("1Mi", "512Ki") or as plain bytes ("1048576"), and returns it in bytes. It is deliberately not resource.ParseQuantity: that would link k8s.io/apimachinery into a binary whose deps_test forbids it.

func Probe

func Probe(csiSocket string) error

Probe is the liveness check: nil iff the CSI socket exists and is a socket.

func Serve

func Serve(ctx context.Context, cfg ServeConfig, d *Driver, log *slog.Logger) error

Serve runs the CSI server and the registration server until ctx is done or the kubelet reports a failed registration, then removes both sockets.

It returns nil on a clean ctx cancellation (SIGTERM) and an error when the kubelet refused the registration or a server died — the caller exits non-zero on either.

Order matters. The CSI socket is listening BEFORE the registration socket exists: the kubelet reacts to the registration socket appearing by calling GetInfo and then, immediately, NodeGetInfo on the CSI endpoint GetInfo named. On the way out the registration socket goes FIRST, so the kubelet deregisters the driver before its endpoint stops answering.

func TmpfsData

func TmpfsData(gid uint32, sizeBytes, inodes int64) string

TmpfsData is the tmpfs mount data string for a pod whose fsGroup is gid: mode/owner, the size cap and the inode cap (nr_inodes, #1107). Without nr_inodes the kernel default is half the node's RAM pages' worth of inodes, so a workload could fill its socket directory with empty files without ever touching the size cap.

Types

type Config

type Config struct {
	// NodeID is reported by NodeGetInfo: the node's name.
	NodeID string
	// KubeletRoot is the kubelet's --root-dir. Every target_path must lie under
	// <KubeletRoot>/pods/<pod-uid>/.
	KubeletRoot string
	// Root is the host directory holding the per-pod tmpfs mounts.
	Root string
	// SizeBytes caps each per-pod tmpfs.
	SizeBytes int64
	// Inodes caps the number of inodes (files, directories, sockets — the
	// root directory included) on each per-pod tmpfs: its nr_inodes. At least
	// MinInodes.
	Inodes int64
	// Version is reported by GetPluginInfo.
	Version string
}

Config is the node plugin's static configuration.

type Driver

type Driver struct {
	csi.UnimplementedIdentityServer
	csi.UnimplementedNodeServer
	// contains filtered or unexported fields
}

Driver implements the CSI Identity and Node services.

func NewDriver

func NewDriver(cfg Config, m Mounter, log *slog.Logger) (*Driver, error)

NewDriver validates cfg and returns a Driver that mutates the host through m.

func (*Driver) GetPluginCapabilities

GetPluginCapabilities reports none: in particular no CONTROLLER_SERVICE, so nothing ever calls a controller RPC on this plugin.

func (*Driver) GetPluginInfo

GetPluginInfo reports the driver name and version.

func (*Driver) NodeGetCapabilities

NodeGetCapabilities advertises VOLUME_MOUNT_GROUP only. With the CSIDriver's fsGroupPolicy: File that is what makes the kubelet hand the pod's fsGroup to NodePublishVolume as volume_mount_group — instead of recursively chowning the volume itself after the mount, which on this driver would race the app.

func (*Driver) NodeGetInfo

NodeGetInfo reports the node ID. No topology, no volume limit.

func (*Driver) NodePublishVolume

NodePublishVolume mounts the pod's per-pod tmpfs and binds it onto target_path. Every rejection is InvalidArgument, which the kubelet surfaces on the pod as a FailedMount event carrying the message.

func (*Driver) NodeUnpublishVolume

NodeUnpublishVolume unmounts the bind at target_path, then the pod's tmpfs, and removes both directories. Every step tolerates "already done", so a retry after a partial failure — or an unpublish of something never published — converges to OK.

func (*Driver) Probe

Probe reports ready: the plugin has no dependency that can be unready once it is serving.

type Mounter

type Mounter interface {
	// MountedFS reports whether path is itself a mount point and, if so, the
	// filesystem type of the topmost mount there.
	MountedFS(path string) (fstype string, mounted bool, err error)
	// MkdirAll is os.MkdirAll.
	MkdirAll(path string, perm os.FileMode) error
	// Mount is mount(2). The returned error wraps the errno.
	Mount(source, target, fstype string, flags uintptr, data string) error
	// Unmount is umount2(2).
	Unmount(target string, flags int) error
	// Remove is os.Remove; a missing path is NOT an error.
	Remove(path string) error
}

Mounter is every host-mount-namespace side effect the node plugin has. The driver logic talks only to this, so the unit tests exercise the whole publish and unpublish flow against a fake without root; the real implementation (mounter_linux.go) is a thin shim over golang.org/x/sys/unix — the distroless image has no mount(8) binary to shell out to.

func NewMounter

func NewMounter() Mounter

NewMounter returns the real, host-mutating Mounter.

type RegistrationServer

type RegistrationServer struct {
	pluginregistrationv1.UnimplementedRegistrationServer

	// Endpoint is the CSI socket path AS THE KUBELET SEES IT.
	Endpoint string
	Log      *slog.Logger
	// OnFailure is called when the kubelet reports that registration failed.
	// A plugin the kubelet refused serves nothing, so the caller exits
	// non-zero and lets the kubelet's restart backoff try again (with the
	// reason in the pod's last termination state) rather than sit Running
	// with the driver absent from the node's CSINode.
	OnFailure func(error)
}

RegistrationServer is the kubelet plugin-registration service, served by the node plugin itself on <kubelet-root>/plugins_registry/csi.aether.io-reg.sock. It replaces the csi-node-driver-registrar sidecar every other CSI driver ships: that sidecar's whole job is these two RPCs, and serving them here removes a third-party image, its supply chain and a second container whose liveness had to be kept in step with this one.

The kubelet's plugin watcher sees the socket appear, calls GetInfo to learn the driver name and where its CSI endpoint is, validates it (by calling the CSI endpoint's NodeGetInfo), and reports the verdict through NotifyRegistrationStatus.

func (*RegistrationServer) GetInfo

GetInfo tells the kubelet who we are and where the CSI endpoint is.

func (*RegistrationServer) NotifyRegistrationStatus

NotifyRegistrationStatus receives the kubelet's verdict.

type ServeConfig

type ServeConfig struct {
	CSISocket          string
	RegistrationSocket string
	// ShutdownGrace bounds the graceful gRPC stop; zero means 5s.
	ShutdownGrace time.Duration
}

ServeConfig names the two sockets the plugin serves.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL