news 2026/9/10 14:18:26

统一CA签发etcd、K8s与Harbor证书的生产实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
统一CA签发etcd、K8s与Harbor证书的生产实践

干这行久了就会遇到一个共同的问题:生产环境里不是只有一套系统需要证书,etcd要、K8s要、Harbor私有仓库也要,而绝大多数安装文档都是“各签各的”,最后搞出一堆互不信任的孤儿证书。本文就围绕“用同一套CA把etcd、K8s、Harbor的证书全部统一签发”这条主线,把生产环境证书体系怎么设计、怎么落地、怎么排障讲透,适合正在搭集群、或者已经被证书过期整怕了的运维朋友。

我自己在搭建K8s集群时,最头疼的从来不是组件本身,而是证书关系。etcd的peer证书要包括三台节点地址,apiserver的SAN要把VIP、域名、Pod网段都列进去,Harbor那边又得让每台工作节点的docker或者containerd信任它。如果你单独看每个系统的安装文档,都能装起来,但把它们串在一起,信任链就断了。所以后来我干脆花了一天时间,把整套证书体系重新理顺,用一套私有CA签发所有证书,一次解决问题。

1. 证书设计:为什么生产环境必须自建CA

1.1 一套CA vs 每个系统各签各的

接触过的生产项目里,很多兄弟图省事,etcd用etcd自带脚本签一套,K8s用kubeadm生成一套,Harbor又自己生成一套,结果是每个服务跑起来都正常,但只要出现跨组件调用,就疯狂报x509: certificate signed by unknown authority。最经典的场景就是:K8s的apiserver要连etcd,可是apiserver拿的CA证书和etcd自己签发的CA压根不是一家,哪怕你把etcd证书路径全配对了,仍然握手失败。

所以我的结论很明确:生产环境必须自建一个私有CA,所有的etcd证书、K8s组件证书、Harbor证书都由这同一个CA来签发。这样整个集群的信任链只有一条,任何一个组件校验另一个组件时,都能往上追到同一个根证书,不存在“互相不认识”的问题。而且后续如果要做证书轮转,也只需要替换对应服务的证书,其他组件不用动。

有人会问,直接用公共CA签不行吗?公共CA签的证书肯定没这个信任问题,但第一,内网域名和IP地址没法在公共CA买到有效的证书,第二,etcd peer证书这种特殊用途的证书,公共CA根本不会签。所以建内网私有CA是必经之路。

1.2 证书目录与有效期规划

建CA之前,先把目录结构想清楚。我习惯用一个独立目录管理所有证书,不要把证书散落在各个组件的目录里,不然期限一到,你都不知道哪个目录里还有一张没轮转的证书。

我推荐的目录结构是这样的:

/pki ├── ca/ │ ├── ca.key # 根CA私钥,权限0600 │ ├── ca.crt # 根CA证书 │ └── openssl.cnf # 统一的openssl配置 ├── etcd/ │ ├── etcd-ca.crt │ ├── server.crt │ ├── server.key │ ├── peer.crt │ ├── peer.key │ ├── client.crt │ └── client.key ├── k8s/ │ ├── apiserver.crt │ ├── apiserver.key │ ├── kubelet.crt │ ├── kubelet.key │ ├── admin.crt │ ├── admin.key │ └── ... └── harbor/ ├── harbor.crt └── harbor.key

有效期规划上,我给出的建议是:根CA签发10年,服务端证书3年,etcd这类内部组件可以签5年。不要所有证书都签10年,因为一旦私钥泄露,10年的撤销周期会让人崩溃;但签1年又太短,生产环境频繁轮转容易出问题。3到5年是一个比较平衡的区间。整个证书目录备份一份放到离线存储里,根CA的私钥我建议直接放离线环境生成,不碰生产网络。

提示:根CA私钥是全局最高权限,一定要设置文件权限为600,并且严格执行离线备份。这玩意儿一旦丢了,整个集群所有证书都得重新签发,那场面我经历过一次,绝对不想再经历。

2. 根CA与openssl配置:后续所有证书的地基

2.1 openssl.cnf 的关键配置

