ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

有了Docker为什么还要K8s?一文看懂容器编排的核心价值

有了Docker为什么还要K8s?一文看懂容器编排的核心价值 刚接触容器时很多朋友都有过这样的疑问我费了不少力气把应用打成 Docker 镜像用docker run一条命令就能跑起来似乎一切都很顺畅。那为什么还要学习 Kubernetes简称 K8s它到底解决了 Docker 解决不了的问题在实际项目中这个问题的答案会越来越清晰。单机环境下用 Docker 管理一两个容器完全没问题但当容器数量增多、部署环境从一台机器变成多台机器之后情况就完全不一样了。本文会从容器编排的实际痛点出发对比 Docker 和 K8s 的定位差异再用一个完整的示例说明 K8s 在自动恢复、弹性伸缩、滚动更新上的处理方式最后整理一些入门阶段最容易踩的坑。1. 背景与核心概念1.1 从“容器化”到“容器编排”先来看一个最直观的场景。开发环境里我们用 Docker 启动一个 Nginx 服务命令非常简单docker run -d --name web -p 80:80 nginx:1.27这个命令做了什么它从镜像仓库拉取nginx:1.27镜像创建并启动一个名为web的容器然后把宿主机的 80 端口映射到容器的 80 端口。浏览器访问这台机器的 IP就能看到 Nginx 的欢迎页。对于单个应用这套流程确实够用。但回到真实业务微服务架构下一个系统可能有二十个甚至更多服务每个服务至少两个副本有的服务负载高需要从 2 个副本扩展到 10 个副本高峰期过后再缩回来某台服务器宕机了运行在上面的容器需要自动迁移到其他健康节点发布新版本时希望做到滚动发布而不是停机更新服务之间需要通过服务名互相访问容器 IP 经常变化不能写死。这些都是docker run解决不了的。Docker 本身的定位是“容器运行时”它的核心能力是构建镜像、启动容器、管理单个容器生命周期。而 K8s 的定位是“容器编排平台”它解决的是大规模容器环境下“如何调度、如何保持期望状态、如何对外提供服务”的问题。1.2 通俗理解虚拟机、Docker 与 K8s 的关系很多新手会把这三者放在一起比较其实它们不在同一个层次。虚拟机是整个操作系统级别的隔离一台物理机可以虚拟出多台虚拟机每台虚拟机有独立的操作系统内核资源开销较大。Docker 是操作系统级别的虚拟化多个容器共享宿主机内核通过 Linux 内核的 namespace 和 cgroup 实现资源隔离与限制。容器启动快、镜像体积小、资源利用率高。K8s 不直接管理容器它管理的是“一组机器上的所有容器”。它像一个调度中心告诉你哪个容器应该跑在哪台机器上、需要几个副本、资源上限是多少、访问入口是什么。用一个不太严谨但好理解的类比Docker 像是集装箱本身它负责把应用和依赖打包好而 K8s 像是港口管理系统它决定哪个箱子放在哪个泊位什么时候装卸怎么样让货物高效流转。没有集装箱港口管理系统意义不大只有集装箱没有管理系统港口就会乱套。生产环境里“有了 Docker 还要用 K8s”本质是业务规模从“几个容器”发展到“几十上百个容器”后对自动化管理的需求。1.3 什么时候必须考虑 K8s并不是所有项目都需要 K8s。如果业务还处于起步阶段服务器只有一两台手动执行docker run或者写一个简单的 Shell 脚本做备份完全够用。当出现下面这些迹象时就可以认真考虑引入 K8s业务现象单机 Docker 的困境应用实例超过 5 个启动和停止非常频繁手动执行命令容易出错难以统一管理服务需要多副本保证高可用docker run只能手动启动多个容器无法做健康检查后的自动摘除服务器超过 3 台不知道容器分布在哪个节点登录每一台机器查看容器状态效率低缺乏全局视图发布新版本要保留在线连接不能全部重启Docker 原生不支持滚动更新策略容器遇到异常退出后需要自动重启Docker 的--restartalways只在本机生效无法跨节点恢复2. 环境准备用 K8s 需要安装什么2.1 本地学习环境推荐在真正进入实践之前先搭建一个学习环境。主流的本地 K8s 环境有 Minikube、K3s、Kind还有 Docker Desktop 自带的 Kubernetes 集群。如果你的电脑上已经安装了 Docker Desktop并且版本支持可以直接在设置里开启 Kubernetes打开 Docker Desktop进入 Settings设置找到 Kubernetes 选项卡勾选 Enable Kubernetes点击 Apply Restart。等待 Kubernetes 启动完成后终端执行kubectl version --client kubectl get nodes如果能看到一个 Ready 状态的节点说明集群已经正常运行。如果没有使用 Docker Desktop也可以选择 Minikubeminikube start --driverdockerMinikube 会创建一个虚拟机级别的单节点集群适合学习和实验。生产环境则不建议在开发机上模拟通常会使用云厂商的托管集群或者自建多节点集群。2.2 需要安装的命令行工具无论使用哪种集群都会用到kubectl这个命令行工具。Linux 或 macOS 下可以用包管理器安装下面给出常见安装命令# macOS 使用 Homebrew brew install kubectl # Ubuntu / Debian 使用 apt sudo apt-get update sudo apt-get install -y kubectl # 验证安装 kubectl version --client安装完成后kubectl会读取~/.kube/config文件中的集群信息来连接集群。如果是 Minikube在启动集群后会自动写入配置如果是 Docker Desktop启用 Kubernetes 后也会自动配置好。2.3 版本兼容性提醒Kubernetes 的版本迭代速度比较快。本文示例使用的环境是 Docker Desktop 内置的 Kubernetes 1.28 版本属于通用稳定版本。实际项目中需要注意kubectl的版本与集群版本不要相差太大一般建议保持大版本一致或者最多相差一个次要版本否则可能出现 API 兼容问题。3. Kubernetes 核心架构与关键概念3.1 控制平面与工作节点K8s 集群从逻辑上分为两部分控制平面Control Plane和工作节点Worker Node。控制平面是集群的大脑负责维护集群的期望状态、调度 Pod、响应变更事件。它包含几个核心组件kube-apiserver所有请求的统一入口是组件之间通信的桥梁etcd集群状态的存储数据库保存所有 API 对象的定义和当前状态kube-scheduler负责决定新创建的 Pod 应该调度到哪个节点kube-controller-manager运行各种控制器比如 Node 控制器、ReplicaSet 控制器、Endpoints 控制器等。工作节点是真正运行容器的机器。每个节点上都有kubelet负责与 API Server 通信接收 Pod 定义并启动容器kube-proxy负责实现 Service 的负载均衡和转发规则容器运行时可以是 Docker、containerd、CRI-O 等。学习阶段有一个概念值得记住K8s 并不会直接操作容器它通过容器运行时接口CRI与容器运行时交互。也就是说Docker 在 K8s 中更多扮演的是“被编排者”的角色而不是“管理者”。3.2 Pod最小的调度单位很多人第一次接触 K8s 时会下意识地问为什么不能像 Docker 一样直接运行容器非要引入 Pod 这个概念Pod 是 K8s 中最小、最基本的部署和调度单元。一个 Pod 里可以有一个或多个容器这些容器共享同一个网络命名空间、共享存储卷可以看作一个“逻辑主机”。在实际使用中最常见的做法是一个 Pod 里只放一个业务容器。但是有些场景需要把一个辅助容器和主容器放在同一个 Pod 里比如日志收集容器负责把主容器打印到 stdout 的日志转发到集中式日志系统流量代理容器负责拦截并转发主容器的网络流量配置刷新容器监听配置中心在配置变化时更新本地文件。这些辅助容器和主容器同生命周期、同调度非常方便。3.3 Deployment声明期望状态Deployment 是最常用的工作负载控制器它描述了一个应用的期望状态。比如下面的 YAMLapiVersion: 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.27 ports: - containerPort: 80这段配置的意思是创建名为nginx-deployment的 Deployment 对象副本数为 3即同时保持 3 个 Pod 运行Pod 的标签是app: nginx模板中定义了容器镜像nginx:1.27。把这段配置保存为nginx-deployment.yaml然后执行kubectl apply -f nginx-deployment.yaml执行后K8s 的控制器会不断检查当前运行的 Pod 数量。如果少于 3 个它会自动创建新的 Pod如果多于 3 个它会终止多余的 Pod。这种“声明式”的管理方式是 K8s 最重要的设计理念之一。3.4 Service稳定的访问入口Pod 不是固定不变的。当某个 Pod 崩溃Deployment 会创建新的 Pod新 Pod 的 IP 和原来完全不同。而且如果应用有多个副本用户的请求不可能只访问某一个 Pod。这时候就需要 Service。Service 是 K8s 中实现服务发现和负载均衡的抽象。它为一组 Pod 提供稳定的虚拟 IP 和 DNS 名称。比如下面的 ServiceapiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - port: 80 targetPort: 80 type: ClusterIP这个 Service 会匹配所有带有app: nginx标签的 Pod然后提供一个稳定的虚拟 IP。集群内部的其他服务可以通过nginx-service这个 DNS 名称访问 Nginx而不需要关心 Pod IP 的变化。4. 完整实战从单机 Docker 到 K8s 部署4.1 场景描述为了直观对比我们做一个小实验。假设有一个简单的 Web 应用使用 Nginx 作为静态页面服务器。先用 Docker 启动一个容器然后把它迁移到 K8s 集群中并且验证多副本、自愈和滚动更新能力。4.2 第一步Docker 单容器启动在开发机上创建一个工作目录mkdir -p docker-vs-k8s cd docker-vs-k8s创建一个index.html文件!DOCTYPE html html head meta charsetUTF-8 titleDocker vs K8s Demo/title /head body h1Hello from Docker Container/h1 /body /html然后写一个最简单的 DockerfileFROM nginx:1.27-alpine COPY index.html /usr/share/nginx/html/index.html构建镜像docker build -t web-demo:v1 .启动容器docker run -d --name web-demo -p 8080:80 web-demo:v1本地访问http://localhost:8080可以看到页面内容。这完全符合单机使用 Docker 的直觉。现在模拟故障停止容器docker stop web-demo此时服务直接不可用。需要手动执行docker start web-demo才能恢复。这背后的问题很明显单机 Docker 不具备故障自动恢复的能力也无法在另一台机器上重新拉起容器。4.3 第二步迁移到 K8s 集群现在把同样的镜像部署到 K8s 中。先禁用之前启动的 Docker 容器docker rm -f web-demo创建 Deployment 定义文件web-deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: web-demo spec: replicas: 3 selector: matchLabels: app: web-demo template: metadata: labels: app: web-demo spec: containers: - name: web-demo image: web-demo:v1 imagePullPolicy: IfNotPresent ports: - containerPort: 80这里注意到imagePullPolicy: IfNotPresent因为本地环境里镜像已经被构建出来了所以 K8s 会优先使用本地镜像不用每次拉取。创建 Service 定义文件web-service.yamlapiVersion: v1 kind: Service metadata: name: web-demo-service spec: selector: app: web-demo ports: - port: 80 targetPort: 80 type: NodePort这里选择了NodePort类型便于在宿主机上直接通过端口访问服务。NodePort 类型下K8s 会从 30000-32767 范围内分配一个端口。应用配置kubectl apply -f web-deployment.yaml kubectl apply -f web-service.yaml查看 Pod 和 Servicekubectl get pods -o wide kubectl get svc web-demo-service预期输出中会有 3 个Running状态的 Pod分布在一个或多个节点上。Service 的NodePort端口会自动分配假设是 30080那么浏览器访问http://localhost:30080就能看到页面。4.4 第三步验证自愈能力现在手动删除一个 Pod模拟进程崩溃kubectl delete pod web-demo-xxxxx # 使用实际 Pod 名称此时 K8s 的控制器会检测到实际 Pod 数量少于期望数量于是立刻创建新的 Pod 来补充。整个过程不需要人工干预。等几秒后查看kubectl get pods会发现 Pod 数量仍然保持 3 个并且新 Pod 已经处于 Running 状态。4.5 第四步验证滚动更新接下来模拟发布新版本。修改index.html内容!DOCTYPE html html head meta charsetUTF-8 titleDocker vs K8s Demo/title /head body h1Hello from Kubernetes Rolling Update/h1 /body /html重新构建镜像docker build -t web-demo:v2 .然后更新 Deployment 的镜像版本kubectl set image deployment/web-demo web-demoweb-demo:v2查看滚动更新过程kubectl rollout status deployment/web-demo这个命令会阻塞等待直到滚动更新完成。整个过程中旧的 Pod 会逐个被替换成新 Pod服务始终处于可用状态。这种“滚动更新”能力是手动管理 Docker 容器时很难实现的。4.6 本小节要点通过这个实际例子可以看到Docker 解决的是“怎么把一个应用跑起来”K8s 解决的是“多个应用、多个副本、多个节点之间怎么协作、怎么保持服务稳定”。如果业务只有一台服务器、一个应用用 Docker 就够了。但一旦涉及多副本、多节点、持续发布容器编排的自动化能力就非常关键。5. 从 Docker 切换到 K8s 后的核心能力变化5.1 资源调度与弹性伸缩K8s 可以根据每个 Pod 声明的 CPU 和内存请求量把 Pod 调度到合适的节点上。当集群资源不足时新 Pod 会处于 Pending 状态直到有节点满足资源条件。弹性伸缩方面可以通过 HPAHorizontal Pod Autoscaler根据 CPU 使用率自动调整副本数。下面是一个简单的 HPA 示例apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-demo-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-demo minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50应用后当 Pod 的 CPU 平均利用率超过 50% 时K8s 会自动增加副本数当负载下降时会逐渐减少副本数。这种能力在生产环境的流量峰谷场景中特别实用。5.2 服务发现与负载均衡Docker 中服务之间访问要手动管理 IP 和端口容器重启后 IP 会变。K8s 中Service 对象为后端 Pod 提供了稳定的访问入口并且自带负载均衡。如果一个服务需要暴露到集群外部除了NodePort还可以使用LoadBalancer类型。云厂商提供的托管集群会对接云负载均衡器自动创建外部 IP。5.3 配置管理与机密管理Docker 中不同环境开发、测试、生产的环境变量或配置文件通常靠启动命令传入docker run -e APP_ENVprod -e DB_HOSTxx.xx.xx.xx myapp:latest配置多了以后命令变得又长又容易出错。K8s 提供了 ConfigMap 和 Secret 两种资源来管理配置ConfigMap 用于保存非敏感的配置信息Secret 用于保存敏感信息比如数据库密码、API 密钥默认会进行 Base64 编码存储。使用 ConfigMap 后配置和应用镜像解耦。修改配置时不需要重新构建镜像只需滚动更新 Pod 即可。6. 常见问题与排查思路6.1 镜像拉取超时在本地搭建 K8s 学习环境时镜像拉取慢或者超时是最常见的问题。问题现象常见原因解决思路Pod 一直 Pending事件提示 ImagePullBackOff镜像不存在或仓库访问失败查看 Pod 的 events 确认详细错误信息拉取外部镜像非常慢网络问题或镜像仓库限速配置镜像加速地址或用本地镜像明明是本地镜像Pod 还是从远端拉取imagePullPolicy 默认是 Always显式设置imagePullPolicy: IfNotPresent排查命令kubectl describe pod pod-name重点关注Events这一段里面会写明失败原因比如Failed to pull image。6.2 Pod 一直 CrashLoopBackOffPod 能创建成功但容器启动后很快就退出K8s 会反复重启状态显示CrashLoopBackOff。可能原因应用启动参数错误进程启动后立即退出容器内需要的环境变量没有注入配置文件格式错误应用读取失败启动时依赖的外部服务还没就绪。排查步骤# 查看 Pod 日志 kubectl logs pod-name # 如果容器有 init 容器加 -c 指定容器名 kubectl logs pod-name -c container-name日志是最直接的线索。如果是环境变量问题使用kubectl exec进入容器内查看kubectl exec -it pod-name -- /bin/sh env6.3 Service 无法访问Service 创建成功后通过 ClusterIP 或服务名访问不到后端 Pod。排查顺序检查 Service 的 selector 是否和 Pod 的标签匹配kubectl get svc web-demo-service -o yaml kubectl get pods --show-labels检查 Endpoints 列表是否有内容kubectl get endpoints web-demo-service如果 Endpoints 列表为空说明 selector 没有匹配到任何 Pod。这是最常见的错误来源。检查targetPort是否和容器的实际监听端口一致。比如容器监听 80但targetPort写成 8080就会导致访问失败。6.4 节点资源不足导致 Pending新创建的 Pod 一直处于 Pending 状态kubectl describe pod显示节点不满足 Pod 的资源请求。这种情况一般是集群资源不足或者某个 Pod 声明的资源量超过节点可用资源。解决思路查看节点剩余资源kubectl describe node node-name减小 Pod 的资源请求参数清理集群中不再使用的 Deployment、StatefulSet、Job 等资源。7. 最佳实践与工程建议7.1 镜像版本要规范Docker 和 K8s 都建议在镜像标签上做文章。不要使用latest标签作为生产部署的版本标识。因为latest的变化不可控回滚时无法精确定位之前使用的镜像版本。推荐格式仓库名/镜像名:版本号-构建号比如registry.example.com/web-demo:1.2.3-20250110这样每个镜像都有一个明确的版本K8s 的 Deployment 也可以非常方便地回滚。7.2 配置与镜像分离把环境差异相关的配置从镜像中剥离出来尽量使用 Kubernetes 的 ConfigMap 和 Secret 来管理。理想情况下同一个镜像可以在开发、测试、生产三个环境中使用只是注入的配置不同。这样做的好处是构建一次镜像多处使用修改配置不需要重新构建镜像配置内容可以用版本管理工具统一维护。7.3 使用声明式而不是命令式部署资源时尽量把 YAML 文件保存到 Git 仓库中用kubectl apply -f的方式创建或者更新资源。尽量避免直接用kubectl run、kubectl create deployment这类命令式操作。原因在于声明式文件是持久化的团队成员可以 review可以直接通过 Git 历史追溯变更出现问题时可以快速回退到上一个版本。7.4 关注资源请求与限制每个 Pod 都应该声明容器需要的 CPU 和内存资源。Kubernetes 的调度器依赖这些信息来决定 Pod 分配到哪个节点。如果不声明调度器会把 Pod 当成“无限资源”处理集群资源分配容易失衡。一个合理的资源声明示例resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi100m表示 0.1 个 CPU 核心128Mi表示 128 兆内存。设置 requests 是给调度器提供依据设置 limits 是防止某个应用失控拖垮整台节点。7.5 生产环境安全建议不要用默认 ServiceAccount 运行生产级应用应该为不同业务创建独立的 ServiceAccount敏感信息放在 Secret 中并开启 etcd 加密存储不要直接给容器设置privileged: true除非确实需要特殊系统权限定期备份 etcd这是集群状态的核心对集群升级前先在测试环境验证应用兼容性。8. 总结与学习路线回到文章最开始的提问有了 Docker为什么还要 K8s答案不在于 Docker 不够好而在于两者解决的问题层次不同。Docker 让开发者能够便捷地打包、分发、运行应用K8s 让运维和开发团队能够在多台服务器上批量管理这些容器并且提供自动恢复、自动伸缩、滚动更新、服务发现等生产级能力。如果你已经熟悉 Docker 的基础命令下一步可以按这个顺序学习掌握 YAML 语法理解 K8s 资源定义文件的写法熟悉 Pod、Deployment、Service 这三个最核心的资源把本文的示例多练习几遍学习 ConfigMap 和 Secret 在环境配置管理中的使用理解 Ingress 网关的配置方式知道从集群外部访问服务有哪些路径学习 Helm 的包管理方式掌握应用一键部署的套路最后再接触集群容器网络插件如 Calico、Flannel和监控体系如 Prometheus Grafana。在实际项目中刚开始接触 K8s 不必追求覆盖所有概念。先把“应用能否稳定运行、发布流程是否顺畅、故障能否快速定位”这三个问题解决好再逐步扩展到网络策略、存储卷、安全等高级主题。容器编排是一个庞大的知识体系但只要建立起“声明期望状态、控制循环持续调和”的思维模式后面的学习会顺畅很多。多动手实验多查看事件日志比死记硬背配置项更管用。
返回列表