简介:《Kubernetes in Action中文版》是面向云原生开发、运维与SRE人员的Kubernetes系统学习电子书,由Manning原著引进、七牛容器云团队翻译,电子工业出版社出版,重点解决从Docker容器入门到在Kubernetes中部署分布式应用的实践问题。压缩包内共1个PDF文件,大小约15.62MB,内容完整,适合按章节阅读与案头查阅。全书从Docker与Kubernetes的由来和发展讲起,通过逐步部署一个应用并不断添加功能,帮助读者理解集群架构、容器组织、服务发现与线上流量管理,并延伸到监控、调试和伸缩等高级主题。书中强调操作实践与排错思路,并配有循序渐进的示例与章节组织,可帮助具备一定Linux和容器基础的开发者建立系统知识框架,形成从概念到落地的学习路径,提升实际部署、运维与问题定位能力。目前已有1212人学习下载,适合作为Kubernetes入门到进阶的参考读物。
1. 到底什么是容器编排:一个航运码头的比喻
K8S 是 Kubernetes 的常见缩写,取首字母 K、尾字母 s,中间正好八个字母,念作“库伯内提斯”,在希腊语里是“舵手”的意思。用一句话说清它是什么:一套容器编排系统,专门处理“一堆容器跑在一堆机器上”这件事。
打个比方,集装箱把货物形状标准化了,码头才有可能高效运转。但光有集装箱并不够,还得有人决定哪个箱子装哪条船、哪条船靠哪个泊位、船晚点了剩下的货往哪儿挪、某个泊位塌了怎么把货分流出去。K8S 干的就是这个调度台的活。过去一台服务器上docker run几下就能把服务跑起来,一旦实例变成几十上百个、机器变成一排机柜,手工敲命令的玩法立刻失效,这正是它值得搞懂的原因。
2. 为什么容器跑起来之后麻烦才开始:K8S 存在的真正理由
2.1 容器解决了打包问题,却把分布问题放大了
容器技术真正普及,是因为它把“应用、依赖、运行环境”三样东西打包进了一个镜像文件。同一份镜像,在开发者的笔记本上跑得起来,在测试环境的虚拟机上跑得起来,到了生产机房还是那份东西,环境不一致这类老大难问题被大幅压缩。门槛一降,用的人就多了;用的人一多,单机上跑三个容器就变成了几十台机器上跑几百上千个容器。
麻烦恰好从这里开始。单机时代,进程挂了,机器还在,人上去重启一下就行;分布式时代,容器挂了,容器可能被重新拉起在另一台机器上,IP 变了,端口变了,日志散落在不同节点上。原来靠一台机器、一个 IP、一份配置文件就能说清的系统,忽然变成了一张不断变化的网。容器编排系统要处理的,就是这张网。
这也解释了为什么容器编排的概念一度很热闹:有的方案偏向调度,有的方案偏向部署,还有团队干脆自己写一套集群管理系统,最后发现计算问题比存储问题开放得多——存储的边界是磁盘,计算的边界是整个机房。几轮试错之后,大部分团队发现重新造一遍轮子的代价太高,转而使用成熟的编排系统。
2.2 一个服务上线,至少要回答五个问题
把任何一个后端服务放到多台机器上跑,绕不开下面五个问题。它们看起来朴素,但每一个都能把人卡住半天:
- 跑几个实例、跑在哪几台机器上?某台机器资源不够时,谁来重新安排?
- 其他服务怎么找到它?实例的地址一直在变,难道每次都要改配置?
- 它挂了怎么办?谁来发现、谁来重启、谁来把流量从坏实例上挪走?
- 配置文件和数据库密码怎么传进去?总不能打进镜像里吧。
- 新版本怎么上线?停掉旧的再起新的会中断服务,滚动替换又怎么保证不出错。
单机时代这五个问题大多由人肉运维解决:写脚本、改 nginx 配置、盯着监控、手动重启。机器数量一多,脚本会互相打架,人也盯不过来。编排系统把这些动作固化成了平台能力,让“人回答问题”变成“系统持续回答问题”。
2.3 把整个机房当成一台机器:数据中心操作系统的说法从哪来
有一句流传很广的描述:K8S 是数据中心的操作系统。理解这句话,单机操作系统的类比很好用。一台电脑上,操作系统负责给进程分配 CPU 和内存、负责进程之间的通信、负责某个进程崩了不影响别的进程;换成机房,K8S 负责给 Pod 分配节点、负责服务之间的互相发现、负责某个实例崩了自动补上一个。开发者面对的不再是“第几号机柜第几台机器”,而是“我要三个副本、每个 1 核 2G”。
这个比喻的价值在于它划出了边界:操作系统不管你的程序写得对不对,只管资源怎么分、进程怎么活;编排系统同样不管业务逻辑,只管容器怎么部署、流量怎么走。所以指望引入 K8S 就让系统变快、变稳,是错位的期待,它解决的是“组织方式”,不是“代码质量”。
给数据中心做一套操作系统的想法其实不新,几十年前就有研究项目尝试过,只是当年的硬件与网络条件撑不起这个设想。容器把交付单位变小、变轻之后,这个设想才真正落地。
2.4 期望状态:编排不是发一次命令,而是持续对齐
这是整套系统里最容易被忽略、也最该先建立的一个认知。传统运维是命令式的:执行一条命令,机器照做,做完就结束,中间出了岔子没人管。编排系统是声明式的:你写一份清单,说明“我要三个副本、用这个镜像、开这个端口”,然后把它交给集群,集群会一直盯着现实和清单之间的差距,差一个就补一个。
用kubectl把这层关系说清楚就很直观:
# apply 不是“执行一次就完”,而是把这份期望状态提交给集群 kubectl apply -f app.yaml # get 看到的是集群当前的实际情况 kubectl get pods # describe 能看出某个对象为什么没达到期望,事件列表在最后 kubectl describe pod app-7d9f-abcdeapply提交的是一份“愿望”,不是一个动作。提交完之后,哪怕半夜某个容器被系统杀掉,第二天早上你看到的依然是三个副本,因为控制器发现少了一个,会自己补上。理解这一层,后面很多名词就顺了:副本数、健康检查、滚动更新,全是在描述“期望”和“现实”怎么对齐。
提示:声明式的代价是排查方式变了。命令式出错看返回码,声明式出错要看对象状态和事件,
describe往往比logs更早给出答案。
3. 核心构件拆解:Pod、标签、Service 到底在管什么
3.1 Pod:最小调度单位,不是“容器”的同义词
初学者最常见的误解是把 Pod 当成容器的别名。它其实是一层包装:一个 Pod 里可以放一个容器,也可以放多个容器,这些容器共享同一个网络地址和同一批存储卷。调度器安排的是 Pod,不是容器,所以同一个 Pod 里的容器一定落在同一台机器上。
为什么要多这一层?因为有些进程天然要贴在一起跑。比如主程序加一个日志收集的边车容器,两者需要看到同一份日志文件;比如应用加一个本地缓存代理,两者最好走本机回环地址通信。打包成一个 Pod,这些需求就变成了默认成立的事。反过来,两个需要各自独立伸缩的服务就不该塞进同一个 Pod,否则扩副本时只能一起扩,白白浪费资源。
Pod 是短命的,这是必须建立的心理预期。它随时可能因为节点故障、滚动更新、资源不足而被销毁重建,重建后的 Pod 是一个全新的对象,地址也换了。所以任何把 Pod IP 写死在配置文件里的做法,迟早翻车。
3.2 标签与选择器:用贴便签的方式组织对象
集群里对象一多,怎么把它们归类?K8S 的答案是标签:一组挂在对象上的键值对,比如app=web、tier=frontend、env=prod。标签本身不产生任何行为,它的作用是被选择器引用。
选择器就是“按便签找对象”的查询条件。一个 Service 写app=web,意思是“所有贴着这张便签的 Pod,流量都归我管”;一个 Deployment 写app=web,意思是“贴这张便签的 Pod 由我负责维持数量”。标签是松耦合的关键:Service 不知道 Pod 叫什么名字、在哪台机器上,它只认标签。这样 Pod 被销毁重建、换了地址换了节点,Service 完全不用改配置。
这里有个现实中的踩坑点:标签是自由的,但自由意味着责任。两个不同业务的 Deployment 如果碰巧用了同一组标签,它们的 Pod 会被同一个 Service 一起收走,流量打到哪里全看运气。约定好一套命名规范,比事后排查这类问题省事得多。
3.3 Service:给会漂移的 Pod 一个固定门牌
Pod 地址会变,但调用方需要一个稳定入口,Service 就是干这个的。它给一组同标签的 Pod 分配一个固定地址,并在后端做负载均衡。
打个比方,一家公司搬办公室很频繁,但对外只公布一个总机号码,总机后面接哪部分机由内部决定。Service 就是那个总机,Pod 是分机。至于外部流量怎么进来,那是另一层的事,通常由集群的入口组件或者云上的负载均衡器接管,Service 只管集群内部的这一跳。
3.4 Deployment 与 StatefulSet:无状态与有状态是两套玩法
绝大多数 Web 服务是无状态的:任何一个实例都能处理任何一个请求,实例之间没有区别。这类服务用 Deployment 管,滚动更新、回滚、扩缩容都很顺。
但数据库、消息队列这类东西不一样:每个实例有自己的身份、自己的数据盘、自己的启动顺序,实例 A 不能随便替换成实例 B。这类服务用 StatefulSet 管,它保证每个副本有稳定的名字、稳定的存储,重启之后还是原来那个身份。
下面这份清单能看出两类资源的写法差异:
# 无状态服务的典型写法:三个完全等价的副本 apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 3 # 期望副本数,少了会被补上 selector: matchLabels: app: web # 这个 Deployment 认这张便签 template: metadata: labels: app: web # 造出来的 Pod 自动带这张便签 spec: containers: - name: web image: demo/web:1.0 # 镜像地址,不带版本号等于每次都赌运气 ports: - containerPort: 8080 --- # 稳定入口:把贴了 app=web 的 Pod 全部挂到这一个地址后面 apiVersion: v1 kind: Service metadata: name: web spec: selector: app: web ports: - port: 80 # 调用方访问的端口 targetPort: 8080 # 容器实际监听的端口几个参数值得单独说。replicas写的是期望值,不是上限,节点资源不够时它会卡在“部分就绪”的状态。selector.matchLabels和模板里的labels必须对得上,对不上时创建会被直接拒绝,这是新手最常见的报错来源之一。image后面那个版本号千万别省略,省略等于每次拉取都可能拿到不同的东西,出了问题根本没法回溯。port与targetPort是两个概念,前者是对外暴露的,后者是容器里进程真正在听的。
有状态服务换成 StatefulSet 后,副本的数字会变成名字,比如db-0、db-1,并且每个副本会绑定一块独立的存储。这份中文版资料在第 10 章专门讲了这套机制,包括副本之间怎么互相发现。
4. 容易搞混的几组概念:容器、镜像、编排、调度的边界在哪
4.1 容器与镜像:一个是文件,一个是正在跑的进程
易混点:很多人把“容器”和“镜像”当同一件事,说“容器坏了重做一个容器”,实际动手时发现做的是镜像。
为什么会混:日常口语里两个词都被简称为“容器”,而工具链又把构建、推送、运行串得很顺,中间的分界线被磨平了。
怎么区分:镜像是只读的模板,是一堆分层叠加的文件;容器是镜像跑起来之后的进程,有自己的可写层、自己的网络、自己的生命周期。容器删了,可写层里的临时文件也就没了,镜像还在。所以做备份要备份数据卷,不是备份容器。同理,镜像仓库里存的是模板,集群里跑的是实例,两者数量对不上很正常。
4.2 编排与调度:一个管全局,一个只是其中一步
易混点:把编排等同于“把 Pod 分配到哪里跑”。
为什么会混:调度是编排里最显眼、也最容易被观察到的动作,kubectl get pods -o wide一看,Pod 落在哪台机器上一目了然,其余工作看不见。
怎么区分:调度只回答“这个 Pod 该放哪台节点”这一个问题,考虑的是资源余量、亲和规则、污点容忍。编排则覆盖部署结构、流量分配、故障恢复、滚动升级、配置注入这一整套。把编排理解成调度,会得出“Pod 已经 Running 了就没事了”的错误结论——Running 只是起点,能不能接流量、重启后会不会丢数据,全靠编排层别的机制。
4.3 副本数与自动伸缩:一个是常量,一个是策略
易混点:以为设了三个副本就等于“会自动扩容”。
为什么会混:两者写在同一个对象里,名字又都带“数”字,看清单时容易一扫而过。
怎么区分:replicas: 3是个死数字,无论流量涨落都是三个,它保证的是“有几个”,不是“够不够用”。自动伸缩是一套策略,通常由独立的控制器读取指标(CPU 使用率、内存或者自定义指标),动态改那个数字。没有配置策略时,副本数永远不会自己变;配置了策略后,它也可能因为指标抖动而频繁横跳,需要设置冷却窗口。
4.4 配置与密钥:一个存普通参数,一个存敏感信息
易混点:把数据库密码写进普通配置对象里。
为什么会混:两种对象的用法几乎一模一样,都是挂成环境变量或者文件,写起来只差一个关键词。
怎么区分:普通配置用于端口、日志级别、开关这类不怕被看到的参数;密钥对象用于密码、令牌、证书。密钥对象在存储时会做额外处理,并且可以被单独的权限策略管住,值也不会在describe里明文打印。真把敏感信息塞进普通配置,排查问题时随手一贴日志,密码就进聊天记录了。
4.5 有状态与无状态:不是“有没有数据”,而是“实例能不能互换”
易混点:按“写不写磁盘”来判断一个有状态服务。
为什么会混:很多无状态服务也会写日志、写缓存,看起来也在操作磁盘。
怎么区分:判断标准是实例之间能不能随意替换。三个 Web 副本,杀掉任意一个换一个新的,服务照常;三个数据库副本,随便替换一个,数据可能就对不上了。前者用 Deployment,后者用 StatefulSet,配错类型的后果不是报错,而是运行一段时间后出现难以复现的数据问题,这类问题最难查。
5. 从能跑到跑得稳:副本、自愈、监控与伸缩这些话题
5.1 自愈不是魔法,靠的是探针和重启策略
“容器挂了会自动重启”这句话经常被误会成系统能感知业务是否正常。实际上,能否重启由重启策略决定,是否健康由探针判断。健康检查分两类:一类判断进程还活着没有,失败就重启容器;另一类判断能不能接流量,失败就把这个实例从 Service 后面摘掉,但不重启。
这个区分很有用。一个服务启动要加载大量缓存,启动期间活着但还不能服务,这时候如果只有存活探针,会被反复杀掉重启,永远起不来;配上就绪探针,启动期间只是不接流量,等预热完再挂进去,滚动更新才真正平滑。探针的阈值也别照抄,初始延迟设得太短,慢启动的应用会陷入重启循环,这是很典型的一类翻车。
5.2 资源请求与限制:一个决定能不能调度,一个决定会不会被杀
这两个词在清单里挨着写,含义完全不同。
resources: requests: cpu: "500m" # 0.5 核,调度时按这个数找有空位的节点 memory: "256Mi" limits: cpu: "1" # 上限 1 核,超了会被限流,不会杀 memory: "512Mi" # 超了会被直接杀掉,这是硬约束requests是“我至少需要这么多”,调度器据此挑选节点;如果所有节点的余量都不够,Pod 会一直挂在等待状态。limits是“我最多用这么多”,CPU 超了会被限速,表现为响应变慢;内存超了会被直接终止,表现为容器莫名其妙重启。排查时看到反复重启、退出码是内存相关的,第一反应就该去看限制值。
这两个数字还决定了一个 Pod 的服务等级:请求和限制都写且相等,属于最高保障档,资源紧张时最后才被驱逐;只写请求不写限制,属于中间档;两个都不写,属于最低档,节点压力一大最先被清理。给核心服务按最高档配置,给批处理任务按最低档配置,是常见做法。
5.3 调度约束:让该在一起的在一起,该分开的分开
默认调度只看资源余量,但现实里常有额外要求:某个服务必须跑在带固态盘的节点上,某两个副本不能落在同一台机器上,某类批处理任务只能占用空闲节点。这些靠节点选择器、亲和规则、反亲和规则和污点容忍来表达。
一个实用场景是反亲和:把三个副本强制分散到三台节点,任何一台机器挂掉,服务都还剩两个实例。代价是集群节点数不够时,第三个副本会一直等待,永远起不来,所以这类规则通常配“软”约束,尽量分散但不强制。清单里写死硬约束之前,先算一遍集群够不够,能省掉一次半夜排查。
5.4 伸缩分两层:副本数与节点数
流量涨了先扩副本,这是应用层;副本扩到节点装不下时,需要给集群加机器,这是基础设施层。两层要配合,只配了前者,扩容会卡在“Pod 等待调度”上,看起来像命令没生效。
副本的自动伸缩要看指标选得对不对。按 CPU 扩缩容,适合计算密集型服务;按每秒请求数或者队列长度扩缩容,更适合 I/O 密集型服务,因为这类服务 CPU 不高,按 CPU 扩永远不触发。另外要留意扩展的滞后性:容器启动、镜像拉取、应用预热都需要时间,等指标上来再扩,往往已经晚了。给核心链路留一点常驻余量,比把自动伸缩调到最激进更稳。
5.5 出问题时先看什么:一套固定的排查顺序
乱点一通只会浪费时间。一个比较有效的顺序是:先用get看对象状态,Pending 说明调度不上,ImagePullBackOff 说明镜像拉不下来,CrashLoopBackOff 说明容器起来了又挂;再用describe看事件列表,最后那几行通常直接写着原因;再去logs看应用日志,注意看上一个崩溃实例的日志,因为当前实例可能刚启动还没输出;实在不行才exec进去看环境。
这个顺序的价值在于从外到内,越靠前越便宜,越靠后越慢。很多问题在第二步就结束了,硬要从最后一步开始,等于每次都绕远路。
6. 什么时候该翻这份资料,什么时候先放一放
先说结论性的边界。适合开始学的时机有两个:一是你已经在用容器构建和运行应用,但还停留在单机或手工编排阶段,服务一多就开始乱;二是你要接手一个已经跑在集群上的系统,需要看懂别人写的清单和排查思路。这两种情况下,概念不清带来的成本是实打实的——看 YAML 像看天书,出错只能等别人来救。
暂时不适合的情况也很明确。如果你的应用只有一两个服务、跑在一台机器上、半年不升级一次,引入一套集群系统带来的复杂度远大于收益,光是网络、存储、权限这几块的配置就够折腾。另一种是团队里没人愿意维护它,抱着“装上就自动变好”的心态,那大概率会得到一个没人看得懂、也没人敢动的黑匣子。
再把几个常见错位期待摆出来,省得走弯路:
| 你期待的 | 它实际负责的 | 该由谁负责 |
|---|---|---|
| 应用跑得更快 | 合理分配资源、按需扩缩 | 代码与架构优化 |
| 自动修好有 bug 的程序 | 挂掉的实例自动重启 | 开发与测试流程 |
| 替代持续集成 | 接收构建好的镜像并部署 | 构建与发布流水线 |
| 备份业务数据 | 提供存储挂载与快照接口 | 存储方案与备份策略 |
| 自动保证高可用 | 按规则分散副本 | 集群规划与容量评估 |
这份中文版资料的编排方式,正好对应了从“能跑”到“跑得稳”的路径:前面几章先用一个应用把基本流程走通,中间拆开讲 Pod、服务、存储、配置、部署这些构件,后面转向集群内部、安全、资源限制、自动伸缩、调度约束和自定义扩展。它没有要求读者预先懂容器,也没有一上来就抛源码,对刚接触的人来说门槛是友好的。
一个具体的学习建议:别按顺序从头抄到尾。先挑一个小服务,把镜像构建、部署、暴露入口这条线走一遍,跑通之后再回头补标签、存储、探针这些细节。每补一个概念,都去清单里改一处,看集群的反应。概念只有和实际现象对上,才算真的记住。光看不动手,读到后面会发现自己只是在认字。
注意:这份资料写作时所依据的版本比较早,命令输出格式、部分字段的默认行为这些年有过变化。照着敲的时候如果输出对不上,先用版本相关的文档核对一遍,别急着怀疑自己理解错了。
说个自己的教训。早些年我第一次接触这套东西,觉得 YAML 就是配置文件,复制一份改改名字就上线了。结果标签没对齐,Service 后面一个 Pod 都没挂上,我还对着get pods里全是 Running 的输出自信了半天,直到用户反馈打不开才发现问题。从那以后我每次写清单,都会强制先跑三件事:看一眼选择器和标签对不对得上、确认镜像带了明确版本号、检查资源请求写没写。这三步花不了一分钟,能挡掉我遇到过的大半低级故障。
这份中文版资料适合放在手边当参考,遇到具体问题时翻对应章节,比通读一遍记得牢。需要的话去下载页取用就行。希望帮到你。
本文还有配套的精品资源,点击获取