运维工单的应用

一.说明

最开始培训完入行的2年里,进的几家公司和面试遇到的基本都是机器在200个虚拟机以下,运维加上我也就1-2个人。

这种都是自己说了算,做了什么操作自己记住就行了,添加权限也都是开发说一下这边就给加上了,流程配合之间都靠嘴对嘴的传递,当然也可能用qq和微信。

工作环境还是很重要的,现在待的项目运维多的时候5个,虚拟机300往上,还有一大堆别的云产品要维护。这就有必要进行分工了,而不是大家谁闲着就做,那会导致需求人找不到谁在负责,而且负责人也会来回变动。

那需求就来了,根据日常工作发现如下问题:
1.开发不知道找谁能把这件事做成
2.开发来申请添加权限、用qq之类的进行说明描述
3.因为每个人负责一块,都参与工作,没人知道整体进度
4.某个运维做了一些操作别人不太清楚
5.这块他负责的,现在他请假了,接手后不知道怎么做

那看看开发怎么怎么解决上述问题的:
1.产品经理有需求要提jira
2.在jira上有任务表,可以看到每个人接到的任务,涉及哪一块
3.代码写完后合并到master分支要开发组长去确认,注释也写的很多
file

具体还会有相应的工作流,将每个需求都从头到尾追踪好,后面上线测试环境甚至生产环境都有相应的管理流程。
file

二.初代设计

最开始想的是在内部confluence(一个笔记网站)上记录下我们有哪些任务要做,按照日期划分,每天都记录下,任务名称后面写上名字。
file

当然对于外部的需求,例如加权限,解决一些jenkins发版上的配置错误等等都是完成后来这里补。

现在想想真的很蠢,这记录和没记录的区别完全不大,虽然知道谁什么时间做了哪件事,但维护起来实在费心费力,和其它组的交互也是原始的嘴对嘴交流。

同时这个也完全看不出[任务进度]、[谁在划水]、[某类任务数量]、[当月故障数量]等等,也就是只记录了,但统计非常困难,需要人工去数。

在发现开发用jira后,探索了一下,发现确实很好用,可以实现我们的需求。通过搭配钉钉通知和看版,再建立好清晰完善的工作流程,可以最大效率避免在开发流程上花时间。

三.效果展示

用jira建立2个项目,一个是对外工单用于外部需求的处理,一个是对内工单只内部使用记录任务。

给区分开是因为夹杂在一起会很乱,内部都好说就几个人大家都按照任务进行类别创建,比如购买服务器就建立[资金管理]任务,处理故障就建立[故障处理]任务。

但开发是不管这些的,人家不管是申请权限、处理报错、申请资源全都是[故障处理]这个默认问题类型。所以要精简为2-3类最好,这里是权限和其它类型。

外部任务:
1.权限申请,这里权限申请会搭配钉钉脚本做超时通知,用户要定时续期权限,当然可以调整更大的选项,例如6个月。单独添加申请人选项,是因为申请者可能还没有jira,或者是外包人员
file

当权限超过规定期限,会给jira的工单发布者和管理员均发送一个钉钉消息
file

2.其它任务
file

内部任务:
1.服务管理,这里是内部的任务,例如发版、搭建服务、备份数据等等操作。对于一个大任务一定要拆开成版本,也就是合集,把任务去细化。
file

不然你随便写个k8s优化那就假大空了,没有一个目标和划水没有区别,做2天感觉烦躁就懒得做了,就没有意义了。同时对于生产等重要操作,要编写配套任务的《操作用例》,你没猜错,就是对标测试的《测试用例》。因为运维不求快求稳,文档操作不出事,比出现问题后补救要成本小得多。
file

2.报警可以选择几个类别,这里后面会做zabbix联动建立jira任务,其实zabbix本身就可以针对每条报警做处理和记录,但放到jira是统一管理了。
file

3.其它类别,都是自己人,可以把问题做的细致一点,这样利于后面用jira的筛选器做统计。比如统计某人这个月发版多少次,鼠标勾选几下就出来结果了。
file

像我自从工单建立后,正式生产发版一共10次
file

四.工单运作流程

对于外部工单,设置为默认经办人是运维组长,到他那里后,看到钉钉通知,再进行后续任务分配,将人员调动起来。

可以根据jira中查询每个人的任务量,再到grafana上进行综合展示,这里还没做完,后续再补图,相当于做内部的《看板》,将大家的任务状态用大盘做可视化。

这样就会通过大屏看板一目了然,可以找到谁这周任务最少,或者做的拖沓,本来[ELK日志分析]建设这个大项目,里面有3个jira任务,预计是14天完成,结果第一个人任务2周过去了还没完结,说明有问题。

