通过Jenkins、docker自动化部署项目(python + vue)

1,428 阅读10分钟

通过Jenkins 、docker 自动化部署项目(python + vue)


前言

使用Jenkins自动化部署项目可以极大的节省项目上线后更新推送的时间

第一次部署花费时间较长是因为项目需要pip安装依赖,之后更新代码所需时间非常短 image.png

1.介绍

环境准备
1.本文环境是本地虚拟机或者是云服务器 系统是centos7.8(也可以是其他linux版本)
2.需要两台服务器(虚拟机可以安装两个centos7系统用于模拟两台服务器)

了解即可:uWSGI 和 nginx的区别 参考文章1
参考文章2

1.1 整个流程(以自动化部署后端为例)

  1. 在电脑上安装VMware(自行安装,如果有云服务器请跳过)
  2. VMware安装两个centos系统 2台云服务器安装centos7,总之必须保证有两台服务器安装好linux系统。
  3. 两台主机(A和B)。A专门用于放置Jenkins,B是项目要部署到的目标服务器
  4. 主机A利用docker安装Jenkins
  5. 准备好django项目并推到gitee或其他git仓库,并添加Dockerfile等用于docker部署django的文件
  6. 单独准备一个仓库放置自动化部署的相关配置文件,比如Jenkins的流水线配置文件
  7. 用Jenkins拉取代码(默认代码拉取到Jenkins安装目录的workspace目录下)
  8. 进入Jenkins的web页面,创建流水线任务,启动即可
主机名主机软件主机功能
主机AJenkins1.打包前端代码并推送到主机B 、2.推送后端代码以及相应的自动化构建配置文件到主机B
主机B (项目要部署到的目标服务器)docker通过docker安装mysql,django,nginx等后端开发应用

image.png 参考视频,不全但可以提供不少借鉴之处

1.2 前端部署到服务器的两种方式

1. 生产环境部署(本文所推荐的)

自己电脑npm run bulid构建dist包,放在服务器nginx目录下

2. 开发环境部署(和开发时一样,服务器使用npm run serve运行)

前端部署到服务器直接运行代码,不打包成dist文件,需要软件:

  • pm2 (可选,使node服务在后台运行,不占用进程)
  • nodejs (js代码运行的基础环境)
  • npm(一般安装的nodejs会自带,若安装node后没有再另行安装)

1.3 更改ssh超时时间,避免频繁掉线(可以跳过此步)

1.输入命令:vim  /etc/ssh/sshd_config,打开配置文件。

2.找到这两行:

#ClientAliveInterval 0
#ClientAliveCountMax 3

3.去掉注释,修改成:

# ClientAliveInterval表示服务器端向客户端请求消息的时间间隔, 默认是0秒,不发送。
#设置为60,表示每60秒,sshd都和ssh client打个招呼,检测它是否存在,不存时即断开连接。
ClientAliveInterval 60
# ClientAliveCountMax指如果发现客户端没有相应,则判断一次超时,这个参数设置允许超时的次数,比如10。
# 两个设置合起来,则代表允许超时 60秒*60 = 60分钟。
ClientAliveCountMax 60

4.再输入命令:service sshd restart,重新启动系统SSH服务。

1.4 开放网络安全组的端口

主机A 开放80、8080(Jenkins需要这个端口)

主机B 根据项目需要

1.5 准备好项目分支

避免影响正常项目开发,后端用于自动化部署的代码仓库应该新建一个分支,可以叫devop分支,前端同理

2. 安装docker和docker-compose

请参考官网安装教程

3. 安装Jenkins

参考我的另一文章

4. 使用Jenkins构建流水线(pipeline)任务


4.1 简介

  我们常用Jenkins构建自由风格(freestyle)流水线(pipeline)任务,两者都能帮我们实现CI/CD,达到我们想要的结果,但是前者不便维护,可读性和可移植性很差。
  Jenkins 流水线是一套插件,它支持实现和集成持续交付流水线到 Jenkins。流水线使用 Groovy语法将简单到复杂的交付流水线建模为“代码”。

总结: pipeline流水线操作不用过多关于自由风格项目的图形设置与步骤,只需将相关的操作,生成对应代码,并按自己的需求进行一步一步执行即可,同时该代码的移植性更强;并且在流水线语法中,可自动生成我们需要的代码,便于维护。

