美文网首页K8s
通过Jenkins流水线自动部署SpringBoot应用到K8S

通过Jenkins流水线自动部署SpringBoot应用到K8S

作者: 上岸的魚 | 来源:发表于2020-03-27 22:04 被阅读0次

    上文我们通过Jenkins流水线完成了对.NetCore应用部署K8S集群的实践,地址:https://www.jianshu.com/p/b062e5a3d04f
    今天我们尝试将Springboot项目自动部署到K8S集群中

    1.准备工作

    由于我们前面搭建了私有的nexus仓库,为了今后开发部署方便,我们将服务器maven指向我们的私有仓库。
    Maven镜像仓库
    前面我们已经搭建了Nexus镜像仓库(https://www.jianshu.com/p/6073dc452efa)
    Nexus仓库默认已经给我们配置好了Maven的仓库,为了加快下载速度,我们调整下仓库的路径,使用阿里的Maven镜像源。
    地址修改为:http://maven.aliyun.com/nexus/content/groups/public/


    k8s master节点修改maven源为私有仓库地址
    cd /root/.m2   进入m2目录,如果没有就在root目录创建一个。
    ls  查看是否有settings.xml文件,如果没有则创建一个
    vim settings.xml  文件内容修改如下:
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> 
        <mirrors>
            <mirror>
                <id>eosmaven</id>
                <mirrorOf>central</mirrorOf>
                <name>eos maven</name>
                <url>http://nexus.xxxx.cn/repository/maven-public/</url>   
            </mirror>
        </mirrors>    
    </settings>
    ---------------说明:http://nexus.xxxx.cn/repository/maven-public/是我们私有仓库地址--------------
    

    编译环境准备
    编译过程需要jdk及maven的支持,master节点 /root/jenkins/是我们主要的编译环境,目录如下图


    deploy-template:存放yaml文件模板,yaml模板略,可自行编写。
    deplay:存放根据模板生成后的yaml文件
    jdk:jdk环境
    tools:存放不同的jdk版本
    workspace:存放jenkins下载及编译区
    tools目录结构如下,主要存放各种不同版本的编译环境,每一版本一个目录,由jenkins决定用哪个编译环境执行:

    springboot项目开发
    用开发工具制作一个简单的springboot项目,并上传到git仓库中,编写过程略。

    2.编写流水线脚本

    println("#############################################开始流水线##################################################")
    //env.JOB_NAME ***
    //env.WORKSPACE /var/jenkins_home/workspace/***
    //env.K8S_TYPE="$params.K8S_TYPE"
    //env.DOCKER_TYPE="$params.DOCKER_TYPE"
    def jobName = "${JOB_NAME}"
    def defaultEnv,defaultApiHost,defaultImageHost,defaultApolloMeta, imageVersion,versionTimestamp, codeUrl, branch, appId, nodePort, serviceAndversion, jdkVersion, namespace, nodes, host, replicas, cpu, memory,  devopsUrl, version, service, imageName
    try {
        def paraBodyJson = readJSON text: "${params.JSON_BODY}"
    
        defaultEnv = paraBodyJson.defaultEnv
        if (!defaultEnv?.trim()) {
            println("defaultEnv 为空")
            defaultEnv  = "uat";
        }
        defaultApiHost = paraBodyJson.defaultApiHost
        if (!defaultApiHost?.trim()) {
            println("defaultApiHost 为空")
            defaultApiHost  = "api.xxx.cn";
        }
    
        defaultImageHost = paraBodyJson.defaultImageHost
        if (!defaultImageHost?.trim()) {
            println("defaultImageHost 为空")
            defaultImageHost = "hub.xxx.cn/java/";
        }
    
        defaultApolloMeta = paraBodyJson.defaultApolloMeta
        if (!defaultApolloMeta?.trim()) {
            println("defaultApolloMeta 为空")
            defaultApolloMeta = "http://service-apollo-config-server-dev.apollo:8080";
        }
    
        service = paraBodyJson.service
        if (!service?.trim()) {
            println("service 不能为空")
            sh "exit 1"
        }
        version = paraBodyJson.version
        if (!version?.trim()) {
            println("version 不能为空")
            sh "exit 1"
        }
        serviceAndversion = service + "-" + version
        appId = paraBodyJson.appId
        if (!appId?.trim()) {
            println("appId 不能为空")
            sh "exit 1"
        }
    
        nodePort = paraBodyJson.nodePort
        if (!nodePort?.trim()) {
            println("nodePort 不能为空")
            sh "exit 1"
        }
        println("nodePort:" + nodePort)
    
        codeUrl = paraBodyJson.codeUrl
        imageVersion = paraBodyJson.imageVersion
        if (!codeUrl?.trim() && !imageVersion?.trim()) {
            println("codeUrl和imageVersion 不能同时为空")
            sh "exit 1"
        }
        branch = paraBodyJson.branch
        if (!branch?.trim()) {
            branch = "master"
        }
        namespace = paraBodyJson.namespace
        if (!namespace?.trim()) {
            println("namespace 不能为空")
            sh "exit 1"
        }
        versionTimestamp = paraBodyJson.versionTimestamp
        if (!versionTimestamp?.trim()) {
            versionTimestamp =  version + "." + System.currentTimeMillis()
        }
    
        host = paraBodyJson.host
        if (!host?.trim()) {
            host = defaultApiHost
        }
        jdkVersion = paraBodyJson.jdkVersion
        if (!jdkVersion?.trim()) {
            jdkVersion = "openjdk8"
        }
        nodes = paraBodyJson.node
        if (!nodes?.trim()) {
            nodes = ""
        }
        replicas = paraBodyJson.replicas
        if (!replicas?.trim()) {
            replicas = "1"
        }
        cpu = paraBodyJson.cpu
        if (!cpu?.trim()) {
            cpu = "0"
        }
        memory = paraBodyJson.memory
        if (!memory?.trim()) {
            memory = "0"
        }  
    } catch (errx) {
        println("参数解析错误" + errx)
        sh "exit 1"
    }
    
    node('k8s-master') { 
            imageName = defaultImageHost + service + ":" +versionTimestamp
            stage('Clone Code') {
                println("#############################################开始拉取代码##################################################")
                sh 'find /root/.m2/repository/ -name "*lastUpdated*" | xargs rm -rf'
                git branch: branch, url: codeUrl
                println("#############################################拉取代码成功##################################################")
            }
            stage('Maven Compile') {
                println("#############################################开始编译##################################################")
                withEnv(["JAVA_HOME=/root/jenkins/tools/${jdkVersion}", "MVN_HOME=/root/jenkins/tools/maven3"]) {
                    sh '"$MVN_HOME/bin/mvn" --version'
                    sh '"$MVN_HOME/bin/mvn" compile'
                }
                println("#############################################编译成功##################################################")
            }     
            stage('Maven Build') {
                println("#############################################开始打包##################################################")
                def dockerfile = """
    FROM hub.xxxx.cn/base/openjdk:11-jre
    USER root
    ADD  *.jar  /
    EXPOSE 8080
    ENTRYPOINT ["java", "-jar", "/app.jar"]
            """
                if ("openjdk8".equals(jdkVersion.toString())) {
                    dockerfile = dockerfile.replace("11-jre", "8-jre")
                }
                withEnv(["JAVA_HOME=/root/jenkins/tools/${jdkVersion}", "MVN_HOME=/root/jenkins/tools/maven3"]) {
                    sh '"$MVN_HOME/bin/mvn" --version'
                    sh '"$MVN_HOME/bin/mvn" clean:clean package -DskipTests'
                    sh 'rm -rf ${WORKSPACE}/docker'
                    sh 'mkdir -p ${WORKSPACE}/docker'
                    sh 'cp -r ${WORKSPACE}/target/*.jar ./docker/app.jar'
                    sh "echo '${dockerfile}' >./docker/Dockerfile"
                }
                println("#############################################打包成功##################################################")
            }
            stage('Build Image') {
                println("#############################################开始build docker镜像##################################################")
                sh "docker build -t ${imageName} ${WORKSPACE}/docker/."
                sh "docker push ${imageName}"
                println("#############################################build docker镜像件成功##################################################")
            }
        }
        stage('K8S Deploy') {
            println("#############################################开始部署到集群##################################################")
            def yamldir = "/root/jenkins/deploy/deploy-"
            def yamlTemplatedir = "/root/jenkins/deploy-template/deploy-"
            if (nodes == 'dmz') {
                yamlTemplatedir = yamlTemplatedir + 'dmz-'
            }
            yamlTemplatedir = yamlTemplatedir + 'project.yaml'
            println(yamlTemplatedir)
            def fileContents = readFile file: yamlTemplatedir, encoding: "UTF-8"
            fileContents = fileContents.replace("{{namespace}}", namespace)
            fileContents = fileContents.replace("{{name}}", serviceAndversion)
            fileContents = fileContents.replace("{{replicas}}", replicas)
            fileContents = fileContents.replace("{{cpu}}", cpu)
            fileContents = fileContents.replace("{{host}}", host)
            fileContents = fileContents.replace("{{memory}}", memory)
            fileContents = fileContents.replace("{{image}}", imageName)
            fileContents = fileContents.replace("{{appId}}", appId)
            fileContents = fileContents.replace("{{apolloMeta}}", defaultApolloMeta)
            fileContents = fileContents.replace("{{nodePort}}", nodePort)
            sh "rm -rf ${yamldir}${serviceAndversion}.yaml"
            sh "echo '${fileContents}' >${yamldir}${serviceAndversion}.yaml"
            //sh "kubectl apply -f /root/jenkins/deployment/deployment-${serviceAndversion}.yaml"
            def consoleApply = sh(script: 'kubectl apply -f '+yamldir + serviceAndversion + '.yaml', returnStdout: true)
            String[] consoleArr = consoleApply.split("\n|\r");
            for (console in consoleArr) {
                println(console)
                /* if(console.startsWith("deployment.apps") && console.endsWith("unchanged")){
                     println("#############################################部署到集群没有变化,流水线退出##################################################")
                     sh "exit 1"
                 }*/
            }       
        }     
        println("#############################################流水线执行成功##################################################")
    }
    

    流水线脚本上传GIT中。

    3.配置Jenkins流水线

    Jenkins创建流水线,增加一个文本参数JSON_BODY,如下图:



    在流水线配置环节,我们选择脚本从git拉取,这种方式便于后续脚本调整升级与维护。



    保存流水线。

    4.验证发布过程

    启动流水线,输入启动参数,启动构建

    {
        "codeUrl":"https://xxxxx.com/spring01.git",
        "jdkVersion":"openjdk8",
        "remark":"",
        "defaultEnv":"uat",
        "appId":"123",
        "defaultImageHost":"hub.xxx.cn/spring/",
        "nodePort":"32005",
        "version":"blue",
        "service":"spring01",
        "namespace":"demo"
    } 
    

    经过几次失败后调试,总算看到以下信息,说明流水线运行成功:

    #############################################流水线执行成功##################################################
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] End of Pipeline
    Finished: SUCCESS
    

    我们打开浏览器输入IP:nodePort端口,看到我们的demo请求已经正常显示。


    5.结语

    无论.net/java其部署过程基本一致,只是编译脚本及dockerfile的编写略有差异,我们可以为不同语言编写不同的jenkins脚本实现各自的自动化部署,随着这项工作的熟练,我们还可以将其归整为公共模板的形式供其他项目使用。
    私有maven仓库并不是必须的,但为了开发及部署的便捷,强烈建议自己搭建一个私有仓库。
    另外Jenkins脚本尽可能少用shell脚本,shell的灵活度及调试都是比较让人苦恼的。

    相关文章

      网友评论

        本文标题:通过Jenkins流水线自动部署SpringBoot应用到K8S

        本文链接:https://www.haomeiwen.com/subject/efgruhtx.html