前言
相信很多前端同学对 vue 或 react 的开发很熟悉了, 也知道如何去打包生成一个生产环境的包, 但对于生产环境的部署可能有些同学了解比较少. 小公司可能都是后端帮忙部署了, 大公司会有专门的运维同学部署, 对于生产环境的部署工作有些同学接触的不多, 所以这次来分享和总结下前端项目部署相关的实战经验: 从静态站点的部署, 到 node 项目的部署, 再到负载均衡的部署, 顺便也会分享一下提高部署效率的脚本和方法.
准备工作
一台或多台服务器或虚拟机.
一份 vue 或 react 项目的打包后的文件.
一份 node 项目的源码.
静态站点的部署
静态站点的部署指的是前端的 html/CSS/JS 资源的部署, 如 vue 或 react 打包后生成的 HTML,CSS 和 JS 文件, 我们将这些文件上传到服务器后, 通过 Nginx 将这些资源暴露到公网上.
上传文件到服务器
就是将文件人工将打包后的文件拷贝到服务器上, 这个很简单, 但如果每次都是人工拷贝, 部署效率未免会低了一些, 所以建议使用一些脚本或工具. 在大公司, 一般对服务器权限控制得很严格, 可能需要各种跳板机或动态密码等, 而大公司一般都有专门的运维人员或者 CI/CD 工具.
小公司可能相对自由一些, 可以允许个人直接 SSH 连接服务器, 此时可以配合使用 rsync 或 scp 命令 (Linux 或 Mac 系统) 来一键上传文件到服务器上, 给部署提效. 在这里分享一下以前使用过的部署脚本, 在前端项目根目录新建一个名为 deploy.sh 的文件:
- #!/bin/bash
- function deploy() {
- # 测试服务器
- test_host="root@test_server_ip"
- # 生产服务器
- prod_host="root@prod_server_ip"
- project_path="/srv/YourProject"
- if [ "$1" == "prod" ]; then
- target="$prod_host:$project_path"
- else
- target="$test_host:$project_path"
- fi
- rsync -azcuP ./dist/ --exclude node_modules --exclude coverage --exclude .env --exclude .nyc_output --exclude .Git "$target"
- echo "deploy to $target"
- }
- deploy $@
以上脚本的意思是将 / dist 目录下的所有文件, 上传到对应服务器的 / srv/YourProject 目录下. 测试环境的部署是直接在根目录运行./deploy.sh, 该命令会将 / dist 目录直接上传到 root@test_server_ip 服务器上;
生产环境的部署是在后面加一个参数./deploy.sh prod, 这样可以实现多环境部署. 更进一步的做法是将运行脚本的命令直接写进 package.JSON 中, 如:
- "scripts": {
- "build": "vue-cli-service build --mode staging",
- "deploy": "npm run build && ./deploy.sh",
- "deploy:prod": "npm run build && ./deploy.sh prod"
- },
这样, 通过 NPM run deploy 命令就可以实现直接打包并部署到测试环境了. 如果你的公司目前还在用人工拷贝或 FTP 工具这种低效的部署方式, 不妨试一下用上面的脚本来提效哦.
PS: 由于 rsync 命令只在 Linux 或 Mac 才有, 所以只有开发环境是 Linux 或 Mac 的用户才可以运行哦, Windows 用户是没法跑这个命令的.
编写网站的 conf
上传文件到服务器后, 就可以着手配置 nginx 了. 一般 nginx 的配置都会放在 / etc/nginx/conf.d 目录下, 我们在该目录新建一个 test.conf 作为该项目的配置:
- server {
- listen 80;
- server_name your-domain.com; # 域名
- location / {
- root /srv/YourProject; # 网站源码根目录
- index index.HTML;
- }
- location /API {
- proxy_pass http://localhost:8080; # 反向代理后端接口地址
- }
- }
一般来说, 静态站点只需配置以上几个就可以了,
server_name 表示域名, 需要先解析到服务器的公网 ip;
root 表示服务器中代码所在的位置,
index 指明了默认的处理文件是 index.HTML;
location /API 是反向代理后端服务(这里假设了后端服务部署在本地 8080 端口), 即 your-domain.com/API 的请求都会转发到
http://localhost:8080
上, 一般用该方法可以完美解决前端跨域的问题.
修改 nginx 的 conf 后需要 reload 一下 nginx 服务: nginx -s reload
测试
如果上一步配置的域名是已经解析到服务器 ip 了的, 就可以直接在公网上通过访问域名来访问你的站点了. 如果不是, 可以修改一下本机的 host 文件, 使得配置的域名可以在本机访问; 或者通过 http://localhost 来访问.
node 项目的部署
node 项目在开发时可以用 node App.JS 这样的命令来启动服务, 但在服务器上如果使用这个命令, 退出服务器后 node 进程就停止了, 所以需要借助可以让 node 进程 keep alive 的工具. 现在一般都是用 pm2.
安装 pm2
NPM install -g pm2
pm2 的一些常用命令:
- pm2 start App.JS # 启动 App.JS 应用程序
- pm2 start App.JS -i 4 # cluster mode 模式启动 4 个 App.JS 的应用实例 # 4 个应用程序会自动进行负载均衡
- pm2 start App.JS --name="api" # 启动应用程序并命名为 "api"
- pm2 start App.JS --watch # 当文件变化时自动重启应用
- pm2 start script.sh # 启动 bash 脚本
- pm2 list # 列表 PM2 启动的所有的应用程序
- pm2 monit # 显示每个应用程序的 CPU 和内存占用情况
- pm2 show [App-name] # 显示应用程序的所有信息
- pm2 logs # 显示所有应用程序的日志
- pm2 logs [App-name] # 显示指定应用程序的日志
- pm2 flush
- pm2 stop all # 停止所有的应用程序
- pm2 stop 0 # 停止 id 为 0 的指定应用程序
- pm2 restart all # 重启所有应用
- pm2 reload all # 重启 cluster mode 下的所有应用
- pm2 gracefulReload all # Graceful reload all apps in cluster mode
- pm2 delete all # 关闭并删除所有应用
- pm2 delete 0 # 删除指定应用 id 0
- pm2 scale API 10 # 把名字叫 API 的应用扩展到 10 个实例
- pm2 reset [App-name] # 重置重启数量
- pm2 startup # 创建开机自启动命令
- pm2 save # 保存当前应用列表
- pm2 resurrect # 重新加载保存的应用列表
- pm2 update # Save processes, kill PM2 and restore processes
- pm2 generate # Generate a sample JSON configuration file
- pm2 deploy App.JSON prod setup # Setup "prod" remote server
- pm2 deploy App.JSON prod # Update "prod" remote server
- pm2 deploy App.JSON prod revert 2 # Revert "prod" remote server by 2
- pm2 module:generate [name] # Generate sample module with name [name]
- pm2 install pm2-logrotate # Install module (here a log rotation system)
- pm2 uninstall pm2-logrotate # Uninstall module
- pm2 publish # Increment version, Git push and NPM publish
用 pm2 启动项目
一般来说, 我们可以直接使用 pm2 start App.JS --name="my-project" 这样的命令来启动 node 项目, 但是这样手打的命令会不好管理, 所以我们一般会在 node 项目的根目录下新建一个 pm2.JSON 文件来指定 pm2 启动时的参数, 如:
- {
- "name": "my-project",
- "script": "./server/index.js",
- "instances": 2,
- "cwd": ".",
- "exec_mode" : "cluster"
- }
name 表示 pm2 进程的名称, script 表示启动的文件入口, instances 表示启动的示例数量(一般建议数值不大于服务器处理器的核数),cmd 表示应用程序所在的目录.
我们在服务器启动 node 项目时就可以直接 pm2 start pm2.JSON.
nginx 代理绑定域名
node 项目使用 pm2 运行后, 只是运行在服务器的某个端口, 如 http://server_ip:3000, 如果该服务需要通过域名直接访问, 则还需要用 nginx 代理到 80 端口. 在 / etc/nginx/conf.d 新建一个 my-project.conf(文件命名随意哈, 一般可以用网站域名. conf):
- server {
- listen 80;
- server_name your-domain.com;
- location / {
- proxy_pass http://localhost:3000;
- }
- }
这是最简单的一个配置了, 可以根据实际情况加一些参数哈. 做完以上的步骤就完成了一个 node 项目的部署啦.
前端负载均衡部署
相信很少同学可以接触到负载均衡的部署了, 当一个项目的访问量已经大到需要使用负载均衡的时候, 一般都会有专门的运维同学去搞了. 负载均衡说白了就是将大量的并发请求分担到多个服务器上.
负载均衡的架构有很多种, 项目的架构是一个不断演进的过程, 采用哪种负载均衡的架构需要具体问题具体分析, 所以本文不会讲什么时候适合用哪种架构(笔者也不会, 笑), 接下来将会分享实战如何用 Nginx 从零搭建一个经典的负载均衡架构案例.
image
Nginx Server 是直接暴露在最前端的机器, 当用户发起请求, 首先到达的是 Nginx 服务器, 然后 Nginx 服务器再将请求通过某种算法分发到各个二级服务器上(图中的 Centos2,Centos3,Centos4), 此时 Nginx Server 充当的就是一个负载均衡的机器(Load Balancer).
笔者手上没有这么多的服务器, 为了更完整地演示, 所以现在借助 VirtualBox 建立四个虚拟机来模拟四个服务器(当然条件确实限制时可以用同一个服务器的四个端口来代替).
image
笔者新建了四个 Centos8 系统的虚拟机, 用以假设 4 台服务器, 对外都有独立 ip(假设分别是 192.168.0.1,2,
3
,
4
). 如前面的架构图所示, Centos1 将会是作为 Nginx Server, 充当最前端的负载均衡服务器, 而其余的 Centos2,Centos3,Centos4 作为应用服务器, 为用户提供真正的服务. 接下来咱们一步一步去搭建这个系统.
一, 应用服务器搭建服务站点
万丈高楼平地起, 咱们首先得先搭建一个能对外的服务, 这个服务可以是一个网站也可以是一个接口. 为了简单起见, 我们就直接起一个 koa 的 Hello World, 同时为了后面验证负载均衡的效果, 每台机器上部署的代码都稍微改一下文案, 如: Hello Centos2,Hello Centos3,Hello Centos4, 这样方便后面验证用户的请求是被分发到了哪一台服务器.
koa 的 demo 站点已经为大家准备好了: koa-loadbalance.
我们这里以 Centos2(192.168.0.2)(ip 是虚构的)这台虚拟机为例, 将会用 pm2 部署 koa 站点在该虚拟机上.
通过 scp 或 rsync 命令将源码上传到 Centos2 服务器
还记得上面的 deploy.sh 脚本吗? 如果你添加了脚本在项目中, 就可以 NPM run deploy 直接部署到服务器上了. demo 源码中有这个脚本, 大家可以改一下里面实际的 ip, 再执行命令哈.
SSH 进入 Centos2 服务器
SSH root@192.168.0.2
安装 node 环境
curl -sL https://rpm.nodesource.com/setup_13.x | sudo bash -
sudo yum install Node.JS
可以在这里看当前 node 有哪些版本, 选最新的就行, 现在是 13.
安装 pm2
NPM i pm2 -g
pm2 启动站点
在项目根目录执行:
pm2 start pm2.JSON
pm2 list 检查一下项目启动情况 , 同时用 curl localhost:3000 看返回值:
image
同理, 按上面步骤给 Centos3 和 Centos4 服务器都将服务部署起来.(记得改一下 index.JS 中的 Hello XXX 方便后面验证). 不出意外的话, 我们的网站就分别运行在三台服务器的 3000 端口了:
- 192.168.0.2:3000 ==> Hello Centos2
- 192.168.0.3:3000 ==> Hello Centos3
- 192.168.0.4:3000 ==> Hello Centos4
有同学可能会问, 为什么 Centos2,Centos3,Centos4 不用装 Nginx 的? 在实际操作中, 应用服务器其实是不用暴露在公网上的, 它们与负载均衡服务器只需通过内网直接连接就可以了, 这样更安全; 而我们的站点又是 Node 项目, 本身就可以提供 web 服务, 所以不用再装一个 Nginx 进行代理或转发了.
二, 搭建 Nginx Server
nginx 的安装方法: Nginx Install.
在 Centos1 安装好 nginx 就可以了.
三, 实现负载均衡
一开始还没了解过负载均衡时可能会觉得很难完全不知道是怎么配的, 然后接下来你会发现超级简单, 因为只需要 nginx 一个配置就可以了: upstream.
集群所有节点
我们将上面已经部署好的 Centos2,Centos3,Centos4 集群起来, nginx 配置类似下面这样:
- upstream APPNAME {
- server host1:port;
- server host2:port;
- }
APPNAME 可以自定义, 一般是项目名. 在 / etc/nginx/conf.d 新建一个 upstream.conf:
- upstream koa-loadbalance {
- server 192.168.0.2:3000;
- server 192.168.0.3:3000;
- server 192.168.0.4:3000;
- }
这样, 我们已经将三台服务器集成为了 http://koa-loadbalance 的一个集群, 下一步会使用它.
配置对外的站点
接下来是配置一个面向用户的网站了, 我们假设网站会使用 www.a.com 这个域名, 在 / etc/nginx/conf.d 下新建 a.com.conf:
- server {
- listen 80;
- server_name www.a.com;
- charset utf-8;
- location / {
- proxy_pass http://koa-loadbalance; # 这里是上面集群的名称
- }
- }
配置结束后记得 nginx -s reload 重启一下, 这样就完成负载均衡的配置了.
四, 测试
如果你的域名是真实的且已经解析到 nginx 服务器, 则此时可以直接通过域名访问了. 由于笔者这里用的是虚拟机, 公网不可访问, 所以这里配置一下宿主机的 host, 使得 www.a.com 指向 centos1 服务器, 然后在浏览器打开 www.a.com 就可以测试我们的负载均衡网站啦. Mac 系统上是 sudo vi /etc/hosts, 在最后面添加一行:
- # IP 是 Centos1 的 ip
- 192.168.0.1 www.a.com
image
我们在浏览器访问 www.a.com, 可以看到随着不断刷新, 服务器返回了不同的 Hello CentosX, 说明我们的请求被分发到三台服务器上了, 负载均衡的配置生效啦.
五, 负载均衡的几种策略
nginx 的 upstream 可以设置很多种负载均衡的策略, 以下介绍几个常用的策略.
轮询(默认): 每个请求按时间顺序逐一分配到不同的后端服务器, 如果后端服务器 down 掉, 能自动剔除.
- upstream test {
- server 192.168.0.2:3000;
- server 192.168.0.3:3000;
- server 192.168.0.4:3000;
- }
指定权重 weight: 指定轮询几率, weight 和访问比率成正比, 用于后端服务器性能不均的情况.
- upstream test {
- server 192.168.0.2:3000 weight=5;
- server 192.168.0.3:3000 weight=10;
- server 192.168.0.4:3000 weight=20;
- }
ip_hash: 每个请求按访问 ip 的 hash 结果分配, 这样每个访客固定访问一个后端服务器, 可以解决 session 的问题.
- upstream test {
- ip_hash;
- server 192.168.0.2:3000;
- server 192.168.0.3:3000;
- server 192.168.0.4:3000;
- }
fair(第三方): 按后端服务器的响应时间来分配请求, 响应时间短的优先分配.
- upstream test {
- server 192.168.0.2:3000;
- server 192.168.0.3:3000;
- server 192.168.0.4:3000;
- fair;
- }
url_hash(第三方): 按访问 url 的 hash 结果来分配请求, 使每个 url 定向到同一个 (对应的) 后端服务器, 后端服务器为缓存时比较有效.
- upstream test {
- server 192.168.0.2:3000;
- server 192.168.0.3:3000;
- server 192.168.0.4:3000;
- hash $request_uri;
- hash_method crc32;
- }
更多的策略请参考: ngx_http_upstream_module, 根据实际情况使用上面的这些策略, 没有特别需求就使用默认的轮询方式也可以.
最后
从静态站点到 node 站点, 再到负载均衡, 相信看完本文大家对整个前端的部署体系都有了一个比较全面的了解. 特别是负载均衡, 平时接触得少总觉得特别复杂, 其实看完了会觉得很简单. 更高级一些的部署可能会用上 Docker 或 k8s 的集群了, 这个就留待后面再说啦.
对于部署方式的提效, 本文也分享了一个使用 rsync 命令的脚步, 配合 package.JSON 的 script, 可以做到一个命令就完成部署的动作.
当然, 该做法也还是有很大的优化空间的, 真正好用的部署方式应该是持续集成, 通过 Jenkins 或其他工具实现自动化部署, 代码 push 上去就自动构建和部署了. 如果你的公司还在用最原始的部署方式, 不妨加把劲多探索一些这些更爽更溜的操作啦.
来源: http://www.jianshu.com/p/611973aee967