k0s Installation - Production Setup

Status: Active
Last Updated: 2026-01-30
Category: Containers - Orchestration
Prerequisites: k0s-introduction, docker-basics
Time: 3-4 hours
Tags: k0s, kubernetes, installation, production, cluster

Summary

Master production-ready k0s installation patterns for controller and worker nodes, high availability clusters, configuration management, and automated deployment. Learn systemd integration, backup strategies, and upgrade procedures for reliable Kubernetes clusters.

๐ŸŽฏ What You'll Learn

By the end of this article, you'll be able to:

๐Ÿ—๏ธ Architecture Patterns

Single-Node (Development)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Controller + Worker   โ”‚
โ”‚  (Combined on one node) โ”‚
โ”‚                         โ”‚
โ”‚  - Control Plane        โ”‚
โ”‚  - Worker Components    โ”‚
โ”‚  - Pods run here        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Use case: Development, testing, small workloads
Resources: 1GB RAM, 1 CPU minimum

Multi-Node (Production)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Controller 1   โ”‚  โ”‚  Controller 2   โ”‚  โ”‚  Controller 3   โ”‚
โ”‚  (Control only) โ”‚  โ”‚  (Control only) โ”‚  โ”‚  (Control only) โ”‚
โ”‚  - API Server   โ”‚  โ”‚  - API Server   โ”‚  โ”‚  - API Server   โ”‚
โ”‚  - Scheduler    โ”‚  โ”‚  - Scheduler    โ”‚  โ”‚  - Scheduler    โ”‚
โ”‚  - etcd         โ”‚  โ”‚  - etcd         โ”‚  โ”‚  - etcd         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“                     โ†“                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      Load Balancer                         โ”‚
โ”‚               (HAProxy, Nginx, or cloud LB)                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†“                     โ†“                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Worker 1     โ”‚  โ”‚    Worker 2     โ”‚  โ”‚    Worker 3     โ”‚
โ”‚  - kubelet      โ”‚  โ”‚  - kubelet      โ”‚  โ”‚  - kubelet      โ”‚
โ”‚  - containerd   โ”‚  โ”‚  - containerd   โ”‚  โ”‚  - containerd   โ”‚
โ”‚  - Pods         โ”‚  โ”‚  - Pods         โ”‚  โ”‚  - Pods         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Use case: Production workloads
Resources: 2GB RAM, 2 CPU per controller; 4GB RAM, 2 CPU per worker

๐ŸŽ›๏ธ k0s Configuration

Default Configuration

# Generate default config
k0s config create > k0s.yaml

k0s.yaml (annotated):

apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
metadata:
  name: k0s
spec:
  api:
    # API server configuration
    address: 0.0.0.0  # Listen on all interfaces
    port: 6443
    k0sApiPort: 9443
    sans:  # Additional SANs for TLS cert
    - 10.0.1.10
    - controller.example.com
  
  storage:
    # etcd configuration
    type: etcd
    etcd:
      peerAddress: 10.0.1.10  # Controller's IP
  
  network:
    # Networking configuration
    provider: calico
    calico:
      mode: vxlan
    podCIDR: 10.244.0.0/16
    serviceCIDR: 10.96.0.0/12
  
  # Install default components
  extensions:
    helm:
      repositories:
      - name: stable
        url: https://charts.helm.sh/stable
      charts: []
    
    storage:
      type: external_storage
  
  # Telemetry (disable if needed)
  telemetry:
    enabled: true

Custom Configuration

k0s-custom.yaml:

apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
metadata:
  name: production-cluster
spec:
  api:
    address: 10.0.1.10
    port: 6443
    sans:
    - 10.0.1.10
    - 10.0.1.11
    - 10.0.1.12
    - k8s.example.com
    - "*.k8s.example.com"
  
  storage:
    type: etcd
    etcd:
      peerAddress: 10.0.1.10
  
  network:
    provider: calico
    calico:
      mode: vxlan
      mtu: 1450
    podCIDR: 10.244.0.0/16
    serviceCIDR: 10.96.0.0/12
  
  # Pod Security Standards
  podSecurityPolicy:
    defaultPolicy: restricted
  
  # kube-proxy mode
  konnectivity:
    agentPort: 8132
  
  # Controller manager flags
  controllerManager:
    extraArgs:
      node-monitor-grace-period: 40s
      node-monitor-period: 5s
  
  # Scheduler flags
  scheduler:
    extraArgs:
      v: "2"
  
  # Worker profiles
  workerProfiles:
  - name: default
    values:
      maxPods: 110
  - name: high-density
    values:
      maxPods: 250
  
  extensions:
    storage:
      create_default_storage_class: false
    
    helm:
      repositories:
      - name: bitnami
        url: https://charts.bitnami.com/bitnami
      
      charts:
      - name: metrics-server
        chartname: bitnami/metrics-server
        version: "6.2.0"
        namespace: kube-system

