授之以漁-運(yùn)維平臺發(fā)布模塊四(Jenkins Pipeline+Saltstack改造篇)

接上篇《授之以漁-運(yùn)維平臺發(fā)布模塊三(Jenkins篇)》,今天介紹下針對Jenkins pipeline+saltstack的發(fā)布改造姐呐。

一、 Jenkins Pipeline的總體介紹

  • Pipeline徒欣,簡而言之饭望,就是一套運(yùn)行于Jenkins上的工作流框架悔政,將原本獨立運(yùn)行于單個或者多個節(jié)點的任務(wù)連接起來翁涤,實現(xiàn)單個任務(wù)難以完成的復(fù)雜流程編排與可視化桥言。
  • Pipeline是Jenkins2.X的最核心的特性,幫助Jenkins實現(xiàn)從CI到CD與DevOps的轉(zhuǎn)變
  • Pipeline是一組插件迷雪,讓Jenkins可以實現(xiàn)持續(xù)交付管道的落地和實施。
  • 持續(xù)交付管道(CD Pipeline)是將軟件從版本控制階段到交付給用戶或客戶的完整過程的自動化表現(xiàn)虫蝶。軟件的每一次更改(提交到源代碼管理系統(tǒng))都要經(jīng)過一個復(fù)雜的過程才能被發(fā)布章咧。
  • Pipeline提供了一組可擴(kuò)展的工具,通過Pipeline Domain Specific Language(DSL)syntax可以達(dá)到Pipeline as Code(Jenkinsfile存儲在項目的源代碼庫)的目的能真。
  • Stage:階段赁严,一個Pipeline可以劃分成若干個Stage,每個Stage代表一組操作粉铐,例如:“Build”疼约,“Test”,“Deploy”蝙泼。

二程剥、 早期設(shè)計思路

在Jenkins早期版本中,還沒有Saltstack插件汤踏,所以項目在Jenkins構(gòu)建后织鲸,只能是通過Jenkins調(diào)用SSH或者SFTP將項目傳到目標(biāo)主機(jī)上舔腾。但我們公司由于有堡壘機(jī)的存在,SSH的唯一入口只能是堡壘機(jī)搂擦,傳統(tǒng)的方法不可行稳诚。于是就有了《授之以漁-運(yùn)維平臺發(fā)布模塊一(Jenkins篇)》http://www.reibang.com/p/0e052e79e134

上文中那個可以遠(yuǎn)程回調(diào)的Saltstack接口瀑踢,小名叫Salt_jenkins_post君扳还,Jenkins上的回調(diào)參數(shù)如下curl -d "job=JOB_NAME" http://xxxx/salt_jenkins_post。是的橱夭,沒錯氨距,只需要回傳項目名字即可,后續(xù)的一些針對這個項目的要做的一系列動作都存在一個叫做Release表中徘钥,他包含了如下幾個字段:

class Release (models.Model):
    class Meta: 
        verbose_name = '項目發(fā)布'
        verbose_name_plural = verbose_name
        ordering = ['-release_time']
    release_name = models.CharField('項目名稱',max_length=40)
    release_time = models.DateTimeField('項目發(fā)布時間')
    release_svn_address = models.CharField('項目SVN地址',max_length=170)
    release_no = models.CharField('項目版本號',max_length=10,blank=True)
    release_next_no = models.CharField('項目下次版本號',max_length=10,blank=True)
    release_hosts = models.TextField('發(fā)布主機(jī)',blank=True,max_length=1000)
    release_path = models.CharField('發(fā)布路徑',max_length=50)
    release_fail_hosts_count = models.PositiveIntegerField('成功主機(jī)數(shù)',blank=True)
    release_success_hosts_count = models.PositiveIntegerField('失敗數(shù)字?jǐn)?shù)',blank=True)
    release_release_dir = models.TextField('發(fā)布路徑',blank=True,max_length=1000)
    release_reboot_sync = models.CharField('同步重啟',default='0',blank=True,max_length=5)
    release_monitor_process = models.CharField('監(jiān)測進(jìn)程',blank=True,max_length=150)
    release_monitor_dir = models.CharField('監(jiān)測目錄',blank=True,max_length=150)
    release_purge = models.CharField('清除緩存',default='0',blank=True,max_length=5)
    release_purge_name = models.CharField('清除緩存項目',blank=True,max_length=40)
    release_purge_dir = models.CharField('清除緩存項目路徑',default='',blank=True,max_length=40)
    release_pipeline = models.CharField('項目流水線',default='',blank=True,max_length=40)
    release_after_commands = models.TextField('項目前置命令',blank=True,max_length=1000)
    release_before_commands = models.TextField('項目后置命令',blank=True,max_length=1000)

