/static/codemoomoo2.png

สร้าง k3s Cluster แบบ HA 3 โหนด บน openSUSE Tumbleweed, Ubuntu และ Arch Linux

คู่มือแบบละเอียดสำหรับการติดตั้ง join และดูแล k3s cluster แบบ high-availability ที่แต่ละโหนดรันคนละ distro พร้อมตัวอย่างการ deploy ใช้งานจริง

สารบัญ

  1. บทนำ
  2. ภาพรวมสถาปัตยกรรม
  3. Lab Topology และสิ่งที่ต้องเตรียม
  4. ขั้นตอนเตรียมเครื่องที่เหมือนกันทุกโหนด
  5. โหนดที่ 1 — openSUSE Tumbleweed (Server ตัวแรก)
  6. โหนดที่ 2 — Ubuntu (Server ตัวที่สอง)
  7. โหนดที่ 3 — Arch Linux (Server ตัวที่สาม)
  8. ตรวจสอบ Cluster
  9. เข้าถึง Cluster ด้วย kubectl จาก Workstation
  10. ตัวอย่างการ Deploy
  11. การดูแลรักษา Cluster
  12. การแก้ปัญหา
  13. สรุป

1. บทนำ

k3s เป็น Kubernetes distribution แบบเบาที่ได้รับการรับรอง CNCF พัฒนาโดย Rancher (ปัจจุบันอยู่ใต้ SUSE) ต่างจาก Docker Swarm cluster ที่ทุกโหนดต้องรัน Docker Engine binary เดียวกันเป๊ะ ๆ k3s cluster ต้องการแค่ให้แต่ละโหนดรัน k3s เวอร์ชัน เดียวกันเท่านั้น — ระบบปฏิบัติการที่อยู่ข้างใต้ไม่มีผลต่อ control plane เลย ทำให้เหมาะกับ home lab หรือ edge fleet ที่มีฮาร์ดแวร์และ distro หลากหลายสะสมมาตามกาลเวลา

คู่มือนี้จะสร้าง 3-server, embedded-etcd, highly-available cluster โดยใช้ 3 distro ที่ต่างกัน — หนึ่งโหนดต่อหนึ่ง distro:

โหนด Hostname Distro บทบาท
โหนด 1 k3s-node1 openSUSE Tumbleweed Server (cluster-init)
โหนด 2 k3s-node2 Ubuntu (22.04/24.04) Server (join)
โหนด 3 k3s-node3 Arch Linux Server (join)

เนื่องจากทั้ง 3 โหนดรัน server role พร้อม embedded etcd ถ้าโหนดใดโหนดหนึ่งล่ม cluster ก็ยังคง serve API และ schedule workload ต่อได้ตามปกติ — resilience ระดับเดียวกับที่คุณคาดหวังจาก Docker Swarm แบบ 3-manager หรือ Nomad/Consul quorum แบบ 3-server

2. ภาพรวมสถาปัตยกรรม

3. Lab Topology และสิ่งที่ต้องเตรียม

ตัวอย่างชุด IP ที่ใช้ตลอดคู่มือนี้ — เปลี่ยนเป็นของคุณเองได้:

โหนด IP Address
k3s-node1 (openSUSE) 192.168.1.11
k3s-node2 (Ubuntu) 192.168.1.12
k3s-node3 (Arch) 192.168.1.13

สิ่งที่ต้องมีในทุกโหนด:

4. ขั้นตอนเตรียมเครื่องที่เหมือนกันทุกโหนด

รันคำสั่งต่อไปนี้บน ทั้งสามโหนด ไม่ว่าจะเป็น distro ไหนก็ตาม

ตั้ง hostname ที่ไม่ซ้ำกัน:

sudo hostnamectl set-hostname k3s-node1   # เปลี่ยนตามแต่ละโหนด

ทำให้ทุกโหนด resolve หากันได้ ตั้ง local DNS หรือเพิ่ม entry ใน /etc/hosts บนทั้งสามโหนด:

sudo tee -a /etc/hosts <<'EOF'
192.168.1.11 k3s-node1
192.168.1.12 k3s-node2
192.168.1.13 k3s-node3
EOF

Port ที่ firewall ต้องเปิดระหว่างโหนด (คำสั่งเฉพาะแต่ละ distro อยู่ในหัวข้อถัดไป):

