PVE 上部署单节点 Talos Kubernetes 和 Portainer

记录一次在 PVE homelab 宿主机上部署 Talos Linux 单节点 Kubernetes control-plane,并继续安装 kubectlk9smetrics-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
2
3
4
API managed
immutable file system
minimal packages
secure by default

这也是我这次选择它的原因:我想在 PVE 里跑一个尽量干净、可重复、少手工漂移的 Kubernetes 环境。

它不是传统 Linux

Talos 不是“先装 Linux,再 apt install kubelet/containerd/kubeadm”。

它自己就内建了 Kubernetes 节点需要的组件,比如:

1
2
3
4
5
Linux kernel
containerd
kubelet
etcd / control plane 相关组件
Talos 自己的 machined / apid / trustd

所以它不是一台可以随便 SSH 上去改配置的 Linux 服务器。下面这些传统操作在 Talos 里基本不存在:

1
2
3
4
5
ssh root@node
apt install nginx
systemctl status kubelet
journalctl -u kubelet
vim /etc/xxx

Talos 的管理方式是从外部通过 API 完成:

1
2
3
4
5
talosctl services
talosctl logs kubelet
talosctl get addresses
talosctl get disks
talosctl dashboard

也就是说,排障习惯要从“登录机器查”变成“站在外部用 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
2
3
4
5
6
7
8
9
10
创建 VM
挂 Talos ISO
启动进入 maintenance mode
在外部机器上安装 talosctl
生成 controlplane.yaml / worker.yaml / talosconfig
apply-config 到节点
安装到虚拟磁盘
bootstrap Kubernetes
获取 kubeconfig
用 kubectl / k9s / Portainer 管理 Kubernetes

这点和安装 Ubuntu Server 很不一样。Talos ISO 只是启动和安装入口,真正的系统配置由 talosctl 从外部灌进去。

它适合什么场景

我觉得 Talos 比较适合:

1
2
3
4
5
6
PVE 上跑长期使用的 homelab Kubernetes
裸机 Kubernetes 集群
边缘节点
生产 Kubernetes 节点
想减少 OS 层漂移
想用声明式方式管理节点

不太适合:

1
2
3
4
5
想 SSH 进去随便 apt install 的人
想把节点当普通 Linux 服务器用
经常需要手工改系统配置
想顺便跑一堆非 K8s 服务
刚学 Linux/K8s,需要大量本地排障的人

如果只是第一次学习 Kubernetes 原理,Debian/Ubuntu + k3s 可能更直观。
如果是想搭一个长期稳定、干净、低维护的 homelab Kubernetes,Talos 很适合。

Talos 和 Omni 的关系

Talos 是底层节点操作系统,talosctl 是命令行管理工具。Omni 是 Sidero Labs 提供的多集群生命周期管理平台。

可以理解成:

1
2
3
Talos Linux = 节点操作系统
talosctl = 命令行管理工具
Omni = Web UI / 多集群生命周期管理平台

自己在 PVE homelab 里玩,不一定需要 Omni。本文只使用 talosctl

环境

PVE 宿主机:

1
2
3
hostname: pve
IP: 192.168.5.2/24
bridge: vmbr0

Talos 节点:

1
2
3
4
VMID: 101
name: talos-cp-1
IP: 192.168.5.141
role: control-plane + worker

已有的 OpenWrt 路由器 VM:

1
2
3
VMID: 100
name: openwrt
注意:不要动它

找到的 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
2
pvesm status
qm list

返回:

1
2
3
Name             Type     Status     Total (KiB)      Used (KiB) Available (KiB)        %
local dir active 71017632 14943212 52421200 21.04%
local-lvm lvmthin active 148086784 547921 147538862 0.37%
1
2
VMID NAME                 STATUS     MEM(MB)    BOOTDISK(GB) PID
100 openwrt running 1024 2.38 15740

把 ISO 放到 PVE ISO 目录:

1
cp /root/metal-amd64.iso /var/lib/vz/template/iso/metal-amd64.iso

创建 VMID 101:

1
2
3
4
5
6
7
8
9
10
11
qm create 101 \
--name talos-cp-1 \
--memory 8192 \
--cores 4 \
--cpu host \
--machine q35 \
--net0 virtio,bridge=vmbr0 \
--ostype l26 \
--scsihw virtio-scsi-pci \
--agent 0 \
--balloon 0

添加 UEFI、磁盘和 ISO:

1
2
3
4
qm set 101 --bios ovmf --efidisk0 local-lvm:0,efitype=4m,pre-enrolled-keys=0
qm set 101 --scsi0 local-lvm:100,format=raw,discard=on,ssd=1
qm set 101 --ide2 local:iso/metal-amd64.iso,media=cdrom
qm set 101 --boot 'order=ide2;scsi0'

启动:

1
qm start 101

Talos maintenance IP 后来确认是:

1
192.168.5.141

安装 talosctl

宿主机一开始没有 talosctl

1
command -v talosctl

无返回。

安装:

1
2
curl -fsSL https://talos.dev/install -o /tmp/talos-install.sh
sh /tmp/talos-install.sh

返回:

1
2
3
4
5
6
talosctl was successfully installed

Installed version:
Client:
Tag: v1.13.2
OS/Arch: linux/amd64

检查 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
2
3
4
NODE   NAMESPACE   TYPE   ID      VERSION   SIZE     READ ONLY   TRANSPORT   ROTATIONAL   WWID   MODEL           SERIAL
runtime Disk loop0 2 83 MB true
runtime Disk sda 2 107 GB false virtio QEMU HARDDISK
runtime Disk sr0 2 334 MB false sata true QEMU DVD-ROM

安装盘就是:

1
/dev/sda

生成 Talos 配置

创建配置目录:

1
mkdir -p /root/talos-cp-1

生成配置。这里是单节点 control-plane,并允许 control-plane 调度业务 Pod:

1
2
3
4
5
talosctl gen config talos-home https://192.168.5.141:6443 \
--install-disk /dev/sda \
--output-dir /root/talos-cp-1 \
--config-patch-control-plane $'cluster:\n allowSchedulingOnControlPlanes: true' \
--force

返回:

1
2
3
Created /root/talos-cp-1/controlplane.yaml
Created /root/talos-cp-1/worker.yaml
Created /root/talos-cp-1/talosconfig

确认配置里有安装盘和允许调度:

1
rg -n "install:|disk:|allowScheduling" /root/talos-cp-1/controlplane.yaml

返回:

1
2
3
76:    install:
77: disk: /dev/sda # The disk used for installations.
348: allowSchedulingOnControlPlanes: true # Allows running workload on control-plane nodes.

apply-config 和真正安装

执行:

1
2
3
4
talosctl apply-config --insecure \
--nodes 192.168.5.141 \
--file /root/talos-cp-1/controlplane.yaml \
--mode reboot

当时返回:

1
Applied configuration without a reboot

这个返回容易误导。后面通过 PVE 观察到 scsi0 实际发生了大量写入:

1
2
3
scsi0:
wr_bytes: 568524800
wr_highest_offset: 107374182400

再用已生成的 talosconfig 检查:

1
talosctl --talosconfig /root/talos-cp-1/talosconfig version --nodes 192.168.5.141

返回:

1
2
3
4
5
6
Client:
Tag: v1.13.2
Server:
NODE: 192.168.5.141
Tag: v1.13.2
Enabled: RBAC

这说明已经不是 maintenance mode,而是已安装后的 Talos。

移除 ISO 并硬盘启动

安装完成后,移除 ISO:

1
2
3
qm set 101 --boot 'order=scsi0' --delete ide2
qm stop 101
qm start 101

确认没有 pending:

1
qm pending 101

返回里只有当前配置:

1
2
cur boot: order=scsi0
cur scsi0: local-lvm:vm-101-disk-1,discard=on,size=100G,ssd=1

QEMU 运行参数中也没有 drive-ide2,说明 ISO 已经不在运行态。

bootstrap Kubernetes

设置 talosctl 默认 endpoint 和 node:

1
2
talosctl --talosconfig /root/talos-cp-1/talosconfig config endpoint 192.168.5.141
talosctl --talosconfig /root/talos-cp-1/talosconfig config node 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
2
3
talosctl --talosconfig /root/talos-cp-1/talosconfig health \
--nodes 192.168.5.141 \
--endpoints 192.168.5.141

关键返回:

1
2
3
4
5
6
7
waiting for etcd to be healthy: OK
waiting for kubelet to be healthy: OK
waiting for all control plane static pods to be running: OK
waiting for all k8s nodes to report ready: OK
waiting for kube-proxy to report ready: OK
waiting for coredns to report ready: OK
waiting for all k8s nodes to report schedulable: OK

安装 kubectl

Talos 生成的是 Kubernetes v1.36.0,Debian 仓库里的 kubectl1.32.3,跨度偏大,所以从 Kubernetes 官方下载 v1.36.1

1
curl -fsSL https://dl.k8s.io/release/stable.txt

返回:

1
v1.36.1

安装:

