Learning path 01 · Core to production

Docker & Kubernetes
从单进程到生产集群

这不是一份命令速查表。我们从“软件究竟如何被交付和运行”出发,逐层理解镜像、容器、Pod、控制器、网络、存储、调度与生产治理。

你已有计算机基础,所以我们不会回避进程、文件系统、网络命名空间或控制循环。每一章先讲“为什么”,再讲对象关系和工作机制,最后通过命令与当前仓库里的服务把抽象概念落地。

阶段 1单机运行
阶段 2容器交付
阶段 3集群编排
阶段 4可靠治理
阶段 5生产排障
CHAPTER 01

先理解问题:为什么“在我机器上能跑”还不够

程序不是孤立的二进制。它依赖运行时、系统库、配置、端口、文件和启动顺序。部署失败通常不是代码错了,而是运行环境与开发环境不一致。

搬家类比:传统部署像只把家具寄走,然后希望新房尺寸、插座和燃气接口刚好一致。容器则像把家具连同标准化房间一起装进集装箱,目的地只需提供统一吊装接口。

核心逻辑

一次可靠交付必须回答三个问题:运行什么、用什么配置运行、由谁保证它持续运行。Docker 重点回答前两个,Kubernetes 重点回答第三个。

对象关系 · 从代码到服务
源代码业务逻辑
构建产物二进制/JAR
运行环境库与配置
在线服务进程与流量
# 先把示例作为普通进程运行
cd docker-k8s-core
go test ./...
go run ./app
curl http://localhost:8080/healthz
易错点:把“构建成功”等同于“可部署”。真正的交付物还要明确启动命令、监听端口、配置来源、健康语义和数据位置。
动手:修改 APP_MESSAGE 后启动服务,观察代码不变而运行行为改变。思考哪些内容应进入镜像,哪些应在部署时注入。
CHAPTER 02

进程、虚拟机与容器:隔离发生在哪里

容器不是轻量虚拟机。容器里的应用仍是宿主机内核管理的普通进程,只是它看到的进程、网络、挂载点和资源范围被 namespace 与 cgroup 限制了。

公寓类比:虚拟机像每户自带地基和发电机的独立住宅;容器像一栋公寓里的独立房间。房间门锁和水电表彼此独立,但大家共用建筑主体,也就是宿主机内核。
维度虚拟机容器
隔离边界虚拟硬件与独立内核进程视图与资源
启动速度通常秒到分钟通常毫秒到秒
安全边界通常更强共享内核,需额外加固
对象关系 · 两种虚拟化
应用层VM: App + Guest OS | Container: App + 用户态依赖
隔离层Hypervisor | namespaces + cgroups
基础层硬件 | 宿主机内核 + 硬件
# 容器仍能在宿主机进程列表中找到
docker run --rm -d --name demo nginx:alpine
docker top demo
docker stats demo
docker inspect demo
易错点:认为容器天然安全。root 容器、过宽 capability、宿主机目录挂载或特权模式都可能穿透预期边界。
动手:比较容器内外的 ps、hostname 和网络接口,找出哪些视图被隔离、哪些内核信息仍然共享。
CHAPTER 03

Docker 四个核心对象:Dockerfile、镜像、容器、仓库

Dockerfile 是构建配方,镜像是不可变模板,容器是模板的一次运行,Registry 是分发镜像的仓库。区分这四者,能避免大多数初学混乱。

面包房类比:Dockerfile 是配方,镜像是一批封装好的冷冻面团,容器是一只正在烤箱里烘烤的面包,Registry 是冷库。删除面包不会删除配方或冷冻面团。
对象关系 · 构建与运行
Dockerfile声明步骤
build →
Image只读层
run →
Container进程+可写层
← pull
Registry镜像分发
docker build -t demo/hello-k8s:v1 .
docker image ls
docker run --name hello -p 8080:8080 demo/hello-k8s:v1
docker logs -f hello
docker rm -f hello

容器生命周期通常是 create → start → stop → remove。容器停止后可写层还在,但生产系统不应把它当持久化方案。

易错点:使用可变的 latest 标签推断版本。标签只是指针,不是内容身份;生产部署应使用明确版本,关键场景可固定 digest。
动手:连续运行两个同镜像容器,给它们不同 APP_MESSAGE。验证“镜像相同,实例配置不同”。
CHAPTER 04

Dockerfile:分层缓存、多阶段构建与最小镜像

