Featured image of post 踩坑 React + Node.js/Express + Google Cloud Build + Docker 前后端分离应用部署

踩坑 React + Node.js/Express + Google Cloud Build + Docker 前后端分离应用部署

这段时间完成了一个繁体 - 简体线上转换的小项目,采用 react+express 前后端分离开发。顺便学习了一下 google cloud build 和 docker 的相关应用。踩了些坑,在这里记录下。

这段时间完成了一个繁体 - 简体线上转换的小项目,采用 react+express 前后端分离开发。顺便学习了一下 google cloud build 和 docker 的相关应用。踩了些坑,在这里记录下。

GitHub

Demo

前端代码

前端使用 create-react-app 开发,采用 axios 请求后端的接口。

前端请求后端需要制定一个 BASE_URL,由于最后是封装进 docker 镜像中,而一般来讲 create-react-app 由于使用 webpack 打包,BASE_URL 需要在编译时就指定,不适合 docker 这种模式。所以这里需要一个能取到运行时环境变量的方法。

我采用的方法是前端加入一个config.json放在 public 目录下,里面放置 BASE_URL 的值。前端程序每次启动先请求 config 得到后端真实地址再进行后续请求。然后在 docker 容器运行时覆写这个 config,就能动态制定 BASE_URL。

后来发现一个 npm 包能做到类似功能:runtime-env-cra

后端代码

后端唯一需要动态指定的是服务运行的端口,这里用到dotenv这个 npm 包,这样环境变量可以在 docker 运行时指定,然后后端程序直接读取process.env.PORT即可。

Docker 镜像

前后端代码安装依赖的时候发现会从package-lock.jsonyarn.lock)里读取我本地开发时候用的淘宝源安装,但这样在国外机器上构建镜像的时候就很慢。暂时的解决方法是在 Dockerfile 中删掉 lock 文件。

前端 Dockerfile

前端镜像用分步构建,先用node:lts-alpine这个镜像编译 react app,然后把编译好的文件复制出来,用nginx:stable-alpine作为 http server。

记录一点,COPY 指令如果前面那个参数是目录,会把目录里面的具体内容复制走,而不是把整个目录复制走。这点跟 mv 和 cp 都不一样。

还有一点,如果 RUN 里面有多个命令用&&之类的符号连接,在最前面加个set -x ; 可以查看如果有报错到底是哪个命令出错了,方便调试。

文件:https://github.com/juzeon/tw-cn/blob/master/frontend/Dockerfile

前面说到的运行时写入config.json的代码放在 CMD 指令里面。

后端 Dockerfile

由于用到 opencc 库作为繁体 - 简体转换,而这个库里面核心代码是 C 写的,安装依赖时遇到 node-gyp 报错。刚开始还以为是 alpine 或者 python 的问题,换了几个镜像问题依旧。最后在构建中加入一行RUN apk add python make gcc g++增加编译环境就解决了。

文件:https://github.com/juzeon/tw-cn/blob/master/backend/Dockerfile

Google Cloud Build

有几个坑:

  1. 如果在步骤中用到环境变量(或者 SECRET),需要采用entrypoint: 'bash'格式。
  2. Secret Manager 权限设置比较复杂,具体可以看这个文档:https://cloud.google.com/build/docs/interacting-with-dockerhub-images?hl=zh-cn。注意 Secret Manager 是收费的,$0.06/个/月。
  3. 构建触发器可以选择 github 上的项目。如果你用自建私有 git 仓库,需要在自己的服务器上安装谷歌提供的gcloud命令行工具,配置一下钩子。

另可参考文档:https://cloud.google.com/build/docs/configuring-builds/create-basic-configuration?hl=zh-cn

文件:https://github.com/juzeon/tw-cn/blob/master/cloudbuild.yaml

Cloud Build 每天免费 120 分钟,还是很不错的。

docker-compose

docker 容器编排,同一个网络中的容器和容器间交互是不需要用ports做端口映射的,只有暴露给宿主机/公网的端口才需要。

这里的配置是前后端分别在容器中运行,另外再运行一个 nginx 镜像,将前后端组合起来,前端挂在/下,后端挂在/backend下,然后暴露 18080 端口(映射为 nginx 容器的 80 端口)给公网。

在 volumes 中挂载了一个 nginx 配置文件,覆写容器中默认的 vhost。

文件:https://github.com/juzeon/tw-cn/blob/master/docker-compose.yml

nginx 配置文件

其中把后端挂载/backend目录的时候遇到 URI 方面的问题。查阅以下资料:

Nginx reverse proxy + URL rewrite

Nginx 代理 proxy pass 配置去除前缀

一文理清 nginx 中的 location 配置(系列一)

可以写成下面几种形式之一:

1
2
3
4
location ^~ /backend {
  rewrite ^/backend/(.*)$ /$1 break;
  proxy_pass   http://backend:9999;
}
1
2
3
4
location ^~ /backend {
  rewrite ^/backend(.*)$ $1 break;
  proxy_pass   http://backend:9999;
}
1
2
3
location ^~ /backend/ {
  proxy_pass   http://backend:9999/;
}

上面配置中的^~都是可以去掉的,加上是为了保证优先级。

具体是什么原理,可以参考上面的链接的资料。

本来自己 nginx 都是乱配,通过这次把 location、proxy 相关的东西理解的更深刻了一点。

文件:https://github.com/juzeon/tw-cn/blob/master/nginx.conf

总结

这次进行一次全栈开发 + 部署,还是有学到很多运维相关的知识。

Licensed under CC BY-NC-SA 4.0