Salt_jenkins_post君的作用就是在接到Jenkins構(gòu)建后(Batch tasks)的回調(diào)命令衔蹲,會根據(jù)Jenkins發(fā)布目標(biāo)的JOB_NAME找到發(fā)布主機(jī)release_hosts,最后通過在通過Saltstack調(diào)用目標(biāo)發(fā)布主機(jī)執(zhí)行pkg.upgrade_available呈础、pkg.mod_repo舆驶、pkg.get_repopkg.install來安裝RPM包而钞,最后在根據(jù)配置信息決定是否調(diào)用release_before_commands執(zhí)行一些后置命令沙廉,如重啟,刪除及release_purge域名刷新推送臼节,如CDN撬陵。

優(yōu)點:一站式服務(wù),全含
缺點:無法拆分每一步网缝,無法做到發(fā)布步驟的獨立步驟的重放

三巨税、 Pipeline帶來的新的設(shè)計思路

利用Stage:階段,一個Pipeline可以劃分成若干個Stage粉臊,每個Stage代表一組操作草添,于是Release_pipeline_model君誕生了。

class Release_pipeline_model (models.Model):
    class Meta:
        verbose_name = '項目發(fā)布流水線模塊'
        verbose_name_plural = verbose_name
    release_pipeline_model_name = models.CharField('流水線模塊', max_length=40)
    release_pipeline_model_id = models.CharField('流水線模塊簡稱', max_length=40)
    release_pipeline_responsive_name = models.CharField('流水線Responsive名稱', max_length=40)
    release_pipeline_model_edit_menu = models.CharField('流水線重放可視',default='0',max_length=40)
    release_pipeline_model_template = models.TextField('流水線模塊模板',  blank=True, max_length=1000)
    release_pipeline_model_change_at = models.DateTimeField('最后更改時間', auto_now=True)

    def __unicode__(self):
        return str(self.release_pipeline_model_name)