Port Protocol หน้าที่
6443 TCP Kubernetes API server
2379-2380 TCP etcd client/peer traffic (เฉพาะระหว่าง server ด้วยกัน)
8472 UDP Flannel VXLAN overlay (traffic ระหว่าง pod)
10250 TCP Kubelet metrics/logs API
51820, 51821 UDP Flannel Wireguard backend (เฉพาะถ้าเปลี่ยน CNI backend เป็น Wireguard)

5. โหนดที่ 1 — openSUSE Tumbleweed (Server ตัวแรก)

เตรียม package และ firewall:

sudo zypper refresh
sudo zypper install -y curl open-iscsi

# openSUSE มี firewalld มาให้เป็น default
sudo firewall-cmd --permanent --add-port=6443/tcp
sudo firewall-cmd --permanent --add-port=2379-2380/tcp
sudo firewall-cmd --permanent --add-port=8472/udp
sudo firewall-cmd --permanent --add-port=10250/tcp
sudo firewall-cmd --reload

ติดตั้ง k3s และเริ่มต้น cluster:

curl -sfL https://get.k3s.io | sh -s - server \
  --cluster-init \
  --node-name k3s-node1 \
  --write-kubeconfig-mode 644

--cluster-init บอกให้ k3s สร้าง embedded-etcd cluster ใหม่ตั้งแต่ต้นบนโหนดนี้ แทนที่จะ join cluster ที่มีอยู่แล้ว

ดึง token สำหรับ join — ต้องใช้กับโหนดที่ 2 และ 3:

sudo cat /var/lib/rancher/k3s/server/node-token

ยืนยันว่า service ทำงานปกติ:

sudo systemctl status k3s
sudo k3s kubectl get nodes

6. โหนดที่ 2 — Ubuntu (Server ตัวที่สอง)

เตรียม package และ firewall:

sudo apt update
sudo apt install -y curl

sudo ufw allow 6443/tcp
sudo ufw allow 2379:2380/tcp
sudo ufw allow 8472/udp
sudo ufw allow 10250/tcp
sudo ufw reload

ข้อควรระวังเรื่อง Ubuntu + systemd-resolved: DNS stub resolver default ของ Ubuntu (127.0.0.53) อาจทำให้ CoreDNS ในคลัสเตอร์สับสนได้ ถ้าภายหลังพบว่า DNS resolution จาก pod มีปัญหา ให้ติดตั้ง k3s ใหม่โดยเพิ่ม --resolv-conf /run/systemd/resolve/resolv.conf เพื่อข้าม stub ของ systemd-resolved

Join เป็น server เพิ่ม (แทนที่ <TOKEN> ด้วยค่าจากโหนด 1):

curl -sfL https://get.k3s.io | sh -s - server \
  --server https://192.168.1.11:6443 \
  --token <TOKEN> \
  --node-name k3s-node2 \
  --write-kubeconfig-mode 644

ข้อมูลอ้างอิง — ถ้าต้องการ agent-only node แทน (ไม่ได้ใช้ใน topology 3-server นี้ แต่มีประโยชน์ถ้าภายหลังเพิ่มเครื่องที่ 4 ไว้รัน workload อย่างเดียว):

curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.11:6443 K3S_TOKEN=<TOKEN> sh -

7. โหนดที่ 3 — Arch Linux (Server ตัวที่สาม)

Arch ไม่มี package k3s อย่างเป็นทางการใน repo หลัก แต่ install script จากต้นทางใช้งานได้เหมือนกับ distro อื่นที่ใช้ systemd ทุกประการ นอกจากนี้ยังมี AUR package (k3s-bin) ให้เลือกใช้ถ้าอยากติดตั้งผ่าน pacman

เตรียม package และ firewall. Arch ไม่มี firewall มาให้เป็น default — ติดตั้ง firewalld ให้สอดคล้องกับอีกสองโหนด หรือจะใช้ iptables/nftables ตรง ๆ ก็ได้:

sudo pacman -Syu --noconfirm curl firewalld

sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-port=6443/tcp
sudo firewall-cmd --permanent --add-port=2379-2380/tcp
sudo firewall-cmd --permanent --add-port=8472/udp
sudo firewall-cmd --permanent --add-port=10250/tcp
sudo firewall-cmd --reload

Join เป็น server ตัวที่สาม:

