Back
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 配置(系列一)

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

location ^~ /backend {
  rewrite ^/backend/(.*)$ /$1 break;
  proxy_pass   http://backend:9999;
}
location ^~ /backend {
  rewrite ^/backend(.*)$ $1 break;
  proxy_pass   http://backend:9999;
}
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
-1