- Posted on
- • Artificial Intelligence
Artificial Intelligence K3s Projects
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence K3s Projects: Practical AI on Lightweight Kubernetes
Want to run real AI workloads on a Raspberry Pi 4, an Intel NUC, or a spare VM without turning your homelab into a power plant? K3s, the lightweight Kubernetes from Rancher, makes it easy to deploy AI services that are reproducible, portable, and surprisingly fast—even on edge hardware.
The problem: AI stacks are often heavy, complex, and demand pricey GPUs. The value: with K3s, you get a tiny, production-grade Kubernetes that can schedule containerized AI apps (LLMs, image classifiers, speech recognizers) anywhere—from a single node to a fleet.
Below is a practical, bash-friendly guide with 3–5 actionable projects to get you from zero to AI on K3s, including concrete manifests, curl tests, and minimal moving parts.
Why K3s for AI at the edge
Tiny footprint: single binary, bundled containerd, lower memory overhead than full Kubernetes.
Multi-arch: great for x86_64 and ARM (Raspberry Pi, Jetson).
Reproducible deployments: YAML/Helm installs beat “works on my laptop.”
Portable scaling: start with one node, add more later, same manifests.
Network/load balancing: with MetalLB, you get LoadBalancer IPs on bare metal/LAN.
Prerequisites
Linux host(s) with root/sudo.
Recommended: 2+ CPU cores, 4+ GB RAM for basic inference; more for LLMs.
Outbound internet to pull images/charts.
Install baseline CLI tools (curl, git, tar, gzip, iptables, conntrack):
- apt (Debian/Ubuntu):
sudo apt update
sudo apt install -y curl git tar gzip iptables conntrack ca-certificates
- dnf (Fedora/RHEL/CentOS Stream):
sudo dnf -y install curl git tar gzip iptables conntrack-tools ca-certificates
- zypper (openSUSE/SLE):
sudo zypper refresh
sudo zypper install -y curl git tar gzip iptables conntrack ca-certificates
1) Foundation: Install K3s + Helm + MetalLB
Install K3s (single-node):
curl -sfL https://get.k3s.io | sh -
sudo systemctl status k3s --no-pager
Get kubectl access (K3s ships with kubectl and a kubeconfig):
mkdir -p "$HOME/.kube"
sudo cp /etc/rancher/k3s/k3s.yaml "$HOME/.kube/config"
sudo chown "$(id -u)":"$(id -g)" "$HOME/.kube/config"
kubectl get nodes
Install Helm:
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
Install MetalLB (to get LoadBalancer IPs on bare-metal/LAN):
helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm install metallb metallb/metallb -n metallb-system --create-namespace
Pick a free IP range on your LAN (outside DHCP). Then apply:
cat <<'EOF' | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.240-192.168.1.250 # change to your LAN range
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-advertisement
namespace: metallb-system
EOF
2) Project: Triton Inference Server (CPU) + ONNX model
Goal: Serve a real image classification model via HTTP on your LAN using NVIDIA Triton (CPU mode is fine).
Create a namespace and deploy Triton with an initContainer that downloads an ONNX model into an in-cluster model repository:
kubectl create namespace ai
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: triton
namespace: ai
labels:
app: triton
spec:
replicas: 1
selector:
matchLabels:
app: triton
template:
metadata:
labels:
app: triton
spec:
volumes:
- name: model-repo
emptyDir: {}
initContainers:
- name: fetch-model
image: alpine:3.19
command: ["/bin/sh","-lc"]
args:
- |
set -eux
mkdir -p /models/squeezenet/1
wget -O /models/squeezenet/1/model.onnx \
https://github.com/onnx/models/raw/main/vision/classification/squeezenet/model/squeezenet1.0-12.onnx
volumeMounts:
- name: model-repo
mountPath: /models
containers:
- name: triton
image: ghcr.io/triton-inference-server/server:23.10-py3
args: ["tritonserver","--model-repository=/models","--http-port=8000","--grpc-port=8001","--metrics-port=8002"]
ports:
- name: http
containerPort: 8000
- name: grpc
containerPort: 8001
- name: metrics
containerPort: 8002
readinessProbe:
httpGet:
path: /v2/health/ready
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: model-repo
mountPath: /models
---
apiVersion: v1
kind: Service
metadata:
name: triton
namespace: ai
labels:
app: triton
spec:
type: LoadBalancer
selector:
app: triton
ports:
- name: http
port: 8000
targetPort: 8000
- name: grpc
port: 8001
targetPort: 8001
- name: metrics
port: 8002
targetPort: 8002
EOF
Find the service IP and test:
kubectl -n ai get svc triton
TRITON_IP=$(kubectl -n ai get svc triton -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -fsS "http://$TRITON_IP:8000/v2/health/ready" && echo "Triton ready"
curl -fsS "http://$TRITON_IP:8000/v2/models/squeezenet" | jq .
Tip: Real inference with Triton uses JSON + binary tensors over HTTP/gRPC. Start here with health checks, then integrate a client when ready.
3) Project: Run a small local LLM with llama.cpp server
Goal: Expose a tiny LLM API on your LAN using CPU-only inference (works on modest boxes). You supply a GGUF model file.
On the host, place a GGUF model at /opt/llama-models/model.gguf (for example, a small TinyLlama or Phi-2-quantized GGUF you’ve downloaded):
sudo mkdir -p /opt/llama-models
# Copy your GGUF file to /opt/llama-models/model.gguf
ls -lh /opt/llama-models/
Deploy a Pod and LoadBalancer Service:
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: llama-models-pv
spec:
capacity:
storage: 8Gi
accessModes: ["ReadOnlyMany"]
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /opt/llama-models
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: llama-models-pvc
namespace: ai
spec:
accessModes: ["ReadOnlyMany"]
resources:
requests:
storage: 8Gi
volumeName: llama-models-pv
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: llama
namespace: ai
labels:
app: llama
spec:
replicas: 1
selector:
matchLabels:
app: llama
template:
metadata:
labels:
app: llama
spec:
containers:
- name: server
image: ghcr.io/ggerganov/llama.cpp:server
command: ["/bin/sh","-lc"]
args:
- |
set -eux
llama-server -m /models/model.gguf --host 0.0.0.0 --port 8080 --ctx-size 2048 --parallel 2
ports:
- name: http
containerPort: 8080
volumeMounts:
- name: models
mountPath: /models
volumes:
- name: models
persistentVolumeClaim:
claimName: llama-models-pvc
---
apiVersion: v1
kind: Service
metadata:
name: llama
namespace: ai
labels:
app: llama
spec:
type: LoadBalancer
selector:
app: llama
ports:
- name: http
port: 8080
targetPort: 8080
EOF
Test a basic completion:
LLAMA_IP=$(kubectl -n ai get svc llama -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -s "http://$LLAMA_IP:8080/completion" \
-H "Content-Type: application/json" \
-d '{"prompt":"Write a haiku about K3s.","n_predict":64,"temperature":0.7}'
Note: For faster inference, use more quantized models or GPU acceleration (see optional notes below).
4) Project: Observe your AI with Prometheus + Grafana
Goal: Instrumentation you can trust. Triton exposes Prometheus metrics (port 8002). We’ll install a monitoring stack and scrape those metrics.
Install the kube-prometheus-stack chart:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
kubectl -n monitoring get pods
Tell Prometheus to scrape Triton via a ServiceMonitor:
cat <<'EOF' | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: triton-sm
namespace: monitoring
spec:
namespaceSelector:
matchNames: ["ai"]
selector:
matchLabels:
app: triton
endpoints:
- port: metrics
path: /metrics
interval: 15s
EOF
Access Grafana:
- Get the admin password:
kubectl -n monitoring get secret monitoring-grafana -o jsonpath='{.data.admin-password}' | base64 -d; echo
- Port-forward Grafana to your workstation (or expose with a LoadBalancer if you prefer):
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80
# Then browse http://localhost:3000 (user: admin, password: shown above)
Now watch request rates, latencies, and resource usage while you hit Triton or the LLM server.
Optional: Notes on GPUs and multi-node
GPUs: If you have NVIDIA hardware, install the NVIDIA driver and NVIDIA Container Toolkit on the host, then the Kubernetes NVIDIA device plugin in the cluster. K3s works well with it. Exact package names differ by distro/version—follow your GPU vendor’s docs. Afterward, add tolerations/limits like
nvidia.com/gpu: 1to your Pods.Multi-node: Join more agents to your K3s server with a single token and reuse the same manifests. Add nodeSelectors or affinities to steer LLMs to beefier nodes.
Troubleshooting quick hits
Services not getting IPs: ensure your MetalLB IP pool matches your LAN and doesn’t conflict with DHCP.
Timeouts pulling images: check DNS/firewall; try
crictl infoandcrictl pullon the node.LLM too slow: choose a smaller GGUF (q4_k_m or q5_k_m), reduce context size, or add GPU.
Conclusion and Call to Action
You don’t need a datacenter to build and ship AI. With K3s plus a few well-chosen containers, you can:
Serve computer vision models via Triton.
Host a local LLM API with llama.cpp.
Observe everything with Prometheus and Grafana.
Pick one project above, paste in the manifests, and watch it go. From there, wire up a simple CLI, a cron job, or a web UI—then iterate. When you’re ready, push the same YAML to a bigger box or a small cluster and scale out with confidence.
If you found this useful, try turning one of these into a reusable Helm chart for your lab—or share your results and tweaks with the community. Happy hacking!