curl -sfL https://get.k3s.io | sh -s - server \
  --server https://192.168.1.11:6443 \
  --token <TOKEN> \
  --node-name k3s-node3 \
  --write-kubeconfig-mode 644

อีกทางเลือก: AUR package ถ้าอยากจัดการ k3s ผ่าน pacman แทน:

yay -S k3s-bin
sudo systemctl enable --now k3s

(ยังต้องตั้งค่า K3S_URL/K3S_TOKEN หรือ flag เทียบเท่าใน /etc/systemd/system/k3s.service.env ก่อน start service)

8. ตรวจสอบ Cluster

รันจากโหนด server ตัวไหนก็ได้ในสามตัว:

sudo k3s kubectl get nodes -o wide

ผลลัพธ์ที่ควรได้ — ทั้งสามโหนดสถานะ Ready แต่ละตัวแสดง OS image ของตัวเอง ในคอลัมน์ OS-IMAGE (พิสูจน์ว่านี่คือ mixed-distro cluster จริง ๆ):

NAME        STATUS   ROLES                       AGE   VERSION        OS-IMAGE
k3s-node1   Ready    control-plane,etcd,master   10m   v1.34.6+k3s1   openSUSE Tumbleweed
k3s-node2   Ready    control-plane,etcd,master   7m    v1.34.6+k3s1   Ubuntu 24.04.2 LTS
k3s-node3   Ready    control-plane,etcd,master   5m    v1.34.6+k3s1   Arch Linux

ตรวจสอบว่า system pod ขึ้นมาเรียบร้อยดี:

sudo k3s kubectl get pods -A

9. เข้าถึง Cluster ด้วย kubectl จาก Workstation

โดย default k3s จะเขียน kubeconfig ไว้ที่ /etc/rancher/k3s/k3s.yaml ชี้ไปที่ 127.0.0.1 ถ้าจะใช้จาก laptop/workstation ให้ copy มาแล้วแก้ server address:

scp k3s-node1:/etc/rancher/k3s/k3s.yaml ~/.kube/k3s-lab.yaml
sed -i 's/127.0.0.1/192.168.1.11/' ~/.kube/k3s-lab.yaml
export KUBECONFIG=~/.kube/k3s-lab.yaml
kubectl get nodes

เพื่อความทนทานเผื่อโหนด 1 ล่ม สามารถชี้ kubectl ไปที่ IP ของ server ตัวไหนก็ได้ในสามตัวบน port 6443 หรือจะวาง VIP/round-robin DNS ครอบทั้งสาม IP ไว้ก็ได้

10. ตัวอย่างการ Deploy

10.1 Deployment แบบ Stateless พื้นฐาน + ClusterIP Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-web
  template:
    metadata:
      labels:
        app: hello-web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: hello-web
spec:
  selector:
    app: hello-web
  ports:
    - port: 80
      targetPort: 80
kubectl apply -f hello-web.yaml
kubectl get pods -o wide   # สังเกตว่า replica กระจายไปทั้ง 3 โหนด

10.2 เปิด Service ด้วย NodePort

มีประโยชน์เมื่ออยากเข้าถึง service ตรง ๆ ผ่าน IP ของโหนดโดยไม่ต้องตั้ง Ingress:

apiVersion: v1
kind: Service
metadata:
  name: hello-web-nodeport
spec:
  type: NodePort
  selector:
    app: hello-web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

ตอนนี้ http://192.168.1.11:30080, http://192.168.1.12:30080 หรือ http://192.168.1.13:30080 ทุกที่จะเข้าถึง service เดียวกันได้หมด ไม่ว่า pod จะไปอยู่โหนดไหนจริง ๆ ก็ตาม

10.3 Ingress ด้วย Traefik ที่ bundle มาให้

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-web-ingress
spec:
  rules:
    - host: hello.homelab.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-web
                port:
                  number: 80

ชี้ hello.homelab.local ไปที่ IP โหนดใดก็ได้ (ผ่าน /etc/hosts บนเครื่อง client หรือใช้ DNS จริง) แล้ว:

curl -H "Host: hello.homelab.local" http://192.168.1.11

10.4 StatefulSet พร้อม Persistent Storage

ตัวอย่างการใช้ local-path StorageClass ที่ bundle มาให้ ผ่าน PVC template — ในที่นี้ใช้กับ instance PostgreSQL ขนาดเล็ก โดยดึง credential มาจาก Secret (ดู 10.5):

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          envFrom:
            - secretRef:
                name: postgres-credentials
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: local-path
        resources:
          requests:
            storage: 2Gi

