溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

授之以漁-運(yùn)維平臺(tái)發(fā)布模塊一(Jenkins篇)

發(fā)布時(shí)間:2020-06-29 11:46:26 來源:網(wǎng)絡(luò) 閱讀:611 作者:大Q的夢(mèng)想 欄目:建站服務(wù)器

本著步子邁得太大容易扯蛋的原則,平臺(tái)設(shè)計(jì)初衷就是能調(diào)用開源產(chǎn)品肯定不自己做,這樣平臺(tái)只作為一個(gè)綜合調(diào)度中心使用,無需考慮后面具體的功能實(shí)現(xiàn)邏輯。

使用Jenkins還是要追溯到很久很久之前認(rèn)知的一家公司,當(dāng)時(shí)的技術(shù)總監(jiān)張曉峰讓我學(xué)到了持續(xù)集成引擎Hudson,也就是后來的Jenkins。以前公司是Jenkins結(jié)合Maven,Ant做敏捷式開發(fā),而我只是取巧,用了其中的一些最基本的功能來實(shí)現(xiàn)系統(tǒng)發(fā)布更新。

傳統(tǒng)的運(yùn)維發(fā)布

  • SVN遷出代碼-本地打成tar包(rar包)- sftp上傳到服務(wù)器

  • Jenkins建立發(fā)布項(xiàng)目-SVN遷出代碼-通過SFTP插件直接分發(fā)到服務(wù)器

優(yōu)點(diǎn):粗暴簡單


缺點(diǎn)

  • 效率: 發(fā)布方式一單一服務(wù)器上線沒問題,多服務(wù)器分發(fā)效率下降。如果網(wǎng)內(nèi)是統(tǒng)一入口登錄(即堡壘機(jī)為單一入口時(shí)),發(fā)布工作將變得極為困難。

  • 安全風(fēng)險(xiǎn):發(fā)布方式二的SSH賬號(hào)密碼必須存在Jenkins上,雖然不是明文,但.....同樣也面臨這服務(wù)器的22口要對(duì)Jenkins開放,安全是問題。

采用系統(tǒng)方案:YUM

授之以漁-運(yùn)維平臺(tái)發(fā)布模塊一(Jenkins篇)我當(dāng)時(shí)的思路:公司是等保三級(jí)的單位,在當(dāng)初我制定內(nèi)網(wǎng)規(guī)則的時(shí)候,強(qiáng)烈建議SSH登錄范圍必須限定,統(tǒng)一的入口可以極大的減少被***跳板式***的可能,所以我想到了是用YUM更新的方式:

  1. 發(fā)布:把代碼從SVN上遷出后,打成RPM包(強(qiáng)烈推介FPM)

  2. 更新:通過YUM的特性,更新的程序包每次保持版本號(hào)+1,例如test-519-1.x86_64(519就是Jenkins的發(fā)布版本號(hào)),服務(wù)器每次只需要執(zhí)行以下2條命令即可。

    bash yum clean all yum install test

  3. 批量操作:通過Saltstack去通知每臺(tái)服務(wù)器去進(jìn)行Yum的動(dòng)作啦。。。

  4. 回退: 就更簡單了,粗暴點(diǎn)在YUM服務(wù)器直接 mv test-518-1.x86_64 test-520-1.x86_64即可,斯文點(diǎn)當(dāng)然還是回調(diào)Jenkins的接口,使用TAG回滾。

具體邏輯及實(shí)施

那么下面先來解決打RPM包,更新YUM源的問題(我的Jenkins就是我們內(nèi)網(wǎng)的YUM源):

配置Jenkins

首先我們需要打開Jenkins中的batch tasks(批處理,其實(shí)就是腳本),不會(huì)用Jenkins自己百度吧。

  • 點(diǎn)擊Add post-build action-選擇Invoke batch tasks

  • Batch tasks里填入腳本

bash
mkdir -p /home/release/$JOB_NAME && \
fpm -s dir -t rpm -n $JOB_NAME -v $BUILD_NUMBER --prefix /home/www/bbs -C /var/lib/jenkins/workspace/$JOB_NAME -p /home/release/$JOB_NAME ./ && \
createrepo --update /home/release/$JOB_NAME/ && \
curl -d "job_id=$JOB_NAME" http://salt master IP/cmdb/salt_jenkins_post/


這段Jenkins腳本的大體意思:

  • 創(chuàng)建/home/release/$JOB_NAME目錄

  • 然后把/var/lib/jenkins/workspace/$JOB_NAME(Jenkins項(xiàng)目工作區(qū))的代碼打成一個(gè)以$JOB_NAME命名,版本號(hào)為$JOB_NAME的RPM包,其存放在/home/release/$JOB_NAME這個(gè)目錄里,其解壓后會(huì)解壓到/home/www/bbs目錄。

  • 然后createrepo --update /home/release/$JOB_NAME/ 通知更新更新YUM源.

  • 最后就是回調(diào)我的Salt接口(此接口的作用其實(shí)就是根據(jù)這個(gè)項(xiàng)目反查對(duì)應(yīng)的哪幾臺(tái)發(fā)布主機(jī),然后在這些主機(jī)上執(zhí)行yum install命令,是不是很無腦~)

Saltstack接口(saltjenkinspost)

由于我的平臺(tái)和Salt master是同一臺(tái)(主要是省事),省去了調(diào)用API,直接調(diào)用了本地Saltstack已經(jīng)封裝的一些yum install 之類的命令。

  • upgradeavailable 驗(yàn)證yum源是否更新

  • install 安裝

  • modrepo 創(chuàng)建yum源

  • getrepo 驗(yàn)證yum源是否存在

  • intro 執(zhí)行更新后的一些命令

