前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Egg 项目部署实战 docker-compose 微信云托管 serverless 三种方案

首页2022-06-17 22:35:24Front-End
部署EggDockerServerless

Egg 项目写完,接口在本地 7001 端口跑得好好的,接下来就卡在同一个地方,这玩意儿到底往哪儿放。有人说买台云服务器 pm2 起起来就行,有人说上 Docker,有人说直接 serverless 一把梭。我把这三条路都真跑了一遍,从本地起容器开始,一直到线上接口能被前端页面调通,中间该踩的坑基本都踩到了。

这篇是那次的完整记录,命令、配置文件、每一步该看到什么界面全在里面。你可以当手册翻,也可以顺着读一遍,理解为什么部署这件事会从「一条 docker run」慢慢演化成「一个 compose 文件」再到「一次 git push」。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 本地用 Docker 把 node、MySQL、Redis、Nginx 四个环境一个个装起来,搞清楚每个参数在干嘛
  • 用 docker-compose 把这四个服务编排成一组,靠自定义网桥让容器之间用容器名互相通信
  • Nginx 容器里怎么放前端静态资源,怎么把接口反向代理到 Egg 服务
  • 把整套 docker-compose 搬到云服务器上,包括 CentOS 装 Docker、放行端口、Redis 持久化配置
  • 用微信云托管做流水线部署,push 代码自动构建
  • 把 Egg 部署到腾讯云 serverless,包括 CLI 部署和控制台自定义部署两种姿势
  • 这套 2022 年的流程,放到今天有哪些地方要改

# 一、本地 Docker 环境搭建

先说清楚,这一节手动做的所有事情,到了第二节都会被一条 docker-compose up -d 替代。那还有必要一个一个手动装吗?我的答案是有必要。只有自己敲过一遍 docker run,你才知道 compose 文件里的 ports、volumes、environment 分别对应命令行的哪个参数,线上出问题的时候才知道该去哪儿看。

mac 下装 Docker 用 brew install docker 就行。这里有个坑要注意,这条命令只装了 CLI,本机没有 daemon,光敲 docker ps 会报 Cannot connect to the Docker daemon。要跑容器还得装 Docker Desktop,也就是 brew install --cask docker,装完打开一次让它把守护进程拉起来。

https://hub.docker.com 拉取镜像速度比较慢,我们推荐使用国内的镜像源访问速度较快 https://hub.daocloud.io

# 1.1 设置国内镜像源

Docker Desktop 的偏好设置里有个 Docker Engine 面板,改的其实就是 daemon.json 这个文件,写完点 Apply & Restart 就生效。

Docker Desktop 中配置 registry-mirrors 镜像加速地址

registry-mirrors 是一个数组,Docker 拉镜像的时候会先走这里配的地址,拉不到才回源到官方仓库。配它的目的很单纯,就是让 docker pull 别卡在 0%。

{
  "registry-mirrors": ["https://registry.docker-cn.com/"]
}
@前端进阶之旅: 代码已经复制到剪贴板

改完之后可以用 docker info 确认一下,输出最下面会多一段 Registry Mirrors,能看到你刚才填的地址才算配上了。至于镜像地址本身,这两年国内的公共加速源变动特别频繁,当年能用的今天不一定还在,遇到拉不动别急着怀疑网络,先确认这个源本身是不是已经废了,用你云厂商控制台给的专属加速地址通常最稳。

进入该网站https://hub.daocloud.io获取镜像的下载地址。

# 1.2 docker 命令基础

后面每一节都会反复用到这几条命令,先过一遍,混熟了排查问题会快很多。

  • docker images 查看镜像
  • docker ps 查看启动的容器 (-a 查看全部)
  • docker rmi 镜像ID 删除镜像
  • docker rm 容器ID 删除容器
  • docker exec -it 1a8eca716169(容器ID:docker ps获取) sh 进入容器内部
  • docker inspect bf70019da487(容器ID) 查看容器内的信息

这里面最值得单独说的是 docker inspect。它吐出来的是一大坨 JSON,包含容器的网络配置、挂载点、环境变量、启动命令,Node 连不上 MySQL 的时候我一般第一个看它,确认容器到底挂在哪个网络上、IP 是多少。docker exec -it 容器ID sh 则是进容器内部,注意有些精简镜像(比如 alpine)里没有 bash,只能用 sh。

