flask+ansible 打造自己的自动化运维平台

一、前言

      随着企业信息化要求越来越高,云化架构带来挑战和冲击,海量设备的运维压力也是越来越大,虽然有了批量操作工具,但自动化运维工具操作主要还是依赖于手工执行(脚本小子),手工执行又存在着操作流程不规范,操作记录不可控,批量脚本不统一等多个问题,有较大风险造成人为误操作的可能。

      一直以来是想做个系统来规避这些问题,前期也有过其他开发团队开发过此类产品试用,但开发不懂运维,测试起来很多问题,这些问题后来因为开发项目无法支撑流产了,也没实际用起来。

     批量操作的工具,用过puppet、saltstack、ansible,管理资产超过5000+,目前在用ansible。 Ansible了解过有个官方系统tower,测试装了下,人太高大上,也不是很符合我们批量操作使用的场景。

     近来时间比较充裕,学习了下python的开发框架,自己动手,按照自己的需求来开发,可以更贴合使用。以前觉得开发好难好难,真动手去做了,做个简单系统自己内部使用还是可以的~

    系统中使用的框架是python flask + ansible+mysql。  

    整个demo系统资源也上传到了共享,大家有感兴趣的,可以自己动手玩玩~

    https://download.csdn.net/download/vincent0920/88768831

二、系统设计

系统整体分7个模块:
登录页面:系统的入口,所有其他页面需要做登录控制,只有登录后才能使用。登录只简单做下账号密码验证,什么双因子,验证码防爆力破解的安全要求后期看需要再实现了。

首页:用户登录后,展示的平台整体情况,简单的图表展示,展示一些统计类,top类数据,趋势类数据。

接入清单:对纳管主机的管控视图,支持常规字段的查询。

主机导入:支持页面导入自定义主机分组,导入结果入库,页面支持主机组信息查询。

模板页面:自定义模板的上传页面,规定模板上传的格式,上传后支持查询。

作业页面:可以基于模板去配置作业,配置作业后支持查询记录,支持作业的一个测试拨测并可查询测试结果。

作业记录:作业正式执行的界面,带入测试的记录,支持执行按钮、异步作业和执行结果查询。

三、实现过程 

项目Flask程序的目录结构如下:

ansible/

├── app.py            ----flask主程序

├── blueprints        ----蓝图目录 各模块后台处理代码

├── config.py         ----配置文件 数据库等配置文件

├── decorators.py     ----装饰器  代码重用文件

├── exts.py           ----解决循环引用的问题

├── migrations        ----数据库迁移目录 数据库类操作

├── models.py         ----数据库模型文件 数据库表初始化设置

├── mycelery.py       ----异步处理的代码

├── scrtpts           ----ansible 调用的脚本目录

├── static            ----前台页面的静态文件 css,js,image等

└── templates         ----前台页面的html模板

1、登录页面      

     套用的是之前学习过的一个测试项目登录页面,本来还涉及邮箱注册的功能,考虑到我这个不放在公网使用,就修改去掉了,用户账号增加通过后台录入数据。

     登录需要做个登录控制,每个页面访问前需要先登录。可以设置登录装饰器如下:

def login_required(func):

    # 保留func的信息

    @wraps(func)

    # func(a,b,c)

    # func(1,2,c=3)

    def inner(*args, **kwargs):

        if g.user:

            return func(*args, **kwargs)

        else:

            return redirect(url_for("auth.login"))

    return inner

    登录时校验前端提交的数据可符合要求,可通过wtforms模块。

form.py

# Form:主要就是用来验证前端提交的数据是否符合要求

class LoginForm(wtforms.Form):

    username = wtforms.StringField(validators=[Length(min=3, max=8, message="用户格式错误!")])

    password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误!")])

登录模块代码:

from flask import Blueprint, render_template, jsonify, redirect, url_for, session
from exts import  db
from flask import request
import string
import random
from .forms import  LoginForm
from models import UserModel
from werkzeug.security import generate_password_hash, check_password_hash

# /auth
bp = Blueprint("auth", __name__, url_prefix="/auth")

