kOps 权威指南:kops create keypair —— keyset 证书注入、CA 轮换与源码级原理剖析
【免费下载链接】kopsKubernetes Operations (kOps) - Production Grade k8s Installation, Upgrades and Management项目地址: https://gitcode.com/gh_mirrors/kop/kops
kops create keypair是 kOps 中向集群 keyset(密钥集)添加 CA 证书与私钥的核心命令,也是实施自定义 CA、CA 轮换与 service-account 密钥管理等安全操作的入口。本文将以其官方 CLI 文档 docs/cli/kops_create_keypair.md 为骨架,结合 cmd/kops/create_keypair.go 与 upup/pkg/fi/ca.go 等源码实现,完整讲解命令语法、参数语义、四类输入组合行为、all批量轮换模式,以及底层的 keyset 模型与证书签发原理,帮助你理解并能直接上手使用该命令。
命令概述:向 keyset 添加 CA 证书与私钥
kops create keypair的作用是向一个指定的 keyset 添加一对 CA 证书与私钥。其官方 Synopsis 描述如下:
Add a CA certificate and private key to a keyset.
kOps 将集群的 PKI 材料(CA 证书、私钥、service-account 密钥等)组织为若干个keyset,每个 keyset 内含一个或多个证书/私钥对(称为 keypair item)。当集群需要更换 CA、引入外部 CA、或者轮换 service-account 签名密钥时,就通过本命令向对应 keyset 注入新的 keypair。
命令基本语法:
kops create keypair {KEYSET | all} [flags]该命令是kops create的子命令之一,与create cluster、create instancegroup、create secret、create sshpublickey并列,注册于 cmd/kops/create.go 中。其定位与create cluster等资源创建命令不同:keypair 属于 PKI 密钥材料,不能通过 YAML 配置文件创建(kops create的文档明确注明 "keypairs and secrets cannot be created from YAML config files yet")。
核心概念:keyset、primary 与可轮换 keyset
理解本命令之前,需要先掌握三个关键概念,它们都直接体现在源码数据结构中。
keyset 与 KeysetItem
在 upup/pkg/fi/ca.go 中,Keyset是一个由多个KeysetItem(证书/私钥对)组成的集合:
// Keyset is a parsed api.Keyset. type Keyset struct { LegacyFormat bool Items map[string]*KeysetItem // Primary is the KeysetItem that is considered the "active" key. // It is guaranteed to be non-nil, if there are any keypairs. Primary *KeysetItem } // KeysetItem is a certificate/key pair in a Keyset. type KeysetItem struct { Id string DistrustTimestamp *time.Time Certificate *pki.Certificate PrivateKey *pki.PrivateKey }也就是说,一个 keyset 内部可以同时保存多个 keypair(例如新旧两代 CA),每个 keypair 有唯一Id(由证书序列号派生)、可选的DistrustTimestamp(吊销/不信任时间戳)、证书与私钥。Keyset.Primary指向当前"活跃"的那一个。
Primary:真正负责签发的那个 keypair
文档明确说明:
One of the certificate/private key pairs in each keyset must be primary. The primary keypair is the one used to issue certificates (or, for the "service-account" keyset, service-account tokens). As a consequence, a keypair added to an empty keyset must be made primary.
每个 keyset 中必须有一个 primary keypair,它是实际用于签发证书(对service-accountkeyset 而言则是签发 service-account token)的密钥。因此:
- 向空 keyset 添加的第一个 keypair 必须标记为 primary;
- 非 primary 的 keypair 是备用/轮换中的候选,等待未来被 promote。
rotatableKeysetFilter:哪些 keyset 允许被操作
并非所有 keyset 都支持通过本命令添加 keypair。cmd/kops/create_keypair.go 中的过滤器(rotatableKeysetFilter,见该文件第 86-88 行)定义了"可轮换 keyset"的判定规则:
func rotatableKeysetFilter(name string, _ *fi.Keyset) bool { return name == "all" || name == "service-account" || strings.Contains(name, "-ca") }即:keyset 名称为service-account,或者以-ca结尾(如kubernetes-ca、kubelet-ca),或者特殊值all,才被视为可轮换并允许注入新 keypair。若对其它 keyset 执行命令,RunCreateKeypair会直接报错:
adding keypair to "xxx" is not supported命令语法与全部参数
kops create keypair {KEYSET | all} [flags]专属选项
| 选项 | 类型 | 说明 |
|---|---|---|
--cert string | String | CA 证书文件的路径 |
--key string | String | CA 私钥文件的路径 |
--primary | Bool | 将该 keypair 设为签发证书所用的 primary keypair |
-h, --help | Bool | 显示帮助 |
三个专属 flag 都在 cmd/kops/create_keypair.go 第 138-140 行注册:
cmd.Flags().StringVar(&options.CertPath, "cert", options.CertPath, "Path to CA certificate") cmd.Flags().StringVar(&options.PrivateKeyPath, "key", options.PrivateKeyPath, "Path to CA private key") cmd.Flags().BoolVar(&options.Primary, "primary", options.Primary, "Make the keypair the one used to issue certificates")继承自父命令的全局选项
与其它 kops 命令一致,本命令还继承以下全局选项:
| 选项 | 说明 |
|---|---|
--name string | 集群名称,覆盖KOPS_CLUSTER_NAME环境变量 |
--state string | state 存储位置(kops config 文件),覆盖KOPS_STATE_STORE环境变量 |
--config string | YAML 配置文件(默认$HOME/.kops.yaml) |
-v, --v Level | 日志级别 verbosity |
--alsologtostderrthreshold severity等 | klog 相关日志参数 |
--name是必填的:命令的Args校验逻辑(第 99-129 行)在集群名为空时直接返回--name is required。
四类输入组合与行为规则
文档的核心逻辑围绕"证书/私钥是否提供"展开,共四种组合,分别对应不同的行为:
1. 两者都不提供:自动生成自签名 CA
If neither a certificate nor a private key is provided, a new self-signed certificate and private key will be generated.
此时命令会自动生成一对全新的自签名证书与私钥。对应源码(createKeypair函数):
- 先通过
pki.GeneratePrivateKey()生成 RSA 私钥(见下文"私钥生成"); - 再以
pki.BuildPKISerial(time.Now().UnixNano())生成序列号,构造pki.IssueCertRequest,其中Type: "ca"、Subject.CommonName为 keyset 名称,调用pki.IssueCert签发自签名 CA 证书。
2. 只提供私钥:由私钥推导自签名证书
If no certificate is provided but a private key is, a self-signed certificate will be generated from the provided private key.
当--key给出但--cert未给出时,命令读取并解析用户私钥(pki.ParsePEMPrivateKey),然后用该私钥生成对应的自签名证书——证书的公钥对应该私钥,保证证书与私钥匹配。
3. 只提供证书:仅注入证书、无私钥
If a certificate is provided but no private key is, the certificate will be added to the keyset without a private key. Such a certificate cannot be made primary.
当--cert给出但--key未给出时,仅把该 CA 证书加入 keyset,不附带私钥。这类"证书-only"条目无法成为 primary(primary 必须持有私钥才能签发证书)。upup/pkg/fi/ca.go 的AddItem对此有强校验:
if privateKey == nil && primary { return item, fmt.Errorf("private key not provided for primary item") }典型使用场景:导入外部/公司 CA 的公钥证书用于信任链构建,但私钥由外部 CA 保管、不落入 kOps state store。
4. 两者都提供:注入完整外部 CA
kops create keypair kubernetes-ca \ --cert ~/ca.pem --key ~/ca-key.pem \ --name k8s-cluster.example.com --state s3://my-state-store这是最典型的"接入自有 CA"场景:读取~/ca.pem与~/ca-key.pem(内部会执行utils.ExpandPath展开~),分别经pki.ParsePEMCertificate/pki.ParsePEMPrivateKey解析后写入 keystore。命令成功后输出:
using user provided cert: /home/user/ca.pem using user provided private key: /home/user/ca-key.pem Created kubernetes-ca <id>实战:为所有可轮换 keyset 批量生成次级密钥
If the keyset is specified as "all", a newly generated secondary certificate and private key will be added to each rotatable keyset.
当第一个参数为all时,命令会遍历 keystore 中所有 keyset,对每个可轮换 keyset 自动生成一对全新的次级(secondary)证书与私钥并加入:
kops create keypair all \ --name k8s-cluster.example.com --state s3://my-state-store这正是集群 CA 轮换的标准前置步骤。其源码路径(RunCreateKeypair):
keyStore.ListKeysets()列出全部 keyset;- 对每个 keyset 用
rotatableKeysetFilter过滤; - 逐个调用
createKeypair生成并写入次级 keypair。
之后可用kops promote keypair将新生成的次级 keypair 提升为 primary,完成切换,具体流程可参考 kops_promote_keypair 与 kops_distrust_keypair 文档。
注意:all模式不能与--cert、--key、--primary同时使用,否则会报错,例如:
cannot specify --cert with "all" cannot specify --primary with "all"同时,命令每次只能操作一个 keyset(can only add to one keyset at a time),即kops create keypair ca-a ca-b这样的多 keyset 写法是不被允许的。
源码级原理:从参数到写入 keystore 的完整链路
createKeypair的完整执行流程(cmd/kops/create_keypair.go 第 186-268 行):
- 解析私钥:若
--key非空,展开路径、os.ReadFile读取、pki.ParsePEMPrivateKey解析; - 确定证书来源:
- 若
--cert为空:若还没有私钥则先生成私钥,随后用pki.IssueCert以Type: "ca"签发自签名证书(Subject.CommonName为 keyset 名,序列号由时间戳 + 随机数构成); - 若
--cert非空:读取文件并pki.ParsePEMCertificate解析;
- 若
- 定位 keyset:
keyStore.FindKeyset(ctx, name);- keyset 不存在时:若指定了
--primary,则fi.NewKeyset(cert, privateKey)创建新 keyset(首个条目自动为 primary);否则报错the first keypair added to a keyset must be primary; - keyset 已存在时:调用
keyset.AddItem(cert, privateKey, options.Primary)追加条目;
- keyset 不存在时:若指定了
- 持久化:
keyStore.StoreKeyset(ctx, name, keyset)写回 state store; - 输出结果:打印
Created <keyset> <id>。
其中AddItem还有两个值得注意的细节(upup/pkg/fi/ca.go 第 199-246 行):
- 不能向空 keyset 添加 secondary:
!primary && k.Primary == nil时报错cannot add secondary item when no existing primary item; - 条目 Id 与序列号的关系:新条目的 Id 基于
pki.BuildPKISerial(time.Now().UnixNano())生成,并保证不高于现有条目的序列号(确保"新条目 Id 更大/更新"),且 primary 的 Id 不会低于已有条目;纯证书条目(无私钥)的 Id 会被安排在 primary 之前。
私钥生成细节
pkg/pki/privatekey.go 中GeneratePrivateKey默认生成RSA 2048 位私钥(DefaultPrivateKeySize = 2048),并支持通过环境变量KOPS_RSA_PRIVATE_KEY_SIZE覆盖密钥长度:
var DefaultPrivateKeySize = 2048 ... if os.Getenv("KOPS_RSA_PRIVATE_KEY_SIZE") != "" { rsaKeySize = int(v) } rsaKey, err := rsa.GenerateKey(crypto_rand.Reader, rsaKeySize)证书序列号生成
pkg/pki/csr.go 的BuildPKISerial将纳秒时间戳左移 32 位后与 32 位加密随机数做或运算,得到一个"极难碰撞且随创建时间单调递增"的序列号:
serial := big.NewInt(timestamp) serial.Lsh(serial, 32) serial.Or(serial, randomComponent)自签名 CA 的签发
pkg/pki/issue.go 的IssueCert在收到Type: "ca"的请求时,会把证书模板的IsCA置为true并自行签名(不需要 keystore 中的签发者),从而产出符合 CA 语义的自签名根证书;而签发非 CA 证书时则会从 keystore 中查找 primary keypair 作为签发者——这也解释了为什么 primary 必须持有私钥。
常见报错与排查要点
| 错误信息 | 原因 |
|---|---|
--name is required | 未通过--name或KOPS_CLUSTER_NAME提供集群名 |
must specify name of keyset to add keypair to | 缺少{KEYSET \| all}位置参数 |
can only add to one keyset at a time | 传入了多个 keyset 名称 |
adding keypair to "xxx" is not supported | 目标 keyset 不满足-ca后缀 /service-account/all过滤条件 |
cannot specify --cert with "all"等 | all模式下误传了--cert/--key/--primary |
the first keypair added to a keyset must be primary | 向不存在的 keyset 添加 keypair 时未指定--primary |
private key not provided for primary item | 只提供证书却试图用--primary标记(或新 keyset 首条目无私钥) |
cannot add secondary item when no existing primary item | keyset 为空(或 Primary 为 nil)时添加非 primary 条目 |
与相关命令的协同:完整的密钥生命周期
kops create keypair只是 kOps 密钥管理链路的一环,围绕 keyset 还提供:
- kops promote keypair:将 keyset 中的某个 keypair 提升为 primary;
- kops distrust keypair:给 keypair 打上
DistrustTimestamp,使其不再受信任; - kops get keypairs:列出/查看集群 keyset 及其 keypair;
- kops trust keypair:撤销 distrust(重新信任)。
典型轮换流程为:create keypair(生成次级)→promote keypair(切换 primary)→ 滚动更新集群 →distrust keypair(吊销旧密钥)。此外,常见的kubernetes-cakeyset 名称在 upup/pkg/fi/ca.go 中被定义为常量CertificateIDCA = "kubernetes-ca",是集群主 CA 的固定标识。
总结
kops create keypair通过一条命令优雅地解决了 kOps 集群 PKI 的三大诉求:注入外部 CA(证书+私钥)、信任外部证书(仅证书、非 primary)、自动化 CA 轮换(all批量生成次级密钥)。理解其背后的 keyset/primary 模型、rotatableKeysetFilter过滤规则以及"首个条目必须 primary"的约束,就能在实际运维中安全、正确地管理集群证书生命周期。本文所有命令与参数说明均以当前仓库的 docs/cli/kops_create_keypair.md 及 cmd/kops/create_keypair.go 为准,可据此查阅更完整的代码细节。
【免费下载链接】kopsKubernetes Operations (kOps) - Production Grade k8s Installation, Upgrades and Management项目地址: https://gitcode.com/gh_mirrors/kop/kops
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考