Documentation

Configuration

One YAML file holds every setting. Flags and environment variables still work and still win.

The configuration file

KubeSolo reads its settings from one YAML file, /etc/kubesolo/config.yaml. The installer creates it, and the service starts KubeSolo with --config=/etc/kubesolo/config.yaml. A file only needs the keys it changes; anything omitted keeps its default.

/etc/kubesolo/config.yaml
1apiVersion: kubesolo.io/v1alpha1
2kind: Config
 
4network:
5 nodeIP: 10.0.0.5
6metrics:
7 enabled: true
8portainer:
9 edgeID: "your-edge-id"
10 edgeKey: "your-edge-key"

The file lives outside the data directory on purpose: it holds settings, not cluster state, so it survives kubesoloctl reset. It is written 0600 and owned by root because it can hold portainer.edgeKey, a credential. A file that declares an apiVersion KubeSolo does not recognise is rejected.

When changes take effect

On restart. KubeSolo reads its configuration once at startup and derives every path, certificate and component argument from it. There is no live reload.

bash
1$ sudo systemctl restart kubesolo # systemd
2$ sudo rc-service kubesolo restart # OpenRC
3$ sudo service kubesolo restart # SysV init

One setting can never change: path. Every certificate, the cluster database and all container state live below it, and nothing moves them. Choose it at install time.

Precedence

Four layers, lowest to highest. Each layer overrides only the settings it actually sets.

LayerNotes
1. Built-in defaultsWhat KubeSolo runs with when nothing is set.
2. The configuration file/etc/kubesolo/config.yaml, or the file named by --config / KUBESOLO_CONFIG.
3. Environment variablesOne KUBESOLO_* variable per setting. An empty value counts as set.
4. Command-line flagsDeprecated, but they still win.

If a change in the file seems to have no effect, a flag or environment variable in the service definition is overriding it:

bash
1$ grep -- '--' /etc/systemd/system/kubesolo.service

To see what KubeSolo actually resolves, without starting anything:

bash
1$ sudo kubesolo --config=/etc/kubesolo/config.yaml --print-config

Editing the configuration

With kubesoloctl

bash
1$ sudo kubesoloctl config get # show everything
2$ sudo kubesoloctl config get network.mtu # show one setting
3$ sudo kubesoloctl config set network.mtu 1400 # change one setting
4$ sudo kubesoloctl config edit # edit in $EDITOR, validated before saving
5$ sudo kubesoloctl config validate # check the installed file
6$ kubesoloctl config schema # every setting, type, default and flag

Nothing is written until validation passes, so a rejected value leaves the file as it was. List and map settings take the same form as their flag: kubernetes.apiServer.extraSANs takes 10.0.0.4,kubesolo.local, and kubernetes.kubelet.systemReserved takes cpu=1,memory=500Mi.

By hand

The file is plain YAML. When KubeSolo or kubesoloctl writes it, comments are dropped and keys are sorted alphabetically; the previous version is kept as /etc/kubesolo/config.yaml.bak. Validate hand edits before restarting with sudo kubesoloctl config validate.

The repository ships an annotated example file with every setting at its default and a command to verify each one.

The whole document

Every setting at its default:

config.yaml (defaults)
1apiVersion: kubesolo.io/v1alpha1
2kind: Config
 
4path: /var/lib/kubesolo # immutable after install
 
6kubernetes:
7 nodeName: "" # empty = the hostname
8 apiServer:
9 extraSANs: []
10 startupTimeoutSeconds: 600
11 kubelet:
12 cpuManager:
13 policy: none # none | static
14 policyOptions: {}
15 reservedCPUs: "" # defaults to "0" under the static policy
16 systemReserved: {} # e.g. {cpu: "1", memory: 500Mi}
 
18network:
19 nodeIP: "" # empty = auto-detect, preferring a private address
20 mtu: 0 # 0 = auto-detect
21 disableIPv6: false
22 loadBalancer:
23 enabled: true
24 ip: "" # empty = the node IP
 
26runtime:
27 endpoint: "" # empty = embedded containerd
28 containerMode: null # null = auto-detect; true or false to force
 
30storage:
31 localPath:
32 enabled: true
33 sharedPath: ""
34 dbWALRepair: false
 
36logging:
37 debug: false
38 pprof: false
 
40metrics:
41 enabled: false
42 bindAddress: 127.0.0.1:9105
 