我在接口處理的每一步后都會(huì)驗(yàn)證返回的主機(jī)是否跟數(shù)據(jù)庫預(yù)設(shè)的項(xiàng)目主機(jī)一樣,只有一樣了才會(huì)進(jìn)行下一步(比如接口只返回了一臺(tái)服務(wù)器通過salt執(zhí)行的結(jié)果,而數(shù)據(jù)庫里該項(xiàng)目是兩臺(tái)服務(wù)器,我會(huì)認(rèn)為這個(gè)發(fā)布有問題,而進(jìn)行中斷)這樣也是為了避免有的主機(jī)更新成功了,有的主機(jī)沒更新成功,導(dǎo)致線上用戶體驗(yàn)不好(目前已經(jīng)成功從深信服公司要到了負(fù)載均衡的API)后面要做的就是采用灰度發(fā)布,從負(fù)載上摘除一個(gè)然后就更新一個(gè),更新完畢再加回負(fù)載。

import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt

try:
    import salt.client
except:
    pass
from cmdb.models import *

class Salt_jenkins:
    def __init__(self, host_list, job):
        self.client = salt.client.LocalClient()
        self.host_list = host_list
        self.type = type
        self.job = job


    def upgradeavailable(self):
        """檢測(cè)目標(biāo)主機(jī)組項(xiàng)目在yum上是否有新版本更新,返回可以更新的主機(jī)"""
        ret = self.client.cmd('%s'% self.host_list, 'pkg.upgrade_available', ['%s'% self.job],expr_form='list',ret='return_redis')
        true_hostlist = []
        for host in ret.keys():
            if ret['%s' % host]:
                true_hostlist.append(host)
            else:
                pass
        return true_hostlist

    def install(self):
        """YUM安裝項(xiàng)目RPM包"""
        ret = self.client.cmd('%s'% self.host_list, 'pkg.install', ['%s'% self.job],expr_form='list',ret='return_redis')
        true_hostlist = []
        for host in ret.keys():
            if ret['%s' % host] != {}:
                install_ret = ret['%s' % host]['%s' % self.job]
                if install_ret != '':
                    true_hostlist.append(host)
                else:
                    pass
            else:
                pass
        return true_hostlist

    def modrepo(self):
        """創(chuàng)建項(xiàng)目YUM源"""
        ret = self.client.cmd('%s'% self.host_list, 'pkg.mod_repo',['repo=%s'% self.job,'baseurl=http://172.18.11.98/release/%s'% self.job,'enabled=1','gpgcheck=0','name=%s'% self.job,'priority=10'],expr_form='list',ret='return_redis')
        true_hostlist = []
        for host in ret.keys():
            if type(ret['%s' % host]) == dict:
               true_hostlist.append(host)
            else:
                pass
        return  true_hostlist

    def getrepo(self):
        """驗(yàn)證項(xiàng)目YUM源是否存在"""
        ret = self.client.cmd('%s'% self.host_list, 'pkg.get_repo', ['repo=%s'% self.job],expr_form='list',ret='return_redis')
        true_hostlist = []
        for host in ret.keys():
            if ret['%s' % host] != {}:
               true_hostlist.append(host)
            else:
                pass
        return  true_hostlist

    def intro(self):
        """執(zhí)行svn_intro內(nèi)命令"""
        command = Svn.objects.get(svn_name = self.job).svn_intro
        ret = self.client.cmd('%s'% self.host_list, 'cmd.run', ['%s'% command],expr_form='list',ret='return_redis')
        return ret


@csrf_exempt    
def salt_jenkins_post(request):
    if request.method == 'POST':     
        ip = request.META.get("REMOTE_ADDR", None)
        if ip == '172.18.11.98':   
            job =  request.POST.get('job_id')
            job_hosts =  Svn.objects.get(svn_name=job).svn_hosts
            if job =='cms_template.youth.cn' or job =='cms_assets.youth.cn':
                pass
            else:
                """初始化Salt_jenkins"""
                salt_jenkins = Salt_jenkins(job_hosts, job)

                """目標(biāo)主機(jī)檢查YUM源是否存在"""
                if sorted(salt_jenkins.getrepo()) == sorted(str(job_hosts).split(',')):
                    pass
                else:
                    """不存在就創(chuàng)建YUM源"""
                    salt_jenkins.modrepo()
                """目標(biāo)主機(jī)檢查YUM源是否更新"""
                if sorted(salt_jenkins.upgradeavailable()) == sorted(str(job_hosts).split(',')):
                    if sorted(salt_jenkins.install()) == sorted(str(job_hosts).split(',')):
                        """目標(biāo)主機(jī)執(zhí)行命令"""
                        salt_jenkins.install()
                        """目標(biāo)主機(jī)執(zhí)行命令"""
                        salt_jenkins.intro()
                        return HttpResponse('install success')
                    else:
                        return HttpResponse('install fail')
                else:
                    return HttpResponse('upgradeavailable fail')
        else:
            return HttpResponse('ip deny')
    else:
        return HttpResponse('get deny')

后續(xù)會(huì)介紹發(fā)布的狀態(tài)返回,也就是Saltstack的MasterEvent及通過Jenkins結(jié)合Saltstack創(chuàng)新發(fā)布項(xiàng)目。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI