学K8S和真正动手搭K8S之间,隔着一个经常被忽略又极其关键的组件——etcd。我见过不少朋友聊Pod、Deployment头头是道,但问一句“K8S把集群状态到底存在哪”,答案就开始含糊了。实际上K8S控制面的所有状态,节点的调度结果、Pod的实时状态、ConfigMap和Secret的内容,全部以键值对形式存放在etcd里。etcd才是那个真正决定“你现在看到的集群长什么样”的数据库。
这篇文章从K8S基础架构讲起,带着你把它最关键的数据底座拆开看明白,然后完整走一遍单节点etcd服务的部署流程。过程中会把Raft协议的通俗逻辑、配置文件里每个字段的含义、systemd托管方法、备份恢复手段都讲清楚。适合正在搭实验环境、或者想彻底搞懂K8S控制面组件之间如何协作的人。内容全部基于我实际搭建和踩坑的经验,不是照搬文档。
1. 先把K8S的大体结构讲透:etcd到底坐在哪张椅子上
1.1 控制面的四块拼图:API Server、调度器、控制器、etcd
K8S集群从逻辑上分成两大部分:控制面和工作节点。工作节点是真正跑业务容器的地方,控制面负责做决策、管状态。控制面里的核心组件主要有四个:API Server、Controller Manager、Scheduler,以及一个独立的分布式键值存储etcd。
我用一个类比来解释这四者的关系:把K8S当成一家公司,API Server是前台接待处,所有外部请求、内部组件之间的沟通都要经过它;Controller Manager是拿着规章制度做检查的部门,不停确保现实状态和期望状态一致;Scheduler是排班员,负责把任务分配合适的人;而etcd就是这家公司的档案室,所有决定、所有现场记录都写进档案,业务人员要了解现状也去档案室查。没有档案室,整个公司所有决策都会失去依据。
所以控制面正常运行的基本路径是:你通过kubectl向API Server提交一个期望状态,API Server把这个期望状态写入etcd,Scheduler从etcd读到新任务并做调度决策,Controller Manager通过watch机制监听状态变化并持续调整。etcd一旦不可用,API Server连读写都无法完成,整个集群的控制功能直接瘫痪。这也是为什么etcd在K8S里地位如此之高。
1.2 为什么K8S的数据存储偏偏是一个键值库
有人可能会想,K8S这么复杂的系统,为什么不用MySQL这种传统数据库?理解这个问题的关键在于K8S对数据存储的需求非常特殊。
首先需要强一致。API Server是最核心的组件,所有控制器都通过它读取状态,如果读到的数据是过期的或者不一致的,调度和控制器就可能做出错误决策。etcd基于Raft共识算法,能保证写入一旦返回成功,后续任何节点都能读到这个值,这就是强一致性。其次还需要监听(Watch)能力:Controller Manager和Scheduler不需要定期轮询数据,而是订阅key的变化,数据一变就立即收到通知。这是K8S能对资源变化做出秒级响应的重要原因。再就是Lease租约机制,节点心跳、Pod存活都依赖这个能力,租约到期自动清理,避免用复杂任务定期扫描。
用表格对比一下更直观:
| 能力 | etcd | Redis | MySQL |
|---|---|---|---|
| 一致性 | Raft强一致 | 主从异步复制,可能丢数据 | 半同步或组复制可以做到,但部署重 |
| 变更通知 | 原生Watch | 有Pub/Sub但消息不持久、语义不同 | 需解析binlog,实现复杂 |
| 租约机制 | 内建Lease | 有TTL,但精准续约能力没有etcd完善 | 需自己写表、写定时任务 |
| 命令事务 | 支持mini-transaction | Lua脚本 | 强事务能力 |
| 部署形态 | 独立小进程,适合组件间协调 | 独立进程 | 重,运维成本高 |
一句话总结,etcd是专门为“分布式系统中的协调状态”设计的产品,K8S需要的能力它刚好都有,而这几个能力在通用数据库上要么缺失,要么需要大量额外开发。所以K8S选etcd不是偶然,是设计定位决定的。
1.3 单节点etcd:什么时候够用,什么时候属于“没办法只能这么干”
单节点etcd部署在我的日常里非常常见,基本集中在三类场景:本地开发学习、搭建最小化K8S实验集群、边缘或离线环境里只要一个控制面。
在这些场景下,单节点etcd完全够用。K8S实验环境不追求可用性,只追求“能跑、能练、能观察”。而生产环境的K8S集群,etcd几乎都会以三个节点起步,因为单节点一旦故障,整个集群的控制权就没了。这个取舍本质上是:你愿意为“控制器挂一台”承受多大损失。学习环境都能接受,生产环境坚决不能接受。
所以这篇文章里的单节点部署,本身就是一种“实验室方案”,我会把其中的得与失讲明白。如果你在真实生产环境照搬,我把话放前面:至少组三台etcd,这只是底线。
2. 动手前必须搞清楚的etcd核心机制
2.1 Raft协议讲成人话:会议记录员和过半确认
etcd内部最重要的东西就是Raft协议,它负责让集群里多个节点保持一致。不想读论文的话,可以把它理解成一套“全员投票的会议记录机制”。
集群里会选出一个Leader,叫负责人,负责把大家的修改意见记成日志,然后分发给每个成员。每个成员收到后确认“我记下了”,当超过一半的成员都确认之后,这次修改才真正生效。单节点etcd的quorum是1,也就是只需要自己确认就算生效,没有任何网络分裂问题,写路径极短。这也是单节点etcd性能表现不错的原因之一。
这里有个常识:节点数设计通常取奇数,三个节点允许坏一个,五个节点允许坏两个。原因也很简单,过半确认要求“严格超过一半”,两个节点反而比一个节点更脆弱。所以生产环境一定是三五七这样的奇数组合。理解了Raft的过半机制,你就会明白etcd的强一致性不是玄学,是靠日志复制和投票保证的。
2.2 版本选择与数据落盘规划
部署etcd之前先干两件事:选版本、选目录。
etcd目前主流版本是3.5分支,也是K8S较新版本控制面通用的选择。版本跨度不要太大,太老的3.3或者太新的4.x在没有明确兼容依据前都不要碰。以我的经验,看K8S官方文档里那张“每个K8S版本推荐使用的etcd版本对照表”最稳妥,跟着选就行。
数据目录建议放在独立磁盘上,有条件优先用SSD。etcd本质上是一个写敏感的存储组件,磁盘IO延迟会直接影响写响应时间,进而影响K8S资源提交的延迟。单独划一个 /var/lib/etcd 目录是为了权限隔离,独立磁盘则是为了性能隔离,避免其他业务日志把磁盘IO占满之后,整个K8S控制面跟着遭殃。
单节点etcd虽然不需要应对集群通信,但数据目录膨胀是必须考虑的问题。默认后端配额是2GB,达到配额后etcd会进入只读模式,K8S也就没法再创建新资源。这个问题我会在后面的常见问题章节里详细记录,这里先记住:存储空间不是越大越安心,你得有一套监控和压缩方案。
2.3 端口、目录和权限:动手前先画一张小地图
etcd占用两个端口:2379是客户端通信端口,K8S的API Server通过它读写数据;2380是集群节点间通信端口,单节点环境用不到,但配置里必须保留。用一张小地图记忆:2379面向外部,2380面向内部。
目录规划建议如下:
- 可执行文件:/usr/local/bin/etcd、etcdctl
- 配置文件:/etc/etcd/etcd.conf.yml
- 数据目录:/var/lib/etcd
- 日志:交给systemd的journald统一管理
权限方面,我们专门建一个名为etcd的系统用户,给它的权限就是数据目录的读写权。不要用root去跑etcd,虽然能跑,但一旦被攻破或者误操作,权限边界完全不同。最小权限原则在这里同样适用。
3. 单节点etcd服务部署全过程实录
3.1 环境准备与二进制安装
这里以一台干净的Linux服务器为例,systemd作为初始化系统。单节点etcd的资源要求不高,2核4G起步完全够用,关键是磁盘别太慢。
第一步,创建系统用户和目录:
useradd --system --home /var/lib/etcd --shell /sbin/nologin etcd mkdir -p /var/lib/etcd /etc/etcd chown -R etcd:etcd /var/lib/etcd然后去etcd官方Release页面下载对应版本的Linux amd64压缩包。命令示例:
ETCD_VER=v3.5.9 wget https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz tar zxf etcd-${ETCD_VER}-linux-amd64.tar.gz cp etcd-${ETCD_VER}-linux-amd64/etcd /usr/local/bin/ cp etcd-${ETCD_VER}-linux-amd64/etcdctl /usr/local/bin/ etcd --version etcdctl version熟悉Linux的直接用这种做法,简单透明,也方便脚本化和固化版本。用发行版仓库安装虽然省事,但仓库里的版本往往偏旧,跟K8S版本兼容性不一定对得上。我是偏好二进制方案的,特别是在这种“我应该精确知道跑在什么版本上”的组件上。
下载如果比较慢,可以找靠谱的镜像加速,但一定校验压缩包的sha256,避免文件损坏或者篡改。
3.2 写配置文件:每一个字段都值得看懂
etcd从3.4开始推荐使用YAML格式配置。下面是单节点环境一份最简配置:
name: 'etcd-single'>Python类型提示Type Hints详解:从基础语法到静态类型检查实践
Python 类型提示(Type Hints)详解说实话,我最早看到 Python 类型提示的时候,内心是抵触的。Python 本来就是动态语言,写起来自由,随手一个变量想塞什么塞什么,加类型注解总觉得像是自己给自己套…
用Flask+Vue构建校园任务平台:从状态机设计到全栈开发实践
这个项目我在技术交流群里被追问过很多次,标题一眼看过去把 Flask、Python、Vue、PyCharm、Django 全列上了,乍一看是个技术栈大杂烩,实际上拆开来讲,核心就是一件事:做一个能在校园里“发任务、接任务、交任务、评任务…
前端风控参数if-match生成逻辑拆解:从抓包到算法还原
去年年底接了一个某查查平台的接口调试任务,需要在合规的前提下对接它的数据服务。调试过程中发现一个很有意思的现象:搜索接口的请求体里躺着一个名为 if-match 的参数,这个参数看起来像是一个普通的头字段,但每次请求的值都不一…
Chakra UI组件库深度拆解:设计令牌、暗色模式与React最佳实践
1. 组件体系概览:为什么我最终选择了Chakra UI做了几年前端,组件库用过不少。从早期的Bootstrap到后来的Ant Design、Element Plus,再到Tailwind CSS这种原子化方案,每个阶段都有让人眼前一亮的东西,但也都有让人挠头的…
TextMeshPro SDF数据链路全解析:从字体文件到图集纹理
做Unity的开发者,只要有文本显示的需求,基本都绕不开TextMeshPro这套方案。用久了TMP的人都会有个感觉:它渲染的字体边缘干净、缩放不变形,还能随手掏出描边、阴影、发光这些效果。而这一切的核心,就是标题里那个关键词…
Linux线程控制实战:同步互斥、生命周期与线程池构建
做Linux服务端开发这些年,我发现自己对Linux线程控制的认知分了好几个阶段。最开始觉得线程控制无非就是pthread_create、pthread_join、pthread_mutex_lock这一套API,能跑起来就行;后来开始管线程池、处理并发任务,才发现线程控制…