关于自由风格的简单教程请看

4.2 流水线的阶段流程

整个部署流程分为前端和后端自动化部署两条流水线,其实也可以合并成一条流水线,但是由于后端代码构建时间过长,所以把前端代码自动化部署独立出来

后端流水线 拉取后端代码以及自动化部署配置文件 -> 把代码和配置文件打成tar包发送到要部署的服务器 -> 利用publish over ssh执行sh脚本文件,构建docker镜像以及启动容器服务 -> 构建完成后发送钉钉消息


前端流水线 拉取前端代码以及自动化部署配置文件(从配置文件中读取Jenkinsfile) -> 在Jenkins中用node.js进行编译项目npm run build -> 推送项目到指定服务器 -> 清空Jenkins的workspace

image.png

流水线语法参考

4.3 后端代码构建

  • 新建流水线任务,任务名取中英文都可以
  • 流水线定义选择 pipeline from scm,从仓库中拉取Jenkinsfile,并且scm选择git。(scm是版本控制系统)。凭据可以选择ssh或者账号密码(具体自行百度配置,很简单)
  • 编写Jenkinsfile,使用声明式语法与脚本式语法相结合,通过流水线语法的脚本的片段生成器生成代码
# 拉取代码的片段生成器应该选
checkout: check out from version control 

然后点击生成流水线脚本即可。然后把脚本复制到Jenkinsfile中。

提示如果出错,进入blueocean页面,点击空白处进入日志页面

4.2.2 拉取项目自动化部署的配置文件

├── compose
|  ├── mysql
|  |  ├── conf
|  |  |  └── my.cnf
|  |  └── init
|  |     └── init.sql
|  ├── nginx
|  |  ├── Dockerfile
|  |  └── nginx.conf
|  └── redis
|     └── redis.conf
├── devops.sh
├── docker-compose.yml
├── Jenkinsfile
    

通过jenkinsfile帮助我们拉取后端代码