对于这种,说明任务太有挑战性,就多给他分配外部工单进行锻炼,腾出其它组员的时间,晚上加班/值班,也都多安排他来。工单尽量要区分清楚,用强制选项的填选来规定,而不是都在备注里填,很多人懒得去备注里写。

通知脚本

我是编写python脚本定时去获取jira上每个人的最新任务,进行对应人钉钉通知,每个人一个私有钉钉群,由其他运维同事拉一个3人组,加上机器人后再退掉即可,这样省的麻烦别的同事了。

效果如下,这样每个人就有一个私人通知的钉钉群,未来也可以增加入gitlab等等其它的私人通知,所以叫[信息通知小助手],群名称叫[办公信息通知]。
file

图中可以看到第一个是我发布的任务别人完成了,当发布任务时,被发布者会收到这个信息,会写明谁发布的和一些简要说明。当接单人员完成后,发布者可以知道完成了,具体信息也都会加到附件和评论里,方便查阅。

当然也可以加一些别的工作流程,例如增加一个开始任务也进行通知一下,尤其对开发流程有复杂的工作流,当测试环境上线需要通知测试进行业务功能测试、测试人员完成后确认再通知开发人员进行uat环境上线,这就要每个环境都通知。

申请机器人

1.先添加一个机器人
file

2.选择自定义类型
file

3.选择关键字,这里意思是发送的信息,必须带上这些关键字才能发成功,也就是图片里的通知:xxx开头。复制好token,写到后续的配置文件里去。
file

具体脚本

定时跑的的脚本,可以设置三分钟一次,那就3分钟检查一次有没有新任务。
jira_notice.py

#!/usr/bin/python3
#钉钉token关键字jira

import datetime, os, sys, json, requests, configparser
from jira import JIRA
from datetime import datetime
from datetime import timedelta

#读取配置文件
CONF_SITE="/data/tools/python_lib/script_conf.cfg"
cfg = configparser.ConfigParser()
cfg.read(CONF_SITE)

#[jira的url、jira账号、jira密码、]
JIRA_URL = cfg['jira']['jira_url']
JIRA_USERNAME = cfg['jira']['jira_username']
JIRA_PASSWORD = cfg['jira']['jira_password']
ISSUE_INFO_FILE = cfg['jira']['issue_info_file']
ISSUE_INFO_SIZE = cfg['jira']['issue_info_size']
START_STATUS_LIST = cfg['jira']['start_status'].split(',')
STOP_STATUS_LIST = cfg['jira']['stop_status'].split(',')
DD_URL = cfg['dingding']['dd_url']

def write_cfg(file_dict):
    #将字典写入到配置文件里
    with open(ISSUE_INFO_FILE, 'w') as fp:
        fp.write(str(file_dict))

def read_cfg():
    #读取配置文件内容
    with open(ISSUE_INFO_FILE, 'r') as fp:
        data = fp.read()
    file_dict = eval(data)
    return file_dict

def new_notice(notice_url, nowtime, title, is_summary, is_project, is_type, notice_type, is_notice, key_name):
    #[拼接字符串]
    msg = """### %s \n
> 时间: %s \n
> 概要: %s \n
> 项目: %s \n
> 类型:%s \n
> %s: %s \n
> 地址: [任务地址](%s/browse/%s?) \n
"""

    headers = {'Connection': 'close', "Content-Type": "application/json"}
    data = {"msgtype": "markdown", 
        "markdown": {
            "title": title,
            "text": msg %(title, nowtime, is_summary, is_project, is_type, notice_type, is_notice, JIRA_URL, key_name)
        }
    }

    print(title)
    print(notice_url)
    r = requests.post(notice_url, data=json.dumps(data), headers=headers, verify=False)
    print(r.text)

def jcpzrw_notice(notice_url, nowtime, is_summary, title, is_created, is_apply, is_duration, key_name):
    #[拼接字符串]
    msg = """### %s \n
> 时间: %s \n
> 概要: %s \n
> 申请日期: %s \n
> 申请人: %s \n
> 使用时长:%s \n
> 地址: [任务地址](%s/browse/%s?) \n
"""

    headers = {'Connection': 'close', "Content-Type": "application/json"}
    data = {"msgtype": "markdown", 
        "markdown": {
            "title": title,
            "text": msg %(title, nowtime, is_summary, is_created, is_apply, is_duration, JIRA_URL, key_name)
        }
    }

    print(title)
    r = requests.post(notice_url, data=json.dumps(data), headers=headers, verify=False)
    print(r.text)