很多教程生成的证书就是在命令行里直接带一个-subj,然后顺手一签就完事,但生产环境的证书一定要有SAN,CN字段在现代浏览器和K8s校验机制里基本退居二线了。我不建议在每次签发时都手写一长串-addext,更靠谱的做法是准备一个统一的openssl.cnf,把CA的扩展规则和请求规则都定义好。

我平时用的openssl.cnf大概长这样:

[ req ] default_bits = 2048 default_keyfile = privkey.pem distinguished_name = req_distinguished_name req_extensions = req_ext x509_extensions = v3_ca prompt = no [ req_distinguished_name ] C = CN ST = BeiJing L = BeiJing O = MyCompany CN = MyCompany-Private-CA [ req_ext ] subjectAltName = @alt_names [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:TRUE keyUsage = critical, keyCertSign, cRLSign [ v3_req ] basicConstraints = CA:FALSE keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @alt_names

这里有个细节要注意:v3_req里的extendedKeyUsage我直接写成了serverAuth, clientAuth,这样同一张证书既能当服务端证书用,也能当客户端证书用。etcd的server、peer、client三张证书,还有K8s的apiserver证书,其实都有双向校验的场景,如果你在签发时只写了一个用途,后面组件之间互相调用时就会因为EKU不匹配报错。

alt_names这个小节不用放在配置文件里,每次签发时可以用环境变量或者在命令行通过-extensions v3_req -config 配置文件配合实现。我更习惯的做法是:在同一个openssl.cnf里维护多个alt_names段,比如说[ etcd_alt_names ][ apiserver_alt_names ][ harbor_alt_names ],每次用-extensions指定对应段落,配置文件一次到位,方便复用。

2.2 生成根CA的实际命令与安全要求

配置文件准备到位后,生成根CA就很简单了。我用的命令是:

cd /pki/ca openssl genrsa -out ca.key 4096 chmod 600 ca.key openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \ -config openssl.cnf -extensions v3_ca \ -subj "/C=CN/ST=BeiJing/L=BeiJing/O=MyCompany/CN=MyCompany-Private-CA"

注意这里-extensions v3_ca指定了CA证书扩展,basicConstraints=critical, CA:TRUE是关键,少了这个,签出来的证书只是一个普通证书,不能拿去签发其他证书。-days 3650是10年,根CA长一点没关系,配套的安全措施到位就行。

生成之后可以先看一眼证书内容确认没问题:

openssl x509 -in ca.crt -noout -text | grep -A 2 "Basic Constraints"

只要看到CA:TRUE,根CA就算正式就绪。接下来所有etcd、K8s、Harbor证书都会用这一套根CA来签发。

3. etcd证书签发:最容易踩坑的一环

3.1 etcd三类证书的角色与SAN

etcd证书是这套体系里最容易被忽略的。很多K8s集群故障案例,Root Cause都是etcd证书的SAN没配全。etcd那里一共有三类证书,分别对应不同的通信场景:

证书类型使用方用途关键SAN
server证书etcd服务端接收客户端(如apiserver)的TLS连接本机IP、127.0.0.1、etcd集群域名
peer证书etcd节点间集群内节点之间互相通信所有etcd节点IP、hostname
client证书etcd客户端apiserver等客户端连接etcd时出示无特殊要求,CN建议为etcd-client

很多人只给server证书配了本机IP和127.0.0.1,结果etcd集群三个节点之间通信时,peer证书里的SAN没有包含另外两个节点地址,节点之间互相握手直接失败,集群一直报etcdserver: request timed out。这个坑我踩过之后才明白:etcd的peer证书SAN必须把所有参与集群的节点IP和hostname都列上去,一个都不能漏。

如果用的是三节点etcd集群,节点IP假设是10.0.1.11、10.0.1.12、10.0.1.13,那peer证书的SAN就要同时包含这三个IP。server证书按节点各签各的,每台机器只需要有自己的IP和127.0.0.1就行。client证书不用写SAN,因为它是主动发起连接的一方。

3.2 实际签发流程与节点验证

我这边为了省事,用了一个快速的签发方式:先生成私钥和CSR,再用根CA签发。以etcd server证书为例,完整命令如下:

cd /pki/etcd openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr \ -subj "/CN=etcd-server" \ -reqexts etcd_server -config /pki/ca/openssl.cnf openssl x509 -req -in server.csr -CA /pki/ca/ca.crt -CAkey /pki/ca/ca.key \ -CAcreateserial -out server.crt -days 1825 -sha256 \ -extfile /pki/ca/openssl.cnf -extensions etcd_server

注意这里的-extfile指定了那个统一配置文件,-extensions etcd_server指定配置里的SAN段。-CAcreateserial会自动生成一个ca.srl序列号文件,这个文件别删,每次签发的证书序列号都靠它维护,如果丢了而证书又恰好有吊销需求,会非常麻烦。

peer证书签发方式类似,只是把CN改成etcd-peer,SAN段改成包含三个节点地址。签发完成之后,先不要急着把证书配到etcd里,先用openssl验证一下SAN是否完整:

openssl x509 -in peer.crt -noout -ext subjectAltName

输出里应该能看到所有节点IP。这一步验证几十秒的事,却能在集群启动的时候替你节省好几个小时的排查时间。

实操心得:etcd集群的三个节点可以使用同一张peer证书,因为peer证书的SAN已经包含了所有节点地址,复制到每台机器上即可。server证书则建议各节点单独生成,各写各的本机IP,避免某台机器IP变更时全部受影响。

4. Kubernetes组件证书签发:把CM、kubelet、apiserver一次搞定

4.1 K8s证书清单与SAN覆盖

K8s的证书体系比etcd复杂一些。手动负责签发的证书主要集中在apiserver、kubelet、controller-manager、scheduler、admin等几个角色。我列了一张自己在生产环境实际使用的清单:

证书组件类型关键SAN/CN
apiserver.crtkube-apiserverservermaster节点IP、集群VIP、service网段首个IP、kubernetes.default等域名
kubelet.crtkubeletserver/client本节点IP、hostname
admin.crtkubectlclientCN=kubernetes-admin
controller-manager.crtcontroller-managerclientCN=system:kube-controller-manager
scheduler.crtschedulerclientCN=system:kube-scheduler

最容易被卡住的是apiserver证书的SAN。很多人只写了master节点的IP和域名,但忽略了apiserver还要被Service网段的虚拟IP访问,也就是kubernetes.default.svc这个内部域名对应的ClusterIP。如果不把这个IP和域名写进SAN,集群内部访问apiserver时就会不断报证书校验失败。

在签发apiserver证书时,我一般会在SAN里放以下内容:

[ apiserver_alt_names ] DNS.1 = kubernetes DNS.2 = kubernetes.default DNS.3 = kubernetes.default.svc DNS.4 = kubernetes.default.svc.cluster.local IP.1 = 10.96.0.1 IP.2 = 192.168.20.10 IP.3 = 127.0.0.1

10.96.0.1是K8s默认Service网段的第一个地址,如果你们的Service网段改了,记得对应修改。192.168.20.10是apiserver所在节点的实际IP,如果有负载均衡VIP也要加进去。这个SAN配置是集群能不能正常初始化的关键。

4.2 用已有CA让kubeadm继续签发的两种做法

标题里提到的“利用已有ca证书让k8s签发”,在生产里非常实用。K8s的kubeadm默认会在初始化时生成一套全新的CA,但如果你已经有一套私有CA,想把K8s纳入统一信任链,就需要让kubeadm用已有CA来签发。方法很简单:在跑kubeadm init之前,把已有的ca.crtca.key放进K8s的pki目录,kubeadm检测到已有CA后就不会重新生成。

具体做法:

mkdir -p /etc/kubernetes/pki cp /pki/ca/ca.crt /etc/kubernetes/pki/ca.crt cp /pki/ca/ca.key /etc/kubernetes/pki/ca.key chmod 600 /etc/kubernetes/pki/ca.key kubeadm init --config kubeadm-init.yaml

这样kubeadm在初始化时,会用已有的CA去签发apiserver、kubelet等所有组件证书。但这里有一个坑:如果用外部CA,apiserver调用kubelet时,kubelet的默认CA路径仍然指向/etc/kubernetes/pki/ca.crt,这个文件必须在所有节点上保持一致。换言之,只要所有节点用同一个ca.crt,信任链就是通的。

另一种做法更灵活:既然已经有CA,且etcd的证书也已经由同一个CA签发,那么在kubeadm的配置文件里就要明确告诉kubeadm,etcd的CA路径和我们现有的CA是同一个。这个在kubeadm-init.yaml里可以通过指定etcd.external配置来实现,如果使用外部etcd,需要把etcd的ca文件、cert文件路径显式写出来,否则apiserver连etcd时校验不过。

apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 controlPlaneEndpoint: "192.168.20.10:6443" etcd: external: endpoints: - https://10.0.1.11:2379 - https://10.0.1.12:2379 - https://10.0.1.13:2379 caFile: /etc/kubernetes/pki/etcd/ca.crt certFile: /etc/kubernetes/pki/etcd/client.crt keyFile: /etc/kubernetes/pki/etcd/client.key

这里etcd的client证书也必须是同一个CA签发的,如果etcd的CA和K8s的CA不是同一个,上面这段配置就是白搭,集群初始化一定会报连接etcd失败。

5. Harbor自签证书简化签发:一张证书覆盖所有访问入口

5.1 Harbor证书需求拆解

Harbor的证书需求看起来不复杂,但实际上有一个容易被坑的点:Harbor要同时被开发人员、CI/CD系统、K8s工作节点访问,而访问入口可能是一个域名,也可能是多个域名加IP的混合体。你不可能让开发浏览器提示不信任,也不可能让每台机器单独配一套证书,最合理的方案就是签一张SAN证书,把使用到的所有入口全部放到SAN里。

比如说Harbor的访问域名是harbor.example.com,同时还有一个内网IP10.10.20.5,那SAN就要同时包含两者:

[ harbor_alt_names ] DNS.1 = harbor.example.com DNS.2 = ml.example.com IP.1 = 10.10.20.5

这样开发访问harbor.example.com、CI系统走内网IP访问,全部用同一张证书,不用单独给每个入口签证书。签发命令和上面基本一致,证书用途还是serverAuthclientAuth都加上,因为有的CI工具在push镜像时会进行双向TLS校验。

5.2 Harbor端配置与客户端信任(docker/containerd)

Harbor侧配置很简单,修改harbor.yml,把https证书路径指向你签好的证书和私钥:

https: port: 443 certificate: /data/cert/harbor.crt private_key: /data/cert/harbor.key

然后重新运行./install.sh或者只重新prepare并重启nginx容器。这里有个细节:Harbor所有节点的证书更新后,要记得重启Harbor的核心容器,因为有的环境里nginx容器会缓存旧证书,你改了文件它不一定生效。

真正麻烦的是客户端信任。Linux上的docker默认不信任任何私有CA,你需要把CA证书放到docker的证书目录里:

mkdir -p /etc/docker/certs.d/harbor.example.com cp /pki/ca/ca.crt /etc/docker/certs.d/harbor.example.com/ca.crt systemctl restart docker

注意这个目录名必须和Harbor的访问域名完全一致,如果你用IP访问,目录名就要写成IP。docker会直接按目标地址去查找对应目录下的证书文件,目录名对不上,它就认为你没有配置信任关系。

K8s工作节点如果是用containerd拉取镜像,一般新版containerd支持CRI风格的证书目录配置,放在/etc/containerd/certs.d/harbor.example.com/ca.crt,同时需要在/etc/containerd/config.toml里启用:

version = 2 [plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.example.com".tls] ca_file = "/etc/containerd/certs.d/harbor.example.com/ca.crt"

如果不配置这一步,K8s工作节点从Harbor拉取镜像时会直接报x509: certificate signed by unknown authority

注意:如果你在K8s节点同时装了docker和containerd,两边都要配。别只配了docker,结果工作节点用的是containerd,镜像拉不下来还一脸懵。我在实际环境里就被这种“双运行时”配置坑过,排查了将近半天。

6. 常见问题与排查技巧实录

6.1 证书问题排查速查表

这节把我在实际运维中碰到的证书问题整理成了速查表,遇到类似报错可以直接对照。

报错信息可能原因快速处理
x509: certificate signed by unknown authority对方不信任当前CA把根CA证书分发到所有组件/节点的信任目录
x509: certificate is valid for xxx, not yyySAN不匹配重新签发证书,把访问域名和IP全部加入SAN
certificate has expired or is not yet valid证书过期/时钟漂移检查证书有效期,校准节点时间NTP
tls: failed to verify certificate: x509: cannot validate certificate for 10.96.0.1 because it doesn't contain any IP SANsapiserver证书漏掉Service网段IP重新签发apiserver证书,补充10.96.0.1等IP
etcdserver: request timed outpeer证书SAN没包含其他节点地址重新签发peer证书,SAN覆盖所有etcd节点
unauthorized: authentication required用户名密码问题和证书无关,检查docker login凭证

遇到证书报错,先别急着怀疑组件配置,第一时间用openssl s_client去看实际提供的证书内容:

openssl s_client -connect 10.0.1.11:2379 -showcerts

这个命令会直接显示TLS握手时对端发过来的证书链,比看日志高效得多。如果显示出来的证书SAN和你预期不一致,那就是证书用错了或者签发时SAN没写全。

6.2 几个值得记住的细节

第一,集群里的证书有效期最好在签发时就统一规划好,并在日历上标记到期时间。K8s的证书轮换是个繁琐工程,apiserver证书过期会造成整个控制面不可用。我见过太多兄弟的集群突然“莫名其妙”不可用,最后排查发现是证书过期。

第二,证书私钥分发时一定要用安全通道,别直接通过明文聊天工具传。生产环境的K8s证书私钥等同于集群管理权限,泄露了就等于把集群交给了别人。我一般用内置的加密传输方式或者带权限控制的内部对象存储来分发。

第三,Harbor自签证书在发到开发手里时,记得同时附上根CA证书,并且告诉开发“双击安装到信任根证书颁发机构”。很多开发遇到浏览器报警告就直接截图找运维了,提前把说明写好能省掉不少对接成本。

第四,如果使用kubeadm,它的证书轮转机制是自动的,但仅针对它自己签发的证书,而且要求CA还是它自己那套。如果你用了外部CA,就得自己盯证书有效期,定期做手动轮转。生产环境里,结合kubeadm certs renew和手动替换文件再重启组件即可。

7. 个人体会:CA设计决定运维幸福感

我个人的体会是,证书体系的设计其实决定了整个K8s生产环境后续运维的幸福感。搭建集群时多花一天把CA、SAN、有效期规划好,后面两年你都不会被证书问题找上门;如果图省事各签各的,之后每一次排障都是在为当初的草率买单。

最后再分享一个小技巧:所有证书签完之后,建议写一个简单的README放在/pki目录下,记录每张证书的用途、SAN内容和到期时间。不用写得多复杂,就是一个普通的txt文件,但半年之后你回来做轮转时,看到这个文件就知道哪张证书对应哪个服务、SAN覆盖了什么地址,不用对着openssl x509 -text一行行去猜。这个习惯帮我省了不少事,每次接手别人的集群我也会第一时间补上这份文档。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 14:15:58

渠道数据采集技术解析与最佳实践

1. 渠道数据采集的核心价值与挑战 在当前的商业环境中,渠道数据已经成为企业决策的重要依据。我见过太多企业因为缺乏有效的渠道数据采集方法,导致市场策略偏离实际需求。渠道数据采集的本质,是通过系统化手段获取销售渠道中的关键业务指标&a…

作者头像 李华
网站建设 2026/9/10 14:15:19

Phase 1: Requirements Discovery

Phase 1: Requirements & Discovery 【免费下载链接】planning-with-files Persistent file-based planning for AI coding agents and long-running tasks. Crash-proof markdown plans, session recovery after /clear and compaction, per-turn re-injection against co…

作者头像 李华
网站建设 2026/9/10 14:14:54

软考证书的职场价值与高效备考策略

1. 软考现象背后的深层逻辑最近在技术社区看到一个很有意思的现象:一边是"软考无用论"的持续发酵,一边是每年报考人数屡创新高。作为参加过三次软考的老兵,今天想从行业内部视角,聊聊这个看似矛盾的现象背后&#xff0c…

作者头像 李华