pipeline {
    agent any
    environment {
      git_url = 'https://gitee.com/apoem/lishui-oa-back.git'
      git_auth = 'e716d8bb-daf5-4fd5-91c9-e34545215c37'
    }

    stages {
        stage('拉取代码') {
            steps {
            # 注意:引用环境变量时,必须是双引号
                checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: "${env.git_auth}" , url: "${env.git_url}" ]]])
            }
        }
        stage('安装依赖') {
            when {
                branch 'production'
                environment name: 'DEPLOY_TO', value: 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

注意 所有git拉取的代码默认放在/var/jenkins_home/workspace

后端代码仓库要增加三个文件,django要对setting.py区分生产环境和开发环境。

4.2.2 用docker部署django

参考教程:Docker部署Django由浅入深系列(下): 八步部署Django+Uwsgi+Nginx+MySQL+Redis

  • 1.因为要使用uwsgi,所以要安装uwsgi,需要在requirements.txt 中添加 uwsgi = 2
  • 2.写好Dockerfile,以及docker compsoe相关文件
  • 3.更改settings.py 中关于redis,mysql的配置

4.2.3 publish over ssh

publish over ssh 配置 : 如何配置请参考 使用 :使用片段生成器 要更改exec command 时间,避免执行时间不够,导致构建项目失败

4.4 前端代码自动化部署

前端自动化部署示意图

方案一:Jenkins拉取代码,然后利用nodeJS插件执行npm build生成dist文件,然后部署到服务器(就是放到nginx所配置的网站目录下,过程如上图所示

方案二:Jenkins拉取代码,然后直接推送代码到目标服务器,由目标服务器安装node环境,执行打包构建任务

这里我们选择方案一,因为这样更简单直接,但是方案二也可以

前端和后端自动化部署的配置文件都被放在一个仓库,但是分支不同。前者是front分支,后者是master分支。

前端自动化部署的配置文件简单,只有Jenkinsfile和shell脚本


新建一个流水线项目,项目名是前端自动化部署,在配置页面配置好Jenkinsfile所在仓库,Jenkins会根据里面的Jenkinsfile开始工作。

pipeline {
    agent any
    environment {
        // 前端代码仓库地址
        git_web_url = 'https://gitee.com/apoem/lishuioa-web.git'
        // 自动化部署相关配置文件的地址(里面包含Jenkins流水线配置,但拉的是front分支,不是master分支)
        git_conf_url = 'https://gitee.com/apoem/lishuioa-conf.git'
        git_auth = 'dddddd-ddf5-ddd5-cccc-ccccccc'
    }

    stages {
        // 拉取前端代码,以及自动化部署的相关配置
        stage('拉取代码') {
            steps {
                // 通过pwd,可以发现当前代码在/var/jenkins_home/workspace/${JOB_NAME}下
                checkout([$class: 'GitSCM', branches: [[name: '*/master']], XXXXXXXXXXXXXX])
                // 拉取自动化部署相关配置文件代码
                checkout([$class: 'GitSCM', branches: [[name: '*/front']], XXXXXXXXXXXXXX])
            }
        }
        // 构建代码,生成dist文件
        stage('npm build') {
            steps {
                // 通过pwd,可以发现当前代码在/var/jenkins_home/workspace/${JOB_NAME}下
                checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'CleanBeforeCheckout'], [$class: 'RelativeTargetDirectory', relativeTargetDir: 'web']], userRemoteConfigs: [[credentialsId: "${env.git_auth}" , url: "${env.git_web_url}" ]]])
                // 拉取自动化部署相关配置文件代码
                checkout([$class: 'GitSCM', branches: [[name: '*/front']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'conf']], userRemoteConfigs: [[credentialsId: "${env.git_auth}" , url: "${env.git_conf_url}" ]]])
            }
        }
        // 把dist推送到要部署的服务器
        stage('部署项目') {
            steps {
                echo 'Deploying'
                // 注意:source files选项中  可以使用匹配
                // *.war      所有war包
                // *          只传输文件,文件夹不会传输
                // **         所有文件
                //注意 sourcefile是相对Jenkins的workspace/${JOB_NAME}/而言的
                sshPublisher(publishers: [sshPublisherDesc(configName: 'deploy-server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 600000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/usr/local/devops/lishuioa', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '**/back.tar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh /usr/local/devops/lishuioa/devops.sh', execTimeout: 600000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/usr/local/devops/lishuioa/', remoteDirectorySDF: false, removePrefix: "conf", sourceFiles: "conf/**")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
            }
        }
    }
    // post 步骤包含的是整个pipeline或阶段完成后一些附加的步骤,比如构建任务完成后发钉钉消息
    post {
        success {
            dingtalk (
                //robot 填的是全局配置中设置的钉钉机器人的id
                robot: 'dce831e5-0535-44ea-9644-de7f572c75ca',
                type:'MARKDOWN',
                title: "success: ${JOB_NAME}",
                // text: ["- 成功构建:${JOB_NAME}项目!\n- 版本:${tag}\n- 持续时间:${currentBuild.durationString}\n- 任务:#${JOB_NAME}"]
                text: [" <font size=12  color=#497568>成功构建:${JOB_NAME}项目</font>\n- **持续时间**:${currentBuild.durationString}\n- **更新记录**:暂无\n- **构建日志:**[点击查看详情](${env.BUILD_URL}console)"]
            )
        }
        failure {
            dingtalk (
                robot: 'dce831e5-0535-44ea-9644-de7f572c75ca',
                type:'MARKDOWN',
                title: "fail: ${JOB_NAME}",
                // text: ["- 失败构建:${JOB_NAME}项目!\n- 版本:${tag}\n- 持续时间:${currentBuild.durationString}\n- 任务:#${JOB_NAME}"]
                text: ["# <font  size=12 color=#c04851> 失败构建:${JOB_NAME}项目</font>\n- **持续时间**:${currentBuild.durationString}\n- **更新记录**:暂无\n- **构建日志:**[点击查看详情](${env.BUILD_URL}console)"]
            )
        }
    }
}

NodeJS插件需要在全局工具配置中简单配置一下

补充 Jenkinsfile中的dingtalk是配置钉钉机器人,构建成功后会有钉钉通知

微信图片_20230427232357.jpg

4.5 数据备份

  1. 图片等静态资源

比如前端用户上传的图片如果放在了static文件夹,就需要把该文件夹放在项目的其他目录中,然后用docker的容器卷将容器内的static目录挂载到容器外的static中。避免更新代码时static空文件夹覆盖用户数据

  1. mysql

使用主从复制

如果后端需要保存大量图片,建议使用oss服务器,这样服务器只要保存图片链接,而非图片本身

踩坑

1.如果后台项目启动失败,如何查错

查看容器日志以及uwsgi日志,nginx日志

2.前端自动化部署出问题,排查发现是构建出错

通过Linux的top命令可以看见服务器内存使用情况,经查wa%值过高可以达到82%,而且剩余可用的内存只有30mb。所以可能是2G内存太小不够用。解决方法1.加内存2.限制Jenkins内存使用

% wa     等待输入输出的CPU时间百分比

  • 解释:CPU等待磁盘IO操作的时间。和CPU的处理速度相比,磁盘IO操作是非常慢的,有很多这样的操作,比如:CPU在启动一个磁盘读写操作后,需要等待磁盘读写操作的结果。在磁盘读写操作完成前,CPU只能处于空闲状态。Linux系统在计算系统平均负载时会把CPU等待IO操作的时间也计算进去,所以在我们看到系统平均负载过高时,可以通过wa来判断系统的性能瓶颈是不是过多的IO操作造成的
  1. 前端自动化构建必须至少是2核4G内存,否则在npm build的时候会导致服务器崩溃。(在阿里云ecs测试时如此)

解决方法2

在Jenkins配置页面修改jvm参数,限制Jenkins的内存使用

#键
JAVA_OPTS
#值
-server -Xms2048m -Xmx2048m -XX:PermSize=1024m -XX:MaxPermSize=1024m

#**-Xms1024M**
#表示设置JVM启动内存为1024M,必须以M为单位.
#**-Xmx1024M**
#表示设置JVM启动内存的最大值为1024M,必须以M为单位。
#**-XX:PermSize=1024M**
#表示JVM初始分配的永久代的容量,必须以M为单位。
#**-XX:MaxPermSize=1024M**
#表示JVM允许分配的永久代的最大容量,必须以M为单位,大部分情况下这个参数默认为64M。
  1. 客户端发送超过5mb的excel文件,但是发送时间过长,最后发送失败,经检查后端一直没有接收到文件,并且前端只显示发送时间过长导致失败。 推测有如下可能性 (1) 前端axios设置connect时间短 (2)nginx配置有误 (3)uwsgi 配置有误。 排查(1)之后,并且检查nginx日志发现和(3)uwsgi有关
#uwsgi.ini文件
#设置工作进程使用虚拟内存超过N MB就回收重启
#reload-on-as= 1024
#python-autoreload=1

补充

自动化部署流程图

image.png

image.png

5.相关知识拓展

大型企业自动化部署会需要至少4台主机,如下图

image.png image.png

5.1 图中为什么会有gitlab

大型公司,把会代码放在gitlab,企业自己搭建一个服务器,安装好git仓库,用于放置代码。gitlab就是私有仓库软件。本文我们使用了gitee,所以免去了搭建gitlab的过程。

5.2 图中为什么会有harbor,为什么需要harbor

在实际生产运维中,往往需要把镜像发布到几十、上百台或更多的节点上。这时单台Docker主机上镜像已无法满足,项目越来越多,镜像就越来越多,都放到一台Docker主机上是不行的,我们需要一个像Git仓库一样系统来统一管理镜像。这里介绍的是一个企业级镜像仓库Harbor,将作为我们容器云平台的镜像仓库中心。
Habor是由VMWare公司开源的容器镜像仓库。事实上,Habor是在Docker-Registry上进行了相应的企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括:管理用户界面,基于角色的访问控制 ,AD/LDAP集成以及审计日志等,足以满足基本企业需求。

如果你想加入harbor,会添加如下流程
1.jenkins推送jar包到服务器,服务器构建镜像

2.Jenkins 本地制作镜像,然后推到服务器

2.1 告知服务器拉取哪个镜像
2.2 判断服务器是否正在运行容器,如果有,需要删除
xxxxxxxxx是否存在当前镜像,xxxx,xxxsx

参考文章

参考文章:gitlab-cicd+docker 自动化部署