CMDB

CMDB - 发布系统

完全自己开发一套发布系统
表设计

  • 环境,
  • 主机 , -> salt-id
  • 代码地址、就是包 -> 地址
  • 应用 -> app
    -记录日志 -> 时间,事件

SaltStack
SaltStack 采用 C/S模式
master和minion之间的通信用到了zeromq消息队列 ,每个minion 有一个salt_id 是绝对唯一的
Master与Minion之间通过ZeroMq进行消息传递,使用了ZeroMq的发布-订阅模式,连接方式包括tcp,ipc

废话少说,直接上图:

 
发布系统流程图.jpg

表设计
models.py

from django.db import models

# Create your models here.
class Use_Env(models.Model):
    name = models.CharField(max_length=32, blank=True, null=True, verbose_name='环境名')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = '环境表'

class Host(models.Model):
    hostname = models.CharField(max_length=32, blank=True, null=True, verbose_name="salt_id")
    ip = models.CharField(max_length=64, blank=True, null=True, verbose_name='IP')

    def __str__(self):
        return self.hostname

    class Meta:
        verbose_name_plural = "主机表"


class Record_Log(models.Model):
    timestamp = models.CharField(max_length=64, blank=True, null=True, verbose_name='时间')
    project = models.ForeignKey(to='App', blank=True, null=True, verbose_name='项目', related_name='proj')
    package = models.ManyToManyField(to='Package', blank=True, null=True, verbose_name='包', related_name='pack')
    env = models.ForeignKey(to='Use_Env', blank=True, null=True, verbose_name='环境', related_name='env')

    def __str__(self):
        return self.timestamp

    class Meta:
        verbose_name_plural = '记录日志'


class App(models.Model):
    name = models.CharField(max_length=32, blank=True, null=True, verbose_name='应用名')
    path = models.CharField(max_length=64, blank=True, null=True, verbose_name='应用路径')
    environment = models.ForeignKey(to='Use_Env', blank=True, null=True, verbose_name='环境')
    hosts = models.ForeignKey(to='Host', blank=True, null=True, verbose_name='对应主机', related_name='apphost')
    # _script = models.CharField(max_length=32, blank=True, null=True, verbose_name='部署脚本')
    package = models.ForeignKey(to='Package', blank=True, null=True, verbose_name='代码', related_name='apppack')
    _app = models.ForeignKey(to='App', blank=True, null=True, verbose_name='上级应用')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = '项目表'


class Package(models.Model):
    name = models.CharField(max_length=64, blank=True, null=True, verbose_name='包名/版本号')
    pack_path = models.CharField(max_length=64, blank=True, null=True, verbose_name='代码路径/地址')
    project = models.ForeignKey(to='App', blank=True, null=True, verbose_name='所属项目', related_name='packapp')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = '代码'

迁移,创建数据库
python manage.py makemigrations
python manage.py migrate

伪代码

views.py

def pubilsh(request):
    if request.method == 'GET':
        env = models.Use_Env.object.all()
        return render(request,'fabu.html', locals())
    else:
        env = request.POST.get('env')
        app = request.POST.get('app')
        obj_list = models.App.objects.filter(name=app,environment__name=env)  # 跨表查询

        # 拿到对应的主机组   代码 -> 地址
        # 循环 主机组  推送代码
        app_name = ORM 查表
        host_list = [{'id':'salt-id','path':'/data/app/www/abc'},]   # 这里是通过数据库取到的
        package = 'svn://xxxx'   # svn 地址

template

fabu.html

<form method='post'>
    <label>应用:</label>
    <input type="text" name="app"> # name -> key  ,框是 -> values
        <select class="form-control" id="nubers" name="env">   # name="env" -> key
            {% for i in env %}
                <option value="{{ i.name }}"> {{ i.name }}</option>  # value="{{ i.name }}" -> values
            {% endfor %}
        </select>
    <input type="submit" value="提交">
</form>

自动化管理平台 -> 必须是 和salt-master 安装在同一台机上 ,使用salt原生的API

第一步 在 自动化管理平台 里面下载代码 (可打包) 通过 subporcsess 执行命令
# from subprocess import Popen, PIPE
import os
path = os.getcwd() + r'/project_path/'
subprocsess -> 执行命令
# cd path
# mkdir app_name && cdapp_name
# svn co $package
# tar 打包

create.sh

#!/bin/bash
cd path
mkdir $app_name && cd $app_name
svn co $package
tar 打包



subprocess.call(['cd',  '-l'])


from subprocess import Popen, PIPE 

