Skip to content
Merged
4 changes: 2 additions & 2 deletions charts/cozystack/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ templateOptions:
fileValues: []
jsonValues: []
literalValues: []
talosVersion: "v1.12"
talosVersion: "v1.14"
withSecrets: "secrets.yaml"
kubernetesVersion: "v1.34.3"
kubernetesVersion: "v1.36.5"
full: false
applyOptions:
preserve: false
Expand Down
311 changes: 216 additions & 95 deletions charts/cozystack/templates/_helpers.tpl

Large diffs are not rendered by default.

32 changes: 20 additions & 12 deletions charts/cozystack/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,18 @@ floatingIP: ""
# non-existent link and the cluster endpoint will be unreachable.
# Example: vipLink: eth0.4000
vipLink: ""
image: "ghcr.io/cozystack/cozystack/talos:v1.12.6"
image: "ghcr.io/cozystack/cozystack/talos:v1.14.2"
podSubnets:
- 10.244.0.0/16
serviceSubnets:
- 10.96.0.0/16

# Optional override for machine.kubelet.nodeIP.validSubnets and
# cluster.etcd.advertisedSubnets. When left empty the chart derives
# the value from the node's IPv4-default-gateway-bearing link at
# render time (via talm.discovered.default_addresses_by_gateway), so
# the generated machine config matches the node's actual network
# Optional override for the kubelet node IP subnets
# (machine.kubelet.nodeIP.validSubnets; KubeNodeConfig nodeIP.validSubnets
# from Talos v1.14) and cluster.etcd.advertisedSubnets. When left empty
# the chart derives the value from the node's IPv4-default-gateway-bearing
# link at render time (via talm.discovered.default_addresses_by_gateway),
# so the generated machine config matches the node's actual network
# without any values.yaml edit. Set this only when you want to pin a
# specific subnet — typically for multi-homed nodes where the
# IPv4-default-gateway link is not the subnet you want kubelet/etcd
Expand Down Expand Up @@ -178,12 +179,15 @@ etcd:
# - bar=baz
extraKernelModules: []

# Extra kubelet args added to the preset's kubelet.extraConfig
# (cpuManagerPolicy: static, maxPods: 512). Operator keys must be
# DISJOINT from the built-in set; a collision fails the render with
# a hint pointing at the offending key. Example:
# Extra kubelet args added to the preset's built-in kubelet settings
# (cpuManagerPolicy: static, maxPods: 512), which land in
# machine.kubelet.extraConfig, or KubeletConfig config from Talos v1.14.
# Operator keys must be DISJOINT from the built-in set; a collision
# fails the render with a hint pointing at the offending key. Keys and
# values follow KubeletConfiguration. Example:
# extraKubeletExtraArgs:
# feature-gates: "NodeSwap=true"
# featureGates:
# NodeSwap: true
extraKubeletExtraArgs: {}