@bp.route("/login", methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template("login.html")
    else:
        form = LoginForm(request.form)
        if form.validate():
            username = form.username.data
            password = form.password.data
            user = UserModel.query.filter_by(username=username).first()
            if not user:
                print("用户在数据库中不存在!")
                return redirect(url_for("auth.login"))
            if check_password_hash(user.password, password):
                # cookie:
                # cookie中不适合存储太多的数据,只适合存储少量的数据
                # cookie一般用来存放登录授权的东西
                # flask中的session,是经过加密后存储在cookie中的
                session['user_id'] = user.id
                return redirect("/")
            else:
                print("密码错误!")
                return redirect(url_for("auth.login"))
        else:
            print(form.errors)
            return redirect(url_for("auth.login"))

@bp.route("/logout")
def logout():
    session.clear()
    return redirect("/")

效果展示:

2、首页

     主要是做个看板展示内容,包含图表,例如对主机接入的统计数字、对作业任务的统计数字、对模板的统计数字;再加上从不同维度不同图形展示趋势(散点图、柱形图、饼形图)。主要工作在前端页面设计上,后端只需匹配查询具体数值传递给前端即可。

    前端中,首先定义图表展示的区间,我把分成了3部分区域,分别是标题+数字框+趋势图。其次,在趋势图这块,用的是echarts模板,有示例很好用,可参考,下载模板即插即用

Examples - Apache ECharts

效果展示:

3、接入清单(inventory)

      纯查询的页面,主要是用来查询全量纳管主机的一个拨测全局情况,里面有些字段可以和cmdb进行联动,例如业务系统、系统类型、系统分类,通过关联的字段,后期也可根据这些字段做些自定义作业。

后端主要涉及一个分页实现:

page = request.args.get(get_page_parameter(), type=int, default=1)
limit=10
start = (page - 1) * limit
end = start + limit
pagadata=data.slice(start, end)
pagination=Pagination(page=page,total=data.count(), bs_version=3, prev_label="上一页", next_label="下一页", per_page=limit)
total_page = pagination.total

效果展示:

4、主机导入

     导入实际是往数据库插入数据,不往主机上上传文件。再导入前先写个导入基本指导说明,导入后在页面下午展示导入过的记录情况。

    导入时除了往数据库插入数据,还需要向系统中hosts文件新增主机组分组数据。

后端代码:

@bp.route('/toexcel',methods = ['GET','POST'])
@login_required
def toExcel():
    if request.method == 'POST':
        file = request.files.get('file')
        f = file.read()
        data_file = xlrd.open_workbook(file_contents=f)
        table = data_file.sheet_by_index(0)
        nrows = table.nrows
        ncols = table.ncols
        hostgroup = table.row_values(0)[1]
        with open('/etc/ansible/hosts', 'a') as file:
           file.write('['+hostgroup+']'+'\n')
        
        with open('/etc/ansible/hosts', 'a') as file:
        
         for i in range(0, nrows):
            row_date = table.row_values(i)
            ip = row_date[0]
            marktype = row_date[1]
            adduser = g.user.username
            jierudata = db.session.query(InventoryModel.jieruinfo).filter(InventoryModel.ip==ip).first()
            try:
               jieruinfo = jierudata[0]
            except TypeError:
               jieruinfo = '地址未接入'
                      
            addhost = GroupModel(ip=ip, marktype=marktype, adduser=adduser, jieruinfo=jieruinfo)
            db.session.add(addhost)
            db.session.commit()    
            file.write(ip+'\n')       

    data=GroupModel.query.filter(GroupModel.id>0) 
    page = request.args.get(get_page_parameter(), type=int, default=1)
    limit=10
    start = (page - 1) * limit
    end = start + limit
    pagadata=data.slice(start, end)
    pagination = Pagination(page=page, total=data.count(), bs_version=3, prev_label="上一页", next_label="下一页", per_page=limit)
    total_page = pagination.total
     
    return render_template("execl.html", pagination=pagination, pagadata=pagadata,total_page=total_page)

效果展示:

5、模板页面

       定义好制作模板的填写要素,首先模板名得具有唯一性,后续作业是需要基于模板名制作;其次模板内容这里,目前只考虑使用ansible的testping、shell、playbook的三个模块,当执行脚本时,也会引用此处的模板内容,也就是脚本内容,例如:

  1. 当执行testping时,内容后端写死了命令格式,此处不需调用模板内容。
  2. 当执行shell时,模板内容需要填写需要操作的命令内容,例如date,后端执行就会直接调用执行date命令
  3. 当执行playbook时,此时模板内容需要填写剧本脚本名称,例如test.yml。路径统一放在script目录下。(此处考虑执行脚本的规范统一,暂不支持界面随意直接上传脚本)

后端代码:

@bp.route('/templateadd',methods = ['GET','POST'])
@login_required
def addtemp():
        f1 = request.args.get("f1")
        f2 = request.args.get("f2")
        f3 = request.args.get("f3")
        f4 = request.args.get("f4")

        if len(f1)==0 and len(f2)==0 and len(f3)==0 and len(f4)==0:
           data=TemplateModel.query.filter(TemplateModel.id>0)
        else:
           adduser = g.user.username
           addtemp = TemplateModel(tempname=f1, temptype=f2, description=f3, tempsrc=f4, createuser=adduser)
           db.session.add(addtemp)
           db.session.commit()
           data=TemplateModel.query.filter(TemplateModel.id>0) 
           
        
        page = request.args.get(get_page_parameter(), type=int, default=1)
        limit=5
        start = (page - 1) * limit
        end = start + limit
        pagadata=data.slice(start, end)
        pagination = Pagination(page=page, total=data.count(), bs_version=3, prev_label="上一页", next_label="下一页", per_page=limit)
        total_page = pagination.total
     
        return render_template("template.html", pagination=pagination, pagadata=pagadata,total_page=total_page)
              
@bp.route('/search/template')
@login_required
def search_template():
        f5 = request.args.get("f5")
        f6 = request.args.get("f6")
        f7 = request.args.get("f7")
        f8 = request.args.get("f8")
        f9 = request.args.get("f9")
        

        if len(f5)==0 and len(f6)==0 and len(f7)==0 and len(f8)==0 and len(f9)==0:
           data=TemplateModel.query.filter(TemplateModel.id>0) 
        else:      
           data=TemplateModel.query.filter(TemplateModel.tempname.like('%'+f5+'%'),TemplateModel.temptype.like('%'+f6+'%'),TemplateModel.description.like('%'+f7+'%'),TemplateModel.tempsrc.like('%'+f8+'%'),TemplateModel.createuser.like('%'+f9+'%'))
               
        page = request.args.get(get_page_parameter(), type=int, default=1)
        limit=5
        start = (page - 1) * limit
        end = start + limit
        pagadata=data.slice(start, end)
        pagination = Pagination(page=page, total=data.count(), bs_version=3, prev_label="上一页", next_label="下一页", per_page=limit)
        total_page = pagination.total

        return render_template("template.html", pagination=pagination, pagadata=pagadata,total_page=total_page)

效果展示:

6、作业页面

    定义好制作作业的填写要素,首先作业名也得具有唯一性,作业需要基于模板名制作;其次需要关联前面添加的主机组(执行时调用的IP组)。

   作业添加完,支持对作业的测试拨测,定义一台测试主机,要求是作业在执行前必须先执行作业测试,测试完刷新测试的标签并展示记录。

   测试输出结果,可能会较多的文字输出,所以做了一个链接展示,点击后可详细展示输出内容。

   这里没有直接调用ansible的api,直接是调用的command模块,系统的shell命令来执行ansible相关的命令,需要考虑的是对ansible的输出结果再做格式化的调整。

后端代码(ansible调用部分):

          if tempname=='连通检测':        
              command = 'ansible %s -m ping -o' % groupname
              result = ""
              try:
                  result = os.popen(command).read()
              except Exception as e:
                  resultinfo=("执行Ansible脚本发生异常,异常信息:%s" % e)
              if result:
                  resultinfo=("返回结果:%s" % result)
              else:
                  resultinfo=("返回结果为空")
              
              TasktestviewModel.query.filter_by(taskname=f11).update({'resultinfo':resultinfo,'testtaginfo':testtaginfo})   
              db.session.commit()           
              data=TasktestviewModel.query.filter(TasktestviewModel.id>0)    
              
           if tempname=='命令执行':            
              command = f"ansible {groupname} -m shell -a \" {content} \" -o"
              result = ""
              try:
                  result = os.popen(command).read()
              except Exception as e:
                  resultinfo=("执行Ansible脚本发生异常,异常信息:%s" % e)
              if result:
                  resultinfo=("返回结果:%s" % result)
              else:
                  resultinfo=("返回结果为空")
              
              TasktestviewModel.query.filter_by(taskname=f11).update({'resultinfo':resultinfo,'testtaginfo':testtaginfo})   
              db.session.commit()           
              data=TasktestviewModel.query.filter(TasktestviewModel.id>0)    
 
           if tempname=='任务编排':   
              command = f"ansible-playbook ./scrtpts/{content} -e group={groupname} |sed \'s/**\*/******************************/g\'"
              result = ""
              try:
                  result = os.popen(command).read()
              except Exception as e:
                  resultinfo=("执行Ansible脚本发生异常,异常信息:%s" % e)
              if result:
                  resultinfo=("返回结果:%s" % result)
              else:
                  resultinfo=("返回结果为空")
              
              TasktestviewModel.query.filter_by(taskname=f11).update({'resultinfo':resultinfo,'testtaginfo':testtaginfo})   
              db.session.commit()           
              data=TasktestviewModel.query.filter(TasktestviewModel.id>0)    

 
           else:   
               resultinfo="该作业类型不支持"

效果展示:

7、作业记录

    作业的正式执行是放在作业记录中,实现逻辑和作业测试模块基本一致,只是这个步骤中会去调用主机组信息,对主机组里所有ip去执行相应操控。

    需要考虑的一个问题就是作业执行,涉及机器多时,必然ansible执行时间会比较长,此时需要去设置异步处理,flask的celery模块可以实现该功能(前提还需要安装下redis),将作业任务加到异步队列中执行,这样前端可不必等作业执行直接返回业务,等ansible执行完可以再去看执行结果即可。(celery还可去获取任务具体执行的状态,例如进行中、已完成等信息,后期可考虑再加上。)

后端代码:

Celery部分

# 创建celery对象

def make_celery(app):

  celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],

                  broker=app.config['CELERY_BROKER_URL'])

  TaskBase = celery.Task

  class ContextTask(TaskBase):

    abstract = True



    def __call__(self, *args, **kwargs):

      with app.app_context():

        return TaskBase.__call__(self, *args, **kwargs)

  celery.Task = ContextTask

  app.celery = celery

  # 添加任务

  celery.task(name="do_command")(do_command)

  return celery