1
2
3
4
5
6
curl -fL --proto '=https' --tlsv1.2 \
https://dl.k8s.io/release/v1.36.1/bin/linux/amd64/kubectl \
-o /usr/local/bin/kubectl

chmod +x /usr/local/bin/kubectl
kubectl version --client=true

返回:

1
2
Client Version: v1.36.1
Kustomize Version: v5.8.1

验证集群:

1
kubectl get nodes -o wide

返回:

1
2
NAME            STATUS   ROLES           AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE          KERNEL-VERSION          CONTAINER-RUNTIME
talos-0pm-w6u Ready control-plane 39m v1.36.0 192.168.5.141 <none> Talos (v1.13.2) 6.18.29-talos (amd64) containerd://2.2.3

安装 k9s

查询最新 release:

1
gh release view --repo derailed/k9s --json tagName --jq .tagName

返回:

1
v0.50.18

安装:

1
2
3
4
5
6
curl -fL https://github.com/derailed/k9s/releases/download/v0.50.18/k9s_Linux_amd64.tar.gz \
-o /tmp/k9s_Linux_amd64.tar.gz

tar -xzf /tmp/k9s_Linux_amd64.tar.gz -C /tmp k9s
install -m 0755 /tmp/k9s /usr/local/bin/k9s
k9s version

返回:

1
2
3
Version:    v0.50.18
Commit: 6dbf571c59fd48dc5b384aa46ee7f3e5decfae2b
Date: 2026-01-11T20:09:14Z

安装 metrics-server

部署:

1
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

返回:

1
2
3
4
5
6
7
8
9
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

一开始 k9s 顶部显示:

1
2
CPU: n/a
MEM: n/a

查看日志:

1
kubectl -n kube-system logs deploy/metrics-server --tail=80

关键报错:

1
2
Failed to scrape node
tls: failed to verify certificate: x509: cannot validate certificate for 192.168.5.141 because it doesn't contain any IP SANs

处理方式:给 metrics-server 加 --kubelet-insecure-tls

1
2
3
kubectl -n kube-system patch deployment metrics-server \
--type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

返回:

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
2
NAME            CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
talos-0pm-w6u 453m 11% 1009Mi 13%

这时 k9s 的 CPU/MEM 就不会再是 n/a

安装 Avahi mDNS

为了让 PVE 宿主机有本地域名广播,安装 Avahi:

1
2
3
apt-get update
apt-get install -y avahi-daemon avahi-utils
systemctl enable --now avahi-daemon.service

状态:

1
systemctl status avahi-daemon.service --no-pager

返回:

1
2
Active: active (running)
avahi-daemon: running [pve.local]

需求是广播:

1
pve.j4125.local -> 192.168.5.2

创建服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Publish PVE mDNS alias
After=avahi-daemon.service
Requires=avahi-daemon.service

[Service]
Type=simple
ExecStart=/usr/bin/avahi-publish-address -R pve.j4125.local 192.168.5.2
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

安装并启动:

1
2
3
cp /root/avahi-publish-pve-j4125.service /etc/systemd/system/avahi-publish-pve-j4125.service
systemctl daemon-reload
systemctl enable --now 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
2
3
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o /tmp/get_helm.sh
chmod 700 /tmp/get_helm.sh
/tmp/get_helm.sh

返回:

1
helm installed into /usr/local/bin/helm

安装 local-path:

1
2
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

返回:

1
storageclass.storage.k8s.io/local-path patched

添加 Portainer Helm 仓库:

1
2
3
helm repo add portainer https://portainer.github.io/k8s/
helm repo update portainer
helm search repo portainer/portainer --versions | head

返回:

1
2
NAME               	CHART VERSION	APP VERSION
portainer/portainer 239.2.0 ce-latest-ee-2.39.2

部署 Portainer:

1
2
3
4
5
6
7
8
helm upgrade --install portainer portainer/portainer \
--namespace portainer \
--create-namespace \
--set service.type=NodePort \
--set service.httpsNodePort=30779 \
--set persistence.enabled=true \
--set persistence.size=10Gi \
--set persistence.storageClass=local-path

返回:

1
2
3
4
NAME: portainer
NAMESPACE: portainer
STATUS: deployed
REVISION: 1

Portainer 的 local-path 踩坑

Portainer PVC 一开始 Pending:

1
kubectl -n portainer describe pvc portainer

关键报错:

1
2
3
failed to provision volume with StorageClass "local-path":
pods "helper-pod-create-..." is forbidden:
violates PodSecurity "baseline:latest": hostPath volumes (volume "data")

