本文永久链接: https://www.xtplayer.cn/kubernetes/kubelet-cert-expired/

问题现象

有时间可能会出现节点状态不正常,然后查看 kubelet 容器日志时,会看到如上图的证书过期的错误信息。

问题排查

  1. 如果你是 rke1 版本创建的 local 集群或者下游自定义集群,可以使用以下命令查询证书有效期:
printf '\n%-16s %-16s %-14s %s\n' "开始时间" "过期时间" "剩余天数" "证书路径"
echo "----------------------------------------------------------------------------"

current_timestamp=$(date +%s)

for crt in $(ls /etc/kubernetes/ssl/*.pem | grep -v '\-key\.'); do
# 获取开始时间
start_date=$(openssl x509 -startdate -noout -in "$crt" | cut -d= -f 2)
start_iso=$(date --date="$start_date" "+%Y-%m-%d" 2>/dev/null || echo "N/A")

# 获取过期时间
end_date=$(openssl x509 -enddate -noout -in "$crt" | cut -d= -f 2)
end_iso=$(date --date="$end_date" "+%Y-%m-%d")

# 计算剩余天数
end_timestamp=$(date --date="$end_date" +%s 2>/dev/null)
if [ -n "$end_timestamp" ] && [ "$end_timestamp" -gt 0 ]; then
days_remaining=$(( (end_timestamp - current_timestamp) / 86400 ))

# 格式化天数显示
if [ "$days_remaining" -lt 0 ]; then
days_display="过期$(( -days_remaining ))天"
elif [ "$days_remaining" -eq 0 ]; then
days_display="今天过期"
else
days_display="$days_remaining天"
fi
else
days_display="N/A"
fi

printf '%-12s %-12s %-11s %s\n' \
"$start_iso" \
"$end_iso" \
"$days_display" \
"$crt"
done | sort -k2
  1. 如果你是 rke2 版本创建的 local 集群或者下游自定义集群,可以使用以下命令查询证书有效期:
#!/bin/bash

# 函数:计算剩余天数
calculate_days_left() {
local expire_date="$1"
local current_epoch=$(date +%s)
local expire_epoch=$(date --date="$expire_date" +%s 2>/dev/null)

if [ -z "$expire_epoch" ]; then
echo "INVALID"
return
fi

if [ $expire_epoch -gt $current_epoch ]; then
local seconds_left=$((expire_epoch - current_epoch))
local days_left=$((seconds_left / 86400))
echo "$days_left"
else
echo "EXPIRED"
fi
}

# 函数:检查证书信息并格式化输出
check_cert_info() {
local dir="$1"
local description="$2"

printf '\n%s\n' "$description"
printf '%-12s %-12s %-10s %-50s %s\n' "start_time" "expire_time" "days_left" "subject" "ssl_path_name"
printf '%.12s %.12s %.10s %.50s %s\n' "------------" "------------" "----------" "--------------------------------------------------" "--------------------------------------------------"

local CRT_LIST
if [[ "$dir" == *"maxdepth"* ]]; then
CRT_LIST=$(eval "find $dir -iname '*.crt' -o -iname '*.pem' 2>/dev/null" | grep -v '\-key\.')
else
CRT_LIST=$(find "$dir" \( -iname "*.crt" -o -iname "*.pem" \) 2>/dev/null | grep -v '\-key\.')
fi

if [ -z "$CRT_LIST" ]; then
printf '%-12s %-12s %-10s %-50s %s\n' "N/A" "N/A" "N/A" "N/A" "No certificates found"
return
fi

{
for crt in $CRT_LIST; do
if [ -f "$crt" ]; then
# 获取证书信息
start_time=$(openssl x509 -startdate -noout -in "$crt" 2>/dev/null | cut -d= -f 2-)
expire_time=$(openssl x509 -enddate -noout -in "$crt" 2>/dev/null | cut -d= -f 2-)
subject=$(openssl x509 -subject -noout -in "$crt" 2>/dev/null | sed 's/subject=//' | sed 's/^[ \t]*//;s/[ \t]*$//')

if [ -n "$start_time" ] && [ -n "$expire_time" ] && [ -n "$subject" ]; then
start_iso=$(date --date="$start_time" "+%Y-%m-%d" 2>/dev/null || echo "Invalid Date")
expire_iso=$(date --date="$expire_time" "+%Y-%m-%d" 2>/dev/null || echo "Invalid Date")
days_left=$(calculate_days_left "$expire_time")

printf '%-12s %-12s %-10s %-50s %s\n' "$start_iso" "$expire_iso" "$days_left" "$subject" "$crt"
else
printf '%-12s %-12s %-10s %-50s %s\n' "ERROR" "ERROR" "ERROR" "ERROR" "$crt"
fi
fi
done
} | sort -k2 # 按过期时间排序
}

# 检查服务器证书
check_cert_info "/var/lib/rancher/rke2/server/tls/" "Server TLS Certificates:"

# 检查代理证书
check_cert_info "/var/lib/rancher/rke2/agent/ -maxdepth 1" "Agent Certificates:"
  • 对于 rke1 集群,如果发现 /etc/kubernetes/ssl 目录下的证书过期,那么可以对集群进行证书更新。对于 rke1 创建的 local 集群,需要执行 rke cert rotate 进行证书更新。对于 rke1 创建的下游集群,可以在 rancher ui 的集群管理中进行集群证书更新。
  • 对于 rke2 集群,如果发现 /var/lib/rancher/rke2/server/tls/ 目录下的证书过期,那么可以对集群进行证书更新。对于 rke2 创建的 local 集群,可以根据文档 https://docs.rke2.io/zh/advanced#%E8%AF%81%E4%B9%A6%E8%BD%AE%E6%8D%A2 进行证书更新。对于 rke2 创建的下游集群,可以在 rancher ui 的集群管理中进行集群证书更新。

有时候,根据以上方法排查后,发现证书都没有过期或者更新证书后 kubelet 依旧报 x509 证书错误。那这种情况就应该是 kubelet 内置的证书过期了。

  • 对于 rke1 集群,可以使用命令 openssl x509 -enddate -noout -in "/var/lib/kubelet/pki/kubelet.crt" 检查证书有效期。
  • 对于 rke2 集群,可以使用命令 openssl x509 -enddate -noout -in "/var/lib/rancher/rke2/agent/serving-kubelet.crt" 检查证书有效期。

问题分析

Kubelet 会使用一个内置证书,如果手动指定这个证书,可以通过参数 –tls-cert-file–tls-private-key-file 进行配置。如果没有手动指定证书,那么 kubelet 会自动在 /var/lib/kubelet/pki 目录下生成 kubelet.crt 和 kubelet.key 证书文件。这个证书有效期为一年,并不会自动更新。

解决方法

rke1

临时的,可以把 kubelet 生成的证书删除,然后重启 kubelet 容器后会自动重新生成证书。

docker exec kubelet rm /var/lib/kubelet/pki/kubelet.crt
docker exec kubelet rm /var/lib/kubelet/pki/kubelet.key
docker restart kubelet

在 Rancher v2.3.3 或 RKE v1.0.0 之前的版本中,没有为 kubelet 配置 --tls-cert-file--tls-private-key-file 参数。针对这个问题,在 Rancher v2.3.3 或 RKE v1.0.0 之后的集群配置中,增加了参数 generate_serving_certificate 来自动轮换 kubelet 证书。默认这个参数值为 false,设置为 true 自动轮换证书。

在 rke 配置或者自定义集群 YAML 配置中添加 generate_serving_certificate 参数:

services:
kubelet:
generate_serving_certificate: true

注意: 在修改或者添加 generate_serving_certificate 参数之前,请根据文档 https://docs.rancher.cn/docs/rke/config-options/services/_index/#kubelet-%E9%80%89%E9%A1%B9 的说明查看一下 Kubelet Serving Certificate 需求。

rke2

rke2 集群默认是传递了 --tls-cert-file--tls-private-key-file 参数,默认配置为 --tls-cert-file=/var/lib/rancher/rke2/agent/serving-kubelet.crt --tls-private-key-file=/var/lib/rancher/rke2/agent/serving-kubelet.key

如果在 rke2 集群中查询到这个证书过期,临时的也可以删除 serving-kubelet 证书然后重启 rke2 服务。

rm -rf /var/lib/rancher/rke2/agent/serving-kubelet.crt
rm -rf /var/lib/rancher/rke2/agent/serving-kubelet.key
systemctl restart rke2-server.service

也可以根据文档 https://docs.rke2.io/zh/advanced#%E8%AF%81%E4%B9%A6%E8%BD%AE%E6%8D%A2 对 rke2 local 集群证书进行更新,或者在 rancher ui 的集群管理中进行集群证书更新。如果执行证书更新后 serving-kubelet 证书没有更新,那么请升级 rke2 版本到 1.28+。