← 返回首页 K8S Web / 文档 / K8S 证书管理

🔐 K8S 证书管理 ⭐A1 即本环境真实部署配置 k8s.ergesanbaishou.com:现有 80 版 + 443 升级版,可直接抄  |  A 域名 SSL(443):两类证书辨析、挂载已有证书、cert-manager、内网自签、报错速查  |  B 集群内部证书(pki):kubeadm 签发、SAN 定制、到期续期

A1⭐ 部署方式:外层 NGINX 443 → Ingress(本环境真实配置 k8s.ergesanbaishou.com)

本环境真实链路:k8s.ergesanbaishou.com → Ingress(hello1.qingfeng.dev)→ nginx-hello(qingfeng 空间)。证书统一挂外层 nginx,在 443 终止 TLS,后面集群内走 http —— 一处换证、全站生效,K8s 侧零改动。你机器上现在已有 80 版配置,只需按下面 ② 改 3 处即可升级 HTTPS:

浏览器 ──HTTPS:443──▶ 外层NGINX(证书在这,TLS终止)
                        │ 301(80→443)
                        └──HTTP:31588──▶ ingress-nginx(NodePort) ──▶ svc:8080 ──▶ pod:80(nginx-hello)

① 现状 —— 你机器上已有的 80 版配置(原样,可用):

# /etc/nginx/conf.d/k8s.ergesanbaishou.com.conf
# hello1.qingfeng.dev → K8s Ingress → nginx-hello(qingfeng 空间)
# 外层 nginx:80 → ingress-nginx NodePort:31588 → svc:8080 → pod:80
upstream k8s_ergesanbaishou {
    server 10.18.170.34:31588;     # ★ ingress-nginx 的 NodePort(80 对应)
    keepalive 32;
}

