前世今生
1410是刚毕业那会做一个毕业照展示网站项目。
早期使用的是Vue.js作为前端项目,Java作为后端项目。
中间将Python写的服务端替换了Java后端。
最后将Golang写的服务端替换了Python后端。
近期又做了一些前端方面的改造:
- 使用authelia作为Nginx层的SSO网关,设置为登录可访问(不能实现全站CDN的原因是需要NGINX层)
- 照片使用腾讯云CDN访问,并且为加快访问速度还使用了腾讯云的万象图片处理
- 抛弃后端接口,直接使用json存储照片列表
- 实现前端项目动静分离,引入CDN加快访问速度
- 使用coding的持续集成实现自动化部署
以后再也不用操心改完代码还要做一堆的操作让代码上线了,网站访问速度和安全性都得到了提升。
升级打怪过程
这篇文章就主要介绍一下将vue项目build后实现动静分离和自动化部署过程,以及遇到的一些坑点。
以下是1410前端项目的项目结构:
1 | |-- public //存放不可变静态资源 |
改造vue项目实现动静分离
如我上述的结构,改造vue项目非常简单,主要分为三步:
在根目录中创建
.env.deploy
文件,然后写入1
2NODE_ENV=production
DEPLOY=online修改
vue.config.js
,将一下内容写入:1
2
3
4
5
6
7
8
9
10
11// 根据自定义的变量来进行内容设置,将这块放在整个js文件的最上面即可
let BASE_URL = '/'
switch(process.env.DEPLOY) {
case 'online':
BASE_URL = 'https://cdn.songbo.fun/'
break
default:
BASE_URL = '/'
}
然后在module.exports中增加一行,注意','
publicPath: BASE_URL在
package.json
中的scripts增加一行"deploy": "vue-cli-service build --mode deploy"
此处注意**,**
完成已上三步后就改造完成了,如果需要打包正常的项目使用npm run build
如果需要动静分离,使用npm run deploy
即可。
最终效果如下,在dist/index.html
,对应的css地址就换成了如下的内容:
1 | <link href=http://cdn.songbo.fun/static/css/app.887c93b2.css rel=preload as=style> |
** 注意 这一步也没有遇到什么坑点,很快就完成了项目的动静分离。
实现自动化部署
上一步已经实现了打包的过程,初步具备了前端站点动静分离的条件,最终生成的dist目录结构如下:
1 | |-- index.html // 需要copy到docker镜像中 |
自动化部署总体流程比较简单,分为如下几步:
- 选择一个devops服务提供商,我目前主要用两个:coding的持续集成以及github的action,配置起来都比较简单。
- 选定Docker镜像并且准备好对应的配置,我这里选用的是
nginx
的docker镜像,准备了config/nginx.conf
文件和Dockerfile
文件,对应的文件存放位置可以在上文中看到,文件具体内容如下:
nginx.conf:Dockerfile:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78# nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
map $http_x_forwarded_for $clientRealIp {
"" $remote_addr;
~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;
}
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
gzip on;
client_max_body_size 100m;
client_body_buffer_size 10m;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
proxy_request_buffering off;
proxy_buffering off;
server {
listen 80;
server_name localhost;
charset utf-8;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1y;
access_log off;
add_header Cache-Control "public";
}
location ~* \.(?:css|js)$ {
try_files $uri =404;
expires 1y;
access_log off;
add_header Cache-Control "public";
}
location ~ ^.+\..+$ {
try_files $uri =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}1
2
3
4
5
6
7
8
9
10
11FROM nginx:1.17.9-alpine
ENV TZ=Asia/Shanghai
WORKDIR /usr/share/nginx/html
COPY ./config/nginx.conf /etc/nginx/nginx.conf
COPY ./dist /usr/share/nginx/html
EXPOSE 80 - 将对应的文件打包成镜像,然后部署到宿主机上,我使用的是coding的持续集成,对应的jenkinsfile如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90pipeline {
agent any
stages {
stage('检出') {
steps {
checkout([$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]],
userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]])
}
}
stage('构建') {
steps {
echo '构建中...'
sh 'npm install'
sh 'npm run deploy'
sh 'docker build -t ${registry}/${projectName}:${GIT_COMMIT} .'
sh "docker login --username='${username}' --password='${password}' '${registry}'"
sh 'docker push ${registry}/${projectName}:${GIT_COMMIT}'
echo '构建完成.'
}
}
stage("部署到远端服务") {
steps {
script {
def remoteConfig = [:]
remoteConfig.name = "my-remote-server"
remoteConfig.host = "10.0.0.1"
remoteConfig.port = 22
remoteConfig.allowAnyHosts = true
withCredentials([
sshUserPrivateKey(
credentialsId: "${remote_cred}",
keyFileVariable: 'id_rsa'
),
]) {
// SSH 登陆用户名
remoteConfig.user = "root"
// SSH 私钥文件地址
remoteConfig.identityFile = id_rsa
// 请确保远端环境中有 Docker 环境
sshCommand(
remote: remoteConfig,
command: "docker login -u ${username} -p ${password} ${registry}",
sudo: true,
)
sshCommand(
remote: remoteConfig,
command: "docker rm -f ${containerName} | true",
sudo: true,
)
// DOCKER_IMAGE_VERSION 中涉及到 GIT_LOCAL_BRANCH / GIT_TAG / GIT_COMMIT 的环境变量的使用
// 需要在本地完成拼接后,再传入到远端服务器中使用
DOCKER_IMAGE_URL = sh(
script: "echo ${registry}/${projectName}:${GIT_COMMIT}",
returnStdout: true
)
sshCommand(
remote: remoteConfig,
command: "docker run -d -p 2111:80 --name ${containerName} ${DOCKER_IMAGE_URL}",
sudo: true,
)
echo "部署成功,请到 https://1410.xxxx.com 预览效果"
}
}
}
}
stage('上传CDN') {
steps {
echo '构建中...'
sh 'node index.js'
echo '上传完成'
}
}
}
environment {
registry = ''
username = ''
password = ''
projectName = '1410'
containerName = '1410'
remote_cred = ''
}
} - 为防止docker镜像启动不成功,导致原有的网站不可访问,所以将对应的
dist
中的文件上传到CDN中作为最后一步,这样可以保证服务启动后,访问的是最新的站点,将dist文件上传到cos上我使用的是我前期写的一个开源项目alidaodao-cos-uploader,具体的步骤可以访问项目后进行配置。
到此整个自动化部署的过程也结束了,可以看看对应的运行步骤截图:
遇到的问题以及解决方案
coding持续集成部署到宿主机时始终报错:invalid privatekey: [B@51d78dde
最终查到该问题是由于我使用的连接到宿主机的SSH密钥的版本过新导致的,我使用的密钥如下:
1 | -----BEGIN OPENSSH PRIVATE KEY----- |
但是coding使用的Jenkins只支持如下密钥:
1 | -----BEGIN RSA PRIVATE KEY----- |
问题找到,解决就简单了,将对应的OPENSSH的密钥改成RSA的密钥即可:ssh-keygen -p -m PEM -f 你的密钥地址
,然后再转化的密钥存入对应凭据中就解决了。
总结
对于服务端开发者,自动化早已深入人心,它能帮你节省大量的时间和精力,如何用好自动化技术也是我们的挑战,欢迎与我一起交流。
参考文献: