CMDB - 发布系统
完全自己开发一套发布系统
表设计
- 环境,
- 主机 , -> salt-id
- 代码地址、就是包 -> 地址
- 应用 -> app
-记录日志 -> 时间,事件
SaltStack
SaltStack 采用 C/S模式
master和minion之间的通信用到了zeromq消息队列 ,每个minion 有一个salt_id 是绝对唯一的
Master与Minion之间通过ZeroMq进行消息传递,使用了ZeroMq的发布-订阅模式,连接方式包括tcp,ipc
废话少说,直接上图:
![](https://upload-images.jianshu.io/upload_images/14972130-65a2fcad0d5d8bf7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/852/format/webp)
表设计
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
# 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, PIPEpath = 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