首页
/ Kubernetes The Hard Way:用 openssl 手动搭建 PKI,为全部组件签发 TLS 证书

Kubernetes The Hard Way:用 openssl 手动搭建 PKI,为全部组件签发 TLS 证书

2026-09-05 11:29:26作者:柏廷章Berta

本篇围绕 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 证书,并把这些“组件级凭证”分发到 servernode-0node-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 免密访问的三台机器(servernode-0node-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:TRUEkeyUsage = cRLSign, keyCertSign 声明这张根证书是 CA 证书,只能用于签发证书和 CRL,不能当普通 TLS 证书用;
  • CN = CA 即根 CA 的通用名。

[admin] 与各组件 section:用 CN/O 编码 Kubernetes 身份

Kubernetes 的认证(authentication)会把证书中的 DN 字段映射为 API 用户身份。各 section 的命名正是围绕这套映射来设计的:

  • [admin]CN = adminO = system:masterssystem:masters 组在 RBAC 中被预绑定为集群级管理员,持有这张证书的用户等效于 cluster-admin;
  • [node-0] / [node-1]CN = system:node:node-0(node-1 同理)、O = system:nodesca.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-proxyO = system:node-proxier,对应 kube-proxy 拉取 Service/Endpoint 信息所需的身份与 RBAC 绑定;
  • [kube-controller-manager]CN = system:kube-controller-managerO = system:kube-controller-manager
  • [kube-scheduler]CN = system:kube-scheduler,其 DN 中的 O = system:system:kube-scheduler 是配置文件中的原样写法;
  • [service-accounts]:只有 CN = service-accountsca.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 clientclient, 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.1DNS.0~DNS.4 是为集群内部 DNS 预留的:kubernetes/default service 会被分配到 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.crtca.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

循环体内每条命令的作用:

  1. openssl genrsa -out "${i}.key" 4096——为组件生成 4096 位 RSA 私钥;
  2. openssl req -new -key ... -config ca.conf -section ${i}——按 ca.conf与证书同名的 section(如 [node-0][kube-proxy])填充 DN 和 req_extensions,生成 CSR。注意文件名与 section 名必须严格一致(kube-api-server 带连字符,不是下划线);
  3. 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-proxykube-controller-managerkube-schedulerkubelet 的客户端证书将在下一个 lab(Generating Kubernetes Configuration Files for Authentication)中被打包进各组件的 kubeconfig 文件。

纵深:这些证书在各组件中到底被谁消费

把证书“放对位置”只是第一步,理解 systemd unit 中的消费方式才能验证前面 DN/SAN 设计的正确性:

  • API Serverunits/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 Managerunits/kube-controller-manager.service):--kubeconfig 中内嵌 kube-controller-manager.crt/.key(lab 05 生成),--cluster-signing-key-file=/var/lib/kubernetes/ca.key 解释了 ca.key 下发到 server 的必要性。
  • Kubeletunits/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-proxyunits/kube-scheduler.serviceunits/kube-proxy.service):均以 --config 指向各自的 YAML 配置,其中的客户端凭证即本 lab 签发的 system:kube-schedulersystem:kube-proxy 证书。
  • 从源码结构看,etcd 是例外:units/etcd.service 中 etcd 监听 http://127.0.0.1:2379/2380,走本机回环明文 HTTP,不参与 TLS 体系,因此 lab 04 没有为 etcd 签发证书。

验证与收尾清单

完成本 lab 后,可对照以下清单自检,任何一项缺失都会让后续 lab 05~09 启动失败:

  1. jumpbox 工作目录含 ca.keyca.crt 及 8 组 *.key/*.csr/*.crt,可用 ls -1 *.crt *.key *.csr 核对;
  2. 抽查证书 DN 是否与 ca.conf 对应 section 一致,例如 openssl x509 -in node-0.crt -noout -subject -ext subjectAltName,应看到 CN = system:node:node-0, O = system:nodesDNS:node-0, IP:127.0.0.1
  3. node-0node-1/var/lib/kubelet/ 下各有 ca.crtkubelet.crtkubelet.key
  4. server 的 home 目录含 ca.keyca.crtkube-api-server.crt/.keyservice-accounts.crt/.key
  5. 牢记 README 的定位:该教程面向学习而非生产,自签名 CA + 明文传输的私钥分发方式仅适合学习环境。

完成以上步骤后,即可进入下一个实验 Generating Kubernetes Configuration Files for Authentication,把本 lab 签发的各组件客户端证书封装成 kubelet、kube-proxy、scheduler、controller-manager 与 admin 的 kubeconfig 文件。

登录后查看全文
热门项目推荐
相关项目推荐