OpenSibleOpenSible Stack Hub
← All blueprints

k3s HA (embedded etcd)

OSS

Lightweight k3s cluster with embedded etcd and Longhorn storage.

by opensible· ⬇ 0 installs· Kubernetes· v1.0.0· template k3s-bootstrap

Use in the OpenSible console

Open Infrastructure → Stack Hub Blueprints, switch the source to Cloud hub, then pick k3s HA (embedded etcd) and press Use.

Requirements

  • ansible >=2.14

Default variables

defaults.json
{
  "agents": [
    {
      "ip": "10.0.0.20",
      "name": "k3s-agent-1"
    }
  ],
  "ha_mode": false,
  "servers": [
    {
      "ip": "10.0.0.10",
      "name": "k3s-server-1"
    }
  ],
  "k3s_version": "stable",
  "cluster_name": "opensible-k3s",
  "cluster_token": "change-me-please",
  "disable_traefik": true,
  "install_longhorn": false
}

vars.example.yml

vars.example.yml
---
cluster_name: opensible-k3s
cluster_token: "change-me-please"
k3s_version: v1.30.4+k3s1

ha_mode: false
control_plane_endpoint: ""    # VIP/DNS when ha_mode is true

disable_traefik: true
disable_servicelb: false
disable_local_storage: false
disable_network_policy: false

flannel_backend: vxlan
cluster_cidr: 10.42.0.0/16
service_cidr: 10.43.0.0/16
cluster_dns: 10.43.0.10

install_longhorn: false
longhorn_replica_count: 3

reset_existing_cluster: true
open_firewall: true
fetch_kubeconfig: true

servers:
  - { name: k3s-server-1, ip: 10.0.0.10 }
agents:
  - { name: k3s-agent-1,  ip: 10.0.0.20 }

Playbook (playbook.yml)

playbook.yml
---
# Rendered from template: k3s Cluster Bootstrap
# OpenSible k3s template generation: 2026-07-k3s-hardened-v10
# Cluster: opensible-k3s (HA=False, servers=1, agents=1)
# k3s version/channel: stable | Flannel: vxlan | Pod CIDR: 10.42.0.0/16
# Inventory sidecar: inventories/opensible-k3s.yml

