web单页面应用测试环境部署方案
概述
我们在开发项目时,经常需要部署测试环境。本文以webpack为例,记录一些常见的问题和解决方案。
目录
项目内的js参数配置
我们在搭建测试环境时,某些参数是不同的,比如api请求地址
,web页面的title
,某些第三方的appid
等。我们有以下两种方案来解决这个问题:
- 在运行时判断当前环境,并使用相应的参数。
- 在编译时判断构建环境,直接将变量编译成常量。
我们知道,在运行时处理这些参数会导致编译得到的js文件更大(判断环境的代码加进去了嘛),并且更加消耗客户端的执行时间(每次运行都要判断当前环境)。因此方案二彻底打败了方案一,下面我们说说方案二如何实现。
为了达到在编译时区分项目环境的目的,我们必须告诉webpack
当前构建的是什么环境。我们可以通过传递环境变量的方式(BUILD_ENV=development webpack --some-arguments
)告诉webpack
当前构建的环境。然后在webpack
的配置文件(webpack.config.js
)中注入配置到项目中去。这里我们要用到一个插件webpack.DefinePlugin。
// webpack.config.js
const path = require('path')
const webpack = require('webpack')
const API_HOST = process.env.BUILD_ENV === "development" ? "https://dev.example.com/user/v1" : "https://api.example.com/user/v1";
// 编译时输出API_HOST
console.log(API_HOST);
module.exports = {
entry: [
'./src/index.js'
],
plugins: [
new webpack.DefinePlugin({
'API_HOST': JSON.stringify(API_HOST),
}),
],
output: {
publicPath: '/',
path: path.resolve(__dirname, 'dist'),
filename: 'static/[name].js'
},
resolve: {
extensions: ['*', '.js'],
},
optimization: {
minimize: false,
}
};
// index.js 用到API_HOST变量的文件
var loginURL = API_HOST + "/user/login";
// 运行时输出 loginURL
console.log(loginURL)
然后运行BUILD_ENV=development npm run build
编译项目。观察编译得到的js文件:
// 编译得到的js
function(module, exports) {
var loginURL = "https://dev.example.com/user/v1" + "/user/login";
// 运行时输出 loginURL
console.log(loginURL)
/***/ }
我们发现源代码中的API_HOST变量被他的值https://dev.example.com/user/v1
代替了。这样我们就完成了通过传递环境变量控制项目全局参数的功能。
Tips: 试着执行
BUILD_ENV=production npm run build
来确信API_HOST会随之变化。
使用docker部署测试环境
在部署web端项目时,我们需要选择一个高性能的web-server,如:nginx, apache httpd, caddy等。在这里我们选择caddy作为web服务器,主要是看中了他配置简单,支持Let’s Encrypt等特性。
Tips: 去caddy的官方网站下载一个适合你的操作系统的构建版本,然后放置到你的web项目目录下并运行他,就可以在浏览器中通过http来访问你的网站了。
使用docker部署web项目一般有多种做法,下面列举了2种我使用过的方案:
- 使用官方的web-server镜像,通过docker volume挂载你的web项目。
- 使用官方的web-server作为基础镜像,构建自己的docker镜像,将你的web项目打包到镜像中。
以上两种方案各有优劣,方案一更接近传统的部署方案,每次更新项目只需要更新host上被挂载目录中的文件即可。方案二则更像是刻录在实体光碟上的软件,每个版本都有对应的镜像,对版本更新回滚等操作更加友好。我更倾向于第二种方案,可以更好地配合git等实现CICD,在构建大型应用时也更加好用。我们以第二种方案为例,讲述一下从构建镜像到最终部署的流程。
首先,我们需要为我们的web项目编写Dockerfile。
# Dockerfile
# 用node作为builder的基础镜像,因为构建我的项目需要用到node(react项目)
FROM node:latest as builder
WORKDIR /home/www
# 默认BUILD_ENV为production
ARG BUILD_ENV=production
COPY . .
# 安装npm依赖,根据网络环境会消耗一些的时间
RUN npm i
# 构建, 设置环境变量
RUN BUILD_ENV=${BUILD_ENV} npm run build
# 这里是最终运行镜像的环境,用到了caddy作为web-server
FROM narrowizard/caddy:latest
WORKDIR /srv
# 将刚才构建的文件复制过来
COPY --from=builder /home/www/dist/ .
# 复制Caddy的配置文件(需要放在项目目录中)
COPY --from=builder /home/www/Caddyfile /etc/Caddyfile
Tips: 这里没有用caddy官方的镜像,因为caddy官方镜像将/srv目录声明为volume,会导致镜像重建时更新失败的问题,具体原因参照: https://github.com/narrowizard/dockerfiles/tree/master/caddy
# Caddyfile, 在这个文件里配置web-server的相关内容
:80 {
root /srv
gzip # 开启gzip
rewrite { # 重写路由, history模式的react项目或vue项目需要开启这个, 否则无法重定向路由到首页
to {path} /
}
header / {
Cache-Control "no-store" # 关闭根目录的缓存
}
header /static {
Cache-Control "max-age=2592000" # 设置/static目录的缓存时间
}
}
Caddyfile主要用来配置web-server的相关内容。
接下来运行docker --build-arg BUILD_ENV=dev build -t ${image name}:${tag} .
就可以构建指定版本(dev或者prod)的docker镜像了。
Tips: 赶快运行 docker run -t some-container -p 80:80 ${image name}:${tag} 来运行你构建的镜像吧!
结合gitlab实现CICD
到这里为止,我们已经构建了一个web项目的镜像,接下来就需要将这个镜像运行到服务器上来完成部署。我们先来看一下还需要做多少事情才能把项目部署到服务器吧。
- 将镜像托管到远程的registry服务
- 通过ssh登陆目标(用来运行镜像的服务器)服务器
- 从registry更新镜像到目标的版本
- 终止旧版本的镜像
- 运行新版本的镜像,完成!
乍一看好像不是很麻烦,但是想到以后每次更新都要做这么多事(还包括前面的的构建步骤),整个人都不太好了。不过不要着急,配合一些CICD的工具,我们就可以实现全自动化的项目更新。我们在这里以gitlab为例。
我们只需要在我们的项目根目录下创建一个.gitlab-ci.yml
文件,并且在服务器上注册一个gitlab-runner
,然后每次push master分支到gitlab,就会触发以下的任务,完成自动部署拉。
pre-release-build:
image: docker:latest
stage: build
tags:
- shared
only:
- master
script:
- docker --build-arg BUILD_ENV=dev build -t $CI_PROJECT_NAME:$CI_COMMIT_REF_NAME .
- docker push $CI_PROJECT_NAME:$CI_COMMIT_REF_NAME
pre-release:
stage: deploy
variables:
GIT_STRATEGY: none
tags:
- shared
only:
- master
script:
- docker stop $CI_PROJECT_NAME-PRE || true
- docker rm $CI_PROJECT_NAME-PRE || true
- docker pull $CI_PROJECT_NAME:$CI_COMMIT_REF_NAME
- docker run -d -p 80:80 --name $CI_PROJECT_NAME-PRE $CI_PROJECT_NAME:$CI_COMMIT_REF_NAME
关于如何注册gitlab-runner的内容已经超出了本文的讲述范围,请自行查阅相关资料。