同一个 Nestjs 服务,我先后用三种方式部署过。最早是买台云服务器自己装 docker,用 docker-compose 把 MySQL、Redis 和 Node 服务编排在一起;后来做小程序后端,改成微信云托管,推代码就自动走流水线;再后来有个访问量很低的接口服务,直接扔到腾讯云 serverless 上,不跑就不花钱。
这三条路的取舍点完全不一样。自建服务器最灵活但要自己管运维,云托管省心但绑死微信生态,serverless 便宜但有代码包体积和冷启动的硬约束。
这篇把三种方式从头到尾各走一遍,中间穿插当时卡住我的地方。看完你应该能判断自己的项目该走哪条,以及每条路上会踩到什么。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
- CentOS 上装 docker 和 docker-compose 的完整命令,以及镜像源为什么必须配
- 用一份
docker-compose.yml同时拉起 MySQL、Redis 和 Nest 服务 depends_on为什么保证不了「数据库就绪再启动服务」,restart才是兜底- Nestjs 项目的 Dockerfile 该怎么写,时区那几行为什么不能省
- 微信云托管的流水线部署流程,以及容器内改代码为什么无效
- 腾讯云 serverless 部署 Nest 的两种方式,模板和自定义
scf_bootstrap是什么,为什么要chmod 777- 云函数代码包 500M 上限怎么处理
# 一、云服务器上用 docker-compose 部署
先说最传统的那条路。买台云服务器,自己装 docker,用 compose 把几个服务编排起来。这条路的好处是所有东西都在你手上,想装什么装什么;代价是从装 docker 开始的每一步都得自己来。
# 装 docker 环境
第一步装依赖工具包。yum-utils 提供了 yum-config-manager 命令,后面加镜像源要用;device-mapper-persistent-data 和 lvm2 是 docker 存储驱动需要的。
yum install yum-utils device-mapper-persistent-data lvm2 -y

看到一堆 Installed 或者 already installed 就算过了。这一步基本不会出问题,真出问题一般是 yum 源本身有问题,先 yum makecache 一下。
接着换软件源。docker 官方源在国内拉取很慢,换成阿里的镜像。
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

命令执行完会在 /etc/yum.repos.d/ 下多一个 docker-ce.repo 文件。这里配的是软件源,管的是 docker 这个程序本身的下载速度,跟后面配的镜像加速是两回事,别搞混了。
然后装 docker 本体。
yum install docker-ce docker-ce-cli containerd.io -y
这三个包分别是 docker 守护进程、命令行客户端和容器运行时。装完还没跑起来,要手动启动。
systemctl start docker
# 设为开机启动
systemctl enable docker
systemctl enable 那行别省。服务器重启之后 docker 不自动起来,你的服务就全没了,这种事发生在半夜运维重启机器的时候特别难受。
# 配镜像加速
这一步管的是拉镜像的速度。
vi /etc/docker/daemon.json
{
"registry-mirrors": ["https://register.docker-cn.com/"]
}
后续拉取镜像直接从 https://hub.docker.com 网站拉取速度更快
重启docker
systemctl restart docker
这里有个坑要注意。register.docker-cn.com 这个官方中国区镜像早就停了,现在填它是无效的,docker pull 该慢还是慢,而且不会报错,让人以为配好了。现在能用的加速地址变动比较频繁,我的做法是去阿里云容器镜像服务控制台,它会给你一个专属的加速地址,填那个最稳。配完之后用 docker info 看一眼,输出里的 Registry Mirrors 那一段能确认有没有生效。
# 先拉个 MySQL 试试
环境装完先验证一下,别等到编排整套服务的时候才发现 docker 有问题。
docker pull daocloud.io/library/mysql:8.0.20

一层一层的 Pull complete 就是 docker 的分层镜像在下载。这里用的是 daocloud 的镜像仓库地址,好处是不用配加速也能拉,坏处是它不一定同步了最新的 tag。
运行mysql镜像
docker run -d -p 3307:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456(设置登录密码) be0dbf01a0f3(镜像ID)

命令里那两个括号是注释说明,实际敲的时候要去掉。-p 3307:3306 是宿主机 3307 映射到容器 3306,这里故意错开是因为服务器上可能已经装了 MySQL 占着 3306。-e MYSQL_ROOT_PASSWORD 是 MySQL 官方镜像约定的环境变量,不传这个容器起不来。
跑完 docker ps 能看到容器在 Up 状态就对了。如果是 Exited,用 docker logs mysql 看原因,八成是密码环境变量没传或者端口被占。
进入mysql容器内部

进容器用 docker exec -it mysql bash,然后 mysql -uroot -p 输密码。能进到 mysql> 提示符就说明整条链路都通了。
至此mysql镜像搭建成功,下面我们使用
docker-compose来管理docker容器,不在单独一个个安装MySQL、redis、nginx
# 装 docker-compose
一个个 docker run 的问题在于,参数记不住、顺序要靠人保证、容器之间怎么互相访问还得单独配网络。compose 把这些都写进一个 yml 文件里。
# 使用国内源安装
curl -L https://get.daocloud.io/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
uname -s 和 uname -m 会被展开成系统名和架构,比如 Linux 和 x86_64,拼出来正好是对应平台的二进制文件名。
设置docker-compose执行权限
chmod +x /usr/local/bin/docker-compose
下下来的是个二进制文件,默认没有执行权限,不加这一步敲命令会报 Permission denied。
创建软链
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
有些系统的 PATH 里没有 /usr/local/bin,软链到 /usr/bin 保证哪个用户都能直接敲。
测试是否安装成功:
$ docker-compose --version
docker-compose version 1.22.0, build f46880fe
能打出版本号就装好了。这里补一句时效性的话,docker-compose 独立二进制这个形态属于 v1,后来 docker 把它做成了插件,命令变成 docker compose(中间是空格)。新装的机器上两种都可能有,行为基本一致但配置文件的兼容性有细微差别。这篇里的写法是 v1 的。
# 编排文件怎么写
这份 yml 是整节的核心,一次拉起三个服务。
version: '3.0'
services:
# docker容器启动的redis默认是没有redis.conf的配置文件,所以用docker启动redis之前,需要先去官网下载redis.conf的配置文件
redis: # 服务名称
container_name: redis # 容器名称
image: daocloud.io/library/redis:6.0.3-alpine3.11 # 使用官方镜像
# 配置redis.conf方式启动
command: redis-server /usr/local/etc/redis/redis.conf --requirepass 123456 --appendonly yes # 设置redis登录密码 123456、--appendonly yes:这个命令是用于开启redis数据持久化
# 无需配置文件方式启动
# command: redis-server --requirepass 123456 --appendonly yes # 设置redis登录密码 123456
ports:
- 6380:6379 # 本机端口:容器端口
restart: on-failure # 自动重启
volumes:
- ./deploy/redis/db:/data # 把持久化数据挂载到宿主机
- ./deploy/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf # 把redis的配置文件挂载到宿主机
- ./deploy/redis/logs:/logs # 用来存放日志
environment:
- TZ=Asia/Shanghai # 解决容器 时区的问题
networks:
- my-server
Redis 这一段有两个地方值得单独说。
volumes 里的三条挂载,第一条是数据目录。容器被删除时里面的文件会一起消失,Redis 开了 appendonly 做持久化,如果不把 /data 挂出来,重新部署一次数据就没了。这是最容易踩的坑,本地测的时候感觉不到,因为你不会频繁删容器。
第二条挂的是配置文件。Redis 官方镜像里确实不带 redis.conf,command 里指定了配置文件路径的话,宿主机上对应位置必须真有这个文件,否则容器启动就报错退出。嫌麻烦的话用注释掉的那种写法,不要配置文件,直接命令行传参数。
TZ=Asia/Shanghai 这个环境变量解决的是容器内时区。不配的话容器是 UTC 时间,Redis 的 key 过期时间、日志时间戳全都跟你想的差 8 小时。
接着是 MySQL 那段。