- name: Prepare nodes for k3s
  hosts: 10.0.0.10,10.0.0.20,
  become: true
  gather_facts: true
  any_errors_fatal: true
  vars:
    _node_users:
      10.0.0.10: root
      10.0.0.20: root
    _node_ports:
      10.0.0.10: 22
      10.0.0.20: 22
    _node_names:
      10.0.0.10: "k3s-server-1"
      10.0.0.20: "k3s-agent-1"
  pre_tasks:
    - name: Apply per-host SSH connection settings
      ansible.builtin.set_fact:
        ansible_user: "{{ _node_users[inventory_hostname] | default(ansible_user) }}"
        ansible_port: "{{ _node_ports[inventory_hostname] | default(ansible_port | default(22)) }}"
  tasks:
    - name: Detect container/LXC environment (OrbStack, Proxmox LXC, etc.)
      ansible.builtin.shell: |
        set -e
        virt=$(systemd-detect-virt 2>/dev/null || echo unknown)
        echo "virt=$virt"
      register: k3s_virt_probe
      changed_when: false
      failed_when: false

    - name: Set fact for LXC/container-safe defaults
      ansible.builtin.set_fact:
        k3s_in_container: "{{ 'lxc' in (k3s_virt_probe.stdout | default('')) or 'container' in (k3s_virt_probe.stdout | default('')) or 'docker' in (k3s_virt_probe.stdout | default('')) }}"

    - name: Remove existing Kubernetes/k3s state before install
      ansible.builtin.shell: |
        set +e
        echo '[pre-clean] removing existing kubeadm/k3s/rke2/microk8s state if present'
        for unit in k3s k3s-agent rke2-server rke2-agent kubelet snap.microk8s.daemon-kubelite microk8s.daemon-kubelite; do
          systemctl stop "$unit" 2>/dev/null || true
          systemctl disable "$unit" 2>/dev/null || true
          systemctl reset-failed "$unit" 2>/dev/null || true
        done
        if [ -x /usr/local/bin/k3s-uninstall.sh ]; then /usr/local/bin/k3s-uninstall.sh || true; fi
        if [ -x /usr/local/bin/k3s-agent-uninstall.sh ]; then /usr/local/bin/k3s-agent-uninstall.sh || true; fi
        if [ -x /usr/local/bin/rke2-uninstall.sh ]; then /usr/local/bin/rke2-uninstall.sh || true; fi
        if [ -x /usr/local/bin/rke2-agent-uninstall.sh ]; then /usr/local/bin/rke2-agent-uninstall.sh || true; fi
        if command -v kubeadm >/dev/null 2>&1; then
          timeout 60s kubeadm reset -f --cri-socket=unix:///run/containerd/containerd.sock 2>/dev/null || timeout 60s kubeadm reset -f || true
        fi
        if command -v microk8s >/dev/null 2>&1; then
          timeout 60s microk8s reset --destroy-storage 2>/dev/null || true
          snap remove microk8s --purge 2>/dev/null || true
        fi
        # Stop any leftover Kubernetes API process that still owns 6443.
        if command -v ss >/dev/null 2>&1; then
          ss -H -ltnp 'sport = :6443' 2>/dev/null | while read -r line; do
            echo "[pre-clean] 6443 listener: $line"
            for pid in $(printf '%s\n' "$line" | sed -n 's/.*pid=\([0-9][0-9]*\).*/\1/p'); do
              comm=$(ps -o comm= -p "$pid" 2>/dev/null | tr -d ' ')
              case "$comm" in
                kube-apiserver|k3s|rke2|kubelite|microk8s*) kill "$pid" 2>/dev/null || true; sleep 2; kill -9 "$pid" 2>/dev/null || true ;;
                *) echo "[pre-clean] not killing non-Kubernetes process pid=$pid comm=$comm" ;;
              esac
            done
          done
        fi
        # Unmount kubelet/CNI mounts before deleting directories.
        awk '$2 ~ /^\/var\/lib\/kubelet/ || $2 ~ /^\/run\/k3s/ || $2 ~ /^\/run\/flannel/ {print $2}' /proc/mounts 2>/dev/null | sort -r | xargs -r -n1 umount -fl 2>/dev/null || true
        rm -rf /etc/kubernetes /var/lib/etcd /etc/cni/net.d /var/lib/cni /var/lib/kubelet \
               /etc/rancher/k3s /var/lib/rancher/k3s /run/k3s /run/flannel \
               /etc/rancher/rke2 /var/lib/rancher/rke2 /var/lib/rancher/agent \
               /var/snap/microk8s /snap/microk8s 2>/dev/null || true
        for link in cni0 flannel.1 flannel-v6.1 kube-ipvs0 flannel-wg flannel-wg-v6 vxlan.calico; do ip link delete "$link" 2>/dev/null || true; done
        ip link show 2>/dev/null | awk -F': ' '/^[0-9]+: cali/ {print $2}' | cut -d@ -f1 | xargs -r -n1 ip link delete 2>/dev/null || true
        if command -v iptables-save >/dev/null 2>&1 && command -v iptables-restore >/dev/null 2>&1; then
          iptables-save 2>/dev/null | grep -vE 'KUBE-|CNI-|FLANNEL|cali-|K3S' | iptables-restore 2>/dev/null || true
        fi
        if command -v ip6tables-save >/dev/null 2>&1 && command -v ip6tables-restore >/dev/null 2>&1; then
          ip6tables-save 2>/dev/null | grep -vE 'KUBE-|CNI-|FLANNEL|cali-|K3S' | ip6tables-restore 2>/dev/null || true
        fi
        systemctl daemon-reload 2>/dev/null || true
        for i in $(seq 1 20); do
          if ! command -v ss >/dev/null 2>&1 || ! ss -H -ltn 'sport = :6443' 2>/dev/null | grep -q LISTEN; then
            echo '[pre-clean] port 6443 is free'
            exit 0
          fi
          sleep 1
        done
        echo '[pre-clean] warning: port 6443 is still in use after cleanup'
        ss -H -ltnp 'sport = :6443' 2>/dev/null || true
      args: { executable: /bin/bash }
      failed_when: false
      changed_when: true

    - name: Disable swap now with bounded timeout (skipped in containers)
      ansible.builtin.shell: |
        set +e
        active_swaps=$(awk 'NR > 1 { count++ } END { print count + 0 }' /proc/swaps 2>/dev/null || echo 0)
        if [ "$active_swaps" -eq 0 ]; then
          echo 'No active swap devices found.'
          exit 0
        fi
        echo "Found $active_swaps active swap device(s); disabling with timeout."
        if command -v timeout >/dev/null 2>&1; then
          timeout 15s swapoff -a
          rc=$?
          if [ "$rc" -eq 124 ]; then
            echo 'swapoff timed out after 15s; continuing so automation does not hang.'
          fi
          exit 0
        fi
        python3 - <<'PY' || true
        import subprocess
        try:
            subprocess.run(['swapoff', '-a'], timeout=15, check=False)
        except Exception as exc:
            print(f'swapoff skipped/timeout: {exc}')
        PY
      args: { executable: /bin/bash }
      failed_when: false
      changed_when: false
      when: not k3s_in_container | bool

    - name: Check if /etc/fstab exists
      ansible.builtin.stat:
        path: /etc/fstab
      register: k3s_fstab_stat

    - name: Disable swap in /etc/fstab (persistent)
      ansible.builtin.replace:
        path: /etc/fstab
        regexp: '^([^#].*\sswap\s.*)$'
        replace: '# \1'
      when: k3s_fstab_stat.stat.exists | bool and not k3s_in_container | bool
      failed_when: false

    - name: Load required kernel modules
      ansible.builtin.shell: |
        modprobe br_netfilter 2>/dev/null || true
        modprobe overlay 2>/dev/null || true
        modprobe nf_conntrack 2>/dev/null || true
      changed_when: false
      failed_when: false
      when: not k3s_in_container | bool

    - name: Persist kernel modules
      ansible.builtin.copy:
        dest: /etc/modules-load.d/k3s.conf
        mode: '0644'
        content: |
          br_netfilter
          overlay
          nf_conntrack
      when: not k3s_in_container | bool
      failed_when: false

    - name: Set sysctl for Kubernetes networking
      ansible.posix.sysctl:
        name: "{{ item.name }}"
        value: "{{ item.value }}"
        sysctl_file: /etc/sysctl.d/99-k3s.conf
        state: present
        reload: true
      loop:
        - { name: net.bridge.bridge-nf-call-iptables, value: '1' }
        - { name: net.bridge.bridge-nf-call-ip6tables, value: '1' }
        - { name: net.ipv4.ip_forward, value: '1' }
      when: not k3s_in_container | bool
      failed_when: false

    - name: Detect firewall in use
      ansible.builtin.shell: |
        if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi active; then echo ufw
        elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state 2>/dev/null | grep -qi running; then echo firewalld
        else echo none
        fi
      register: k3s_fw
      changed_when: false
      failed_when: false

    - name: Open k3s firewall ports (ufw)
      ansible.builtin.shell: |
        ufw allow 6443/tcp || true
        ufw allow 10250/tcp || true
        ufw allow 8472/udp || true
        ufw allow 51820/udp || true
      when: k3s_fw.stdout | default('') == 'ufw'
      changed_when: false
      failed_when: false

    - name: Open k3s firewall ports (firewalld)
      ansible.builtin.shell: |
        firewall-cmd --permanent --add-port=6443/tcp || true
        firewall-cmd --permanent --add-port=10250/tcp || true
        firewall-cmd --permanent --add-port=8472/udp || true
        firewall-cmd --permanent --add-port=51820/udp || true
        firewall-cmd --reload || true
      when: k3s_fw.stdout | default('') == 'firewalld'
      changed_when: false
      failed_when: false