他的究極完成體如下:存放著名稱扼仲,標(biāo)識远寸,可視化按鈕及具體的Stage內(nèi)容(里面的部分內(nèi)容采用變量,根據(jù)不同項目生成不同的Stage信息屠凶,Pipeline語法詳見https://jenkins.io/doc/book/pipeline/

image.png

Salt_jenkins_post君也被拆分成了Salt_jenkins_after_commands君驰后、Salt_jenkins_before_commands君、Salt_jenkins_getrepo君矗愧、Salt_jenkins_install君灶芝、Salt_jenkins_purge君、Salt_jenkins_reboote君、Salt_jenkins_upgradeavailable君监署。每人負(fù)責(zé)一部分颤专,且在Release表的release_pipeline記錄著他們的排序。

image.png

最后的系統(tǒng)會根據(jù)release_pipeline的排序生成Jenkins調(diào)用的Pipeline script钠乏,效果如下:

pipeline{
    agent any
    stages {

        stage('遷出代碼') {
            steps{
                checkout([$class: 'SubversionSCM', additionalCredentials: [], excludedCommitMessages: '', excludedRegions: '', excludedRevprop: '', excludedUsers: '', filterChangelog: false, ignoreDirPropChanges: false, includedRegions: '', locations: [[cancelProcessOnExternalsFail: true, credentialsId: 'f999860f-8121-4367-913b-21cf61969200', depthOption: 'infinity', ignoreExternalsOption: true, local: '.', remote: "http://172.17.130.96/svndata/dz.m_youth/trunk"]], quietOperation: true, workspaceUpdater: [$class: 'UpdateUpdater']])                
            }
        }
        stage('創(chuàng)建目錄') {
            steps{
                sh 'mkdir -p /home/release/$JOB_NAME'
            }
        }
        stage('打FPM包') {
            steps{
                sh 'fpm -s dir -x .svn  -t rpm -n $JOB_NAME -v $BUILD_NUMBER --prefix /home/dz -C /var/lib/jenkins/workspace/$JOB_NAME -p  /home/release/$JOB_NAME  ./'
            }
        }
        stage('更新YUM源') {
            steps{
                sh 'createrepo --update /home/release/$JOB_NAME/'
            }
        }
        stage('客戶端驗證YUM源是否存在') {
            steps{
                script {
                    try{
                        out = sh(script: 'curl -d "job_id=$JOB_NAME" http://veronica.youth.cn/cmdb/salt_jenkins_getrepo/', returnStdout: true)
                        if (out == '{"status":1}'){
                            echo 'salt_jenkins_getrepo ok'
                        }else{
                            sh 'exit 1'
                        }
                    }catch(Exception e){
                        error("salt_jenkins_getrepo not ok")
                    }    
                }
            }
        } 
        stage('客戶端檢測YUM源是否更新') {
            steps{
                script {
                    try{
                        out = sh(script: 'curl -d "job_id=$JOB_NAME" http://veronica.youth.cn/cmdb/salt_jenkins_upgradeavailable/', returnStdout: true)
                        if (out == '{"status":1}'){
                            echo 'salt_jenkins_upgradeavailable ok'
                        }else{
                            sh 'exit 1'
                        }
                    }catch(Exception e){
                        error("salt_jenkins_upgradeavailable not ok")
                    }    
                }            
            }
        }   
        stage('客戶端RPM包安裝') {
            steps{
                script {
                    try{
                        out = sh(script: 'curl -d "job_id=$JOB_NAME" http://veronica.youth.cn/cmdb/salt_jenkins_install/', returnStdout: true)
                        if (out == '{"status":1}'){
                            echo 'salt_jenkins_install ok'
                        }else{
                            sh 'exit 1'
                        }
                    }catch(Exception e){
                        error("salt_jenkins_install not ok")
                    }    
                }            
            }
        }  
        stage('客戶端后置命令') {
            steps{
                sh 'curl -d "job_id=$JOB_NAME" http://veronica.youth.cn/cmdb/salt_jenkins_before_commands/'
            }
        }    
    }
}

前臺的展示是這樣的:用的jquery.nestable.js

image.png

部分JS也雙手奉上:

jQuery(function($){
    $('.dd').nestable({
        maxDepth: 1,
    });
    var aa=$('#nestable_list_1').nestable('serialize')
    $('#release_pipeline').val(JSON.stringify(aa))

    $('.dd').nestable({'group':1}).on('change', function() {
            var r=$('#nestable_list_1').nestable('serialize')
            console.log(JSON.stringify(r))
            $('#release_pipeline').val(JSON.stringify(r))
        }
    );
});




 function reboot_sync_hidden(obj){
        if($("#release_reboot_sync_type").val()=="0"){
          document.getElementById("monitor_process").style.display ="none";
          document.getElementById("monitor_dir").style.display ="none";
        }
        else {
          document.getElementById("monitor_process").style.display ="block";
          document.getElementById("monitor_dir").style.display ="block";

    }
}

//獲取URL參數(shù)栖秕,?id=xx
function getQueryString(name) {
    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
    var r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return unescape(r[2]);
    }
    return null;
}

