TT Lab
Get started
Learn Learning paths Courses

Kubernetes Distributions — Build Them Yourself

Where the truth of k0s configuration lives

Continue in TT Lab

One-line summary

k0s cluster configuration lives in a file on each controller under static configuration, and in the ClusterConfig object inside the API under dynamic configuration. If you edit the file without knowing which mode you are in, you get "I edited it and nothing happened."

Why this was needed

k0s is introduced as setting up a cluster with one binary and one k0s.yaml. So when an operator wants to change settings, they naturally open /etc/k0s/k0s.yaml. Two kinds of incidents happen here.

First, under the default static configuration, k0s does not watch the file. The configuration documentation says you can change the file while running, but you must restart k0s to have it take effect. If there are several controllers, you have to make the files on all the controllers identical and restart them all. If you fix only one and forget, the settings change only when that node is next restarted, and the controllers end up holding different settings from one another.

Second, if you turn on dynamic configuration (--enable-dynamic-config) to get rid of this inconvenience, a trap in the opposite direction appears. According to the dynamic configuration documentation, when the cluster is first created, the first controller reads the file once as a bootstrap value and stores it in the API, and after that the object in the API is the source of truth for all controllers. From then on, cluster-level settings in the file are ignored even if you edit it and restart.

How it works

The configuration file may be partial

k0s config create prints a configuration with all defaults filled in. But k0s accepts a partial configuration and fills in missing values with defaults, so it reads better to keep only what you want to change in the actual file. The reason to look at the defaults is to know "what gets decided for the values I did not write." On this lab's VM, the default output was Pod CIDR 10.244.0.0/16, Service CIDR 10.96.0.0/12, datastore etcd, and telemetry on. This VM runs on top of Kubernetes, so the default ranges overlap with the host cluster, and they were changed to 172.20/172.21 in the file.

k0s sysinfo is a pre-check that the same binary runs before installation. It shows memory, the /var/lib/k0s filesystem and its free space, the cgroup version and controllers, and kernel settings as pass or warning per item, and with -o json it can also be read by machines.

Cluster settings and node settings are different

Even under dynamic configuration, the file is not entirely ignored. The documentation classifies spec.api (that node's API server), spec.storage (that node's etcd or SQLite), and spec.network.controlPlaneLoadBalancing as per-node settings that continue to be read from the file. This is a natural design, since the API server and storage have to be up before any object can be read. Also, items such as network.podCIDR, serviceCIDR, and provider cannot be changed after the cluster is created, so the documentation says you must write them in the file at manual installation.

The other cluster-level settings (worker profiles, kube-router and kube-proxy settings, Helm extensions, and so on) are decided by the clusterconfig/k0s object in the kube-system namespace. The controller watches this object in the Operator style, recreates the related resources when it changes, and leaves the result as events. k0s config status shows those events (SuccessfulReconcile, FailedReconciling).

정적 구성   파일 수정 ──(재시작해야)──▶ 반영
동적 구성   파일 수정 ──(재시작해도)──▶ 클러스터 수준 설정은 무시
            ClusterConfig 수정 ──(즉시)──▶ 반영, 이벤트 기록

Worker profiles appear as ConfigMaps

The worker profile (spec.workerProfiles) in the worker node configuration documentation is a bundle of kubelet configuration overrides. k0s creates a ConfigMap for each profile, and the worker reads at startup the ConfigMap it picked with --profile. In measurement, the name carried the Kubernetes minor version, as in worker-config-<프로필>-1.36 (the placeholder is the profile name). So you can visibly confirm whether the setting took effect from the existence of this ConfigMap and the values inside kubeletConfiguration.

Helm extensions create Chart objects

The Helm charts documentation describes two methods. One is creating the Chart object directly (recommended), and the other is declaring a repository and chart in spec.extensions.helm so that k0s converts it into a Chart object. The repository must be a proper Helm repository that has an index.yaml, and when a Chart is deleted, k0s removes the release. In measurement, the name of a Chart created by declaration was k0s-addon-chart-<차트 이름> (the placeholder is the chart name), and it had a finalizer attached for removal.

What it looks like in the field

Here are the scenes seen in measurement (k0s v1.36.4+k0s.0).

What really matters in practice

What you will do in the next lab

You start from a single k0s that came up with static configuration. After comparing the defaults with the file and reading the pre-check, you put a worker profile in the file and compare before and after a restart. You switch to dynamic configuration and see the ClusterConfig created, then edit the file in the same way and confirm that this time it is ignored even after a restart. Finally, you declare a worker profile and the podinfo chart in the object, and record what remains when you delete the declaration.