- name: Install first k3s server (k3s-server-1)
  hosts: 10.0.0.10,
  become: true
  gather_facts: false
  any_errors_fatal: true
  vars:
    k3s_token: "change-me-please"
    _node_users:
      10.0.0.10: root
    _node_ports:
      10.0.0.10: 22
    _node_names:
      10.0.0.10: "k3s-server-1"
  pre_tasks:
    - name: Apply per-host SSH connection settings
      ansible.builtin.set_fact:
        ansible_user: "{{ _node_users[inventory_hostname] | default(ansible_user) }}"
        ansible_port: "{{ _node_ports[inventory_hostname] | default(ansible_port | default(22)) }}"
  tasks:
    - name: Install first k3s server (bootstrap, with retry + diagnostics)
      ansible.builtin.shell: |
        set -o pipefail
        cleanup_existing_kubernetes() {
          set +e
          for unit in k3s k3s-agent rke2-server rke2-agent kubelet snap.microk8s.daemon-kubelite microk8s.daemon-kubelite; do systemctl stop "$unit" 2>/dev/null || true; systemctl disable "$unit" 2>/dev/null || true; systemctl reset-failed "$unit" 2>/dev/null || true; done
          if [ -x /usr/local/bin/k3s-uninstall.sh ]; then /usr/local/bin/k3s-uninstall.sh || true; fi
          if [ -x /usr/local/bin/k3s-agent-uninstall.sh ]; then /usr/local/bin/k3s-agent-uninstall.sh || true; fi
          if [ -x /usr/local/bin/rke2-uninstall.sh ]; then /usr/local/bin/rke2-uninstall.sh || true; fi
          if [ -x /usr/local/bin/rke2-agent-uninstall.sh ]; then /usr/local/bin/rke2-agent-uninstall.sh || true; fi
          if command -v kubeadm >/dev/null 2>&1; then timeout 60s kubeadm reset -f --cri-socket=unix:///run/containerd/containerd.sock 2>/dev/null || timeout 60s kubeadm reset -f || true; fi
          if command -v microk8s >/dev/null 2>&1; then timeout 60s microk8s reset --destroy-storage 2>/dev/null || true; snap remove microk8s --purge 2>/dev/null || true; fi
          if command -v ss >/dev/null 2>&1; then
            ss -H -ltnp 'sport = :6443' 2>/dev/null | while read -r line; do
              echo "[k3s-bootstrap] 6443 listener before retry cleanup: $line"
              for pid in $(printf '%s\n' "$line" | sed -n 's/.*pid=\([0-9][0-9]*\).*/\1/p'); do
                comm=$(ps -o comm= -p "$pid" 2>/dev/null | tr -d ' ')
                case "$comm" in kube-apiserver|k3s|rke2|kubelite|microk8s*) kill "$pid" 2>/dev/null || true; sleep 2; kill -9 "$pid" 2>/dev/null || true ;; *) echo "[k3s-bootstrap] non-Kubernetes 6443 owner kept: pid=$pid comm=$comm" ;; esac
              done
            done
          fi
          awk '$2 ~ /^\/var\/lib\/kubelet/ || $2 ~ /^\/run\/k3s/ || $2 ~ /^\/run\/flannel/ {print $2}' /proc/mounts 2>/dev/null | sort -r | xargs -r -n1 umount -fl 2>/dev/null || true
          rm -rf /etc/kubernetes /var/lib/etcd /etc/cni/net.d /var/lib/cni /var/lib/kubelet /etc/rancher/k3s /var/lib/rancher/k3s /run/k3s /run/flannel /etc/rancher/rke2 /var/lib/rancher/rke2 /var/lib/rancher/agent 2>/dev/null || true
          # Nuke stale kubeconfigs from prior kubeadm/k3s installs so k3s kubectl
          # does not pick up old CA certs and hit x509 unknown-authority errors.
          rm -f /root/.kube/config /root/.kube/config.bak /etc/kubernetes/admin.conf 2>/dev/null || true
          for h in $(ls /home 2>/dev/null); do rm -f "/home/$h/.kube/config" 2>/dev/null || true; done
          for link in cni0 flannel.1 flannel-v6.1 kube-ipvs0 flannel-wg flannel-wg-v6 vxlan.calico; do ip link delete "$link" 2>/dev/null || true; done
          ip link show 2>/dev/null | awk -F': ' '/^[0-9]+: cali/ {print $2}' | cut -d@ -f1 | xargs -r -n1 ip link delete 2>/dev/null || true
          systemctl daemon-reload 2>/dev/null || true
          set -e
        }
        cleanup_existing_kubernetes
        # Detect containerized host (LXC/OrbStack/Docker) and pick a safe snapshotter.
        EXTRA=""
        virt="$(systemd-detect-virt 2>/dev/null || echo unknown)"
        case "$virt" in
          lxc|lxc-libvirt|docker|podman|container-other|openvz|systemd-nspawn)
            EXTRA="--snapshotter=native" ;;
        esac
        # Also force native if overlayfs-on-overlayfs is detected
        if [ -z "$EXTRA" ] && grep -qE 'overlay .* overlay' /proc/mounts 2>/dev/null; then
          if mount | awk '$3=="/var/lib" || $3=="/" {print $5}' | grep -q overlay; then
            EXTRA="--snapshotter=native"
          fi
        fi
        attempt=0; max=3
        while [ $attempt -lt $max ]; do
          attempt=$((attempt+1))
          echo "[k3s-bootstrap] attempt $attempt/$max (extra=$EXTRA)"
          if curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL="stable" K3S_TOKEN='{{ k3s_token }}' sh -s - server --disable=traefik --cluster-cidr=10.42.0.0/16 --service-cidr=10.43.0.0/16 --cluster-dns=10.43.0.10 --write-kubeconfig-mode=0640 --node-name=k3s-server-1 --node-ip=10.0.0.10 $EXTRA; then
            systemctl is-active --quiet k3s && exit 0
          fi
          echo '[k3s-bootstrap] k3s failed to start — dumping diagnostics:'
          systemctl status k3s --no-pager -l 2>&1 | tail -n 40 || true
          journalctl -xeu k3s --no-pager 2>&1 | tail -n 80 || true
          echo '[k3s-bootstrap] cleaning up before retry'
          cleanup_existing_kubernetes
          # On second attempt, force native snapshotter regardless of detection
          EXTRA="--snapshotter=native"
          sleep 10
        done
        echo '[k3s-bootstrap] exhausted retries'
        exit 1
      args:
        executable: /bin/bash

    - name: Wait for k3s API to be reachable on first server
      ansible.builtin.wait_for:
        host: "{{ inventory_hostname }}"
        port: 6443
        timeout: 300

    - name: Wait for node-token file
      ansible.builtin.wait_for:
        path: /var/lib/rancher/k3s/server/node-token
        timeout: 180

    - name: Read node-token
      ansible.builtin.slurp:
        src: /var/lib/rancher/k3s/server/node-token
      register: k3s_node_token_b64

    - name: Publish join facts
      ansible.builtin.set_fact:
        k3s_node_token: "{{ k3s_node_token_b64.content | b64decode | trim }}"
        k3s_server_url: "https://10.0.0.10:6443"

    - name: Wait for first server node Ready
      ansible.builtin.shell: |
        set +e
        # Force k3s kubectl to use the k3s-generated kubeconfig. A stale
        # /root/.kube/config or KUBECONFIG env from a previous kubeadm/k3s
        # install would otherwise cause x509 certificate signed by unknown authority.
        unset KUBECONFIG
        export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
        KUBECTL='/usr/local/bin/k3s kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml'
        expected="{{ _node_names[inventory_hostname] }}"
        node_ip="{{ inventory_hostname }}"
        nodes="$($KUBECTL get nodes -o wide --no-headers 2>/tmp/k3s-nodes.err)"
        rc=$?
        if [ $rc -ne 0 ]; then
          echo "[first-ready] kubectl failed rc=$rc"
          cat /tmp/k3s-nodes.err 2>/dev/null || true
          systemctl status k3s --no-pager -l 2>&1 | tail -n 25 || true
          journalctl -u k3s --no-pager 2>&1 | tail -n 40 || true
          exit 1
        fi
        printf '%s\n' "$nodes"
        status="$(printf '%s\n' "$nodes" | awk -v n="$expected" '$1==n {print $2; exit}')"
        if [ "$status" != "Ready" ]; then
          status="$(printf '%s\n' "$nodes" | awk -v ip="$node_ip" '$6==ip {print $2; exit}')"
        fi
        if [ "$status" = "Ready" ]; then
          echo READY
          exit 0
        fi
        echo "[first-ready] expected node '$expected' at IP '$node_ip' is not Ready yet (status=${status:-missing})"
        $KUBECTL get pods -A -o wide 2>/dev/null | tail -n 30 || true
        journalctl -u k3s --no-pager 2>&1 | tail -n 30 || true
        exit 1
      environment:
        KUBECONFIG: /etc/rancher/k3s/k3s.yaml
      register: k3s_first_ready
      changed_when: false
      retries: 45
      delay: 10
      until: k3s_first_ready.rc == 0

