Skip to content

Cloud-init customization#

Using custom user-data#

k0smotron’s CAPI bootstrap providers for both controllers and workers allow you to append your own cloud-init content to the generated user data via the spec.customUserDataRef field.

This is useful when you need to:

  • Run additional provisioning logic (install agents, tweak OS, configure networking).
  • Ship extra cloud-init modules beyond what k0smotron emits.
  • Use templated variables to reuse the k0smotron-generated commands in your own snippets.

How it works#

  • k0smotron generates the base cloud-init for the node (files, commands, etc.).
  • If spec.customUserDataRef is set on the bootstrap object, its content is appended to the user data.
  • On bootstrap, cloud-init merges user-data.

Example (Controller):

---
apiVersion: v1
kind: Secret
metadata:
  name: cp-custom-userdata
stringData:
  customUserData: |
    #cloud-config
    packages:
      - htop
    runcmd:
      - echo "hello from custom controller cloud-init"
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: K0sControlPlane
metadata:
  name: my-cp
spec:
  replicas: 1
  k0sConfigSpec:
    customUserDataRef:
      secretRef:
        name: cp-custom-userdata
        key: customUserData
  machineTemplate:
    infrastructureRef:
      apiGroup: infrastructure.cluster.x-k8s.io
      kind: DevMachineTemplate
      name: my-cp-tpl
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: DevMachineTemplate
metadata:
  name: my-cp-tpl
spec:
  template:
    spec:
      backend:
        docker: {}

Example (WorkerConfigTemplate):

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: worker-custom-userdata
data:
  customUserData: |
    #cloud-config
    write_files:
      - path: /etc/motd
        permissions: "0644"
        content: |
          Welcome from custom worker cloud-init
---
apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
kind: K0sWorkerConfigTemplate
metadata:
  name: my-worker-template
spec:
  template:
    spec:
      customUserDataRef:
        configMapRef:
          name: worker-custom-userdata
          key: customUserData

Your custom content should be valid cloud-init. Include a #cloud-config header at the top of the snippet.

Using jinja templating#

k0smotron supports Jinja templating in your customUserData. You can use Jinja syntax to include variables and control structures.

Note

Read more about instance data templating in the cloud-init documentation.

Using CloudInitVars feature gate#

Important

Use CloudInitVars feature gate in case you really need to and if you absolutely understand the implications. It is not recommended for general use. Most likely, you don't need it and should stick to the regular customUserData with pre/postK0sCommands approach.

When the CloudInitVars feature gate is enabled, k0smotron exposes generated commands and files as Jinja variables instead of putting them into runcmd and files sections. You can embed into your customUserData. The variables include:

  • {{ k0smotron_k0sDownloadCommands }} — shell line with all download steps chained by &&.
  • {{ k0smotron_k0sInstallCommand }} — the k0s install command (controller/worker specific).
  • {{ k0smotron_k0sStartCommand }} — the command that starts k0s.
  • {{ k0smotron_files }} — a list of file objects with fields: path, content, permissions, and owner, encoding or append when set.

Note

append is rejected when the machine is provisioned by the RemoteMachine provider, over SSH or through a job. Those paths re-run from the start on every retry, so appending would duplicate the content.

Warning

In this mode k0smotron does not write the files, so your template must honour owner, encoding and append itself. Ignoring encoding leaves base64 content encoded, and ignoring append replaces the file instead. The optional keys are only present when set, so read them with f.get(...).

Example customUserData using variables:

runcmd:
  - {{ k0smotron_k0sDownloadCommands }}
  - echo "About to install" && {{ k0smotron_k0sInstallCommand }}
  - {{ k0smotron_k0sStartCommand }}

write_files:
{% for f in k0smotron_files %}
  - path: {{ f.path }}
    permissions: "{{ f.permissions }}"
{% if f.get('owner') %}
    owner: "{{ f.owner }}"
{% endif %}
{% if f.get('encoding') %}
    encoding: "{{ f.encoding }}"
{% endif %}
{% if f.get('append') %}
    append: true
{% endif %}
    content: {{ f.content | tojson }}
{% endfor %}
  - path: /my/custom/file.txt
    permissions: "0644"
    content: |
      Welcome from custom cloud-init with variables

Keep the block tags at column zero. cloud-init enables trim_blocks but not lstrip_blocks, so indented tags emit whitespace that breaks the list.

Use tojson for content instead of a block scalar, which would take its indentation from the first line.

Another approach is to use the variables in a completely custom script:

runcmd:
  - echo -n "custom" > /root/custom
  - /root/cloud-init.sh

write_files:
  - path: /root/cloud-init.sh
    permissions: "0755"
    content: |
      #!/usr/bin/bash
      set -euo pipefail

      {{ k0smotron_k0sDownloadCommands }}
      {{ k0smotron_k0sInstallCommand }}
      {{ k0smotron_k0sStartCommand }}
{% for f in k0smotron_files %}
  - path: {{ f.path }}
    permissions: "{{ f.permissions }}"
    content: {{ f.content | tojson }}
{% endfor %}
  - path: /root/my-extra-file
    content: test
    permissions: "0600"

You don't need to start the document with ## template: jinja or #cloud-init, k0smotron does it.

Enabling the feature gate#

You can enable the CloudInitVars feature gate by:

  • Adding the --feature-gates=CloudInitVars=true flag to the k0smotron bootstrap provider args.
  • Setting the K0SMOTRON_FEATURE_GATES environment variable to k0smotron controller manager deployment. For example, if you are using the k0smotron Helm chart, you can set it like this:

    env:
    - name: K0SMOTRON_FEATURE_GATES
      value: "CloudInitVars=true"
    
  • Setting the K0SMOTRON_FEATURE_GATES environment variable for clusterctl. For example:

    export K0SMOTRON_FEATURE_GATES="CloudInitVars=true"
    clusterctl init --bootstrap k0sproject-k0smotron