name: k3s-server description: | k3s control-plane (server) node with ServiceLB, Traefik v2, and local-path-provisioner enabled by default. Publishes kubeconfig back to the operator via layer artifacts and registers a ClusterProfile on first boot.
k3s-server -- k3s control-plane node
Candy Properties
| Property | Value |
|---|---|
| Install files | charly.yml, task:, service:, artifact:, secret_require: |
| Depends on | /charly-infrastructure:k3s |
| Service | k3s.service (system scope, enabled) |
What this candy does
- Reads
K3S_CLUSTER_TOKENfrom the credential store (secret_require:). - Writes
/etc/rancher/k3s/config.yamlwith:token:— the pre-shared cluster tokenwrite-kubeconfig-mode: "0644"— so the operator can scp the kubeconfig backtls-san:—${K3S_SERVER_HOSTNAME}(orhostname -nfallback)disable: []— explicitly empty, so ServiceLB, Traefik v2, and local-path-provisioner all install as default k3s addons.
- Emits
/etc/systemd/system/k3s.servicerunningk3s server. - After setup, the runtime publishes
/etc/rancher/k3s/k3s.yamlback to the operator via the newartifact:candy-schema feature. The retrieved file lands at~/.cache/charly/clusters/<deploy_name>/kubeconfig.yamlwith127.0.0.1rewritten to${K3S_SERVER_HOSTNAME}so the operator cankubectlthe cluster from their machine. - The
K3sPostProvisionhook (Go, runs after artifact retricheck) merges the kubeconfig into~/.kube/configunder context<deploy_name>and writes a matching ClusterProfile to~/.config/charly/clusters/<deploy_name>.yamlwithingress.class=traefikandstorage.class_default=local-path.
Operator setup — none required (auto-generated)
K3S_CLUSTER_TOKEN auto-generates on first deploy. The resolver
(charly/layer_secrets.go — ensureCandySecret) detects the missing
secret_require: entry, generates a 32-byte hex token via
generateAndStoreSecret, and persists it to the active credential
backend (keyring / config-file fallback). Every subsequent
k3s-server and k3s-agent deploy reads the same persisted value —
zero operator setup, server and agents automatically share the token.
Override with a specific value (uncommon — only when reproducing a specific cluster identity, e.g., disaster recovery):
charly secrets set charly/secret/K3S_CLUSTER_TOKEN $(openssl rand -hex 32)
Retrieve the auto-generated token (for debugging or out-of-band agent join):
charly secrets get charly/secret K3S_CLUSTER_TOKEN
Usage
# charly.yml
vm:
k3s-srv:
source: { kind: cloud_image, url: "…" }
disposable: true
ram: 4G
cpus: 2
deployments:
box:
"vm:k3s-srv":
target: vm
vm_source: k3s-srv
add_candy: [k3s-server]
env:
- K3S_SERVER_HOSTNAME=k3s-srv.lan # optional but recommended
charly vm create k3s-srv
charly bundle add vm:k3s-srv
# → kubeconfig auto-retrieved + ClusterProfile written
kubectl --context k3s-srv get nodes
# addon health is asserted by the candy's declarative `kube: addons` check step
# (served out-of-process by candy/plugin-kube — see Deploy-scope below); there is
# no host `charly check kube` command.
Tests
Build-scope:
/etc/rancher/k3s/config.yamlexists, mode 0600./etc/systemd/system/k3s.serviceexists.
Deploy-scope (using the declarative kube: check verb — see /charly-kubernetes:check-k8s).
The cluster-probe verb is the declarative kube: check verb (served out-of-process by
candy/plugin-kube — there is no host charly check kube command); the k8s spelling is
reserved for the deploy KIND only:
kube: wait-nodes— at least 1 node Ready.kube: ingressclass—traefikpresent.kube: storageclass—local-pathpresent.kube: addons— Traefik + ServiceLB + local-path-provisioner all Ready.
Related Candies
/charly-infrastructure:k3s— Base candy installing the k3s binary (required dep)/charly-infrastructure:k3s-agent— Worker nodes joining this server/charly-coder:kubernetes-layer—kubectl/helmon the operator side (not needed in the cluster)/charly-kubernetes:check-k8s— The test verb used by this candy's deploy-scope checks