###后台执行命令

celery -A app.celery worker --loglevel=info -P gevent   --logfile="/root/celery.log" &

效果展示:

四、总结收获

       一直以来从没学习过开发,到这次是做的第二个测试项目,一个人摸索着,也算是完整的做完了两个项目。从一开始觉得很难入手,到一步一步做完,最后感觉其实也不是很难,很多事就是这样,万事开头难,真正开始做起来后,就意味着你离目标就会越来越近。

       也是通过这样一个实际运维需求转化的开发需求实操案例,进一步加深了对python flask的了解和使用。系统前端没有ui的美化,主打一个简(土)单(到)明(掉)了(渣)。但麻雀虽小,也算是五脏俱全了,个人测试使用应该是可以满足,很多其他方面的优化和完善内容,之后再来学习补充咯!

     There are many things that can not be broken!

     如果觉得本文对你有帮助,欢迎点赞、收藏、评论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/342745.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云计算管理-linux

1.权限 基本权限与归属 访问权限 读取:允许查看内容-read r 写入:允许修改内容-write w 可执行:允许运行和切换-excute x 对于文本文件: r读取权限:cat、less、grep、head、tail w写入权…

推荐在线PS修图网页版工具PHP网站源码

在线PS修图网页版工具PHP网站源码,PHP在线照片图片处理PS网站程序源码photoshop网页版。 有很多朋友们都是在用PS作图的,众所周知在使用和学习PS时是需要下载软件的,Photoshop软件对电脑配置也是有一定要求的,今天就为大家带来一…

