抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前世今生

1410是刚毕业那会做一个毕业照展示网站项目。

早期使用的是Vue.js作为前端项目,Java作为后端项目。

中间将Python写的服务端替换了Java后端。

最后将Golang写的服务端替换了Python后端。

近期又做了一些前端方面的改造:

  • 使用authelia作为Nginx层的SSO网关,设置为登录可访问(不能实现全站CDN的原因是需要NGINX层)
  • 照片使用腾讯云CDN访问,并且为加快访问速度还使用了腾讯云的万象图片处理
  • 抛弃后端接口,直接使用json存储照片列表
  • 实现前端项目动静分离,引入CDN加快访问速度
  • 使用coding的持续集成实现自动化部署

以后再也不用操心改完代码还要做一堆的操作让代码上线了,网站访问速度和安全性都得到了提升。

升级打怪过程

这篇文章就主要介绍一下将vue项目build后实现动静分离和自动化部署过程,以及遇到的一些坑点。
以下是1410前端项目的项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|-- public //存放不可变静态资源
|-- index.html
|-- favicon.ico
|-- data.json
|-- src
|-- plugins //存放要引入的插件例如iview
|-- App.vue //项目入口
|-- main.js //项目配置
|-- config //存放打包成容器后的nginx的配置
|-- nginx.conf
|-- .env.deploy 打包时指定的文件
|-- index.js 上传打包后dist文件夹到COS
|-- Dockerfile 打包成docker镜像的配置
|-- Jenkinsfile 持续集成的配置文件
|-- vue.config.js vue的配置文件
|-- package.json //存放打包命令以及依赖
...

改造vue项目实现动静分离

如我上述的结构,改造vue项目非常简单,主要分为三步:

  1. 在根目录中创建.env.deploy文件,然后写入

    1
    2
    NODE_ENV=production
    DEPLOY=online
  2. 修改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
  3. 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
2
3
4
5
|-- index.html   // 需要copy到docker镜像中
|-- static // 整个文件夹都需要放到COS上去,使用CDN访问
|-- js
|-- img
|-- css

自动化部署总体流程比较简单,分为如下几步:

  1. 选择一个devops服务提供商,我目前主要用两个:coding的持续集成以及github的action,配置起来都比较简单。
  2. 选定Docker镜像并且准备好对应的配置,我这里选用的是nginx的docker镜像,准备了config/nginx.conf文件和Dockerfile文件,对应的文件存放位置可以在上文中看到,文件具体内容如下:
    nginx.conf:
    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;
    }
    }
    }
    Dockerfile:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    FROM 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
  3. 将对应的文件打包成镜像,然后部署到宿主机上,我使用的是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
    90
    pipeline {
    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 = ''
    }
    }
  4. 为防止docker镜像启动不成功,导致原有的网站不可访问,所以将对应的dist中的文件上传到CDN中作为最后一步,这样可以保证服务启动后,访问的是最新的站点,将dist文件上传到cos上我使用的是我前期写的一个开源项目alidaodao-cos-uploader,具体的步骤可以访问项目后进行配置。

到此整个自动化部署的过程也结束了,可以看看对应的运行步骤截图:
coding持续集成

遇到的问题以及解决方案

coding持续集成部署到宿主机时始终报错:invalid privatekey: [B@51d78dde

最终查到该问题是由于我使用的连接到宿主机的SSH密钥的版本过新导致的,我使用的密钥如下:

1
2
3
4
5
6
-----BEGIN OPENSSH PRIVATE KEY-----

PRIVATE KEY

-----END OPENSSH PRIVATE KEY-----

但是coding使用的Jenkins只支持如下密钥:

1
2
3
4
5
6
-----BEGIN RSA PRIVATE KEY-----

PRIVATE KEY

-----END RSA PRIVATE KEY-----

问题找到,解决就简单了,将对应的OPENSSH的密钥改成RSA的密钥即可:ssh-keygen -p -m PEM -f 你的密钥地址,然后再转化的密钥存入对应凭据中就解决了。

总结

对于服务端开发者,自动化早已深入人心,它能帮你节省大量的时间和精力,如何用好自动化技术也是我们的挑战,欢迎与我一起交流。

参考文献:

评论