一.说明
最开始培训完入行的2年里,进的几家公司和面试遇到的基本都是机器在200个虚拟机以下,运维加上我也就1-2个人。
这种都是自己说了算,做了什么操作自己记住就行了,添加权限也都是开发说一下这边就给加上了,流程配合之间都靠嘴对嘴的传递,当然也可能用qq和微信。
工作环境还是很重要的,现在待的项目运维多的时候5个,虚拟机300往上,还有一大堆别的云产品要维护。这就有必要进行分工了,而不是大家谁闲着就做,那会导致需求人找不到谁在负责,而且负责人也会来回变动。
那需求就来了,根据日常工作发现如下问题:
1.开发不知道找谁能把这件事做成
2.开发来申请添加权限、用qq之类的进行说明描述
3.因为每个人负责一块,都参与工作,没人知道整体进度
4.某个运维做了一些操作别人不太清楚
5.这块他负责的,现在他请假了,接手后不知道怎么做
那看看开发怎么怎么解决上述问题的:
1.产品经理有需求要提jira
2.在jira上有任务表,可以看到每个人接到的任务,涉及哪一块
3.代码写完后合并到master分支要开发组长去确认,注释也写的很多
具体还会有相应的工作流,将每个需求都从头到尾追踪好,后面上线测试环境甚至生产环境都有相应的管理流程。
二.初代设计
最开始想的是在内部confluence(一个笔记网站)上记录下我们有哪些任务要做,按照日期划分,每天都记录下,任务名称后面写上名字。
当然对于外部的需求,例如加权限,解决一些jenkins发版上的配置错误等等都是完成后来这里补。
现在想想真的很蠢,这记录和没记录的区别完全不大,虽然知道谁什么时间做了哪件事,但维护起来实在费心费力,和其它组的交互也是原始的嘴对嘴交流。
同时这个也完全看不出[任务进度]、[谁在划水]、[某类任务数量]、[当月故障数量]等等,也就是只记录了,但统计非常困难,需要人工去数。
在发现开发用jira后,探索了一下,发现确实很好用,可以实现我们的需求。通过搭配钉钉通知和看版,再建立好清晰完善的工作流程,可以最大效率避免在开发流程上花时间。
三.效果展示
用jira建立2个项目,一个是对外工单用于外部需求的处理,一个是对内工单只内部使用记录任务。
给区分开是因为夹杂在一起会很乱,内部都好说就几个人大家都按照任务进行类别创建,比如购买服务器就建立[资金管理]任务,处理故障就建立[故障处理]任务。
但开发是不管这些的,人家不管是申请权限、处理报错、申请资源全都是[故障处理]这个默认问题类型。所以要精简为2-3类最好,这里是权限和其它类型。
外部任务:
1.权限申请,这里权限申请会搭配钉钉脚本做超时通知,用户要定时续期权限,当然可以调整更大的选项,例如6个月。单独添加申请人选项,是因为申请者可能还没有jira,或者是外包人员
当权限超过规定期限,会给jira的工单发布者和管理员均发送一个钉钉消息
2.其它任务
内部任务:
1.服务管理,这里是内部的任务,例如发版、搭建服务、备份数据等等操作。对于一个大任务一定要拆开成版本,也就是合集,把任务去细化。
不然你随便写个k8s优化那就假大空了,没有一个目标和划水没有区别,做2天感觉烦躁就懒得做了,就没有意义了。同时对于生产等重要操作,要编写配套任务的《操作用例》,你没猜错,就是对标测试的《测试用例》。因为运维不求快求稳,文档操作不出事,比出现问题后补救要成本小得多。
2.报警可以选择几个类别,这里后面会做zabbix联动建立jira任务,其实zabbix本身就可以针对每条报警做处理和记录,但放到jira是统一管理了。
3.其它类别,都是自己人,可以把问题做的细致一点,这样利于后面用jira的筛选器做统计。比如统计某人这个月发版多少次,鼠标勾选几下就出来结果了。
像我自从工单建立后,正式生产发版一共10次
四.工单运作流程
对于外部工单,设置为默认经办人是运维组长,到他那里后,看到钉钉通知,再进行后续任务分配,将人员调动起来。
可以根据jira中查询每个人的任务量,再到grafana上进行综合展示,这里还没做完,后续再补图,相当于做内部的《看板》,将大家的任务状态用大盘做可视化。
这样就会通过大屏看板一目了然,可以找到谁这周任务最少,或者做的拖沓,本来[ELK日志分析]建设这个大项目,里面有3个jira任务,预计是14天完成,结果第一个人任务2周过去了还没完结,说明有问题。
对于这种,说明任务太有挑战性,就多给他分配外部工单进行锻炼,腾出其它组员的时间,晚上加班/值班,也都多安排他来。工单尽量要区分清楚,用强制选项的填选来规定,而不是都在备注里填,很多人懒得去备注里写。
通知脚本
我是编写python脚本定时去获取jira上每个人的最新任务,进行对应人钉钉通知,每个人一个私有钉钉群,由其他运维同事拉一个3人组,加上机器人后再退掉即可,这样省的麻烦别的同事了。
效果如下,这样每个人就有一个私人通知的钉钉群,未来也可以增加入gitlab等等其它的私人通知,所以叫[信息通知小助手],群名称叫[办公信息通知]。
图中可以看到第一个是我发布的任务别人完成了,当发布任务时,被发布者会收到这个信息,会写明谁发布的和一些简要说明。当接单人员完成后,发布者可以知道完成了,具体信息也都会加到附件和评论里,方便查阅。
当然也可以加一些别的工作流程,例如增加一个开始任务也进行通知一下,尤其对开发流程有复杂的工作流,当测试环境上线需要通知测试进行业务功能测试、测试人员完成后确认再通知开发人员进行uat环境上线,这就要每个环境都通知。
申请机器人
1.先添加一个机器人
2.选择自定义类型
3.选择关键字,这里意思是发送的信息,必须带上这些关键字才能发成功,也就是图片里的通知:xxx开头。复制好token,写到后续的配置文件里去。
具体脚本
定时跑的的脚本,可以设置三分钟一次,那就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表示权限过期时候通知哪些管理员。
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