资讯动态

单节点K8S外部etcd部署实操:从证书到接入全流程

发布时间:2026/10/17 13:08:38 来源:尧图企业网站定制
1. 单节点K8S为什么绕不开etcd——先搞懂它在集群里的位置很多人第一次接触Kubernetes会被那一长串组件名字直接砸晕API Server、Scheduler、Controller Manager、kubelet、kube-proxy、etcd……每一个看起来都很抽象网上一搜全是架构图箭头绕来绕去越看越懵。我自己的体会是真正让K8S“活”起来的那根脊梁骨其实是etcd。你把它搞明白了K8S的很多东西会瞬间通顺。etcd到底是什么可以用一句话概括它是整个集群的“数据库”而且是唯一的数据源。K8S集群里所有资源对象——Pod、Deployment、Service、ConfigMap、Secret、Namespace以及节点状态、调度结果、控制器工作状态全部以键值对的形式存放在etcd里。API Server是无状态的服务它每次启动后都要从etcd拉取全量状态Scheduler要把Pod调度到哪个节点先问etcdController Manager要调谐Deployment和实际Pod数量也是从etcd对比。换句话说控制平面所有组件都围绕etcd转它是整个集群一致性和持久化的根基。单节点集群也一样。很多人以为“我只搭一台机器玩K8S是不是可以不用etcd”不能。哪怕你只有一台机器kube-apiserver依然依赖etcd做存储没有它API Server根本无法启动kubectl连过来只会得到连接被拒。所以单节点不是“不用etcd”而是“这个etcd只服务一个控制平面副本数为一”。这也是初学者最容易踩的认知误区。在部署形态上etcd通常有两种接入方式。第一种是kubeadm默认的做法在控制平面节点上以Static Pod的形式拉起一个etcd容器数据目录在宿主机上API Server通过本机2379端口访问。这种方式最省事但对新手来说有个问题etcd被“包”进了K8S的Pod体系里你想单独观察它、操作它、或者把它升级成多节点高可用集群感知会比较弱。第二种方式是外部etcdetcd以独立进程或独立节点的方式部署kubeadm在初始化时通过配置文件把API Server指到外部etcd地址。这种方式多一步独立部署的手续但好处很明显——你能完全掌控etcd本身也更容易理解K8S各组件之间的数据流。本文走的就是第二条路。我建议新手至少亲手做一次外部etcd的部署。原因很简单把数据库的安装、证书、启动、验证全走一遍你对“API Server到底在跟谁说话”会有非常直观的体感。等到以后真要搭生产级多节点高可用集群外部etcd本身就是必经之路。2. 环境准备与版本选型一次把坑排干净2.1 硬件与OS选型搭建这套环境不需要高性能机器。我自己验证时用的是一台2核4G内存的虚拟机硬盘40G跑一个控制平面加一个etcd进程完全足够。如果你想顺手把集群日常使用的场景也测一下4核8G会更舒服但2C4G不会成为瓶颈。操作系统方面Debian系和RedHat系都行。下面的操作以Ubuntu 22.04为例命令稍微换一下也能平移到CentOS或openEuler系列。重点不在发行版而在一组K8S和etcd共同依赖的基础环境变量这些不配好后面会连环报错。版本选型是很多人忽略、却最容易埋雷的地方。要知道Kubernetes对etcd有兼容性要求不同K8S版本会声明自己支持哪些etcd版本区间。我的建议很简单先确定K8S版本再反查对应的etcd版本。比如K8S 1.28系列官方声明支持的etcd版本是3.5.x那你就不要图新鲜装一个4.x的etcd否则API Server可能因为版本不匹配直接拒绝工作。kubeadm还会在init前做预检版本不对会在第一步就报错但与其等到报错再回头不如一开始就定好版本。举一个我常用的组合K8S 1.28.x etcd 3.5.9 containerd 1.7.x。这套组合经过反复验证稳定、资料多、报错也容易搜到。注意containerd版本不要低于1.6因为1.6以下版本对K8S 1.28的CRI支持不完整。2.2 基础系统配置四件套配置好主机名和hosts解析。单节点集群可以简化但hosts解析我仍然建议做因为后面kubeadm生成的证书里会写入节点名如果解析不到组件间通信会失败hostnamectl set-hostname k8s-single echo 192.168.1.100 k8s-single /etc/hosts关闭swap。kubelet有强制要求swap开启时节点会处于Ready状态之外的状态或者直接拒绝启动。这一条是硬性的别心存侥幸swapoff -a sed -i / swap / s/^/#/ /etc/fstab加载内核模块并打开流量转发。K8S使用iptables做流量转发时需要bridged流量也经过iptables规则所以br_netfilter必加载ip_forward必打开。顺手把配置固化到/etc/modules-load.d/和/etc/sysctl.d/重启后仍然生效cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 net.bridge.bridge-nf-call-ip6tables 1 EOF sysctl --system同步时间。etcd对时钟偏移敏感证书校验依赖时间窗口K8S组件之间也有很多超时判断。若时间偏差超过几百毫秒可能出现“证书过期”的假报错。我在虚拟机环境里吃过这个亏宿主机休眠后虚拟机时间漂移etcd日志里报tls握手失败排查半天发现不是证书问题而是系统时间差了一分钟。建议装上chrony并同步到网络时间源。2.3 容器运行时安装K8S从1.24开始移除了内置的Dockershim所以现在都必须通过CRIContainer Runtime Interface来驱动容器。containerd是最主流的选择配置也最简单。安装完containerd后有一件事必须做把Cgroup驱动改成systemd。K8S官方要求kubelet和容器运行时的cgroup驱动保持一致否则节点会一直处于NotReady状态日志里反复出现“failed to run Kubelet”或cgroup相关的告警。做法是先导出默认配置再修改其中的SystemdCgroup字段mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml编辑/etc/containerd/config.toml找到[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options]这一节把SystemdCgroup设为true然后重启containerd并设置开机自启。这一步的坑在于很多人忘记了sandbox_image的仓库地址。在国内网络环境下默认的registry.k8s.io/pause:3.9可能拉不下来。建议先改成可访问的镜像源或者提前手动拉取pause镜像并打上tag。否则后面kubeadm init时kubelet拉不到pause容器Pod也无从启动节点永远不Ready。我为了省事直接在config.toml里把sandbox镜像地址替换掉一劳永逸。3. 外部etcd单节点部署证书、配置与启动验证3.1 二进制与目录结构我习惯用二进制方式部署外部etcd不用容器原因是这一章希望你能清楚看到每个文件的去向容器化会把路径和权限问题隐藏起来不利于理解。在etcd的官方GitHub Releases页面下载对应架构的压缩包例如etcd-v3.5.9-linux-amd64.tar.gz解压后直接得到两个好用文件etcd和etcdctl。把它们放到/usr/local/bin/下即可。目录规划也顺手做好后面写配置的时候就不用临时想/var/lib/etcd # 数据目录 /etc/etcd/ # 配置文件目录 /etc/etcd/pki/ # 证书目录数据目录的属主记得改成root:root并限制权限etcd以root身份运行时会自动以etcd用户降权但我们是二进制直接启动保持root也没问题单节点测试无安全顾虑。3.2 用cfssl生成证书CA、服务端与Peer证书etcd是TLS双向认证的模型组件之间通信需要证书。在K8S集群里API Server访问etcd使用的是客户端证书etcd节点之间通信使用Peer证书etcd对外提供服务使用Server证书。单节点情况下peer证书仍然要生成因为etcd启动时会加载它去监听peer端口无clientCertAuth等特殊设置时缺失会导致启动报错。我用cfssl生成证书。先创建CAmkdir -p /etc/etcd/pki cd /etc/etcd/pki cat ca-csr.json EOF { CN: etcd-ca, key: { algo: rsa, size: 2048 }, names: [ { C: CN, L: Beijing, ST: Beijing } ] } EOF cfssl gencert -initca ca-csr.json | cfssljson -bare ca再生成etcd服务端证书它要同时给客户端提供2379端口的TLS服务。需要注意hosts字段要覆盖本机的所有访问方式包括IP、主机名、localhost缺一个就可能在某个地址上出现证书不匹配错误cat etcd-csr.json EOF { CN: etcd-server, key: { algo: rsa, size: 2048 }, hosts: [ localhost, k8s-single, 192.168.1.100, 127.0.0.1 ] } EOF cfssl gencert \ -caca.pem \ -ca-keyca-key.pem \ -configca-config.json \ -profileserver \ etcd-csr.json | cfssljson -bare etcd-server这里需要一个ca-config.json里面定义profile。最简配置如下serverprofile的serverAuth和clientAuth同时启用因为同一张证书既要用于服务端认证也要在API Server访问时充当客户端凭证。实践里很多老手会把client证书单独再签一份纯粹是为了权限范围更清晰单节点测试可以一张复用{ signing: { default: { expiry: 87600h }, profiles: { server: { expiry: 87600h, usages: [ signing, key encipherment, server auth, client auth ] }, peer: { expiry: 87600h, usages: [ signing, key encipherment, server auth, client auth ] } } } }再生成peer证书peer证书的CN通常设置为节点名hosts中包含节点IP和名称同时也要包含localhost和127.0.0.1因为在单节点模式下peer通信的监听地址就绑在本机cat etcd-peer-csr.json EOF { CN: k8s-single, key: { algo: rsa, size: 2048 }, hosts: [ localhost, k8s-single, 192.168.1.100, 127.0.0.1 ] } EOF cfssl gencert \ -caca.pem \ -ca-keyca-key.pem \ -configca-config.json \ -profilepeer \ etcd-peer-csr.json | cfssljson -bare etcd-peerCFSSL这个工具在Ubuntu上可以源码安装也可以直接用二进制包。安装方式不是重点重点是证书生成的流程CA在最顶层服务端和peer都是CA签发的下级证书。如果你看到“证书扩展属性不正确”或者远程连接时报“unknown authority”基本就是CA不一致导致的。3.3 编写etcd配置与systemd单元etcd支持命令行参数或配置文件两种方式。命令行参数适合临时测试配置文件适合长期管理。我把关键参数写成一个EnvironmentFile再配合systemd来管理这样调试时直接改文件就行不用重新敲命令。配置文件/etc/etcd/etcd.confETCD_NAMEetcd-single ETCD_DATA_DIR/var/lib/etcd ETCD_LISTEN_PEER_URLShttps://192.168.1.100:2380 ETCD_LISTEN_CLIENT_URLShttps://192.168.1.100:2379,https://127.0.0.1:2379 ETCD_ADVERTISE_CLIENT_URLShttps://192.168.1.100:2379 ETCD_INITIAL_ADVERTISE_PEER_URLShttps://192.168.1.100:2380 ETCD_INITIAL_CLUSTERetcd-singlehttps://192.168.1.100:2380 ETCD_INITIAL_CLUSTER_STATEnew ETCD_INITIAL_CLUSTER_TOKENetcd-k8s-cluster ETCD_CERT_FILE/etc/etcd/pki/etcd-server.pem ETCD_KEY_FILE/etc/etcd/pki/etcd-server-key.pem ETCD_CLIENT_CERT_AUTHtrue ETCD_TRUSTED_CA_FILE/etc/etcd/pki/ca.pem ETCD_PEER_CERT_FILE/etc/etcd/pki/etcd-peer.pem ETCD_PEER_KEY_FILE/etc/etcd/pki/etcd-peer-key.pem ETCD_PEER_CLIENT_CERT_AUTHtrue ETCD_PEER_TRUSTED_CA_FILE/etc/etcd/pki/ca.pem逐个解释一下关键配置initial-cluster里写的是“节点名该节点peer地址”的格式单节点也是一样它让etcd在启动时知道自己属于哪个集群、集群有哪些成员。如果这里写错etcd会尝试去连接一个不存在的成员然后一直等待数据目录里也会留下错误状态。initial-cluster-state设为new表示创建新集群如果你之后需要添加成员再把它改成existing。client-cert-auth和peer-client-cert-auth一定要为true这决定了API Server访问时必须携带客户端证书这点在K8S接入章节非常重要。systemd单元/etc/systemd/system/etcd.service[Unit] Descriptionetcd key-value store Afternetwork.target [Service] Typenotify EnvironmentFile/etc/etcd/etcd.conf ExecStart/usr/local/bin/etcd Restartalways RestartSec10 LimitNOFILE65536 [Install] WantedBymulti-user.targetTypenotify是个容易忽略的细节etcd在完成初始化后会主动发一个“ready”通知给systemdsystemd收到后才认为服务启动成功。如果用默认的simple类型systemd会以为etcd启动成功但实际上etcd还在等待集群成员加入造成服务状态与实际不一致。启动并检查systemctl daemon-reload systemctl enable --now etcd systemctl status etcd查看日志正常输出确认没有告警后用etcdctl做端到端验证export ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-server.pem \ --key/etc/etcd/pki/etcd-server-key.pem \ endpoint health返回healthy就说明etcd服务端已经正常监听。再检查成员列表etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-server.pem \ --key/etc/etcd/pki/etcd-server-key.pem \ member list如果成员列表只有一个并且name是etcd-single说明单节点etcd已就绪。这一步走完相当于数据库已经建好就等K8S接入了。4. 让kubeadm接入外部etcd控制平面组件的完整落地4.1 安装kubelet、kubeadm、kubectl三个K8S核心工具统一安装版本用上一步定好的1.28.x。安装方式取决于发行版Ubuntu上先配置好软件源再指定版本安装。有一点需要特别注意kubelet、kubeadm、kubectl三个工具的版本必须一致或有明确的兼容关系不要让apt自动帮你装最新版否则可能出现kubeadm版本与kubelet版本不匹配的警告虽然不致命但会增加排查负担。apt-get update apt-get install -y kubelet1.28.* kubeadm1.28.* kubectl1.28.* systemctl enable --now kubelet这时候kubelet先启动反而会失败因为集群还不存在kubelet会反复找静态Pod配置文件或等待着kubeadm写入配置。这个报错是正常的别慌。我们先把节点上的kubelet配置都准备好它自然会像等待指令的士兵一样运行。4.2 准备kubeadm配置文件外部etcd接入kubeadm默认会在控制平面节点上生成一个etcd的Static Pod并直接拉起内嵌etcd。要改成接入外部etcd需要在初始化时写一个kubeadm.yaml配置文件声明etcd外部地址和证书路径。配置文件的内容是接入成败的分水岭我贴出一个可直接照抄的模板apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.5 networking: podSubnet: 10.244.0.0/16 etcd: external: endpoints: - https://192.168.1.100:2379 caFile: /etc/etcd/pki/ca.pem certFile: /etc/etcd/pki/etcd-server.pem keyFile: /etc/etcd/pki/etcd-server-key.pemnetworking.podSubnet这里要提前规划它会直接影响后续安装网络插件时选用的网段。我用的10.244.0.0/16是Flannel的默认网段如果后面选Calico一般用192.168.0.0/16这取决于你之后用哪个CNI插件最好先定下来。etcd.external这一节就是整篇文章的关键——kubeadm一看到这节配置就不会再去生成内置etcd的Static Pod而是把API Server启动参数里的etcd连接地址指向我们刚才部署的外部etcd端点。证书路径也直接引用etcd的pki目录。还有一个外部etcd必需的初始化参数controlPlaneEndpoint。单节点集群不是必须但如果以后想横向扩展控制平面建议现在就设上指向本机IP和6443端口controlPlaneEndpoint: 192.168.1.100:64434.3 初始化集群并验证构建结果执行初始化前先做一个预检把潜在问题都暴露出来kubeadm init --config kubeadm.yaml --dry-run这个命令不会真正创建集群只会检查配置和系统环境。如果预检通过再正式执行kubeadm init --config kubeadm.yaml等初始化跑完重点观察最后几行输出。正常情况kubeadm会提示你执行export KUBECONFIG/etc/kubernetes/admin.conf然后拷贝admin.conf到用户目录。拿到kubeconfig后验证集群状态export KUBECONFIG/etc/kubernetes/admin.conf kubectl get nodes kubectl get pods -n kube-system这里有一个新手几乎必踩的坑node状态是NotReadykube-system下有很多Pod在CrashLoopBackOff。别急着找etcd的问题先看是哪些Pod起不来。如果etcd相关Pod已经不存在说明外部etcd接入成功如果看到coredns-xxx处于Pending多半是CNI网络插件还没装。这个阶段最典型的失败是API Server起不来事件里报etcdserver: request timed out或context deadline exceeded那才是etcd连接问题。网络插件安装需要另下功夫我通常用Flannel最简单kubectl apply -f kube-flannel.yml装完后等待约一到三分钟再执行kubectl get nodes如果状态变为Ready说明整个控制平面已经完整跑通。4.4 现实验证外部etcd里的K8S数据这是我最喜欢的一步验证K8S确实在往外部etcd写入数据。先在etcd里查一下当前的KV情况etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-server.pem \ --key/etc/etcd/pki/etcd-server-key.pem \ get / --prefix --keys-only | head -50你会看到大量以/registry/开头的键比如/registry/pods/kube-system/coredns-xxx、/registry/nodes/k8s-single、/registry/deployments/kube-system/coredns。这些就是K8S资源在etcd中的真实存储路径。读取一个具体的键值验证数据完整性etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-server.pem \ --key/etc/etcd/pki/etcd-server-key.pem \ get /registry/nodes/k8s-single返回的是一大段JSON结构里面记录了Node的元数据、状态、资源分配信息。看到这些内容你对“K8S把状态写在etcd里”这句话就有了实感。直接操作etcd的危险性也要提醒一句这些键承载了整个集群状态非排查场景不要做delete、put之类操作否则K8S的调谐机制连反应都来不及可能直接导致集群数据丢失。5. 单节点集群的日常验证与常见故障排查5.1 重启顺序与数据持久化单节点集群的一个特殊性在于etcd只有一个副本所以它没有高可用也没有自动故障转移。机器重启后etcd会重新加载磁盘上的数据K8S控制平面组件也会各自拉取一遍全量状态。要保证重启后集群可用必须注意服务启动顺序先让etcd先起来再拉起kubeletkubelet会负责启动静态Pod形态的API Server等控制平面组件。我用systemd管理etcd所以设置好etcd服务开机自启同时kubelet也是开机自启顺序在绝大多数情况下没问题。但有一个隐患必须提前处理etcd数据目录的持久化。如果你跑在虚拟机里记得给/var/lib/etcd所在磁盘做快照或备份。单节点集群丢失etcd目录等于丢失整个集群因为没有其他节点持有数据的副本。备份最简单的方式是快照也可以用etcdctl自带的能力etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-server.pem \ --key/etc/etcd/pki/etcd-server-key.pem \ snapshot save /backup/etcd-snapshot.db恢复时用etcdctl snapshot restore在一个空的数据目录里重建再把initial-cluster-state改成existing之前的状态。这套流程在单节点上不常测但真要遇到数据损坏时会救命。5.2 排查链路当API Server连接不上etcd假设执行kubectl get nodes时直接报error: etcdserver: request timed out或者apiserver的Pod一直CrashLoop我们需要按顺序排查。第一步看API Server的日志kubectl logs -n kube-system kube-apiserver-k8s-single | tail -50如果日志里反复出现connect: connection refused说明API Server根本连不到etcd的端口检查etcd监听状态和防火墙。如果出现tls: bad certificate说明证书链路出问题。证书问题的排查是第一优先级。依次检查三件事etcd环境的ETCD_CLIENT_CERT_AUTHtrue是否开启API Server传给etcd的CA文件是否和etcd使用同一把CAAPI Server的客户端证书里的CN和扩展用法是否符合etcd的要求。我遇到过最典型的错误是kubeadm生成的kubeconfig里certificate-authority字段指向了K8S自己的CA路径而不是etcd的CA结果TLS校验时完全对不上。解决方式很简单把kubeadm.yaml里的caFile改为etcd的/etc/etcd/pki/ca.pem重新生成一次API Server的客户端证书再重启静态Pod即可。5.3 证书到期与时间漂移etcd部署在非容器环境用证书时最讨厌的问题是证书过期。单节点测试环境如果长期不重启一年半载之后证书到期集群会突然“罢工”。排查方法直接看etcd日志里面会明确记录哪个证书在何时过期。日常防止这个问题可以在日历上设一个提前一个月的提醒重新签发证书并按顺序替换先签新证书再改etcd配置最后重启etcd。时间漂移这块也要盯住。前面提到的chrony同步如果挂掉了etcd在2500毫秒的看门狗间隔内检测到时钟跳跃会直接panic退出。日志里会出现failed to check the clock这样的字眼这种情况不是数据问题单纯是NTP服务失效恢复时间同步再重启etcd就能恢复。5.4 存储空间与碎片etcd会持续写入数据即使数据量小历史版本和碎片也会占用磁盘。测试环境最容易忽略的是/var/lib/etcd满了之后etcd进入只读模式日志里出现apply failed: etcdserver: no space。定期做碎片整理和压缩是有益的etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-server.pem \ --key/etc/etcd/pki/etcd-server-key.pem \ defragdefrag需要在线执行并且执行期间会阻塞写请求单节点测试环境可以接受。压缩历史版本可以用compact命令配合--prefix筛选出一个合适的保留时间范围。这类操作我建议只在真实必要的时候执行不要把所有维护动作都做一遍既没收益还可能增加风险。5.5 一张自检清单我做了一套简单的巡检清单单节点集群每次上线前都会过一遍检查项命令或方法预期结果etcd服务状态systemctl status etcdactive (running)etcd健康etcdctl endpoint healthhealthyAPI Server状态kubectl get pod -n kube-system -o wideRunning无频繁重启节点状态kubectl get nodesReady存储使用率df -h /var/lib/etcd不超过85%时间同步chronyc trackingSystem time正常无大偏差证书有效期查看pem文件剩余时间不少于30天这套流程我每次迁移环境或给别人排查问题时都用它基本能在五分钟内定位是etcd还是K8S控制平面的问题省下大量瞎折腾的时间。最后再分享一个小技巧我习惯把etcd的启动参数和K8S的kubeadm.yaml文件都放进同一份Git仓库里一次配置下次重搭环境直接拉下来改IP就能用。这个东西日常不会频繁改动但真到需要恢复环境的时候那份提交记录能帮你省下不少回忆时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