镜像列表里那些 <none> 的悬空镜像,是重复构建留下的中间产物,占空间还看着乱。

删除none的镜像,要先删除镜像中的容器。要删除镜像中的容器,必须先停止容器。

顺序反了会报 image is being used by stopped container,所以下面这条得在容器清干净之后再跑。

$ docker rmi $(docker images | grep "none" | awk '{print $3}')
@前端进阶之旅: 代码已经复制到剪贴板

完整的清理三连是这样,停容器、删容器、删镜像,一步都不能跳。

$ docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') //停止容器

$ docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }') //删除容器

$ docker rmi $(docker images | grep "none" | awk '{print $3}') //删除镜像
@前端进阶之旅: 代码已经复制到剪贴板

这三条都是靠 grep 匹配文字来筛 ID 的,属于比较糙的写法,好处是任何版本的 Docker 都能跑。现在的 Docker 其实自带了更安全的清理命令,docker image prune 专门清悬空镜像,docker system prune 连停掉的容器、没在用的网络一起清,用它们不会误伤名字里刚好带 none 的镜像。

# 1.3 环境准备

一个完整的 Egg 服务跑起来要四样东西,Node 运行时、MySQL、Redis、Nginx。这里拉取nginx、node、redis、mysql镜像,一个一个来。

# 1、安装 node 镜像

进入https://hub.daocloud.io 搜索node,切换到版本获取下载地址。

  • docker pull daocloud.io/library/node:12.18
  • docker tag 28faf336034d node 重命名镜像

docker tag 干的事情其实只是给同一个镜像挂一个新名字,不会复制一份数据,所以重命名镜像后IMAGE ID都是一样的。

docker images 中重命名前后两条记录的 IMAGE ID 完全相同

拉一次镜像动辄几百兆,网络不好的时候拉一次要好几分钟。所以我习惯把常用镜像导出到本地存着,换机器或者重装 Docker 直接导回来。也可以导出镜像到本地备份 docker save -o node.image(导出镜像要起的名称) 28faf336034d(要导出的镜像的ID)。

docker save 把 node 镜像导出成本地文件

导完验证一下确实能用,我们先删除之前的镜像 docker rmi 28faf336034d -f 强制删除。加 -f 是因为这个镜像上还挂着 tag 和容器引用,不强制删会被拒绝。

强制删除本地 node 镜像

再次导入本地镜像

docker load -i node.image(导入的镜像名称)

导入完你会发现镜像的 REPOSITORY 和 TAG 都是 <none>,这是正常的,docker save 存的是镜像层数据,标签信息没跟着回来。

docker load 导入后镜像列表里出现一条无标签记录

然后再次重命名镜像即可

docker tag 28faf336034d node:v1.0(版本v1.0)

重新给导入的镜像打上 node:v1.0 标签

补一句时效性的话,Node 12 这条线早就结束维护了,当年写这篇的时候它还是可用的 LTS,今天再起新项目不要再挑这个版本,直接用当前的 LTS 大版本,安全补丁和 npm 生态兼容性都会好很多。下面所有出现 node:12.18 的地方,思路照搬,版本号换成你项目实际用的就行。

# 2、安装 MySQL 镜像

进入https://hub.daocloud.io 搜索mysql,切换到版本获取下载地址。

在镜像站搜索 mysql 并选择 8.0.20 版本

  • docker pull daocloud.io/library/mysql:8.0.20

拉取过程会一层一层往下走,每一层对应 Dockerfile 里的一条指令,已经存在的层会直接显示 Already exists 跳过。

docker pull 拉取 mysql 镜像的分层下载过程

启动MySQL镜像

这条命令里参数不少,逐个看一下就明白了。-p 3307:3306 是把宿主机的 3307 映射到容器里的 3306,之所以不用 3306 对 3306,是因为很多人本机早就装过一个 MySQL 占着 3306,撞端口会直接启动失败。-e 注入的环境变量是官方镜像约定好的,容器第一次启动时会读它来初始化 root 密码。

docker run -d(后台运行) -p 3307:3306(本机端口:MySQL运行端口) --name mysql(容器名称) -e MYSQL_ROOT_PASSWORD=123456(设置mysql密码) be0dbf01a0f3(mysql镜像ID)
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意,MYSQL_ROOT_PASSWORD 只在数据目录为空、也就是容器第一次初始化的时候生效。如果你挂载了 volume 之后再改这个变量,密码是不会变的,重来一次得先把挂载出来的数据目录删干净。

