При написании сценариев развертывания неизбежно потребуется выполнение некоторых удаленных команд или передача файлов.
использовал раньшеshбиблиотеку, вызовите sh.ssh для удаленного выполнения некоторых команд и sh.scp для передачи файлов, но в реальном использовании это довольно проблематично Просто для имитации входа пользователя вам нужно определить метод для имитации ввода отдельно.
Почувствуй это:
from sh import ssh
PASS = 'xxxx'
def ssh_interact(line, stdin):
line = line.strip()
print(line)
if line.endswith('password:'):
stdin.put(PASS)
ssh('x.x.x.x', _out=ssh_interact)
Позже нашелparamikoБиблиотека более элегантная и удобная, поэтому я заменю sh на pramiko.
До этого я узнал от своих коллег, что когда paramiko удаленно выполняет скрипт Python, выходное содержимое скрипта может быть выведено через конвейер stderr, поэтому напрямую используйте paramikoSSHClientв классеexec_commandВыполнение метода, невозможно судить об успешном выполнении команды, читая, есть ли вывод в канале stderr. Поэтому используйте более низкий уровеньChannelКатегорияrecv_exit_statusМетод лучше оценивает код выхода выполнения.
Установить
можно сделать с помощьюpip install paramiko
Установка, подробности здесь не повторяются.
упаковка
Сначала определите несколько исключений
# coding: utf-8
import os.path
from paramiko import SSHClient, AutoAddPolicy, AuthenticationException
class ConnectError(Exception):
"""
连接错误时抛出的异常
"""
pass
class RemoteExecError(Exception):
"""
远程执行命令,失败时抛出的异常
"""
pass
class SCPError(Exception):
"""
远程下发文件时抛出的异常
"""
pass
...
class Remote(object):
def __init__(self, host, username, password=None, port=22, key_filename=None):
self.host = host
self.username = username
self.password = password
self.port = port
self.key_filename = key_filename
self._ssh = None
def _connect(self):
self._ssh = SSHClient()
self._ssh.set_missing_host_key_policy(AutoAddPolicy())
try:
if self.key_filename:
self._ssh.connect(self.host, username=self.username, port=self.port, key_filename=self.key_filename)
else:
self._ssh.connect(self.host, username=self.username, password=self.password, port=self.port)
except AuthenticationException:
self._ssh = None
raise ConnectionError('连接失败,请确认用户名、密码、端口或密钥文件是否有效')
except Exception as e:
self._ssh = None
raise ConnectionError('连接时出现意料外的错误:%s' % e)
def get_ssh(self):
if not self._ssh:
self._connect()
return self._ssh
создавать экземплярSSHClient
класс, через егоconnect()
способ получить SSH-соединение.
Следует отметить, что если узел удаленного доступа подключается в первый раз, это неизвестное устройство, и его необходимо аутентифицировать.set_missing_host_key_policy()
метод установки стратегии, который используется здесьAutoAddPolicy()
.
здесь_connect
Есть два способа войти в систему: один — указать имя пользователя и пароль хоста, а другой — использовать файл ключа. При подключении проверьте, указан ли ключевой файл, затем авторизуйтесь таким образом, в противном случае авторизуйтесь под логином и паролем.
_connect()
Хотя это фактический метод установления соединения, фактический внешний интерфейсget_ssh()
, если уже установлено SSH-соединение, верните его напрямую, чтобы избежать повторного установления соединения.
class Remote(object):
...
def ssh(self, cmd, root_password=None, get_pty=False, super=False):
cmd = self._prepare_cmd(cmd, root_password, super)
stdout = self._exec(cmd, get_pty)
return stdout
def _prepare_cmd(self, cmd, root_password=None, super=False):
if self.username != 'root' and super:
if root_password:
cmd = "echo '{}'|su - root -c '{}'".format(root_password, cmd)
else:
cmd = "echo '{}'|sudo -p '' -S su - root -c '{}'".format(self.password, cmd)
return cmd
def _exec(self, cmd, gty_pty=False):
channel = self.get_ssh().get_transport().open_session()
if get_pty:
channel.get_pty()
channel.exec_command(cmd)
stdout = channel.makefile('r', -1).readlines()
stderr = channel.makefile_stderr('r', -1).readlines()
ret_code = channel.recv_exit_status()
if ret_code:
msg = ''.join(stderr) if stderr else ''.join(stdout)
raise RemoteExecError(msg)
return stdout
При удаленном выполнении некоторых команд могут потребоваться права администратора.В этом случае необходимо принять некоторые решения.Во-первых, если имя пользователя, указанное при входе в систему, не является root, вам необходимо внести некоторые изменения в команды. Здесь есть два случая для модификации.sudo
Разрешения, нужно только добавить исполняемую команду в sudo и выполнить ее, а другое, что у обычных пользователей нетsudo
разрешение, которое необходимо пройти черезsu
переключиться наroot
Выполнить после идентификации, в этом случае необходимо предоставитьroot
пароль.
Еще одна вещь, которую следует отметить, это то, чтоget_pty
Этот параметр фактически выполняется удаленноsudo
команда, общий хост должен будет пройтиtty
можно выполнить, поставивget_pty
установлено значениеTrue
, который может имитироватьtty
, но также будет проблема, если это удаленное выполнение процесса, который должен выполняться в течение длительного времени, например запускnginx
Служба, при выходе из SSH после выполнения удаленной команды все программы, запущенные в это время, также завершатся, поэтому, когда вам нужно запустить какие-то службы или программы через удаленные команды, указать нельзяget_pty
параметры; но в то же время, если обычный пользователь входит удаленно, у него нет прав на выполнениеservice
заказал. Один из предлагаемых способов - изменить/etc/sudoers
конфигурационный файл, закомментироватьDefaults requiretty
эта линия.
class Remote(object):
...
def scp(self, local_file, remote_path):
if not os.path.exists(local_file):
raise SCPError("Local %s isn't exists" % local_file)
if not os.path.isfile(local_file):
raise SCPError("%s is not a File" % local_file)
sftp = self.get_ssh().open_sftp()
try:
sftp.put(local_file, remote_path)
except Exception as e:
raise SCPError(e)
Сначала подтвердите, что файл, который нужно доставить, существует и не является каталогом. В противном случае будет выдано исключение. в то же время,remote_path
Должен быть абсолютным каталогом файла на удаленном хосте, например./tmp/xxx.xxx
, вместо/tmp
.
использовать
# coding: utf-8
from remote_client import RemoteClient
rc = RemoteClient('10.1.100.1', 'test', 'test_pass')
rc.ssh('whoami') # [u'test\n']
rc.scp('/tmp/test.out', '/tmp/test.out')
Суммировать
По сравнению сsh
,paramiko
Это не полуторная звезда, которую легко использовать, здесь просто простой пакет,paramiko
Само по себе есть много других применений, и вы можете активно их обсуждать.
Вышеупомянутое только мое понимание.Если есть какие-либо ошибки, пожалуйста, поправьте меня.