你已有计算机基础,所以我们不会回避进程、文件系统、网络命名空间或控制循环。每一章先讲“为什么”,再讲对象关系和工作机制,最后通过命令与当前仓库里的服务把抽象概念落地。
先理解问题:为什么“在我机器上能跑”还不够
程序不是孤立的二进制。它依赖运行时、系统库、配置、端口、文件和启动顺序。部署失败通常不是代码错了,而是运行环境与开发环境不一致。
核心逻辑
一次可靠交付必须回答三个问题:运行什么、用什么配置运行、由谁保证它持续运行。Docker 重点回答前两个,Kubernetes 重点回答第三个。
# 先把示例作为普通进程运行
cd docker-k8s-core
go test ./...
go run ./app
curl http://localhost:8080/healthz
APP_MESSAGE 后启动服务,观察代码不变而运行行为改变。思考哪些内容应进入镜像,哪些应在部署时注入。进程、虚拟机与容器:隔离发生在哪里
容器不是轻量虚拟机。容器里的应用仍是宿主机内核管理的普通进程,只是它看到的进程、网络、挂载点和资源范围被 namespace 与 cgroup 限制了。
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离边界 | 虚拟硬件与独立内核 | 进程视图与资源 |
| 启动速度 | 通常秒到分钟 | 通常毫秒到秒 |
| 安全边界 | 通常更强 | 共享内核,需额外加固 |
# 容器仍能在宿主机进程列表中找到
docker run --rm -d --name demo nginx:alpine
docker top demo
docker stats demo
docker inspect demo
ps、hostname 和网络接口,找出哪些视图被隔离、哪些内核信息仍然共享。Docker 四个核心对象:Dockerfile、镜像、容器、仓库
Dockerfile 是构建配方,镜像是不可变模板,容器是模板的一次运行,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。验证“镜像相同,实例配置不同”。Dockerfile:分层缓存、多阶段构建与最小镜像
每条构建指令都会形成可复用层。稳定且昂贵的步骤应靠前,经常变化的源码应靠后。多阶段构建把编译环境和运行环境分开。
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 源码,再看哪些层重新执行。Docker 网络、端口、Volume、配置与 Compose
容器有自己的网络命名空间。应用监听容器端口,端口发布把宿主机流量转发进去。Volume 把数据生命周期从容器生命周期中分离。
-p 8080:80 是告诉前台:外部拨 8080 时转到房间的 80。# 环境变量、端口、命名 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。容器运行规范:安全、日志、资源与排障
一个“能启动”的镜像离可运营还很远。生产容器应使用非 root 用户、只读文件系统、最小权限、明确资源上限,并能通过日志和健康端点解释自己的状态。
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 ...
Kubernetes 架构:声明期望,而不是遥控每台机器
Kubernetes 的关键不是“远程运行 Docker”,而是声明期望状态。API Server 保存意图,控制器持续比较现实与期望,调度器为新 Pod 选择节点,kubelet 在节点上落实计划。
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 当普通脚本执行。它实际是在更新声明,随后异步控制循环才逐步收敛;命令成功不等于业务已经就绪。kubectl get pods -w,观察控制器自动补回副本。Pod、ReplicaSet 与 Deployment:工作负载如何逐层管理
Pod 是最小调度单位,可包含共享网络和 Volume 的一个或多个容器。ReplicaSet 维持副本数,Deployment 再管理 ReplicaSet,从而实现声明式更新与回滚。
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 和持久化数据层。
Service、DNS 与 Ingress:流量怎样找到短命的 Pod
Pod 会替换,IP 会变化。Service 通过 label selector 维护后端集合,并提供稳定虚拟 IP 与 DNS 名。Ingress 或 Gateway 再把集群外 HTTP 流量路由到 Service。
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 通常借助云负载均衡器暴露到外部。
ConfigMap、Secret、Volume 与持久化存储
镜像应在环境间保持一致。ConfigMap 注入普通配置,Secret 承载敏感值,Volume 为 Pod 提供文件视图,PVC 则向集群申请具有独立生命周期的持久化存储。
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 静态加密、外部密钥系统和审计。
APP_MESSAGE,执行 kubectl rollout restart deployment/hello-k8s,验证新 Pod 读取新值。Probe、requests、limits、调度与自动扩缩容
readiness 决定是否接流量,liveness 决定是否重启,startup 为慢启动应用争取时间。requests 是调度承诺,limits 是运行上限,HPA 根据指标调整副本数。
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 设得过高浪费容量,过低则造成节点过度承诺。
选择正确工作负载: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 绑定,但不会替你解决数据库复制、一致性、备份或故障转移。
多租户与安全:Namespace、RBAC、ServiceAccount、NetworkPolicy
Namespace 提供命名和策略边界,ServiceAccount 是 Pod 的集群身份,RBAC 决定身份能操作哪些 API,NetworkPolicy 限制 Pod 之间的网络流量。
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 只为“先跑起来”。一旦容器被攻破,攻击者就可能控制整个集群。kubectl auth can-i 验证允许与拒绝。可观测性与故障树:按层排查,而不是随机试命令
排障从症状所在层向下验证:入口是否到达 Ingress、Service 是否有 Endpoint、Pod 是否 Ready、容器是否运行、应用是否健康、依赖是否可达。
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 |
完整实验:从本地代码到 Kubernetes 滚动发布
现在把前面的对象串起来:测试源码、构建镜像、让集群获得镜像、应用 Kustomize 清单、验证 Service、更新版本并观察滚动发布。
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 组成一个可应用单元。
学习地图:从“会部署”走向“能负责生产”
掌握 Kubernetes 不是记住所有 kind,而是能解释对象之间的责任边界,并在故障时沿控制链、所有者链和流量链寻找证据。
建议练习顺序
- 只用 Docker 运行、配置、观察服务。
- 部署到本地 K8s,理解对象所有者关系。
- 制造三类故障并按故障树恢复。
- 加入 Ingress、HPA、RBAC、NetworkPolicy。
- 最后学习 Helm、GitOps、监控告警和供应链安全。
# 课程收尾验证
make test
kubectl kustomize k8s/
kubectl diff -k k8s/
# 然后完成配套测验
open quiz.html
你现在应该带走什么
镜像定义可交付内容,容器提供进程隔离,Pod 是调度单位,Deployment 管理版本与副本,Service 稳定寻址,控制器持续纠偏。配置、数据、权限、资源和流量各有独立对象。
接下来完成 进阶 Quiz,再按第 15 章完整运行一次项目。真正的掌握来自“预测系统行为 → 操作 → 观察证据 → 修正模型”。