# Extra kernel sysctls added to machine.sysctls alongside the
Expand Down Expand Up @@ -232,7 +236,11 @@ timeServers: []
# a key that collides with a preset built-in fails the render — for
# controllerManager (bind-address / allocate-node-cidrs / cluster-cidr),
# scheduler (bind-address), and apiServer (the oidc-* args, but only
# when oidcIssuerUrl is set). Example:
# when oidcIssuerUrl is set). From Talos v1.14 kube-apiserver takes no
# authentication or authorization flags, so any oidc-*, anonymous-auth,
# authentication-config or authorization-config/-mode/-webhook-* key
# fails the render; oidcIssuerUrl becomes a KubeAuthenticationConfig
# document instead. Example:
# extraApiServerArgs: { max-requests-inflight: "2000" }
extraApiServerArgs: {}
extraControllerManagerArgs: {}
Expand Down
7 changes: 3 additions & 4 deletions charts/generic/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ templateOptions:
# Pin both: an empty key either renders what the node picks (with a warning)
# or stops the render on newer contracts. The Kubernetes version is the
# default of the pinned Talos line, so a fresh project lands on what that
# line ships and a re-sync does not move the control plane. Replace it with
# the version your cluster runs.
talosVersion: "v1.13"
# line ships. Replace it with the version your cluster runs.
talosVersion: "v1.14"
withSecrets: "secrets.yaml"
kubernetesVersion: "v1.36.2"
kubernetesVersion: "v1.37.0"
full: false
applyOptions:
preserve: false
Expand Down
126 changes: 86 additions & 40 deletions charts/generic/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,16 @@
{{- define "talos.config.machine.common" }}
machine:
type: {{ .MachineType }}
{{- if not (include "talm.contract.kubedocs" .) }}
kubelet:
nodeIP:
validSubnets:
{{- if .Values.advertisedSubnets }}
{{- toYaml .Values.advertisedSubnets | nindent 8 }}
{{- else }}
{{- /* Fall back to the subnet of the node's default-gateway-bearing
link. cidrNetwork masks host bits so the emitted YAML is the
canonical network form (192.168.201.0/24) rather than the
host form (192.168.201.10/24). Dedupe after masking because
a link with a secondary address in the same subnet would
otherwise produce duplicate list entries. */ -}}
{{- $addrs := fromJsonArray (include "talm.discovered.default_addresses_by_gateway" .) }}
{{- if not $addrs }}
{{- fail "values.yaml: `advertisedSubnets` was left empty and talm could not derive a default from discovery. No default-gateway-bearing link was found on the node. This field is a cluster-wide subnet selector fed to kubelet and etcd; `talm template` is invoked once per node and cannot merge per-node values into one cluster value. Either set advertisedSubnets explicitly in values.yaml, or ensure the node has a default route before running `talm template`." }}
{{- end }}
{{- $subnets := list }}
{{- range $addrs }}
{{- $subnets = append $subnets (. | cidrNetwork) }}
{{- end }}
{{- range uniq $subnets }}
- {{ . }}
{{- end }}
{{- end }}
{{- include "talm.advertised_subnets" (dict "root" . "indent" 8) }}
{{- with .Values.extraKubeletExtraArgs }}
extraConfig:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- end }}
{{- with .Values.certSANs }}
certSANs:
{{- toYaml . | nindent 2 }}
Expand Down Expand Up @@ -85,6 +67,82 @@ machine:
{{- end }}
{{- end }}

{{- define "talos.config.cluster_name" -}}
{{ include "talm.validate.dns1123subdomain" (dict "value" (.Values.clusterName | default .Chart.Name) "field" "clusterName") | quote }}
{{- end }}

{{- define "talos.config.endpoint" -}}
{{ required "values.yaml: `endpoint` must be set to the cluster control-plane URL (e.g. https://<vip>:6443). This field is cluster-wide: every node's kubelet and kube-proxy dials it, so it cannot be auto-derived from the current node's IP -- `talm template` runs once per node and has no way to reconcile per-node IPs into a single shared endpoint. For multi-node setups use a VIP or an external load balancer; for single-node clusters the node's routable IP works." .Values.endpoint | quote }}
{{- end }}

{{- /* Cluster settings on contract v1.14+. Only etcd stays in v1alpha1;
the rest are typed documents the engine merges into the ones the
generator emits, so they carry only what this chart sets. */ -}}
{{- define "talos.config.cluster.kubedocs" }}
{{- if eq .MachineType "controlplane" }}

cluster:
etcd:
advertisedSubnets:
{{- include "talm.advertised_subnets" (dict "root" . "indent" 6) }}
{{- end }}
---
apiVersion: v1alpha1
kind: KubeClusterConfig
clusterName: {{ include "talos.config.cluster_name" . }}
endpoint: {{ include "talos.config.endpoint" . }}
---
apiVersion: v1alpha1
kind: KubeNodeConfig
nodeIP:
validSubnets:
{{- include "talm.advertised_subnets" (dict "root" . "indent" 4) }}
{{- with .Values.extraKubeletExtraArgs }}
---
apiVersion: v1alpha1
kind: KubeletConfig
config:
{{- toYaml . | nindent 2 }}
{{- end }}
---
apiVersion: v1alpha1
kind: KubeNetworkConfig
podSubnets:
{{- toYaml .Values.podSubnets | nindent 2 }}
serviceSubnets:
{{- toYaml .Values.serviceSubnets | nindent 2 }}
{{- if eq .MachineType "controlplane" }}
{{- with .Values.extraControllerManagerArgs }}
---
apiVersion: v1alpha1
kind: KubeControllerManagerConfig
extraArgs:
{{- include "talm.render.component_args" (dict "args" . "field" "extraControllerManagerArgs" "indent" 2) }}
{{- end }}
{{- with .Values.extraSchedulerArgs }}
---
apiVersion: v1alpha1
kind: KubeSchedulerConfig
extraArgs:
{{- include "talm.render.component_args" (dict "args" . "field" "extraSchedulerArgs" "indent" 2) }}
{{- end }}
{{- include "talm.guard.apiserver_args_kubedocs" . }}
{{- if or .Values.extraApiServerArgs .Values.certSANs }}
---
apiVersion: v1alpha1
kind: KubeAPIServerConfig
{{- with .Values.extraApiServerArgs }}
extraArgs:
{{- include "talm.render.component_args" (dict "args" . "field" "extraApiServerArgs" "indent" 2) }}
{{- end }}
{{- with .Values.certSANs }}
certExtraSANs:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