每条构建指令都会形成可复用层。稳定且昂贵的步骤应靠前,经常变化的源码应靠后。多阶段构建把编译环境和运行环境分开。

流水线类比:如果包装盒上的日期每天变化,不应因此重新冶炼钢材。把变化频率不同的步骤拆层,Docker 才能复用前面已完成的工作。
对象关系 · 当前 Dockerfile
golang 基础镜像编译器
build 阶段生成 /out/server
COPY --from
distroless非 root 运行
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY go.mod ./
COPY app ./app
RUN CGO_ENABLED=0 go build -trimpath -o /out/server ./app

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]

ENTRYPOINT 定义主程序,CMD 常作为默认参数。主进程应正确处理信号,日志写 stdout/stderr,配置从外部注入。

易错点:COPY . . 再下载依赖,会让任何源码变化都击穿缓存。还要用 .dockerignore 排除 Git、缓存、密钥和构建产物。
动手:执行两次 docker build --progress=plain,观察 CACHED;只改 Go 源码,再看哪些层重新执行。
CHAPTER 05

Docker 网络、端口、Volume、配置与 Compose

容器有自己的网络命名空间。应用监听容器端口,端口发布把宿主机流量转发进去。Volume 把数据生命周期从容器生命周期中分离。

酒店类比:容器端口像房间内线,宿主机端口像酒店总机号码。-p 8080:80 是告诉前台:外部拨 8080 时转到房间的 80。
对象关系 · 单机容器通信
浏览器localhost:8080
宿主机端口8080
NAT →
容器端口8080
Volume独立数据
# 环境变量、端口、命名 volume
docker run --rm -p 8080:8080 \
  -e APP_MESSAGE="configured at runtime" \
  -v demo-data:/data \
  demo/hello-k8s:v1

# 多容器项目
docker compose up -d
docker compose logs -f
docker compose down

同一 Compose 网络里的服务用服务名通信,例如应用访问 postgres:5432,不要写容器 IP。IP 是实例细节,服务名才是稳定契约。

易错点:应用只监听 127.0.0.1,导致端口已发布却无法访问。容器服务通常要监听 0.0.0.0
动手:创建两个容器加入同一自定义网络,用容器名互相访问;删除并重建其中一个,验证名字不变而 IP 可能变化。
CHAPTER 06

容器运行规范:安全、日志、资源与排障

一个“能启动”的镜像离可运营还很远。生产容器应使用非 root 用户、只读文件系统、最小权限、明确资源上限,并能通过日志和健康端点解释自己的状态。

船舶类比:把服务装进容器只是造好了船舱。还需要载重线、报警器、航海日志和逃生规则,否则风平浪静时看不出问题,遇到故障便无从判断。
对象关系 · 可运营容器
进程非 root
stdout 日志发生了什么
healthz还能否工作
cgroup资源边界
docker logs --tail 100 hello
docker stats hello
docker inspect --format '{{.State.Status}}' hello
docker exec -it hello /bin/sh   # distroless 镜像通常没有 shell
docker run --read-only --cap-drop ALL --memory 64m ...
易错点:把 shell 当作唯一排障工具。最小镜像可能没有 shell、curl 或 ps;应依靠日志、指标、健康端点和临时 debug 容器。
动手:给容器设置很小的内存上限,观察退出状态与 OOM 信息。随后恢复合理限制,理解“限制”既是保护也是风险。
CHAPTER 07

Kubernetes 架构:声明期望,而不是遥控每台机器

Kubernetes 的关键不是“远程运行 Docker”,而是声明期望状态。API Server 保存意图,控制器持续比较现实与期望,调度器为新 Pod 选择节点,kubelet 在节点上落实计划。

恒温器类比:你不会每分钟手动开关暖气,而是设定 22°C。恒温器持续测量现实并纠偏。Deployment 的 replicas 就像目标温度,控制循环负责让实际 Pod 数接近期望值。
架构图 · 控制面与工作节点
用户入口kubectl → API Server → etcd(集群事实库)
决策系统Controller Manager(纠偏)+ Scheduler(选节点)
工作节点kubelet → container runtime → Pod;kube-proxy / CNI 负责网络
kubectl cluster-info
kubectl get nodes -o wide
kubectl api-resources
kubectl explain deployment.spec
kubectl get events --sort-by=.lastTimestamp

所有主要对象都遵循 apiVersion/kind/metadata/spec/status。你提交 spec,系统写入 status;不要手工编辑 status。