def main():
    #[时间]
    nowtime = datetime.now()
    nowtime = str(nowtime.strftime('%Y-%m-%d %H:%M:%S'))

    #读取记录
    if os.path.exists(ISSUE_INFO_FILE):
        issue_data_dict = read_cfg()
    else:
        issue_data_dict = {}

    #查询最近24小时内所有更新的issu
    jira = JIRA(basic_auth=(JIRA_USERNAME, JIRA_PASSWORD), options = {'server': JIRA_URL})
    query_sql = 'updated >= -24h ORDER BY updated DESC'
    query_info = jira.search_issues(query_sql  ,maxResults=100000)

    print("循环检查jira任务")
    for i in query_info: #循环任务
        key_name = i.key #不能直接用,直接用是一个对象
        issue = jira.issue(key_name)
        is_summary = str(issue.fields.summary) #概要
        is_project = str(issue.fields.project) #项目名
        is_type = str(issue.fields.issuetype) #类型
        is_assignee = str(issue.fields.assignee) #经办人
        is_creator = str(issue.fields.creator) #创建人
        is_status = str(issue.fields.status) #状态

        #查看状态是否是新建或者结束
        if is_status in STOP_STATUS_LIST:
            #查看是否在记录中
            if key_name in issue_data_dict.keys():
                if  issue_data_dict[key_name][1] == "start": #issue从开始变为结束通知
                    one_user = "creator"
                else:
                    one_user = "none"
            else:
                one_user = "creator"
            job_status = "stop"

        else:
            #查看是否在记录中
            if key_name in issue_data_dict.keys():
                if is_assignee != issue_data_dict[key_name][0]: #经办人变了通知
                    one_user = "assignee"
                elif issue_data_dict[key_name][1] == "stop": #状态重置了通知
                    one_user = "assignee"
                else:
                    one_user = "none"
            else:
                one_user = "assignee"
            job_status = "start"

        #把记录写入到字典中
        issue_data_dict[key_name] = [is_assignee, job_status]
        jira_name_list = cfg.sections()

        #发送通知
        if one_user == "assignee":
            if is_assignee in jira_name_list:
                dd_token = cfg[is_assignee]['dd_token']
                notice_url = DD_URL + dd_token
                title = "通知: jira新增任务" + key_name
                new_notice(notice_url, nowtime, title, is_summary, is_project, is_type, "报告人", is_creator, key_name)
            else:
                print(key_name + "中报告人的钉钉并未被记录")
        elif one_user == "creator":
            if is_creator in jira_name_list:
                dd_token = cfg[is_creator]['dd_token']
                notice_url = DD_URL + dd_token
                title = "通知: jira完成任务" + key_name
                new_notice(notice_url, nowtime, title, is_summary, is_project, is_type, "经办人", is_assignee, key_name)
            else:
                print(key_name + "经办人的钉钉并未被记录")
        else:
            print(key_name + "已经通知过,跳过通知")

    #清理字典文件,不要太庞大
    while len(issue_data_dict) > int(ISSUE_INFO_SIZE):
        tmp_key = list(issue_data_dict.keys())[0]
        if tmp_key == "JCPZRW_Usage_list":
            continue
        else:
            del issue_data_dict[tmp_key]

    write_cfg(issue_data_dict)

main()

配置文件脚本,因为我这里对所有pyton脚本进行了统一管理,凡是配置都放到固定文件里,就单独做了一个文件。这里填写jira地址和账号密码,用于调用API,不一定非要管理账号,只要可以看到所有项目即可,因为要统计项目信息。

最后面的张三李四是每个人的钉钉显示中文名称,jira账号名和他的钉钉机器人的token号。1000是临时文件保存多少issue信息,防止过大。后面的status是jira工作流的状态信息,一般《新建》代表任务开始,《关闭》代表任务完成,这里默认即可。最后notice_user表示权限过期时候通知哪些管理员。
file

script_conf.cfg

[jira]
jira_url = http://10.0.1.1:8080
jira_username = admin
jira_password = 123456
issue_info_file = /tmp/jira_issue_info.dict
issue_info_size = 1000
start_status = 待办,待处理,已登记,新建,NEW
stop_status = DONE,线上验证通过,关闭,问题关闭,已验收,已关闭
jcpzrw_notice_user = 张三,李四

[dingding]
dd_url = https://oapi.dingtalk.com/robot/send?access_token=

[张三]
jira_name=zhangsan
dd_token=cdasdsadasd2dadas

[李四]
jira_name=lisi
dd_token=adasdadada
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论