p = subprocess.Popen('sh create.sh', stdout=PIPE, shell=True)   

第二步 推送 salt stack -> state.sls # 状态管理
写 state.sls 规则的yml文件
通过 Python 代码 salt-api 调用 state 触发推送

第三部 执行远程端代码 -> cmd.run cd 路径 python xxx

django celery 被封装成了 djcelery

就要学会如何使用

celery.py
form __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from django.conf import settings

os.environ.setdefault('DJANGO_SETTIONG_MODULE', '项目名称.settiongs')
app = Celery('项目名称')

app.config_from_object('django.conf:settings')

app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

@app.task(bind=True)
def debug_task(self):
    print ('Request:  {0!x}'.format(self.request))

settings.py

# 文件最后添加

import djcelery
from celery.schedules improt crontab
from datetime import timedelta
djcelery.setup_loader()

CELERY_TIMEZONE = TIME_ZONE
BROKER_URL='redis://:'             # redis 地址 发送端口
CELERY_RESULT_BACKEND = 'redis://:'      # redis 接收端口
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Africa/Nairobi'
CELERY_IMPORTS = ['应用名目录下的.task',]   # 应用名目录下的.task ,主要看有没有task.py文件
CELERY_MAX_TASKS_PRR_CHILD = 3
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'

task.py

form __future__ import absolute_import, unicode_literals
import time
import requests
from celery import shared_task
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.shortcuts import render, HttpResponse,redirect



@shared_task:
def add(x, y):
    return x+y   # 定义自己的推送代码


@shared_task:
def add(x, y):
    return x*y


@shared_task:
def xsun(numbers):
    print (sum(numbers))
    return sum(numbers)    

urls.py

urlpatterns = [
           url(r'^celery/', views.celery_status),   # 必须要写的路由      
          ]

views.py

def celery_status(request):
    import datetime
    import json
    if request.method == 'GET':
        if request.GET.get('x') and request.GET.get('y'):
            if request.GET.get('after'):
                ctime = datetime.datetime.now()
                utc_ctime = datetime.datetime.utcfromtimestamp(ctime.timestamp())
                s1 = datetime.timedelta(seconds=int(request.GET.get('after'))*60)
                ctime_x = utc_ctime + s1
            
            year = request.GET.get('year')
            mouth = request.GET.get('month')
            day = reuqest.GET.get('day')
            hour = request.GET.get('hour')
            minute = request.GET.get('minute')

            if year and mouth and day and hour and minute:
                ctime = datetime.datetime(year=int(year), month=int(mouth)),
                                           day=int(day), hour=int(hour), minute=int(minute))
                # 把当前的本地时间转换成 UTC 时间
                ctime_x = datetime.datetime.utcfromtimestamp(ctime.timestamp)
            
            if ctime_x:
                #  最核心的代码
                ret = add.apply_async(args=[int(request.GET.get('x')), int(request.GET.get('y'))], eta=ctime_x)
                num = ret.id
 
            if request.GET.get('cancel'):
                async = AsyncResult(id=request.GET.get('cancel'), app=app)
                async.revoke(terminate=True)
                cancel_tag = '取消成功'
 
            if request.GET.get('stop'):
                async = AsyncResult(id=request.GET.get('stop'), app=app)       
                async.revoke()
                stop_tag='中止成功'
            return render(request, 'celery.html', locals())
        else:
            ret = request.POST.get('id','')
            data = ""
            if ret:
                async = AsyncResult(id=ret,app=app)
                if async.successful():
                    data = "执行成功,数据是: " + str( async.get() ) 
                    async.forget()
                elif async.failed():
                    data='执行失败'
                elif async.status == 'PBNDING':
                    data = "等待被执行"
                elif async.status == 'RBTPY' :
                    data = '任务异常正常重试'
                elif async.status == 'STARTBD':
                    data = "任务正在执行"
                else:
                    data = "未知"
        retrun render(request, 'celery.html', locals())

celery 需要在命令行里单独启动 terminal

celery worker -A 发布 -l debug

templates
celery.html

<form method="post">
    {% csrf_token %}
    id: <input type="text" name='id'>
    结果: <input type="text" value="{{ data }}">
    <input type="submit" value="提交">
</form>

<br>    # 空行
<hr>    # 分割线