易错点:kubectl apply 当普通脚本执行。它实际是在更新声明,随后异步控制循环才逐步收敛;命令成功不等于业务已经就绪。
动手:部署后删除一个 Pod,连续执行 kubectl get pods -w,观察控制器自动补回副本。
CHAPTER 08

Pod、ReplicaSet 与 Deployment:工作负载如何逐层管理

Pod 是最小调度单位,可包含共享网络和 Volume 的一个或多个容器。ReplicaSet 维持副本数,Deployment 再管理 ReplicaSet,从而实现声明式更新与回滚。

剧团类比:Pod 是一组必须同台演出的演员;ReplicaSet 保证每晚有两组演员;Deployment 是制作人,负责新旧演员组的交接、节奏和回滚。
对象关系 · 所有者链
Deployment版本与发布策略
owns →
ReplicaSet期望副本
owns →
Pod × N容器运行单元
scheduled →
Node工作机器
kubectl apply -f k8s/deployment.yaml
kubectl get deploy,rs,pod -o wide
kubectl rollout status deployment/hello-k8s
kubectl set image deployment/hello-k8s app=demo/hello-k8s:v2
kubectl rollout history deployment/hello-k8s
kubectl rollout undo deployment/hello-k8s

Pod 名和 IP 都是临时的。不要让业务依赖具体 Pod;依赖稳定 Service 和持久化数据层。

易错点:直接修改 Deployment 管理的 Pod。控制器会用模板重新创建它,手工修改随时丢失。应该修改 Deployment 的 Pod template。
动手:把 replicas 从 2 改为 3,再把镜像标签改为 v2。观察新 ReplicaSet 创建、旧副本逐步缩减。
CHAPTER 09

Service、DNS 与 Ingress:流量怎样找到短命的 Pod

Pod 会替换,IP 会变化。Service 通过 label selector 维护后端集合,并提供稳定虚拟 IP 与 DNS 名。Ingress 或 Gateway 再把集群外 HTTP 流量路由到 Service。

餐厅类比:Pod 是每天轮班的厨师,Service 是永远不变的出餐窗口,Ingress 是商场总服务台。顾客不需要知道今天是哪位厨师,也不应直接闯进后厨。
架构图 · 一次 HTTP 请求
Clientexample.com
IngressHost/Path 路由
Service稳定 DNS/VIP
Pod Endpointslabel 匹配
kubectl get service hello-k8s
kubectl get endpointslice -l kubernetes.io/service-name=hello-k8s
kubectl port-forward service/hello-k8s 8080:80
kubectl run curl --rm -it --image=curlimages/curl -- \
  curl http://hello-k8s.default.svc.cluster.local

port 是 Service 端口,targetPort 是 Pod 端口。ClusterIP 面向集群内,LoadBalancer 通常借助云负载均衡器暴露到外部。

易错点:Service selector 与 Pod label 不一致。此时 Service 存在,但 EndpointSlice 为空,访问往往超时或拒绝连接。
动手:故意把 Service selector 改错,检查 EndpointSlice;恢复 label 后观察后端自动出现。
CHAPTER 10

ConfigMap、Secret、Volume 与持久化存储

镜像应在环境间保持一致。ConfigMap 注入普通配置,Secret 承载敏感值,Volume 为 Pod 提供文件视图,PVC 则向集群申请具有独立生命周期的持久化存储。

舞台类比:镜像是演员固定的剧本能力,ConfigMap 是当晚场次安排,Secret 是只有授权人员能看的保险箱密码,PVC 是剧院长期租用的仓库。
对象关系 · 配置与数据
ConfigMap非敏感配置
env/file →
Pod短生命周期
mount →
PVC存储申请
bind →
PV/StorageClass真实存储
kubectl create configmap app-config --from-literal=APP_MESSAGE=hello
kubectl create secret generic db-secret --from-literal=password='change-me'
kubectl get configmap app-config -o yaml
kubectl describe pvc data

# Deployment 中引用环境变量
envFrom:
  - configMapRef:
      name: hello-k8s

Secret 的 base64 只是编码,不是加密。需要配合 RBAC、etcd 静态加密、外部密钥系统和审计。

易错点:更新 ConfigMap 后期待所有环境变量立即变化。环境变量只在容器启动时读取;挂载文件可更新,但应用还需支持重载。
动手:更新 APP_MESSAGE,执行 kubectl rollout restart deployment/hello-k8s,验证新 Pod 读取新值。
CHAPTER 11

Probe、requests、limits、调度与自动扩缩容

readiness 决定是否接流量,liveness 决定是否重启,startup 为慢启动应用争取时间。requests 是调度承诺,limits 是运行上限,HPA 根据指标调整副本数。

机场类比:readiness 是“登机口是否开放”,liveness 是“飞机是否还具备飞行能力”,startup 是“飞机仍在完成起飞前检查”。三者回答不同问题,不能用同一敷衍答案。
对象关系 · 资源治理闭环
Scheduler按 requests 选节点
Pod受 limits 约束
metrics →
HPA调整 replicas
Deployment创建/删除 Pod
kubectl top pods
kubectl describe pod POD_NAME
kubectl get hpa
kubectl autoscale deployment hello-k8s --cpu-percent=70 --min=2 --max=10

resources:
  requests: {cpu: 20m, memory: 16Mi}
  limits:   {cpu: 200m, memory: 64Mi}

CPU 超限通常被节流,内存超限可能触发 OOMKilled。requests 设得过高浪费容量,过低则造成节点过度承诺。

易错点:liveness 检查依赖外部数据库。数据库短暂故障会让所有应用 Pod 一起重启,制造更大雪崩。liveness 应只判断进程是否无法自愈。
动手:查看当前 Pod 的 QoS、requests、limits 和 probe 事件;模拟 readiness 失败并确认 Pod 存活但不再接流量。
CHAPTER 12

选择正确工作负载:Job、CronJob、StatefulSet、DaemonSet

Deployment 适合无状态长期服务,但不是万能锤。一次性任务用 Job,定时任务用 CronJob,需要稳定身份和存储绑定的副本用 StatefulSet,每节点一个实例用 DaemonSet。

用工类比:Deployment 是轮班客服;Job 是完成即离场的搬家队;CronJob 是每天凌晨来的清洁工;StatefulSet 是有固定座位和档案柜的会计;DaemonSet 是每层楼都必须有的保安。
对象关系 · 需求到控制器
持续无状态Deployment
完成型任务Job/CronJob
稳定身份StatefulSet
每节点一个DaemonSet
kubectl create job migrate --image=myapp:v1 -- ./migrate
kubectl create cronjob cleanup --image=busybox --schedule="0 2 * * *" -- echo clean
kubectl get jobs,cronjobs,statefulsets,daemonsets
kubectl logs job/migrate

StatefulSet 提供稳定序号、DNS 和 PVC 绑定,但不会替你解决数据库复制、一致性、备份或故障转移。

易错点:看到“有状态”就立即上 StatefulSet。若状态可放在外部数据库,对应用保持无状态通常更简单、更易扩缩。
动手:写一个输出当前时间后退出的 Job,再改为 CronJob。观察成功次数、并发策略和历史保留。
CHAPTER 13

多租户与安全:Namespace、RBAC、ServiceAccount、NetworkPolicy

Namespace 提供命名和策略边界,ServiceAccount 是 Pod 的集群身份,RBAC 决定身份能操作哪些 API,NetworkPolicy 限制 Pod 之间的网络流量。

办公楼类比:Namespace 是不同部门,ServiceAccount 是员工证,RBAC 是门禁权限表,NetworkPolicy 是楼层间的防火门。只有部门名字不同,并不会自动阻止员工串门。
对象关系 · 最小权限
Pod使用 ServiceAccount
identity →
RoleBinding绑定主体与权限
Roleverbs + resources
NetworkPolicy限制数据面流量
kubectl auth can-i get secrets --as=system:serviceaccount:default:app
kubectl get role,rolebinding,serviceaccount -A
kubectl create namespace learning
kubectl config set-context --current --namespace=learning

RBAC 管 API 权限,不管普通 TCP 流量;NetworkPolicy 管网络流量,不管谁能读取 Kubernetes Secret。两者互补。

易错点:给应用绑定 cluster-admin 只为“先跑起来”。一旦容器被攻破,攻击者就可能控制整个集群。
动手:创建只允许读取 ConfigMap 的 Role,把它绑定给专用 ServiceAccount,再用 kubectl auth can-i 验证允许与拒绝。
CHAPTER 14

可观测性与故障树:按层排查,而不是随机试命令

排障从症状所在层向下验证:入口是否到达 Ingress、Service 是否有 Endpoint、Pod 是否 Ready、容器是否运行、应用是否健康、依赖是否可达。