- name: Join k3s agents
  hosts: 10.0.0.20,
  become: true
  gather_facts: false
  any_errors_fatal: false
  vars:
    k3s_token: "change-me-please"
    k3s_server_url: "https://10.0.0.10:6443"
    _node_users:
      10.0.0.20: root
    _node_ports:
      10.0.0.20: 22
    _node_names:
      10.0.0.20: "k3s-agent-1"
  pre_tasks:
    - name: Apply per-host SSH connection settings
      ansible.builtin.set_fact:
        ansible_user: "{{ _node_users[inventory_hostname] | default(ansible_user) }}"
        ansible_port: "{{ _node_ports[inventory_hostname] | default(ansible_port | default(22)) }}"
  tasks:
    - name: Install k3s agent (with retry)
      ansible.builtin.shell: |
        set -o pipefail
        EXTRA=""
        virt="$(systemd-detect-virt 2>/dev/null || echo unknown)"
        case "$virt" in lxc|lxc-libvirt|docker|podman|container-other|openvz|systemd-nspawn) EXTRA="--snapshotter=native" ;; esac
        attempt=0; max=5
        while [ $attempt -lt $max ]; do
          attempt=$((attempt+1))
          echo "[join-agent] attempt $attempt/$max (extra=$EXTRA)"
          if curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL="stable" K3S_TOKEN='{{ k3s_token }}' K3S_URL='{{ k3s_server_url }}' sh -s - agent --node-name={{ _node_names[inventory_hostname] }} --node-ip={{ inventory_hostname }} $EXTRA; then
            systemctl is-active --quiet k3s-agent && exit 0
          fi
          echo '[join-agent] failed, dumping diagnostics + cleaning up'
          journalctl -xeu k3s-agent --no-pager 2>&1 | tail -n 60 || true
          if [ -x /usr/local/bin/k3s-agent-uninstall.sh ]; then /usr/local/bin/k3s-agent-uninstall.sh || true; fi
          rm -rf /etc/rancher/k3s /var/lib/rancher/k3s /run/k3s /var/lib/kubelet || true
          EXTRA="--snapshotter=native"
          sleep 15
        done
        echo '[join-agent] exhausted retries'
        exit 1
      args:
        executable: /bin/bash
        creates: /usr/local/bin/k3s

