Skip to content

部署微服务

当我们在开发完程序代码后,就需要将程序部署到服务器,不管是演示环境还是生产环境,程序部署都有一个共同的特点: 那就不断的提交测试,重复的发布,如果每次都是人为发布,这种重复没有意义的工作不仅浪费时间,影响效率,严重的话,还会让发布人员 心生烦躁,耐心尽失,甚至导致他们删库跑路。

Jenkins是一个开放源代码软件,意味着任何人都可以查看它的源代码、对其进行修改和贡献。开源使得Jenkins能够免费提供,并确保其长期的发展和维护。 Jenkins为开发团队提供了一个平台,使其能够自动化各种任务,例如代码构建、测试、部署等。这种自动化使得开发流程更为流畅,可以快速地发现和修复问题,从而加快软件的发布速度。

上一节中间件的部署,咱们已经通过docker部署了jenkins,但是还需要很多初始化配置工作要做。

jenkins 官方文档

提示

从经验来看,很多部署人员喜欢将部署流程写在sh脚本文件中,然后手动将脚本文件上传至服务器,再通过jenkins连接ssh方式调用服务器sh脚本来实现自动化部署。

不推荐这样做,增加了自动化部署的复杂度,大量的脚本安装无疑是巨大的工作量,并且给迁移增加额外工作。

同时导致整个部署流程不直观,整个部署的生命周期部分在jenkins中,部分在自定义脚本中,出现问题排查复杂困难,如果部署运维工作需要其他人接手,这种模式对于接手之人简直就是折磨。

既然我们用jenkins,就应该充分发挥它的能力,建议所有的部署流程应该以流水线的方式定义在jenkins中,并且jenkins的流水线脚本可以放在版本库中管理,可以灵活的迁移复用。 整个流程在脚本中一目了然,同时jenkins可以管理部署的整个生命周期,非常方便排查问题。

部署流程

在开始创建部署任务之前,我们先大致了解一下咱们系统的部署流程

配置 jenkins

初始化

浏览器打开jenkins的管理地址,参考上一下中间件发布的端口直接访问就行。 因为是第一次打开jenkins,需要咱们咱们填入初始密码,这个密码是jenkins创建随机生成的,网页上提示这个密码所在位置:/var/jenkins_home/secrets/initialAdminPassword

注意:因为咱们jenkins是docker部署,所以这个地址实际是docker容器内部的地址。 但是我们部署时已经将jenkins_home目录的数据卷映射至主机目录,所以不需要去容器内部去找。

比如我是映射到了/data/jenkins/jenkins_home目录 直接获取

sh
$ cat /data/jenkins/jenkins_home/secrets/initialAdminPassword

显示出密码直接填入浏览器,点击 继续

会让咱们安装插件,点 安装推荐的插件

安装需要时间。等待插件全部安装完成,如果出现安装失败,点继续安装直到所有插件均安装完成。

推荐插件安装完成后,会让我们设置一下管理员密码,照着设置完成就好,记住就这个密码,后面登录都会用这个新密码登录,忘记了挺麻烦的。

点击 保存并完成

实例配置,默认即可,一般和你当前浏览器地址一致,点击 保存并完成

ok,已经初始化好了 开始使用

插件安装

Git拉取代码的插件开头社区推荐已经默认安装。 后端发布主要需要依赖 MavenJava17 ,这两个插件也已默认安装。 因为我们安装的是 jdk17-lts 版本的 jenkins 容器,所以自带 jdk17 环境,无需额外安装配置jdk17。

前端发布需要 NodeJs 打包编译,之前安装的推荐插件里面没有 NodeJs ,所以我们需要安装 NodeJs 插件。 在我们发布任务时,想要获取git源码库的所有分支,实现选择分支发布,还需要插件 Git Parameter

打开 系统管理 -> 插件管理 -> 可选
如果是英文的可能中文没翻译好,Manage Jenkins -> Plugins -> Available plugins

输入 Nodejs 查找到结果 选中并安装。 输入 Git Parameter 查找到结果 选中并安装。

选中 安装完成后重启Jenkins(空闲时)

等待插件安装完成并重启 Jenkins

到这里,我们需要的插件工具均已安装完成,不过使用之前还需要对这些插件工具进行具体的配置。

工具配置

打开 系统管理 -> 全局工具配置

配置NodeJs:找到 Maven 安装 -> 新增 Maven
勾选自动安装,选择最新版本即可。
填写Name: 一般填写工具名 + 版本号 (这个名称相当于一个唯一标识,后面jenkins脚本使用此工具时需要用这个Name指定)
我这边填写 maven3.9.5

配置NodeJs:找到 NodeJs 安装 -> 新增 NodeJs
同理:填写Name并选择版本,建议选择Node20,咱们前端要求NodeJs18以上,选18亦可以。
我这边Name填写:nodejs20.9.0

点击 应用保存

添加凭据

