Docker 运维入门实战:给容器加上限,别让一个服务拖垮整台机器
关键词:资源限制、内存上限、CPU 上限、重启策略、OOM、docker stats
上一篇我们讲了不停机更新:一次只动一个服务,等健康了再继续,出了问题能回滚。今天把这条线的最后一块拼图补上——更新流程再稳,也挡不住一个服务自己失控:内存越吃越多、CPU 占满,最后整台机器都跟着卡,甚至把别的服务也拖死。解决办法不是等它出事再重启,而是提前给每个容器划好边界:能用多少资源、死了之后怎么办,都在配置里写清楚。
本文还是接着前面的结构讲:gateway(Nginx 网关)、web(静态网页)、api(8000 端口的 Python 接口服务),并且假设健康检查已经配好——资源限制和健康检查是搭档:限制负责"别越界",健康检查负责"越界或出问题时能被发现"。
一、先看清现状:谁在吃资源
加限制之前,先看一眼现在的资源占用,别凭感觉拍数字:
docker stats --no-stream
这张表里最值得看的两列是 MEM USAGE / LIMIT 和 CPU %。MEM USAGE 是当前用了多少,LIMIT 是上限——没加限制时,这个上限就是整台主机的内存,也就是"理论上能吃光整台机器"。如果某个服务平时只用 100MB 左右,某天却涨到几个 GB,那它就是第一个需要加限制的候选。
平时也可以只看某一个服务,不用每次都刷全表:
docker stats --no-stream api
二、单机上怎么写限制:别被 deploy 误导
这里有一个新手最容易踩的坑,需要先说清楚。Compose 文档里常见的写法是:
services:
api:
deploy:
resources:
limits:
cpus: "0.50"
memory: 512M
但这套 deploy.resources 只在 Swarm 集群模式下生效,单机 docker compose up 会直接忽略它——写了等于没写。单机上真正生效的是下面这套写法:
services:
api:
mem_limit: 512m
cpus: 0.5
restart: unless-stopped
web:
mem_limit: 256m
cpus: 0.5
restart: unless-stopped
gateway:
mem_limit: 256m
cpus: 0.5
restart: unless-stopped
mem_limit 是内存上限,cpus 是 CPU 上限(0.5 表示最多用半个核)。数值怎么定?看 docker stats 里平时的用量,再留一倍左右的余量:平时 100MB 左右的服务,给 256MB;平时 200MB 左右的,给 512MB。限制不是越小越好——设得比正常用量还低,服务会频繁被杀,反而更不稳定。
改完配置后,同样一次只动一个服务,沿用上一篇的更新办法:
docker compose up -d --no-deps api
docker compose ps api
三、内存超了会怎样:OOM 不是报错,是直接被杀
内存限制到了之后,Linux 内核的 OOM Killer 会直接杀掉超限的进程,容器随之退出。现场看起来常常是"服务突然没了,日志里最后一条还很正常"。要确认是不是被 OOM 杀掉的,可以看容器的退出状态和系统日志:
docker inspect api --format '{{.State.OomKilled}} {{.State.ExitCode}}'
docker compose logs --tail 50 api
OomKilled 是 true,就说明是内存超限被杀,而不是代码自己退出的。这时别急着把限制调大,先想想内存涨上去的原因:是正常的流量高峰,还是某个接口在泄漏?前者调大限制合理,后者调大只是把"拖垮机器"的时间往后推了一点。
CPU 超了则温和得多:cpus 只是节流,不会杀进程,服务只是变慢。所以 CPU 限制可以设得相对宽松一点,内存限制才是保命的那一道线。
四、死了之后怎么办:重启策略选哪一个
限制能防止一个服务拖垮整机,但被杀掉的服务还得有人把它拉起来,这就是重启策略的事。四个选项,区别只在"什么时候重启":
| 策略 | 含义 | 适合谁 |
|---|---|---|
no |
从不自动重启 | 调试时临时用 |
on-failure |
非正常退出才重启 | 想区分"自己正常退出"和"崩了"时 |
always |
只要停了就重启,连主机重启后也拉起来 | 希望服务永远在线时 |
unless-stopped |
和 always 一样,但你手动停掉的就不拉起来 |
日常部署的首选 |
对 my-app 这三个服务,unless-stopped 是最省心的默认选择:机器重启后服务自己回来,平时你手动 docker compose stop api 做维护时,它也不会自作主张又爬起来。always 只有在"哪怕我手动停了,下次开机也要回来"这种场景才需要,大多数个人项目用不上。
重启策略和健康检查配合起来看才完整:重启只能保证"进程在跑",健康检查才能告诉你"服务真的能用"。上一篇讲过的验收三条——健康状态 (healthy)、日志无报错、从网关侧真实请求一次——加了限制之后同样适用,缺一不可。
五、一个容易忽略的点:限制是给单个容器的,不是整站的
mem_limit: 512m 是每个容器各自的上限,不是三个服务加起来 512MB。如果 api、web、gateway 各给 512MB,理论上三个服务一共能吃到 1.5GB。定数字时要把所有服务的上限加起来,和主机的总内存对照一下:上限之和最好留出余量,别让"每个都合法"加起来把机器吃满。网关和静态网页平时用量很小,给 256MB 绰绰有余;真正需要放宽的通常只有跑业务逻辑的 api。
结语
从单容器、Compose 编排、数据卷、反向代理和 HTTPS、日志与健康检查、滚动更新,到今天的资源限制和重启策略,这条 Docker 运维的线已经走完了:服务能对外提供、出了问题能定位、改了代码能安全更新、单个服务失控也不会拖垮整台机器。下一篇可以换一个方向:把这套东西从"手动敲命令"变成"改动自动部署",也就是用 GitHub Actions 或 GitCode 流水线做一次最简单的 CI/CD——改了代码、推上去,服务器自己更新。
- 点赞
- 收藏
- 关注作者
评论(0)