Используйте paramiko для удаленного выполнения команд и создания файлов

Python SSH

При написании сценариев развертывания неизбежно потребуется выполнение некоторых удаленных команд или передача файлов.

использовал раньше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Само по себе есть много других применений, и вы можете активно их обсуждать.

Вышеупомянутое только мое понимание.Если есть какие-либо ошибки, пожалуйста, поправьте меня.