Install with config:

sudo k0s install controller --config k0s-custom.yaml
sudo k0s start

๐ŸŽฎ Controller Installation

Dedicated Controller (No Workers)

# Install controller only
sudo k0s install controller \
  --enable-worker=false \
  --config /etc/k0s/k0s.yaml

# Enable systemd service
sudo systemctl enable k0scontroller

# Start service
sudo systemctl start k0scontroller

# Check status
sudo k0s status
sudo systemctl status k0scontroller

# View logs
sudo journalctl -u k0scontroller -f

Controller with Custom Options

# Install with specific data dir
sudo k0s install controller \
  --data-dir /var/lib/k0s \
  --enable-worker=false \
  --config /etc/k0s/k0s.yaml

# Install with specific log level
sudo k0s install controller \
  --enable-worker=false \
  --logging '{"level": "debug"}' \
  --config /etc/k0s/k0s.yaml

Controller Systemd Service

View generated service:

cat /etc/systemd/system/k0scontroller.service

Example service file:

[Unit]
Description=k0s - Zero Friction Kubernetes
Documentation=https://docs.k0sproject.io
After=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/k0s
ExecStart=/usr/local/bin/k0s controller --config=/etc/k0s/k0s.yaml
Restart=always
RestartSec=5s
KillMode=process

[Install]
WantedBy=multi-user.target

๐Ÿ‘ท Worker Installation

Generate Worker Token

On controller:

# Generate token for worker to join
sudo k0s token create --role=worker > worker-token.txt

# Token is valid for 4 hours by default
# Generate with custom expiry
sudo k0s token create --role=worker --expiry=24h > worker-token.txt

Install Worker Node

On worker node:

# 1. Install k0s binary
curl -sSLf https://get.k0s.sh | sudo sh

# 2. Install as worker with token
sudo k0s install worker --token-file /path/to/worker-token.txt

# Or pass token directly
sudo k0s install worker --token "TOKEN_STRING_HERE"

# 3. Start worker
sudo k0s start

# 4. Check status
sudo k0s status
sudo systemctl status k0sworker

Worker with Custom Config

# Custom data dir and labels
sudo k0s install worker \
  --token-file /path/to/token.txt \
  --data-dir /data/k0s \
  --labels "node-role.kubernetes.io/worker=true,workload=web"

# Custom kubelet args
sudo k0s install worker \
  --token-file /path/to/token.txt \
  --kubelet-extra-args "--max-pods=250 --kube-reserved=cpu=500m,memory=1Gi"

Verify Worker Joined

On controller:

# List nodes
sudo k0s kubectl get nodes

# Output:
# NAME      STATUS   ROLES    AGE   VERSION
# worker1   Ready    <none>   1m    v1.28.5+k0s.0
# worker2   Ready    <none>   1m    v1.28.5+k0s.0

๐Ÿข High Availability Setup

3-Controller HA Cluster

Requirements:


Step 1: Initialize First Controller

controller1 (10.0.1.10):

# Create HA config
cat > /etc/k0s/k0s.yaml <<EOF
apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
metadata:
  name: ha-cluster
spec:
  api:
    externalAddress: loadbalancer.example.com
    sans:
    - 10.0.1.10
    - 10.0.1.11
    - 10.0.1.12
    - loadbalancer.example.com
  storage:
    type: etcd
    etcd:
      peerAddress: 10.0.1.10
EOF

# Install and start
sudo k0s install controller \
  --enable-worker=false \
  --config /etc/k0s/k0s.yaml

sudo k0s start

Step 2: Generate Controller Join Token

On controller1:

# Generate token for additional controllers
sudo k0s token create --role=controller > controller-token.txt

Step 3: Join Additional Controllers

controller2 (10.0.1.11):

# Create config (same as controller1 but different peerAddress)
cat > /etc/k0s/k0s.yaml <<EOF
apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
metadata:
  name: ha-cluster
spec:
  api:
    externalAddress: loadbalancer.example.com
    sans:
    - 10.0.1.10
    - 10.0.1.11
    - 10.0.1.12
    - loadbalancer.example.com
  storage:
    type: etcd
    etcd:
      peerAddress: 10.0.1.11
EOF

# Install with token
sudo k0s install controller \
  --enable-worker=false \
  --config /etc/k0s/k0s.yaml \
  --token-file /path/to/controller-token.txt