{{- /* Shared cluster section */ -}}
{{- define "talos.config.cluster" }}

Expand All @@ -94,9 +152,9 @@ cluster:
{{- toYaml .Values.podSubnets | nindent 6 }}
serviceSubnets:
{{- toYaml .Values.serviceSubnets | nindent 6 }}
clusterName: {{ include "talm.validate.dns1123subdomain" (dict "value" (.Values.clusterName | default .Chart.Name) "field" "clusterName") | quote }}
clusterName: {{ include "talos.config.cluster_name" . }}
controlPlane:
endpoint: {{ required "values.yaml: `endpoint` must be set to the cluster control-plane URL (e.g. https://<vip>:6443). This field is cluster-wide: every node's kubelet and kube-proxy dials it, so it cannot be auto-derived from the current node's IP -- `talm template` runs once per node and has no way to reconcile per-node IPs into a single shared endpoint. For multi-node setups use a VIP or an external load balancer; for single-node clusters the node's routable IP works." .Values.endpoint | quote }}
endpoint: {{ include "talos.config.endpoint" . }}
{{- if eq .MachineType "controlplane" }}
{{- with .Values.extraControllerManagerArgs }}
controllerManager:
Expand Down Expand Up @@ -124,23 +182,7 @@ cluster:
{{- end }}
etcd:
advertisedSubnets:
{{- if .Values.advertisedSubnets }}
{{- toYaml .Values.advertisedSubnets | nindent 6 }}
{{- else }}
{{- /* Fall back to the subnet of the node's default-gateway-bearing
link; cidrNetwork masks host bits to emit canonical network
form. Dedupe handled the same way as validSubnets above.
Empty discovery already errored via validSubnets' required()
guard, so we reach this block only when at least one address
was resolved. */ -}}
{{- $subnets := list }}
{{- range fromJsonArray (include "talm.discovered.default_addresses_by_gateway" .) }}
{{- $subnets = append $subnets (. | cidrNetwork) }}
{{- end }}
{{- range uniq $subnets }}
- {{ . }}
{{- end }}
{{- end }}
{{- include "talm.advertised_subnets" (dict "root" . "indent" 6) }}
{{- end }}
{{- end }}

Expand Down Expand Up @@ -291,7 +333,11 @@ cluster:
{{- define "talos.config.multidoc" }}
{{- include "talos.config.machine.common" . }}

{{- if include "talm.contract.kubedocs" . }}
{{- include "talos.config.cluster.kubedocs" . }}
{{- else }}
{{- include "talos.config.cluster" . }}
{{- end }}
{{- include "talm.config.registries.multidoc" . }}
{{- include "talos.config.network.multidoc" . }}
{{- end }}
23 changes: 14 additions & 9 deletions charts/generic/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ podSubnets:
serviceSubnets:
- 10.96.0.0/16

# Optional override for machine.kubelet.nodeIP.validSubnets and
# cluster.etcd.advertisedSubnets. When left empty the chart derives
# the value from the node's IPv4-default-gateway-bearing link at
# render time (via talm.discovered.default_addresses_by_gateway), so
# the generated machine config matches the node's actual network
# Optional override for the kubelet node IP subnets
# (machine.kubelet.nodeIP.validSubnets; KubeNodeConfig nodeIP.validSubnets
# from Talos v1.14) and cluster.etcd.advertisedSubnets. When left empty
# the chart derives the value from the node's IPv4-default-gateway-bearing
# link at render time (via talm.discovered.default_addresses_by_gateway),
# so the generated machine config matches the node's actual network
# without any values.yaml edit. Set this only when you want to pin a
# specific subnet — typically for multi-homed nodes where the
# IPv4-default-gateway link is not the subnet you want kubelet/etcd
Expand Down Expand Up @@ -103,11 +104,15 @@ certSANs: []
# - bar=baz
extraKernelModules: []