- name: Post-install verification and add-ons
  hosts: 10.0.0.10,
  become: true
  gather_facts: false
  vars:
    _node_users:
      10.0.0.10: root
    _node_ports:
      10.0.0.10: 22
    _node_names:
      10.0.0.10: "k3s-server-1"
  pre_tasks:
    - name: Apply per-host SSH connection settings
      ansible.builtin.set_fact:
        ansible_user: "{{ _node_users[inventory_hostname] | default(ansible_user) }}"
        ansible_port: "{{ _node_ports[inventory_hostname] | default(ansible_port | default(22)) }}"
  tasks:
    - name: Wait for all nodes to register (Ready)
      ansible.builtin.shell: |
        /usr/local/bin/k3s kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml get nodes --no-headers | awk '{print $2}' | grep -c '^Ready$' || true
      environment:
        KUBECONFIG: /etc/rancher/k3s/k3s.yaml
      register: k3s_ready_count
      changed_when: false
      retries: 60
      delay: 10
      until: (k3s_ready_count.stdout | default('0') | int) >= 2
      failed_when: false

    - name: Show cluster nodes
      ansible.builtin.command: /usr/local/bin/k3s kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml get nodes -o wide
      environment:
        KUBECONFIG: /etc/rancher/k3s/k3s.yaml
      register: k3s_nodes_out
      changed_when: false
      failed_when: false

    - name: Display node listing
      ansible.builtin.debug:
        msg: "{{ k3s_nodes_out.stdout_lines | default([]) }}"

    - name: Read kubeconfig from first server
      ansible.builtin.slurp:
        src: /etc/rancher/k3s/k3s.yaml
      register: k3s_kubeconfig_b64

    - name: Ensure local ~/.kube exists
      ansible.builtin.file:
        path: "{{ lookup('env', 'HOME') }}/.kube"
        state: directory
        mode: '0700'
      delegate_to: localhost
      become: false
      run_once: true

    - name: Write kubeconfig to controller (~/.kube/opensible-k3s.yaml)
      ansible.builtin.copy:
        dest: "{{ lookup('env', 'HOME') }}/.kube/opensible-k3s.yaml"
        mode: '0600'
        content: '{{ (k3s_kubeconfig_b64.content | b64decode) | replace("127.0.0.1", "10.0.0.10") | replace("default", "opensible-k3s") }}'
      delegate_to: localhost
      become: false
      run_once: true

