
千万 QPS 架构里的“容器化”到底在解决什么问题很多团队把容器化理解成“把 VM 里的服务重新跑一遍 Docker”但真正支撑千万级流量的容器化核心不是装了个新工具而是把部署模型从“整机模式”切换成“进程资源配额模式”。这篇文章我要讲的不是第 218 讲的完整课程纪要而是从 VM 到容器这条路上最值得你花时间搞清楚的几个关键点它们本质上是两套资源管理逻辑容器化的收益要等业务规模上来之后才明显以及迁移过程中最容易踩到的坑。如果你是正在做“业务系统怎么容器化改造”的工程师或者正准备把运行了很久的 VM 部署切换到 Kubernetes建议先把下面内容过一遍。它不一定能让你立刻设计出千万 QPS 的方案但至少能让你少做几轮无谓的返工。1. 千万 QPS 架构要的其实是“弹性密度”不是单纯换部署方式1.1 先理解QPS 指标和部署模式的关系千万 QPS 是一个极高并发的目标。到了这个量级服务实例数一定不是十几个而是成百上千甚至更多。如果所有实例都跑在独立 VM 上会出现两个很直接的问题每台 VM 都包含完整的操作系统内存、CPU 都要分一部分给系统本身资源浪费明显。新扩容一台 VM 从启动到服务可用通常需要几分钟流量突增时响应不够快。容器技术不一样。它共享宿主机内核只把应用和运行依赖打包进镜像。启动一个容器往往只需要几秒资源占用也比完整 VM 小很多。所以当服务数量多、流量波动大时“能不能快速腾出资源、拉起新实例、把流量接住”就成了 QPS 规模上升的关键。1.2 容器化的收益是“密度”带来的我见过很多团队做容器化改造最直观的感受是同一台物理机上以前跑 3 个 VM 就接近资源极限改成容器后可以跑十几个服务实例。这不是说容器能把 CPU 凭空变多而是它避免了重复的 OS 开销。这种密度优势在流量低谷时还可以反过来用容器数量可以很细地缩容不需要像 VM 一样“整台保留或整台释放”。所以把“从 VM 到容器”理解成部署方式的变化其实还不够。真正变化的是资源分配粒度从 GB 级整机分配变成 MB 级容器分配。启动速度从分钟级变成秒级。弹性能力从人工创建 VM、安装系统、再挂到负载均衡变成自动化调度。1.3 什么场景不建议马上容器化不是所有业务都适合立刻容器化。如果服务只有两三个实例一天的请求量也很稳定容器化的收益就不明显。反而会因为引入镜像构建、编排系统、网络模型而增加维护成本。我一般会这样判断如果服务数量少、变更频率低、资源利用率不高继续用 VM 问题也不大。如果服务数量多、版本发布频繁、流量有明显峰谷或者正在准备接入 Kubernetes 做弹性扩缩容那就应该认真做容器化改造。2. 从 VM 到容器先搞清楚两条技术路线的边界2.1 VM 解决的是“硬件隔离”容器解决的是“进程隔离”VM 里运行着完整的 Guest OS虚拟化层把物理机切成多台“虚拟整机”。每台机器的内核、系统库、文件系统都是独立的。容器则共享宿主机内核多个容器之间通过 namespace 做资源隔离通过 cgroups 做资源限制。这句话意味着容器的性能损耗通常比 VM 低进程启动更快。容器里的“系统”不完整很多依赖要由镜像提供。容器的隔离边界没有 VM 那么硬内核共享也意味着内核级问题会影响所有容器。所以从 VM 迁移到容器时不要理所当然地认为“容器里能跑完整 systemd、能直接登录改配置、能当小 VM 用”。你要转变的思路是应用自带运行环境运行时只要内核匹配就可以启动。2.2 业务系统怎么容器化改造实际是“三类改造”很多人搜索“业务系统怎么容器化改造”得到的答案大多是“写 Dockerfile、构建镜像、启动容器”。但在真实项目里改造通常要拆成三类第一类是纯部署迁移。代码和依赖不动只写 Dockerfile把原来的启动命令原样搬进容器。这叫包装式容器化适合无状态应用。第二类是配置改造。应用里有本地文件读写、日志目录、环境变量、随机端口、注册中心地址等这些需要改成容器能接受的挂载卷、配置中心或环境变量。比如日志不能写死 /var/log/xxx.log要输出到 stdout 或挂载卷。第三类是架构改造。比如会话状态要放到 Redis本地缓存要调整定时任务要拆分。这类改造最花时间但也是高并发场景下真正需要做的工作。别一上来就想要“零成本平滑迁移”。从 VM 到容器最划算的路径是先挑无状态服务试水把链路跑通后再处理有状态服务。2.3 从 VMware 到 Docker 的迁移顺路问题搜索热词里有“vmware 下载教程”“vm 安装 win10”“vm 安装 ubuntu”这类关键词。这说明不少团队长期依赖 VMware 这类虚拟化工具管理环境。从 VMware 物理机/虚拟机迁移到 Docker常见顺路是在 VM 上先准备好应用运行依赖。把项目代码放到 VM 中构建镜像。测试通过后将镜像传到镜像仓库。目标服务器直接拉取镜像运行不再需要完整的 VM 环境。这条路径可以让你先绕过“本地环境还是 Windows/Mac”的问题。如果本地是 Windows可以先装一个 Linux VM 做构建机保证 Dockerfile 里的命令和线上一致。3. 容器化改造实操从单机验证到发布到 Kubernetes3.1 最小可行验证先跑一个单容器服务我没有上来就铺 Kubernetes。第一轮测试我会先在单台 Linux 机器上用 Docker 跑通一个服务。以典型的 Java/Node 应用为例第一版 Dockerfile 往往长这样FROM openjdk:17-jdk-slim WORKDIR /app COPY target/app.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建命令docker build -t demo-app:1.0 .启动命令docker run -d --name demo-app -p 8080:8080 demo-app:1.0这个阶段要检查三个东西容器状态是否一直 Running。日志是否正常打印。本地访问 http://localhost:8080/health 是否返回预期的响应。这三件事都正常才说明镜像本身没大问题。这里不要跳过健康检查。很多容器启动失败不是因为代码有问题而是启动命令的工作目录、依赖路径、时区、环境变量不对。3.2 改造系统的配置环境变量、日志、持久化容器是“一次性”的容器删除后里面的文件也就没了。所以至少要处理三件事配置改成环境变量注入。日志输出到 stdout由容器运行时统一收集。需要保留的数据挂载到 volume 或外部存储。示例运行命令docker run -d \ --name demo-app \ -p 8080:8080 \ -e SPRING_PROFILES_ACTIVEprod \ -e LOG_LEVELinfo \ -v /data/logs:/app/logs \ demo-app:1.0这些改动看着简单但恰恰是“容器内部非 root 运行服务”问题的前置条件。如果容器里用 root 启动服务挂载目录的权限经常出现“Permission denied”。更安全的做法是在 Dockerfile 里创建专用用户。3.3 进入编排用 Kubernetes 管理多实例和滚动更新当单容器跑通并且你需要在多台机器上规模化运行时再上 Kubernetes。不要反过来一开始就搞一套复杂集群结果本地镜想起不来排查成本很高。一个最简单的 Deployment 配置示例apiVersion: apps/v1 kind: Deployment metadata: name: demo-app spec: replicas: 3 selector: matchLabels: app: demo-app template: metadata: labels: app: demo-app spec: containers: - name: demo-app image: registry.example.com/demo-app:1.0 ports: - containerPort: 8080 resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi readinessProbe: httpGet: path: /health port: 8080这里面的 resources 是关键。requests 是调度依据limits 是运行时限制。如果团队刚容器化最容易犯的错误就是只写 limits 不写 requests或者反过来。两者都要写并且要基于实际压测结果来定。有些团队会用 KubeSphere 这类平台发布容器界面化操作确实能降低门槛。但底层原理还是这一套镜像、工作负载、服务、配置、存储、滚动更新。界面只是把 YAML 封装了遇到问题时最终还是要回到 YAML 和日志来排查。4. 高并发下判断容器是否“能扛千万 QPS”的指标和策略4.1 不要用“能不能启动”来判断容器性能容器能启动说明部署过程正常不代表它能扛住高并发。要回答“我能处理多少 QPS”至少要测出三类数据单实例的极限 QPS 和对应延迟。每个实例的 CPU、内存、线程池、连接池占用曲线。水平扩容后总 QPS 是否线性增长有没有出现资源争抢或连接数限制。我在测试时会先压单个 Pod逐步加压找到 TP99 开始明显上升的阈值。然后再扩到 3 个、5 个 Pod观察总吞吐。如果加实例后 QPS 没有同步上涨问题往往出在数据库连接数、注册中心限流、负载均衡后端连接上限或下游依赖容量。4.2 千万 QPS 级需要的能力自动扩缩容和服务治理到了千万 QPS 规模靠人工盯监控、手动扩副本已经不太现实。大部分团队都会依赖容器化编排系统的自动扩缩容能力也就是 HPAHorizontal Pod Autoscaler。HPA 的思路很简单根据 CPU 使用率、内存使用率或自定义业务指标自动调整 Pod 副本数。一个简单的 HPA 配置示例apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: demo-app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-app minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70注意 HPA 不是设置完就完事。它有两个先决条件每个 Pod 必须设置了 resources.requests。应用本身的启动时间最好在几十秒以内否则扩容后新 Pod 没就绪流量还是进不来。如果请求量飙升速度很快HPA 默认的扩容节奏可能不够快。这时候需要考虑两层策略一层是基础副本数是否预留足够另一层是根据“请求量分钟级趋势”做预测式扩容而不是等 CPU 已经升高再扩。4.3 “千万 QPS”的指标口径要先对齐不同团队对“千万 QPS”的理解差距很大。有人说的是网关入口 QPS有人说的是核心订单接口 QPS还有人把静态资源请求也混在一起。我建议在做容器化架构设计时先把指标口径理清楚QPS 是入口总流量还是业务有效请求是峰值 QPS还是持续 QPS有没有写操作写操作占比多少下游依赖能承受多少 QPS失败重试会占用多少额外比例这些问题不解决就算容器组扩容得再快也可能把压力集中到数据库或某个同步调用上。真实的千万 QPS 架构一定不是“所有请求都直接打到数据库”而是经过网关、缓存、消息队列、批量削峰等多个环节分摊。5. 从 VM 到容器最容易踩坑的地方和排查顺序5.1 镜像构建时间和体积失控很多团队第一版 Dockerfile 会把整个构建工具链都放进运行镜像。比如 Java 应用把 JDK、Maven、Git 全放进去结果镜像几个 GB构建和推送都慢。更合理的做法是采用多阶段构建FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuilder /build/target/app.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]第一阶段的 Maven 只负责编译打包运行镜像只保留 JRE 和应用 jar体积会小很多。这个改动对本地开发可能无所谓但放到生产集群后镜像变少意味拉取更快、扩容更快。5.2 文件权限和非 root 用户问题容器内默认用 root 运行很多开发期没问题到了生产安全扫描时会直接报高危。同时挂载持久化卷后root 创建的文件在宿主机上可能无法删除或修改。解决办法也很标准在 Dockerfile 里创建用户并指定运行用户RUN useradd --create-home --uid 1001 appuser USER appuser但这会带来一个连带问题挂载卷目录的属主可能不是 1001。这时可以先在宿主机把目录属主改成 1001或者在镜像启动脚本里用 entrypoint 做一次 chown但 chown 需要 root 权限所以通常会配合一个“初始化容器”或“启动前权限修正容器”来完成。5.3 网络模型和端口边界VM 里每个实例通常有独立 IP应用之间直接通过 IP 访问。容器迁移后Pod IP 是动态的不能写死。这时候需要引入 Service、DNS 或注册中心。很多从 VM 迁上来的应用会在配置里写死下游服务地址。迁移到 Kubernetes 后这种地址经常变成“偶尔通偶尔不通”。因为实例重建后 IP 变了老的配置自然失效。解决方案是把下游地址改成服务名或环境变量例如http://order-service:8080。这也是为什么容器化改造总会连带引入服务发现和服务网格的原因。5.4 日志、监控、安全要提前接入容器比 VM 更容易创建和销毁这意味着日志和监控不能依赖登录机器查看。日志需要统一收集到 Elasticsearch、Loki、S3 或云日志平台。监控至少要看容器 CPU、内存、重启次数、网络延迟等指标。这里也回应一下热搜词里的“镜像安全和容器安全”。容器镜像不是越新越好也不是官方镜像就一定安全。实际排查顺序是先看镜像基础 OS 层是否有已知漏洞。再看应用依赖版本是否有风险。然后看容器运行权限是否过大。最后看运行时是否需要特权模式或挂载宿主机敏感目录。如果安全团队要求镜像扫描尽量在 CI 阶段接入镜像扫描工具而不是等镜像已经部署到生产环境再审查。5.5 排查链路遇到问题先按这个顺序走无论容器起不来、访问异常、内存溢出还是 QPS 性能上不去我建议统一按下面的顺序排查看现象。是启动失败、启动后退出、请求失败、还是性能不达标。看日志。优先看 stdout/stderr不要先改代码。看状态。用docker ps -a或kubectl get pod看容器运行状态和重启次数。看事件。Pod 的 Events 里会写明镜像拉取失败、健康检查失败、OOMKilled、Back-off 等信息。看资源。检查 CPU、内存、磁盘、网络连接数。看输入。确认配置、环境变量、挂载卷、下游地址是否正确。最后看版本。确认镜像 tag、应用代码版本、Kubernetes 版本之间是否存在兼容问题。这个顺序看起来很基础但实践中绝大多数容器问题都出在第 2 到第 5 步反而不是代码逻辑本身。最后留几个我能算作“经验”的判断从 VM 迁移到容器真正要盯住的不是“用没用上 Docker”而是你有没有具备这几个能力能不能用一条命令启动整套服务。能不能在几秒内新建一个实例并接收流量。能不能让流量高峰时自动扩容、低谷时自动缩容。能不能快速丢弃一个异常实例而不影响整体可用性。能不能在容灾时把整套服务迁移到另一组机器。如果这些能力暂时还没有那容器化改造大概率还没有完成。如果只是把 VM 里的应用重新用 Docker 打了一遍包那只是换了个启动方式离“千万 QPS 架构”还有很长距离。我个人的建议是先把单一无状态服务完整跑通再把有状态模块逐个拆出去最后才考虑自动扩缩容和全面容器化。整个过程里最值得投入时间的是配置管理、日志链路、资源规格和健康检查。这四个点做扎实了后面扩展就很顺这四个点没做好即使容器数量再多也只是把一个不稳定的系统复制成了很多份。