单节点 VPS 上运行 k3s,什么时候值得?
了解 k3s 单节点实际占用约 1 GB 内存、安装时为何会因 Traefik 抢占 80 端口失败,以及哪些情况下 Docker Compose 更合适。
k3s 是什么,以及单个节点能提供什么
k3s 是以单个二进制文件打包的完整 Kubernetes 发行版。在单台 VPS 上运行 k3s,可以获得完整的 Kubernetes API,而不需要由 3 台机器组成的控制平面。它是经过认证的 Kubernetes 发行版,因此在这里可应用的清单,之后也可应用到托管集群中。安装只需执行一条命令,大约需要 1 分钟。代价是可供应用使用的内存会减少,同时还会引入 Docker Compose 不会遇到的一系列故障模式。
SUSE 面向边缘站点和小型部署构建 k3s。它与上游 Kubernetes 的每项差异,都是为了减小体积。默认数据存储由名为 kine 的 shim 提供,底层使用 sqlite,而不是 etcd,因此无需维护 etcd 仲裁组。containerd 已嵌入二进制文件,无需单独安装。同一个二进制文件还默认提供用于集群 DNS 的 CoreDNS、作为入口控制器的 Traefik、ServiceLB(也称为 klipper-lb),使 LoadBalancer 服务在没有云提供商的情况下也能工作、用于持久化卷的 local-path provisioner、metrics-server,以及用于 Pod 网络的 flannel。上述组件全部默认启动。因此,对于一台此前已经运行其他服务的 VPS,下面介绍的端口冲突是最常见的初始问题。
单个 k3s 节点何时值得使用
遵循以下规则。如果您需要的是 Kubernetes API,就运行 k3s:例如,您正在受控机器上学习 Kubernetes,或者所需软件只发布了 Helm chart。清单的可移植性也很重要,因为您在这里编写的 Deployment 可以原样迁移到托管集群。如果您需要的是应用本身,就使用 Docker Compose。Compose 用更少的组件启动相同的容器,而且一年后,VPS 上的 Compose 文件比一整目录清单更容易阅读。
请明确单个节点无法提供什么。
- 不提供高可用性。VPS 重启时,所有工作负载都会停止。Kubernetes 可以将 Pod 重新调度到其他节点,但这里没有其他节点。
- 不提供可在服务持续运行的情况下执行滚动更新,除非应用允许在同一台机器上运行两个副本并共享一个卷。
- 存储固定在该主机上,原因见下方的 local-path 部分。
- 无论是否部署任何内容,控制平面都会占用约 1 GB RAM。
这些限制并不意味着 k3s 是错误的选择。但如果您通常以可靠性为理由选择它,那么这个理由并不成立。如果您实际需要的是多台机器,以便构建真正的多节点集群,那么应先做出这个决定:在自有硬件上运行 Proxmox,还是租用 VPS。在确定节点来源后,再由 k3s 决定节点上运行什么。
部署前,k3s 会占用多少 RAM 和 CPU
k3s 项目发布的是实测数据,而不是估算值。请仔细阅读,因为人们引用的数字并不是空闲状态下的 k3s。
The data behind this chart
[
{
"label": "Server, sqlite datastore",
"ram_mb": "1,596",
"cpu_percent_of_one_core": 6
},
{
"label": "Server, embedded etcd",
"ram_mb": "1,606",
"cpu_percent_of_one_core": 6
},
{
"label": "Agent node only",
"ram_mb": "275",
"cpu_percent_of_one_core": 3
}
]在该测试中,server 节点的 RAM 使用量在第 95 百分位为 1,596 MB,CPU 使用量约为一个核心的 6%。这些是项目发布的数据,不是本指南中的测量结果。测试运行的是 k3s v1.26.5,启用了所有随附组件,以及 Prometheus 和 Grafana 监控栈。因此,该数字包含了实际工作负载,而不是空集群。将 sqlite 更换为内置 etcd 后,RAM 使用量升至 1,606 MB。agent 节点只运行 kubelet 和 containerd,不运行控制平面,使用了 275 MB。文档规定 server 的最低配置为 2 个核心和 2 GB RAM。该最低配置仅覆盖 k3s 及其随附组件,不包括您的工作负载。
实际情况是:在 2 GB VPS 上,控制平面和随附的附加组件会占用大部分资源,资源不足时首先发生的通常是 kubelet 驱逐 pod。对于运行几个小型服务的单节点,4 GB 是较为宽裕的起点。请测量您自己的主机,不要盲目相信任何已发布的数据,包括本文中的数据。
free -h
sudo k3s kubectl top node
sudo k3s kubectl get pods -A在安装前运行 free -h,并在 kube-system 中的每个 pod 都显示为 Running 后再次运行。两次结果的差值就是控制平面在您的硬件上占用的资源。安装后的前一两分钟内,k3s kubectl top node 会返回 error: Metrics API not available,因为 metrics-server 尚未抓取任何数据。这不是故障。如果您需要让同一台主机同时运行 k3s 和其他任务,则 为 VPS 规划 RAM 和 CPU 中的计算方式同样适用。
将 k3s 固定到指定版本,而不是 latest
大多数人直接复制的快速安装命令,会安装你执行命令当天稳定通道所指向的版本。对于需要长期运行的机器,应固定版本。k3s 为每个 Kubernetes 次要版本提供一个通道,因此 INSTALL_K3S_CHANNEL=v1.36 会跟随 v1.36 内的补丁版本更新,不会在你不知情的情况下跳到其他次要版本。截至 2026 年 8 月,稳定通道指向 v1.36.3+k3s1。
先写入配置文件,再执行安装。k3s 启动时会读取 /etc/rancher/k3s/config.yaml,因此其中的配置既会应用于首次启动,也会应用于之后的每次启动。
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'EOF'
tls-san:
- k3s.example.com
EOF
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=v1.36 sh -如需固定到某个确切版本,而不是固定到通道,请使用 INSTALL_K3S_VERSION=v1.36.3+k3s1。加号是标签的一部分。然后确认服务已启动。
k3s --version
sudo systemctl status k3s
sudo k3s kubectl get node
sudo k3s kubectl get pods -Akubectl get node 应在大约三十秒内列出一个 STATUS 为 Ready 的节点,并且 kube-system 中的每个 Pod 都应进入 Running 或 Completed 状态。节点长时间停留在 NotReady,通常表示容器运行时从未启动,因此请查看 sudo journalctl -u k3s -n 100 --no-pager。如果 VPS 使用了不常见的镜像,请先运行 sudo k3s check-config,再进行其他排查:它会报告缺失的内核功能,比查看日志更快定位问题。
80 端口已被占用,以及修复时需要放弃什么
对于已经在 VPS 上提供服务的主机,这是最容易遇到的问题。安装会成功,但 Traefik 始终无法获取地址,而原来运行的网站仍然正常工作。因此,直到您尝试访问 Ingress,表面上看不到任何故障。
其工作机制如下:内置的 Traefik chart 会在 80 和 443 端口上创建一个类型为 LoadBalancer 的 Service。ServiceLB 会通过创建一组小型 Pod 来处理该请求。这些 Pod 的名称以 svclb- 开头,并在每个节点上通过 hostPort 占用这些端口。hostPort 会将容器端口直接发布到节点自身的网络命名空间中,作用与 docker run -p 80:80 完全相同。如果 nginx、Caddy、Apache 或其他容器已经占用 80 端口,内核不会将同一个端口分配两次,因此调度器没有可放置该 Pod 的节点。
sudo k3s kubectl -n kube-system get pods
sudo k3s kubectl -n kube-system get svc traefik
sudo ss -lntp '( sport = :80 or sport = :443 )'您会看到 svclb Pod 处于 Pending 状态,并且 Service 没有外部地址:
svclb-traefik-8f2c1a-r6k9x 0/2 Pending 0 3m
traefik LoadBalancer 10.43.62.11 <pending> 80:31480/TCP,443:30219/TCP运行 kubectl -n kube-system describe pod svclb-traefik-... 可以直接显示原因:
0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.ss -lntp 会告诉您哪个进程占用了该端口。解决方法不止一种,但每种方法都有代价。
将端口交给 k3s。 停止并禁用现有 Web 服务器,然后让 Traefik 接管 80 和 443。这适用于 VPS 将专门作为 k3s 主机使用,且原来提供的所有服务都会迁移到 Ingress 后面的情况。
禁用 ServiceLB,保留现有代理。 使用 --disable=servicelb 安装。类型为 LoadBalancer 的 Service 仍会分配 NodePort,因此 Traefik 仍可通过 31480 等高位端口访问,然后由 nginx 或 Caddy 代理到 127.0.0.1:31480。您放弃的是外部地址:该 Service 会一直报告 <pending>。这看起来像故障,但实际上是您主动选择的结果。
禁用 Traefik,使用自己的代理进行路由。 使用 --disable=traefik 安装。此后集群中没有 Ingress 控制器,因此 Ingress 对象完全不会生效:它们只会保存在 API 中,没有控制器监视。若您通过主机代理将流量路由到 NodePort,这种方式没有问题。如果您已经确定如何处理 HTTP,这也是更直接的选择。如果您还不确定应该使用哪种前置代理,请先确定 nginx、Caddy 和 Traefik 之间的反向代理选择,再禁用任何组件。
这两个标志都应传给安装程序,也可以写入配置文件:
tls-san:
- k3s.example.com
disable:
- traefik
- servicelb安装后编辑该文件并运行 sudo systemctl restart k3s 同样有效,因为 --disable 不只是安装时跳过组件。它还会删除已经部署的组件,因此更改会在运行中的集群上生效。
如果您要保留 Traefik,但修改 chart 的配置方式,不要编辑 /var/lib/rancher/k3s/server/manifests/traefik.yaml。k3s 每次启动时都会使用默认值重写该文件。请改为在同一目录中添加单独的文件,因为 /var/lib/rancher/k3s/server/manifests 中的内容会在启动时以及文件发生变化时自动应用。
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
forwardedHeaders:
trustedIPs:
- 10.0.0.0/8此示例设置了一个 Traefik chart 值,即受信任的代理地址。使用相同机制可以设置 chart 暴露的其他值,包括端口。
单节点上的持久化存储
k3s 自带一个名为 local-path 的默认 StorageClass,由 Rancher 的 local-path provisioner 提供支持。未指定 storageClassName 的 PersistentVolumeClaim 会使用该 StorageClass。卷存储在 /var/lib/rancher/k3s/storage 中,每个卷在节点自己的磁盘上使用一个独立子目录。
sudo k3s kubectl get storageclass
sudo k3s kubectl get pvc -A
sudo ls -l /var/lib/rancher/k3s/storage“存储在节点自己的磁盘上”会带来两个后果,而且通常不会立即显现,而是在之后造成问题。
该 StorageClass 使用 volumeBindingMode: WaitForFirstConsumer,因此新的 PVC 会一直处于 Pending 状态,直到某个 pod 实际挂载它。kubectl describe pvc 的输出如下:
waiting for first consumer to be created before binding这是正常现象。因此,单独创建 PVC 后等待,其状态不会改变。
绑定后,卷会携带指向创建它的节点的节点亲和性。在该卷的整个生命周期内,使用此声明的所有 pod 都会被固定在该节点上。只有一个节点时通常不会发现这个问题。之后添加第二个节点,如果某个 pod 无法迁移,看起来就像调度器出现故障。运行 kubectl get pv -o yaml 后,可以在 nodeAffinity 中找到对应的主机名。
备份由您负责。重建 VPS 会删除该目录,下面的卸载脚本也会删除它。请备份 /var/lib/rancher/k3s/storage,同时备份 /var/lib/rancher/k3s/server/db/state.db 中的 sqlite 数据存储。复制该数据库前必须停止服务,因为它是一个正在使用的数据库。另一种做法是将集群视为可随时丢弃的资源,并将所有 manifest 保存在 git 中。
入口与 TLS
启用 Traefik 后,标准的 Ingress 对象即可满足需求。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: traefik
tls:
- hosts:
- hello.example.com
secretName: hello-tls
rules:
- host: hello.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port:
number: 80证书不会自动出现。通常的做法是使用 cert-manager:根据其发布的 manifest 安装,再配置一个 ClusterIssuer。截至 2026 年 8 月,当前版本为 v1.21.1。
sudo k3s kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.1/cert-manager.yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
email: you@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- http01:
ingress:
ingressClassName: traefikHTTP-01 challenge 要求 ACME(自动证书管理环境)服务器从公网连接到 http://hello.example.com/.well-known/acme-challenge/...。因此,DNS A 记录必须已经指向 VPS,且端口 80 必须能够访问 Traefik。如果您禁用了 ServiceLB,并在前面部署了自己的代理,该代理也必须转发 challenge 路径,否则 cert-manager 会停滞,并在 Challenge 对象中显示以下信息:
Waiting for HTTP-01 challenge propagation: wrong status code '404', expected '200'使用 sudo k3s kubectl describe certificate hello-tls 和 sudo k3s kubectl get order,challenge -A 监控证书签发过程。
kubeconfig,以及 API 服务器为何保持私有
k3s 会将管理员凭据写入 /etc/rancher/k3s/k3s.yaml。该文件归 root 所有,默认权限为 600。文件中包含具有 cluster-admin 权限的客户端证书,因此任何能够读取它的人都可以完全控制集群。
sudo k3s kubectl get node
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get node单独安装的 kubectl 未设置 KUBECONFIG 时会因 The connection to the server localhost:8080 was refused - did you specify the right host or port? 而失败,因为它会回退到一个与 k3s 无关的默认配置。请设置 KUBECONFIG,或使用 sudo k3s kubectl;后者会自动读取正确的文件。
您会看到推荐使用 --write-kubeconfig-mode 644,以便普通用户运行 kubectl。请明确这项操作的影响:它会让本机上的每个本地账户都能读取 cluster-admin 凭据。在单管理员服务器上,这可能是可以接受的取舍;在共享服务器上则不可取。复制该文件可以只授予一个用户访问权限,而不会将其公开:
mkdir -p ~/.kube
sudo install -o $USER -g $USER -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config该文件中的 server: 行读取 https://127.0.0.1:6443。要从笔记本电脑使用 kubectl,请勿将 6443 暴露到互联网。公开的 Kubernetes API 会持续遭受攻击;API 暴露后,小型集群可能会被他人用于挖掘加密货币。请通过 SSH 建立隧道,并保持文件中的地址不变:
ssh -N -L 6443:127.0.0.1:6443 user@your-vps
KUBECONFIG=./k3s.yaml kubectl get node如果必须通过私有网络地址访问 API,请使用 tls-san 安装,并在其中列出该名称或地址,然后编辑复制文件中的 server: 行,使其与之匹配。如果没有 SAN(主题备用名称)条目,kubectl 会拒绝连接:
x509: certificate is valid for 127.0.0.1, 10.43.0.1, not 203.0.113.10集群文档列出的入站端口包括:用于 API 的 TCP 6443、用于节点间 flannel VXLAN 的 UDP 8472,以及用于 kubelet 指标的 TCP 10250。在单节点集群上,这些端口都不需要向互联网开放。
containerd 不是 Docker
k3s 使用其内置的 containerd,并且不会与 Docker 共享镜像存储。使用 docker build 刚构建的镜像对 k3s 不可见,因此 Pod 会因 ErrImagePull 失败,即使 docker images 能列出该镜像。请显式导入:
docker save myapp:0.1 | sudo k3s ctr images import -
sudo k3s crictl images然后不要为该容器使用 :latest 标签,因为 :latest 默认使用 imagePullPolicy 的 Always,kubelet 仍会访问镜像仓库。其他任何标签默认使用 IfNotPresent,该策略会使用已导入的镜像。在同一台 VPS 上同时运行两者没有问题。但需要注意,VPS 上的标准 Docker 安装和 k3s 在同一台机器上分别维护自己的镜像存储和 iptables 规则。
如何删除 k3s
安装程序会写入卸载脚本。删除操作不支持部分卸载,也无法撤销。
sudo /usr/local/bin/k3s-uninstall.sh该脚本会停止并删除服务,删除数据存储和持久卷数据,移除节点配置,以及删除安装程序添加的工具。在 agent 节点上,脚本则是 k3s-agent-uninstall.sh。请先将 /var/lib/rancher/k3s/storage 下的所有内容复制到其他位置,因为该目录也会被删除。完成后,使用 ip link show 和 sudo ss -lntp 检查是否仍有进程占用端口或网络接口。残留的 cni0 或 flannel.1 接口会在下次重启时清除。
发现 Kubernetes 的单节点部署比任务实际需要的复杂度更高,并不意味着操作失败,这是很常见的结果。通常花一个下午,就可以将这些工作负载迁回 Compose。
故障模式及您将看到的字符串
Node NotReady,或 k3s 反复重启。 首先阅读 sudo journalctl -u k3s -n 200 --no-pager。在小型 VPS 上,常见原因是内核的内存不足终止程序机制终止了该进程;dmesg 中会显示一行包含 k3s-server 的日志。文档所述的 2 GB 最低配置确实是硬性下限。
Pod 卡在 Pending。 kubectl describe pod 每次都会说明原因。Insufficient memory 或 Insufficient cpu 表示节点已无可用资源。didn't have free ports 表示上文所述的 hostPort 冲突。PVC 上出现 waiting for first consumer 表示 WaitForFirstConsumer 正在正常工作。
ImagePullBackOff。 可能是该标签在节点可访问的任何镜像仓库中都不存在,也可能是您使用 Docker 构建了镜像,却没有将其导入 containerd。
Traefik 可以响应,但应用没有响应。 响应正文为 404 page not found 时,表示该响应来自 Traefik 本身,说明请求已到达,但没有匹配的路由器。检查 Ingress host 是否与您输入的名称匹配,并确认 ingressClassName 为 traefik。
集群 DNS 解析失败,但主机解析正常。 使用 sudo k3s kubectl -n kube-system logs -l k8s-app=kube-dns 检查 CoreDNS。出现类似 plugin/loop: Loop ... detected for zone "." 的消息会阻止 CoreDNS 启动。这是因为 CoreDNS 被配置为转发到一个又将请求转发回 CoreDNS 的解析器,而 /etc/resolv.conf 中的环回地址正会导致这种情况。使用 --resolv-conf /run/systemd/resolve/resolv.conf 将 k3s 指向实际的上游文件。
FAQ
在单个 VPS 上运行 k3s 是否值得?
如果您需要 Kubernetes API,那么值得运行:可以在自己控制的机器上学习 Kubernetes,将部署保存为可移植的 manifest,运行仅发布 Helm chart 的软件,或构建以后要迁移到托管集群的系统。如果您只想运行容器,则不值得,因为 Docker Compose 的维护成本低得多,并且通常能多出约 1 GB 可用内存。单节点不提供高可用性,因此可靠性不是选择单节点 k3s 的理由。
为什么我的 k3s LoadBalancer 服务一直处于 Pending 状态?
ServiceLB 会创建 svclb- 个 Pod,并在节点上使用 hostPort 占用该服务的端口,因此只有端口空闲的节点才能调度这些 Pod。如果 nginx 或其他代理已经占用端口 80,Pod 就会一直处于 Pending 状态,服务也不会获得外部地址。kubectl -n kube-system describe pod svclb-... 会报告 0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports。释放该端口,或使用 --disable=servicelb 重新安装,然后将流量代理到服务仍会分配的 NodePort。
k3s 在 VPS 上需要多少内存?
文档规定服务器节点的最低配置为 2 个核心和 2 GB 内存;在运行工作负载之前,这一配置仅覆盖 k3s 及其打包组件。该项目自己的性能分析显示,运行监控堆栈时,服务器节点占用 1,596 MB,因此应将 2 GB 视为最低配置,将 4 GB 视为单节点运行舒适的起始配置。安装前使用 free -h 测量自己的服务器,并在 kube-system 中的每个 Pod 都显示 Running 后再次测量。
可以在同一个 VPS 上运行 Docker 和 k3s 吗?
可以,两者彼此独立。k3s 使用自己的内置 containerd,因此使用 docker build 构建的镜像在运行 docker save myapp:0.1 | sudo k3s ctr images import - 之前对 k3s 不可见。两者也分别写入自己的 iptables 规则,并使用各自的桥接网络。请监控内存总量,因为 Docker、k3s 及其容器无法在 2 GB 的服务器上同时正常运行。
如何彻底删除 k3s?
在服务器节点上运行 sudo /usr/local/bin/k3s-uninstall.sh,在 agent 节点上运行 sudo /usr/local/bin/k3s-agent-uninstall.sh。该操作会停止服务、删除数据存储、删除 /var/lib/rancher/k3s/storage 下的持久卷数据,并移除随附工具。请先将需要保留的数据复制到服务器外部,因为此操作无法撤销。残留的 cni0 或 flannel.1 网络接口会在下次重启时消失。