44api:
45 enabled: false
46 socketPath: "" # empty = <path>/config.sock
 
48portainer:
49 edgeID: ""
50 edgeKey: ""
51 async: false
52 image: docker.io/portainer/agent:lts
 
54d2k:
55 enabled: false
56 namespace: d2k

Every setting

Every setting, with its deprecated flag and its environment variable. All of them except path apply on the next restart.

SettingFlagEnv varDefaultAppliesDescription
path--pathKUBESOLO_PATH/var/lib/kubesoloimmutableDirectory for all state: certificates, the Kine database and containerd state.
logging.debug--debugKUBESOLO_DEBUGfalserestartDebug logging.
logging.pprof--pprof-serverKUBESOLO_PPROF_SERVERfalserestartGo pprof server on port 6060, all interfaces.
network.nodeIP--node-ipKUBESOLO_NODE_IP"" (auto-detect)restartIPv4 address for the API server, kubeconfig, kubelet and LoadBalancer EXTERNAL-IP. See node IP.
network.mtu--mtuKUBESOLO_MTU0 (auto-detect)restartMTU for the cni0 bridge and pod interfaces.
network.disableIPv6--disable-ipv6KUBESOLO_DISABLE_IPV6falserestartTurn IPv6 off for CoreDNS, the kubelet and the host. See IPv6.
network.loadBalancer.enabled--load-balancerKUBESOLO_LOAD_BALANCERtruerestartSet EXTERNAL-IP on LoadBalancer Services. Required by d2k.
network.loadBalancer.ip--load-balancer-ipKUBESOLO_LOAD_BALANCER_IP"" (node IP)restartAddress published as EXTERNAL-IP.
runtime.endpoint--container-runtime-endpointKUBESOLO_CONTAINER_RUNTIME_ENDPOINT"" (embedded)restartCRI endpoint of a host-managed runtime instead of the embedded containerd.
runtime.containerMode--container-modeKUBESOLO_CONTAINER_MODEunset (auto-detect)restartForce container mode on or off.
kubernetes.nodeName—KUBESOLO_NODE_NAME"" (hostname)restartNode name. Trimmed and lowercased.
kubernetes.apiServer.extraSANs--apiserver-extra-sansKUBESOLO_APISERVER_EXTRA_SANS[]restartExtra IPs or DNS names for the API server certificate.
kubernetes.apiServer.startupTimeoutSeconds--startup-timeoutKUBESOLO_STARTUP_TIMEOUT600restartSeconds each component may take to pass its startup health check. Raise it on SD cards.
kubernetes.kubelet.cpuManager.policy--cpu-manager-policyKUBESOLO_CPU_MANAGER_POLICYnonerestartnone or static. Static is not allowed in container mode.
kubernetes.kubelet.cpuManager.policyOptions--cpu-manager-policy-optionsKUBESOLO_CPU_MANAGER_POLICY_OPTIONS{}restartStatic policy options, such as full-pcpus-only.
kubernetes.kubelet.cpuManager.reservedCPUs--reserved-cpusKUBESOLO_RESERVED_CPUS"" ("0" under static)restartCpuset held back for the host.
kubernetes.kubelet.systemReserved--system-reservedKUBESOLO_SYSTEM_RESERVED{}restartResources withheld from allocatable: cpu, memory, ephemeral-storage, pid.
storage.localPath.enabled--local-storageKUBESOLO_LOCAL_STORAGEtruerestartlocal-path storage provisioner and default StorageClass.
storage.localPath.sharedPath--local-storage-shared-pathKUBESOLO_LOCAL_STORAGE_SHARED_PATH""restartShared filesystem for volumes instead of the data directory.
storage.dbWALRepair--db-wal-repairKUBESOLO_DB_WAL_REPAIRfalserestartCheck the SQLite database at startup and clear WAL files if it is corrupt.
portainer.edgeID--portainer-edge-idKUBESOLO_PORTAINER_EDGE_ID""restartPortainer Edge ID.
portainer.edgeKey--portainer-edge-keyKUBESOLO_PORTAINER_EDGE_KEY""restartPortainer Edge key. Secret: redacted by the config API.
portainer.async--portainer-edge-asyncKUBESOLO_PORTAINER_EDGE_ASYNCfalserestartEdge async mode.
portainer.image--portainer-edge-imageKUBESOLO_PORTAINER_EDGE_IMAGEdocker.io/portainer/agent:ltsrestartEdge Agent image, including the tag.
d2k.enabled--d2kKUBESOLO_D2KfalserestartDocker-compatible API on port 2376 (amd64 and arm64 only).
d2k.namespace--d2k-namespaceKUBESOLO_D2K_NAMESPACEd2krestartNamespace d2k deploys into and translates against.
metrics.enabled--metrics-serverKUBESOLO_METRICS_SERVERfalserestartPrometheus metrics endpoint. See observability.
metrics.bindAddress--metrics-bind-addressKUBESOLO_METRICS_BIND_ADDRESS127.0.0.1:9105restartAddress the metrics endpoint listens on.
api.enabled—KUBESOLO_API_ENABLEDfalserestartServe this configuration over a unix socket.
api.socketPath—KUBESOLO_API_SOCKET_PATH"" (<path>/config.sock)restartSocket location. Must stay under the unix socket path limit.

