The NodeConfig custom resource

The NodeConfig custom resource

NodeConfig is a namespaced custom resource (group keel.tlspu.io, version v1, short name nc). Each NodeConfig describes one periodic task that Keel applies on every matching node.

Schema

apiVersion: keel.tlspu.io/v1
kind: NodeConfig
metadata:
  name: <name>
  namespace: <namespace>
spec:
  type: <string>          # required; selects the handler
  interval: <duration>    # optional; e.g. "1h", "10m"; defaults to --default-interval
  params:                 # optional; arbitrary key/value strings
    <key>: <value>        # values are templated against facts
  services:               # optional; templated systemd service names
    - <service>
  nodeSelector:           # optional; Kubernetes LabelSelector
    matchLabels:
      node-role: worker
  watchConfigMaps:        # optional; triggers in the NodeConfig namespace
    - name: application-config

spec.type

Required. Selects the handler that interprets the params. Built-in values: file, package. The optional admission webhook rejects unknown types; without it, unknown types are reported in status as errors. See adding-a-type.md to add more.

spec.interval

Optional. How often the task runs, e.g. 30m, 6h, 24h. When unset, the operator's --default-interval (10 minutes) is used. Durations use Go syntax (no d suffix). Values must be positive. Successful runs repeat on the interval; failed runs use controller backoff. Spec edits and watched ConfigMap changes bypass the interval.

spec.params

Optional map of arbitrary string values. Values are resolved through Go templating against the facts store before being passed to the handler. Unknown template references fail the run and are reported in status. See facts and templates for available fields and helpers.

spec.services

Optional list of systemd service names, rendered using the same facts and helpers as params. Rendering and unit-name checks finish before applying the handler. Use literal names rather than paths, options or wildcard patterns. They are restarted when the handler reports a change (e.g. a file's contents differed), or when retrying a pending restart from an earlier run. systemctl daemon-reload is run before the restarts. Adding a service to an otherwise unchanged configuration does not itself trigger a restart. See handlers for partial failure behavior.

spec.nodeSelector

Optional Kubernetes LabelSelector, supporting matchLabels and matchExpressions. Omitted or empty selects all nodes. Selection uses the current Node labels; local Node changes enqueue configs for reevaluation. Excluded nodes do not apply configurations or remove previously applied state, and their last status entry can remain from an earlier matching run. Newly matching nodes run according to their last successful interval and current spec.

spec.watchConfigMaps

Optional list of {name: ...} references to ConfigMaps in the same namespace. Create, update, delete and recreation events enqueue referring NodeConfigs. A hash of referenced names, UIDs and resource versions is persisted per node, so a changed trigger bypasses a successful run's remaining interval, including after agent restarts. Missing ConfigMaps are valid and represented in the hash. A ConfigMap is a trigger only: its data is not injected into facts or parameters. For example, an external controller can update an annotation on the watched ConfigMap when a package repository or other external source changes.

Types

file

Maintain a file's contents on the host.

Param Required Description
filename yes Absolute path of the file to maintain
contents yes Desired contents (may be empty)
mode no Permission bits in octal, e.g. "0644" (default 0644 when writing content)

The file is written atomically. When content and mode already match, no change is reported and nothing is rewritten. Without mode, existing permissions are left alone only when contents already match; a content replacement uses 0644. Specify mode explicitly when permissions matter. Special bits (setuid/setgid/sticky) in mode are ignored. See handlers for ownership, symlinks and replacement limitations.

package

Ensure a package is installed on the host.

Param Required Description
package_name yes Name of the package
version no Exact version to ensure installed

The package manager is detected from the host /etc/os-release (apt/dnf/apk/zypper/pacman). If the package is already installed (and the version matches), no change is reported. Omitting version ensures presence without upgrading an installed package. See handlers for exact OS detection, backend commands and version-pinning limitations.

Status

status:
  observedGeneration: 3
  nodes:
    node-01:
      lastRunTime: "2026-10-15T12:00:00Z"
      lastResult: success        # success | error
      changed: true
      observedGeneration: 3
      triggerDigest: "<sha256>" # present when ConfigMaps are watched
      restartPending: false     # true when a service restart needs retrying
      message: configuration applied, services restarted

status.nodes is keyed by node name, because every node runs its own instance and writes only its own entry (via a per-node JSON merge patch, so pods never clobber each other). The first result and meaningful outcome, generation or trigger changes are persisted; identical steady-state no-op results do not rewrite status. Per-node observedGeneration controls spec-change scheduling; the top-level value is informational and may reflect a different node. triggerDigest tracks external ConfigMap changes. lastRunTime is the most recently persisted run, which may precede successful no-op runs suppressed from status. lastResult is success or error; message explains the outcome (e.g. an error message, or whether services were restarted). If a service restart fails after a change, restartPending is set and the restart is retried on the next run.

Lifecycle

Deleting a NodeConfig stops future reconciliation after agents observe deletion. It does not revert files, uninstall packages, restore services or cancel a host operation already in progress. Excluding a node through the selector also leaves host state in place. There is no suspend field or built-in absent state. See the user guide for change and removal workflows.

Example

apiVersion: keel.tlspu.io/v1
kind: NodeConfig
metadata:
  name: install-htop
  namespace: tak-os
spec:
  type: package
  interval: 6h
  params:
    package_name: htop