- name: Install root kubeconfig on all server nodes
  hosts: 10.0.0.10,
  become: true
  gather_facts: false
  vars:
    _node_users:
      10.0.0.10: root
    _node_ports:
      10.0.0.10: 22
    _node_names:
      10.0.0.10: "k3s-server-1"
  pre_tasks:
    - name: Apply per-host SSH connection settings
      ansible.builtin.set_fact:
        ansible_user: "{{ _node_users[inventory_hostname] | default(ansible_user) }}"
        ansible_port: "{{ _node_ports[inventory_hostname] | default(ansible_port | default(22)) }}"
  tasks:
    - name: Wait for /etc/rancher/k3s/k3s.yaml
      ansible.builtin.wait_for:
        path: /etc/rancher/k3s/k3s.yaml
        timeout: 120
    - name: Refresh /root/.kube/config from k3s
      ansible.builtin.shell: |
        set -e
        install -d -m 0700 /root/.kube
        cp -f /etc/rancher/k3s/k3s.yaml /root/.kube/config
        chmod 0600 /root/.kube/config
        chown root:root /root/.kube/config
        if ! grep -q 'KUBECONFIG=/root/.kube/config' /root/.bashrc 2>/dev/null; then
          echo 'export KUBECONFIG=/root/.kube/config' >> /root/.bashrc
        fi
      changed_when: false

Versions (1)

  • v1.0.0playbook.yml