授之以渔-运维平台发布模块一(Jenkins篇)

本着步子迈得太大容易扯蛋的原则,平台设计初衷就是能调用开源产品肯定不自己做,这样平台只作为一个综合调度中心使用,无需考虑后面具体的功能实现逻辑。

使用Jenkins还是要追溯到很久很久之前认知的一家公司,当时的技术总监张晓峰让我学到了持续集成引擎Hudson,也就是后来的Jenkins。以前公司是Jenkins结合Maven,Ant做敏捷式开发,而我只是取巧,用了其中的一些最基本的功能来实现系统发布更新。

传统的运维发布

  • SVN迁出代码-本地打成tar包(rar包)- sftp上传到服务器
  • Jenkins建立发布项目-SVN迁出代码-通过SFTP插件直接分发到服务器

优点:粗暴简单
缺点

  • 效率: 发布方式一单一服务器上线没问题,多服务器分发效率下降。如果网内是统一入口登录(即堡垒机为单一入口时),发布工作将变得极为困难。

  • 安全风险:发布方式二的SSH账号密码必须存在Jenkins上,虽然不是明文,但.....同样也面临这服务器的22口要对Jenkins开放,安全是问题。

采用系统方案:YUM

Paste_Image.png

我当时的思路:公司是等保三级的单位,在当初我制定内网规则的时候,强烈建议SSH登录范围必须限定,统一的入口可以极大的减少被黑客跳板式攻击的可能,所以我想到了是用YUM更新的方式:

  1. 发布:把代码从SVN上迁出后,打成RPM包(强烈推介FPM)

  2. 更新:通过YUM的特性,更新的程序包每次保持版本号+1,例如test-519-1.x86_64(519就是Jenkins的发布版本号),服务器每次只需要执行以下2条命令即可。

    yum clean all
    yum install test
    
  3. 批量操作:通过Saltstack去通知每台服务器去进行Yum的动作啦。。。

  4. 回退: 就更简单了,粗暴点在YUM服务器直接 mv test-518-1.x86_64 test-520-1.x86_64即可,斯文点当然还是回调Jenkins的接口,使用TAG回滚。

具体逻辑及实施

那么下面先来解决打RPM包,更新YUM源的问题(我的Jenkins就是我们内网的YUM源):

配置Jenkins

首先我们需要打开Jenkins中的batch tasks(批处理,其实就是脚本),不会用Jenkins自己百度吧。

  • 点击Add post-build action-选择Invoke batch tasks
  • Batch tasks里填入脚本
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脚本的大体意思:

  • 创建/home/release/$JOB_NAME目录
  • 然后把/var/lib/jenkins/workspace/$JOB_NAME(Jenkins项目工作区)的代码打成一个以$JOB_NAME命名,版本号为$JOB_NAME的RPM包,其存放在/home/release/$JOB_NAME这个目录里,其解压后会解压到/home/www/bbs目录。
  • 然后createrepo --update /home/release/$JOB_NAME/ 通知更新更新YUM源.
  • 最后就是回调我的Salt接口(此接口的作用其实就是根据这个项目反查对应的哪几台发布主机,然后在这些主机上执行yum install命令,是不是很无脑~)

Saltstack接口(salt_jenkins_post)

由于我的平台和Salt master是同一台(主要是省事),省去了调用API,直接调用了本地Saltstack已经封装的一些yum install 之类的命令。

  • upgradeavailable 验证yum源是否更新
  • install 安装
  • modrepo 创建yum源
  • getrepo 验证yum源是否存在
  • intro 执行更新后的一些命令

我在接口处理的每一步后都会验证返回的主机是否跟数据库预设的项目主机一样,只有一样了才会进行下一步(比如接口只返回了一台服务器通过salt执行的结果,而数据库里该项目是两台服务器,我会认为这个发布有问题,而进行中断)这样也是为了避免有的主机更新成功了,有的主机没更新成功,导致线上用户体验不好(目前已经成功从深信服公司要到了负载均衡的API)后面要做的就是采用灰度发布,从负载上摘除一个然后就更新一个,更新完毕再加回负载。

-- coding: utf-8 --
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):
    """检测目标主机组项目在yum上是否有新版本更新,返回可以更新的主机"""
    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安装项目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):
    """创建项目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):
    """验证项目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):
    """执行svn_intro内命令"""
    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)
        """验证Jenkins IP,安全考虑"""
        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.cn' or job =='cms_assets.cn':
                pass
            else:
                """初始化Salt_jenkins"""
                salt_jenkins = Salt_jenkins(job_hosts, job)


            """目标主机检查YUM源是否存在"""
            if sorted(salt_jenkins.getrepo()) == sorted(str(job_hosts).split(',')):
                pass
            else:
                """不存在就创建YUM源"""
                salt_jenkins.modrepo()
            """目标主机检查YUM源是否更新"""
            if sorted(salt_jenkins.upgradeavailable()) == sorted(str(job_hosts).split(',')):
                if sorted(salt_jenkins.install()) == sorted(str(job_hosts).split(',')):
                    """目标主机执行命令"""
                    salt_jenkins.install()
                    """目标主机执行命令"""
                    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')

后续会介绍发布的状态返回,也就是Saltstack的MasterEvent及通过Jenkins结合Saltstack创新发布项目。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容