容器化之后服务器选型变了吗?三个最容易买多的地方

容器化之后服务器选型变了吗?三个最容易买多的地方

摘要:很多团队容器化之后买服务器还是按以前的思路来——CPU核数、内存大小、磁盘容量,跟裸机时代一样算。但容器化之后资源利用率模型变了,有些地方你买的配置比实际需要的多了一倍甚至更多。本文从实际项目经验出发,讲清楚容器化之后服务器选型的三个变化。

关键词:容器化、Docker、K8s、服务器选型、资源规划

分类:运维 / IDC / K8s


从一个"买多了"的故事说起

一个客户做了容器化改造,原来3台裸机跑3个服务,改成K8s之后来找我买服务器搭集群。

他的需求单是这么写的:

Master节点:1台 8核16G
Worker节点1:1台 16核32G(跑Web服务,原来占一台16核32G)
Worker节点2:1台 16核32G(跑API服务,原来占一台16核32G)
Worker节点3:1台 16核32G(跑后台任务,原来占一台16核32G)

思路很直接:原来每个服务占一台16核32G,容器化之后还是按这个配置买。

我问他:“你这些服务现在CPU实际用多少?”

他登上去看了下:

Web服务:CPU均值25%,内存用了8G
API服务:CPU均值15%,内存用了6G
后台任务:CPU均值10%(偶尔飙到60%),内存用了4G

三个服务加起来实际需要的资源:CPU峰值6核左右、内存18G。

他要买4台机器总共64核128G。

多花了将近一倍的钱。

容器化之后最大的变化就是资源可以共享和超分。裸机时代每个服务独占一台机器,大部分时间CPU和内存是浪费的。容器化之后多个容器共享节点资源,利用率能上去,用不着按裸机时代的配置来买。


第一个最容易买多的地方:CPU

裸机时代的逻辑

裸机部署的时候,CPU核数是按峰值需求选型的。你的API服务高峰期要用到8核,那就买8核的机器。但高峰期可能一天就两三个小时,剩下21个小时CPU利用率不到20%。

8个核里有6个大部分时间在闲着。

容器化之后变了什么

K8s的requests机制允许CPU超分。Pod的requests是保底,limits是峰值。多个Pod共享节点的CPU,非高峰时段互相借用空闲的CPU。

resources:
  requests:
    cpu: "500m"    # 保底0.5核
  limits:
    cpu: "2000m"   # 峰值2核

一个8核节点,可以跑16个request=500m的Pod。实际运行中大部分Pod不会同时用满CPU,所以高峰期个别Pod可以burst到2核。

# 实际资源使用
kubectl top nodes
NAME        CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
worker-1    3200m        40%    12Gi            75%

CPU实际用了40%,但requests已经分配了70%。意味着有30%的CPU余量可以给Pod突发使用。

怎么选CPU

不要按裸机时代的峰值选。按以下方式估算:

节点CPU核数 = 所有Pod的requests之和 × 1.3(留30%余量)
# 估算示例
pods = [
    {"name": "web", "cpu_request": "500m", "replicas": 2},
    {"name": "api", "cpu_request": "1000m", "replicas": 2},
    {"name": "worker", "cpu_request": "500m", "replicas": 1},
]

total_cpu = sum(
    int(p["cpu_request"].replace("m", "")) / 1000 * p["replicas"]
    for p in pods
)
# total_cpu = 0.5×2 + 1×2 + 0.5×1 = 3.5核

node_cpu = total_cpu * 1.3
# node_cpu ≈ 4.55核 → 选8核节点足够

如果按裸机思维,三个服务峰值各需要2-4核,可能买3台8核甚至16核。容器化后资源可以超分,8核节点可能就够了。

什么时候不能超分: 如果你的服务是计算密集型的(视频转码、数据分析、AI推理),CPU长期跑满,那不能超分,老老实实按实际需求配。


第二个最容易买多的地方:内存

裸机时代的问题

一个Java服务堆内存配了8G,操作系统和中间件再占4G,这台机器就得16G起步。实际上Java堆用了5G,剩下的3G在大部分时间是空的。

但内存不像CPU可以超分。Pod的内存request了多少就要预留多少。一个request=8Gi的Pod,节点必须有8Gi的可用内存才能调度进去。

容器化之后怎么优化

第一步:把应用的JVM内存调准。

很多Java服务的-Xmx是按"万一需要"设的。比如设8G,但实际峰值只用4G。

# 看JVM实际内存使用
jstat -gc <pid> | tail -1
S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU
0.0   524288.0  0.0  432156.0 4194304.0 2097152.0 8388608.0  4194304.0  98304.0 87654.0

OU(老年代使用)约4G,老年代总量8G。一半是空的。

# JVM参数调整
# 原来
-Xmx8g -Xms8g

# 调整后(留20%余量)
-Xmx5g -Xms5g

堆内存从8G降到5G,Pod的memory request跟着降。

第二步:容器的memory request设对。

# 不好的做法:requests和limits设一样
resources:
  requests:
    memory: "8Gi"
  limits:
    memory: "8Gi"

# 更好的做法:requests设为实际使用量,limits留余量
resources:
  requests:
    memory: "4Gi"    # JVM堆5G + 非堆/系统约1G → 但实际上容器内存limit要大于JVM
  limits:
    memory: "6Gi"

注意一个坑:容器的memory limit要大于JVM的堆内存。 JVM除了堆内存,还有非堆(Metaspace、线程栈、直接内存等)、以及操作系统层面的开销。经验公式:

容器memory limit ≈ JVM -Xmx × 1.3 ~ 1.5

-Xmx=5G的话,容器limit设6.5-7.5G比较安全。

第三步:多个服务混部。

不同服务的内存使用模式不同。Java服务内存在启动后基本稳定。Python/Node.js服务可能有内存泄漏,内存在缓慢增长。

把这些服务放在同一个节点上,Java服务的稳定内存占用加上Python/Node.js的弹性内存占用,比各自独占一台机器的总内存需求小。

裸机时代:
  Java服务独占16G机器(用了10G)
  Python服务独占8G机器(用了5G)
  总计:24G

容器化混部:
  两个服务放在同一台16G节点上
  Java request 6G + Python request 3G = 9G
  留7G余量,足够了
  总计:16G

怎么选内存

节点内存 = 所有Pod的memory requests之和 × 1.3(留余量给系统组件和突发)

比裸机时代算出来的数字通常小30-50%。


第三个最容易买多的地方:磁盘

裸机时代

每个服务的服务器上都有本地磁盘——系统盘、数据盘、日志盘。一个服务配500G SSD是标配。

三台服务器就是1.5T的SSD。但实际上每台只用了100-200G。

容器化之后

容器是无状态的。应用跑在容器里,容器删了重建,数据不受影响(前提是数据存在外面)。

这意味着:

  • 系统盘需求下降。 K8s节点的系统盘主要给OS和容器运行时用。镜像会占用一些空间但可以清理。100-200G对大部分节点够了。

  • 应用数据不应该在本地。 日志走stdout到日志收集系统(EFK/Loki),不用存在本地。用户文件走对象存储。数据库有自己的独立存储。

  • 只有需要持久化存储的服务才配大磁盘。 比如数据库(虽然前面说了数据库建议裸机)、Elasticsearch、Prometheus这些有状态的服务。

裸机时代:
  3台服务器 × 500G SSD = 1.5T
  实际使用:约500G

容器化后:
  3个K8s节点 × 200G SSD = 600G
  独立存储(NAS/对象存储):按需
  实际总使用量差不多,但节点磁盘成本降了

容器镜像的空间管理

容器化之后有一个新的空间消耗:Docker镜像。

# 查看磁盘使用
docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          15        8         12.5GB    5.2GB (41%)
Containers      8         8         1.2GB     0B
Local Volumes   3         2         850MB     200MB
Build Cache     0         0         0B        0B

