☰
k3s证书过期急救与100年有效期落地指南
2026/10/27 11:32:30 网站建设 项目流程

凌晨两点被电话叫醒,说集群里的 kubectl 全连不上了,报错清清白白写着x509: certificate has expired or is not yet valid,登录机器一看 k3s 服务根本没起来,日志里刷的全是证书过期的错误——这种场面做过几年边缘集群运维的人基本都撞见过一两次。k3s 之所以被大量用在边缘节点、小规模私有环境和一台机器就能跑起来的场景里,就是因为安装快、依赖少、一个二进制搞定,但它默认签发的一年期证书也埋了个定时炸弹:装机那天没人记得,一年后某天突然发现整个控制面哑火。这篇东西就是围绕k3s 证书过期这件事展开的,重点放在“傻瓜式”三个字上:不绕弯、不要求你精通 TLS 原理、不需要你去啃一堆源码,也能把证书换掉让集群重新站起来;更进一步,把“100 年过期”这件事从想法变成可执行的操作方案,让下一个接手的人不用再为同一件事加班。适合的角色包括:单机 k3s 自建服务的个人开发者、管理几十个边缘节点的小团队运维、以及正在评估 k3s 能不能长期无人值守跑下去的架构决策者。下面所有内容都基于 k3s 常见版本的通用行为整理,具体到你手上的版本如果细节有出入,按文中给的确认方法自己核对一遍再动手。

1. 先搞清楚 k3s 证书体系:过期的到底是哪一张

动手之前必须先把“谁过期了”这件事掰开。很多人一看证书过期,第一反应是整个 tls 目录一把梭删干净重启,结果 CA 也被删了,集群直接变成新装状态,之前的 etcd 数据和 kubeconfig 全对不上,损失比证书过期本身大得多。k3s 的证书不是一张,是一窝,按职责分成 CA 证书和叶子证书两层,CA 活得久一点,叶子证书短命,过期通常发生在叶子上。

1.1 证书家族与默认寿命对照表

先把默认路径说清楚,绝大部分安装方式下,服务端证书都落在/var/lib/rancher/k3s/server/tls/这个目录里,agent 侧的证书在/var/lib/rancher/k3s/agent/下面。默认有效期大概是这样分布的:

证书/密钥位置默认有效期角色
server-ca.crt / .keyserver/tls/10 年签发 apiserver 等 serving 证书的根
client-ca.crt / .keyserver/tls/10 年签发各类客户端证书的根
request-header-ca.crt / .keyserver/tls/10 年聚合 API 的请求头认证
etcd 相关 CAserver/tls/etcd/10 年etcd 节点间与客户端通信
apiserver.crt / .keyserver/tls/1 年对外提供 HTTPS 服务
apiserver-kubelet-client.crtserver/tls/1 年apiserver 访问 kubelet 用
kubelet-client.crt / .keyserver/tls/1 年同上,不同版本命名略有差异
client-admin.crt / .keyserver/tls/1 年kubeconfig 里那个 admin 身份
client-controller.crt、client-scheduler.crt 等server/tls/1 年各控制面组件身份
auth-proxy-client.crtserver/tls/1 年API 聚合代理
service.keyserver/tls/无过期概念对称密钥,用于 ServiceAccount token
dynamic-cert.jsonserver/tls/记录 apiserver 动态证书特殊文件,不是证书本身

从这张表能读出一个关键结论:真正到点失效的是那批一年期的叶子证书,CA 默认还有九年左右。这意味着大多数情况下你有救——CA 还在,就能重新签发;只有当你把集群装了十年以上、或者系统时间被拨乱过、或者把 CA 文件弄丢了,才会掉进“CA 也没了”的深坑。所以我一直跟人说,k3s 证书过期是“可恢复故障”,不是“数据灾难”,前提是你别一上来就rm -rf。

顺带提一句dynamic-cert.json。这个文件在较新的 k3s 版本里出现,用来承载 apiserver 的动态证书内容,它的存在会导致你单纯重生成了.crt文件但 apiserver 依然用着旧的那份。处理证书问题的标准动作之一就是把它一起清掉,这一点后面路线 B 会细讲。

1.2 为什么默认只签一年:设计取舍与真实翻车场景

很多人第一反应是“一年也太短了,为什么不默认十年”。这个问题值得说透,因为它直接决定了“改 100 年”到底是不是一个合理选择。

一年期的设计动机其实很朴素:短期证书降低了私钥泄露后的暴露窗口,配合自动轮换可以做到定期换血;同时它也在倒逼使用者建立轮换流程,而不是装完就不管。这个逻辑在有人值守、有自动化体系的环境里是成立的,k3s 也确实提供了自动续期的机制。但问题在于,k3s 被用得太“边缘”了——一台工控机、一个门店网关、一个实验室的演示集群,装完三个月之后就没人再登过那台机器,SSH 密码都快忘了。这种场景下,一年期证书就是一个纯粹的定时炸弹,没有任何安全收益,因为没人会去主动轮换,也没有监控盯着到期时间。

我见过几种典型的翻车现场,可以说几乎覆盖了所有踩坑姿势:

  • 机器长期断电后重启:边缘设备闲置了大半年,重新上电,系统时间因为主板电池没电回到了几年前,k3s 一起来就报certificate is not yet valid,比过期还懵。
  • 停机数月后服务起不来:测试集群关了一个季度,再开机systemctl start k3s直接失败,journalctl -u k3s里全是 x509 错误。
  • kubectl 突然连不上:k3s 服务本身还活着,但 kubeconfig 里的 admin 客户端证书过期了,kubectl get nodes报Unable to connect to the server: x509: certificate has expired,而systemctl status k3s显示 active,非常有迷惑性。
  • 集群重启后部分节点失联:server 节点没事,但 agent 节点的 kubelet 客户端证书过期,节点状态变成 NotReady,日志里 kubelet 疯狂重连失败。

这几种现场的共性是什么?没有监控到期时间,也没有预留维护窗口。所以“把有效期拉长到 100 年”本质上不是技术上的最优解,而是针对这类无人值守场景的工程妥协——用极低的维护成本换掉一个高概率的故障点。你要清楚地知道这是妥协,不是最佳实践。

1.3 “改成 100 年”到底改在哪一层:三种方案的本质差异

市面上流传的“k3s 证书 100 年”说法很多,但做法差异巨大,效果完全不同,动手前必须先分清楚自己在改哪一层。

第一种是改签发有效期(编译期)。k3s 的证书是启动时按代码里写死的时长生成的,把源码里写time.Hour * 24 * 365的地方改成time.Hour * 24 * 365 * 100,重新编译出二进制,之后所有新签发的叶子证书就是 100 年。这是最彻底的方案,改完之后不用再操心。代价是你要自己维护一个 k3s 分支,每次升级都得重新打补丁,而且二进制来源变成“自己编的”,出问题没有官方支持兜底。

第二种是定期主动轮换(运行期)。不改代码,用系统定时任务在证书到期前调用 k3s 内置的certificate rotate命令重新签发,然后再重启服务。本质上是把“100 年”换成“永远保持新鲜”,效果上同样能消灭定时炸弹,好处是二进制保持官方原版,升级路径干净。缺点是需要在每台机器上部署定时任务,并且重启服务这件事本身有中断风险。

第三种是只延长 CA,让叶子证书自动续。这个思路听起来聪明但其实不解决问题,因为日志里报错的从来都是叶子证书,CA 十年本来就够长。所以真正需要动的还是叶子那一年。

我个人对这三种的态度是:单机、无人值守、跑在别人够不着的边缘设备上,选第一种,一次做完永绝后患;有一定规模、有配置管理能力的集群,选第二种,更可控;两者可以叠加使用。下面三条实操路线就是按这个逻辑排的,你可以直接对号入座。

2. 动手前的体检与备份:这三件事没做完别敲命令

我见过太多人一看到报错就急着敲命令,结果把本来还能救的现场搞成不可逆。证书处理这个事,操作本身其实不复杂,复杂的是判断“我现在处于什么状态”。所以在敲任何命令之前,花十分钟做三件事:确认版本和状态、做完整备份、想清楚回滚路径。

2.1 三分钟环境体检清单

第一步,确认 k3s 的版本和安装方式。版本决定了你手上有没有k3s certificate rotate这个子命令,也决定了源码里证书时长的具体位置。执行:

k3s --version systemctl status k3s --no-pager

版本号会输出形如k3s version v1.28.x+k3s1的内容。粗略的经验是:较新的 1.2x 之后版本普遍带certificate子命令,更老的版本可能没有,需要靠删文件重签的方式处理。别猜,直接验证:

k3s certificate --help

能列出rotate、rotate-ca、check之类的子命令就说明可用。这里特别推荐k3s certificate check,如果你的版本支持,它能直接列出所有证书的到期时间,一眼看出哪张过期了,比一张张 openssl 翻要快得多:

k3s certificate check --output json

不支持的话,用 shell 循环兜底,效果一样:

for f in /var/lib/rancher/k3s/server/tls/*.crt; do echo "== $f" openssl x509 -in "$f" -noout -subject -dates done

第二步,确认系统时间。这个步骤经常被跳过,但它恰恰是最容易出鬼的地方:

timedatectl date -u

看两样东西:一是时间是否正确,二是时区/UTC 是否一致。如果机器时间偏差超过几分钟,证书校验就会出问题;如果时间回到了过去(比如主板电池没电),会报not yet valid而不是 expired,处理方式完全不同。时间不对的话,先把时间校准再谈证书,否则你签出来的新证书可能一生成就是“还没生效”的状态。校时可以临时手工设置,长期还是靠时间同步服务来保证。

第三步,确认集群形态。单节点还是多节点、有没有用嵌入式 etcd、有没有接外部数据库,这三点决定了操作顺序。查一下:

kubectl get nodes -o wide

如果kubectl已经连不上,就看配置文件和进程:

ls /etc/rancher/k3s/ ps -ef | grep -v grep | grep k3s

/etc/rancher/k3s/config.yaml里如果能看到datastore-endpoint之类的配置,说明是外部数据源,证书操作不会影响到数据层,可以放心一些;如果是嵌入式 etcd,就要额外注意 etcd 自己的证书目录/var/lib/rancher/k3s/server/tls/etcd/。

2.2 必须备份的文件与验证备份是否可用

备份这件事,我的原则是:只备份你必须保住的,不要把没用的东西一起打包,因为备份目录本身也可能被误删。

必须保住的,第一优先级是/var/lib/rancher/k3s/server/tls/整个目录,第二是/var/lib/rancher/k3s/server/db/(嵌入式 etcd 的数据),第三是/etc/rancher/k3s/(配置文件)和/etc/rancher/k3s/k3s.yaml(kubeconfig),第四是/var/lib/rancher/k3s/server/cred/(各类 token 和加密材料,像passwd、node-token这些)。

systemctl stop k3s TS=$(date +%Y%m%d%H%M%S) mkdir -p /root/k3s-backup-$TS tar czf /root/k3s-backup-$TS/server.tgz \ /var/lib/rancher/k3s/server/tls \ /var/lib/rancher/k3s/server/cred \ /var/lib/rancher/k3s/server/db \ /etc/rancher/k3s

一定要先停服务再备份,运行状态下db目录里可能有未落盘的事务,复制出来是脏的。备份完成之后别急着往下走,验证一下备份能不能解开:

tar tzf /root/k3s-backup-$TS/server.tgz | head -n 20

能列出文件就说明包是完整的。这一步看着多余,但我真见过打包命令因为磁盘满只写了一半、关键时刻解不开的。

注意:备份目录不要放在/var/lib/rancher/下面,也不要放在 k3s 的数据目录里。我就遇到过有人图省事放在同分区,结果清理数据目录时把备份一起删了。

另一个容易被忽略的点是kubeconfig 的副本。很多人的/etc/rancher/k3s/k3s.yaml被复制到了别处或者被改过 server 地址,k3s certificate rotate重签 admin 证书之后,这些副本里的证书内容并不会自动同步,需要重新复制一遍。提前把你会用到 kubeconfig 的所有位置记下来,后面逐个更新。

2.3 停机窗口与回滚预案

单节点场景下,处理证书必然要重启 k3s 服务,期间控制面不可用。如果你的业务 Pod 还在跑,只要它们不依赖 apiserver 做实时决策,重启服务本身不会杀掉 Pod,容器继续运行,但所有kubectl、控制器、调度器都会短暂挂起。所以选一个业务低峰期做,并且提前跟相关方打招呼。

多节点场景就更要小心。三种角色要分开考虑:server 节点重启时该节点上的控制面组件会短暂不可用,但只要还有其他 server 存活,集群整体可用;agent 节点重启只会影响本节点上的 Pod;如果所有 server 节点同时重启,控制面就完全中断了。

回滚预案的核心是这个备份要能在十分钟内还原回去。所以我建议你提前把还原命令写在一个脚本里,甚至演练一遍:

#!/bin/bash # rollback.sh set -e systemctl stop k3s rm -rf /var/lib/rancher/k3s/server/tls tar xzf /root/k3s-backup-XXXX/server.tgz -C / --strip-components=0 systemctl start k3s

注意 tar 解包时的路径处理,server.tgz里带的是绝对路径结构,解到/上是对的,但--strip-components不要乱加。还原之后验证systemctl status k3s和kubectl get nodes。

提示:所有操作都在同一个终端里做完,别中途开新窗口敲别的命令。证书这事儿,两个终端同时动手的翻车率远高于你想象。

3. 三条实操路线:按你的现场情况对号入座

准备工作做完,进入正题。下面三条路线是按“现场损坏程度”从轻到重排列的,先判断自己在哪一档,不要越级操作。

3.1 路线A:服务还起得来,用内置命令轮换

适用场景:systemctl status k3s是 active,但kubectl报证书过期;或者k3s certificate check明确告诉你有证书快到期或已过期,但进程还活着。这是最理想的情况,因为内置命令能帮你把重签、写入、刷新这一套动作都做完。

操作步骤并不复杂,但顺序很重要:

# 1. 停服务 systemctl stop k3s # 2. 轮换指定的证书类别 k3s certificate rotate \ --data-dir /var/lib/rancher/k3s \ --certificates admin,api-server,kubelet,auth-proxy # 3. 启动服务 systemctl start k3s # 4. 验证 k3s certificate check

--certificates后面跟的是一组类别名,不同版本可用的取值略有差异,常见的有admin、api-server、controller-manager、scheduler、k3s-controller、k3s-cloud-controller、etcd、auth-proxy、kubelet。记不住就k3s certificate rotate --help看一眼列表,只挑你需要的。如果实在不确定,不指定这个参数通常会让命令按默认集合处理,但我不建议图省事——轮换的范围越大,重启后需要重新同步的东西就越多。

这里有几个必须解释的“为什么”。

为什么先停服务?因为 rotte 命令写的是磁盘上的证书文件,正在运行的进程已经把旧证书读进内存了,停服务是为了避免运行态和磁盘态不一致带来的诡异现象。有些版本里 rotate 在服务运行时执行会提示需要先停止,别硬来。

为什么 admin 一定要轮换?因为你在本机kubectl用的就是 admin 身份,即使 apiserver 的 serving 证书换新了,客户端证书过期一样连不上。这两张是不同层次的东西,很多人只换了 apiserver 那张,结果发现kubectl还是报错,白折腾一轮。

为什么 etcd 证书要单独考虑?如果你的集群用了嵌入式 etcd,etcd 自己的证书在server/tls/etcd/下,和 k8s 那套是分开的。--certificates etcd是独立的一项。高可用集群里,etcd 证书轮换涉及 peer 之间互相认证,必须所有 server 节点同步操作,否则 etcd 集群会因为互相不信任而散伙。这一条是很多人栽跟头的地方,后面第 5 章还会展开。

轮换完成重启后,还有一步别忘了:把新的/etc/rancher/k3s/k3s.yaml同步到你本地或者跳板机上,否则你本地那份 kubeconfig 里还是过期的客户端证书。如果你在远端机器上操作,可以用这个方式快速确认新 kubeconfig 能用:

cp /etc/rancher/k3s/k3s.yaml ~/.kube/config sed -i 's/127.0.0.1/你的实际地址/' ~/.kube/config kubectl get nodes

3.2 路线B:彻底起不来,删短期证书让 k3s 重签

适用场景:systemctl start k3s直接失败,journalctl -u k3s里能看到明确的x509: certificate has expired,服务根本活不过来,路线 A 的命令执行不了。这是最常见的“严重现场”,也是所谓“傻瓜式”最需要覆盖的一档。

核心思路一句话:保留 CA,删掉所有短期证书和dynamic-cert.json,让 k3s 启动时用还在有效期内的 CA 重新签发一批新的。

先把要删和要留的分清楚。必须留下的:所有形如*-ca.crt、*-ca.key、*-ca-no-key.crt的文件,service.key,以及etcd/目录下同类的 CA 文件。必须删掉的:其余所有.crt和.key,以及dynamic-cert.json。

用脚本比手敲可靠得多:

systemctl stop k3s cd /var/lib/rancher/k3s/server/tls # 先列一下要删什么,确认无误再执行 find . -maxdepth 1 -type f \( -name "*.crt" -o -name "*.key" \) \ ! -name "*-ca.crt" ! -name "*-ca.key" ! -name "*-ca-no-key.crt" ! -name "service.key" -print # 确认后把上面命令的 -print 换成 -delete

执行完检查一下目录,剩下的应该就是几个 CA 和 service.key:

ls -la /var/lib/rancher/k3s/server/tls/

然后清掉动态证书缓存文件:

rm -f /var/lib/rancher/k3s/server/tls/dynamic-cert.json

接着启动:

systemctl start k3s journalctl -u k3s -f --no-pager

盯着日志看,正常情况下会看到 k3s 重新生成各类证书并完成启动。如果启动成功,验证:

kubectl get nodes

这里有一个特别容易踩的坑要说清楚:如果/var/lib/rancher/k3s/server/tls/etcd/目录下的叶子证书也过期了,只删tls/根目录是不够的。etcd 的证书是单独一套,需要同样处理一遍:

cd /var/lib/rancher/k3s/server/tls/etcd find . -maxdepth 1 -type f \( -name "*.crt" -o -name "*.key" \) \ ! -name "*-ca.crt" ! -name "*-ca.key" -delete

还有/var/lib/rancher/k3s/server/tls/下面的request-header-ca是 CA,叶子叫request-header.crt,别误删。删之前一定用-print看一眼列表,这个习惯救过我好几次。

另外,路线 B 有一个隐藏前提:CA 本身必须还在有效期内。如果你的集群已经存在十年以上,或者系统时间曾经被改到很远的未来又改回来,导致 CA 实际已过期,那么删叶子证书也没用,k3s 会用一张过期的 CA 去签发新证书,签出来的东西一样不可用。这种情况就只能走rotate-ca(需要所有节点协同)或者干脆重建集群。判断方法很简单:

openssl x509 -in /var/lib/rancher/k3s/server/tls/server-ca.crt -noout -dates

notAfter还在未来就没问题。

3.3 路线C:从源码层把有效期改到100年

这条路线解决的问题是“我以后不想再处理这件事了”。它不是在运行期打补丁,而是换一个自己编译的二进制,让此后生成的每一张叶子证书都是 100 年。

先说清楚代价,免得有人只看优点就上手。自己编译意味着:升级 k3s 版本时要重新打补丁重新编译;出问题时要能区分是上游 bug 还是你的改动引起的;二进制没有签名和官方支持。所以这条路线我只建议用在单机或小规模、无人值守、升级频率极低的场景,比如门店网关、工控机、偏远地区的采集节点。有专职运维的正式生产集群,老老实实走路线 A 加定时任务更稳妥。

这条路线可以和路线 A/B 组合使用:先用 B 把集群救活,再用 C 把二进制换掉,下次重启时新证书自动就是 100 年。

具体怎么改、怎么编译、怎么验证,第 4 章展开讲,这里先把判断标准给你:如果你的 k3s 是用来长期无人值守跑业务的,且你愿意维护一个自编译二进制,就走 C;否则停在 B 就足够了,后面用定时任务兜底。

4. 100年版本的完整落地过程:从改代码到验证

这一章是给愿意走编译路线的人准备的,全程给你可执行的步骤。如果你不打算走这条,可以跳过去看第 5 章的问题排查,那部分对三条路线都适用。

4.1 源码定位:那些写着365的地方

k3s 的证书生成逻辑集中在一个地方,源码里相关目录大致是pkg/daemons/control/deps/,证书签发函数和有效期相关的常量都在里面。定位方法不用死记,用检索最快:

git clone --depth 1 --branch v1.28.x+k3s1 https://你的代码托管地址/k3s.git cd k3s grep -rn "365" pkg/daemons/control/deps/*.go

你会看到几处关键位置,典型的形态是这样:

// 叶子证书(一年) NotAfter: time.Now().Add(time.Hour * 24 * 365), // CA 证书(十年) NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),

要把叶子证书改成 100 年,就是把365那行的系数改成36500,写法可以是time.Hour * 24 * 365 * 100,也可以用常量表达式365 * 24 * time.Hour * 100,效果一样。这里有个细节必须提醒:CA 那行也建议一并拉长,否则叶子证书活 100 年、CA 十年后就过期了,100 年的叶子照样验不过,等于白改。所以我的做法是把 CA 从* 10改成* 100,让整个链条一起拉长。

改完之后不要急着编译,先确认改动覆盖全了。有些版本里证书时长的计算被抽成了变量或者函数参数,不同调用点会传不同值,单纯改一个字面量可能漏掉某些证书类别。再搜一遍:

grep -rn "NotAfter" pkg/daemons/control/deps/*.go grep -rn "certDuration\|CertDuration" pkg/ pkg/daemons/ --include=*.go

把每一处出现的地方都过一遍,确认没有遗漏。改代码这事最怕的就是“以为改完了”,结果某类证书还是老样子,一年后又要来一次。

4.2 编译打包与替换

k3s 用 Go 写,编译环境要求 Go 版本和源码匹配。较新的 k3s 分支一般要 Go 1.20 以上,具体看仓库根目录的go.mod:

head -n 5 go.mod

编译命令的核心是交叉编译加静态链接,因为目标机器上不一定有 glibc:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -mod=vendor -trimpath \ -ldflags '-s -w' \ -o k3s ./cmd/k3s

几个参数解释一下,方便你按需调整。CGO_ENABLED=0保证产出的是静态二进制,拷到任何同架构的 Linux 上都能跑;GOARCH按你的目标机器改,ARM 设备就是arm64;-mod=vendor是因为 k3s 仓库带 vendor 目录,用本地依赖能避免拉包失败;-trimpath去掉路径信息,二进制小一点也更干净;-ldflags '-s -w'去掉符号表和调试信息,体积能小一截,对边缘设备很友好。

编译完检查一下产物:

ls -lh k3s file k3s ./k3s --version

务必在替换之前跑一下--version,确认能正常执行。我遇到过一次编译出来的是 ARM 版本,直接拷到 x86 机器上,替换完 systemctl 起不来,排查了半天才发现架构搞错了。

替换的步骤要稳:

# 1. 停服务 systemctl stop k3s # 2. 备份现有二进制 cp /usr/local/bin/k3s /usr/local/bin/k3s.bak-$(date +%Y%m%d) # 3. 替换 install -m 755 k3s /usr/local/bin/k3s # 4. 刷新 systemd(如果 unit 文件没变其实可以跳过,但做了更保险) systemctl daemon-reload # 5. 清掉旧证书,让新二进制重新签发 # 注意:这里沿用路线 B 的清理方式,保留 CA cd /var/lib/rancher/k3s/server/tls find . -maxdepth 1 -type f \( -name "*.crt" -o -name "*.key" \) \ ! -name "*-ca.crt" ! -name "*-ca.key" ! -name "*-ca-no-key.crt" ! -name "service.key" -delete cd etcd 2>/dev/null && find . -maxdepth 1 -type f \( -name "*.crt" -o -name "*.key" \) \ ! -name "*-ca.crt" ! -name "*-ca.key" -delete rm -f /var/lib/rancher/k3s/server/tls/dynamic-cert.json # 6. 启动 systemctl start k3s

注意第 5 步,这一步决定了新二进制是否真的生效。如果你只替换了二进制但不清旧证书,k3s 启动时会发现现有证书还没过期,直接拿来用,你看到的还是老的一年期证书,会误以为改动没生效。必须让它在“缺证书”的状态下重新生成,才会走你改过的那段代码。

有些人会选择把 CA 也一起换掉(连 CA 也改成 100 年),这就需要走k3s certificate rotate-ca或者干脆重装。我的建议是:如果你的集群是单机且数据可以重来,直接重装一个 100 年版本最干净;如果数据不能丢,就保留现有 CA 只换叶子,虽然 CA 还是十年,但至少解决了最紧迫的那批一年期证书,十年后再处理一次 CA 也不迟。

4.3 验证:openssl一行命令确认到期年份

改完之后必须验证,不能只看服务起没起来。最直接的方法是看证书的到期时间:

openssl x509 -in /var/lib/rancher/k3s/server/tls/apiserver.crt -noout -dates

正常应该输出类似:

notBefore=Nov 15 10:00:00 2024 GMT notAfter=Oct 22 10:00:00 2124 GMT

notAfter的年份是 2124 而不是 2025,就说明改动生效了。再把所有证书扫一遍,确认没有漏网的:

for f in /var/lib/rancher/k3s/server/tls/*.crt; do printf "%-45s " "$(basename $f)" openssl x509 -in "$f" -noout -enddate 2>/dev/null done

如果某个证书还是老日期,说明该类证书的签发逻辑没改到,回去再 grep 一遍。用k3s certificate check如果你的版本支持,输出会更直观,它会列出每张证书的类别、到期时间和剩余天数。

最后做一次端到端验证:

kubectl get nodes kubectl get pods -A

节点 Ready、Pod 正常,才算真正完成。别忘了把 kubeconfig 同步出去,以及确认 agent 节点(如果是多节点)重连正常——agent 那边如果还缓存着旧的客户端证书,可能需要重启 agent 服务才会重新申请。

提示:验证时顺手把每张证书的到期时间记录到你的文档或者监控系统里。即使改成了 100 年,写下来总没错,将来接手的人会感谢你。

5. 常见问题与排查实录

这一章是我认为最有价值的部分,因为官方文档不会告诉你“删完证书重启后 apiserver 起来了但 controller 一直 CrashLoop”这种事的成因。下面按报错现象组织,方便你对照现场。

5.1 报错速查表

现象大概率原因处理方向
x509: certificate has expired or is not yet valid叶子证书过期,或系统时间错乱先查date,再查证书到期时间
certificate is not yet valid系统时间早于证书 notBefore,常见于断电后 RTC 复位校准系统时间,再重启服务
Unable to connect to the server: x509: certificate signed by unknown authorityCA 被换过,kubeconfig 里的客户端证书和当前 CA 不匹配重新拉取/etc/rancher/k3s/k3s.yaml
k3s服务起不来,日志刷failed to load CA certCA 文件被误删或损坏从备份还原*-ca.crt/*-ca.key
apiserver 正常但kubectl get全部超时dynamic-cert.json里的旧证书还在被使用停服务删该文件再启动
节点 NotReady,kubelet 报证书错误agent 侧客户端证书过期重启 agent 或清理/var/lib/rancher/k3s/agent/下过期证书
etcd 集群只剩一个节点,其他报certificate错误etcd peer 证书过期或 CA 不一致所有 server 同步轮换 etcd 证书
轮换后 controller-manager 反复重启客户端证书类别漏轮换补--certificates controller-manager再轮换
服务起来了但kubectl报Forbidden权限问题而非证书问题检查 RBAC,别往证书方向排查

这个表我建议直接贴到你的运维笔记里。特别是最后一条,很多人看到报错里有x509就认定是证书问题,其实Forbidden是认证过了但授权没过,方向完全错了。

5.2 多节点与高可用集群的特殊处理

单机处理证书是“一个人的事”,多节点就变成“一群人的事”,顺序错了集群直接散架。核心原则是:先处理 server,再处理 agent;server 之间要同步,agent 可以滚动。

嵌入式 etcd 的高可用集群最简单粗暴的做法是滚动处理:一次只动一个 server 节点,动完确认kubectl get nodes里该节点 Ready、etcd 健康(kubectl -n kube-system get pods | grep etcd或者从集群内部查etcdctl endpoint health),然后再动下一个。中间如果出问题,停下来处理,别贪进度。

etcd 证书轮换有一个隐藏陷阱:peer 证书是节点之间互相认证用的,如果你只轮换了节点 A 的 etcd 证书,节点 B 还用旧 CA 去验 A,就会报certificate signed by unknown authority,表现为 etcd 成员之间互相踢。所以etcd 证书的 CA 要么全集群一起换,要么一个都别换。叶子证书轮换也建议所有节点在同一维护窗口内完成。

agent 节点的处理相对独立。agent 侧的 client 证书放在/var/lib/rancher/k3s/agent/下面,常见文件名类似client-kubelet.crt、client-kube-proxy.crt。这些过期后最省事的做法是直接删掉对应文件然后重启 agent 服务:

systemctl stop k3s-agent find /var/lib/rancher/k3s/agent -maxdepth 1 -name "client-*.crt" -o -maxdepth 1 -name "client-*.key" -delete systemctl start k3s-agent

agent 会重新向 server 申请证书。注意要在 server 证书已经处理完之后再做,否则申请到的还是老状态。滚动处理时一次一个节点,观察节点状态和调度情况,确认业务 Pod 正常迁移或恢复再动下一个。

还有一点:多节点环境里时间同步是刚需。单机时间飘一点可能只是本地问题,多节点时间不一致会导致证书校验在节点间随机失败,表现极其诡异——同一个 kubectl 命令这次成功下次超时。部署之前把时间同步服务配好,这是成本最低、收益最高的预防措施。

5.3 几条踩坑换来的经验

第一条,永远先看时间再看证书。我遇到过至少三次“证书过期”其实是因为机器时间不对。有一次是虚拟机被迁移到宿主机后 RTC 没同步,时间回到了两年前;有一次是主板电池彻底没电,每次断电都回到出厂日期。这两种情况下你辛辛苦苦重签证书,重启后还是同样的报错,因为时间仍然是错的,新证书一生成就是“未生效”状态。所以排查顺序固定成:date→ 证书notBefore/notAfter→ 服务日志,能省掉大量瞎折腾。

第二条,不动 CA 就永远有退路。这是我给自己定的铁律。CA 文件是整台机器上最不该碰的东西,只要它在,任何叶子证书问题都能通过删除重签恢复,最坏情况也不过是集群配置丢了重建一遍。一旦 CA 被删或损坏,所有客户端证书立刻失效,agent 全部掉线,等于集群重建。所以清理证书的脚本里,我永远把*-ca.crt、*-ca.key、service.key排除在外,而且执行前一定先-print看列表。

第三条,定时任务比 100 年更靠谱。说实话,改成 100 年虽然省事,但它解决的是“证书时间”问题,不是“我忘了这事”的问题。真正让我睡得着觉的组合是:100 年二进制 + 一个每月跑一次的检查脚本,发现剩余天数少于阈值就告警。检查脚本很简单,几行 bash 就够了:

#!/bin/bash THRESHOLD=180 for f in /var/lib/rancher/k3s/server/tls/*.crt; do end=$(openssl x509 -in "$f" -noout -enddate | cut -d= -f2) end_ts=$(date -d "$end" +%s) now_ts=$(date +%s) days=$(( (end_ts - now_ts) / 86400 )) if [ "$days" -lt "$THRESHOLD" ]; then echo "WARN: $(basename $f) 剩余 ${days} 天" fi done

挂到 crontab 里每天跑一次,输出重定向到日志,有告警就接上你的通知渠道。成本不到十分钟,收益是彻底消灭这类事故。

第四条,别在业务高峰期做,也别一次动全部节点。这条听起来像废话,但我见过有人在中午十二点给生产集群轮换证书,重启过程中调度器挂了两分钟,一批定时任务全部错过时间窗,事后排查了半天。还有就是多节点一次性全停,etcd 失去法定人数,恢复起来比证书本身麻烦得多。

第五条,文档写下来,写给下一个人看。处理完一次之后,把版本号、操作步骤、备份路径、你踩过的坑写成一页纸放进仓库。证书这东西两三年才碰一次,等到下次出问题,连上次是谁解决的都查不到了。

最后分享一个我自己的经验:处理 k3s 证书这事儿,最怕的不是技术难,是慌。凌晨被叫醒,脑子一片空白,手一抖就是rm -rf。所以我把整个流程做成一个脚本,脚本里每一步都有确认和日志,出问题时先跑脚本、看日志、再决定下一步。慢一点,但不会翻车。这套东西做完之后,我这边跑着 k3s 的几台边缘设备,从装机到现在没再因为证书的事被叫起来过——这大概就是折腾这一轮的真正价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询