背景
運維windows服務器的同學都知道,windows服務器進行批量管理的時候非常麻煩,沒有比較順手的工具,雖然saltstack和ansible都能夠支持windows操作,但是使用起來總感覺不太舒服,比如ansible使用的winrm進行遠程操作,需要提前在windows上進行設置,並且要求操作系統版本和powershell版本,根據個人使用經驗,經常存在鏈接不上,連接慢,或者推送失敗的問題,較高的失敗率對於自動化運維體系的建立不利,所以與其受制於人,不如自己動手寫一個agent,完全自己控制。
技術平台
服務器:windows服務器,測試過win2008r2、win2012、win7和win10.其他版本不知道怎么樣。
開發語言:python
python模塊:pywin32(python中的windows庫),pyinstaller(用於將py文件生成exe程序),flask(web服務器)。以上三個模塊都可以通過pip安裝。以及一些python內置模塊
優勢:完全自己控制,根據自己需要開發功能。服務器端不需要安裝任何其他依賴(不需要安裝python環境,exe自帶運行環境)
流程結構
代碼實現
agent的實現代碼如下,代碼有詳細的注釋,就不解釋了。

1 # -*- coding:utf-8 -*- 2 import win32serviceutil 3 import win32service 4 import win32event 5 from flask import Flask 6 from flask import request 7 import sys 8 import os 9 import zipfile 10 import requests 11 import shutil 12 import re 13 import time 14 #設置編碼 15 reload(sys) 16 sys.setdefaultencoding('utf-8') 17 ################################################################ 18 ##########自定義函數區########################################## 19 ################################################################ 20 #下載文件函數 21 def get_url_file(url): 22 #下載到臨時目錄,如果臨時目錄不存在就創建一個 23 download_path = "D:\\tmp\\%s" % (time.time()) 24 if os.path.exists(download_path): 25 pass 26 else: 27 os.makedirs(download_path) 28 r = requests.get(url, stream=True) 29 #解析文件名,如url為:http://www.ftp.com/test.zip那么文件名就是test.zip,並下載和寫入文件 30 filename = "%s\\%s" % (download_path, url.split('/')[-1]) 31 with open(filename, "wb") as pdf: 32 for chunk in r.iter_content(chunk_size=1024): 33 if chunk: 34 pdf.write(chunk) 35 return {'filename':filename,'download_path':download_path} 36 37 38 def config_zabbix(hostname): 39 #復制一份C:\\zabbix\\zabbix_agentd_win.conf.bak的zabbix模板文件,並修改其中的Hostname=new為新的hostname,然后生成新的配置文件。 40 try: 41 open('C:\\zabbix\\zabbix_agentd_win.conf', 'w').write(re.sub(r'Hostname=new', 'Hostname=%s' % hostname, open('C:\\zabbix\\zabbix_agentd_win.conf.bak').read())) 42 except: 43 return "error" 44 # 重啟zabbix agent服務 45 service_list = os.popen('net start').read() 46 if service_list.find('Zabbix Agent') == -1: 47 try: 48 os.system('net start "Zabbix Agent"') 49 except: 50 return "error" 51 else: 52 try: 53 os.system('net stop "Zabbix Agent') 54 except: 55 return "error" 56 try: 57 os.system('net start "Zabbix Agent') 58 except: 59 return "error" 60 return "ok" 61 62 63 # 加壓縮文件函數 64 def un_zip(file_name,dest_path): 65 zip_file = zipfile.ZipFile(file_name) 66 file_pre = file_name.split('\\')[-1].split('.')[0] 67 for names in zip_file.namelist(): 68 zip_file.extract(names, dest_path) 69 zip_file.close() 70 return (dest_path + '\\' + file_pre) 71 ################################################################### 72 #############自定義函數區結束###################################### 73 ################################################################### 74 75 #windows服務中顯示的名字 76 class zlsService(win32serviceutil.ServiceFramework): 77 _svc_name_ = 'zls_agent' ###可以根據自己喜好修改 78 _svc_display_name_ = 'zls_agent' ###可以根據自己喜好修改 79 _svc_description_ = 'zls_agent' ###可以根據自己喜好修改 80 81 82 def __init__(self,args): 83 win32serviceutil.ServiceFramework.__init__(self,args) 84 self.stop_event = win32event.CreateEvent(None, 0, 0, None) 85 self.run = True 86 87 def SvcDoRun(self): 88 app = Flask(__name__) 89 ############################################################## 90 #########flask路由設置區,自定義功能也放在這里################ 91 ############################################################## 92 #推送文件服務,比如傳入一個url和目標地址,裝有本agent的客戶端就會下載這個url並把文件放在指定位置 93 @app.route('/transferfile', methods=['GET', 'POST']) 94 def transferfile(): 95 if request.method == "GET": 96 url = request.args.get('url') 97 dest = request.args.get('dest') 98 try: 99 down_data = get_url_file(url) 100 filename = down_data['filename'] 101 download_path = down_data['download_path'] 102 except BaseException, e: 103 return "download url file error : %s" % e 104 # 加壓縮文件 105 try: 106 filepath = un_zip(filename,download_path) 107 except BaseException, e: 108 return "un zip error : %s" % e 109 # 刪除壓縮文件 110 try: 111 os.remove(filename) 112 except: 113 pass 114 # 復制文件內容到指定目錄 115 try: 116 shutil.copytree('%s' % filepath.replace('\\', '\\\\'), '%s' % dest) 117 except BaseException, e: 118 return "copy file error: %s" % e 119 # d刪除解壓縮文件夾 120 try: 121 shutil.rmtree(filepath.replace('\\', '\\\\')) 122 except: 123 pass 124 return "ok" 125 #修改配置文件路由,用來遠程修改zabbix配置文件,比如curl http://server_ip:50000/zabbix?hostname="zabbix_agent"就會把位於服務器上的zabbix_agentd配置文件中的Hostname修改為Hostname=zabbix_agent。 126 @app.route('/zabbix', methods=['GET', 'POST']) 127 def config_zabbix_func(): 128 if request.method == "GET": 129 hostname = request.args.get('hostname') 130 result = config_zabbix(hostname) 131 return result 132 #測試路由,用來測試agent是否正在運行,返回ok表示正在運行 133 @app.route('/', methods=['GET', 'POST']) 134 def test(): 135 if request.method == "GET": 136 return "ok" 137 #使用flask自帶的web服務器,監聽本地所有地址的50000端口 138 ############################################################## 139 #############自定義功能區結束################################# 140 ############################################################## 141 app.run(host='0.0.0.0', port=50000) 142 def SvcStop(self): 143 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 144 win32event.SetEvent(self.stop_event) 145 self.ReportServiceStatus(win32service.SERVICE_STOPPED) 146 self.run = False 147 148 if __name__ == '__main__': 149 import sys 150 import servicemanager 151 if len(sys.argv) == 1: 152 try: 153 evtsrc_dll = os.path.abspath(servicemanager.__file__) 154 servicemanager.PrepareToHostSingle(zlsService) #如果修改過名字,名字要統一 155 servicemanager.Initialize('zlsService',evtsrc_dll) #如果修改過名字,名字要統一 156 servicemanager.StartServiceCtrlDispatcher() 157 except win32service.error as details: 158 import winerror 159 if details == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: 160 win32serviceutil.usage() 161 else: 162 win32serviceutil.HandleCommandLine(zlsService) #如果修改過名字,名字要統一
生成exe程序
安裝完成pyinstaller以后,打開命令行窗口
輸入:pyinstaller -F windows_service.py
說明:-F表示生成一個單一的exe文件。其他選項使用pyinstaller --help查看。
執行完成后,提示生成exe程序
找到這個程序
將程序復制到測試服務器上,安裝
以管理員身份打開cmd,中輸入 windows_servcie.exe install
提示安裝成功
啟動服務,然后設置成自動啟動
測試,瀏覽器中訪問IP:50000端口,返回ok表示agent運行正常
后續:
想要卸載服務,可以使用
重要說明:
以上的測試,均在測試環境中進行,不要用在正式環境,因為這個agent沒有加密通信,存在被人攻擊的危險!!!
實際使用過程中,一定要對通信進行加密,可以使用https或者在命令中加入token,對token進行認證,只有正確的token才執行命令,或者使用rsa證書加密,這里就不再舉例。
此agent只是給了三個示例性應用,實際用的時候,可以根據自己需要在函數區和路由定義區加入自己的程序,比如把這個agent做成監控系統,獲取本地信息穿給服務端,在此拋磚引玉,剩下的就看自己的發揮了。