原因:local-path provisioner 创建 helper Pod 时需要 hostPath,被 PodSecurity baseline 拦住。

处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl label namespace portainer \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/audit=privileged \
pod-security.kubernetes.io/warn=privileged \
--overwrite

kubectl label namespace local-path-storage \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/audit=privileged \
pod-security.kubernetes.io/warn=privileged \
--overwrite

kubectl -n local-path-storage rollout restart deploy/local-path-provisioner
kubectl -n portainer delete pod -l app.kubernetes.io/name=portainer

之后 PVC 绑定成功:

1
kubectl -n portainer get pods,svc,pvc -o wide

返回:

1
2
3
4
5
6
7
8
NAME                            READY   STATUS    RESTARTS   AGE   IP           NODE
pod/portainer-55bf664fb-ng927 1/1 Running 0 84s 10.244.0.9 talos-0pm-w6u

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/portainer NodePort 10.101.252.85 <none> 9000:30777/TCP,9443:30779/TCP,30776:30776/TCP

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
persistentvolumeclaim/portainer Bound pvc-fc203726-f431-4bbe-882b-5ca900c22871 10Gi RWO local-path

验证 Web:

1
curl -k -I --connect-timeout 5 https://192.168.5.141:30779

返回:

1
2
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

访问地址:

1
https://192.168.5.141:30779

首次进入需要创建 Portainer 管理员账号。

Portainer 首次初始化超时

如果页面提示:

1
2
3
New Portainer installation
Your Portainer instance timed out for security purposes.
To re-enable your Portainer instance, you will need to restart Portainer.

重启 Portainer:

1
2
kubectl -n portainer rollout restart deployment/portainer
kubectl -n portainer rollout status deployment/portainer --timeout=120s

返回:

1
2
deployment.apps/portainer restarted
deployment "portainer" successfully rolled out

然后立刻打开:

1
https://192.168.5.141:30779

串口相关踩坑

给 VMID 101 开串口:

1
qm set 101 --serial0 socket --vga serial0

直接 qm reset 101 不一定会让这类硬件变更生效。实际遇到:

1
2
qm terminal 101
unable to find a serial interface

检查:

1
qm pending 101

能看到:

1
2
new serial0: socket
new vga: serial0

说明配置还在 pending。需要:

1
2
qm stop 101
qm start 101

之后:

1
qm terminal 101

返回:

1
starting serial terminal on interface serial0 (press Ctrl+O to exit)

这才说明串口真正生效。

worker NotReady:NTP 时间同步失败

后面又遇到一次 worker 节点下线。k9s 里看到 worker 上的 kube-flannelkube-proxy 等 Pod 变成 Pending,节点列表里有一个节点不是 Ready。

先看节点:

1
kubectl get nodes -o wide

返回:

1
2
3
NAME            STATUS     ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE          KERNEL-VERSION          CONTAINER-RUNTIME
talos-0pm-w6u Ready control-plane 32d v1.36.0 192.168.5.10 <none> Talos (v1.13.2) 6.18.29-talos (amd64) containerd://2.2.3
talos-6yj-16k NotReady <none> 32d v1.35.0 192.168.5.11 <none> Talos (v1.13.2) 6.18.29-talos (amd64) containerd://2.2.3

一开始容易误判成网络断了。实际检查发现远端 PVE 和 worker IP 都能 ping 通:

1
2
ping -c 3 -W 2 192.168.5.3
ping -c 3 -W 2 192.168.5.11

返回:

1
2
64 bytes from 192.168.5.3: icmp_seq=1 ttl=64 time=1.59 ms
64 bytes from 192.168.5.11: icmp_seq=1 ttl=64 time=1.15 ms

说明不是宿主机掉线,也不是 worker 机器关机。

继续看 Kubernetes 里的节点详情:

1
kubectl describe node talos-6yj-16k | tail -120

关键返回:

1
2
3
4
MemoryPressure   Unknown   NodeStatusUnknown   Kubelet stopped posting node status.
DiskPressure Unknown NodeStatusUnknown Kubelet stopped posting node status.
PIDPressure Unknown NodeStatusUnknown Kubelet stopped posting node status.
Ready Unknown NodeStatusUnknown Kubelet stopped posting node status.

这说明 Kubernetes 不是完全找不到机器,而是 kubelet 不再上报心跳。

再看 Talos 服务状态:

1
2
3
talosctl --talosconfig /root/talos-cp-1/talosconfig services \
--nodes 192.168.5.11 \
--endpoints 192.168.5.10

关键返回:

1
2
3
4
5
NODE           SERVICE      STATE     HEALTH   LAST EVENT
192.168.5.11 apid Running OK Health check successful
192.168.5.11 containerd Running OK Health check successful
192.168.5.11 cri Running OK Health check successful
192.168.5.11 kubelet Waiting ? Waiting for time sync

这里就定位到了:kubelet 没启动,因为 Talos 正在等待时间同步

继续看时间服务器:

1
2
3
talosctl --talosconfig /root/talos-cp-1/talosconfig get timeservers \
--nodes 192.168.5.11 \
--endpoints 192.168.5.10

返回:

1
2
NODE           NAMESPACE   TYPE               ID            VERSION   TIMESERVERS
192.168.5.11 network TimeServerStatus timeservers 1 ["time.cloudflare.com"]

再查 machined 日志:

1
2
3
4
talosctl --talosconfig /root/talos-cp-1/talosconfig logs machined \
--nodes 192.168.5.11 \
--endpoints 192.168.5.10 \
--tail 200 | grep -iE 'time|ntp|sync|clock|kubelet' | tail -120

关键报错:

1
2
time query error with server "198.18.0.4"
read udp 192.168.5.11:54120->198.18.0.4:123: i/o timeout

这里的坑是:Talos 默认用 time.cloudflare.com,但是当前网络里它被解析到了 198.18.0.4,并且 UDP 123 请求超时。

198.18.0.0/15 常见于测试网段或透明代理环境,不是一个稳定的 NTP 目标。结果就是:

1
2
3
4
5
6
time.cloudflare.com -> 198.18.0.4
NTP 123/udp timeout
Talos time sync 卡住
kubelet 不启动
Kubernetes 节点 NotReady
DaemonSet Pod Pending / Terminating

我也试过路由器 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
2
3
4
5
talosctl --talosconfig /root/talos-cp-1/talosconfig patch machineconfig \
--nodes 192.168.5.11 \
--endpoints 192.168.5.10 \
--patch $'machine:\n time:\n servers:\n - ntp.aliyun.com\n - time1.cloud.tencent.com\n - cn.pool.ntp.org' \
--mode no-reboot

返回:

1
2
patched MachineConfigs.config.talos.dev/v1alpha1 at the node 192.168.5.11
Applied configuration without a reboot

等几十秒后重新检查:

1
2
3
talosctl --talosconfig /root/talos-cp-1/talosconfig services \
--nodes 192.168.5.11 \
--endpoints 192.168.5.10

返回:

1
192.168.5.11   kubelet      Running   OK       Health check successful

再看 Kubernetes:

1
kubectl get nodes -o wide

返回:

1
2
3
NAME            STATUS   ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE          KERNEL-VERSION          CONTAINER-RUNTIME
talos-0pm-w6u Ready control-plane 32d v1.36.0 192.168.5.10 <none> Talos (v1.13.2) 6.18.29-talos (amd64) containerd://2.2.3
talos-6yj-16k Ready <none> 32d v1.35.0 192.168.5.11 <none> Talos (v1.13.2) 6.18.29-talos (amd64) containerd://2.2.3

这个问题和前面的 exit code 139 不是同一个问题。

139 是 kubelet/kube-proxy 进程段错误;这次是 NTP 时间同步失败导致 kubelet 根本没启动

总结

最终状态:

1
2
3
4
5
6
7
8
9
10
11
Talos control-plane: 192.168.5.10
Talos worker: 192.168.5.11
Talos: v1.13.2
Kubernetes control-plane: v1.36.0
Kubernetes worker: v1.35.0
kubectl: v1.36.1
k9s: v0.50.18
metrics-server: Running
Portainer CE: 2.39.2
Portainer URL: https://192.168.5.141:30779
StorageClass: local-path

关键坑:

  1. apply-config 返回 Applied configuration without a reboot 不代表没安装,要结合 Talos API 和磁盘写入判断。
  2. PVE 的硬件变更,例如删除 ISO、切换串口,很多时候需要 qm stop && qm startqm reset 不够。
  3. metrics-server 在 Talos 上可能因为 kubelet 证书没有 IP SAN 导致 CPU/MEM n/a,加 --kubelet-insecure-tls 后恢复。
  4. local-path provisioner 会被 PodSecurity baseline 拦 hostPath,需要给相关 namespace 打 privileged 标签。
  5. Portainer 首次初始化有安全超时,超时后重启 deployment 即可重新初始化。
  6. Talos worker 如果卡在 Waiting for time sync,kubelet 不会启动,节点会变成 NotReady;需要检查 NTP 是否可达。

参考


转载无需注明来源,放弃所有权利