sudo k0s start

Repeat for controller3 (10.0.1.12)


Step 4: Verify HA Cluster

On any controller:

# Check etcd members
sudo k0s etcd member-list

# Output:
# member1, started, controller1, 10.0.1.10:2380
# member2, started, controller2, 10.0.1.11:2380
# member3, started, controller3, 10.0.1.12:2380

Load Balancer Configuration

HAProxy example (/etc/haproxy/haproxy.cfg):

frontend k8s_api
    bind *:6443
    mode tcp
    default_backend k8s_controllers

backend k8s_controllers
    mode tcp
    balance roundrobin
    option tcp-check
    
    server controller1 10.0.1.10:6443 check fall 3 rise 2
    server controller2 10.0.1.11:6443 check fall 3 rise 2
    server controller3 10.0.1.12:6443 check fall 3 rise 2

frontend k0s_api
    bind *:9443
    mode tcp
    default_backend k0s_controllers

backend k0s_controllers
    mode tcp
    balance roundrobin
    
    server controller1 10.0.1.10:9443 check fall 3 rise 2
    server controller2 10.0.1.11:9443 check fall 3 rise 2
    server controller3 10.0.1.12:9443 check fall 3 rise 2

๐Ÿค– Automated Deployment

Ansible Playbook

playbooks/k0s-cluster.yaml:

---
- name: Install k0s controllers
  hosts: controllers
  become: yes
  
  vars:
    k0s_version: v1.28.5+k0s.0
    cluster_name: production
  
  tasks:
    - name: Download k0s binary
      get_url:
        url: "https://github.com/k0sproject/k0s/releases/download/{{ k0s_version }}/k0s-{{ k0s_version }}-amd64"
        dest: /usr/local/bin/k0s
        mode: '0755'
    
    - name: Create k0s config directory
      file:
        path: /etc/k0s
        state: directory
    
    - name: Template k0s config
      template:
        src: k0s-config.yaml.j2
        dest: /etc/k0s/k0s.yaml
    
    - name: Install first controller
      command: k0s install controller --enable-worker=false --config /etc/k0s/k0s.yaml
      when: inventory_hostname == groups['controllers'][0]
    
    - name: Start first controller
      systemd:
        name: k0scontroller
        state: started
        enabled: yes
      when: inventory_hostname == groups['controllers'][0]
    
    - name: Wait for first controller
      wait_for:
        port: 6443
        timeout: 300
      when: inventory_hostname == groups['controllers'][0]
    
    - name: Generate controller join token
      command: k0s token create --role=controller
      register: controller_token
      delegate_to: "{{ groups['controllers'][0] }}"
      run_once: true
      when: inventory_hostname != groups['controllers'][0]
    
    - name: Install additional controllers
      command: k0s install controller --enable-worker=false --config /etc/k0s/k0s.yaml --token "{{ controller_token.stdout }}"
      when: inventory_hostname != groups['controllers'][0]
    
    - name: Start additional controllers
      systemd:
        name: k0scontroller
        state: started
        enabled: yes
      when: inventory_hostname != groups['controllers'][0]

- name: Install k0s workers
  hosts: workers
  become: yes
  
  tasks:
    - name: Download k0s binary
      get_url:
        url: "https://github.com/k0sproject/k0s/releases/download/{{ k0s_version }}/k0s-{{ k0s_version }}-amd64"
        dest: /usr/local/bin/k0s
        mode: '0755'
    
    - name: Generate worker join token
      command: k0s token create --role=worker
      register: worker_token
      delegate_to: "{{ groups['controllers'][0] }}"
      run_once: true
    
    - name: Install worker
      command: k0s install worker --token "{{ worker_token.stdout }}"
    
    - name: Start worker
      systemd:
        name: k0sworker
        state: started
        enabled: yes

Terraform Module

modules/k0s/main.tf:

resource "null_resource" "k0s_controller" {
  count = var.controller_count
  
  connection {
    host        = var.controller_ips[count.index]
    user        = "ubuntu"
    private_key = file(var.ssh_private_key)
  }
  
  provisioner "remote-exec" {
    inline = [
      "curl -sSLf https://get.k0s.sh | sudo sh",
      "sudo k0s install controller --enable-worker=false --config /tmp/k0s.yaml",
      "sudo k0s start",
    ]
  }
}

๐Ÿ’พ Backup and Restore

Backup Cluster

# Full backup (includes etcd data)
sudo k0s backup --save-path /backup/k0s-backup-$(date +%Y%m%d-%H%M%S).tar.gz