镜像积累多了会占不少空间。定期清理:

# 清理不用的镜像和容器
docker system prune -a

# K8s节点上用crictl
crictl rmi --prune

也可以配一个定时清理的CronJob。但更好的做法是CI/CD构建新镜像的时候自动清理旧版本,只保留最近3-5个版本。


一个真实的选型对比

那个客户的最终选型:

原始方案(按裸机思路):
  Master:1台 8核16G
  Worker:3台 16核32G
  总计:56核112G
  月费约6000-8000元

优化方案(按容器化思路):
  Master:1台 4核8G
  Worker:2台 8核32G
  总计:20核72G
  月费约3000-4000元

月费省了将近一半。

20核72G够用吗?算一下:

所有Pod的requests:
  Web(×2):CPU 1核 + 内存 4G = CPU 2核 + 内存 8G
  API(×2):CPU 1核 + 内存 3G = CPU 2核 + 内存 6G
  Worker(×1):CPU 0.5核 + 内存 2G
  Ingress Controller:CPU 0.5核 + 内存 0.5G
  CoreDNS等系统组件:CPU 0.5核 + 内存 0.5G
  合计:CPU 5.5核 + 内存 17G

两个Worker节点:
  可分配 CPU:约14核(8×2-系统预留)
  可分配内存:约56G(32×2-系统预留)

CPU使用率:5.5/14 = 39%(余量充足,高峰期可以burst)
内存使用率:17/56 = 30%(余量充足)

够用了。实际运行了几个月,CPU峰值到过60%,内存峰值到过50%,都在安全范围内。


什么时候不能省

上面说的都是"可以省"的场景。但有些情况不能省:

计算密集型服务。 CPU长期跑满(视频转码、数据分析、AI推理),不能超分。按实际需求选型。

内存敏感型服务。 数据库、Redis、Elasticsearch,内存要给足不能省。内存不够会导致性能急剧下降甚至OOM。

IO密集型服务。 数据库、日志处理,磁盘性能很重要。不能因为"容器化了磁盘需求就少了"就把SSD换成HDD。

生产环境的高可用节点。 至少2个工作节点,一个挂了另一个能顶上。不要为了省钱只买1个工作节点。


一个估算框架

容器化之后的服务器选型,按这个流程估算:

第一步:列出所有服务
  每个服务的Pod数、CPU request、memory request

第二步:加总
  总CPU requests = 各服务CPU之和
  总memory requests = 各服务内存之和

第三步:考虑余量
  节点CPU = 总CPU × 1.3(留30%给突发和系统组件)
  节点内存 = 总内存 × 1.3

第四步:选节点规格
  单节点规格要能让至少2-3个服务同时运行
  不要选太大的节点(故障爆炸半径大)
  不要选太小的节点(调度碎片严重)

第五步:确定节点数量
  至少2个工作节点(高可用)
  总需求 ÷ 单节点容量 + 1(冗余)

跟裸机时代最大的区别:按requests选而不是按limits选。 requests是保底需求,limits是峰值。多个服务的峰值不会同时到来,所以节点不需要按所有服务的峰值之和来配。


总结

容器化之后服务器选型有三个容易买多的地方:

项目裸机时代容器化之后省多少
CPU按峰值选型按requests选,可超分30-50%
内存按堆内存×2精确配置JVM+容器内存20-40%
磁盘每台服务500G SSD节点小盘+独立存储30-50%

核心原因就一个:容器化之后多个服务共享节点资源,利用率上去了,不需要为每个服务单独预留峰值资源。

但有几种情况不能省:计算密集型、内存敏感型、IO密集型。这些服务的资源不能超分,按实际需求选型。

先跑起来看实际资源使用数据,再根据数据调整配置。不要一开始就按最大需求买——K8s的好处之一就是可以随时加节点扩容。


有问题评论区聊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值