From 197e994a28bc8a83e99be459d4011094723bb413 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Thu, 14 Dec 2017 16:17:00 +0800 Subject: [PATCH 01/18] add DevOps Tools --- ...67\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 4 ++++ 1 file changed, 4 insertions(+) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index 356add7..2da390a 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -35,3 +35,7 @@ WeiXin: Geekwolf
**MongoDB压测:** [iibench&sysbench](https://github.com/tmcallaghan)
**数据库管理:** [数据库迁移工具flyway](https://flywaydb.org/)、表结构对比工具sqllog +#### DevOps Tool +**开源运维系统:**  [autoops](https://github.com/hequan2017/autoops)、[OpsManage](https://github.com/welliamcao/OpsManage)、[opman-django](https://github.com/hgz6536/opman-django)
+**CMDB:**  [CMDB(hequan)](https://github.com/pengzihe/cmdb)、[CMDB(voilet)](https://github.com/voilet/cmdb)、[roncoo-cmdb](https://github.com/roncoo/roncoo-cmdb)
+**故障管理&监控二次开发:**  [fms](https://github.com/geekwolf/fms)
From c04a2a318af87f95186e512e1f9c360243729edb Mon Sep 17 00:00:00 2001 From: geekwolf Date: Wed, 27 Dec 2017 09:03:19 +0800 Subject: [PATCH 02/18] add PaaS OpenSource --- ...\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index 2da390a..c4de4c8 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -19,7 +19,7 @@ WeiXin: Geekwolf
**数据库可视化:**   zeppelin、metabase、Heka、redash、superset
**流控系统:**  Panabit、[在线数据包分析工具Pcap Analyzer](http://le4f.net/post/post/pcap-online-analyzer)
**安全检查:**  chrootkit、rkhunter
-**PaaS:**   Cloudify、Cloudfoundry、Openshift、[Deis](http://www.deis.io/) (Docker、CoreOS、[Atomic](https://access.redhat.com/articles/rhel-atomic-getting-started)、[ubuntu core/Snappy](http://www.ubuntu.com/cloud/tools/snappy)、[RancherOS](http://rancher.com))
+**PaaS:**   Cloudify、Cloudfoundry、Openshift、[Deis](http://www.deis.io/) (Docker、CoreOS、[Atomic](https://access.redhat.com/articles/rhel-atomic-getting-started)、[ubuntu core/Snappy](http://www.ubuntu.com/cloud/tools/snappy)、[RancherOS](http://rancher.com))、[DomeOS](http://domeos.org)、[Rainbond](http://www.rainbond.com/)
**Troubleshooting:**[Sysdig](http://www.sysdig.org/) 、Systemtap、Perf
**服务发现:**   [SmartStack](http://nerds.airbnb.com)、etcd
**持续集成:**  [Go](http://www.go.cd)、Jenkins、Gitlab、[facebook代码审查工具phabricator](http://phabricator.org/)、[spinnaker](http://spinnaker.io/)、[PHP代码持续集成工具PHPCI](https://www.phptesting.org)
From 1cbdb2ebce477463ffdbca2f65e2767416c9b345 Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Thu, 18 Jan 2018 19:54:18 +0800 Subject: [PATCH 03/18] =?UTF-8?q?=E5=A2=9E=E5=8A=A0APM=E5=B7=A5=E5=85=B7sk?= =?UTF-8?q?ywarking=E3=80=81UAVStack=E3=80=81Google=20Opencensus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index c4de4c8..246cb1e 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -11,7 +11,7 @@ WeiXin: Geekwolf
**微服务平台:** OpenShift、Cloud Foundry、Kubernetes、Mesosphere
**性能监控工具:** dstat(多类型资源统计)、atop(htop/top)、nmon(类Unix系统性能监控)、slabtop(内核slab缓存信息)、sar(性能监控和瓶颈检查)、sysdig(系统进程高级视图)、tcpdump(网络抓包)、iftop(类似top的网络连接工具)、iperf(网络性能工具)、smem)(高级内存报表工具)、collectl(性能监控工具)、[TCP优化监控工具tcpdive](https://github.com/fastos/tcpdive)
**响应时间统计工具:**   [tcprstat](https://github.com/Lowercases/tcprstat)
-**免费APM工具:**  [mmtrix(见过的最全面的分析工具)](http://www.mmtrix.com/evaluate/result)、[alibench](http://alibench.com/)、[JAVA性能监控pinpoint](https://github.com/naver/pinpoint)、[cat](https://github.com/dianping/cat)
+**免费APM工具:**  [mmtrix(见过的最全面的分析工具)](http://www.mmtrix.com/evaluate/result)、[alibench](http://alibench.com/)、[JAVA性能监控pinpoint](https://github.com/naver/pinpoint)、[cat](https://github.com/dianping/cat)、[skywalking](http://skywalking.org/)、[UAVStack](https://uavorg.github.io/main/)、[Google Opencensus](http://opencensus.io/)
**进程监控:**  [mmonit](http://mmonit.com/monit/documentation/monit.html)、Supervisor、[frigga](https://github.com/xiaomi-sa/frigga)、 [StrongLoop Process Manager](http://strong-pm.io/compare/)
**日志系统:**  Logstash、Scribe、Graylog、ELKStack
**绘图工具:**  RRDtool、Gnuplot
From 4865312d4d867f10a207bdadf420d4bd7320c9a6 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Mon, 29 Jan 2018 13:28:17 +0800 Subject: [PATCH 04/18] update --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 087f407..74a1ce4 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,5 @@ sa-scripts @……@ Enjoy yourself
博客:http://www.simlinux.com
By Geekwolf + +![赞赏](https://raw.githubusercontent.com/geekwolf/fms/master/doc/images/wxzf.png) From 2982c877c06f2bb705ec84e43f623dcab6acf9c6 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Wed, 31 Jan 2018 11:10:23 +0800 Subject: [PATCH 05/18] add plogstash to archive logs from Filebeat->Redis --- ops-scripts/plogstash/README.md | 10 ++ ops-scripts/plogstash/plogstash.py | 244 +++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 ops-scripts/plogstash/README.md create mode 100644 ops-scripts/plogstash/plogstash.py diff --git a/ops-scripts/plogstash/README.md b/ops-scripts/plogstash/README.md new file mode 100644 index 0000000..f0e4c85 --- /dev/null +++ b/ops-scripts/plogstash/README.md @@ -0,0 +1,10 @@ +#### 用途 + +- 基于Redis List日志消息,归档日志文件 +- 解决Logstash(新版本单线程可解决)归档乱序问题 +- 架构: Filebeat->Redis->Plogstash->Files +#### 用法: +``` +python3 plogstash.py +Usage: plogstash.py [start|stop|restart|status] +``` diff --git a/ops-scripts/plogstash/plogstash.py b/ops-scripts/plogstash/plogstash.py new file mode 100644 index 0000000..a9b34b7 --- /dev/null +++ b/ops-scripts/plogstash/plogstash.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# @Author: Geekwolf +# @Date: 2018-01-29 14:23:04 +# @Last Modified by: Geekwolf +# @Last Modified time: 2018-01-31 10:55:01 + +#!/usr/bin/env python3 +# daemon.py + +import os +import sys +import time +import redis +import json +import re +import atexit +import signal +# import collections + + +class Base(object): + + def __init__(self, *args, **kwargs): + + self.pidfile = '/var/run/plogstash.pid' + self.service_name = 'Plogstash' + self.path = '/var/log/plogstash' + os.makedirs(self.path, exist_ok=True) + self.logfile = '%s/%s.log' % (self.path, self.service_name) + + self.redis_host = '127.0.0.1' + self.redis_password = 'geekwolf' + self.redis_port = 5044 + self.redis_db = 0 + self.redis_key = 'filebeat' + self.batch_size = 5000 + self.expires = 5 # second + self.archive_time = 1 # how long time to archive + self.base_dir = '/data/logs' + # self._tmp = '/tmp/.%s' % self.service_name + + +class Daemon(Base): + + def __init__(self, *args, **kwargs): + super(Daemon, self).__init__(*args, **kwargs) + + def daemonize(self): + + # First fork (detaches from parent) + try: + if os.fork() > 0: + raise SystemExit(0) # Parent exit + except OSError as e: + raise RuntimeError('fork #1 failed.') + + os.chdir('/') + # set this will 777 + # os.umask(0) + os.setsid() + # Second fork (relinquish session leadership) + try: + if os.fork() > 0: + raise SystemExit(0) + except OSError as e: + raise RuntimeError('fork #2 failed.') + + # Flush I/O buffers + sys.stdout.flush() + sys.stderr.flush() + + # Replace file descriptors for stdin, stdout, and stderr + with open(self.logfile, 'ab', 0) as f: + os.dup2(f.fileno(), sys.stdout.fileno()) + with open(self.logfile, 'ab', 0) as f: + os.dup2(f.fileno(), sys.stderr.fileno()) + with open(self.logfile, 'rb', 0) as f: + os.dup2(f.fileno(), sys.stdin.fileno()) + + # Write the PID file + print(os.getpid()) + with open(self.pidfile, 'w') as f: + print(os.getpid(), file=f) + + # Arrange to have the PID file removed on exit/signal + atexit.register(lambda: os.remove(self.pidfile)) + + # Signal handler for termination (required) + def sigterm_handler(signo, frame): + raise SystemExit(1) + + signal.signal(signal.SIGTERM, sigterm_handler) + + def get_now_date(self): + + return time.strftime('%Y-%m-%d', time.localtime(time.time())) + + def get_now_timestamp(self): + + return time.time() + + def get_now_time(self): + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + + def logging(self, msg): + + with open(self.logfile) as f: + print('%s %s' % (self.get_now_time(), msg)) + + def append_log(self): + pass + + def start(self): + + if os.path.exists(self.pidfile): + raise RuntimeError('Already running') + else: + try: + self.daemonize() + self.append_log() + self.status() + except RuntimeError as e: + print(e, file=sys.stderr) + raise SystemExit(1) + + def stop(self): + + # f = os.open(self.pipe_path, os.O_RDONLY | os.O_NONBLOCK) + # ret = os.read(f, 1024).decode('utf-8') + # print(ret.split('\n')) + # os.close(f) + + if os.path.exists(self.pidfile): + # with open(self._tmp) as f: + # _data = f.read() + # if _data is not None and len(eval(_data)) > 0: + # for k, v in eval(_data).items(): + # v = v['fd'].rstrip('\n') + # v.close() + with open(self.pidfile) as f: + os.kill(int(f.read()), signal.SIGTERM) + print('Plogstash is stopped') + else: + print('Not running', file=sys.stderr) + raise SystemExit(1) + + def restart(self): + + self.stop() + self.start() + + def status(self): + + try: + with open(self.pidfile, 'r') as f: + pid = int(f.read().strip()) + except: + pid = None + + if pid: + print('%s is running as pid:%s' % (self.service_name, pid)) + else: + print('%s is not running' % self.service_name) + + +class Worker(Daemon): + + def __init__(self, *args, **kwargs): + super(Worker, self).__init__(self, *args, **kwargs) + + def _redis(self): + + pool = redis.ConnectionPool(host=self.redis_host, password=self.redis_password, port=self.redis_port, db=self.redis_db, socket_timeout=10000) + rc = redis.StrictRedis(connection_pool=pool) + return rc + + def get_redis_data(self): + + _data = self._redis().lrange(self.redis_key, 0, self.batch_size - 1) + # 删除数据(可考虑处理完再删除) + return _data + + def del_redis_data(self): + + _data = self._redis().ltrim(self.redis_key, self.batch_size, -1) + + def append_log(self): + + file_meta = {} + # file_handler = collections.defaultdict(dict) + # try: + # os.mkfifo(self.pipe_path) + # except Exception as e: + # print(str(e)) + + # pipe_ins = os.open(self.pipe_path, os.O_SYNC | os.O_CREAT | os.O_RDWR) + while True: + time.sleep(self.archive_time) + _data = self.get_redis_data() + if _data: + for _d in _data: + try: + _d = json.loads(_d.decode('utf-8')) + _path = '%s/%s/%s/%s' % (self.base_dir, _d['fields']['env'], self.get_now_date(), _d['fields']['ip_address']) + os.makedirs(_path + '/logs', exist_ok=True) + file_name = _d['source'].split('/')[-1] + # _path = '%s/%s/%s/%s' % (self.base_dir, _d['fields']['env'],self.get_now_date(), _d['fields']['ip_address']) + + if re.match('nohup', file_name): + file_path = '%s/%s' % (_path, file_name) + else: + file_path = '%s/logs/%s' % (_path, file_name) + + with open(file_path, 'a') as f: + f.write(_d['message'] + '\n') + # if 'fd' not in file_handler[file_path]: + # f = open(file_path, 'a', buffering=1024000) + # file_handler[file_path]['fd'] = str(f) + # file_handler[file_path]['time'] = self.get_now_timestamp() + except Exception as e: + self.logging(str(e)) + self.del_redis_data() + # with open(self._tmp, 'w') as f: + # f.write(json.dumps(file_handler)) + +if __name__ == '__main__': + + if len(sys.argv) != 2: + print('Usage: {} [start|stop|restart|status]'.format(sys.argv[0]), file=sys.stderr) + raise SystemExit(1) + + daemon = Worker() + if sys.argv[1] == 'start': + daemon.start() + elif sys.argv[1] == 'stop': + daemon.stop() + elif sys.argv[1] == 'restart': + print("Restart ...") + daemon.restart() + elif sys.argv[1] == 'status': + daemon.status() + else: + print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr) + raise SystemExit(1) From ad37a8c775be98c8d558b2e23e69110fc4669fcb Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Mon, 26 Feb 2018 09:45:01 +0800 Subject: [PATCH 06/18] add netdata --- ...\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index 246cb1e..d83084a 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -7,7 +7,7 @@ WeiXin: Geekwolf
**配置类工具:** Capistrano、Chef、puppet、func、salstack、Ansible、rundeck、CFengine、Rudder
**web管理平台:** [Redis云管理平台-CacheCloud](https://github.com/sohutv/cachecloud)
**自动化构建和测试:** Ant、Maven、Selenium、PyUnit、QUnit、JMeter、Gradle、PHPUnit
-**监控类工具:** Cacti、Nagios(Icinga)、Zabbix([模板大全](https://monitoringartist.github.io/zabbix-searcher/))、基于时间监控前端Grafana、Mtop、MRTG(网络流量监控图形工具)、[Monit](https://mmonit.com/) 、Diamond+Graphite+Grafana
+**监控类工具:** Cacti、Nagios(Icinga)、Zabbix([模板大全](https://monitoringartist.github.io/zabbix-searcher/))、基于时间监控前端Grafana、Mtop、MRTG(网络流量监控图形工具)、[Monit](https://mmonit.com/) 、Diamond+Graphite+Grafana、[netdata](https://my-netdata.io/)
**微服务平台:** OpenShift、Cloud Foundry、Kubernetes、Mesosphere
**性能监控工具:** dstat(多类型资源统计)、atop(htop/top)、nmon(类Unix系统性能监控)、slabtop(内核slab缓存信息)、sar(性能监控和瓶颈检查)、sysdig(系统进程高级视图)、tcpdump(网络抓包)、iftop(类似top的网络连接工具)、iperf(网络性能工具)、smem)(高级内存报表工具)、collectl(性能监控工具)、[TCP优化监控工具tcpdive](https://github.com/fastos/tcpdive)
**响应时间统计工具:**   [tcprstat](https://github.com/Lowercases/tcprstat)
From ea2e08ad87ba74fe2f8372ba5b5e40e118f56bbc Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Thu, 1 Mar 2018 18:13:01 +0800 Subject: [PATCH 07/18] add OpenZipkin a distributed tracing system --- ...\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index d83084a..873aeb3 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -11,7 +11,7 @@ WeiXin: Geekwolf
**微服务平台:** OpenShift、Cloud Foundry、Kubernetes、Mesosphere
**性能监控工具:** dstat(多类型资源统计)、atop(htop/top)、nmon(类Unix系统性能监控)、slabtop(内核slab缓存信息)、sar(性能监控和瓶颈检查)、sysdig(系统进程高级视图)、tcpdump(网络抓包)、iftop(类似top的网络连接工具)、iperf(网络性能工具)、smem)(高级内存报表工具)、collectl(性能监控工具)、[TCP优化监控工具tcpdive](https://github.com/fastos/tcpdive)
**响应时间统计工具:**   [tcprstat](https://github.com/Lowercases/tcprstat)
-**免费APM工具:**  [mmtrix(见过的最全面的分析工具)](http://www.mmtrix.com/evaluate/result)、[alibench](http://alibench.com/)、[JAVA性能监控pinpoint](https://github.com/naver/pinpoint)、[cat](https://github.com/dianping/cat)、[skywalking](http://skywalking.org/)、[UAVStack](https://uavorg.github.io/main/)、[Google Opencensus](http://opencensus.io/)
+**免费APM工具:**  [mmtrix(见过的最全面的分析工具)](http://www.mmtrix.com/evaluate/result)、[alibench](http://alibench.com/)、[JAVA性能监控pinpoint](https://github.com/naver/pinpoint)、[cat](https://github.com/dianping/cat)、[skywalking](http://skywalking.org/)、[UAVStack](https://uavorg.github.io/main/)、[Google Opencensus](http://opencensus.io/)、[OpenZipkin](https://zipkin.io/)
**进程监控:**  [mmonit](http://mmonit.com/monit/documentation/monit.html)、Supervisor、[frigga](https://github.com/xiaomi-sa/frigga)、 [StrongLoop Process Manager](http://strong-pm.io/compare/)
**日志系统:**  Logstash、Scribe、Graylog、ELKStack
**绘图工具:**  RRDtool、Gnuplot
From fff852d9013d1fc3559357de025b055c37ab2d26 Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Wed, 7 Mar 2018 10:52:44 +0800 Subject: [PATCH 08/18] Add Big Data Analyze Tools --- ...\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index 873aeb3..cc554fd 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -34,7 +34,7 @@ WeiXin: Geekwolf
**MySQL物理备份工具**: Xtrabackup、LVM Snapshot
**MongoDB压测:** [iibench&sysbench](https://github.com/tmcallaghan)
**数据库管理:** [数据库迁移工具flyway](https://flywaydb.org/)、表结构对比工具sqllog - +**大数据管理套件: ** [Ambari](http://incubator.apache.org/ambari/)、[Cloudera Manger(CDH)](https://www.cloudera.com) #### DevOps Tool **开源运维系统:**  [autoops](https://github.com/hequan2017/autoops)、[OpsManage](https://github.com/welliamcao/OpsManage)、[opman-django](https://github.com/hgz6536/opman-django)
**CMDB:**  [CMDB(hequan)](https://github.com/pengzihe/cmdb)、[CMDB(voilet)](https://github.com/voilet/cmdb)、[roncoo-cmdb](https://github.com/roncoo/roncoo-cmdb)
From 9887d4156f9e28d80f8abe9adbf92d1315c94e7e Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Wed, 7 Mar 2018 10:53:35 +0800 Subject: [PATCH 09/18] =?UTF-8?q?Update=20=E5=BC=80=E6=BA=90=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E4=B8=80=E8=A7=88=E8=A1=A8.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index cc554fd..d5c8fc2 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -33,7 +33,7 @@ WeiXin: Geekwolf
**MySQL逻辑备份工具**: mysqldump、mysqlhotcopy、mydumper、MySQLDumper 、mk-parallel-dump/mk-parallel-restore
**MySQL物理备份工具**: Xtrabackup、LVM Snapshot
**MongoDB压测:** [iibench&sysbench](https://github.com/tmcallaghan)
-**数据库管理:** [数据库迁移工具flyway](https://flywaydb.org/)、表结构对比工具sqllog +**数据库管理:** [数据库迁移工具flyway](https://flywaydb.org/)、表结构对比工具sqllog
**大数据管理套件: ** [Ambari](http://incubator.apache.org/ambari/)、[Cloudera Manger(CDH)](https://www.cloudera.com) #### DevOps Tool **开源运维系统:**  [autoops](https://github.com/hequan2017/autoops)、[OpsManage](https://github.com/welliamcao/OpsManage)、[opman-django](https://github.com/hgz6536/opman-django)
From 29c0c4de6273bfd8c2e9bd0991c01fd30d19ced8 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Fri, 25 May 2018 20:08:14 +0800 Subject: [PATCH 10/18] =?UTF-8?q?=E8=BF=81=E7=A7=BBmarkdown=E6=A0=BC?= =?UTF-8?q?=E5=BC=8Fwiki=E5=88=B0Confluence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ops-scripts/migratetoconfluence.py | 164 +++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 ops-scripts/migratetoconfluence.py diff --git a/ops-scripts/migratetoconfluence.py b/ops-scripts/migratetoconfluence.py new file mode 100644 index 0000000..f8e40ad --- /dev/null +++ b/ops-scripts/migratetoconfluence.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# @Author: Geekwolf +# @Date: 2018-05-24 17:58:16 +# @Last Modified by: Geekwolf +# @Last Modified time: 2018-05-25 19:50:37 + +import pymysql +import collections +import requests +import json +import markdown + + +class DBHelper(object): + """docstring for DBHelper""" + + def __init__(self): + self.host = '192.168.1.1' + self.user = 'wiki' + self.password = 'password' + self.database = 'db' + self.conn = None + self.cur = None + + def ConnDB(self): + try: + self.conn = pymysql.connect(self.host, self.user, self.password, self.database, charset='utf8') + except Exception as e: + print(str(e)) + return False + self.cur = self.conn.cursor() + return True + + def Close(self): + if self.conn and self.cur: + self.cur.close() + self.conn.close() + return True + + def Execute(self, sql, params=None): + self.ConnDB() + try: + if self.conn and self.cur: + self.cur.execute(sql, params) + self.conn.commit() + except Exception as e: + print(str(e)) + self.Close() + return False + return True + + def Select(self, sql, params=None): + self.Execute(sql, params) + return self.cur.fetchall() + + +class SyncWiki(object): + """docstring for SyncWiki""" + + def __init__(self, ): + + # The Space:autotest Key Name + self.space = 'ops' + self.url = 'http://confluence' + self.username = 'geekwolf' + self.password = 'geekwolf' + self.session = self.GetSession() + # self.home_page = '{} Home'.format(self.space) + self.home_page='ops' + self.headers = {'Content-Type': 'application/json'} + self.dbhelper = DBHelper() + + def GetSession(self): + session = requests.session() + data = {'os_username': self.username, 'os_password': self.password, 'login': 'Log in'} + res = session.post(self.url, data) + return session + + def MarkdownToHtml(self, content): + # convert_url = "{}/rest/api/contentbody/convert/storage".format(self.url) + # print(convert_url) + # data = {"value": content, "representation": "wiki"} + # ret = self.session.post(convert_url, json.dumps(data), headers=self.headers) + + ret = markdown.markdown(content, extensions=['fenced_code', 'codehilite', 'extra', 'abbr', 'attr_list', 'def_list', 'footnotes', + 'tables', 'smart_strong', 'admonition', 'codehilite', 'headerid', 'meta', 'nl2br', 'sane_lists', 'smarty', 'toc', 'wikilinks']) + return ret + + def GetPageId(self, title): + ''' + 通过分类名称获取在Confluence中的id + ''' + content_url = '{}/rest/api/content?spaceKey={}&title={}'.format(self.url, self.space, title) + data = self.session.get(content_url).json() + id = data['results'][0]['id'] + return id + + def CreatePageMethod(self, id, title, value=None): + + page_url = '{}/rest/api/content'.format(self.url) + data = {"type": "page", "ancestors": [{"id": id}], "title": title, "space": { + "key": self.space}, "body": {"storage": {"value": value, "representation": "storage"}}} + self.session.post(page_url, json.dumps(data), headers=self.headers) + + def CreateTypePage(self): + ''' + 创建分类页面(二级分类) + ''' + group_page_url = '{}/rest/api/content'.format(self.url) + group_info = self.GetGroupInfo() + + try: + for k, v in group_info.items(): + self.CreatePageMethod(self.GetPageId(self.home_page), k) + for i in v: + self.CreatePageMethod(self.GetPageId(k), i) + ret = True + except Exception as e: + print(str(e)) + ret = False + return ret + + def CreatePage(self): + ''' + 根据标题和内容创建对应子类的页面 + ''' + content = self.GetWiki() + for i in content: + try: + id = self.GetPageId(i[0]) + title = i[1] + value = self.MarkdownToHtml(i[2]) + self.CreatePageMethod(id, title, value=value) + print('{}------{}已经创建'.format(i[0], i[1])) + except Exception as e: + print('{}------{}创建失败:{}'.format(i[0], i[1], str(e))) + + def GetGroupInfo(self): + + sql = 'select * from wiki_group;' + result = self.dbhelper.Select(sql) + _group_info = collections.defaultdict(list) + _group_dict = dict([(r[0], r[1]) for r in result]) + + for r in result: + if r[3] is None: + _group_info[r[1]] = [] + else: + _group_info[_group_dict[r[3]]].append(r[1]) + return _group_info + + def GetWiki(self): + + sql = 'SELECT g.name,w.title,w.content from wiki_wiki as w LEFT JOIN wiki_group as g ON w.group_id = g.id' + result = self.dbhelper.Select(sql) + return result + + +if __name__ == '__main__': + + ins = SyncWiki() + if ins.CreateTypePage(): + ins.CreatePage() + From 3f9d64470f28e288b0037260e37322d2ed6510c7 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Wed, 11 Jul 2018 11:47:57 +0800 Subject: [PATCH 11/18] add zabbix_report_email --- .../zabbix/zabbix_report_email/README.md | 9 + .../zabbix/zabbix_report_email/config.ini | 28 ++ .../zabbix/zabbix_report_email/default.docx | Bin 0 -> 38024 bytes .../zabbix/zabbix_report_email/report.py | 244 ++++++++++++++++++ .../zabbix_report_email/requirements.txt | 3 + ...abbix_report.py => zabbix_report_excel.py} | 0 6 files changed, 284 insertions(+) create mode 100644 ops-scripts/zabbix/zabbix_report_email/README.md create mode 100644 ops-scripts/zabbix/zabbix_report_email/config.ini create mode 100644 ops-scripts/zabbix/zabbix_report_email/default.docx create mode 100644 ops-scripts/zabbix/zabbix_report_email/report.py create mode 100644 ops-scripts/zabbix/zabbix_report_email/requirements.txt rename ops-scripts/zabbix/{zabbix_report.py => zabbix_report_excel.py} (100%) diff --git a/ops-scripts/zabbix/zabbix_report_email/README.md b/ops-scripts/zabbix/zabbix_report_email/README.md new file mode 100644 index 0000000..b454459 --- /dev/null +++ b/ops-scripts/zabbix/zabbix_report_email/README.md @@ -0,0 +1,9 @@ +#### 安装依赖 +pip install -r requirements.txt +#### 使用说明 +1. 邮件图文告警 + 在Zabbix配置邮件发送告警脚本 +2. 报表 + 在config.ini中graph配置info(要出报表的主机及对应的graphid) + python report.py report + \ No newline at end of file diff --git a/ops-scripts/zabbix/zabbix_report_email/config.ini b/ops-scripts/zabbix/zabbix_report_email/config.ini new file mode 100644 index 0000000..eefa937 --- /dev/null +++ b/ops-scripts/zabbix/zabbix_report_email/config.ini @@ -0,0 +1,28 @@ +[monitor] +zbx_url = http://zbx.simlinux.com/ +graph_url = chart2.php +item_graph_url = chart.php +username = geekwolf +password = geekwolf +temp_dir = tmp +log_file = zbx.log + +[graph] +#显示执行时前一天的数据,报表使用 +period = 86400 +width = 580 +height = 230 +info = [{"name":"HOST1","graphids":[1304,1306,1301,1302]},{"name":"HOST2","graphids":[1296,1298,1293,1294]},{"name":"HOST3","graphids":[1247,1263,1251,1267]}] + +[ftp] +host = 1.1.1.1 +port = 21 +username = ftpuser +password = ftpuser + +[email] +smtpserver = email.simlinux.com +username = geekwolf@simlinux.com +password = test +port = 25 + diff --git a/ops-scripts/zabbix/zabbix_report_email/default.docx b/ops-scripts/zabbix/zabbix_report_email/default.docx new file mode 100644 index 0000000000000000000000000000000000000000..85201dd1d12caa32be32f1f10f50f75417c8e42d GIT binary patch literal 38024 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∓}W$t8_CN|y7 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 M5`n2mZ~}-203NHJE&u=k literal 0 HcmV?d00001 diff --git a/ops-scripts/zabbix/zabbix_report_email/report.py b/ops-scripts/zabbix/zabbix_report_email/report.py new file mode 100644 index 0000000..57f6a5d --- /dev/null +++ b/ops-scripts/zabbix/zabbix_report_email/report.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @Author: Geekwolf +# @Date: 2018-05-07 13:26:12 +# @Last Modified by: Geekwolf +# @Last Modified time: 2018-07-11 10:52:39 + +import ConfigParser +import cookielib +import urllib2 +import urllib +import ast +import datetime,time +from docx import Document +from docx.shared import Pt, RGBColor +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml.ns import qn +from docx.enum.style import WD_STYLE_TYPE +from docx.shared import Inches +from io import BytesIO +import collections +from ftplib import FTP +import os +import mimetypes +import sys +import smtplib +from email.header import Header +from email.mime.text import MIMEText +from email.mime.image import MIMEImage +from email.mime.multipart import MIMEMultipart +from email.utils import parseaddr, formataddr + +config = ConfigParser.RawConfigParser() +config.read(os.path.join(os.path.dirname(os.path.abspath(__file__)),'./config.ini')) + +class ZabbixGraph(object): + + def __init__(self): + + self.url = config.get('monitor', 'zbx_url') + self.username = config.get('monitor', 'username') + self.password = config.get('monitor', 'password') + self.graph_url = self.url + config.get('monitor', 'graph_url') + self.item_graph_url = self.url + config.get('monitor', 'item_graph_url') + self.width = config.get('graph', 'width') + self.height = config.get('graph', 'height') + self.period = config.get('graph', 'period') + self.temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),config.get('monitor', 'temp_dir')) + self.log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),config.get('monitor','log_file')) + if not os.path.exists(self.temp_dir): + os.makedirs(self.temp_dir) + self.urlOpener = self.GetSession() + + def GetSession(self): + + cookiejar = cookielib.CookieJar() + urlOpener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar)) + values = {"name": self.username, 'password': self.password, 'autologin': 1, "enter": 'Sign in'} + data = urllib.urlencode(values) + request = urllib2.Request(self.url, data) + try: + urlOpener.open(request, timeout=10) + self.urlOpener = urlOpener + except urllib2.HTTPError, e: + print e + return urlOpener + + def Log(self,rec): + t = time.strftime('%Y-%m-%d %H:%M:%S') + with open(self.log_file,'a') as f: + f.write('{} {}'.format(str(t),str(rec))) + + + def GetRequest(self,values,url,id): + + _data = urllib.urlencode(values) + request = urllib2.Request(url, _data) + url = self.urlOpener.open(request) + ext = mimetypes.guess_extension(url.headers['content-type']) + imagename = '{}/{}{}'.format(self.temp_dir, str(id), ext) + with open(imagename, 'wb') as f: + f.write(url.read()) + return imagename + + def GetItemGraph(self,id): + + values = {'itemids': id, 'width': self.width, 'height': self.height, 'period': self.period} + imagename = self.GetRequest(values,self.item_graph_url,id) + return imagename + + def GetGraph(self): + + info = ast.literal_eval(config.get('graph', 'info')) + data = collections.defaultdict(list) + for i in info: + values = {} + for j in i['graphids']: + values = {'graphid': j, 'width': self.width, 'height': self.height, 'period': self.period} + imagename = self.GetRequest(values,self.graph_url,j) + # image = BytesIO() + # image.write(url.read()) + data[i['name']].append(imagename) + # imagename = "%s/%s.png" % (self.temp_dir, str(j) + i['name']) + # f = open(imagename, 'wb') + # f.write(image) + self.WriteDoc(data) + + def GetStyles(self): + + # doc = Document() + # 在脚本打包成二进制时,需要指定default.docx路径,否则会报错 + doc = Document(docx=os.path.join(os.getcwd(), 'default.docx')) + style_head = doc.styles.add_style('style_head', WD_STYLE_TYPE.PARAGRAPH) + style_head.font.size = Pt(25) + style_head.font.name = u'微软雅黑' + style_head.font.bold = True + style_head._element.rPr.rFonts.set(qn('w:eastAsia'), u'微软雅黑') + style_head.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER + + style_title = doc.styles.add_style('style_title', WD_STYLE_TYPE.PARAGRAPH) + style_title.font.size = Pt(15) + style_title.font.name = u'微软雅黑' + style_title.font.bold = True + style_title._element.rPr.rFonts.set(qn('w:eastAsia'), u'微软雅黑') + + sub_title = doc.styles.add_style('sub_title', WD_STYLE_TYPE.PARAGRAPH) + sub_title.font.size = Pt(10) + sub_title.font.name = u'微软雅黑' + sub_title.font.bold = True + sub_title._element.rPr.rFonts.set(qn('w:eastAsia'), u'微软雅黑') + + return doc, style_head, style_title, sub_title + + @staticmethod + def GetYesterdayTime(): + + _time = datetime.date.today() - datetime.timedelta(days=1) + return str(_time) + + def WriteDoc(self, data): + + doc, style_head, style_title, sub_title = self.GetStyles() + _dict = {0: '一', 1: '二', 2: '三'} + _time = ZabbixGraph.GetYesterdayTime() + head = doc.add_paragraph(u'zbx监控报表', style='style_head') + sub_head = doc.add_paragraph(_time) + sub_head.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER + for d in enumerate(data): + title = '{}、{}'.format(_dict[d[0]], d[1]) + doc.add_paragraph(title.decode('utf8'), style='style_title') + for idx, val in enumerate(data[d[1]]): + #sub_title = u'内存' if idx%2 == 1 else u'CPU' + if idx >=2: + sub_title = u'内存' + else: + sub_title = u'CPU' + if idx%2 != 1: + doc.add_paragraph(sub_title, style='sub_title') + doc.add_picture(val, width=Inches(6.5), height=Inches(3)) + file = 'report-{}.docx'.format(('').join(_time.split('-'))) + doc.save(file) + #如果将报表上传ftp,可以去掉注释 + #self.FtpUpload(file) + + def DelTemp(self): + + os.system('rm -rf report-* {}'.format(self.temp_dir)) + + def FtpUpload(self, file): + + host = config.get('ftp', 'host') + port = config.get('ftp', 'port') + username = config.get('ftp', 'username') + password = config.get('ftp', 'password') + ftp = FTP(host=host) + ftp.login(user=username, passwd=password) + ftp.storbinary('STOR ' + file, open(file, 'rb')) + ftp.quit() + self.DelTemp() + +class AlarmInfo(ZabbixGraph): + + def format(content): + + name, addr = parseaddr(content) + return formataddr((Header(name, 'utf-8').encode(), addr)) + + def Email(self): + + smtpserver = config.get('email', 'smtpserver') + username = config.get('email','username') + password = config.get('email','password') + port = config.get('email','password') + try: + smtp = smtplib.SMTP() + smtp.connect(smtpserver) + smtp.login(username,password) + return smtp + except Exception as e: + self.Log(str(e)) + + def SendEmail(self,_info): + + itemid = _info[2].split('|')[0] + imagename = self.GetItemGraph(itemid) + fro = config.get('email','username') + _content = (':').join(_info[3].split('|')[1:]) + content = '{}
'.format(_content) + msg = MIMEMultipart() + #msg['From'] = '监控告警<{}>'.format(fro).decode('utf-8') + msg['From'] = "%s<%s>" % (Header("监控告警","utf-8"),fro) + msg['Subject'] = Header((': ').join(_info[2].split('|')[-2:]),'utf-8') + msg['To'] = _info[1] + msg.attach(MIMEText(content,'html','utf-8')) + + with open(imagename,'rb') as f: + img = MIMEImage(f.read()) + img.add_header('Content-ID', '') + msg.attach(img) + + try: + email = self.Email() + email.sendmail(fro,_info[1],msg.as_string()) + email.quit() + except Exception as e: + self.Log(str(e)) + + def main(self,_info): + try: + if len(_info) == 4: + rec = '{}\t{}\n{}\n'.format(_info[1],_info[2],_info[3]) + self.Log(rec) + self.SendEmail(_info) + elif len(_info) == 2 and _info[1] == 'report': + print 'tt' + self.GetGraph() + except Exception as e: + self.Log(str(e)) + + +if __name__ == '__main__': + + ins = AlarmInfo() + ins.main(sys.argv) diff --git a/ops-scripts/zabbix/zabbix_report_email/requirements.txt b/ops-scripts/zabbix/zabbix_report_email/requirements.txt new file mode 100644 index 0000000..cbc7840 --- /dev/null +++ b/ops-scripts/zabbix/zabbix_report_email/requirements.txt @@ -0,0 +1,3 @@ +ConfigParser +python-docx +email diff --git a/ops-scripts/zabbix/zabbix_report.py b/ops-scripts/zabbix/zabbix_report_excel.py similarity index 100% rename from ops-scripts/zabbix/zabbix_report.py rename to ops-scripts/zabbix/zabbix_report_excel.py From 97dc3703cd44d00cfcec14a7700a14d293900a79 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Wed, 11 Jul 2018 11:49:53 +0800 Subject: [PATCH 12/18] modify README.md --- ops-scripts/zabbix/zabbix_report_email/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ops-scripts/zabbix/zabbix_report_email/README.md b/ops-scripts/zabbix/zabbix_report_email/README.md index b454459..b70b3ee 100644 --- a/ops-scripts/zabbix/zabbix_report_email/README.md +++ b/ops-scripts/zabbix/zabbix_report_email/README.md @@ -1,9 +1,12 @@ #### 安装依赖 -pip install -r requirements.txt +``` + pip install -r requirements.txt +``` #### 使用说明 1. 邮件图文告警 在Zabbix配置邮件发送告警脚本 2. 报表 在config.ini中graph配置info(要出报表的主机及对应的graphid) +``` python report.py report - \ No newline at end of file +``` From 5feb7d28f8638738580641d2b6e36a58b7af4225 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Wed, 11 Jul 2018 11:50:46 +0800 Subject: [PATCH 13/18] modify README.md --- ops-scripts/zabbix/zabbix_report_email/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ops-scripts/zabbix/zabbix_report_email/README.md b/ops-scripts/zabbix/zabbix_report_email/README.md index b70b3ee..7cc3f98 100644 --- a/ops-scripts/zabbix/zabbix_report_email/README.md +++ b/ops-scripts/zabbix/zabbix_report_email/README.md @@ -1,3 +1,7 @@ +#### 版本 +``` + Python2.7 +``` #### 安装依赖 ``` pip install -r requirements.txt From 5e607e40e89df43ed14b151d944b621262f29c46 Mon Sep 17 00:00:00 2001 From: geekwolf Date: Wed, 11 Jul 2018 14:18:59 +0800 Subject: [PATCH 14/18] fix email content --- .../zabbix/zabbix_report_email/config.ini | 2 +- .../zabbix/zabbix_report_email/report.py | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ops-scripts/zabbix/zabbix_report_email/config.ini b/ops-scripts/zabbix/zabbix_report_email/config.ini index eefa937..6cc567d 100644 --- a/ops-scripts/zabbix/zabbix_report_email/config.ini +++ b/ops-scripts/zabbix/zabbix_report_email/config.ini @@ -11,7 +11,7 @@ log_file = zbx.log #显示执行时前一天的数据,报表使用 period = 86400 width = 580 -height = 230 +height = 600 info = [{"name":"HOST1","graphids":[1304,1306,1301,1302]},{"name":"HOST2","graphids":[1296,1298,1293,1294]},{"name":"HOST3","graphids":[1247,1263,1251,1267]}] [ftp] diff --git a/ops-scripts/zabbix/zabbix_report_email/report.py b/ops-scripts/zabbix/zabbix_report_email/report.py index 57f6a5d..b6a94e4 100644 --- a/ops-scripts/zabbix/zabbix_report_email/report.py +++ b/ops-scripts/zabbix/zabbix_report_email/report.py @@ -3,7 +3,7 @@ # @Author: Geekwolf # @Date: 2018-05-07 13:26:12 # @Last Modified by: Geekwolf -# @Last Modified time: 2018-07-11 10:52:39 +# @Last Modified time: 2018-07-11 14:11:00 import ConfigParser import cookielib @@ -150,13 +150,13 @@ def WriteDoc(self, data): doc.add_paragraph(title.decode('utf8'), style='style_title') for idx, val in enumerate(data[d[1]]): #sub_title = u'内存' if idx%2 == 1 else u'CPU' - if idx >=2: - sub_title = u'内存' - else: - sub_title = u'CPU' - if idx%2 != 1: + if idx >=2: + sub_title = u'内存' + else: + sub_title = u'CPU' + if idx%2 != 1: doc.add_paragraph(sub_title, style='sub_title') - doc.add_picture(val, width=Inches(6.5), height=Inches(3)) + doc.add_picture(val, width=Inches(6.5), height=Inches(3)) file = 'report-{}.docx'.format(('').join(_time.split('-'))) doc.save(file) #如果将报表上传ftp,可以去掉注释 @@ -204,20 +204,18 @@ def SendEmail(self,_info): itemid = _info[2].split('|')[0] imagename = self.GetItemGraph(itemid) fro = config.get('email','username') - _content = (':').join(_info[3].split('|')[1:]) + _content = ('
').join(_info[3].split('\n')) content = '{}
'.format(_content) msg = MIMEMultipart() - #msg['From'] = '监控告警<{}>'.format(fro).decode('utf-8') + #msg['From'] = '监控告警<{}>'.format(fro).decode('utf-8') msg['From'] = "%s<%s>" % (Header("监控告警","utf-8"),fro) msg['Subject'] = Header((': ').join(_info[2].split('|')[-2:]),'utf-8') msg['To'] = _info[1] - msg.attach(MIMEText(content,'html','utf-8')) - - with open(imagename,'rb') as f: + msg.attach(MIMEText(content,'html','utf-8')) + with open(imagename,'rb') as f: img = MIMEImage(f.read()) img.add_header('Content-ID', '') msg.attach(img) - try: email = self.Email() email.sendmail(fro,_info[1],msg.as_string()) @@ -232,8 +230,7 @@ def main(self,_info): self.Log(rec) self.SendEmail(_info) elif len(_info) == 2 and _info[1] == 'report': - print 'tt' - self.GetGraph() + self.GetGraph() except Exception as e: self.Log(str(e)) From 847c14986c514d99f72c8bc91b8ba6175d8c9207 Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Wed, 22 Aug 2018 20:31:49 +0800 Subject: [PATCH 15/18] =?UTF-8?q?Update=20=E5=BC=80=E6=BA=90=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E4=B8=80=E8=A7=88=E8=A1=A8.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" | 1 + 1 file changed, 1 insertion(+) diff --git "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" index d5c8fc2..eb1a292 100644 --- "a/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" +++ "b/\345\274\200\346\272\220\345\267\245\345\205\267\344\270\200\350\247\210\350\241\250.md" @@ -1,6 +1,7 @@ Blog:[http://www.simlinux.com](http://www.simlinux.com)
WeiXin: Geekwolf
+##### 欢迎补充~ **Bootstrapping:** [云霁X86装机工具](http://github.com/idcos/osinstall)、Kickstart、Cobbler、rpmbuild/xen、kvm、lxc、Openstack、 Cloudstack、Opennebula、Eucalyplus、RHEV
From 537089b616821c0ec64b6f63dae111e8e9183ad1 Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Thu, 21 Nov 2019 19:33:34 +0800 Subject: [PATCH 16/18] Create lua-ngx-log.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加Lua处理Nginx日志脚本 --- ops-scripts/lua-ngx-log.md | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 ops-scripts/lua-ngx-log.md diff --git a/ops-scripts/lua-ngx-log.md b/ops-scripts/lua-ngx-log.md new file mode 100644 index 0000000..dcc142e --- /dev/null +++ b/ops-scripts/lua-ngx-log.md @@ -0,0 +1,69 @@ +#### 1. install softs + +``` +wget https://github.com/openresty/lua-nginx-module/archive/v0.10.15.tar.gz +wget http://tengine.taobao.org/download/tengine-2.3.2.tar.gz +wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1.tar.gz +wget https://openresty.org/download/openresty-1.15.8.1.tar.gz +``` +#### 2. install openrestry luajit2 lib +``` +./configure --prefix=/user/local/openresty-1.15.8.1 +make +make install +ln -s /usr/local/openresty-1.15.8.1/luajit/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2 +export LUAJIT_LIB=/usr/local/openresty-1.15.8.1/luajit/lib/ +export LUAJIT_INC=/usr/local/openresty-1.15.8.1/luajit/include/luajit-2.1/ + +``` +#### 3. install tengine + +``` +./configure --prefix=/usr/local/tengine/ --with-http_gzip_static_module --add-module=/data/data/softs/lua-nginx-module-0.10.15/ --add-module=/data/data/softs/ngx_devel_kit-0.3.1/ +make +make install +``` + +#### 4. Nginx Configure: log_by_lua_file +``` +log_by_lua_file /usr/local/tengine/conf/lua/ngx_log.lua; +``` + +### 5. ngx_log.lua +``` +local file_name = '/usr/local/tengine/logs/mapi.ipaylinks.com.audit.log' + +function write_content(fileName, content) + local f = assert(io.open(fileName,'a')) + f:write(content) + f:close() +end + +function urlDecode(s) + if(nil ~= s) + then + s = string.gsub(s, '%%(%x%x)', function(h) return string.char(tonumber(h, 16)) end) + return s + end +end + +function handle_body(s) + s = urlDecode(s) + if(nil ~= s) + then + return string.gsub(s,'payMethodInfo=(.-)&','payMethodInfo=***&') + end +end + + +local extend = string.format('srcip=%s&x_srcip=%s&time="%s"&server=%s&server_ip=%s&method=%s&link="%s://%s%s&status=%s&referer="%s"&post="%s"&user_agent="%s"',ngx.var.remote_addr,ngx.var.http_x_forwarded_for,ngx.var.time_local,ngx.var.server_name,ngx.var.server_addr,ngx.var.request_method,ngx.var.scheme,ngx.var.host,ngx.var.request_uri,ngx.var.status,ngx.var.referer,handle_body(ngx.var.request_body),ngx.var.http_user_agent) + +if(ngx.req.get_method() == 'POST') +then + write_content(file_name, extend..'\n') +end +``` +#### 6.Lua加载顺序 +``` +![avatar](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png) +``` From 920684c7695352dd57301f50ae51f1cf0c181583 Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Thu, 21 Nov 2019 19:35:59 +0800 Subject: [PATCH 17/18] Update lua-ngx-log.md --- ops-scripts/lua-ngx-log.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ops-scripts/lua-ngx-log.md b/ops-scripts/lua-ngx-log.md index dcc142e..01f0f38 100644 --- a/ops-scripts/lua-ngx-log.md +++ b/ops-scripts/lua-ngx-log.md @@ -64,6 +64,4 @@ then end ``` #### 6.Lua加载顺序 -``` -![avatar](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png) -``` +![](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png) From 910df8f67d76cf1323a1941c71bb8d2edf9ff00f Mon Sep 17 00:00:00 2001 From: Geekwolf Date: Wed, 27 Nov 2019 16:23:18 +0800 Subject: [PATCH 18/18] Update lua-ngx-log.md --- ops-scripts/lua-ngx-log.md | 60 ++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/ops-scripts/lua-ngx-log.md b/ops-scripts/lua-ngx-log.md index 01f0f38..bcbeb7f 100644 --- a/ops-scripts/lua-ngx-log.md +++ b/ops-scripts/lua-ngx-log.md @@ -31,10 +31,23 @@ log_by_lua_file /usr/local/tengine/conf/lua/ngx_log.lua; ### 5. ngx_log.lua ``` -local file_name = '/usr/local/tengine/logs/mapi.ipaylinks.com.audit.log' -function write_content(fileName, content) - local f = assert(io.open(fileName,'a')) + +-- local file_name + +-- if ngx.var.file_type == 'audit' then +-- file_name = '/usr/local/apps/nginx/logs/audit.log' +-- elseif ngx.var.file_type == 'mapi'then +-- file_name = '/usr/local/apps/nginx/logs/access.log' +-- end + + +local file_audit = '/usr/local/apps/nginx/logs/audit.log' +local file_mapi = '/usr/local/apps/nginx/logs/access.log' + + +function write_content(file_name, content) + local f = assert(io.open(file_name,'a')) f:write(content) f:close() end @@ -56,12 +69,49 @@ function handle_body(s) end -local extend = string.format('srcip=%s&x_srcip=%s&time="%s"&server=%s&server_ip=%s&method=%s&link="%s://%s%s&status=%s&referer="%s"&post="%s"&user_agent="%s"',ngx.var.remote_addr,ngx.var.http_x_forwarded_for,ngx.var.time_local,ngx.var.server_name,ngx.var.server_addr,ngx.var.request_method,ngx.var.scheme,ngx.var.host,ngx.var.request_uri,ngx.var.status,ngx.var.referer,handle_body(ngx.var.request_body),ngx.var.http_user_agent) +local remote_addr = ngx.var.remote_addr +local http_x_forwarded_for = ngx.var.http_x_forwarded_for +local time_local = ngx.var.time_local +local server_name = ngx.var.server_name +local request_method = ngx.var.request_method +local scheme = ngx.var.scheme +local host = ngx.var.host +local request_uri = ngx.var.request_uri +local status = ngx.var.status +local referer = ngx.var.referer +local body = handle_body(ngx.var.request_body) +local http_user_agent = ngx.var.http_user_agent +local upstream_status = ngx.var.upstream_status +local request_time = ngx.var.request_time +local upstream_response_time = ngx.var.upstream_response_time +local http_host = ngx.var.http_host +local scheme_http_host_request_uri = ngx.var.scheme..'://'..http_host..request_uri +local body_bytes_sent = ngx.var.body_bytes_sent +local http_referer = ngx.var.http_referer +local upstream_addr = ngx.var.upstream_addr + + + +-- if ngx.var.file_type == 'audit' then +-- local extend = string.format('srcip=%s&x_srcip=%s&time="%s"&server=%s&server_ip=%s&method=%s&link="%s://%s%s&status=%s&referer="%s"&post="%s"&user_agent="%s"',remote_addr,http_x_forwarded_for,time_local,server_name,server_addr,request_method,scheme,host,request_uri,status,referer,body,http_user_agent) +-- if(ngx.req.get_method() == 'POST') +-- then +-- write_content(file_audit, extend..'\n') +-- end +-- elseif ngx.var.file_type == 'mapi' then +-- local extend = string.format('{"upstream_status":%s,"request_time":%s,"upstream_response_time":%s,"remote_addr":%s,"time_local":%s,"scheme_http_host_request_uri":%s,"status":%s,"body_bytes_sent":%s,"http_referer":%s,"request_body":%s,"http_user_agent":%s,"http_x_forwarded_for":%s,"upstream_addr":%s}',upstream_status,request_time,upstream_response_time,remote_addr,time_local,scheme_http_host_request_uri,status,body_bytes_sent,http_referer,request_body,http_user_agent,http_x_forwarded_for,upstream_addr) +-- write_content(file_mapi, extend..'\n') +-- end + +local extend_audit = string.format('srcip=%s&x_srcip=%s&time="%s"&server=%s&server_ip=%s&method=%s&link="%s://%s%s&status=%s&referer="%s"&post="%s"&user_agent="%s"',remote_addr,http_x_forwarded_for,time_local,server_name,server_addr,request_method,scheme,host,request_uri,status,referer,body,http_user_agent) +local extend_mapi = string.format('{"upstream_status":%s,"request_time":%s,"upstream_response_time":%s,"remote_addr":%s,"time_local":%s,"scheme_http_host_request_uri":%s,"status":%s,"body_bytes_sent":%s,"http_referer":%s,"request_body":%s,"http_user_agent":%s,"http_x_forwarded_for":%s,"upstream_addr":%s}',upstream_status,request_time,upstream_response_time,remote_addr,time_local,scheme_http_host_request_uri,status,body_bytes_sent,http_referer,request_body,http_user_agent,http_x_forwarded_for,upstream_addr) if(ngx.req.get_method() == 'POST') then - write_content(file_name, extend..'\n') + write_content(file_audit, extend_audit..'\n') end +write_content(file_mapi, extend_mapi..'\n') + ``` #### 6.Lua加载顺序 ![](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png)