这次我们不聊具体工具怎么装,而是先解决一个很多人卡了很久的概念问题:Docker 我已经用得挺顺手了,容器能启动、端口能映射、数据能挂载,为什么社区、公司、面试题里还要天天提 K8s?Docker 和 K8s 到底是不是二选一的关系?
先说结论:Docker 解决的是“怎么把应用打包成标准单元并跑起来”的问题,K8s 解决的是“几百上千个这样的单元如何调度、容灾、升级、对外提供服务”的问题。两者是不同层级的工具,不是替代关系。你可以只用 Docker,但一旦应用规模变大、节点变多、发布变频繁,纯手工维护 Docker 容器会非常痛苦,这时候 K8s 的价值就出来了。
这篇文章会把 Docker 与 K8s 的定位差异、K8s 解决的典型问题、集群环境下的部署与常用操作讲清楚。文章末尾还整理了从 Docker 过渡到 K8s 的常见问题和排查思路,如果你正在准备 K8s 相关学习或面试,这篇可以直接收藏。
1. Docker 与 K8s 核心能力速览
在深入之前,先把两者放在一张表里对比,后面所有内容都围绕这张表展开。
| 能力项 | Docker | Kubernetes(K8s) |
|---|---|---|
| 定位 | 容器引擎,负责镜像构建、容器生命周期管理 | 容器编排平台,负责大规模容器的调度和管理 |
| 核心对象 | 镜像、容器、网络、数据卷 | Pod、Deployment、Service、Namespace、ConfigMap 等 |
| 单机/集群 | 单机为主,Docker Swarm 可做简单集群但能力有限 | 天然面向多节点集群 |
| 弹性伸缩 | 需要手动起容器,没有内置自动伸缩 | 支持 HPA 自动伸缩,根据 CPU、内存或自定义指标扩缩容 |
| 故障恢复 | 容器挂了不会自动重启到其他机器 | 通过 ReplicaSet 和控制器保证期望实例数 |
| 滚动更新 | 需要自己写了脚本或用 Compose 手动更新 | 原生支持滚动更新、金丝雀发布、回滚 |
| 服务发现与负载均衡 | 需要自己搭或依赖外部组件 | 内置 kube-proxy 和 Service 机制 |
| 配置管理 | 通过环境变量或挂载文件 | ConfigMap + Secret,支持动态挂载 |
| 存储编排 | 支持 volume 挂载,但跨节点数据迁移麻烦 | PV/PVC 抽象,存储与 Pod 生命周期解耦 |
| 学习成本 | 低,几天能上手 | 较高,涉及控制平面、工作节点、网络插件等多个概念 |
| 适合场景 | 本地开发、单机部署、CI/CD 构建镜像 | 生产环境多节点、高可用、弹性伸缩、微服务治理 |
从表格能看出来,Docker 是“工具”,K8s 是“平台”。K8s 本身不构建镜像,它运行的就是 Docker(或其他符合 CRI 标准的运行时)构建出来的容器。
2. 为什么 Docker 足够好,但还是不够?
Docker 在单个节点上做的事情非常出色。你写一个 Dockerfile,build 出镜像,run 起容器,映射端口,挂载数据,一套流程非常顺滑。配合 Docker Compose,你甚至可以用一个 YAML 文件把 Web 服务、MySQL、Redis 一次性拉起来,本地开发体验很好。
但生产环境的问题不是“跑起来”,而是“持续稳定地跑”。
举几个实际场景:
第一,节点故障。假设你有 10 台服务器,每台跑着 20 个容器。某一台服务器硬件损坏或系统崩溃,这 20 个容器就全部不可用了。如果靠人工发现并去其他机器上重新启动,恢复时间取决于你的响应速度,可能十分钟,也可能半天。K8s 的做法是:kubelet 定期上报节点状态,控制平面发现节点失联后,会把该节点上的 Pod 调度到其他健康节点重新创建,整个过程无需人工干预。
第二,流量突增。你的服务突然被大量用户访问,Docker 环境下手动再起几个容器,还要手动改负载均衡配置,操作繁琐且容易出错。K8s 里只需要调整 Deployment 的副本数,或者配置好 HPA,让系统根据 CPU 使用率自动扩容。流量下降后再自动缩容,既保证可用性又节约资源。
第三,发布与回滚。每次更新代码,Docker 环境下的常规操作是:停止旧容器、拉新镜像、启动新容器。这个过程会导致服务短暂不可用。如果更新出问题,还要重新拉旧镜像再启动一次。K8s 的 Deployment 支持滚动更新,默认策略下会先启动一个新的 Pod,等它健康检查通过后再杀掉一个旧 Pod,逐个替换,整个过程中服务不中断。如果更新失败,一条命令就能回滚到上一个版本。
第四,服务发现与负载均衡。Docker 容器重启后 IP 会变化,A 服务要调用 B 服务,如果 B 的 IP 变了,A 就要跟着改配置。这在微服务架构下完全不可接受。K8s 的 Service 资源为一组 Pod 提供稳定的虚拟 IP 和 DNS 名称,Pod 挂了重建,Service 会自动更新后端列表,调用方无感知。
这四个场景就是 K8s 存在的核心理由:自动调度、弹性伸缩、滚动更新、服务发现。
3. K8s 核心架构:控制平面与工作节点
理解了为什么需要 K8s,再来看它是怎么实现的。K8s 的架构可以分成两部分:控制平面(Control Plane)和工作节点(Worker Node)。
3.1 控制平面组件
控制平面是集群的“大脑”,负责做出全局决策。主要组件如下:
- kube-apiserver:所有组件和 kubectl 命令的入口,提供 REST API,是集群的通信枢纽。
- etcd:分布式键值存储,保存集群全部状态数据,包括 Pod 信息、配置、期望状态等。etcd 挂了集群就“失忆”了,所以生产环境至少要三节点 etcd 保证高可用。
- kube-scheduler:负责决定新创建的 Pod 应该放在哪个节点上。调度时会考虑节点资源、亲和性、污点等因素。
- kube-controller-manager:运行各种控制器,比如节点控制器、副本控制器、端点控制器等。它确保集群的实际状态不断趋向于用户声明的期望状态。
3.2 工作节点组件
工作节点是真正跑业务容器的地方。每个节点上必须有以下组件:
- kubelet:节点上的“代理人”,负责与 apiserver 通信,管理本节点 Pod 的生命周期,执行启动、停止、健康检查等操作。
- kube-proxy:维护节点上的网络规则,实现 Service 的流量转发和负载均衡。
- 容器运行时:真正创建和运行容器的组件,常见的是 containerd,也有用 CRI-O 的,Docker 本身也可以作为运行时,但新版本 K8s 中已不推荐直接依赖 Docker 守护进程。
3.3 核心资源对象
K8s 的一切操作都围绕资源对象展开。以下是最常用的一组:
- Pod:最小的调度单元。一个 Pod 可以包含一个或多个容器,这些容器共享网络命名空间和存储卷。通常一个 Pod 只放一个主容器。
- Deployment:无状态应用的控制器。声明期望副本数,管理 Pod 的创建、更新、回滚。绝大多数 Web 服务都是用 Deployment 部署的。
- Service:为一组 Pod 提供稳定的访问入口。通过 Label Selector 选择后端 Pod,支持 ClusterIP、NodePort、LoadBalancer 三种常见类型。
- Namespace:逻辑隔离单元。不同团队、不同环境可以放在不同 Namespace 下,避免资源命名冲突。
- ConfigMap 与 Secret:配置管理。ConfigMap 存放普通配置,Secret 存放敏感信息,两者都可以以环境变量或文件方式挂载到 Pod 中。
4. Docker Compose 与 K8s 的定位区别
很多人在学习时会有一个疑问:Docker Compose 也是用 YAML 描述多容器应用,也能一次启动多个服务,它和 K8s 有什么区别?
简单说,Compose 是单机多容器的编排工具,K8s 是跨节点大规模容器编排平台。
Compose 的 YAML 文件里定义 service、network、volume,然后一条docker compose up -d就能把整套应用拉起来。这在开发环境非常方便。但 Compose 本身不管节点故障、不管自动扩容、不管滚动更新。你在 Compose 里定义了 3 个副本,某一台机器挂了,这 3 个副本不会自动跑到别的机器上。
K8s 中也有类似 Compose 的声明式文件,但描述的资源对象更丰富。比如下面这个文件,描述了一个 Nginx 应用的期望状态:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80这份 YAML 的含义是:我希望集群中有 3 个带有app: nginx标签的 Pod,每个 Pod 运行一个 nginx:1.25 容器。提交给 K8s 后,控制平面会保证集群里始终有 3 个这样的 Pod 在运行。如果你手动删掉一个,控制器会马上创建一个新的补上。
再配合 Service 暴露访问入口:
apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80 type: NodePort这条 Service 会把集群内所有标签为app: nginx的 Pod 自动纳入负载均衡池。Pod IP 无论怎么变化,这个 Service 的访问入口始终保持稳定。
5. K8s 集群环境下的 LNMP 与 MySQL 部署思路
现在很多实际业务是 LNMP(Linux + Nginx + MySQL + PHP)架构或 MySQL 主从架构。这类有状态服务在 K8s 里部署方式与无状态应用差别很大,这也是从 Docker 迁移到 K8s 时最容易踩坑的地方。
5.1 无状态应用:PHP-FPM + Nginx
PHP-FPM 和 Nginx 都是无状态应用,适合用 Deployment 部署。典型做法是 Nginx 和 PHP-FPM 分开两个 Deployment,PHP-FPM 通过 Service 暴露给 Nginx 调用。这里的思路是:Nginx 的配置里不再是写死 PHP-FPM 的 IP,而是写 PHP-FPM 这个 Service 的 DNS 名称。
如果 Nginx 和 PHP-FPM 需要共享代码目录,使用 NFS 或对象存储挂载到两个 Pod 上。这里强调一下,不要把代码打进镜像。代码应该挂载到共享存储里,这样更新代码时只需要重新挂载即可,不需要重新构建镜像。
5.2 有状态应用:MySQL 主从
MySQL 这类数据库不能直接用 Deployment 部署,因为每个 Pod 的存储必须独立,并且实例之间需要稳定的网络标识。K8s 提供了 StatefulSet 来解决这个问题。
StatefulSet 的特点是:每个 Pod 有固定的名称和稳定的网络标识,Pod 重建后名称不变,存储卷也不会被删除。
MySQL 主从在 K8s 中的常见方案有两种:一种是手动部署一主一从两个 StatefulSet,通过配置指定主从关系;另一种是使用 Operator 自动化管理,比如 Percona Operator 或 KubeBlocks。Operator 本质上是一个扩展控制器,能感知 MySQL 集群状态,自动处理故障切换、备份恢复等复杂逻辑。
如果你刚入门,建议先从手动方式开始,理解 StatefulSet 的存储和网络行为后再考虑 Operator。
5.3 Single Node 场景:K8s 单节点部署
如果你只是想学习 K8s,不打算一开始就搭多节点集群,可以用以下方式快速入门:
- Minikube:本地单节点集群,适合学习和开发测试。
- Kubeadm:官方推荐的集群搭建工具,支持单节点控制平面和工作节点合并部署。
- Kind:把 K8s 集群跑在 Docker 容器里,适合 CI 环境。
单节点集群能覆盖大部分学习场景,比如 Deployment 发布、Service 暴露、ConfigMap 使用等。但生产环境的高可用能力(节点故障迁移、多副本调度)在单节点上是体验不到的。
6. K8s 常用命令与实际操作示例
这里整理一套从零开始的操作流程。假设你已经通过 kubeadm 或 Minikube 搭好了一个 K8s 集群,并且 kubectl 已经配置好。
6.1 查看集群状态
# 查看节点信息 kubectl get nodes # 查看节点详细信息 kubectl describe node <node-name> # 查看所有 Namespace kubectl get namespaces6.2 部署一个应用
首先创建一个 Deployment 文件,保存为 nginx.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80然后执行:
# 提交资源定义到集群 kubectl apply -f nginx.yaml # 查看 Deployment 状态 kubectl get deployment # 查看 Pod 列表 kubectl get pods -o wide # 查看 Pod 日志 kubectl logs <pod-name> # 进入 Pod 内部排查 kubectl exec -it <pod-name> -- /bin/bash6.3 暴露服务对外访问
# 将 Deployment 暴露为 NodePort 类型的 Service kubectl expose deployment nginx-deployment --type=NodePort --port=80 # 获取 Service 信息 kubectl get svc看到类似80:30080/TCP的输出后,就可以通过http://任意节点IP:30080访问 Nginx 服务了。
6.4 弹性伸缩
# 手动扩容到 5 个副本 kubectl scale deployment nginx-deployment --replicas=5 # 查看扩容结果 kubectl get pods # 手动缩容到 3 个副本 kubectl scale deployment nginx-deployment --replicas=36.5 滚动更新与回滚
# 更新镜像到 1.26 版本 kubectl set image deployment/nginx-deployment nginx=nginx:1.26 # 查看滚动更新状态 kubectl rollout status deployment/nginx-deployment # 如果更新失败,回滚到上一个版本 kubectl rollout undo deployment/nginx-deployment # 查看历史版本 kubectl rollout history deployment/nginx-deployment6.6 删除资源
# 删除 Deployment,连带删除它管理的全部 Pod kubectl delete deployment nginx-deployment # 删除 Service kubectl delete svc nginx-service注意:kubectl delete会直接删除资源,没有任何确认提示,生产环境操作前务必确认资源名称。
7. K8s 故障排查与解决方法
K8s 排查问题的难度比 Docker 高一截,因为涉及组件更多。下面按「应用层 -> 网络层 -> 集群层」梳理排查路径。
7.1 Pod 一直 Pending
现象:kubectl get pods显示 Pod 状态为 Pending,没有变成 Running。
可能原因:
- 集群资源不足,没有节点能满足 Pod 的 CPU/内存请求。
- 存在节点亲和性或污点限制,Pod 无法调度到任何节点。
- 节点上有资源配额限制。
排查方式:
kubectl describe pod <pod-name>输出里会明确写出调度失败的原因,比如0/3 nodes are available: insufficient cpu。解决方式就是扩容节点、下调资源请求配置,或修改亲和性规则。
7.2 Pod 一直 CrashLoopBackOff
现象:Pod 反复创建又崩溃,状态为 CrashLoopBackOff。
可能原因:
- 容器启动命令错误或应用启动即退出。
- 启动依赖的配置缺失,比如 ConfigMap 没挂载对。
- 健康检查探针配置不合理,应用还没就绪就被判定为错误。
排查方式:
# 查看完整事件信息 kubectl describe pod <pod-name> # 查看容器日志 kubectl logs <pod-name> --previous--previous参数很关键,Pod 崩溃后当前日志可能已经为空了,加了这个参数才能看到容器上一次运行的输出。
7.3 Service 无法访问
现象:Pod 都正常,但通过 Service 访问不通。
排查思路按顺序进行:
# 1. 检查 Service 是否选中了正确的 Pod kubectl get endpoints <service-name> # 2. 如果 endpoints 为空,说明 selector 标签没匹配上 kubectl describe svc <service-name> # 3. 检查 Pod 标签 kubectl get pods --show-labels # 4. 进入集群内测试 Service DNS 解析 kubectl run curl-test --image=curlimages/curl -it --rm -- shendpoints 为空是 Service 不通的最常见原因,基本都是 selector 里的标签写错了。
7.4 节点 NotReady
现象:kubectl get nodes显示节点处于 NotReady 状态。
可能原因:
- 节点上的 kubelet 服务停了。
- 节点资源耗尽。
- 网络插件问题导致节点与控制平面通信异常。
排查方式:
# 查看节点事件 kubectl describe node <node-name> # 登录到该节点,查看 kubelet 状态 systemctl status kubelet # 查看 kubelet 日志 journalctl -u kubelet -f节点 NotReady 时,控制平面会等待一段时间后把该节点上的 Pod 调度到其他节点。如果集群只有这一个节点,Pod 会一直处于 Pending 状态,直到节点恢复。
7.5 镜像拉取失败
现象:Pod 状态为 ImagePullBackOff。
可能原因:
- 镜像名或标签写错。
- 私有仓库需要认证信息,Pod 没有配置 imagePullSecrets。
- 节点无法访问镜像仓库,尤其是国内访问部分公共仓库不稳定时。
排查与解决:
kubectl describe pod <pod-name>输出里会显示拉取失败的具体原因。如果是认证问题,创建 Secret 并绑定到 Deployment:
kubectl create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=<用户名> \ --docker-password=<密码> # 在 Deployment 的 template 下增加 imagePullSecrets 配置spec: template: spec: imagePullSecrets: - name: regcred containers: - name: app image: registry.example.com/app:v1国内访问公共镜像仓库慢的问题,可以通过配置镜像加速器或使用自建仓库方案解决,这部分内容按实际环境调整即可。
8. 从 Docker 到 K8s:最佳实践与建议
8.1 先保持 Docker 习惯,再渐进式引入 K8s
不要把已有业务一次性全部迁移到 K8s。建议路径是:先保持 Docker Compose 支撑现有业务,同时搭一套 K8s 测试环境,把非核心服务先迁过去跑一段时间,验证稳定后再扩大范围。
8.2 给容器加上资源请求与限制
K8s 调度器依赖容器的资源请求(requests)来做调度决策,不设置 requests 会导致调度失衡。同时设置 limits 防止单个 Pod 耗尽节点内存。建议所有 Deployment 都显式配置这两个参数:
resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi8.3 使用 ConfigMap 管理配置,不要把配置打进镜像
镜像只包含应用代码和基础环境,环境相关的配置全部放到 ConfigMap 和 Secret 中。这样同一份镜像可以在测试、预发、生产三个环境复用,只是挂载的配置不同。
8.4 设计好健康检查探针
K8s 默认情况下只检查容器进程是否存活,进程活着但应用可能已经无法响应请求了。给关键服务配置 livenessProbe 和 readinessProbe,尤其是 Web 服务,让 K8s 能准确感知服务真实状态。
8.5 有状态服务单独管理
MySQL、Redis、Elasticsearch 这类有状态服务,不要和无状态应用混在一起用 Deployment 部署。优先使用 StatefulSet 或成熟的 Operator 方案。如果数据量不大且对可用性要求不高,初期也可以继续用云数据库托管服务,减少运维压力。
8.6 关注权限边界
K8s 的 RBAC 权限模型比较复杂。初期可以先用 Namespace 做逻辑隔离,不同团队使用不同 Namespace,避免互相影响。给 CI/CD 系统配置独立的 ServiceAccount,只授予所需的最小权限,不要直接使用管理员证书。
8.7 日志和监控要提前规划
没有日志和监控的 K8s 集群,出问题排查会非常痛苦。建议在集群搭建初期就部署指标监控和日志采集方案,至少要保证 kubelet 日志、容器 stdout 日志、kube-apiserver 审计日志都能留存。常用的开源方案有 Prometheus 生态和 Loki 日志栈,具体选型按团队熟悉度来定。
9. 什么时候不需要 K8s?
也来明确一下反面场景。不是所有项目都适合上 K8s,以下情况可以继续用 Docker 或 Docker Compose:
- 单机部署的个人项目或内部工具,没有多节点需求。
- 应用实例数常年只有 1 到 2 个,没有自动扩容需求。
- 没有专职运维或对基础设施不熟悉的小团队。
- 项目生命周期短,上线一次后很少更新。
K8s 是有学习成本和运维成本的。控制平面组件、网络插件、存储插件、证书管理、升级维护,每一项都需要人力和时间去维护。如果你的业务用 Docker Compose 就能稳定跑,没必要为了“技术先进”强行引入 K8s。
10. 总结
回到题目本身:有了 Docker,为什么还要 K8s?答案不是“Docker 不好”,而是“单机工具无法解决集群问题”。Docker 把应用标准化为镜像和容器,K8s 在标准化的基础上提供了调度、伸缩、自愈、滚动更新、服务发现等平台级能力。两者是协作关系,Docker 负责“打包”,K8s 负责“编排”。
如果你正在学习 K8s,第一步别急着部署集群。先用 Docker 把容器、镜像、数据卷、网络这些基础概念吃透,再通过 Minikube 或 kubeadm 搭一套单节点集群,按本文第三节和第六节的流程把 Deployment、Service、滚动更新依次跑一遍。跑通之后再尝试模拟故障场景:删掉一个 Pod 看它怎么恢复,更新镜像看它怎么滚动,把节点停掉看它怎么迁移。这些实验做完,你对 K8s 的理解会比看十篇原理文章都深入。
最后留一个可以继续深入的方向:K8s 的 GPU 调度与 AI 推理服务部署。当前很多团队把模型推理服务跑在 K8s 上,利用其弹性伸缩和资源调度能力管理 GPU 资源。这一步涉及 Device Plugin、GPU 显存调度、自定义指标伸缩等概念,等这篇文章里的内容都掌握了,可以往这个方向继续挖。