凭据可以简单的理解为就是用户名和密码,jenkins可以将这些凭据统一保存,再给每个凭据设置一个唯一的id,这样后面有些工具使用需要账户登录的时候我们就可以用id来指定使用哪个凭据了。 这样的好处是,咱们的密码等凭证可以不用直接明文写在部署脚本文件里面,安全性大大提高,同时一个密码可以被多出引用。

比如我现在要拉取 gitee 的代码,如果仓库是私有的,需要密码登录才能拉取,那我就新建一个gitee的账号密码:

点击 系统管理 -> 凭据 -> 系统 -> 全局凭据 (unrestricted)

点击 添加凭据(Add Credentials)

点击 Create 保存后我们就创建了一个全局的账号密码凭据,id为 gitee,这个 id 我们后面还会用到

开始部署

前面我们已经安装并配置好jenkins和插件,下面来看看创建任务来部署我们的程序。
需要创建两个任务,一个部署前端,一个部署后端微服务。

前端部署

回到首页,点击 新建任务,给任务起个名字,选择流水线,点击 确认,我们的一个任务就新建好了

接着我们配置次任务,进入该任务详情,点击 配置,滚动到流水线,粘贴一下内容:

groovy
#!groovy
pipeline {
//    任何代理节点执行,随机分配
    agent any

//    //可以指定到某类标签代理节点
//    agent {
//        label: 'windows'
//    }

//    //也可以用docker镜像创建容器代理节点
//    docker {
//        image 'maven:3.9'   //镜像地址
//    }

    options {
        timeout(time: 2, unit: 'HOURS')  //超时时间2小时
        retry(1)    //失败后重试次数
    }

    //使用全局工具
    tools {
        //引用之前全局工具配置的NodeJs的name
        nodejs 'nodejs20.9.0'
    }

    //参数化构建
    parameters {
        //添加一个下拉选项,选择是发布前端项目还是发布文档
        choice(name: 'DEPLOY_TYPE', choices: ['frontend', 'docs'], description: '发布类型')
        //获取git仓库分支列表,并选择
        gitParameter branchFilter: 'origin/(.*)',
                defaultValue: 'main',
                name: 'GIT_PROJECT_BRANCH',
                type: 'PT_BRANCH',
                description: '选择发布分支',
                useRepository: 'https://gitee.com/sun-xiaohan/xh-admin-frontend.git'
    }

    //环境变量
    environment {
        //用户密码凭据
        cred_id = "gitee"
        project_name = "xh-admin-frontend"
        //容器名
        service_name = "${project_name}"
        //镜像名
        image = "${service_name}:latest"
        //容器映射端口
        outer_port = "2000"
        //环境
        profile = "production"
        //加入网络
        network = 'xh_default'
        Dockerfile = '''
            FROM nginx:1.25.3-alpine-slim
            # 将编译好的静态文件放进nginx根目录
            COPY dist /usr/share/nginx/html
        '''
    }

    stages {
        stage("拉取代码") {
            steps {
                //拉取代码
                git credentialsId: cred_id, url: 'https://gitee.com/sun-xiaohan/xh-admin-frontend.git', branch: params.GIT_PROJECT_BRANCH
            }
        }
        stage("编译项目") {
            steps {
                sh "npm install"
                script {
                    //发布前端项目
                    if (DEPLOY_TYPE == 'frontend') {
                        sh "npm run build:${profile}"
                    } else {
                        //发布文档
                        sh "npm run docs:build"
                        //发布文档生成的dist在docs/.vitepress,移到当前目录来
                        sh "rm -rf dist && mv docs/.vitepress/dist dist"
                        //把容器名称调整一下
                        service_name = 'xh-admin-docs'
                        //把容器名称调整一下
                        image = service_name + ':latest'
                        //对外端口号改下
                        outer_port = '1999'
                    }
                }
            }
        }
        stage("构建docker镜像") {
            steps {
                //创建Dockerfile
                sh "echo '${Dockerfile}' > Dockerfile"
                //构建镜像
                sh "docker build -t ${image} ."
            }
        }
        stage("容器部署") {
            steps {
                sh 'if !(test -z "$(docker ps -a | grep -w ' + service_name + ' )") then \n' +
                        '        docker stop ' + service_name +' \n' +
                        '        docker rm ' + service_name + '\n' +
                        '    fi'

                sh "docker run " +
                        " --network=${network} " +
                        " --name ${service_name} " +
                        " -p ${outer_port}:80 " +
                        " --restart=always " +
                        " -d ${image}"
            }
        }
    }

    post {
        //失败后会执行到这里,可以写一些发送邮件的逻辑放在这里提醒
        failure {
            echo '可以写一些发送邮件的逻辑放在这里提醒!'
        }
    }
}

如下图:

点击 保存

这样我们前端的构建任务就创建完成了,所有的部署流程均在流程脚本中定义,jenkins管理整个部署流程的生命周期。

点击 立即 构建此任务,因为第一次构建需要初始化一些选项,可能会构建失败,第一次初始化以后,后续就可以选择参数化构建。

构建成功后截图:

后端服务部署

创建任务流程同上,脚本内容略有不同:

groovy
#!groovy
pipeline {
    agent any

    options {
        timeout(time: 2, unit: 'HOURS')  //超时时间2小时
        retry(1)    //失败后重试次数
    }

//    triggers {
//        //定义一个触发器,每天1点触发构建
//        cron(* 1 * * *)
//    }

    //使用全局工具
    tools {
        // jenkins已内置jdk17环境
        // jdk 'jdk17'
        //需要maven环境打包,之前全局配置的Maven Name
        maven 'maven3.9.5'
    }

    //参数化构建
    parameters {
        //添加一个发布下拉选项,选择发布哪个服务
        choice(name: 'DEPLOY_TYPE', choices: ['system', 'file', 'generator'], description: '发布服务')
    }

    //环境变量
    environment {
        cred_id = "gitee"
        git_project_url = "https://gitee.com/sun-xiaohan/xh-admin-backend.git"
        //发布分支
        git_project_branch = "main"
        //发布服务类型
        dir_name = "${DEPLOY_TYPE}"
        service_name = "xh-admin-${DEPLOY_TYPE}"
        mirror_name = "${service_name}:latest"
        NACOS_SERVER_ADDR="nacos:8848"
        NACOS_NAMESPACE="production"
        NACOS_GROUP="xh-admin"
        NACOS_USERNAME="nacos"
        NACOS_PASSWROD="nacos"
        outer_port = ""
        inner_port = ""
        network = 'xh_default'
        Dockerfile = '''
            # 基础镜像
            FROM openjdk:21-ea-17-jdk
            # author
            MAINTAINER sxh
            #设置时区
            RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
            # 创建目录
            RUN mkdir -p /app
            # 指定路径
            WORKDIR /app
            # 复制jar文件到路径
            ADD *.jar /app/app.jar
            # 启动命令,同时设置jvm内存
            ENTRYPOINT ["java","-jar","app.jar"]
        '''
    }

    stages {
        stage("拉取代码") {
            steps {
                //拉取源码
                git credentialsId: cred_id, url: git_project_url, branch: git_project_branch
            }
        }
        stage("打包编译") {
            steps {
                script {
                    if(dir_name == 'system') {
                        outer_port = '2102'
                        inner_port = '2102'
                    }
                    if(dir_name == 'file') {
                        outer_port = '2103'
                        inner_port = '2103'
                    }
                    if(dir_name == 'generator') {
                        outer_port = '2104'
                        inner_port = '2104'
                    }

                    sh "mvn clean -DskipTests=true"
                    sh "mvn install -DskipTests=true"

                    def buildDir = "build-${dir_name}"
                    sh "rm -rf ${buildDir} && mkdir ${buildDir}"
                    sh "mvn package -f ${dir_name}/service/pom.xml -DskipTests=true"
                    sh "mv ${dir_name}/service/target/*.jar ${buildDir}/app.jar"
                    //创建Dockerfile
                    sh "echo '${Dockerfile}' > ${buildDir}/Dockerfile"
                    sh "cd ${buildDir} && docker build -t ${mirror_name} ."
                }
            }
        }
        stage("服务部署") {
            steps{
                script {
                    // 如果容器已存在,则移除容器
                    sh '''
                        if !(test -z "$(docker ps -a | grep -w $service_name )") then
                            docker stop ${service_name}
                            docker rm ${service_name}
                        fi
                    '''
                    sh "docker run " +
                            " --network=${network} " +
                            " --name ${service_name} " +
                            " -p ${outer_port}:${inner_port} " +
                            " -e NACOS_SERVER_ADDR=${NACOS_SERVER_ADDR} " +
                            " -e NACOS_NAMESPACE=${NACOS_NAMESPACE} " +
                            " -e NACOS_GROUP=${NACOS_GROUP} " +
                            " -e NACOS_USERNAME=${NACOS_USERNAME} " +
                            " -e NACOS_PASSWROD=${NACOS_PASSWROD} " +
                            " -e SERVER_PORT=${inner_port} " +
                            " -d ${mirror_name} "
                }
            }
        }
    }

    post {
        //失败后会执行到这里,可以写一些发送邮件的逻辑放在这里提醒下相关人员
        failure {
            echo '可以写一些发送邮件的逻辑放在这里提醒!'
        }
    }
}

这两个构建任务,分别用来构建前端和后端,并且具有参数选择,前端任务可以选择构建前台项目还是docs,后端任务可以选择具体构建哪一个微服务,我们将需要的都执行一遍。 等部署任务成功后,去服务器看下容器:

可以看到服务都部署成功了,后续我们提交代码后,需要发布程序只需要登录 jenkins 点击构建任务即可。

jenkins 功能非常强大,并且有非常丰富的插件生态,可以实现各种复杂的自动化任务,本教程只是做一个样例做参考学习,更多用法请查阅相关文档学习。

同学们跟着教程走下来,可以对整个部署流程做个大致了解,有些地方不懂没关系,在实际使用中慢慢理解学习,举一反三,必能融会贯通。