Kubernetes The Hard Way:用 openssl 手动搭建 PKI,为全部组件签发 TLS 证书
本篇围绕 Provisioning a CA and Generating TLS Certificates 实验展开,讲解在 Kubernetes The Hard Way 教程中如何用 openssl 从零搭建一套 PKI(Public Key Infrastructure)基础设施:先创建一个自签名 Certificate Authority(CA),再为 kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy 以及 admin 用户和 service-accounts 逐一批量签发 TLS 证书,并把这些“组件级凭证”分发到 server、node-0、node-1 各台机器。读完本篇,你不仅能完整复现 CA 创建、证书批量生成与分发的每一步操作,还能理解 ca.conf 中每个 section 的 CN/OU 命名背后的 Kubernetes 授权机制,以及各组件最终在 systemd unit 中如何消费这些证书。
实验前提与整体脉络
Kubernetes 组件之间默认通过 mTLS(双向 TLS)相互认证:API Server 要求每个客户端(kubelet、controller-manager、scheduler、kube-proxy、admin)出示由同一 CA 签发的客户端证书;kube-apiserver 自己又必须出示一张包含正确 SAN 的服务端证书。在 Kubernetes The Hard Way 的 13 个 lab 中,本篇(lab 04)正是这些身份凭证的源头。它的输入输出关系是:
- 输入:lab 03 中已经配好 SSH 免密访问的三台机器(
server、node-0、node-1)和一台jumpbox,以及仓库自带的 ca.conf; - 输出:
ca.key/ca.crt一对根 CA 密钥,加上 8 份组件级*.key/*.crt/*.csr文件,全部生成在jumpbox的当前工作目录中。
所有命令都应在 jumpbox 上执行——jumpbox 是一台不承载集群组件的跳板机,专门用于集中完成证书、kubeconfig 等“凭证生产”工作。
理解 ca.conf:openssl 的“证书生产配方”
教程把复杂的 openssl 细节收敛到了一个配置文件里:ca.conf。原文档明确说,你不需要理解其中的每一行也能完成实验,但它是学习 openssl 配置管理的一张很好的地图。下面按 section 拆解它的结构:
[req]:根 CA 的自签名参数
[req]
distinguished_name = req_distinguished_name
prompt = no
x509_extensions = ca_x509_extensions
[ca_x509_extensions]
basicConstraints = CA:TRUE
keyUsage = cRLSign, keyCertSign
[req_distinguished_name]
C = US
ST = Washington
L = Seattle
CN = CA
prompt = no表示不交互式询问 DN 字段,直接取req_distinguished_name中的值,这是脚本化生成的关键;basicConstraints = CA:TRUE与keyUsage = cRLSign, keyCertSign声明这张根证书是 CA 证书,只能用于签发证书和 CRL,不能当普通 TLS 证书用;CN = CA即根 CA 的通用名。
[admin] 与各组件 section:用 CN/O 编码 Kubernetes 身份
Kubernetes 的认证(authentication)会把证书中的 DN 字段映射为 API 用户身份。各 section 的命名正是围绕这套映射来设计的:
- [admin]:
CN = admin、O = system:masters。system:masters组在 RBAC 中被预绑定为集群级管理员,持有这张证书的用户等效于 cluster-admin; - [node-0] / [node-1]:
CN = system:node:node-0(node-1 同理)、O = system:nodes。ca.conf 中的注释解释了原因:Kubernetes 有一个专门的 Node Authorizer,它只放行“用户名形如system:node:<nodeName>且属于system:nodes组”的 kubelet 请求。此外每个 worker 的扩展字段里都带有subjectAltName = DNS:<host>, IP:127.0.0.1,保证 kubelet 作为服务端(发布 10250 端口)时本地回环访问也能通过主机名校验; - [kube-proxy]:
CN = system:kube-proxy、O = system:node-proxier,对应 kube-proxy 拉取 Service/Endpoint 信息所需的身份与 RBAC 绑定; - [kube-controller-manager]:
CN = system:kube-controller-manager、O = system:kube-controller-manager; - [kube-scheduler]:
CN = system:kube-scheduler,其 DN 中的O = system:system:kube-scheduler是配置文件中的原样写法; - [service-accounts]:只有
CN = service-accounts。ca.conf 的注释说明用途:controller-manager 会用这一对密钥生成并签名 service account token; - [kube-api-server]:
CN = kubernetes,且subjectAltName = @kube-api-server_alt_names引用了独立的 SAN 列表 section。
扩展字段的三种“口味”
组件 section 的 req_extensions 分为三类,体现了“客户端证书”与“服务端证书”的差异:
| 扩展字段 | 取值 | 含义 |
|---|---|---|
basicConstraints |
CA:FALSE |
所有叶子证书均不可再签发子证书 |
extendedKeyUsage |
clientAuth(admin/service-accounts 经 default_req_extensions)或 clientAuth, serverAuth(各组件) |
限定证书用途;组件同时作为 API 客户端和节点服务端,所以需要双用途 |
keyUsage |
critical, digitalSignature, keyEncipherment |
关键用途:数字签名 + 密钥封装 |
nsCertType |
client 或 client, server(kube-api-server) |
Netscape 遗留标记,apiserver 同时承担服务端角色 |
subjectKeyIdentifier |
hash |
用公钥哈希生成 SKI,供 CA 的 authorityKeyIdentifier 链式校验 |
kube-api-server 的 SAN 清单:最容易踩坑的一处
API Server 是唯一一张服务端为主证书,它的 kube-api-server_alt_names section 列出了客户端连接时会校验的所有地址:
[kube-api-server_alt_names]
IP.0 = 127.0.0.1
IP.1 = 10.32.0.1
DNS.0 = kubernetes
DNS.1 = kubernetes.default
DNS.2 = kubernetes.default.svc
DNS.3 = kubernetes.default.svc.cluster
DNS.4 = kubernetes.svc.cluster.local
DNS.5 = server.kubernetes.local
DNS.6 = api-server.kubernetes.local
IP.1 = 10.32.0.1与DNS.0~DNS.4是为集群内部 DNS 预留的:kubernetes/defaultservice 会被分配到10.32.0.0/24(service CIDR)中的10.32.0.1;DNS.5 = server.kubernetes.local是本教程里集群的实际 FQDN(lab 03 中为server机器设置的 FQDN),后续所有 kubeconfig 的--server=https://server.kubernetes.local:6443都依赖这张证书能校验通过;IP.0 = 127.0.0.1覆盖本机回环访问。
如果部署时改了服务器 FQDN 或 Service CIDR,必须同步修改这里,否则客户端 TLS 握手会因 SAN 不匹配而失败。
创建自签名 CA:4096 位根密钥 + 十年有效期
每个 CA 都由“私钥 + 根证书”构成。本教程创建一个自签名 CA——原文档特别提醒:这对完成教程已经足够,但不应被视为真实生产环境的做法(生产上通常使用受信任的根 CA 或内部证书体系)。在 jumpbox 上执行:
{
openssl genrsa -out ca.key 4096
openssl req -x509 -new -sha512 -noenc \
-key ca.key -days 3653 \
-config ca.conf \
-out ca.crt
}
参数逐项说明:
openssl genrsa -out ca.key 4096:生成 4096 位 RSA 私钥。ca.key是整个体系中最敏感的文件,一旦泄露任何人都能签发“合法”证书;-x509:直接输出自签名的 X.509 证书而非 CSR;-sha512:根证书用 SHA-512 签名(叶子证书后面会改用 SHA-256);-noenc:私钥不加密存储,便于后续openssl x509 -CA自动读取——这也是“密钥必须严格保管”这一告诫的技术原因;-days 3653:约 10 年有效期;-config ca.conf:取[req]段的 DN 与ca_x509_extensions扩展。
执行后工作目录应出现 ca.crt 与 ca.key 两个文件:
ca.crt ca.key
批量签发 8 份客户端/服务端证书
下一步为所有组件生成“密钥 + CSR + 签发证书”三件套。先定义证书清单(数组):
certs=(
"admin" "node-0" "node-1"
"kube-proxy" "kube-scheduler"
"kube-controller-manager"
"kube-api-server"
"service-accounts"
)
然后循环调用三个 openssl 子命令:
for i in ${certs[*]}; do
openssl genrsa -out "${i}.key" 4096
openssl req -new -key "${i}.key" -sha256 \
-config "ca.conf" -section ${i} \
-out "${i}.csr"
openssl x509 -req -days 3653 -in "${i}.csr" \
-copy_extensions copyall \
-sha256 -CA "ca.crt" \
-CAkey "ca.key" \
-CAcreateserial \
-out "${i}.crt"
done
循环体内每条命令的作用:
openssl genrsa -out "${i}.key" 4096——为组件生成 4096 位 RSA 私钥;openssl req -new -key ... -config ca.conf -section ${i}——按 ca.conf 中与证书同名的 section(如[node-0]、[kube-proxy])填充 DN 和req_extensions,生成 CSR。注意文件名与 section 名必须严格一致(kube-api-server带连字符,不是下划线);openssl x509 -req ... -CA ca.crt -CAkey ca.key -CAcreateserial——用根 CA 的密钥对 CSR 签名,产出最终证书;-copy_extensions copyall是关键选项:把 CSR 中经 section 注入的扩展(SAN、extendedKeyUsage 等)完整拷贝到最终证书里,否则签出的证书会丢失 SAN,TLS 校验直接失败;-CAcreateserial创建/递增ca.srl序列号文件。
版本提示:
-copy_extensions是较新版本 openssl 才支持的选项,依赖它的前提是 openssl 版本足够新。若你的 openssl 过老,报错时需升级 openssl 或改用extfile方式手动传扩展。
执行后,8 个组件各自产出一份 .key、一份 .csr、一份 .crt。用下面命令核对产物:
ls -1 *.crt *.key *.csr
预期能看到 ca.crt/ca.key 加上 admin.*、node-0.*、node-1.*、kube-proxy.*、kube-scheduler.*、kube-controller-manager.*、kube-api-server.*、service-accounts.* 共 8 组文件。
分发证书:按“组件查找路径”投放
证书签发完成后要拷到各机器上,放到对应组件约定的查找路径。原文档强调:真实环境中这些文件等同于敏感密钥(secret),因为它们就是各组件互相认证的“身份证”。
分发到 worker 节点(node-0 / node-1)
kubelet 的证书对统一放在 /var/lib/kubelet/,注意远端会重命名为固定的 kubelet.crt/kubelet.key(每台节点各自持有 node-0/node-1 自己的那份):
for host in node-0 node-1; do
ssh root@${host} mkdir /var/lib/kubelet/
scp ca.crt root@${host}:/var/lib/kubelet/
scp ${host}.crt \
root@${host}:/var/lib/kubelet/kubelet.crt
scp ${host}.key \
root@${host}:/var/lib/kubelet/kubelet.key
done
ca.crt 同样下发,因为 kubelet 需要它作为信任根来校验 API Server 的服务端证书(后续生成 kubeconfig 时以 --certificate-authority=ca.crt 引用)。
分发到控制平面(server)
API Server、service-accounts 的证书与根 CA 密钥一起放到 server 的 home 目录(下一步 lab 会移入 /var/lib/kubernetes/):
scp \
ca.key ca.crt \
kube-api-server.key kube-api-server.crt \
service-accounts.key service-accounts.crt \
root@server:~/
为什么根 CA 的 key 也要给 server?因为 kube-controller-manager.service 中 --cluster-signing-cert-file=/var/lib/kubernetes/ca.crt 与 --cluster-signing-key-file=/var/lib/kubernetes/ca.key 需要用它来再签发 kubelet 的 Serving 证书和 service account 令牌签名密钥——CA 不只在 bootstrap 时用一次。
原文档的后续提示:
kube-proxy、kube-controller-manager、kube-scheduler与kubelet的客户端证书将在下一个 lab(Generating Kubernetes Configuration Files for Authentication)中被打包进各组件的 kubeconfig 文件。
纵深:这些证书在各组件中到底被谁消费
把证书“放对位置”只是第一步,理解 systemd unit 中的消费方式才能验证前面 DN/SAN 设计的正确性:
- API Server(units/kube-apiserver.service):
--tls-cert-file=/var/lib/kubernetes/kube-api-server.crt与--tls-private-key-file=.../kube-api-server.key让 apiserver 以 TLS 服务端身份监听 6443,客户端会按server.kubernetes.local校验 SAN;--client-ca-file=/var/lib/kubernetes/ca.crt声明“凡是本 CA 签发的客户端证书都予以信任”,这正是 lab 04 为每个组件签发同一 CA 证书的落点;--service-account-key-file=.../service-accounts.crt与--service-account-signing-key-file=.../service-accounts.key对应 ca.conf 中[service-accounts]的注释:apiserver 用这对密钥验证 controller-manager 签发的 service account token;--kubelet-client-certificate/--kubelet-client-key使用 kube-api-server 自己的证书作为客户端身份去访问 kubelet API;--kubelet-certificate-authority=ca.crt则用根 CA 校验 kubelet 的服务端证书。
- Controller Manager(units/kube-controller-manager.service):
--kubeconfig中内嵌kube-controller-manager.crt/.key(lab 05 生成),--cluster-signing-key-file=/var/lib/kubernetes/ca.key解释了 ca.key 下发到 server 的必要性。 - Kubelet(units/kubelet.service):通过
--kubeconfig=/var/lib/kubelet/kubeconfig使用 lab 05 基于node-0.crt/node-0.key生成的 kubeconfig;lab 05 中特意要求 kubelet 的 kubeconfig 使用与节点同名的证书,以匹配 Node Authorizer 的system:node:<nodeName>身份规则——这正是 ca.conf 中[node-0]section 的设计意图。 - Scheduler / kube-proxy(units/kube-scheduler.service、units/kube-proxy.service):均以
--config指向各自的 YAML 配置,其中的客户端凭证即本 lab 签发的system:kube-scheduler、system:kube-proxy证书。 - 从源码结构看,etcd 是例外:units/etcd.service 中 etcd 监听
http://127.0.0.1:2379/2380,走本机回环明文 HTTP,不参与 TLS 体系,因此 lab 04 没有为 etcd 签发证书。
验证与收尾清单
完成本 lab 后,可对照以下清单自检,任何一项缺失都会让后续 lab 05~09 启动失败:
jumpbox工作目录含ca.key、ca.crt及 8 组*.key/*.csr/*.crt,可用ls -1 *.crt *.key *.csr核对;- 抽查证书 DN 是否与 ca.conf 对应 section 一致,例如
openssl x509 -in node-0.crt -noout -subject -ext subjectAltName,应看到CN = system:node:node-0, O = system:nodes及DNS:node-0, IP:127.0.0.1; node-0、node-1的/var/lib/kubelet/下各有ca.crt、kubelet.crt、kubelet.key;server的 home 目录含ca.key、ca.crt、kube-api-server.crt/.key、service-accounts.crt/.key;- 牢记 README 的定位:该教程面向学习而非生产,自签名 CA + 明文传输的私钥分发方式仅适合学习环境。
完成以上步骤后,即可进入下一个实验 Generating Kubernetes Configuration Files for Authentication,把本 lab 签发的各组件客户端证书封装成 kubelet、kube-proxy、scheduler、controller-manager 与 admin 的 kubeconfig 文件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00