Docker与Kubernetes关系本质:容器运行时与编排系统的分工
1. 这不是“选哪个”的问题,而是“谁在什么位置干活”的问题
刚入行那会儿,我常被客户问:“我们该用 Kubernetes 还是 Docker?”——问得特别认真,眼神里带着技术选型的沉重感。后来我才明白,这问题本身就像在问“该用扳手还是螺丝刀”,听起来合理,实则错位。Docker 是个容器运行时,它负责把一个应用连同它的依赖、配置、文件系统一起打包成一个可移植的镜像,然后在宿主机上拉起一个隔离的进程环境来运行它。说白了,Docker 就是那个拧螺丝的工人,干的是单点执行的活。Kubernetes 则完全不同,它压根不碰具体的应用怎么启动、怎么读配置;它只管调度、编排、扩缩容、故障自愈、服务发现和流量治理——它是整个工地的项目经理兼调度中心,手里攥着几十上百台服务器的资源池,盯着成百上千个 Docker 容器(或其他兼容 OCI 的运行时)在哪儿跑、跑得稳不稳、要不要加人手、坏了谁来顶上。所以,“Kubernetes vs. Docker”这个标题,本质上是个伪命题。真正该问的是:我的应用规模、团队结构、发布节奏和稳定性要求,是否已经超出了单机或小集群手动管理 Docker 容器的能力边界?如果你还在用 docker run 启动三个服务,配个 docker-compose.yml 拉起来,那 Kubernetes 不仅是杀鸡用牛刀,更是给厨房装了一套核电站控制系统。但如果你每天要发布 20+ 次、服务间调用链超过 15 层、峰值流量是平日的 8 倍、要求 99.99% 的可用性,那 Docker 就只是你交付物的“出厂包装”,而 Kubernetes 才是你生产环境的“操作系统”。关键词 Kubernetes 、 Docker 、 容器编排 、 微服务运维 、 云原生基础设施 ,全在这层分工逻辑里扎了根。这篇文章不是教你怎么二选一,而是带你亲手拆开这两个工具的“工作界面”,看清它们各自在哪条流水线上拧哪颗螺丝,以及当你的业务从手工作坊升级为智能工厂时,这条流水线该怎么重新布局。
2. 核心设计哲学与能力边界的硬核拆解
2.1 Docker:专注“单体交付”的极致封装者
Docker 的设计哲学,可以用一句话概括: 让软件运行环境从“描述文档”变成“可执行文件” 。在 Docker 出现之前,部署一个 Python Web 应用,你需要写一份《部署手册》,里面写着“请安装 Python 3.9、pip install -r requirements.txt、配置 Nginx 反向代理到 8000 端口、确保 /var/log/myapp 目录有写权限……”,这份文档在不同人的电脑上执行,十次有八次会出错。Docker 把所有这些“环境要求”全部固化进一个分层的、只读的镜像文件里。它的核心机制是 Union File System(联合文件系统) ,比如 Overlay2 驱动,它把基础镜像(如 ubuntu:22.04)、运行时依赖(如 python3.9、pip)、应用代码、配置文件,一层层叠在一起,最上层是可写的容器层。这种分层设计带来了两个关键优势:一是镜像复用率极高,100 个基于同一基础镜像的微服务,宿主机上只需存一份基础层;二是构建速度快,修改代码只重做最上层,下层缓存直接复用。我实测过一个中等规模的 Java 应用,从源码构建镜像,使用 Docker BuildKit 的 cache 机制,增量构建时间能从 6 分钟压到 42 秒。但 Docker 的能力边界也极其清晰:它不关心容器 A 和容器 B 之间怎么通信。 docker run --link 是早期的临时方案,早已废弃; docker network create 能建一个网桥,让同网络下的容器通过容器名互相 ping 通,但这只是 L2/L3 连通性,没有服务发现、没有负载均衡、没有健康检查。你无法告诉 Docker:“当用户访问 /api/orders 时,请把请求轮询转发给所有正在运行的 order-service 容器”,它没这个模块。它的 API 里也没有 “scale to 5 replicas” 这个命令。这就是为什么 docker-compose up -d --scale web=3 看似能扩缩容,但它本质是启动 3 个独立的、彼此无感知的容器实例,一旦其中一台宿主机宕机,这 3 个实例就全挂了,Docker 自己不会去另一台机器上补一个。它的可靠性模型,是单机维度的。
2.2 Kubernetes:面向“大规模协同”的分布式操作系统
如果说 Docker 解决了“软件怎么打包”,那么 Kubernetes 解决的就是“打包好的软件,在成百上千台机器上,怎么活着、怎么协作、怎么自我修复”。它的设计哲学是 声明式 API + 控制循环(Control Loop) 。你不需要告诉 Kubernetes “先创建 Pod A,再创建 Service B,再更新 Ingress C”,你只需要提交一个 YAML 文件,声明你想要的终态:“我需要 5 个 nginx 实例,暴露 80 端口,通过域名 myapp.com 访问”。Kubernetes 的各个控制器(Controller)——比如 ReplicaSet Controller、EndpointSlice Controller、Ingress Controller——会持续地“观察”集群当前状态,并与你声明的目标状态比对。如果发现只有 3 个 Pod 在运行,ReplicaSet Controller 就会立刻创建 2 个新的;如果某个 Pod 的健康探针失败,它会被自动驱逐并重建;如果新 Pod 启动成功,EndpointSlice Controller 就会把它的 IP 加入后端列表。这个“观测-比对-干预”的闭环,每秒都在集群内数以千计地并行发生。Kubernetes 的核心抽象对象,就是围绕这个闭环设计的:
- Pod :最小的调度单元,不是容器,而是一个或多个紧密耦合的容器的集合(比如主应用容器 + 一个 sidecar 日志收集容器),共享网络命名空间和存储卷。这是它区别于 Docker 单容器模型的根本。
- Service :一个稳定的网络端点(ClusterIP 或 NodePort),背后是一组动态变化的 Pod IP。它内置了 kube-proxy 组件,通过 iptables 或 IPVS 规则,实现集群内部的服务发现和四层负载均衡。你永远不用记住 Pod 的 IP,只认 Service 名。
- Deployment :声明式地管理 Pod 的副本集和滚动更新策略。你可以定义 maxSurge=1, maxUnavailable=0,这意味着更新时最多多启 1 个新 Pod,且保证旧 Pod 一个都不能少,直到新 Pod 完全就绪——这是零停机发布的底层保障。
- Ingress :七层(HTTP/HTTPS)的流量入口网关,支持基于 host 和 path 的路由规则、TLS 终止、重写等,把外部流量精准导入到不同的 Service。
提示:Kubernetes 本身不运行容器,它需要一个容器运行时(Container Runtime)。Docker Engine 曾是默认选择,但自 v1.20 起,Kubernetes 宣布弃用 dockershim,转而拥抱更轻量、更符合 OCI(Open Container Initiative)标准的运行时,如 containerd(Docker 自身也已将其作为底层运行时)或 CRI-O。这意味着,你在 Kubernetes 集群里看到的“容器”,其底层可能是 containerd 拉起的,Docker CLI 只是开发者本地的构建和调试工具。这个演进恰恰印证了二者关系的本质:Docker 是构建和分发的“前端”,Kubernetes 是运行和编排的“后端”。
2.3 关键能力对比:一张表看懂谁管什么
下表不是功能罗列,而是按“生产环境真实痛点”归类,告诉你每个能力在哪个环节起作用:
| 生产环境典型需求 | Docker (单机/小集群) 是否原生支持 | Kubernetes 是否原生支持 | 关键说明与实操影响 |
|---|---|---|---|
| 单机快速启动与调试 | ✅ 原生强大 | ⚠️ 过重,需 minikube/k3s | docker run -p 8080:80 nginx 5 秒搞定;K8s 需先 kubectl apply -f pod.yaml ,再 kubectl port-forward ,至少 30 秒起步。 |
| 镜像构建与分发 | ✅ 核心能力(Dockerfile + Registry) | ❌ 无此模块 | K8s 不管你怎么造轮子,只管轮子运过来后怎么装车。镜像构建仍是 Docker Build 或 BuildKit 的主场。 |
| 跨主机容器网络互通 | ❌ 需第三方插件(Weave, Flannel) | ✅ 原生(CNI 插件) | Docker 默认 bridge 网络只限本机;K8s 通过 CNI(如 Calico)自动为每个 Pod 分配集群唯一 IP,跨节点直通。 |
| 服务发现与 DNS | ❌ 仅限同网络容器名解析 | ✅ 原生(CoreDNS) | 在 K8s 里, curl http://orders-service:8080 在任何 Pod 里都有效;Docker 需手动维护 hosts 或用 Consul。 |
| 自动扩缩容(HPA) | ❌ 无 | ✅ 原生(Horizontal Pod Autoscaler) | 基于 CPU/内存或自定义指标(如 QPS),自动调整 Deployment 的副本数。Docker Compose 的 scale 是静态指令。 |
| 滚动更新与回滚 | ⚠️ Compose 支持,但无健康检查保障 | ✅ 原生(Deployment 策略) | K8s 更新时,新 Pod 必须通过 readinessProbe 才加入流量,livenessProbe 失败则重启,回滚 kubectl rollout undo 一键完成。 |
| 存储卷生命周期管理 | ⚠️ docker volume 本地持久化 | ✅ 原生(PV/PVC/StorageClass) | K8s 抽象出持久卷(PV)和申领(PVC),可对接 NFS、AWS EBS、Ceph 等,生命周期独立于 Pod,数据不随 Pod 消亡而丢失。 |
| 多租户与资源配额 | ❌ 无 | ✅ 原生(Namespace + ResourceQuota) | 一个集群可划分为 dev/test/prod Namespace,为每个 NS 设置 CPU/Memory 上限,避免一个项目吃光整台机器。 |
这张表的核心启示是:Docker 的价值在“交付链前端”,Kubernetes 的价值在“运行时后端”。它们不是竞争对手,而是上下游的协作者。一个健康的云原生交付流水线,必然是:开发者用 Docker 构建和测试镜像 → 镜像推送到私有 Registry → CI/CD 流水线触发 Kubernetes 的 kubectl apply → K8s 调度运行。试图用 Docker 替代 K8s 去管百台机器上的千个服务,就像用 Excel 表格去管理一个上市公司的财务总账——不是不能记,而是每次打开表格都要卡死,而且根本没法审计、没法预警、没法自动化。
3. 从零搭建一个可验证的对比实验环境
3.1 环境准备:轻量级但真实的双轨对照
为了让你亲手触摸两者的差异,我推荐一个零成本、零污染的本地实验方案: k3s + Docker Desktop 。k3s 是 Rancher 开源的轻量级 Kubernetes 发行版,专为边缘、CI/CD 和开发测试设计,二进制只有 50MB,内存占用 < 512MB, curl -sfL https://get.k3s.io | sh - 一条命令即可安装,1 分钟内启动一个单节点集群。Docker Desktop 则自带一个嵌入式的 k3s 集群(在设置里开启),同时保留完整的 Docker CLI 功能。这样,你能在同一台 Mac/Windows 电脑上,左手 docker run ,右手 kubectl apply ,实时对比。
注意:不要用 Minikube!它基于 VirtualBox/Vmware 创建完整 VM,启动慢、资源占用高、网络调试复杂。k3s 直接运行在宿主机 Linux 内核上(Docker Desktop 内部也是 LinuxKit),网络透明,
localhost:30000就能访问 NodePort,省去所有端口映射烦恼。
安装步骤精简如下:
- 下载并安装 Docker Desktop (最新版,已内置 k3s)。
- 启动 Docker Desktop,在右下角鲸鱼图标上右键 → “Settings” → “Kubernetes” → 勾选 “Enable Kubernetes”,点击 “Apply & Restart”。等待状态变为绿色 “Kubernetes is running”。
- 打开终端,验证:
此时,你的电脑就是一个微型的“混合云”:Docker 引擎负责构建和运行单个容器;k3s 集群负责编排和管理一组容器。接下来,我们用同一个应用——一个极简的 Python Flask API —— 来走通两条路径。# 检查 Docker 是否就绪 docker --version # 应输出 Docker version 24.x.x docker run hello-world # 第一次会下载镜像,看到欢迎信息即成功 # 检查 Kubernetes 是否就绪 kubectl version --short # 应输出 Client & Server 版本 kubectl get nodes # 应看到一个 Ready 状态的节点 kubectl get pods -A # 应看到 coredns、local-path-provisioner 等系统 Pod Running
3.2 实验一:Docker 单机模式——从构建到运行的全流程
我们创建一个名为 simple-api 的应用,它只有一个 /health 接口返回 JSON。
- 创建项目目录:
mkdir simple-api && cd simple-api - 编写
app.py:from flask import Flask import os app = Flask(__name__) @app.route('/health') def health(): return {"status": "ok", "host": os.getenv('HOSTNAME', 'unknown')} if __name__ == '__main__': app.run(host='0.0.0.0:5000') - 编写
requirements.txt:Flask==2.3.3 - 编写
Dockerfile:FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD ["python", "app.py"] - 构建并运行:
这就是 Docker 的全部:构建(Build)、推送(Push,此处省略)、运行(Run)。现在,你有了一个运行中的服务。但问题来了:如果这个容器意外退出(比如# 构建镜像,打标签为 simple-api:latest docker build -t simple-api:latest . # 启动容器,映射宿主机 5000 端口到容器 5000 端口 docker run -d -p 5000:5000 --name api-v1 simple-api:latest # 验证 curl http://localhost:5000/health # 返回 {"status": "ok", "host": "a1b2c3d4e5"} docker ps # 查看容器 ID 和状态kill -9它的 PID),它不会自动重启。你得手动docker start api-v1。如果想扩容到 3 个实例,得运行三次docker run,并手动管理三个不同的端口(5000, 5001, 5002),再自己搭个 Nginx 做负载均衡。这就是单机 Docker 的“天花板”。
3.3 实验二:Kubernetes 模式——声明式编排的威力
现在,我们用 Kubernetes 来管理同一个 simple-api 镜像,体验声明式的力量。
-
首先,确保镜像在 k3s 节点上可用。由于 k3s 和 Docker Desktop 共享同一个镜像仓库(
/var/lib/rancher/k3s/agent/images),我们直接将本地构建的镜像加载进去:# 将镜像保存为 tar 文件 docker save simple-api:latest > simple-api.tar # 加载到 k3s 的镜像仓库(Docker Desktop 内置 k3s 的路径) sudo k3s ctr images import simple-api.tar实操心得:在生产环境,你绝不会手动
ctr import。正确的流程是:docker build后docker push到私有 Registry(如 Harbor),然后在 K8s 的 Deployment YAML 中指定image: harbor.example.com/myproject/simple-api:v1.0。这里手动加载,只为实验快速验证。 -
编写
deployment.yaml,定义应用的“终态”:apiVersion: apps/v1 kind: Deployment metadata: name: simple-api labels: app: simple-api spec: replicas: 3 # 我要 3 个副本 selector: matchLabels: app: simple-api template: metadata: labels: app: simple-api spec: containers: - name: api image: simple-api:latest # 使用我们刚加载的镜像 ports: - containerPort: 5000 livenessProbe: # 存活探针:每 10 秒检查一次 /health httpGet: path: /health port: 5000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: # 就绪探针:启动后立即检查,通过才加入流量 httpGet: path: /health port: 5000 initialDelaySeconds: 2 periodSeconds: 5 --- # 定义 Service,为这 3 个 Pod 提供稳定入口 apiVersion: v1 kind: Service metadata: name: simple-api-service spec: selector: app: simple-api ports: - protocol: TCP port: 80 targetPort: 5000 type: NodePort # 暴露到宿主机端口,便于本地访问这个 YAML 文件,就是你对 Kubernetes 的“下单”。它没有一句“启动”、“停止”、“重启”的命令,只有“我要 3 个,它们必须健康,必须就绪,必须通过 80 端口提供服务”。
-
应用配置并验证:
# 应用 YAML kubectl apply -f deployment.yaml # 查看 Deployment 状态 kubectl get deployments # 应显示 simple-api 的 READY 3/3 # 查看 Pod 状态(会看到 3 个 Running 的 Pod) kubectl get pods -l app=simple-api # 查看 Service,找到分配的 NodePort(通常是 30000-32767 之间) kubectl get service simple-api-service # 通过 NodePort 访问(假设 NodePort 是 31234) curl http://localhost:31234/health你会得到类似
{"status": "ok", "host": "simple-api-7c8d9b4f5-abcde"}的响应,host字段是 Pod 的名字,证明流量确实打到了后端的某个 Pod 上。 -
亲手破坏,见证自愈 :
- 找到一个 Pod 的名字:
kubectl get pods -l app=simple-api - 删除它:
kubectl delete pod simple-api-7c8d9b4f5-abcde - 立刻执行
kubectl get pods -l app=simple-api,你会发现:旧 Pod 状态变为Terminating,几秒后,一个新的 Pod(名字不同)以ContainerCreating状态出现,很快变成Running。整个过程无需人工干预,ReplicaSet Controller 自动补足了副本数。 - 再次
curl http://localhost:31234/health,服务依然可用。这就是 Kubernetes 的“韧性”。
- 找到一个 Pod 的名字:
3.4 实验三:横向对比——一次发布,两种体验
现在,我们模拟一次真实发布:将应用升级到 v2 版本,返回不同的健康状态。
-
修改
app.py,添加版本标识:@app.route('/health') def health(): return {"status": "ok", "version": "v2", "host": os.getenv('HOSTNAME', 'unknown')} -
重新构建镜像并打新标签:
docker build -t simple-api:v2 . sudo k3s ctr images import simple-api.tar # 再次加载 -
Docker 方式升级 :
- 停止旧容器:
docker stop api-v1 - 删除旧容器:
docker rm api-v1 - 启动新容器:
docker run -d -p 5000:5000 --name api-v2 simple-api:v2 - 验证:
curl http://localhost:5000/health→ 看到"version": "v2" - 问题 :这期间有服务中断(stop 到 run 的间隙),且旧容器的
v1镜像还占着磁盘空间,需手动docker rmi清理。
- 停止旧容器:
-
Kubernetes 方式升级(滚动更新) :
- 修改
deployment.yaml中的image: simple-api:v2,然后:kubectl apply -f deployment.yaml - 实时观察滚动过程:
kubectl rollout status deployment/simple-api # 等待 "deployment \"simple-api\" successfully rolled out" kubectl get pods -l app=simple-api # 会看到旧 Pod 逐步 Terminating,新 Pod 逐步 Running - 关键验证 :在整个滚动过程中,持续执行
curl http://localhost:31234/health,你会发现响应始终成功,只是version字段从v1逐渐变为v2。这是因为 K8s 严格遵循maxSurge和maxUnavailable策略(默认值),确保流量不中断。 - 一键回滚 :如果 v2 版本上线后发现问题,
kubectl rollout undo deployment/simple-api,瞬间回到 v1 版本,所有 Pod 重新拉起。
- 修改
这个实验的价值在于:它剥离了所有云厂商的包装,让你在自己电脑上,用最原始的命令,亲手触摸到两种范式的温度。Docker 给你的是“确定性”——你敲下 run ,它就启动;Kubernetes 给你的是“确定性之上的弹性”——你声明“我要 3 个”,它就给你 3 个,无论机器重启、网络抖动、进程崩溃,它都默默守护着这个数字。选择哪一个,从来不是技术优劣的辩论,而是你当前业务水位线的诚实映射。
4. 真实世界中的选型决策树与避坑指南
4.1 什么时候坚决用 Docker,而不是 Kubernetes?
我见过太多团队,因为“Kubernetes 很火”就盲目上马,结果半年后 DevOps 工程师离职,整个集群没人敢动,CI/CD 流水线瘫痪,最后降级回 docker-compose 。这不是技术倒退,而是回归理性。以下场景,Docker(或 docker-compose )是更优解:
-
个人开发者或小团队(< 3 人)的日常开发与测试 :你正在写一个博客系统,本地用
docker-compose up启动 MySQL、Redis、Nginx 和你的 PHP 应用,所有服务通过docker-compose.yml的networks和depends_on关联。此时,Kubernetes 的 YAML 文件、kubectl命令、Namespace 概念,只会增加认知负担,毫无收益。docker-compose的build、up、logs、exec命令,简洁高效,就是为你量身定做的。 -
CI/CD 流水线中的构建与测试环节 :在 GitHub Actions 或 GitLab CI 中,你用
docker build构建镜像,用docker run启动一个临时数据库容器来跑单元测试。这里的容器是“一次性的、短暂的、隔离的”,Kubernetes 的调度、持久化、服务发现全是冗余。Docker 的轻量和快速启动,是 CI 环境的生命线。 -
嵌入式设备或边缘计算节点(资源极度受限) :一个运行在树莓派上的家庭自动化网关,只需要启动 2-3 个容器(MQTT Broker、Home Assistant、Node-RED)。k3s 虽然轻量,但其 etcd、kube-apiserver、controller-manager 等组件仍需至少 512MB 内存和稳定的存储。而一个
dockerd进程,256MB 内存就能稳稳运行。在这种场景下,追求“Kubernetes 兼容性”是舍本逐末。 -
遗留系统容器化改造的过渡期 :一个单体 Java 应用,你把它打包成 Docker 镜像,用
docker run -d --restart=always启动,配上docker logs -f查看日志。这已经比以前java -jar app.jar &启动强太多了。此时强行拆分成十几个微服务,再上 Kubernetes,只会把一个简单问题复杂化。Docker 是容器化的第一步,也是最坚实的基础。
实操心得:判断是否需要 Kubernetes,有一个朴素的“三问法”:
- 我的服务实例数,是否经常需要动态增减(非手动)? 如果答案是“否”,K8s 的 HPA 就是摆设。
- 我的服务,是否部署在 3 台以上的物理/虚拟机上? 如果所有容器都在一台机器上,K8s 的跨节点调度、故障转移能力就无从谈起。
- 我的团队,是否有专人(或至少一人)能理解并维护
kubectl、YAML、Helm、Prometheus 这套生态? 如果没有,K8s 不是加速器,而是定时炸弹。宁可先用docker-compose+systemd做好服务管理,等团队能力跟上再平滑迁移。
4.2 什么时候 Kubernetes 成为唯一选择?
当你的业务规模和复杂度,已经让 Docker 的“单点管理”模式不堪重负时,Kubernetes 就不再是选项,而是必需品。以下是几个典型的临界点信号:
-
服务网格(Service Mesh)成为刚需 :当你的微服务数量超过 50 个,服务间调用链路复杂,需要统一的可观测性(Tracing)、流量控制(Canary Release)、安全策略(mTLS)。Istio、Linkerd 这些服务网格,其数据平面(Envoy Proxy)必须注入到每个 Pod 中,控制平面则深度集成在 Kubernetes 的 CRD(Custom Resource Definition)之上。你无法脱离 K8s 的声明式 API 和强大的 Admission Control(准入控制)来部署和管理它们。此时,Docker 的
--network参数,连入门门槛都够不到。 -
多云/混合云架构落地 :你的业务既要跑在 AWS 上,也要跑在阿里云上,还要在客户私有数据中心部署。Kubernetes 的核心价值之一,就是提供了统一的、标准化的 API 抽象层。你写一套
deployment.yaml,在 AWS EKS、阿里云 ACK、本地 k3s 上都能运行,只需更换底层的 CNI(网络)和 CSI(存储)插件。而 Docker 的run命令,在不同云厂商的 VPC 网络、安全组、负载均衡配置下,需要写无数份适配脚本,维护成本指数级上升。 -
Serverless 架构的底层支撑 :如今流行的 Knative、OpenFaaS、KEDA 等 FaaS(函数即服务)平台,其核心就是 Kubernetes 的事件驱动扩展。它们监听 Kafka 主题、S3 对象上传、HTTP 请求等事件,动态创建 Pod 来执行函数,执行完自动销毁。这个“按需启停、毫秒级伸缩”的能力,完全建立在 K8s 的调度器(Scheduler)和控制器(Controller)之上。Docker 本身不具备事件监听和自动扩缩的机制。
-
AI/ML 训练任务的批量调度 :一个 PyTorch 分布式训练任务,需要同时启动 8 个 GPU Pod,它们之间通过 RDMA 网络高速通信。Kubernetes 的 Device Plugin 机制可以精确地将 GPU 设备分配给 Pod,而
Job和CronJob对象可以声明式地定义任务的并行度、重试策略和超时时间。你无法用docker run来协调 8 个容器的启动顺序、网络拓扑和资源绑定。
4.3 从 Docker 到 Kubernetes 的平滑迁移路径
很多团队最大的误区,是把迁移当成一场“大换血”。正确的做法,是把它看作一次“渐进式升级”。我亲历过三个成功案例,总结出一条黄金路径:
阶段一:容器化先行(Docker Only)
- 目标:所有应用(包括数据库、中间件)都打包成 Docker 镜像,通过
docker-compose在预发环境运行。 - 关键动作:建立公司级的 Dockerfile 最佳实践(多阶段构建、非 root 用户、固定 UID/GID)、私有镜像仓库(Harbor)、镜像扫描(Trivy)。
- 成果:交付物标准化,环境一致性问题消失 80%,这是后续一切的基础。
阶段二:Kubernetes 试点(K8s for Stateful Apps)
- 目标:选择一个 无状态、流量不大、但对稳定性要求极高 的核心服务(如公司内部的 SSO 认证服务),迁移到 Kubernetes。
- 关键动作:用 Helm Chart 封装该服务的 Deployment、Service、Ingress、ConfigMap;接入 Prometheus + Grafana 做基础监控;配置
livenessProbe和readinessProbe。 - 成果:团队熟悉了
kubectl、YAML、Helm,验证了 K8s 的自愈能力,建立了第一个“成功样板”。
阶段三:平台化赋能(K8s as a Platform)
- 目标:将 Kubernetes 抽象为一个内部 PaaS 平台,为所有业务线提供自助服务。
- 关键动作:开发一个简单的 Web 控制台或 CLI 工具,业务方只需填写应用名、Git 仓库地址、端口号,后台自动生成 Helm Chart 并
helm install;集成 CI/CD,git push自动触发构建、镜像推送、K8s 部署。 - 成果:DevOps 团队从“救火队员”转变为“平台建设者”,业务迭代速度提升 3 倍以上。
常见问题速查表(来自我踩过的坑):
问题现象 根本原因 解决方案 我的教训 Pod 一直 Pending, kubectl describe pod显示0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.节点被打上了 NoSchedule污点(如node-role.kubernetes.io/control-plane:NoSchedule),而你的 Pod 没有容忍(toleration)该污点。在 Pod 的 spec中添加tolerations字段,或使用kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-移除污点(仅限单节点测试)。k3s 默认会给 master 节点打污点,防止普通 Pod 调度上去。这是安全设计,不是 bug。别急着删,先学会容忍。 Service 的 ClusterIP 无法从 Pod 内部访问, curl http://my-service超时CoreDNS 解析失败,或 Service 的 selector标签与 Pod 的labels不匹配。kubectl get endpoints my-service看后端列表是否为空;kubectl get pods --show-labels确认标签;kubectl exec -it a-pod -- nslookup my-service测试 DNS。标签(label)是 K8s 的“胶水”,90% 的网络问题都源于标签对不上。养成习惯: kubectl get pods -l app=myapp和kubectl get svc -l app=myapp一起看。Ingress 404, kubectl get ingress显示 ADDRESS 为空Ingress Controller(如 nginx-ingress)未安装,或未正确关联到你的 Ingress 资源。 kubectl get pods -n ingress-nginx确认 Controller Pod Running;检查 Ingress 的ingressClassName是否与 Controller 的ingressclass名称一致。Ingress 是一个“接口”,不是“实现”。你必须先部署一个具体的 Controller(如 nginx、traefik),它才会监听并处理你的 Ingress 资源。 kubectl logs查不到日志,显示Error from server: no such file or directoryk3s 默认使用 containerd作为运行时,其日志路径与 Docker 不同,且kubectl logs依赖cri-o或containerd的日志插件。确保 k3s 启动时启用了 --disable-agent(不适用)或检查containerd配置;更可靠的方式是kubectl exec -it pod-name -- cat /proc/1/fd/1直接读取 stdout。不要迷信 kubectl logs。在 k3s 环境,crictl logs(k3s 自带的 CRI 工具)往往更准确。
5. 总结:工具
更多推荐
所有评论(0)