通過Jenkins流水線自動(dòng)部署SpringBoot應(yīng)用到K8S集群

上文我們通過Jenkins流水線完成了對(duì).NetCore應(yīng)用部署K8S集群的實(shí)踐笼吟,地址:http://www.reibang.com/p/b062e5a3d04f
今天我們嘗試將Springboot項(xiàng)目自動(dòng)部署到K8S集群中

1.準(zhǔn)備工作

由于我們前面搭建了私有的nexus倉庫桃纯,為了今后開發(fā)部署方便衣迷,我們將服務(wù)器maven指向我們的私有倉庫。
Maven鏡像倉庫
前面我們已經(jīng)搭建了Nexus鏡像倉庫(http://www.reibang.com/p/6073dc452efa)
Nexus倉庫默認(rèn)已經(jīng)給我們配置好了Maven的倉庫儒溉,為了加快下載速度,我們調(diào)整下倉庫的路徑,使用阿里的Maven鏡像源梦谜。
地址修改為:http://maven.aliyun.com/nexus/content/groups/public/


k8s master節(jié)點(diǎn)修改maven源為私有倉庫地址

cd /root/.m2   進(jìn)入m2目錄,如果沒有就在root目錄創(chuàng)建一個(gè)。
ls  查看是否有settings.xml文件唁桩,如果沒有則創(chuàng)建一個(gè)
vim settings.xml  文件內(nèi)容修改如下:
<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/是我們私有倉庫地址--------------

編譯環(huán)境準(zhǔn)備
編譯過程需要jdk及maven的支持闭树,master節(jié)點(diǎn) /root/jenkins/是我們主要的編譯環(huán)境,目錄如下圖


deploy-template:存放yaml文件模板荒澡,yaml模板略报辱,可自行編寫。
deplay:存放根據(jù)模板生成后的yaml文件
jdk:jdk環(huán)境
tools:存放不同的jdk版本
workspace:存放jenkins下載及編譯區(qū)
tools目錄結(jié)構(gòu)如下单山,主要存放各種不同版本的編譯環(huán)境碍现,每一版本一個(gè)目錄,由jenkins決定用哪個(gè)編譯環(huán)境執(zhí)行:

springboot項(xiàng)目開發(fā)
用開發(fā)工具制作一個(gè)簡單的springboot項(xiàng)目米奸,并上傳到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í)為空")
        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("參數(shù)解析錯(cuò)誤" + 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("#############################################流水線執(zhí)行成功##################################################")
}

流水線腳本上傳GIT中慢睡。

3.配置Jenkins流水線

Jenkins創(chuàng)建流水線,增加一個(gè)文本參數(shù)JSON_BODY铡溪,如下圖:



在流水線配置環(huán)節(jié)漂辐,我們選擇腳本從git拉取,這種方式便于后續(xù)腳本調(diào)整升級(jí)與維護(hù)棕硫。



保存流水線髓涯。

4.驗(yàn)證發(fā)布過程

啟動(dòng)流水線,輸入啟動(dòng)參數(shù)哈扮,啟動(dòng)構(gòu)建

{
    "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"
} 

經(jīng)過幾次失敗后調(diào)試纬纪,總算看到以下信息,說明流水線運(yùn)行成功:

#############################################流水線執(zhí)行成功##################################################
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

我們打開瀏覽器輸入IP:nodePort端口灶泵,看到我們的demo請(qǐng)求已經(jīng)正常顯示育八。


5.結(jié)語

無論.net/java其部署過程基本一致,只是編譯腳本及dockerfile的編寫略有差異赦邻,我們可以為不同語言編寫不同的jenkins腳本實(shí)現(xiàn)各自的自動(dòng)化部署髓棋,隨著這項(xiàng)工作的熟練,我們還可以將其歸整為公共模板的形式供其他項(xiàng)目使用惶洲。
私有maven倉庫并不是必須的按声,但為了開發(fā)及部署的便捷,強(qiáng)烈建議自己搭建一個(gè)私有倉庫恬吕。
另外Jenkins腳本盡可能少用shell腳本签则,shell的靈活度及調(diào)試都是比較讓人苦惱的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铐料,一起剝皮案震驚了整個(gè)濱河市渐裂,隨后出現(xiàn)的幾起案子豺旬,更是在濱河造成了極大的恐慌,老刑警劉巖柒凉,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件族阅,死亡現(xiàn)場離奇詭異,居然都是意外死亡膝捞,警方通過查閱死者的電腦和手機(jī)坦刀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔬咬,“玉大人鲤遥,你說我怎么就攤上這事×炙遥” “怎么了盖奈?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長北启。 經(jīng)常有香客問我卜朗,道長,這世上最難降的妖魔是什么咕村? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮蚊俺,結(jié)果婚禮上懈涛,老公的妹妹穿的比我還像新娘。我一直安慰自己泳猬,他們只是感情好批钠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著得封,像睡著了一般埋心。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忙上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天拷呆,我揣著相機(jī)與錄音,去河邊找鬼疫粥。 笑死茬斧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梗逮。 我是一名探鬼主播项秉,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慷彤!你這毒婦竟也來了娄蔼?” 一聲冷哼從身側(cè)響起怖喻,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岁诉,沒想到半個(gè)月后锚沸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唉侄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年咒吐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片属划。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恬叹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出同眯,到底是詐尸還是另有隱情绽昼,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布须蜗,位于F島的核電站硅确,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏明肮。R本人自食惡果不足惜菱农,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柿估。 院中可真熱鬧循未,春花似錦、人聲如沸秫舌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽足陨。三九已至嫂粟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墨缘,已是汗流浹背星虹。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飒房,地道東北人搁凸。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像狠毯,于是被迫代替她去往敵國和親护糖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容