# Kubelet command-line args, rendered as machine.kubelet.extraConfig.
# Talos expects string values. Example:
# Kubelet configuration (KubeletConfiguration fields, not command-line
# flags), rendered as machine.kubelet.extraConfig
# (KubeletConfig config from Talos v1.14).
# Keys and values follow KubeletConfiguration: maxPods is a number,
# feature gates go under featureGates. Example:
# extraKubeletExtraArgs:
# maxPods: "256"
# feature-gates: "NodeSwap=true"
# maxPods: 256
# featureGates:
# NodeSwap: true
extraKubeletExtraArgs: {}

# Kernel sysctls, rendered as machine.sysctls. Values must be YAML
Expand Down
51 changes: 49 additions & 2 deletions charts/talm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,11 @@ busPath: {{ .spec.busPath }}
are the only thing standing between a values typo and a component
that either fails to start or starts with a nonsense flag, and six
copies of them would drift. Expects a dict with "args" and "field"
(the values.yaml key name, used in messages). */ -}}
(the values.yaml key name, used in messages), and optionally "indent"
(default 6, the v1alpha1 cluster.<component>.extraArgs depth). */ -}}
{{- define "talm.render.component_args" }}
{{- $field := .field }}
{{- $indent := .indent | default 6 | int }}
{{- range $k, $v := .args }}
{{- if kindIs "invalid" $v }}
{{- fail (printf "values.yaml: %s.%s has no value. A bare `key:` renders as the literal string \"<nil>\" onto the component command line; give it a value or drop the key." $field $k) }}
Expand All @@ -372,7 +374,52 @@ busPath: {{ .spec.busPath }}
{{- if or (kindIs "map" $v) (kindIs "slice" $v) }}
{{- fail (printf "values.yaml: %s.%s is a %s, but a component flag takes a single scalar value. Write it as the string the component expects." $field $k (kindOf $v)) }}
{{- end }}
{{ $k }}: {{ $v | toString | quote }}
{{- printf "%s: %s" $k ($v | toString | quote) | nindent $indent }}
{{- end }}
{{- end }}

{{- /* Non-empty from contract v1.14, where Talos keeps the Kubernetes
settings in typed documents (KubeClusterConfig, KubeletConfig,
KubeNodeConfig, ...) and refuses the v1alpha1 fields next to them. */ -}}
{{- define "talm.contract.kubedocs" -}}
{{- if and .TalosVersion (not (semverCompare "<1.14.0-0" .TalosVersion)) }}true{{ end }}
{{- end }}