10.5 การใช้งาน ConfigMap และ Secret

kubectl create secret generic postgres-credentials \
  --from-literal=POSTGRES_USER=appuser \
  --from-literal=POSTGRES_PASSWORD='change-me'

kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info

การอ้างอิงทั้งสองใน pod spec:

    spec:
      containers:
        - name: app
          image: your-app:latest
          envFrom:
            - configMapRef:
                name: app-config
            - secretRef:
                name: postgres-credentials

10.6 การ Scale และ Rolling Update

# Scale ออก
kubectl scale deployment hello-web --replicas=6

# Roll ไปเวอร์ชัน image ใหม่
kubectl set image deployment/hello-web nginx=nginx:1.27-alpine
kubectl rollout status deployment/hello-web

# Rollback ถ้ามีอะไรพัง
kubectl rollout undo deployment/hello-web

10.7 โบนัส: รันโหนดด้วย CRI-O แทน containerd

k3s ใช้ containerd เป็น default แต่สามารถชี้ไปที่ CRI-compliant runtime ตัวไหนก็ได้ — รวมถึง CRI-O ที่มีสายตระกูลเดียวกับ Podman ติดตั้งและเปิดใช้ CRI-O บนโหนดก่อน แล้วติดตั้ง k3s ด้วย:

curl -sfL https://get.k3s.io | sh -s - server \
  --container-runtime-endpoint unix:///var/run/crio/crio.sock \
  --cluster-init

ใช้ flag เดียวกันตอน join โหนด server/agent เพิ่มเติม คุ้มค่าที่จะทำถ้าอยากให้ทุก layer ของ stack — ตั้งแต่ build image, รัน container เดี่ยว ๆ ไปจนถึง cluster node — อยู่ในสาย Podman/CRI-O tooling เดียวกันหมด แทนที่จะผสม containerd เข้ามา

11. การดูแลรักษา Cluster

11.1 การอัปเกรด k3s

รัน install script ใหม่โดยระบุเวอร์ชันที่ต้องการ ทำทีละโหนด เริ่มจาก server ก่อน:

curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.34.7+k3s1 sh -s - server --cluster-init

สำหรับ fleet ระดับ production system-upgrade-controller ช่วย automate การ rollout แบบทยอยทำทั่วทุกโหนดได้ แทนที่จะต้อง SSH เข้าไปทำเองทีละเครื่อง

11.2 การสำรองและกู้คืน etcd Snapshot

k3s สำรอง etcd snapshot อัตโนมัติบน server node อยู่แล้ว แต่สามารถสั่งเองได้ด้วย:

sudo k3s etcd-snapshot save --name pre-upgrade-backup
sudo ls /var/lib/rancher/k3s/server/db/snapshots/

การกู้คืน (รันบนโหนดเดียว ต้องหยุด cluster ก่อน):

sudo systemctl stop k3s
sudo k3s server --cluster-reset \
  --cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/pre-upgrade-backup
sudo systemctl start k3s

11.3 การถอดโหนดออกจาก Cluster

# รันจาก server node ที่ยังปกติดี
kubectl drain k3s-node3 --ignore-daemonsets --delete-emptydir-data
kubectl delete node k3s-node3

จากนั้นบนโหนดที่กำลังจะถอดออก:

sudo /usr/local/bin/k3s-uninstall.sh      # ถ้าเป็น server
sudo /usr/local/bin/k3s-agent-uninstall.sh # ถ้าเป็น agent

12. การแก้ปัญหา

13. สรุป

3-server, embedded-etcd k3s cluster ให้ resilience แบบเดียวกับ Swarm — ทนต่อการเสียโหนดได้ 1 ตัวโดยไม่ downtime — พร้อมกับได้ Kubernetes API และ ecosystem เต็มรูปแบบข้างใต้ เพราะ k3s สนใจแค่เวอร์ชันตรงกัน ไม่สนใจว่า distro จะตรงกันหรือไม่ จึงเป็นวิธีที่สะดวกจริง ๆ ในการรวม home lab หรือ edge fleet ที่มี openSUSE, Ubuntu, และ Arch สะสมมาตามธรรมชาติ ให้ทำงานร่วมกันได้โดยไม่ต้องบังคับ standardize OS ก่อน