水管类比:水龙头没水时,不应先拆水厂。先看总阀、楼层阀、房间阀和管道压力。Kubernetes 排障也要沿流量路径逐段确认事实。
故障树 · 请求失败
Ingress?规则/控制器
Service?端口/selector
Endpoint?Ready Pod
Application?日志/依赖
kubectl get pods -o wide
kubectl describe pod POD_NAME
kubectl logs POD_NAME --previous
kubectl get events --sort-by=.lastTimestamp
kubectl get endpointslice
kubectl exec POD_NAME -- wget -qO- http://localhost:8080/healthz
kubectl debug POD_NAME -it --image=nicolaka/netshoot
状态优先检查
Pending资源、调度约束、PVC、镜像拉取
CrashLoopBackOff当前/previous 日志、启动命令、配置
Running 但不通readiness、Service selector、targetPort
易错点:第一反应就是重启。重启可能清掉现场并暂时掩盖根因。先保存事件、日志、配置差异和时间线。
动手:分别制造错误镜像名、错误 selector、错误健康路径,按固定故障树定位,不使用“删 Pod 试试”。
CHAPTER 15

完整实验:从本地代码到 Kubernetes 滚动发布

现在把前面的对象串起来:测试源码、构建镜像、让集群获得镜像、应用 Kustomize 清单、验证 Service、更新版本并观察滚动发布。

端到端架构 · 本仓库示例
Go Apphealthz + config
build →
Imagedemo/hello-k8s
deploy →
Deployment2 Pods
selected by
Serviceport 80
cd docker-k8s-core
make test
make image

# kind 集群需要显式导入本地镜像;Docker Desktop 通常共享镜像
kind load docker-image demo/hello-k8s:local

kubectl apply -k k8s/
kubectl rollout status deployment/hello-k8s
kubectl port-forward service/hello-k8s 8080:80
curl http://localhost:8080/

# 修改镜像/配置后
kubectl rollout restart deployment/hello-k8s
kubectl get pods -w

Kustomize 的作用是组合和覆盖 YAML,而不是模板语言。当前 kustomization.yaml 把 ConfigMap、Deployment、Service 组成一个可应用单元。

易错点:本地镜像存在,但 kind/minikube 节点看不到它,最终出现 ImagePullBackOff。需要导入镜像或推送到集群可访问的 Registry。
验收:删除 Pod 能自愈;Service 仍可访问;更新配置后新 Pod 生效;readiness 失败时不接流量;回滚能恢复上一版本。
CHAPTER 16

学习地图:从“会部署”走向“能负责生产”

掌握 Kubernetes 不是记住所有 kind,而是能解释对象之间的责任边界,并在故障时沿控制链、所有者链和流量链寻找证据。

城市类比:Docker 负责造标准车辆,Kubernetes 负责道路、调度、交通规则和救援系统。会开车不等于会管理城市;生产能力来自对系统关系的理解。
能力关系图 · 三条主线
交付链Source → Image → Registry → Runtime
控制链Deployment → ReplicaSet → Pod → Container
流量链Client → Ingress → Service → EndpointSlice → Pod
数据链ConfigMap/Secret → Pod;PVC → PV → Storage

建议练习顺序

  1. 只用 Docker 运行、配置、观察服务。
  2. 部署到本地 K8s,理解对象所有者关系。
  3. 制造三类故障并按故障树恢复。
  4. 加入 Ingress、HPA、RBAC、NetworkPolicy。
  5. 最后学习 Helm、GitOps、监控告警和供应链安全。
能画出请求路径能解释三种 Probe能选择工作负载能判断数据生命周期能用证据排障
# 课程收尾验证
make test
kubectl kustomize k8s/
kubectl diff -k k8s/

# 然后完成配套测验
open quiz.html
易错点:沉迷 YAML 数量,忽略应用本身的超时、幂等、优雅退出、数据库迁移和可观测性。平台只能放大应用已有的工程质量。
毕业练习:不看答案画出本项目四条关系链,并向别人解释“为什么 Pod 可以被删除,而 Service 不应依赖某个 Pod”。

你现在应该带走什么

镜像定义可交付内容,容器提供进程隔离,Pod 是调度单位,Deployment 管理版本与副本,Service 稳定寻址,控制器持续纠偏。配置、数据、权限、资源和流量各有独立对象。

接下来完成 进阶 Quiz,再按第 15 章完整运行一次项目。真正的掌握来自“预测系统行为 → 操作 → 观察证据 → 修正模型”。