A few combinations are rejected at startup: d2k.enabled without network.loadBalancer.enabled, and the static CPU manager policy in container mode. On arm and riscv64, d2k disables itself with a warning. An MTU below 1280 warns that IPv6 pod traffic may not fragment correctly.

Flags: deprecated, still honoured

Every KubeSolo flag in the table above still works and still overrides the file, so existing installs keep running. They are deprecated: no new flags will be added, and new settings are available only through the file. Boolean flags also have a negated form, such as --no-load-balancer.

FlagStatus
--configNames the configuration file. Defaults to /etc/kubesolo/config.yaml; also KUBESOLO_CONFIG.
--print-configPrints the resolved configuration as YAML and exits without starting anything.
--version, -vPrints the version and exits.
--fullDeprecated and has no effect. KubeSolo always uses upstream Kubernetes defaults. Still accepted with a warning, for compatibility; it will be removed.

Migrating a flag-based install

Installs made before v1.2.1 pass settings as flags in the service definition. kubesoloctl upgrade converts them automatically and keeps the old service file as .bak. To do it by hand, run the binary with the flags the service currently passes plus --print-config, then save the result:

bash
1$ grep '^ExecStart=' /etc/systemd/system/kubesolo.service
2$ sudo /usr/local/bin/kubesolo <those flags> --print-config | sudo tee /etc/kubesolo/config.yaml
3$ sudo chmod 600 /etc/kubesolo/config.yaml

Then reduce the service command line to one flag and restart:

/etc/systemd/system/kubesolo.service
1[Service]
2ExecStart=/usr/local/bin/kubesolo --config=/etc/kubesolo/config.yaml

Configuration API

KubeSolo can serve its configuration over a unix socket, so it can be read and changed programmatically. It is off by default:

yaml
1api:
2 enabled: true
3 socketPath: "" # empty = <path>/config.sock

The socket is mode 0600 and owned by root. File permissions are the whole access model: there is no token and no TLS, because there is no network listener. Anything that can open the socket can change the configuration. Changes made through the API are desired state; they take effect on the next restart, and every write response lists the settings that need one.

MethodPathPurpose
GET/api/v1/configRead the stored configuration. portainer.edgeKey is returned as *** unless you add ?showSecrets=true.
GET/api/v1/config/schemaEvery setting, its type, default and mutability.
PATCH/api/v1/configChange part of it with an RFC 7386 JSON merge patch. null resets a setting to its default.
PUT/api/v1/configReplace the whole document. Omitted settings return to their defaults.
POST/api/v1/config:validateReport what a candidate would change, without saving.
DELETE/api/v1/configReset every setting to its default. path is kept.
GET/healthzLiveness.
bash
1$ sudo curl -s --unix-socket /var/lib/kubesolo/config.sock http://localhost/api/v1/config
 
3$ sudo curl -s -X PATCH --unix-socket /var/lib/kubesolo/config.sock \
4 -H 'Content-Type: application/merge-patch+json' \
5 -d '{"network":{"mtu":1400}}' \
6 http://localhost/api/v1/config

Reads return an ETag; send it back as If-Match to get a 412 if the configuration changed underneath you. Changing path returns 409, and an invalid configuration returns 422. A rejected request writes nothing. Each write is logged with the setting paths it changed, never their values: journalctl -u kubesolo | grep configapi.

The full reference is in the repository: docs/configuration/config-api.md.