消息队列RabbitMQ.01.安装部署与基本使用

目录 RabbitMQ的作用 Message queue 释义 问题思考 存在的问题 优化方案 案例分析 带来的好处 消息队列特点 Email邮件案例分析 Docker安装部署RabbitMQ 1.下拉镜像 2.运行RabbitMQ 3.打开防火墙端口号并重新运行防火墙 4.容器启动后,可以通过 docker logs 容器 查…

TCP和SSL/TLS 协议通信原理

👽System.out.println(“👋🏼嗨,大家好,我是代码不会敲的小符,双非大四,Java实习中…”); 📚System.out.println(“🎈如果文章中有错误的地方,恳请大家指正&a…

Python | 七、栈 Stack、队列 Queue

栈的基础知识 是一种数据结构,在Python中常使用列表来模拟实现特点:先进后出 栈的基本操作 因为Python中通过列表模拟实现栈,所以以下的基本操作实际是列表的一些操作获取长度,使用len(stack)方法进栈,使用stack.app…

自然语言处理的崛起:从初步分析到深度理解

自然语言处理(NLP)是计算机科学、人工智能和语言学的交叉领域,旨在让计算机能够理解和生成人类语言。随着时间的推移,NLP 经历了一系列革命性的变化,从简单的规则和模式匹配到如今的深度学习模型,它们使计算…

API调试?试试Apipost

你是否经常遇到接口开发过程中的各种问题?或许你曾为接口测试与调试的繁琐流程而烦恼。不要担心!今天我将向大家介绍一款功能强大、易于上手的接口测试工具——Apipost,并带你深入了解如何玩转它,轻松实现接口测试与调试。 什么是…

Mysql索引的初步认识

索引基本概念 1、什么是MySQL 索引 索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址(类似于C语言的链表通过指针指向数据记录的内存地址)。使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数…

怎么把一个已经压缩好的大容量的压缩包,分卷后发给别人

环境: Win10 专业版 7Z 360压缩 问题描述: 怎么把一个已经压缩好的大压缩包,分卷 解决方案: 使用压缩软件:许多常用的压缩软件,如WinRAR、7-Zip等,都支持将大的压缩包分卷压缩。您可以使…

Android状态栏布局隐藏的方法

1.问题如下,安卓布局很不协调 2.先将ActionBar设置为NoActionBar 先打开styles.xml 3.使用工具类 package com.afison.newfault.utils;import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graph…

聚道云软件连接器实现航信与用友NC凭证对接,助力企业实现数字化转型

客户介绍: 某自然资源产业集团有限公司是一家专注于自然资源产业的领军企业。自成立以来,该企业始终致力于矿产资源、土地整理和生态修复等领域的业务发展。该企业凭借其卓越的业绩和良好的社会声誉,赢得了广泛的认可与赞誉。 客户痛点&…

8-Python 工匠:使用装饰器的技巧

Python 工匠:使用装饰器的技巧 前言 这是 “Python 工匠”系列的第 8 篇文章。[查看系列所有文章] 装饰器 (Decorator) 是 Python 里的一种特殊工具,它为我们提供了一种在函数外部修改函数的灵活能力。它有点像一顶画着独一无二 符号的神奇帽子&#x…

记一次低级且重大的Presto运维事故

本文纯属虚构,旨在提醒各位别犯类似低级错误。 如有雷同,说的就是你! 文章目录 前言事件回顾后续总结 前言 首先,要重视运维工作和离职人员的交接工作,这个不必多说。一将无能,累死三军! 接下来…

密码学的100个基本概念

密码学作为信息安全的基础,极为重要,本文分为上下两部分,总计10个章节,回顾了密码学的100个基本概念,供小伙伴们学习参考。本文将先介绍前五个章节的内容。 一、密码学历史 二、密码学基础 三、分组密码 四、序列密码 五、哈希…

springboot整合MongoDB实战

目录 环境准备 引入依赖 配置yml 注入mongoTemplate 集合操作 文档操作 创建实体 添加文档 查询文档 更新文档 删除文档 环境准备 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-da…

深度学习(3)--递归神经网络(RNN)和词向量模型Word2Vec

目录 一.递归神经网络基础概念 二.自然语言处理-词向量模型Word2Vec 2.1.词向量模型 2.2.常用模型对比 2.3.负采样方案 2.4.词向量训练过程 一.递归神经网络基础概念 递归神经网络(Recursive Neural Network, RNN)可以解决有时间序列的问题&#xff0c;处理诸如树、图这样…

畅游创新之源!打开谷歌浏览器默认链接https://discovery.lenovo.com.cn/home/baidu/v1/c2的完美指南!

谷歌浏览器怎么默认打开https://discovery.lenovo.com.cn/home/baidu/v1/c2 打开联想电脑管家&#xff0c;点安全防护&#xff0c;把浏览器保护关闭就行了

NAT地址转换协议

目录 NAT应用场景静态NAT动态NATNAPTEasy IPNAT服务器 点击跳转NAT配置&#xff08;动态nat&#xff0c;静态nat&#xff0c;Easy IP&#xff09; NAT应用场景 - 随着网络设备的数量不断增长&#xff0c;对IPv4地址的需求也不断增加&#xff0c;导致可用IPv4地址空间逐渐耗尽…

13. VTK采集点法向量标记、平面切割

今天依旧是在摸索医学图像可视化的一天呢。这个笔记主要介绍了VTK上做法向量标记以及做切割平面的方法。 1. 将多边形数据的采集点法向量标记成锥形符号 在读取和使用.stl文件过程中&#xff0c;我们经常要用到法向量。这个例子展示了我们应该如何计算多边形数据的法向量并用v…

8.Gateway服务网关

3.Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式…