前面我们学习了 Docker、Docker Compose,并在 Docker 容器里通过 pm2-runtime 来跑的 node 服务。
主要是用 pm2 可以在进程崩溃的时候重启进程的功能。
而其实这个功能 Docker 也是有的。
我们来试一下:
setTimeout(() => {
throw new Error('xxx');
}, 1000);
1s 以后抛一个错误,进程会终止。
然后我们把它放到 Docker 容器里跑。
写个 dockerfile:
FROM node:18-alpine3.14
WORKDIR /app
COPY ./index.js .
CMD ["node", "/app/index.js"]
然后构建成镜像:
docker build -t restart-test:first .
在 docker desktop 里可以看到:
然后把它跑起来:
docker run --name=restart-test-container restart-test:first
可以看到,容器 1s 后就停掉了:
当进程退出的时候,容器也会停止。
docker run 的时候也可以指定重启策略:
docker run -d --restart=always --name=restart-test-container2 restart-test:first
这次加上 -d 把它放到后台跑。
--restart 指定总是重启。
然后你在 docker desktop 里就可以看到它一直在 restart:
打印了很多次错误日志:
你可以点击停止,就不会再重启了:
这就是 docker 的自动重启功能。
前面说过,pm2 也可以自动重启。
我们试试:
新建个 222.Dockerfile:
FROM node:18-alpine3.14
WORKDIR /app
COPY ./index.js .
RUN npm install -g pm2
CMD ["pm2-runtime", "/app/index.js"]
然后 build 一下,生成镜像:
docker build -t restart-test:second -f 222.Dockerfile .
然后跑一下:
docker run -d --name=restart-test-container3 restart-test:second
这时候你会发现容器一直是运行状态,但是内部的进程一直在重启:
也就是说,Docker 的自动重启功能和 PM2 的自动重启功能是重合的。
那还有必要用 PM2 么?
其实 PM2 诞生的时候是没有 Docker 这种容器技术的,那时候都是直接部署在机器上,这时候自然需要一个进程管理工具来做进程的重启、负载均衡等功能。这是 PM2 当年很流行的原因。
但后来有了 Docker,里面跑的进程崩溃之后,Docker 容器支持自动重启。
所以,大多数情况下,没必要再用 PM2 了。
而且如果你用了 K8S 这种容器编排工具,那更没必要用 PM2 了,直接让 K8S 去调度、重启容器就可以。
但也有另一种说法,Docker 重新跑容器的成本,是比容器内 PM2 重新跑进程的成本高的,所以用 PM2 来管理进程更好一点。
综上,有了 Docker 基本没大有必要用 PM2 了。但如果单独跑 Docker 容器,还是可以结合 pm2-runtime 来提高重启速度的。
然后我们继续来看 Docker 的重启策略:
默认是 no,也就是不自动重启。
我们测试了 always,是容器退出时总是重启。
其实还有 on-failure 和 unless-stopped 这两种:
on-failure 是只有在非正常退出的时候才重启,相比之下,always 不管是不是非正常退出都重启。
而且 on-failure 还可以指定最多重启几次,比如 on-failure:3 是最多重启三次。
我们试一下:
docker run -d --restart=on-failure:2 --name=restart-test-container4 restart-test:first
可以看到容器重启了 2 次,一共打印了 3 次错误就不再重启了:
再来试试 unless-stopped:
unless-stopped 是除非手动停止,否则总是会重启。
docker run -d --restart=unless-stopped --name=restart-test-container5 restart-test:first
可以看到容器一直在重启:
除非点击停止按钮,也就是执行 docker stop 才会停止:
docker stop restart-test-container5
那看起来和 always 也没啥区别呀,都是只有手动 stop 才能停止,否则一直重启。
还是有区别的,就是在 Docker Deamon 重启的时候。
现在 docker-test-container2 是用的 always 的重启策略,docker-test-container5 是用的 unless-stopped 的重启策略:
这俩容器都停掉了。
现在我们重启 Docker:
他会重启跑 Docker Engine ,也就是 Docker Deamon 的后台进程。
这时候你会发现,always 重启策略的容器又跑起来了,而 unless-stopped 的容器没有重启。这就是这俩的区别:
Docker Compose 是用于同时跑多个 Docker 容器的,它自然也支持 restart 的配置:
案例代码在小册仓库。
总结
Docker 是支持自动重启的,可以在 docker run 的时候通过 --restart 指定重启策略,或者 Docker Compose 配置文件里配置 restart。
有 4 种重启策略:
- no: 容器退出不自动重启(默认值)
- always:容器退出总是自动重启,除非 docker stop。
- on-failure:容器非正常退出才自动重启,还可以指定重启次数,如 on-failure:5
- unless-stopped:容器退出总是自动重启,除非 docker stop
重启策略为 always 的容器在 Docker Deamon 重启的时候容器也会重启,而 unless-stopped 的不会。
其实我们用 PM2 也是主要用它进程崩溃的时候重启的功能,而在有了 Docker 之后,用它的必要性就不大了。
当然,进程重启的速度肯定是比容器重启的速度快一些的,如果只是 Docker 部署,可以结合 pm2-runtime 来做进程的重启。
绝大多数情况下,直接用 Docker 跑 node 脚本就行,不需要 PM2。