function release_pipeline_edit(){
    csrftokens = $.cookie('csrftoken')
    $('#responsive_pipeline_ediit form').submit(function() {
        $.ajax({
            type: "POST",
            url: "../release_pipeline_edit/",
            data: $('#responsive_pipeline_ediit form').serialize(),
            headers: {"X-CSRFtoken": csrftokens},
            async: true,
            cache: false,
            dataType: "json",
            beforeSend: function () {
                Metronic.blockUI({animate: true});
            },
            complete: function () {
                Metronic.unblockUI();
            },
            success: function (obj) {
                if (obj['status'] == 999) {
                    alert(obj['err'])
                    $('#responsive_pipeline_ediit').modal('hide');
                    $('#responsive_pipeline_ediit form')[0].reset();
                } else {
                    alert("流水線修改完成")
                    window.location.href = "../release_list";
                }
            }
        });
        return false;
    })
}
$(document).ready(function(){
    release_pipeline_edit();
});

 function purge_hidden(obj){
        if($("#release_purge_type").val()=="0"){
          document.getElementById("purge_name").style.display ="none";
          document.getElementById("purge_dir").style.display ="none";
        }
        else {
          document.getElementById("purge_name").style.display ="block";
          document.getElementById("purge_dir").style.display ="block";

    }
}

$("#reboot").bind("click", release_pipeline_reboot_edit);

function release_pipeline_reboot_edit(){
        var id = getQueryString("id");
        console.log(id)
        $.ajax({
            type: "GET",
            url: "../release_pipeline_reboot_edit_ajax/?id=" + id,
            async: true,
            cache: false,
            dataType: "json",
            beforeSend: function () {
                Metronic.blockUI({animate: true});
            },
            complete: function () {
                Metronic.unblockUI();
            },
            success: function (obj) {
                if (obj['status']==999){
                    alert(obj['err'])
                    $('#responsive_reboot').modal('hide');
                    $('#responsive_reboot form')[0].reset();
                }else{
                var monitor_process = obj['monitor_process']
                var monitor_dir = obj['monitor_dir']
                var reboot_sync = obj['reboot_sync']
                if (reboot_sync == "1") {
                    $("#release_reboot_sync_type").val(["1"]).trigger('change');
                    document.getElementById("monitor_process").style.display = "block";
                    document.getElementById("monitor_dir").style.display = "block";
                    $("#release_monitor_process").val(monitor_process)
                    $("#release_monitor_dir").val(monitor_dir)
                } else if (reboot_sync == "2") {
                    $("#release_reboot_sync_type").val(["2"]).trigger('change');
                    document.getElementById("monitor_process").style.display = "block";
                    document.getElementById("monitor_dir").style.display = "block";
                    $("#release_monitor_process").val(monitor_process)
                    $("#release_monitor_dir").val(monitor_dir)
                }
                $("#job_id").val(id)
            }}
        })
    }

$('#responsive_reboot form').submit(function(){
        var id = $('#job_id').val();
        var release_reboot_sync_type = $("#release_reboot_sync_type").val();
        var release_monitor_process = $("#release_monitor_process").val();
        var release_monitor_dir = $("#release_monitor_dir").val();
        var jsonData = {
            "id":id,
            "release_reboot_sync":release_reboot_sync_type,
            "release_monitor_process":release_monitor_process,
            "release_monitor_dir":release_monitor_dir,

        }
        csrftokens = $.cookie('csrftoken')
        $.ajax({
            type: "POST",
            data: jsonData,
            url: "../release_pipeline_reboot_edit_ajax/",
            headers:{ "X-CSRFtoken":csrftokens},
            async:true,
            cache: false,
            dataType: "json",
            beforeSend:function(){
                Metronic.blockUI({animate: true});
            },
            complete: function() {
                Metronic.unblockUI();
            },
            success: function(obj) {
                    if (obj['status']==999){
                        alert(obj['err'])
                        $('#responsive_reboot').modal('hide');
                        $('#responsive_reboot form')[0].reset();
                    }else if (obj['status'] == "1"){
                        $('#responsive_reboot').modal('hide');
                        $('#responsive_reboot form')[0].reset();
                        alert ("修改完成");
                    } else {
                        alert ("修改失敗");
                    }
                },
                });
                return false;
                });
        $('#responsive_reboot').on('hide.bs.modal', function () {
            location.reload();
        });


$("#purge").bind("click", release_purge_edit);