<form method="get">
    x:<input type="text" name="x">
    +
    y:<input type="text" name="y">
    <br>
    年: <input type="text" name="year">
    月: <input type="text" name="month">
    日: <input type="text" name="day">
    时: <input type="text" name="hour">
    分: <input type="text" name="minute">
    <br>
    几分钟后: <inpur type="text" name="after">
    <br>
    取消这个任务: <input type="text"  name="cancel">
    结果: <input type="text" value="{{ cancel_tag }}">
    <br>
    中止这个任务: <input type="text" name="stop">
    结果: <input type="text" value="{{ stop_tag }}">
    <br>
    <hr>
    结果: <input type="text" value="{{ stop_tag }}"
    <br>
    <hr>
    结果: <input type="text" value="{{ num }}">
    <input type="submit" value="提交">

</form>



发布代码

第一步 在 自动化管理平台 里面下载代码 (可打包) 通过 subporcsess 执行命令

# from subprocess import Popen, PIPE 
import os 
path = os.getcwd() + r'/project_path/'
subprocsess -> 执行命令
                 # cd path
                 # mkdir $app_name && cd $app_name
                 # svn co $package
                 # tar 打包

create.sh

#!/bin/bash
cd path
mkdir $app_name && cd $app_name
svn co $package
tar 打包

from subprocess import Popen, PIPE
path = os.getcwd() + r'/project_path/'

拼接

cd path && mkdir $app_name && cd $app_name && svn co $package

cmd = 'cd {0} && mkdir {1} && cd {1} && svn co {2}'.format(path, app_name, package)

第二步 推送 salt stack -> state.sls # 状态管理
写 state.sls 规则的yml文件
通过 Python 代码 salt-api 调用 state 触发推送

第三部 执行远程端代码 -> cmd.run cd 路径 python xxx

class MainSalt(object):          # salt 代码
    def __init__(self, tgt='*')
        self.local = sc.LocalClient()
        self.tgt = tgt

    def get_cache_returns(self, func):
        while not self.local.get_cache_returns(func):
            time.sleep(1)
        return self.local.get_cache_returns(func)

  
    def cmd_run(self, run_func):
        if not isinstance(run_func, list):
            raise TypeError(AttributeError)
        cmd_id = self.local.cmd_async(self.tgt, 'cmd.run', run_func)
        ret_cmd = self.get_cache_returns(cmd_id)
        return ret_cmd
       
    def state(self, salt_fun, tag=''):
           
        if tag:    
            disk_id = self.local.cmd_async(self.tgt, 'state.sls', [salt_fun, tag]) 
                  # 实际上 把信息塞入ZeroMq 返回一个ID 
        else: 
            disk_id = self.local.cmd_async(self.tgt, 'state.sls', [salt_fun,])
        ret_disk_data = self.get_cache_returns(disk_id)
        return ret_disk_data

    def push_package(self, pillar_dic):
        tag = 'pillar={0}'.format(json.dumps(pillar_dic))
        salt_fun = 'test'   # test.sls   就是状态管理里面的这个文件 加载
        return self.state(salt_fun, tag)



def sub_run(cmd):
    retrue subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)    

def pubilsh(request):
    if request.method == 'GET':
        env = models.Use_Env.object.all()
        return render(request,'fabu.html', locals())
    else:
        env = request.POST.get('env')
        app = request.POST.get('app')
        obj_list = models.App.objects.filter(name=app,environment__name=env)  # 跨表查询

        # 拿到对应的主机组   代码 -> 地址
        # 循环 主机组  推送代码
        app_name = ORM 查表
        host_list = [{'id':'salt-id','path':'/data/app/www/abc'},]   # 这里是通过数据库取到的
        package = 'svn://xxxx'   # svn 地址


        for i in host_list:
            cmd = 'cd {0} && mkdir {1} && cd {1} && svn co {2}'.format(path, app_name, package)
            ret = sub_run(cmd)   

            m_salt = MainSalt(host.get('id'))
            pillar_dic = {
                         'path':i.get('path')+ '/' + app_name,
                         'app' : app_name
                    }
            ret = m_salt.push_package(pillar_dic)
            
            #第三部
            m_salt.cmd_run('cd {0} && python manage.py runserver 8080'.format(itme.get('path') + '/' + app_name))
        

satlstack 管理

top.sls

base:
  '*': 
    - jar_package

test.sls

test_ci:
  file.recurse:
    - name: {{ pillar['path'] }}
    - source: salt://project_path/{{ pillar['app'] }}   # project_path需要做个软连接
    - user: root
    - dir_mode: 755
    - file_mode: 644
    - template: jinja
    - makedirs: True
    - include_enpty: True


原文地址:https://www.cnblogs.com/huidou/p/10758017.html