server {
    listen 80;
    server_name k8s.ergesanbaishou.com;

    access_log /var/log/nginx/hello1.access.log;
    error_log  /var/log/nginx/hello1.error.log;

    location / {
        proxy_pass http://k8s_ergesanbaishou;

        # ★★★ 灵魂行:透传 Host,Ingress 全靠它分流,丢了必 404 ★★★
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_http_version 1.1;              # WebSocket 支持(可选)
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        "upgrade";

        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

② 升级 HTTPS —— 在上面文件里改 3 处(★ 标出),完整 443 版如下,可整份覆盖:

# /etc/nginx/conf.d/k8s.ergesanbaishou.com.conf —— 443 完整版
# 浏览器 ──HTTPS:443──▶ 外层NGINX(TLS终止) ──HTTP:31588──▶ ingress ──▶ svc:8080 ──▶ pod:80
upstream k8s_ergesanbaishou {
    server 10.18.170.34:31588;     # ★ 改动0:不变,还是 ingress-nginx 的 NodePort
    keepalive 32;
}

# ★ 改动1:原 80 的 location 整块删掉,只留一行 301 跳 https
server {
    listen 80;
    server_name k8s.ergesanbaishou.com;
    return 301 https://$host$request_uri;
}

# ★ 改动2:新增 443 server(证书两行 + 原来的 location 整块搬进来)
server {
    listen       443 ssl;
    server_name  k8s.ergesanbaishou.com;

    # ★ 证书两行(fullchain 含中间证书;先自签,命令在下面)
    ssl_certificate     /etc/nginx/ssl/ergesanbaishou.com.pem;
    ssl_certificate_key /etc/nginx/ssl/ergesanbaishou.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL_ergesan:10m;

    access_log /var/log/nginx/hello1.access.log;
    error_log  /var/log/nginx/hello1.error.log;

    location / {
        proxy_pass http://k8s_ergesanbaishou;

        # ★★★ 灵魂行:透传 Host,Ingress 全靠它分流,丢了必 404 ★★★
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;   # 443 时=https,ingress 靠它识别原始协议

        proxy_http_version 1.1;              # WebSocket 支持(可选)
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        "upgrade";

        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

# ★ 改动3:没有正式证书就先自签一把(浏览器会提示不安全,内网可用)
#   mkdir -p /etc/nginx/ssl
#   openssl req -x509 -nodes -days 825 -newkey rsa:2048 \
#     -keyout /etc/nginx/ssl/ergesanbaishou.com.key \
#     -out    /etc/nginx/ssl/ergesanbaishou.com.pem \
#     -subj "/CN=k8s.ergesanbaishou.com" \
#     -addext "subjectAltName=DNS:k8s.ergesanbaishou.com,DNS:harbor.ergesanbaishou.com"

# 生效三连
nginx -t && systemctl reload nginx
curl -kv https://k8s.ergesanbaishou.com 2>&1 | grep subject   # 应输出 CN=k8s.ergesanbaishou.com

③ Harbor 域名的 SSL 终版配置(实测通过,docker push/pull + UI 全通):与普通站点差异极大 —— 80 端口对 /v2/、/service/ 必须直反代(token 换发与层上传会走 http),443 必须加 proxy_redirect 把 registry 吐的 http Location 改写成 https:

# /etc/nginx/conf.d/harbor.ergesanbaishou.com.conf —— 80+443 终版
upstream harbor_ergesanbaishou {
    server 10.18.170.34:32588;     # harbor 的 ingress NodePort(http)
    keepalive 32;
}

# 80:/v2/ 和 /service/ 直接反代(registry 协议严禁 301);其余路径跳 https 给 UI
server {
    listen 80;
    server_name harbor.ergesanbaishou.com;

    location /v2/ {                        # ★ registry 入口,严禁 301
        proxy_pass http://harbor_ergesanbaishou;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        client_max_body_size 0;            # push 大镜像层不限
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
    location /service/ {                   # ★ token 签发入口,同样严禁 301
        proxy_pass http://harbor_ergesanbaishou;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    location / {                           # 浏览器 UI 才跳 https
        return 301 https://$host$request_uri;
    }
}

# 443:SSL 终止,反代 UI/API
server {
    listen       443 ssl;
    server_name  harbor.ergesanbaishou.com;

    ssl_certificate     /etc/nginx/ssl/harbor.ergesanbaishou.com.pem;
    ssl_certificate_key /etc/nginx/ssl/harbor.ergesanbaishou.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL_harbor:10m;

    access_log /var/log/nginx/harbor-ergesanbaishou.access.log;
    error_log  /var/log/nginx/harbor-ergesanbaishou.error.log;

    client_max_body_size 0;

    location / {
        proxy_pass http://harbor_ergesanbaishou;
        # ★★★ 银弹行:把 registry 返回的 http:// Location 改写成 https,
        #     否则 PUT 跟 Location 走 80 被杀,报 unauthorized(Harbor FAQ ⑩.4)
        proxy_redirect ~^http://harbor\.ergesanbaishou\.com(:\d+)?(/.*)$ https://harbor.ergesanbaishou.com$2;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_http_version 1.1;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        "upgrade";
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}
# 生效: nginx -t && systemctl reload nginx

A2先分清:域名 SSL 证书 ≠ 集群内部证书

域名 SSL 就是"浏览器地址栏那把 🔒":保护用户 → 你的网站的 HTTPS(443) 流量,证书绑定你的域名,挂载在 Ingress 上;它和 kubectl 用的集群内部证书(本页 B 部分)是两套完全独立的体系:

域名 SSL 证书(本部分)集群内部证书(B 部分)
保护什么浏览器 → 网站的用户流量kubectl/组件之间的管理面通信
端口443(Ingress 终止 TLS)6443 / 2379 / 10250
绑定什么你的域名 k8s.example.com节点名 / kubernetes.default / 集群 IP
挂载位置TLS Secret → Ingress tls.secretName/etc/kubernetes/pki/
谁签发公共 CA(Let's Encrypt/阿里云/DigiCert)或自签kubeadm 自建 CA
过期后果浏览器红色"不安全"、小程序/App 拒连kubectl 连不上集群
怎么续换 Secret 里的证书 / cert-manager 自动kubeadm certs renew
# 两条链路看流量,证书各管一段:
浏览器 ──HTTPS:443──▶ Ingress-nginx ──HTTP:80──▶ Service ──▶ Pod
         ▲ 域名 SSL 在这里校验(TLS 终止,后面集群内走明文)

kubectl ──HTTPS:6443──▶ apiserver
         ▲ 内部证书在这里校验(B 部分)
💡 判断口诀:浏览器报"不安全" = 域名 SSL 的事(A 部分);kubectl 连不上 = 内部证书的事(B 部分)。排查前先分清走的是哪条链路。

A3部署① 挂载已有证书 —— 公司发的/买的证书(Secret + Ingress)

最常见场景:已经从阿里云/公司拿到了域名的证书文件,把它挂到 Ingress 上让网站变 HTTPS。三步搞定:

# 0. 你拿到的通常是两个文件(可能是 .crt/.key 或 .pem)
ls
fullchain.pem  privkey.pem
# ★ fullchain = 域名证书 + 中间证书链;只放单张证书部分客户端会报"不受信任"

# (pfx 格式先转换)
# openssl pkcs12 -in cert.pfx -nokeys -out fullchain.pem
# openssl pkcs12 -in cert.pfx -nocerts -nodes -out privkey.pem

# 1. 存成 TLS Secret(★ namespace 必须和 Ingress 在同一个!)
kubectl create secret tls web-tls -n web \
  --cert=fullchain.pem --key=privkey.pem

# 2. Ingress 引用(hosts 必须和证书域名完全一致,包括子域)
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: web
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - k8s.example.com
    secretName: web-tls        # ← 第 1 步创建的 Secret
  rules:
  - host: k8s.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
EOF

# 3. 验证 443 已用新证书
curl -kv https://k8s.example.com 2>&1 | grep -E 'subject:|expire'
  subject: CN=k8s.example.com
  expire date: Jan 20 00:00:00 2027 GMT

# 附:查看 Secret 里证书的域名/到期日
kubectl get secret web-tls -n web -o jsonpath='{.data.tls\.crt}' \
  | base64 -d | openssl x509 -noout -subject -enddate
⚠️ 常见翻车:① Secret 建在了别的 namespace → Ingress 找不到,仍然走默认假证书(Kubernetes Ingress Controller Fake Certificate);② 证书是 *.example.com 通配符,却用 a.b.example.com 访问 → 通配符只匹配一级子域。

A4部署② cert-manager + Let's Encrypt —— 免费证书自动申请/续期

不想每年手动换证书?装 cert-manager,证书自动申请、自动挂载、到期自动续,公网域名全程零人工:

# 1. 安装 cert-manager(一次性,官方清单)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml
kubectl -n cert-manager get pods   # 三个 Pod 全 Running 再继续

# 2. 配置签发器(HTTP-01 挑战;前提:域名已公网解析到 Ingress 入口、80 端口可达)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com                     # 到期提醒邮件
    privateKeySecretRef:
      name: letsencrypt-key
    solvers:
    - http01:
        ingress:
          ingressClassName: nginx
EOF

# 3a. 用法一(最省事):Ingress 加一个注解即可,Secret 自动生成
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
# (tls.secretName 随便起个名,如 web-tls;cert-manager 会自动创建并填充)

# 3b. 用法二(更显式):Certificate 对象,指定域名清单
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: web-cert
  namespace: web                               # ★ 和使用它的 Ingress 同 ns
spec:
  secretName: web-tls                          # 签好后证书存在这个 Secret
  dnsNames:
  - k8s.example.com
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
EOF

# 4. 盯签发状态(READY=True 即成功,Secret 已生成)
kubectl get certificate -n web
NAME       READY   SECRET     AGE
web-cert   True    web-tls    2m
# 卡在 False 就:kubectl describe certificate web-cert -n web  看 Events
⚠️ 排查三连:READY=False 先 describe certificate 看 Events → 多半是 80 端口外网不可达(HTTP-01 挑战失败)或域名解析没指向 Ingress;其次 kubectl get challenge -n web 看挑战详情。

A5部署③ 内网域名自签证书 —— harbor.xxx.local 这类

内网域名(如 harbor.qingfeng.local)没有公网解析,公共 CA 不给签 → 自己签一把,挂法与 A3 完全相同:

# 1. 一条命令自签(825 天,SAN 写全所有要访问的域名)
openssl req -x509 -nodes -days 825 -newkey rsa:2048 \
  -keyout privkey.pem -out fullchain.pem \
  -subj "/CN=harbor.qingfeng.local" \
  -addext "subjectAltName=DNS:harbor.qingfeng.local,DNS:registry.qingfeng.local"

# 2. 存 Secret(与 A3 第 1 步相同)
kubectl create secret tls harbor-tls -n harbor \
  --cert=fullchain.pem --key=privkey.pem

# 3. Ingress tls 段引用 secretName: harbor-tls(完整写法见 A3)

# 4. 验证
curl -kv https://harbor.qingfeng.local 2>&1 | grep subject
  subject: CN=harbor.qingfeng.local
💡 三种部署方式选型:有公网域名 → A4 cert-manager(全自动);证书是买的/公司发的 → A3 挂载;纯内网域名 → A5 自签。详见 Harbor 文档 的私仓 HTTPS 实战。

A6443/SSL 高频报错速查表

现象根因解决方案
浏览器 ERR_CERT_COMMON_NAME_INVALID访问的域名 ≠ 证书 SAN(子域写错/通配符层级不符)核对证书 SAN 与访问域名;openssl x509 -text 查证书实际域名
浏览器"您的连接不是私密连接"自签证书 / 证书过期 / 缺中间证书自签见 A5;过期换证;缺链把中间证书拼进 fullchain
Chrome 正常、部分客户端报 unknown authorityfullchain 只有域名证书没带中间证书cat 域名.crt 中间.crt > fullchain.pem 重建证书文件
http 访问不跳 https外层没做 301 / Ingress 没配 tls外层 nginx return 301 https://...(见 A1);或 Ingress tls 段
配置 443 后无限重定向 ERR_TOO_MANY_REDIRECTSTLS 在外层终止,Ingress 又强制跳 httpsIngress 注解 ssl-redirect: "false"(见 A1 要点)
证书换了但生效的还是旧证书nginx 未 reload / 浏览器缓存nginx -s reload;无痕窗口验证;openssl s_client 看实际下发
cert-manager READY=FalseHTTP-01 挑战失败(80 不通/解析没指对)describe certificate 看 Events;放行 80;检查解析(见 A4)
ERR_SSL_VERSION_OR_CIPHER_MISMATCH证书/私钥不配对(贴错文件)比对 modulus:openssl x509 -noout -modulus vs openssl rsa -noout -modulus
nginx 报 no ssl_certificate is definedlisten 443 ssl 但没写证书两行补 ssl_certificate / ssl_certificate_key
docker push 报 authorization header is required to send HEADregistry 的 /v2/ 被 80→443 301 强跳,重定向剥掉了 Authorization80 上 /v2/ 直接反代勿 301,其余路径才跳 https(详见 Harbor 文档 FAQ ⑩.2)
💡 万能验证三板斧:curl -kv https://域名(看握手证书)→ openssl s_client -connect 域名:443 -servername 域名(看完整链)→ 看错误日志 tail -f /var/log/nginx/*error.log。Ingress 用法见 Ingress 使用文档。

B1集群内部证书体系全景 —— 谁签给谁、放在哪

💡 灵魂三问(读 B 部分前先看): ① 需要手动部署吗? —— 不用。kubeadm init 预检后第一步就自动签发全套证书(B2),零操作; ② 我的机器上默认有吗? —— 凡是能跑起来的 K8s 就一定有:kubeadm / Docker Desktop / Minikube / Rancher 装的集群都自带。验证:kubectl config view --minify 看 server 是不是 https://xxx:6443 —— 是,说明内部证书正在默默工作(你每次 kubectl 都在用它); ③ 线上可以不用吗? —— 不存在"不用"。它不是可选项,是集群强制自带的:apiserver 只提供 HTTPS(明文端口 8080 早在 v1.20 就移除了),没这套证书集群根本起不来。线上要做的不是"部署"而是"运维":备份 ca.key(B2)+ 盯到期续期(B5,组件证书默认 1 年,过期 = kubectl 连不上 = 集群瘫痪)。可选项只有两个:B3 加 SAN(kubectl 需用外网 IP/域名/LB VIP 连 6443 时才做)、B4 手动 openssl(离线/对接企业 CA 才做)。

kubeadm 部署的集群,证书全部集中在 /etc/kubernetes/pki/,由根 CA 统一签发;先把目录结构和用途看懂,后面所有操作都不慌:

/etc/kubernetes/pki/                          # 集群所有证书的家(root 权限)
├── ca.crt  ca.key                            # ★ 根 CA,其余证书全由它签(丢了=灾难,必备份)
├── apiserver.crt  apiserver.key              # ★ API Server 服务端证书(kubectl 连的就是它)
├── apiserver-kubelet-client.crt  .key        # apiserver → kubelet 的客户端证书
├── apiserver-etcd-client.crt  .key           # apiserver → etcd 的客户端证书
├── front-proxy-ca.crt                        # agg API( metrics-server 等)请求头认证 CA
├── front-proxy-client.crt  .key              # 对应客户端证书
└── etcd/
    ├── ca.crt  ca.key                        # etcd 独立 CA(与集群 CA 分开)
    ├── server.crt  server.key                # etcd 服务端证书
    ├── peer.crt  peer.key                    # etcd 节点间通信(多节点 etcd)
    └── healthcheck-client.crt  .key          # 健康检查客户端证书

/etc/kubernetes/*.conf                        # admin/controller-manager/scheduler 的 kubeconfig,
                                              #   内嵌客户端证书(续期时会一并更新)
/var/lib/kubelet/pki/                         # kubelet 自己的证书(默认开启自动轮换)
证书谁在用认证方向默认有效期
ca.crt所有组件信任的根签发者(不给别人用)10 年
apiserver.crtkubectl / kubelet / 所有客户端 → apiserver服务端(serverAuth)1 年
apiserver-kubelet-clientapiserver → kubelet(logs/exec)客户端(clientAuth)1 年
apiserver-etcd-clientapiserver → etcd客户端1 年
etcd/server.crtetcd 对外服务服务端1 年
*.conf 内嵌证书kubectl / scheduler 等控制面组件客户端1 年
kubelet 客户端证书kubelet → apiserver客户端1 年(自动轮换)
💡 记住一条主线:kubectl → apiserver.crt(服务端被验证),kubelet/scheduler → *.conf 内嵌证书(客户端被验证),全部回溯信任 ca.crt —— 所以 ca.key 必须备份且不能外泄。

B2集群安装部署 —— kubeadm 自动签发(标准方式)

用 kubeadm init 安装集群时,证书的"部署"是全自动的:init 在预检后第一步就生成根 CA 和全套组件证书,再启动各静态 Pod —— 这也是推荐方式:

# init 过程中与证书相关的两行日志:
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "apiserver" certificate and key on disk
# ↑ 此时已按默认 SAN(kubernetes/节点名/节点IP/10.96.0.1)生成证书

# 部署完成后立即确认证书状态(还能用多久一目了然)
kubeadm certs check-expiration
CERTIFICATE                EXPIRES          RESIDUAL   CERTIFICATE AUTHORITY
apiserver                  Jan 20, 2027..   364d       ca
apiserver-kubelet-client   Jan 20, 2027..   364d       ca
apiserver-etcd-client      Jan 20, 2027..   364d       etcd-ca
...
ca                         Jan 18, 2036..   3650d      -

# apiserver 是怎么用这些证书的?(静态 Pod 启动参数引用)
grep -E 'tls-cert|client-ca' /etc/kubernetes/manifests/kube-apiserver.yaml
  - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
  --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
  --client-ca-file=/etc/kubernetes/pki/ca.crt

# 指定自定义参数重装/重签时,certificatesDir 也可换位置(默认 /etc/kubernetes/pki)
⚠️ 首次部署就做备份:cp -r /etc/kubernetes /root/k8s-backup-$(date +%F) —— ca.key 一旦丢失,唯一出路是整集群重建。

B3自定义部署 —— 指定 SAN 与有效期

默认证书不含外网 IP、域名、负载均衡 VIP —— 一旦 kubectl 用这些地址连 6443 就会报 x509: certificate is valid for ..., not xxx。两种时机配置:

时机 A:全新安装时(init 前写进 kubeadm 配置)

# kubeadm-init.yaml(v1.31+ 用 v1beta4;旧版集群用 v1beta3,字段相同)
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.35.9
apiServer:
  certSANs:                       # ★ 核心字段:apiserver 证书的 SAN 列表
  - "qingfeng"                    #   节点主机名
  - "192.168.10.10"               #   节点内网 IP
  - "10.96.0.1"                   #   Service CIDR 首地址(kubectl 集群内访问)
  - "k8s.example.com"             #   自定义域名(LB/外网入口)
  - "192.168.10.100"              #   HA 集群的 LB VIP
# --- v1.27+ 还可定制签发时长(可选,默认 1 年) ---
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
certificatesExpiration:
  unitDuration: 8760h             # 单次签发时长,3650d=10年: 87600h

# 用配置文件安装
kubeadm init --config kubeadm-init.yaml

时机 B:集群已运行,追加 SAN 重签 apiserver 证书(最常用)

# 1. 把新 SAN 补进配置(或直接编辑现有 kubeadm.yaml 的 certSANs 列表)

# 2. 只重签 apiserver 证书(不动其他组件,ca.key 还在即可)
kubeadm init phase certs apiserver --config kubeadm-init.yaml
certsServedAPIServer="apiserver"  [certs] Generating "apiserver" certificate and key

# 3. 让 apiserver 重新加载证书:重启静态 Pod(改 manifest 文件触发 kubelet 拉起新容器)
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/ && sleep 20 && mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/

# 4. 验证 SAN 已包含新地址
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A1 'Alternative'
   DNS:kubernetes, DNS:qingfeng, DNS:k8s.example.com,
   IP Address:192.168.10.10, IP Address:10.96.0.1, IP Address:192.168.10.100

# 5. kubectl 用新地址连通验证
kubectl --server=https://k8s.example.com:6443 get nodes
💡 HA 集群:每个控制面节点都要执行"追加 SAN + 重启 apiserver";LB 后面的所有 master 的 apiserver 证书都应包含 LB VIP + 域名。

B4openssl 手动生成 / 替换证书

场景:离线环境、想要 10 年长证书、或对接企业自有 CA。生产仍推荐 kubeadm 托管,手动方式一般只替换个别证书。以 apiserver 为例:

# 0. 备份(动手前必做)
cp -r /etc/kubernetes/pki /root/pki-backup-$(date +%F)

# 1. 生成根 CA(已有 ca.crt/ca.key 可跳过)
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -subj "/CN=qingfeng-ca" -days 3650 -out ca.crt

# 2. 生成 apiserver 私钥 + CSR
openssl genrsa -out apiserver.key 2048
openssl req -new -key apiserver.key -subj "/CN=kube-apiserver" -out apiserver.csr

# 3. 写 SAN 扩展文件(serverAuth 用途)
cat > apiserver.ext <<EOF
subjectAltName=DNS:kubernetes,DNS:kubernetes.default,DNS:qingfeng,DNS:k8s.example.com,IP:192.168.10.10,IP:10.96.0.1,IP:127.0.0.1
extendedKeyUsage=serverAuth
EOF

# 4. 用根 CA 签发(此处签 10 年)
openssl x509 -req -in apiserver.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 3650 -extfile apiserver.ext -out apiserver.crt

# 5. 校验链 + 看 SAN + 看到期日
openssl verify -CAfile ca.crt apiserver.crt
openssl x509 -in apiserver.crt -noout -text | grep -A1 'Alternative'
openssl x509 -in apiserver.crt -noout -enddate

# 6. 替换并重启 apiserver(同 B3 时机 B 第 3 步)
mv apiserver.crt apiserver.key /etc/kubernetes/pki/
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/ && sleep 20 && mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
⚠️ 三条红线:① 别动 ca.key 的密码学属性(换了 CA = 全集群证书重签);② 客户端证书(如 *.conf 内嵌)不要手动 openssl 替换,交给 kubeadm certs renew;③ 替换后立即用 kubectl get nodes 验证,失败就回滚备份。

B5检查到期与一键续期 —— 每季度必做

# 1. 检查所有证书的剩余时间(在控制面节点执行)
kubeadm certs check-expiration
CERTIFICATE                EXPIRES          RESIDUAL   CERTIFICATE AUTHORITY
apiserver                  Jan 20, 2027     364d       ca
admin.conf                 Jan 20, 2027     364d       ca
...

# 2. 单独看某一张证书的到期日
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddate
notAfter=Jan 20 03:12:00 2027 GMT

# 3. 一键续期全部(各证书再延 1 年;需 ca.key 存在)
kubeadm certs renew all
certificate embedded in the kubeconfig file for the admin.conf updated
...

# 4. ★ renew 不会自动重启静态 Pod,必须手动让 apiserver 等重载
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/ && sleep 20 && \
mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
# (scheduler/controller-manager/etcd 同理;偷懒可逐个 crictl stop 旧容器)

# 5. kubectl 的 kubeconfig 也内嵌了旧证书,重新拷贝
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

# 6. 验证
kubectl get nodes && kubeadm certs check-expiration | head -4

B6x509 高频报错速查表

报错根因解决方案
x509: certificate has expired or is not yet valid证书真的过期 / 节点时间不同步先 ntpdate 校时;再 check-expiration + renew(B5 节)
x509: certificate is valid for ..., not xxxSAN 缺少你访问用的 IP/域名certSANs 追加后重签 (B3 时机 B)
certificate signed by unknown authoritykubeconfig 与集群 CA 不匹配(换过集群/残留旧 config)重新分发 admin.conf;检查 KUBECONFIG 变量
You must be logged in to the server (Unauthorized)kubeconfig 内嵌客户端证书过期renew all 后重新 cp admin.conf ~/.kube/config
remote error: tls: bad certificate双向认证中客户端证书缺失/不受信检查 apiserver --client-ca-file 与客户端凭证是否同 CA
kubeadm certs renew 报 missing CAca.key 被删/权限错从备份恢复 pki;无备份只能重建
metrics-server 报 x509: cannot validate certificatekubelet 自签服务端证书不被信metrics-server 加 --kubelet-insecure-tls 或签发 kubelet 服务端证书
💡 互链:权限/认证类排障总览见 问题总汇 · 第 04 节;排障三板斧(describe/logs/events)见 问题总汇 · 第 07 节。