function release_purge_edit(){
        var id = getQueryString("id");
        console.log(id)
        $.ajax({
            type: "GET",
            url: "../release_pipeline_purge_edit_ajax/?id=" + id,
            async: true,
            cache: false,
            dataType: "json",
            beforeSend: function () {
                Metronic.blockUI({animate: true});
            },
            complete: function () {
                Metronic.unblockUI();
            },
            success: function (obj) {
                if (obj['status']==999){
                    alert(obj['err'])
                    $('#responsive_purge').modal('hide');
                    $('#responsive_purge form')[0].reset();
                }else{
                var purge_name = obj['purge_name']
                var purge_dir = obj['purge_dir']
                var purge = obj['purge']
                if (purge == "1") {
                    $("#release_purge_type").val(["1"]).trigger('change');
                    document.getElementById("purge_name").style.display = "block";
                    document.getElementById("purge_dir").style.display = "block";
                    $("#release_purge_name").val(purge_name)
                    $("#release_purge_dir").val(purge_dir)
                }
                $("#job_id").val(id)
            }}
        })
    }

$('#responsive_purge form').submit(function(){
        var id = $('#job_id').val();
        var release_purge_type = $("#release_purge_type").val();
        var release_purge_name = $("#release_purge_name").val();
        var release_purge_dir = $("#release_purge_dir").val();
        var jsonData = {
            "release_purge":release_purge_type,
            "release_purge_name":release_purge_name,
            "release_purge_dir":release_purge_dir,
            "id":id
        }
        csrftokens = $.cookie('csrftoken')
        $.ajax({
            type: "POST",
            data: jsonData,
            url: "../release_pipeline_purge_edit_ajax/",
            headers:{ "X-CSRFtoken":csrftokens},
            async:true,
            cache: false,
            dataType: "json",
            beforeSend:function(){
                Metronic.blockUI({animate: true});
            },
            complete: function() {
                Metronic.unblockUI();
            },
            success: function(obj) {
                    if (obj['status']==999){
                        alert(obj['err'])
                        $('#responsive_purge').modal('hide');
                        $('#responsive_purge form')[0].reset();
                    }else if (obj['status'] == "1"){
                        $('#responsive_purge').modal('hide');
                        $('#responsive_purge form')[0].reset();
                        alert ("修改完成");
                    } else {
                        alert ("修改失敗");
                    }
                },
                });
                return false;
                });
        $('#responsive_purge').on('hide.bs.modal', function () {
            location.reload();
        });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者晓避。
  • 序言:七十年代末簇捍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俏拱,更是在濱河造成了極大的恐慌暑塑,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锅必,死亡現(xiàn)場離奇詭異事格,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)搞隐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門驹愚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劣纲,你說我怎么就攤上這事逢捺。” “怎么了癞季?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵劫瞳,是天一觀的道長。 經(jīng)常有香客問我绷柒,道長志于,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任废睦,我火速辦了婚禮伺绽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘郊楣。我一直安慰自己憔恳,他們只是感情好瓤荔,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布净蚤。 她就那樣靜靜地躺著,像睡著了一般输硝。 火紅的嫁衣襯著肌膚如雪今瀑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機(jī)與錄音橘荠,去河邊找鬼屿附。 笑死,一個胖子當(dāng)著我的面吹牛哥童,可吹牛的內(nèi)容都是我干的挺份。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贮懈,長吁一口氣:“原來是場噩夢啊……” “哼匀泊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起朵你,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤各聘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抡医,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躲因,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年忌傻,在試婚紗的時候發(fā)現(xiàn)自己被綠了大脉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡芯勘,死狀恐怖箱靴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荷愕,我是刑警寧澤衡怀,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站安疗,受9級特大地震影響抛杨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荐类,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一怖现、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玉罐,春花似錦屈嗤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至季蚂,卻和暖如春茫船,著一層夾襖步出監(jiān)牢的瞬間琅束,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工算谈, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留涩禀,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓然眼,卻偏偏與公主長得像艾船,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子高每,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

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