查看当前正在运行的镜像

docker ps -a(正在运行和停止的镜像-a都可见)
@前端进阶之旅: 代码已经复制到剪贴板

不带 -a 只能看到运行中的容器,容器起不来直接挂掉的时候你会看到一片空白,误以为命令没执行。加上 -a 才能看到那条 Exited (1) 的记录,再去 docker logs 里翻原因。

docker ps -a 列出运行中和已退出的容器

删除容器

删除之前需要 stop,也就是 docker stop bac2692e2b9a(容器ID),运行中的容器是删不掉的。

docker rm bac2692e2b9a(容器ID:docker ps获取)
@前端进阶之旅: 代码已经复制到剪贴板

进入容器内部

docker exec -it bac2692e2b9a(容器ID) sh(指定进入方式)
@前端进阶之旅: 代码已经复制到剪贴板

-it 这两个参数经常被一起写,-i 保持标准输入打开,-t 分配一个伪终端,少了任意一个你都敲不了交互式命令。进去之后就是一个普通的 shell,可以直接 mysql -uroot -p 登进数据库确认服务是活的。

进入 MySQL 容器内部并登录数据库

容器内部能连上只说明服务起来了,还得验证端口映射有没有真的通到宿主机。我们使用Navicat新建一个连接测试一下,主机填 127.0.0.1,端口填映射出来的 3307。

Navicat 通过 127.0.0.1:3307 成功连接容器里的 MySQL

能连上,说明我们使用docker安装MySQL的方式是没问题的。

查看MySQL容器日志

docker logs -f(持续跟随输出)  bac2692e2b9a(容器ID)
@前端进阶之旅: 代码已经复制到剪贴板

-f 是 follow,效果类似 tail -f,会挂在那里持续打印新日志,按 Ctrl + C 退出。如果你只想看最后几条,用的是 --tail 100 这个参数,两个别记混了。容器起不来的时候,答案九成写在这里面。

docker logs 输出 MySQL 容器的启动日志

重启容器

如果修改了容器配置,我们需要重新启动容器。

docker restart bac2692e2b9a(容器ID)
@前端进阶之旅: 代码已经复制到剪贴板

设置MySQL权限

mysql8.0后,需要设置,否则node连接不上

fe
  • 一、本地 Docker 环境搭建
    • 1.1 设置国内镜像源
    • 1.2 docker 命令基础
    • 1.3 环境准备
      • 1、安装 node 镜像
      • 2、安装 MySQL 镜像
      • 3、安装 redis 镜像
      • 4、安装 Nginx 镜像
    • 1.4 部署 egg 代码
      • 启动 egg 镜像
  • 二、用 docker-compose 把服务编排起来
    • 2.1 编写 docker-compose.yml 文件
    • 2.2 启动服务
  • 三、Nginx 容器内部署前端
  • 四、把 Docker 部署到云服务器
    • 4.1 安装 docker 环境
      • 安装工具包
      • 设置阿里镜像源
      • 安装 docker
      • 启动 docker
      • 设置 docker 镜像源
      • 安装 mysql 镜像测试
    • 4.2 安装 docker-compose
    • 4.3 开放服务器端口
    • 4.4 部署 egg 项目
      • 修改代码和配置
      • 上传本地 egg 服务端代码到服务器
      • 启动 egg 服务
      • 测试服务
  • 五、部署到微信云托管
    • 5.1 redis 服务
    • 5.2 mysql 服务
    • 5.3 egg 部署
      • 修改代码
      • 新建服务
      • 调试接口
  • 六、egg 部署到腾讯云 serverless
    • 6.1 修改 egg 配置
    • 6.2 命令行部署
      • 配置 YAML
      • 部署到腾讯云
      • 移除
      • 账号配置(可选)
      • 注意!!!
    • 6.3 控制台创建部署(模板部署)
    • 6.4 控制台创建部署(自定义部署)
      • 初始化项目
      • 部署上云
    • 6.5 测试接口
  • 七、这套流程放到今天要改哪些地方
  • 总结
  • 参考

← docker-compose/微信云托管/serverless之部署Nestjs项目Nestjs 学习总结 从控制器到 TypeORM 的后端实战笔记 →