记录一次在 PVE homelab 宿主机上部署 Talos Linux 单节点 Kubernetes control-plane,并继续安装 kubectl、k9s、metrics-server 和 Portainer 的过程。
这次目标不是生产高可用,而是先跑通一个可用的单节点 Kubernetes 环境。
背景:Talos 是什么
Talos Linux 本质上是一个 Kubernetes 专用操作系统,不是传统意义上的 Ubuntu、Debian、CentOS,也不是单纯的 Kubernetes 安装脚本。
一句话概括:
1 | Talos = 一个极简、不可变、无 SSH、无包管理器、完全通过 API 管理的 Linux 发行版,专门用来跑 Kubernetes 节点。 |
官方文档里说 Talos 是一个 Kubernetes optimized Linux distro。它可以跑在裸机或 VM 上,用来承载业务工作负载,也可以运行 Kubernetes control plane 组件和 etcd。它的核心特点包括:
1 | API managed |
这也是我这次选择它的原因:我想在 PVE 里跑一个尽量干净、可重复、少手工漂移的 Kubernetes 环境。
它不是传统 Linux
Talos 不是“先装 Linux,再 apt install kubelet/containerd/kubeadm”。
它自己就内建了 Kubernetes 节点需要的组件,比如:
1 | Linux kernel |
所以它不是一台可以随便 SSH 上去改配置的 Linux 服务器。下面这些传统操作在 Talos 里基本不存在:
1 | ssh root@node |
Talos 的管理方式是从外部通过 API 完成:
1 | talosctl services |
也就是说,排障习惯要从“登录机器查”变成“站在外部用 talosctl 查”。
它和普通 Linux + kubeadm/k3s 的区别
| 维度 | Ubuntu/Debian + kubeadm/k3s | Talos Linux |
|---|---|---|
| 系统定位 | 通用 Linux | Kubernetes 专用 OS |
| SSH | 有 | 默认没有 |
| Shell | 有 | 默认没有 |
| 包管理器 | apt/yum | 没有 |
| systemd | 有 | 没有,使用 Talos 自己的服务模型 |
| 配置方式 | 手工命令、Ansible、cloud-init | 声明式 machine config + API |
| 升级方式 | apt upgrade / 手工升级 kubelet | talosctl upgrade |
| 文件系统 | 大部分可写 | rootfs 不可变 |
| 排障习惯 | 登录机器查 | 从外部用 talosctl 查 |
| 适合人群 | 学习/调试/传统运维 | 想跑干净、稳定、K8s 专用节点的人 |
Talos 的 root filesystem 是只读 squashfs,运行时使用 tmpfs 和少量 overlayfs,/var 主要给 Kubernetes、etcd、kubelet、containerd 等使用。这个设计的目的就是减少系统漂移,让节点更像一个“可替换的 Kubernetes appliance”。
在 PVE 里它是什么形态
在 PVE 里,Talos 的使用方式大概是:
1 | 创建 VM |
这点和安装 Ubuntu Server 很不一样。Talos ISO 只是启动和安装入口,真正的系统配置由 talosctl 从外部灌进去。
它适合什么场景
我觉得 Talos 比较适合:
1 | PVE 上跑长期使用的 homelab Kubernetes |
不太适合:
1 | 想 SSH 进去随便 apt install 的人 |
如果只是第一次学习 Kubernetes 原理,Debian/Ubuntu + k3s 可能更直观。
如果是想搭一个长期稳定、干净、低维护的 homelab Kubernetes,Talos 很适合。
Talos 和 Omni 的关系
Talos 是底层节点操作系统,talosctl 是命令行管理工具。Omni 是 Sidero Labs 提供的多集群生命周期管理平台。
可以理解成:
1 | Talos Linux = 节点操作系统 |
自己在 PVE homelab 里玩,不一定需要 Omni。本文只使用 talosctl。
环境
PVE 宿主机:
1 | hostname: pve |
Talos 节点:
1 | VMID: 101 |
已有的 OpenWrt 路由器 VM:
1 | VMID: 100 |
找到的 Talos ISO:
1 | find /root -maxdepth 3 -type f \( -iname '*talos*' -o -iname '*.iso' \) -printf '%p\t%k KB\n' |
返回:
1 | /root/metal-amd64.iso 325964 KB |
创建 Talos VM
先看 PVE 存储和已有 VM:
1 | pvesm status |
返回:
1 | Name Type Status Total (KiB) Used (KiB) Available (KiB) % |
1 | VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID |
把 ISO 放到 PVE ISO 目录:
1 | cp /root/metal-amd64.iso /var/lib/vz/template/iso/metal-amd64.iso |
创建 VMID 101:
1 | qm create 101 \ |
添加 UEFI、磁盘和 ISO:
1 | qm set 101 --bios ovmf --efidisk0 local-lvm:0,efitype=4m,pre-enrolled-keys=0 |
启动:
1 | qm start 101 |
Talos maintenance IP 后来确认是:
1 | 192.168.5.141 |
安装 talosctl
宿主机一开始没有 talosctl:
1 | command -v talosctl |
无返回。
安装:
1 | curl -fsSL https://talos.dev/install -o /tmp/talos-install.sh |
返回:
1 | talosctl was successfully installed |
检查 Talos maintenance API:
1 | timeout 5 bash -lc '</dev/tcp/192.168.5.141/50000' |
返回码为 0,说明 50000 可达。
检查磁盘:
1 | talosctl get disks --insecure --nodes 192.168.5.141 |
返回:
1 | NODE NAMESPACE TYPE ID VERSION SIZE READ ONLY TRANSPORT ROTATIONAL WWID MODEL SERIAL |
安装盘就是:
1 | /dev/sda |
生成 Talos 配置
创建配置目录:
1 | mkdir -p /root/talos-cp-1 |
生成配置。这里是单节点 control-plane,并允许 control-plane 调度业务 Pod:
1 | talosctl gen config talos-home https://192.168.5.141:6443 \ |
返回:
1 | Created /root/talos-cp-1/controlplane.yaml |
确认配置里有安装盘和允许调度:
1 | rg -n "install:|disk:|allowScheduling" /root/talos-cp-1/controlplane.yaml |
返回:
1 | 76: install: |
apply-config 和真正安装
执行:
1 | talosctl apply-config --insecure \ |
当时返回:
1 | Applied configuration without a reboot |
这个返回容易误导。后面通过 PVE 观察到 scsi0 实际发生了大量写入:
1 | scsi0: |
再用已生成的 talosconfig 检查:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig version --nodes 192.168.5.141 |
返回:
1 | Client: |
这说明已经不是 maintenance mode,而是已安装后的 Talos。
移除 ISO 并硬盘启动
安装完成后,移除 ISO:
1 | qm set 101 --boot 'order=scsi0' --delete ide2 |
确认没有 pending:
1 | qm pending 101 |
返回里只有当前配置:
1 | cur boot: order=scsi0 |
QEMU 运行参数中也没有 drive-ide2,说明 ISO 已经不在运行态。
bootstrap Kubernetes
设置 talosctl 默认 endpoint 和 node:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig config endpoint 192.168.5.141 |
bootstrap 只执行一次:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig bootstrap --nodes 192.168.5.141 |
无输出就是成功返回。
拉 kubeconfig:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig kubeconfig --nodes 192.168.5.141 --force |
kubeconfig 位置:
1 | /root/.kube/config |
Talos 配置位置:
1 | /root/talos-cp-1/talosconfig |
健康检查:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig health \ |
关键返回:
1 | waiting for etcd to be healthy: OK |
安装 kubectl
Talos 生成的是 Kubernetes v1.36.0,Debian 仓库里的 kubectl 是 1.32.3,跨度偏大,所以从 Kubernetes 官方下载 v1.36.1:
1 | curl -fsSL https://dl.k8s.io/release/stable.txt |
返回:
1 | v1.36.1 |
安装:
1 | curl -fL --proto '=https' --tlsv1.2 \ |
返回:
1 | Client Version: v1.36.1 |
验证集群:
1 | kubectl get nodes -o wide |
返回:
1 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME |
安装 k9s
查询最新 release:
1 | gh release view --repo derailed/k9s --json tagName --jq .tagName |
返回:
1 | v0.50.18 |
安装:
1 | curl -fL https://github.com/derailed/k9s/releases/download/v0.50.18/k9s_Linux_amd64.tar.gz \ |
返回:
1 | Version: v0.50.18 |
安装 metrics-server
部署:
1 | kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml |
返回:
1 | serviceaccount/metrics-server created |
一开始 k9s 顶部显示:
1 | CPU: n/a |
查看日志:
1 | kubectl -n kube-system logs deploy/metrics-server --tail=80 |
关键报错:
1 | Failed to scrape node |
处理方式:给 metrics-server 加 --kubelet-insecure-tls:
1 | kubectl -n kube-system patch deployment metrics-server \ |
返回:
1 | deployment.apps/metrics-server patched |
等待:
1 | kubectl -n kube-system rollout status deploy/metrics-server --timeout=180s |
返回:
1 | deployment "metrics-server" successfully rolled out |
验证:
1 | kubectl top nodes |
返回:
1 | NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%) |
这时 k9s 的 CPU/MEM 就不会再是 n/a。
安装 Avahi mDNS
为了让 PVE 宿主机有本地域名广播,安装 Avahi:
1 | apt-get update |
状态:
1 | systemctl status avahi-daemon.service --no-pager |
返回:
1 | Active: active (running) |
需求是广播:
1 | pve.j4125.local -> 192.168.5.2 |
创建服务:
1 | [Unit] |
安装并启动:
1 | cp /root/avahi-publish-pve-j4125.service /etc/systemd/system/avahi-publish-pve-j4125.service |
验证:
1 | avahi-resolve-host-name pve.j4125.local |
返回:
1 | pve.j4125.local 192.168.5.2 |
后来不想广播默认的 pve.local,修改:
1 | /etc/avahi/avahi-daemon.conf |
把:
1 | #publish-addresses=yes |
改成:
1 | publish-addresses=no |
然后重启:
1 | systemctl restart avahi-daemon.service avahi-publish-pve-j4125.service |
部署 Portainer 到 Kubernetes
Portainer 可以管理 Kubernetes 资源,但不能管理 Talos OS 本身。
它能管理:
1 | Deployment / Pod / Service / Ingress / Namespace / ConfigMap / Secret / Helm / Stack |
它不能管理:
1 | Talos OS 配置 / 节点升级 / etcd bootstrap / machine config |
集群最开始没有 StorageClass:
1 | kubectl get storageclass |
返回:
1 | No resources found |
先安装 Helm:
1 | curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o /tmp/get_helm.sh |
返回:
1 | helm installed into /usr/local/bin/helm |
安装 local-path:
1 | kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml |
返回:
1 | storageclass.storage.k8s.io/local-path patched |
添加 Portainer Helm 仓库:
1 | helm repo add portainer https://portainer.github.io/k8s/ |
返回:
1 | NAME CHART VERSION APP VERSION |
部署 Portainer:
1 | helm upgrade --install portainer portainer/portainer \ |
返回:
1 | NAME: portainer |
Portainer 的 local-path 踩坑
Portainer PVC 一开始 Pending:
1 | kubectl -n portainer describe pvc portainer |
关键报错:
1 | failed to provision volume with StorageClass "local-path": |
原因:local-path provisioner 创建 helper Pod 时需要 hostPath,被 PodSecurity baseline 拦住。
处理:
1 | kubectl label namespace portainer \ |
之后 PVC 绑定成功:
1 | kubectl -n portainer get pods,svc,pvc -o wide |
返回:
1 | NAME READY STATUS RESTARTS AGE IP NODE |
验证 Web:
1 | curl -k -I --connect-timeout 5 https://192.168.5.141:30779 |
返回:
1 | HTTP/1.1 200 OK |
访问地址:
1 | https://192.168.5.141:30779 |
首次进入需要创建 Portainer 管理员账号。
Portainer 首次初始化超时
如果页面提示:
1 | New Portainer installation |
重启 Portainer:
1 | kubectl -n portainer rollout restart deployment/portainer |
返回:
1 | deployment.apps/portainer restarted |
然后立刻打开:
1 | https://192.168.5.141:30779 |
串口相关踩坑
给 VMID 101 开串口:
1 | qm set 101 --serial0 socket --vga serial0 |
直接 qm reset 101 不一定会让这类硬件变更生效。实际遇到:
1 | qm terminal 101 |
检查:
1 | qm pending 101 |
能看到:
1 | new serial0: socket |
说明配置还在 pending。需要:
1 | qm stop 101 |
之后:
1 | qm terminal 101 |
返回:
1 | starting serial terminal on interface serial0 (press Ctrl+O to exit) |
这才说明串口真正生效。
worker NotReady:NTP 时间同步失败
后面又遇到一次 worker 节点下线。k9s 里看到 worker 上的 kube-flannel、kube-proxy 等 Pod 变成 Pending,节点列表里有一个节点不是 Ready。
先看节点:
1 | kubectl get nodes -o wide |
返回:
1 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME |
一开始容易误判成网络断了。实际检查发现远端 PVE 和 worker IP 都能 ping 通:
1 | ping -c 3 -W 2 192.168.5.3 |
返回:
1 | 64 bytes from 192.168.5.3: icmp_seq=1 ttl=64 time=1.59 ms |
说明不是宿主机掉线,也不是 worker 机器关机。
继续看 Kubernetes 里的节点详情:
1 | kubectl describe node talos-6yj-16k | tail -120 |
关键返回:
1 | MemoryPressure Unknown NodeStatusUnknown Kubelet stopped posting node status. |
这说明 Kubernetes 不是完全找不到机器,而是 kubelet 不再上报心跳。
再看 Talos 服务状态:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig services \ |
关键返回:
1 | NODE SERVICE STATE HEALTH LAST EVENT |
这里就定位到了:kubelet 没启动,因为 Talos 正在等待时间同步。
继续看时间服务器:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig get timeservers \ |
返回:
1 | NODE NAMESPACE TYPE ID VERSION TIMESERVERS |
再查 machined 日志:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig logs machined \ |
关键报错:
1 | time query error with server "198.18.0.4" |
这里的坑是:Talos 默认用 time.cloudflare.com,但是当前网络里它被解析到了 198.18.0.4,并且 UDP 123 请求超时。
198.18.0.0/15 常见于测试网段或透明代理环境,不是一个稳定的 NTP 目标。结果就是:
1 | time.cloudflare.com -> 198.18.0.4 |
我也试过路由器 192.168.5.1 的 NTP:
1 | nc -uzv -w 2 192.168.5.1 123 |
返回:
1 | iStoreOS.lan [192.168.5.1] 123 (ntp) : Connection refused |
说明路由器本身也没有提供 NTP 服务。
最后修复方式是给 worker 显式配置可用的 NTP 源:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig patch machineconfig \ |
返回:
1 | patched MachineConfigs.config.talos.dev/v1alpha1 at the node 192.168.5.11 |
等几十秒后重新检查:
1 | talosctl --talosconfig /root/talos-cp-1/talosconfig services \ |
返回:
1 | 192.168.5.11 kubelet Running OK Health check successful |
再看 Kubernetes:
1 | kubectl get nodes -o wide |
返回:
1 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME |
这个问题和前面的 exit code 139 不是同一个问题。
139 是 kubelet/kube-proxy 进程段错误;这次是 NTP 时间同步失败导致 kubelet 根本没启动。
总结
最终状态:
1 | Talos control-plane: 192.168.5.10 |
关键坑:
apply-config返回Applied configuration without a reboot不代表没安装,要结合 Talos API 和磁盘写入判断。- PVE 的硬件变更,例如删除 ISO、切换串口,很多时候需要
qm stop && qm start,qm reset不够。 - metrics-server 在 Talos 上可能因为 kubelet 证书没有 IP SAN 导致
CPU/MEM n/a,加--kubelet-insecure-tls后恢复。 - local-path provisioner 会被 PodSecurity baseline 拦 hostPath,需要给相关 namespace 打 privileged 标签。
- Portainer 首次初始化有安全超时,超时后重启 deployment 即可重新初始化。
- Talos worker 如果卡在
Waiting for time sync,kubelet 不会启动,节点会变成 NotReady;需要检查 NTP 是否可达。
参考
- What is Talos Linux?
- Talos System Requirements
- Talos Getting Started
- Talos Production Clusters
- Talos for Linux Admins
- Talos Components
- Talos Architecture
- Talos Image Factory
- What is Omni?
转载无需注明来源,放弃所有权利