Skip to content

Kubernetes DaemonSet Deployment Reference ​

Reference documentation for deploying plexd as a Kubernetes DaemonSet. Covers environment detection, configuration, CRD controller, audit log reader, authentication, and manifest structure.

KubernetesEnvironment ​

KubernetesEnvironment holds metadata about the Kubernetes environment in which plexd is running. A nil value from Detect() indicates the process is not running inside a pod.

go
type KubernetesEnvironment struct {
    InCluster           bool
    Namespace           string
    PodName             string
    NodeName            string
    ServiceAccountToken string
}
FieldSource
InClustertrue if KUBERNETES_SERVICE_HOST env var is set
NamespaceRead from /var/run/secrets/kubernetes.io/serviceaccount/namespace
PodNameHOSTNAME env var
NodeNameMY_NODE_NAME env var (set via downward API)
ServiceAccountToken/var/run/secrets/kubernetes.io/serviceaccount/token

EnvironmentDetector interface ​

go
type EnvironmentDetector interface {
    Detect() *KubernetesEnvironment
}

DefaultDetector implements this interface using real environment variables and filesystem paths.

Config ​

Configuration for Kubernetes integration. Passed as a constructor argument; no file I/O in this package.

FieldTypeDefaultDescription
EnabledboolfalseMust be explicitly enabled
CRDEnabledbooltrue (when Enabled)Whether CRD management is active
Namespacestring(empty — auto-detected)Override namespace
AuditLogPathstring/var/log/kubernetes/audit/audit.logPath to Kubernetes audit log
CRDSyncIntervaltime.Duration10sInterval for CRD state reconciliation
TokenPathstring/var/run/secrets/kubernetes.io/serviceaccount/tokenPath to service account token

Methods ​

  • ApplyDefaults(env *KubernetesEnvironment) — Sets default values for zero-valued fields. When env is non-nil and InCluster is true, sets Enabled=true, CRDEnabled=true, and auto-detects Namespace.
  • Validate() error — Skips validation when Enabled is false. Validates Namespace non-empty, AuditLogPath non-empty, CRDSyncInterval >= 1s, TokenPath non-empty.

PlexdNodeState CRD ​

The PlexdNodeState custom resource definition exposes node state to the Kubernetes API.

go
type PlexdNodeState struct {
    Name            string               `json:"name"`
    Namespace       string               `json:"namespace"`
    UID             string               `json:"uid,omitempty"`
    ResourceVersion string               `json:"resourceVersion,omitempty"`
    Labels          map[string]string    `json:"labels,omitempty"`
    Spec            PlexdNodeStateSpec   `json:"spec"`
    Status          PlexdNodeStateStatus `json:"status,omitempty"`
    LastUpdate      time.Time            `json:"lastUpdate"`
}

type PlexdNodeStateSpec struct {
    NodeID     string            `json:"nodeId"`
    MeshIP     string            `json:"meshIp,omitempty"`
    Metadata   map[string]string `json:"metadata,omitempty"`
    Data       []DataEntry       `json:"data,omitempty"`
    SecretRefs []SecretRef       `json:"secretRefs,omitempty"`
}

type PlexdNodeStateStatus struct {
    Report []DataEntry `json:"report,omitempty"`
}

CRD manifest: deploy/kubernetes/crds/plexdnodestate-crd.yaml

PropertyValue
API groupplexd.plexsphere.com
KindPlexdNodeState
Pluralplexdnodestates
Short namepns
ScopeNamespaced
Versionv1alpha1

Printer columns: Node ID, Mesh IP, Age.

KubeClient interface ​

Abstracts Kubernetes API interactions for testability. All state-modifying methods are idempotent.

go
type KubeClient interface {
    GetNodeState(ctx context.Context, namespace, name string) (*PlexdNodeState, error)
    CreateNodeState(ctx context.Context, state *PlexdNodeState) error
    UpdateNodeState(ctx context.Context, state *PlexdNodeState) error
    DeleteNodeState(ctx context.Context, namespace, name string) error
    WatchNodeState(ctx context.Context, namespace, name string) (<-chan PlexdNodeStateEvent, error)
    CreateSecret(ctx context.Context, secret *KubeSecret) error
    UpdateSecret(ctx context.Context, secret *KubeSecret) error
    DeleteSecret(ctx context.Context, namespace, name string) error
    WatchPlexdHooks(ctx context.Context, namespace string) (<-chan PlexdHookEvent, error)
    UpdatePlexdHookStatus(ctx context.Context, hook *PlexdHook) error
    CreateJob(ctx context.Context, job *PlexdJob) error
}

Sentinel errors ​

ErrorValueDescription
ErrNotFoundkubernetes: resource not foundResource does not exist
ErrAlreadyExistskubernetes: resource already existsResource already exists
ErrUnauthorizedkubernetes: unauthorizedClient lacks valid credentials

CRDController ​

Periodically syncs local node state to a PlexdNodeState CRD resource.

go
type CRDController struct {
    client         KubeClient
    reportNotifier ReportNotifier
    cfg            Config
    namespace      string
    nodeID         string
    meshIP         string
    resourceName   string // "node-{node_id}"
    logger         *slog.Logger
}

Constructor ​

go
func NewCRDController(client KubeClient, cfg Config, nodeID, meshIP, namespace string, reportNotifier ReportNotifier, logger *slog.Logger) *CRDController

Config defaults are applied automatically.

ReportNotifier interface ​

go
type ReportNotifier interface {
    NotifyChange()
}

Called when the CRDController detects new or changed report entries in the status subresource.

Start ​

go
func (c *CRDController) Start(ctx context.Context) error

Creates or updates the PlexdNodeState resource and starts the status watcher goroutine. Blocks until ctx is cancelled.

Start behavior ​

  1. Ensures the CRD resource exists (create or update)
  2. Starts a watch on the resource for status changes
  3. When status report entries change, calls ReportNotifier.NotifyChange()
  4. Blocks until context is cancelled

K8sAuditLogReader ​

Implements auditfwd.K8sAuditReader by reading Kubernetes audit log files in JSON-lines format.

go
type K8sAuditLogReader struct {
    path   string
    offset int64
    logger *slog.Logger
}

Constructor ​

go
func NewK8sAuditLogReader(path string, logger *slog.Logger) *K8sAuditLogReader

ReadEvents ​

go
func (r *K8sAuditLogReader) ReadEvents(ctx context.Context) ([]auditfwd.K8sAuditEntry, error)

Behavior:

  • Reads from r.offset to end of file; only new entries since last call are returned
  • If the file does not exist or cannot be opened, returns an error
  • If the file was truncated (offset > file size), resets offset to 0 and reads from the beginning
  • Malformed JSON lines are skipped with a warning log
  • Timestamp is parsed from stageTimestamp, falling back to requestReceivedTimestamp
  • objectRef and responseStatus may be nil in the audit event; handled gracefully

Audit log format (JSON-lines):

Each line is a Kubernetes audit event:

json
{"kind":"Event","verb":"create","user":{"username":"admin"},"objectRef":{"resource":"pods","namespace":"default","name":"web-1"},"responseStatus":{"code":201},"stageTimestamp":"2025-01-15T10:30:01.000000Z"}

TokenReviewAuthenticator ​

Validates Kubernetes service account tokens via the TokenReview API.

go
type TokenReviewAuthenticator struct {
    client    TokenReviewClient
    logger    *slog.Logger
    audiences []string
}

Constructor ​

go
func NewTokenReviewAuthenticator(client TokenReviewClient, logger *slog.Logger, audiences []string) *TokenReviewAuthenticator

Authenticate ​

go
func (a *TokenReviewAuthenticator) Authenticate(ctx context.Context, token string) (*TokenReviewResult, error)
ConditionResult
Empty tokenError: kubernetes: authenticate: empty token
Client errorError: kubernetes: authenticate: review failed
Not authenticatedError: kubernetes: authenticate: token not authenticated
Audience mismatchError: kubernetes: authenticate: audience mismatch
SuccessReturns *TokenReviewResult

HTTPTokenReviewClient ​

Production implementation that calls the Kubernetes API server:

go
func NewHTTPTokenReviewClient(apiServer, saTokenPath string) *HTTPTokenReviewClient
  • Posts to {apiServer}/apis/authentication.k8s.io/v1/tokenreviews
  • Authenticates with the service account token read from saTokenPath
  • Loads cluster CA from /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  • TLS minimum version: 1.2

HookLister ​

Lists PlexdHook resources through the API server for the capability manifest. It is one authenticated GET per call: no watch, no write.

go
func NewInClusterHookLister(env *KubernetesEnvironment) (*HookLister, error)
func (l *HookLister) ListPlexdHooks(ctx context.Context, namespace string) ([]PlexdHook, error)
  • Connects to https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT; construction fails when either variable is unset
  • Trusts the cluster CA at DefaultCACertPath; construction fails when the bundle cannot be read or holds no certificate
  • GETs /apis/plexd.plexsphere.com/v1alpha1/namespaces/{namespace}/plexdhooks
  • Reads the service account token from env.ServiceAccountToken on every call, because the kubelet rotates it
  • Has no timeout of its own; the caller's context bounds each call
  • TLS minimum version: 1.2
ResponseResult
200 OKThe resources, or an empty slice
401, 403Error wrapping ErrUnauthorized
404Error wrapping ErrNotFound (CRD not installed)
Other statusError naming the status

Boot-time PlexdHook discovery ​

plexd up runs DefaultDetector.Detect() before it sends the capability manifest. In a pod it lists the PlexdHook resources in the ServiceAccount's namespace once, with a 10-second budget, and reports the digest-pinned ones as plexd_hooks (see PlexdHook CRD). The existing plexdhooks get, list, watch grant in the plexd ClusterRole authorizes the list, so no RBAC change is needed. Outside a cluster the step costs one environment variable check. Inside one, a missing grant, a missing CRD, a timeout, or an unreachable API server logs one warning, and the manifest goes out without plexd_hooks. The node boots either way.

Kubernetes manifests ​

All manifests are in deploy/kubernetes/.

FileResources
namespace.yamlNamespace (plexd-system)
crds/plexdnodestate-crd.yamlCustomResourceDefinition (PlexdNodeState)
crds/plexdhook-crd.yamlCustomResourceDefinition (PlexdHook)
serviceaccount.yamlServiceAccount
rbac.yamlClusterRole + ClusterRoleBinding + consumer roles
daemonset.yamlDaemonSet with host networking and capabilities
secret.yamlExample Secret template for bootstrap token

RBAC permissions ​

The plexd ClusterRole grants:

API GroupResourceVerbs
plexd.plexsphere.complexdnodestatesget, list, watch, create, update, patch, delete
plexd.plexsphere.complexdnodestates/statusget, patch, update
(core)secretscreate, get, update, delete
plexd.plexsphere.complexdhooksget, list, watch
plexd.plexsphere.complexdhooks/statusget, update, patch
batchjobscreate, get, list, watch
authentication.k8s.iotokenreviewscreate

Consumer RBAC roles:

RoleResourcesVerbs
plexd-state-readerplexdnodestatesget, list, watch
plexd-state-reporterplexdnodestates/statusget, patch
plexd-secrets-readersecretsget
plexd-hook-readerplexdhooksget, list, watch

DaemonSet configuration ​

SettingValueReason
hostNetworktrueWireGuard mesh requires host networking
dnsPolicyClusterFirstWithHostNetRequired with hostNetwork
priorityClassNamesystem-node-criticalEnsures scheduling on resource pressure
tolerationsoperator: ExistsRun on all nodes including control plane
readOnlyRootFilesystemtrueSecurity hardening
CapabilitiesNET_ADMIN, NET_RAWWireGuard interface management and the nftables policy chain. Without NET_ADMIN, plexd up fails its firewall pre-flight and exits before registering

Environment variables ​

VariableSourceDescription
MY_NODE_NAMEfieldRef: spec.nodeNameKubernetes node name (downward API)
PLEXD_BOOTSTRAP_TOKENsecretKeyRef: plexd-bootstrapBootstrap token from Secret

Volume mounts ​

Mount pathSourceAccess
/etc/plexdConfigMap plexd-configread-only
/var/lib/plexdhostPathread-write
/var/run/plexdhostPathread-write
/var/log/kubernetes/audithostPathread-only

Constants ​

ConstantValue
ServiceAccountBasePath/var/run/secrets/kubernetes.io/serviceaccount
DefaultTokenPath{ServiceAccountBasePath}/token
DefaultNamespacePath{ServiceAccountBasePath}/namespace
DefaultCACertPath{ServiceAccountBasePath}/ca.crt
DefaultAuditLogPath/var/log/kubernetes/audit/audit.log
DefaultCRDSyncInterval10s

See also ​