DaemonSet Primitive¶
The daemonset primitive wraps a Kubernetes DaemonSet and provides health tracking, suspension, and a typed mutation
API for managing pod spec and containers as part of the component lifecycle. A DaemonSet runs one pod per qualifying
node.
Capabilities¶
| Lifecycle interface | Reported status values |
|---|---|
Alive |
Healthy, Creating, Updating, Scaling, Failing |
Graceful |
Healthy, Degraded, Down |
Suspendable |
PendingSuspension, Suspending, Suspended |
Guardable |
Blocked |
DataExtractable |
(side-effecting, no status) |
Building a DaemonSet Primitive¶
import "github.com/sourcehawk/operator-component-framework/pkg/primitives/daemonset"
base := &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: "log-collector",
Namespace: owner.Namespace,
},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "log-collector"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": "log-collector"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "collector"},
},
},
},
},
}
resource, err := daemonset.NewBuilder(base).
WithMutation(MyFeatureMutation(owner.Spec.Version)).
Build()
Mutations¶
Each mutation is a named daemonset.Mutation that receives a *daemonset.Mutator and records edits through typed
editors.
func MonitoringMutation(version string, enabled bool) daemonset.Mutation {
return daemonset.Mutation{
Name: "monitoring",
Feature: feature.NewVersionGate(version, nil).When(enabled),
Mutate: func(m *daemonset.Mutator) error {
m.EnsureContainer(corev1.Container{
Name: "metrics-exporter",
Image: "prom/node-exporter:v1.8.0",
})
return nil
},
}
}
See the mutation system, boolean gating, and version gating.
Internal Mutation Ordering¶
Within each feature, edits run in this fixed category order:
| Step | Category | What it affects |
|---|---|---|
| 1 | Object metadata edits | Labels and annotations on the DaemonSet object |
| 2 | DaemonSetSpec edits | Update strategy, min ready seconds, revision history limit |
| 3 | Pod template metadata edits | Labels and annotations on the pod template |
| 4 | Pod spec edits | Volumes, tolerations, node selectors, service account, security context |
| 5 | Regular container presence | Adding or removing containers from spec.template.spec.containers |
| 6 | Regular container edits | Env vars, args, resources (snapshot taken after step 5) |
| 7 | Init container presence | Adding or removing containers from spec.template.spec.initContainers |
| 8 | Init container edits | Env vars, args, resources (snapshot taken after step 7) |
Container edits (steps 6 and 8) are evaluated against a snapshot taken after presence operations in the same feature.
Relevant Editors¶
For the generic editor and selector concepts, see mutation editors and container selectors.
DaemonSetSpecEditor¶
Controls DaemonSet-level settings via m.EditDaemonSetSpec.
Available methods: SetUpdateStrategy, SetMinReadySeconds, SetRevisionHistoryLimit, Raw.
m.EditDaemonSetSpec(func(e *editors.DaemonSetSpecEditor) error {
e.SetMinReadySeconds(30)
e.SetRevisionHistoryLimit(5)
return nil
})
Use Raw() for fields the typed API does not cover:
m.EditDaemonSetSpec(func(e *editors.DaemonSetSpecEditor) error {
e.Raw().UpdateStrategy = appsv1.DaemonSetUpdateStrategy{
Type: appsv1.RollingUpdateDaemonSetStrategyType,
}
return nil
})
PodSpecEditor¶
Manages pod-level configuration via m.EditPodSpec.
Available methods: SetServiceAccountName, EnsureVolume, RemoveVolume, EnsureToleration, RemoveTolerations,
EnsureNodeSelector, RemoveNodeSelector, EnsureImagePullSecret, RemoveImagePullSecret, SetPriorityClassName,
SetHostNetwork, SetHostPID, SetHostIPC, SetSecurityContext, Raw.
m.EditPodSpec(func(e *editors.PodSpecEditor) error {
e.SetServiceAccountName("log-collector-sa")
e.EnsureVolume(corev1.Volume{
Name: "varlog",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: "/var/log"},
},
})
return nil
})
ContainerEditor¶
Modifies individual containers via m.EditContainers or m.EditInitContainers, combined with a
container selector.
Available methods: EnsureEnvVar, EnsureEnvVars, RemoveEnvVar, RemoveEnvVars, EnsureArg, EnsureArgs,
RemoveArg, RemoveArgs, SetResourceLimit, SetResourceRequest, SetResources, Raw.
m.EditContainers(selectors.ContainerNamed("collector"), func(e *editors.ContainerEditor) error {
e.EnsureEnvVar(corev1.EnvVar{Name: "LOG_LEVEL", Value: "info"})
e.SetResourceLimit(corev1.ResourceCPU, resource.MustParse("200m"))
return nil
})
ObjectMetaEditor¶
Modifies labels and annotations. Use m.EditObjectMetadata for the DaemonSet itself or m.EditPodTemplateMetadata
for the pod template.
Available methods: EnsureLabel, RemoveLabel, EnsureAnnotation, RemoveAnnotation, Raw.
m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error {
e.EnsureLabel("app.kubernetes.io/version", version)
return nil
})
Convenience Methods¶
| Method | Equivalent to |
|---|---|
EnsureContainerEnvVar(ev) |
EditContainers(AllContainers(), ...) → EnsureEnvVar(ev) |
RemoveContainerEnvVar(name) |
EditContainers(AllContainers(), ...) → RemoveEnvVar(name) |
EnsureContainerArg(arg) |
EditContainers(AllContainers(), ...) → EnsureArg(arg) |
RemoveContainerArg(arg) |
EditContainers(AllContainers(), ...) → RemoveArg(arg) |
No EnsureReplicas on DaemonSet
DaemonSets have no replicas field. Use node selectors, tolerations, and affinities in the pod spec to control which nodes run the pods.
Workload-Kind-Agnostic Mutations¶
A mutation written against primitives.WorkloadMutator can be applied to a DaemonSet builder using
daemonset.LiftMutation. This lets one emitter function target DaemonSets, Deployments, and StatefulSets without
duplicating code.
See workload-kind-agnostic mutations for the full pattern.
Suspension¶
DaemonSets have no replicas field, so there is no clean in-place pause mechanism. By default, the DaemonSet is deleted when the component is suspended and recreated when unsuspended.
DefaultDeleteOnSuspendHandlerreturnstrue.DefaultSuspendMutationHandleris a no-op (deletion is handled by the framework).DefaultSuspensionStatusHandleralways reportsSuspendedwith reason"DaemonSet deleted on suspend".
Override these handlers via WithCustomSuspendDeletionDecision, WithCustomSuspendMutation, and
WithCustomSuspendStatus if a different suspension strategy is needed.
Status Handlers¶
ConvergingStatus¶
DefaultConvergingStatusHandler considers a DaemonSet ready when Status.NumberReady >= Status.DesiredNumberScheduled
and DesiredNumberScheduled > 0. When DesiredNumberScheduled is zero and the controller has observed the current
generation (ObservedGeneration >= Generation), the DaemonSet is considered converged with reason "No nodes match the
DaemonSet node selector".
GraceStatus¶
DefaultGraceStatusHandler categorizes health as:
| Status | Condition |
|---|---|
Healthy |
DesiredNumberScheduled == 0 and ObservedGeneration >= Generation (no matching nodes is a valid state) |
Degraded |
DesiredNumberScheduled == 0 but controller has not observed the latest generation, or at least one pod is |
| ready but below desired count | |
Down |
DesiredNumberScheduled > 0 and NumberReady == 0 |
The Healthy status for zero desired pods reflects that having no matching nodes is a valid configuration, not a
failure. The generation check ensures the controller has observed the latest spec before declaring health.
Full Example¶
func NodeAgentMutation(version string, hostLogPath string) daemonset.Mutation {
return daemonset.Mutation{
Name: "node-agent",
Feature: feature.NewVersionGate(version, nil),
Mutate: func(m *daemonset.Mutator) error {
m.EditPodSpec(func(e *editors.PodSpecEditor) error {
e.SetServiceAccountName("node-agent-sa")
e.EnsureVolume(corev1.Volume{
Name: "host-logs",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: hostLogPath},
},
})
return nil
})
m.EditContainers(selectors.ContainerNamed("collector"), func(e *editors.ContainerEditor) error {
e.EnsureEnvVar(corev1.EnvVar{Name: "LOG_PATH", Value: "/host/logs"})
e.SetResourceLimit(corev1.ResourceCPU, resource.MustParse("100m"))
e.SetResourceLimit(corev1.ResourceMemory, resource.MustParse("128Mi"))
e.Raw().VolumeMounts = append(e.Raw().VolumeMounts, corev1.VolumeMount{
Name: "host-logs",
MountPath: "/host/logs",
ReadOnly: true,
})
return nil
})
return nil
},
}
}
Guidance¶
DaemonSets are node-scoped. Unlike Deployments, a DaemonSet runs one pod per qualifying node. Use node selectors, tolerations, and affinities to control which nodes run the pods.
Feature: nil applies unconditionally. Omit Feature for mutations that should always run. Use
feature.NewVersionGate(version, constraints) for version-based gating and chain .When(bool) for runtime boolean
conditions.
Register mutations in dependency order. If mutation B relies on a container added by mutation A, register A first. Internal ordering within each mutation handles intra-mutation dependencies automatically.
DaemonSets are deleted on suspend. There is no in-place scale-to-zero. Override WithCustomSuspendDeletionDecision
if you need the resource to remain in the cluster when the component is suspended.
Use selectors for precision. Targeting AllContainers() when you only mean to modify the primary container can
cause unexpected behavior if sidecar containers are present.