{{- /* From contract v1.14 Talos refuses these kube-apiserver flags in
KubeAPIServerConfig and takes them only as typed documents; fail at
render rather than at apply. */ -}}
{{- define "talm.guard.apiserver_args_kubedocs" }}
{{- range $k, $_ := .Values.extraApiServerArgs }}
{{- range list "oidc-" "anonymous-auth" "authentication-config" "authorization-config" "authorization-mode" "authorization-webhook-" }}
{{- if hasPrefix . $k }}
{{- fail (printf "values.yaml: extraApiServerArgs.%s is not accepted on Talos v1.14 and later, which configures kube-apiserver authentication and authorization through the KubeAuthenticationConfig and KubeAuthorizerConfig documents. Drop it from extraApiServerArgs and add that document as a per-node patch, or pin templateOptions.talosVersion to v1.13 or lower." $k) }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

{{- /* advertisedSubnets as YAML list items at .indent, falling back to the
subnet of the node's default-gateway-bearing link. cidrNetwork masks
host bits so the list holds the canonical network form
(192.168.201.0/24, not 192.168.201.10/24); uniq drops the duplicate a
secondary address in the same subnet would add. Expects a dict with
"root" (the chart context) and "indent". */ -}}
{{- define "talm.advertised_subnets" }}
{{- $root := .root }}
{{- if $root.Values.advertisedSubnets }}
{{- toYaml $root.Values.advertisedSubnets | nindent .indent }}
{{- else }}
{{- $addrs := fromJsonArray (include "talm.discovered.default_addresses_by_gateway" $root) }}
{{- if not $addrs }}
{{- fail "values.yaml: `advertisedSubnets` was left empty and talm could not derive a default from discovery. No default-gateway-bearing link was found on the node. This field is a cluster-wide subnet selector fed to kubelet and etcd; `talm template` is invoked once per node and cannot merge per-node values into one cluster value. Either set advertisedSubnets explicitly in values.yaml, or ensure the node has a default route before running `talm template`." }}
{{- end }}
{{- $subnets := list }}
{{- range $addrs }}
{{- $subnets = append $subnets (. | cidrNetwork) }}
{{- end }}
{{- range uniq $subnets }}
{{- printf "- %s" . | nindent $.indent }}
{{- end }}
{{- end }}
{{- end }}

Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/node-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ A node file can carry Talos config below its modeline (for example, a custom `ho

## Idempotent applies

Repeated `talm apply` runs against an already-configured node do not duplicate entries. Before the strategic merge runs, the engine prunes from the body every primitive-list entry the rendered template already carries (e.g. certSANs, nameservers, validSubnets). For object arrays the upstream patcher merges by identity (machine.network.interfaces by `interface:` or `deviceSelector:`, vlans by `vlanId:`, apiServer admissionControl by `name:`), the prune descends into matched pairs and dedupes the inner primitive lists too — so re-applying after `talm template -I` does not double interface addresses, vlan addresses, or admission-control exemption namespaces. For object arrays without an upstream identity merge (extraVolumes, kernel.modules, wireguard.peers, ...), body items that deep-equal a rendered counterpart are dropped, covering the dominant full-restate case. Fields tagged `merge:"replace"` upstream are passed through verbatim — pruning them would let the upstream replace silently drop the rendered entries on a partial edit. This covers v1alpha1 root paths `cluster.network.podSubnets`, `cluster.network.serviceSubnets`, `cluster.apiServer.auditPolicy`, and the typed `NetworkRuleConfig` paths `ingress` and `portSelector.ports`.
Repeated `talm apply` runs against an already-configured node do not duplicate entries. Before the strategic merge runs, the engine prunes from the body every primitive-list entry the rendered template already carries (e.g. certSANs, nameservers, validSubnets). For object arrays the upstream patcher merges by identity (machine.network.interfaces by `interface:` or `deviceSelector:`, vlans by `vlanId:`, apiServer admissionControl by `name:`), the prune descends into matched pairs and dedupes the inner primitive lists too — so re-applying after `talm template -I` does not double interface addresses, vlan addresses, or admission-control exemption namespaces. For object arrays without an upstream identity merge (extraVolumes, kernel.modules, wireguard.peers, ...), body items that deep-equal a rendered counterpart are dropped, covering the dominant full-restate case. Fields tagged `merge:"replace"` upstream are passed through verbatim — pruning them would let the upstream replace silently drop the rendered entries on a partial edit. This covers v1alpha1 root paths `cluster.network.podSubnets`, `cluster.network.serviceSubnets`, `cluster.apiServer.auditPolicy`, the typed `NetworkRuleConfig` paths `ingress` and `portSelector.ports`, `advertise`, `importRoutes` and `neighbors` of `BGPInstanceConfig`, `overrides` of `CRIBaseRuntimeSpecConfig`, and from Talos v1.14 `podSubnets` and `serviceSubnets` of `KubeNetworkConfig`, `pod` of `KubeStaticPodConfig` and `configuration` of `KubeAuthenticationConfig`, `KubeAuditPolicyConfig` and `KubeCredentialProviderConfig`. The typed paths are matched per kind: `configuration` of `KubeAdmissionControlConfig` merges and is pruned as usual. Component `extraArgs` (kubelet, apiserver, controller-manager, scheduler, proxy, etcd, in v1alpha1 and in the typed documents) merge key by key and replace the whole value of each key the body sets, so the prune drops only keys the body restates exactly; a list-valued flag such as `feature-gates` edited in the node file keeps the rendered entries the body lists.

## What `talm template` does not do

Expand Down
Loading
Loading