本想用fabric(paramiko的作者开发的),发现新老版本存在较大的不同,所以放弃了。还是用paramiko + sh脚本的方式。
远程主机已经设置好了免密登录,连接方式为直连或者frp透传。
可以使用一个conf文件记录自己的主机记录(因为使用秘钥,所以里面没有密码,相对还算安全)
machines.conf
[m1]
name = 大机器
host_ip = 111.222.333.444
user = root
port = 111[m2]
name = 小机器
host_ip = 111.222.333.444
user = root
port = 222
配置文件会被读入为有序字典,然后就可以用了。
import DataManipulation as dm
res = dm.read_conf('machines.conf')
paramiko可以指定一个路径来读取秘钥。
paramikoe执行命令后会返回stdin, stdout, stderr
三项,我们可以只取stdout。
# 执行远程主机命令
def remote_ssh_exe_cmd(ssh_key_fpath=None, machine_conf_fpath=None, machine_code=None, cmd_line=None):# 私钥private_key = paramiko.RSAKey.from_private_key_file(ssh_key_fpath)# 机器配置res = dm.read_conf(machine_conf_fpath)client = paramiko.SSHClient()client.set_missing_host_key_policy(paramiko.AutoAddPolicy())hostname = res[machine_code]['host_ip']username = res[machine_code]['user']port = res[machine_code]['port']try:client.connect(hostname=hostname, port=port,username=username, pkey=private_key)stdin, stdout, stderr = client.exec_command(cmd_line)try:# print('a')cmd_result = stdout.read().decode('utf-8')# print(cmd_result)except:# print('b')cmd_result = Noneexcept:print('remote_ssh_exe_cmd error')cmd_result = Nonefinally:client.close()return cmd_result
一般的调用方法如下:
input_dict = {}
input_dict['ssh_key_fpath'] = '私钥地址'
input_dict['machine_conf_fpath'] = './machines.conf'
input_dict['machine_code'] = 'm1'
input_dict['cmd_line'] = 'df -h'res = remote_ssh_exe_cmd(**input_dict)
假设现在已经可以python程序执行远程操作了,现在我们来做一些常见操作
In [24]: input_dict = {}...: input_dict['ssh_key_fpath'] = 'YOURS/.ssh/id_rsa'...: input_dict['machine_conf_fpath'] = './machines.conf'...: input_dict['machine_code'] = 'm1'...: input_dict['cmd_line'] = 'hostname'...:...: print(dm.remote_ssh_exe_cmd(**input_dict)[1])
andy-Z97X-UD3H
input_dict['cmd_line'] = '''ifconfig -a'''
print(dm.remote_ssh_exe_cmd(**input_dict)[1])
---
In [27]: # 内存...: input_dict['cmd_line'] = '''free -m'''...: print(dm.remote_ssh_exe_cmd(**input_dict)[1])total used free shared buff/cache available
Mem: 32026 6293 15983 213 9749 24910
Swap: 57251 0 57251
In [28]: # cpu...: input_dict['cmd_line'] = '''cat /proc/cpuinfo'''...: print(dm.remote_ssh_exe_cmd(**input_dict)[1])
processor : 0
vendor_id : GenuineIntel
cpu family : 6
In [29]: # cpu个数...: input_dict['cmd_line'] = '''cat /proc/cpuinfo |grep "processor"|wc -l '''...: print(dm.remote_ssh_exe_cmd(**input_dict)[1])
8
In [30]: # 磁盘占用...: input_dict['cmd_line'] = '''df -h'''...: print(dm.remote_ssh_exe_cmd(**input_dict)[1])
文件系统 容量 已用 可用 已用% 挂载点
udev 16G 0 16G 0% /dev
tmpfs 3.2G 66M 3.1G 3% /run
In [31]: # cpu温度...: input_dict['cmd_line'] = '''sensors'''...: print(dm.remote_ssh_exe_cmd(**input_dict)[1])
acpitz-virtual-0
Adapter: Virtual device
temp1: +27.8°C (crit = +105.0°C)
temp2: +29.8°C (crit = +105.0°C)coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +69.0°C (high = +80.0°C, crit = +100.0°C)
Core 0: +67.0°C (high = +80.0°C, crit = +100.0°C)
Core 1: +69.0°C (high = +80.0°C, crit = +100.0°C)
Core 2: +67.0°C (high = +80.0°C, crit = +100.0°C)
Core 3: +65.0°C (high = +80.0°C, crit = +100.0°C)
在m1主机建立/tmp/test.txt
hello test
在Macbook上(m0)执行
import DataManipulation as dminput_dict = {}
input_dict['ssh_key_fpath'] = 'YOURS/.ssh/id_rsa'
input_dict['machine_conf_fpath'] = './machines.conf'
input_dict['machine_code'] = 'm1'
input_dict['cmd_line'] = 'cat /tmp/test.txt'print(dm.remote_ssh_exe_cmd(**input_dict)[1])
---
In [12]: run test4_file.py
hello test
m0端
# 建立副本
input_dict['cmd_line'] = 'cp /tmp/test.txt /tmp/test.txt.bak_1234'
print(dm.remote_ssh_exe_cmd(**input_dict)[1])
m1端
┌─root@andy-Z97X-UD3H:/tmp
└─ $ ls
test.txt
test.txt.bak_1234
---
┌─root@andy-Z97X-UD3H:/tmp
└─ $ cat test.txt.bak_1234
hello test
在m0端执行
# 覆盖文件内容
new_content='hello new content'
input_dict['cmd_line'] = 'echo "%s">/tmp/test.txt' %new_content
print(dm.remote_ssh_exe_cmd(**input_dict)[1])
在m1端执行
┌─root@andy-Z97X-UD3H:/tmp
└─ $ cat test.txt
hello new content
在m0端执行
## 追加写入内容
new_content = 'hello new content 1'
input_dict['cmd_line'] = 'echo "%s">>/tmp/test.txt' % new_content
print(dm.remote_ssh_exe_cmd(**input_dict)[1])
在m1端执行
┌─root@andy-Z97X-UD3H:/tmp
└─ $ cat test.txt
hello new content
hello new content 1
m0端执行
## 注释第二行
input_dict['cmd_line'] = 'cat /tmp/test.txt'
# 1.先读取文件。使用换行符分割,末尾会多出一行
original_content = dm.remote_ssh_exe_cmd(**input_dict)[1].split('\n')[:-1]
original_content[1] = '#' + original_content[1]
# 2 再覆盖写回文件
input_dict['cmd_line'] = 'echo "%s">/tmp/test.txt' % '\n'.join(original_content)
print(dm.remote_ssh_exe_cmd(**input_dict)[1])
m1端执行
┌─root@andy-Z97X-UD3H:/tmp
└─ $ cat test.txt
hello new content
#hello new content 1
什么是scp?scp是一种基于SSH的协议,可在网络上的主机之间提供文件传输。 使用scp,您可以在主机之间快速传输文件以及基本文件属性,例如访问权限和通过FTP无法可用的时间戳。
文件的传输我使用了scp,比sftp应该更好一些,详情可以参考这篇文章
scp在paramiko里面的支持也有点怪,不想用paramiko实现这个功能。突然发现我似乎也没有必要用paramiko这个包,因为大部分的命令是确知的、简单的命令。
# scp方法从远程获得文件
import os
def scp_get_file_from_remote(machine_conf_fpath = None,machine_code =None, remote_absfpath = None, local_absfpath=None ,mode='file'):assert all([machine_code,remote_absfpath, local_absfpath]),'机器号、远程文件地址和本地文件地址不为空'if mode.lower() =='file':# 如果是文件opt = ''else:opt = '-r'# 从机器配置文件里获取信息并组成用户+主机的前缀machine_conf = dm.read_conf(machine_conf_fpath)## 从远端获取文件到本地remote_port = machine_conf[machine_code]['port']remote_host = machine_conf[machine_code]['user'] +'@'+ machine_conf[machine_code]['host_ip']remote_fpath = remote_host + ':' + remote_absfpathscp_cmd_line = 'scp -P{0} {1} {2} {3}'.format(remote_port, opt, remote_fpath, local_absfpath)return os.system(scp_cmd_line)scp_input_dict = {}
scp_input_dict['machine_conf_fpath'] = './machines.conf'
scp_input_dict['machine_code'] = 'm1'
scp_input_dict['remote_absfpath'] = '/tmp/test.txt'
scp_input_dict['local_absfpath'] = '/YOURPATH/test_xxx1.txt'scp_get_file_from_remote(**scp_input_dict)
---
如果要拷贝文件夹的化,就在入参的地方把mode改为folder(其实不为file就可以)
从本地传到远端的几乎一样,就不写了。
既然是自建算网,那么我可以设定一台机器作为数据中转中心(NAS)。
我发现反而需要做的只是:为需要通信的主机生成秘钥对,然后分发到对应主机的ssh文件下面就可以了(authorized_keys)。这样任何一台主机需要数据就可以直接获取。
接下来就把这些操作封装到DM里面就可以方便调用了。
使用sh命令操作是一个很需要小心的事情(一不小心就崩溃了),一方面我们将操作简化和固定化;另一方面需要mongo记录操作日志,使用opr_id作为记号(操作前进行backup,命名为backup_before_opr_xxx),这样万一搞砸了还有一线机会恢复(例如写一个roll_back_by_checkpoint)。
关于python操作mongo的介绍可以看我这篇文章。
以下是个例子,这里用with的方法是为了避免操作打开太多数据库连接(存完数据就释放):
import pymongo
import DataManipulation as dm
import time
with pymongo.MongoClient('mongodb://localhost:27017/') as conn:db = conn['andylog']colt = db['log1']log_dict = {}log_dict['operator'] = 'andy'log_dict['opr_id'] = 'op001'log_dict['create_time'] = dm.get_curdatetime_str()log_dict['event'] = 'cat file'log_dict['cmd_line'] = 'cat /tmp/test.txt'log_dict['ts'] = time.time()colt.insert_one(log_dict)
数据库里的变化:
> db.log1.find()
{ "_id" : ObjectId("5fab5975e1dbe95ebf0444b6"), "operator" : "andy", "opr_id" : "op001", "create_time" : "2020-11-11 11:24:37", "event" : "cat file", "cmd_line" : "cat /tmp/test.txt", "ts" : 1605065077.215517 }
>
关于opr_id: