fix(watchdog): k3s 证书 30 天内到期告警、7 天内转严重,文档说明重启续期与 rotate

This commit is contained in:
Lemon-miaow committed 2026-09-27 10:09:20 +08:00
1 parent 9d0253a75b
commit d112a43c65
4 files changed
+278

No files matched your search

+95
View File
@@ -1,8 +1,12 @@
package watchdog
import (
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"
)
@@ -14,6 +18,13 @@ const (
// clockFor leaves room for an NTP daemon that was just enabled (by the
// installer, or after a reboot) to reach its first synchronization.
clockFor = 30 * time.Minute
// certFor is zero: an expiry date does not heal itself while it waits.
certFor = 0
// certWarnWithin gives a month to find a moment to restart k3s;
// certCriticalWithin is the last week.
certWarnWithin = 30 * 24 * time.Hour
certCriticalWithin = 7 * 24 * time.Hour
// staUnsync is STA_UNSYNC from <linux/timex.h>. The kernel holds it set while
// no NTP daemon disciplines the clock; chronyd and systemd-timesyncd clear it
@@ -84,3 +95,87 @@ func ClockFinding(status int32, ok bool) *Finding {
Hint: "timedatectl; sudo timedatectl set-ntp true (docs/troubleshooting.md §13c)",
}
}
// K3sCertDirs are where k3s keeps the certificates it issues itself, the set
// `k3s certificate check` reads: the API server's serving and client
// certificates, the component and admin client certificates, etcd's, and the
// agent's (kubelet, kube-proxy). temporary-certs is left out: the API server
// makes its loopback certificate there on each start, for a hundred years.
var K3sCertDirs = []string{
"/var/lib/rancher/k3s/server/tls",
"/var/lib/rancher/k3s/server/tls/etcd",
"/var/lib/rancher/k3s/server/tls/kube-controller-manager",
"/var/lib/rancher/k3s/server/tls/kube-scheduler",
"/var/lib/rancher/k3s/agent",
}
// CertFinding reports the certificate in the *.crt files under dirs that
// expires first, once it is within certWarnWithin of expiring (certCriticalWithin,
// or past it: critical). k3s issues its client and serving certificates for a
// year and renews the ones close to expiry only as it starts, so a host that
// runs a year without restarting k3s loses its API server and its kubelet the
// day they lapse. Its CA certificates last ten years and no restart renews
// them. Only the public .crt files are read, never the keys beside them; a
// directory that does not exist (a host without k3s) reports nothing, and a
// file that does not parse is skipped.
func CertFinding(dirs []string, now time.Time) *Finding {
var soonest *x509.Certificate
var file string
for _, dir := range dirs {
// A directory or file that cannot be read reads as empty.
entries, _ := os.ReadDir(dir)
for _, e := range entries {
if !strings.HasSuffix(e.Name(), ".crt") {
continue
}
path := filepath.Join(dir, e.Name())
data, _ := os.ReadFile(path)
// k3s writes a leaf followed by the CA that signed it.
for {
var block *pem.Block
if block, data = pem.Decode(data); block == nil {
break
}
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
if soonest == nil || c.NotAfter.Before(soonest.NotAfter) {
soonest, file = c, path
}
}
}
}
if soonest == nil {
return nil
}
left := soonest.NotAfter.Sub(now)
if left >= certWarnWithin {
return nil
}
sev := Warning
if left < certCriticalWithin {
sev = Critical
}
when := soonest.NotAfter.UTC().Format("2006-01-02 15:04 UTC")
f := &Finding{Key: "k3s-certs", Severity: sev, For: certFor}
// x509 holds a certificate valid through the instant of NotAfter.
if left < 0 {
f.Summary = fmt.Sprintf("k3s 证书 %s(%s)已于 %s 过期:k3s 组件之间无法再认证,集群 API、节点和面板对服务器的管理都会中断",
file, soonest.Subject.CommonName, when)
f.SummaryEN = fmt.Sprintf("the k3s certificate %s (%s) expired at %s: the k3s components can no longer authenticate to each other, so the cluster API, the node and the panel's server management stop working",
file, soonest.Subject.CommonName, when)
} else {
days := int(left / (24 * time.Hour))
f.Summary = fmt.Sprintf("k3s 证书 %s(%s)将于 %s 过期(还剩 %d 天):过期后 k3s 组件之间无法认证,集群 API、节点和面板对服务器的管理都会中断",
file, soonest.Subject.CommonName, when, days)
f.SummaryEN = fmt.Sprintf("the k3s certificate %s (%s) expires at %s (%d days left): once it does, the k3s components can no longer authenticate to each other, so the cluster API, the node and the panel's server management stop working",
file, soonest.Subject.CommonName, when, days)
}
if soonest.IsCA {
f.Hint = "a k3s CA certificate, which no restart renews: rotate it with `k3s certificate rotate-ca` (docs/troubleshooting.md §13d)"
} else {
f.Hint = "sudo systemctl restart k3s: k3s renews its certificates near expiry as it starts, and running game servers keep running; check with sudo k3s certificate check (docs/troubleshooting.md §13d)"
}
return f
}