# Backup to specific location
sudo k0s backup --save-path /mnt/backups/k0s-backup.tar.gz

Backup contains:


Automated Backup Script

/usr/local/bin/k0s-backup.sh:

#!/bin/bash
set -e

BACKUP_DIR="/backup/k0s"
RETENTION_DAYS=7

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Perform backup
BACKUP_FILE="$BACKUP_DIR/k0s-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
k0s backup --save-path "$BACKUP_FILE"

# Compress
gzip -9 "$BACKUP_FILE"

# Remove old backups
find "$BACKUP_DIR" -name "k0s-backup-*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $BACKUP_FILE.gz"

Cron job:

# Daily backup at 2 AM
0 2 * * * /usr/local/bin/k0s-backup.sh >> /var/log/k0s-backup.log 2>&1

Restore Cluster

# Stop k0s
sudo k0s stop

# Restore from backup
sudo k0s restore /backup/k0s-backup-20260130-020000.tar.gz

# Start k0s
sudo k0s start

# Verify
sudo k0s kubectl get nodes

๐Ÿ”„ Upgrade k0s

Upgrade Strategy

Recommended order:

  1. Backup cluster
  2. Upgrade first controller
  3. Upgrade remaining controllers
  4. Upgrade workers

Upgrade Controller

# 1. Backup first
sudo k0s backup --save-path /backup/pre-upgrade-backup.tar.gz

# 2. Download new version
VERSION="v1.29.0+k0s.0"
wget https://github.com/k0sproject/k0s/releases/download/${VERSION}/k0s-${VERSION}-amd64

# 3. Stop k0s
sudo k0s stop

# 4. Replace binary
sudo mv k0s-${VERSION}-amd64 /usr/local/bin/k0s
sudo chmod +x /usr/local/bin/k0s

# 5. Start k0s
sudo k0s start

# 6. Verify version
k0s version

# 7. Check cluster health
sudo k0s kubectl get nodes

Automated Upgrade

Ansible playbook:

---
- name: Upgrade k0s cluster
  hosts: k0s_cluster
  serial: 1  # One host at a time
  become: yes
  
  vars:
    new_k0s_version: v1.29.0+k0s.0
  
  tasks:
    - name: Backup k0s
      command: k0s backup --save-path /backup/pre-upgrade-{{ inventory_hostname }}.tar.gz
      when: "'controllers' in group_names"
    
    - name: Download new k0s version
      get_url:
        url: "https://github.com/k0sproject/k0s/releases/download/{{ new_k0s_version }}/k0s-{{ new_k0s_version }}-amd64"
        dest: /tmp/k0s-new
        mode: '0755'
    
    - name: Stop k0s
      systemd:
        name: "{{ 'k0scontroller' if 'controllers' in group_names else 'k0sworker' }}"
        state: stopped
    
    - name: Replace binary
      copy:
        src: /tmp/k0s-new
        dest: /usr/local/bin/k0s
        mode: '0755'
        remote_src: yes
    
    - name: Start k0s
      systemd:
        name: "{{ 'k0scontroller' if 'controllers' in group_names else 'k0sworker' }}"
        state: started
    
    - name: Wait for node ready
      command: k0s kubectl wait --for=condition=Ready node/{{ inventory_hostname }} --timeout=300s
      delegate_to: "{{ groups['controllers'][0] }}"
      when: "'workers' in group_names"

๐Ÿ” Troubleshooting

Check Service Status

# Service status
sudo systemctl status k0scontroller  # or k0sworker

# View logs
sudo journalctl -u k0scontroller -f

# Check k0s status
sudo k0s status

# Check component health
sudo k0s kubectl get componentstatuses

Common Issues

Issue: Controller won't start

# Check logs
sudo journalctl -u k0scontroller -n 100

# Verify config
sudo k0s config validate /etc/k0s/k0s.yaml

# Check port availability
sudo ss -tulpn | grep -E ':(6443|9443|2379|2380)'

Issue: Worker can't join

# Verify token
sudo k0s token create --role=worker

# Check connectivity to controller
telnet controller-ip 6443

# Check worker logs
sudo journalctl -u k0sworker -f

Issue: etcd cluster unhealthy

# Check etcd status
sudo k0s etcd member-list

# Check etcd health
sudo k0s kubectl get cs

# View etcd logs
sudo journalctl -u k0scontroller | grep etcd

๐Ÿ”— What's Next?

Multi-Node Clusters:

Networking:

Storage:


๐Ÿ“š Resources

Official Docs:

Tools:


๐Ÿ“ Change Log

2026-01-30


Next Article: k0s-multi-node - Production clusters!

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse