Documentation
¶
Index ¶
- Constants
- Variables
- func IsNotFoundError(err error) bool
- func Quantity(value int64) apiresource.Quantity
- func Run(ctx context.Context, cfg Config) error
- func RunCDIHook(ctx context.Context, opts CDIHookOptions, stdin io.Reader) error
- func StageHookBinary(pluginDir string) (string, error)
- func StartNodePlugin(ctx context.Context, driver *Driver, kube kubernetes.Interface, ...) (*kubeletplugin.Helper, error)
- func ThunderClientName(claimUID types.UID) string
- func ThunderGuestSetupSecretName(claimName string) string
- type AllocatedDevice
- type Allocation
- type CDIDeviceStore
- type CDIHookOptions
- type Config
- type Driver
- func (d *Driver) CDIInstallCommand() string
- func (d *Driver) HandleError(ctx context.Context, err error, msg string)
- func (d *Driver) PrepareResourceClaims(ctx context.Context, claims []*resourcev1.ResourceClaim) (map[types.UID]kubeletplugin.PrepareResult, error)
- func (d *Driver) UnprepareResourceClaims(ctx context.Context, claims []kubeletplugin.NamespacedObject) (map[types.UID]error, error)
- type FileCDIDeviceStore
- type GuestArtifacts
- type GuestConfigStore
- type KubernetesGuestConfigStore
- type KubernetesThunderClientStore
- type LibthunderArtifact
- type LibthunderCache
- type NodeInfo
- type PluginConfig
- type ResourceConsumer
- type ThunderClient
- type ThunderClientConfig
- type ThunderClientStore
- type ThunderTokenIssuer
- type TokenIssuer
Constants ¶
const ( // CDIHookCommand is the argv[1] the CDI spec invokes the daemon with. CDIHookCommand = "cdi-hook" // ThunderGuestDir is where the client lives inside a container. It is not // configurable: LD_PRELOAD names this path. ThunderGuestDir = "/etc/thunder" )
const ( EnvNode = "NODE" EnvZone = "ZONE" EnvAdvertisedIP = "ADVERTISED_IP" EnvMinNVDriverVersion = "MIN_DRIVER_VERSION" EnvThunderAPIURL = "THUNDER_API_URL" EnvThunderAPIToken = "THUNDER_API_TOKEN" EnvHostRoot = "HOST_ROOT" EnvLibCUDAPath = "LIBCUDA_PATH" EnvLibNVMLPath = "LIBNVIDIA_ML_PATH" EnvNVSMIPath = "NVIDIA_SMI_PATH" EnvZoneLabel = "NODE_ZONE_LABEL" EnvAdvertisedIPLabel = "NODE_ADVERTISED_IP_LABEL" EnvHostTargetPID = "HOST_TARGET_PID" EnvDRAEnabled = "DRA_ENABLED" EnvDRADriverName = "DRA_DRIVER_NAME" EnvCDISpecDir = "CDI_SPEC_DIR" EnvThunderClientNS = "THUNDER_CLIENT_NAMESPACE" EnvKubeletPluginDir = "KUBELET_PLUGIN_DIR" EnvKubeletRegistrarDir = "KUBELET_REGISTRAR_DIR" EnvThunderInstallURL = "THUNDER_INSTALL_URL" EnvThunderTelemetryURL = "THUNDER_TELEMETRY_URL" EnvArtifactBaseURL = "THUNDER_ARTIFACT_BASE_URL" EnvLibthunderURL = "LIBTHUNDER_URL" EnvLibthunderSHA256 = "LIBTHUNDER_SHA256" EnvCABundlePath = "CA_BUNDLE_PATH" )
const ( DefaultHostRoot = "/host" DefaultLibCUDAPath = "/usr/lib/x86_64-linux-gnu/libcuda.so.1" DefaultLibNVMLPath = "/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1" DefaultNVSMIPath = "/usr/bin/nvidia-smi" DefaultZoneLabel = "topology.kubernetes.io/zone" DefaultAdvertisedIPLabel = "thundercompute.com/advertised-ip" DefaultHostTargetPID = "1" DefaultDRAEnabled = true DefaultDRADriverName = DefaultDriverName DefaultCDISpecDir = "/var/run/cdi" DefaultThunderClientNS = DefaultThunderClientNamespace DefaultKubeletPluginDir = "/var/lib/kubelet/plugins/" + DefaultDriverName DefaultKubeletRegistrarDir = "/var/lib/kubelet/plugins_registry" // DefaultThunderInstallURL is the installer the CDI hook reads the // pinned libthunder.so digest out of. It is never executed. DefaultThunderInstallURL = "https://get.thundercompute.com/install.sh" DefaultThunderTelemetryURL = "https://telemetry.thundercompute.com:2096" // DefaultCABundlePath is the node trust store staged into containers // that ship none of their own. DefaultCABundlePath = "/etc/ssl/certs/ca-certificates.crt" )
const ( DefaultDriverName = thunderDomain DefaultThunderClientNamespace = "thunder-system" DefaultCDIKind = thunderDomain + "/gpu" GPUTypeAttributeName = thunderDomain + "/gpu_type" ZoneAttributeName = thunderDomain + "/zone" )
const ( ServiceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token" ServiceAccountCA = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" )
const ( // ThunderReconcileInterval is how often the daemon re-checks that the node // is in the state it should be in. ThunderReconcileInterval = 10 * time.Second // ThunderReconcileMaxBackoff caps the wait between passes after repeated // failures. A node that cannot enroll keeps retrying, but without hammering // the Thunder API or re-running the installer in a tight loop. ThunderReconcileMaxBackoff = 5 * time.Minute )
const ( ThunderGuestInstallScriptKey = "install-thunder-client.sh" ThunderGuestSecretTokenKey = "enrollment-token" ThunderGuestTokenPath = "/mnt/thunder-setup/enrollment-token" )
const ( ThunderEnrollmentTokenEnv = "THUNDER_ENROLLMENT_TOKEN" ThunderClientInstallCommandEnv = "THUNDER_CLIENT_INSTALL_COMMAND" )
Variables ¶
var ErrNotFound = errors.New("not found")
var ThunderClientGVR = thunderclient.GVR
ThunderClientGVR addresses the per-claim ThunderClient resource.
Functions ¶
func IsNotFoundError ¶
func Quantity ¶
func Quantity(value int64) apiresource.Quantity
func RunCDIHook ¶
RunCDIHook stages the Thunder client into a container that is being created.
It runs on the host, once per container, with the container filesystem at <bundle>/rootfs. Everything the workload needs is written there: the library LD_PRELOAD names, and the config.json identifying the client. Nothing is assumed about the image — it needs no shell, no curl, and no root.
func StageHookBinary ¶
StageHookBinary copies the running daemon to a host path the container runtime can execute, and returns that path.
The hook runs as a bare process on the node, so it cannot be the binary inside this container. The kubelet plugin directory is a hostPath mounted at the same path on both sides, which makes a copy written here valid in the CDI spec the runtime reads. This is the model nvidia-ctk uses.
func StartNodePlugin ¶
func StartNodePlugin(ctx context.Context, driver *Driver, kube kubernetes.Interface, cfg PluginConfig) (*kubeletplugin.Helper, error)
func ThunderClientName ¶
func ThunderGuestSetupSecretName ¶
ThunderGuestSetupSecretName is the Secret a VM mounts to set itself up as a Thunder client.
Types ¶
type AllocatedDevice ¶
AllocatedDevice is one GPU the scheduler assigned to a claim.
type Allocation ¶
type Allocation struct {
ClaimUID types.UID
ClaimNamespace string
ClaimName string
// Devices holds every device the claim was allocated from this driver.
// The operator publishes one device per GPU, so a multi-GPU claim has one
// entry per GPU and GPUCount is simply how many there are.
Devices []AllocatedDevice
RequestName string
PoolName string
DeviceName string
Consumer ResourceConsumer
NodeName string
Zone string
GPUType string
GPUCount int64
}
type CDIDeviceStore ¶
type CDIDeviceStore interface {
Create(ctx context.Context, allocation Allocation, token string) (qualifiedName string, err error)
Remove(ctx context.Context, qualifiedName string) error
// StagedClientID reports the Thunder client the CDI hook enrolled for this
// device, or "" if no container ever started and none was created.
StagedClientID(qualifiedName string) string
}
type CDIHookOptions ¶
type CDIHookOptions struct {
// StateDir is the per-claim directory the daemon staged the enrollment
// token into, and where the exchanged config is cached.
StateDir string
// CentralURL and TelemetryURL configure the client being enrolled.
CentralURL string
TelemetryURL string
// InstallURL, ArtifactBaseURL, LibthunderURL and LibthunderSHA256 locate
// the client library.
InstallURL string
ArtifactBaseURL string
LibthunderURL string
LibthunderSHA256 string
// CacheDir holds this node's shared copy of the library.
CacheDir string
// CABundlePath is the node's trust store, staged into containers that
// have none of their own.
CABundlePath string
// ClientName is what this client is called in Thunder. It is the pod name,
// so an enrollment can be traced back to the workload holding it.
ClientName string
}
CDIHookOptions is what the CDI spec encodes into the hook's argv.
func ParseCDIHookArgs ¶
func ParseCDIHookArgs(args []string) (CDIHookOptions, error)
ParseCDIHookArgs reads the flags the CDI spec encodes into the hook argv.
type Config ¶
type Config struct {
Node string
Zone string
// AdvertisedIP is the address Thunder clients use to reach this node.
// When empty it is resolved from AdvertisedIPLabel and then from the
// node's own IP. See resolveNodeAttributes.
AdvertisedIP string
MinDriverVersion string
ThunderAPIURL string
ThunderAPIToken string
HostRoot string
LibCUDAPath string
LibNVMLPath string
NVSMIPath string
ZoneLabel string
AdvertisedIPLabel string
HostTargetPID string
DRAEnabled bool
DRADriverName string
CDISpecDir string
ThunderClientNS string
KubeletPluginDir string
KubeletRegistrarDir string
// ThunderInstallURL is read, not run: the CDI hook takes the pinned
// libthunder.so digest from it so a node stages the same build the
// installer would have.
ThunderInstallURL string
ThunderTelemetryURL string
ArtifactBaseURL string
// LibthunderURL and LibthunderSHA256 pin the library explicitly and
// skip the installer entirely.
LibthunderURL string
LibthunderSHA256 string
CABundlePath string
}
func ConfigFromEnv ¶
type Driver ¶
type Driver struct {
DriverName string
NodeName string
Kube kubernetes.Interface
Tokens TokenIssuer
Clients ThunderClientStore
CDI CDIDeviceStore
Guest GuestConfigStore
Logger *slog.Logger
}
func (*Driver) CDIInstallCommand ¶
func (*Driver) HandleError ¶
func (*Driver) PrepareResourceClaims ¶
func (d *Driver) PrepareResourceClaims(ctx context.Context, claims []*resourcev1.ResourceClaim) (map[types.UID]kubeletplugin.PrepareResult, error)
func (*Driver) UnprepareResourceClaims ¶
func (d *Driver) UnprepareResourceClaims(ctx context.Context, claims []kubeletplugin.NamespacedObject) (map[types.UID]error, error)
type FileCDIDeviceStore ¶
type FileCDIDeviceStore struct {
SpecDir string
StateDir string
Kind string
LibCUDAPath string
LibNVMLPath string
NVSMIPath string
ClientInstallCommand string
// HookPath is the host executable the container runtime runs while
// creating a container. Empty disables the hook, which leaves the
// container to supply its own Thunder client.
HookPath string
// CacheDir is where the hook keeps this node's copy of libthunder.so.
CacheDir string
// The hook is told where to enrol and where to fetch the library, since
// it runs as a bare process on the host with none of the daemon's config.
CentralURL string
TelemetryURL string
InstallURL string
ArtifactBaseURL string
LibthunderURL string
LibthunderSHA256 string
CABundlePath string
}
func NewFileCDIDeviceStore ¶
func NewFileCDIDeviceStore(specDir string) *FileCDIDeviceStore
func (*FileCDIDeviceStore) Create ¶
func (s *FileCDIDeviceStore) Create(ctx context.Context, allocation Allocation, token string) (string, error)
func (*FileCDIDeviceStore) Remove ¶
func (s *FileCDIDeviceStore) Remove(ctx context.Context, qualifiedName string) error
func (*FileCDIDeviceStore) StagedClientID ¶
func (s *FileCDIDeviceStore) StagedClientID(qualifiedName string) string
StagedClientID reports the Thunder client the CDI hook enrolled for this device. The hook caches the exchanged config in the claim's state dir, and the client ID in it is the only record of the enrollment the daemon has: the exchange happens in the hook, on container create, long after prepare.
An empty string means no container ever started for this claim, so no client was created and there is nothing to revoke.
type GuestArtifacts ¶
type GuestConfigStore ¶
type GuestConfigStore interface {
Create(ctx context.Context, allocation Allocation, token string, installCommand string) (GuestArtifacts, error)
Remove(ctx context.Context, artifacts GuestArtifacts) error
}
type KubernetesGuestConfigStore ¶
type KubernetesGuestConfigStore struct {
Client kubernetes.Interface
}
func NewKubernetesGuestConfigStore ¶
func NewKubernetesGuestConfigStore(client kubernetes.Interface) *KubernetesGuestConfigStore
func (*KubernetesGuestConfigStore) Create ¶
func (s *KubernetesGuestConfigStore) Create(ctx context.Context, allocation Allocation, token string, installCommand string) (GuestArtifacts, error)
func (*KubernetesGuestConfigStore) Remove ¶
func (s *KubernetesGuestConfigStore) Remove(ctx context.Context, artifacts GuestArtifacts) error
type KubernetesThunderClientStore ¶
func NewKubernetesThunderClientStore ¶
func NewKubernetesThunderClientStore(client dynamic.Interface, namespace string) *KubernetesThunderClientStore
func (*KubernetesThunderClientStore) Delete ¶
Delete releases the finalizer and removes the resource. The caller has already revoked the Thunder enrollment by this point, so holding the resource any longer would serve no purpose.
func (*KubernetesThunderClientStore) Get ¶
func (s *KubernetesThunderClientStore) Get(ctx context.Context, claimUID types.UID) (*ThunderClient, error)
func (*KubernetesThunderClientStore) Upsert ¶
func (s *KubernetesThunderClientStore) Upsert(ctx context.Context, client ThunderClient) error
type LibthunderArtifact ¶
LibthunderArtifact identifies one build of the client library.
type LibthunderCache ¶
type LibthunderCache struct {
// Dir is the cache root. It must be readable by the CDI hook, which means
// somewhere under the kubelet plugin directory.
Dir string
// InstallURL is the installer whose pin is authoritative. Ignored when
// both URL and SHA256 are set.
InstallURL string
// URL and SHA256 pin the library explicitly, skipping the installer.
URL string
SHA256 string
// ArtifactBaseURL overrides only the host, keeping the installer's digest.
ArtifactBaseURL string
// HTTP is used for both fetches. Nil means http.DefaultClient.
HTTP *http.Client
// contains filtered or unexported fields
}
LibthunderCache resolves and caches libthunder.so on a node.
func (*LibthunderCache) Ensure ¶
func (c *LibthunderCache) Ensure(ctx context.Context) (string, error)
Ensure downloads libthunder.so unless the node already has that exact build, and returns the path to it. The digest is verified before the file is published, so a truncated or tampered download never becomes the library a workload preloads.
func (*LibthunderCache) Path ¶
func (c *LibthunderCache) Path(artifact LibthunderArtifact) string
Path is where a build is cached, whether or not it has been downloaded. The digest is in the name, so a new release lands as a new file rather than silently replacing the one running containers were staged from.
func (*LibthunderCache) Resolve ¶
func (c *LibthunderCache) Resolve(ctx context.Context) (LibthunderArtifact, error)
Resolve reports which build of libthunder.so this node should stage. The answer is memoised, since it only changes when Thunder ships a release.
type NodeInfo ¶
type NodeInfo struct {
Labels map[string]string
// InternalIP and ExternalIP come from status.addresses and are the
// defaults for the advertised IP, in that order.
InternalIP string
ExternalIP string
}
NodeInfo is the subset of a Kubernetes Node the daemon needs before it can build in-cluster clients.
type PluginConfig ¶
type ResourceConsumer ¶
type ThunderClient ¶
type ThunderClient struct {
ClaimUID types.UID
ClaimNamespace string
ClaimName string
GPUType string
GPUCount int64
Zone string
NodeName string
RequestName string
PoolName string
DeviceName string
Consumer ResourceConsumer
CDIName string
EnrollmentTokenID string
GuestNamespace string
GuestSecret string
CreatedAt time.Time
UpdatedAt time.Time
}
type ThunderClientConfig ¶
type ThunderClientConfig struct {
DeviceID string `json:"deviceId"`
ClientID string `json:"clientId"`
OrgID string `json:"orgId"`
GPUType string `json:"gpuType"`
GPUCount int `json:"gpuCount"`
CentralAPIURL string `json:"centralApiUrl"`
AuthToken string `json:"authToken"`
Claims string `json:"claims"`
EnableGRPCTLS bool `json:"enableGrpcTls"`
ThunderdDiscoveryEnabled bool `json:"thunderdDiscoveryEnabled"`
TelemetryCollector string `json:"telemetryCollector"`
}
ThunderClientConfig is /etc/thunder/config.json, the file libthunder.so reads to learn which client it is and which control plane to talk to. The field set and JSON shape mirror what the Thunder installer writes, so a container staged by the CDI hook is indistinguishable from one set up by `curl | sh`.
func ExchangeClientEnrollment ¶
func ExchangeClientEnrollment(ctx context.Context, httpClient *http.Client, centralURL, telemetryURL, enrollmentToken, hostname string) (ThunderClientConfig, error)
ExchangeClientEnrollment spends a single-use client enrollment token and returns the config the container needs. The token is consumed by this call, which is why it happens once per claim and is cached afterwards.
type ThunderClientStore ¶
type ThunderTokenIssuer ¶
ThunderTokenIssuer adapts the Thunder SDK to the daemon DRA plugin.
func (ThunderTokenIssuer) Mint ¶
func (i ThunderTokenIssuer) Mint(ctx context.Context, allocation Allocation) (string, string, time.Time, error)
func (ThunderTokenIssuer) Revoke ¶
func (i ThunderTokenIssuer) Revoke(ctx context.Context, tokenID string) error
func (ThunderTokenIssuer) RevokeClient ¶
func (i ThunderTokenIssuer) RevokeClient(ctx context.Context, clientID string) error
RevokeClient revokes a client that was enrolled by exchanging a token.
type TokenIssuer ¶
type TokenIssuer interface {
Mint(ctx context.Context, allocation Allocation) (tokenID string, token string, expiresAt time.Time, err error)
Revoke(ctx context.Context, tokenID string) error
// RevokeClient revokes the client an enrollment token was exchanged for.
// Revoking the token alone does not: once spent, the token and the client
// it produced are separate objects in Thunder.
RevokeClient(ctx context.Context, clientID string) error
}