亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

用Python/ target=_blank class=infotextkey>Python進行遠程登陸服務器

這篇文章介紹如何通過使用Paramiko和SCP Python庫自動化遠程服務器任務。使用Python來SSH到主機,執行任務,傳輸文件等。

paramiko和scp是兩個Python庫,我們可以一起使用它們來自動化我們想要在遠程主機上運行的任務,比如重新啟動服務、進行更新或獲取日志文件。

設置SSH密鑰

要驗證SSH連接,我們需要設置一個私有的RSA SSH密鑰(不要與OpenSSH混淆)。我們可以使用以下命令生成密鑰:

$ ssh-keygen -t rsa

這將提示我們為密鑰提供一個名稱。隨便你怎么說:

Generating a public/private rsa key pair. Enter the file in which you wish to save they key (i.e., /home/username/.ssh/id_rsa):

接下來,系統將提示您提供一個密碼(不必填寫)。

現在我們有了密鑰,我們需要將其復制到遠程主機。最簡單的方法是使用ssh-copy-id:

$ ssh-copy-id -i ~/.ssh/mykey username@my_remote_host.org

 

驗證SSH密鑰

如果你想檢查你已經有哪些密鑰,這些可以在你的系統的.ssh目錄中找到:

$ cd ~/.ssh

我們正在尋找以以下頭文件開頭的鍵:

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
巧用Python登陸遠程服務器

 

構造腳本

讓我們安裝庫。

$ pip3 install paramiko scp

在我們編寫一些有意義的Python代碼之前,還有一件事要做!創建一個配置文件來保存連接到主機所需的變量。下面是我們進入服務器所需要的基本內容:

  • Host:我們試圖訪問的遠程主機的IP地址或URL。
  • Username:這是您用于SSH到服務器的用戶名。
  • Passphrase(可選):如果您在創建ssh密鑰時指定了一個Passphrase,請在這里指定。請記住,您的SSH密鑰密碼短語與您的用戶密碼不同。
  • SSH Key:我們前面創建的密鑰的文件路徑。在OSX上,它們存在于系統的~/.ssh文件夾。我們目標的SSH密鑰必須有一個附帶的密鑰,文件擴展名為.pub。這是我們的公鑰;如果您遵循前面的步驟,那么應該已經為您生成了這個文件。

如果你試圖從遠程主機上傳或下載文件,你需要包含兩個額外的變量:

  • Remote Path:文件傳輸目標的遠程目錄的路徑。我們可以上傳東西到這個文件夾或者下載它的內容。
  • Local Path:與上述想法相同,但相反。為了方便起見,我們將使用的本地路徑是簡單的/data,并包含可愛的狐貍gif的圖片。

現在我們有了創建一個config.py文件所需的一切:

"""Remote host configuration."""
from os import environ, path
from dotenv import load_dotenv
# Load environment variables from .env
basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, '.env'))
# Read environment variables
host = environ.get('REMOTE_HOST')
user = environ.get('REMOTE_USERNAME')
ssh_key_filepath = environ.get('SSH_KEY')
remote_path = environ.get('REMOTE_PATH')
local_file_directory = 'data'

新建SSH客戶端

我們將創建一個名為RemoteClient的類來處理與遠程主機的交互。在我們搞得太復雜之前,讓我們先用config.py中創建的變量實例化RemoteClient類:

"""Client to handle connections and actions executed against a remote host."""
class RemoteClient:
    """Client to interact with a remote host via SSH & SCP."""
    def __init__(self, host, user, ssh_key_filepath, remote_path):
        self.host = host
        self.user = user
        self.ssh_key_filepath = ssh_key_filepath
        self.remote_path = remote_path

 

到目前為止還沒有什么令人印象深刻的:我們只是設置了一些變量,并將它們傳遞到一個無用的類中。讓我們在不離開構造函數的情況下進一步討論:

 

"""Client to handle connections and actions executed against a remote host."""
from paramiko import SSHClient, AutoAddPolicy, RSAKey
from paramiko.auth_handler import AuthenticationException, SSHException
class RemoteClient:
    """Client to interact with a remote host via SSH & SCP."""
    def __init__(self, host, user, ssh_key_filepath, remote_path):
        self.host = host
        self.user = user
        self.ssh_key_filepath = ssh_key_filepath
        self.remote_path = remote_path
        self.client = None
        self.scp = None
        self.conn = None
        self._upload_ssh_key()

我們已經添加了三個新東西來實例化我們的類:

self.client = None: self.Client最終將在我們的類中充當連接對象,類似于處理數據庫庫中的conn等術語。在顯式連接到遠程主機之前,我們的連接將為None。

self.scp = None與self.client相同,但專門處理傳輸文件的連接。

Self._upload_ssh_key()不是一個變量,而是一個在客戶機實例化時自動運行的函數。調用_upload_ssh_key()是告訴我們的RemoteClient對象在創建時立即檢查本地ssh密鑰,以便我們可以嘗試將它們傳遞到遠程主機。否則,我們根本無法建立聯系。

巧用Python登陸遠程服務器

 

上傳SSH密鑰到遠程主機

RemoteClient將從兩個私有方法開始:_get_ssh_key()和_upload_ssh_key()。前者將獲取本地存儲的公鑰,如果成功,后者將把這個公鑰傳遞給我們的遠程主機,作為訪問的橄欖枝。一旦本地創建的公鑰存在于遠程機器上,該機器將永遠信任我們的連接請求:不需要密碼。我們將在此過程中包括適當的日志記錄,以防我們遇到任何麻煩:

"""Client to handle connections and actions executed against a remote host."""
from os import system
from paramiko import SSHClient, AutoAddPolicy, RSAKey
from paramiko.auth_handler import AuthenticationException, SSHException
from scp import SCPClient, SCPException
from .log import logger
class RemoteClient:
    """Client to interact with a remote host via SSH & SCP."""
    ...
    def _get_ssh_key(self):
        """
        Fetch locally stored SSH key.
        """
        try:
            self.ssh_key = RSAKey.from_private_key_file(self.ssh_key_filepath)
            logger.info(f'Found SSH key at self {self.ssh_key_filepath}')
        except SSHException as error:
            logger.error(error)
        return self.ssh_key
    def _upload_ssh_key(self):
        try:
            system(f'ssh-copy-id -i {self.ssh_key_filepath} {self.user}@{self.host}>/dev/null 2>&1')
            system(f'ssh-copy-id -i {self.ssh_key_filepath}.pub {self.user}@{self.host}>/dev/null 2>&1')
            logger.info(f'{self.ssh_key_filepath} uploaded to {self.host}')
        except FileNotFoundError as error:
            logger.error(error)

 

_get_ssh_key()非常簡單:它驗證SSH密鑰是否存在于我們在配置中指定的用于連接到主機的路徑上。如果該文件確實存在,我們很樂意設置self.ssh_key變量,這樣我們的客戶端就可以上傳和使用這個密鑰了。Paramiko為我們提供了一個名為RSAKey的子模塊,可以輕松處理所有與RSA密鑰相關的事情,比如將一個私鑰文件解析為一個可用的連接身份驗證。這就是我們得到的:

RSAKey.from_private_key_file(self.ssh_key_filepath)

如果我們的RSA密鑰是不可理解的廢話,而不是真正的密鑰,Paramiko的SSHException會捕捉到這一點,并在解釋這一點之前就引發一個異常。正確地利用庫的錯誤處理需要對“哪里出了問題”進行大量猜測,特別是在某些情況下,比如在一個我們都不會經常搞混的小空間中,可能存在許多未知的情況。

巧用Python登陸遠程服務器

 

連接到客戶端

我們將在客戶機中添加一個名為connect()的方法來處理到主機的連接:

...
class RemoteClient:
    """Client to interact with a remote host via SSH & SCP."""
    ...
    def _connect(self):
        """Open connection to remote host."""
        if self.conn is None:
            try:
                self.client = SSHClient()
                self.client.load_system_host_keys()
                self.client.set_missing_host_key_policy(
                    AutoAddPolicy()
                )
                self.client.connect(
                    self.host,
                    username=self.user,
                    key_filename=self.ssh_key_filepath,
                    look_for_keys=True,
                    timeout=5000
                )
                self.scp = SCPClient(self.client.get_transport())
            except AuthenticationException as error:
                logger.error(f'Authentication failed: 
                    did you remember to create an SSH key? {error}')
                raise error
        return self.client

讓我們來分析一下:

  • SSHClient()為創建代表SSH客戶機的對象奠定了基礎。以下幾行將配置此對象,使其更有用。
  • load_system_host_keys()指示客戶機查找我們過去連接過的所有主機,方法是查看系統的known_hosts文件并找到主機所期望的SSH密鑰。我們過去從未連接到我們的主機,所以我們需要顯式地指定SSH密鑰。
  • set_missing_host_key_policy()告訴Paramiko在出現未知密鑰對時該怎么做。這需要Paramiko內置一個“策略”,我們將具體到AutoAddPolicy()。將我們的策略設置為“自動添加”意味著如果我們試圖連接到一個無法識別的主機,Paramiko將自動在本地添加丟失的密鑰。
  • connect()是SSHClient最重要的方法(正如您可能想象的那樣)。我們終于能夠傳遞我們的主機、用戶和SSH密鑰來實現我們一直在等待的東西:到我們的服務器的一個漂亮的SSH連接!connect()方法也通過大量可選關鍵字參數數組提供了極大的靈活性。我碰巧在這里傳遞了一些:將look_for_keys設置為True將允許Paramiko在~/中查看。ssh文件夾發現自己的ssh密鑰,設置超時將自動關閉我們可能忘記關閉的連接。如果選擇以這種方式連接到主機,我們甚至可以傳遞端口和密碼等變量。

 

斷開連接

在使用完遠程主機后,我們應該關閉與遠程主機的連接。不這樣做不一定是災難性的,但是我遇到過一些實例,其中足夠的掛起連接最終會使端口22的入站流量達到最大。不管您的用例是否認為重啟是一場災難或輕微的不便,讓我們像成年人一樣關閉我們該死的連接,就像我們在排便后擦屁股一樣。不管您的連接環境如何,我提倡設置一個超時變量(如前所述)。無論如何。瞧:

class RemoteClient:
    ...
    def disconnect(self):
        """Close ssh connection."""
        if self.client:
            self.client.close()
        if self.scp:
            self.scp.close()

有趣的事實:設置self.client.close()實際上設置self。將client設置為等于None,這在您可能希望檢查連接是否已經打開的情況下非常有用。

執行Unix命令

我們現在有了一個很棒的Python類,它可以找到RSA密鑰、連接和斷開連接。它確實缺乏做任何有用的事情的能力。

我們可以修復這個問題,并最終開始使用一個全新的方法來執行命令,我將適當地將其命名為execute_commands()(正確地說,“命令”可能不止一個,我們稍后將討論這個問題)。所有這些工作都是由Paramiko客戶端內置的exec_command()方法完成的,它接受一個字符串作為命令并執行它:

 

class RemoteClient:
    ...
    def execute_commands(self, commands):
        """
        Execute multiple commands in succession.
        :param commands: List of unix commands as strings.
        :type commands: List[str]
        """
        self.conn = self._connect()
        for cmd in commands:
            stdin, stdout, stderr = self.client.exec_command(cmd)
            stdout.channel.recv_exit_status()
            response = stdout.readlines()
            for line in response:
                logger.info(f'INPUT: {cmd} | OUTPUT: {line}')

 

我們剛剛創建的函數execute_commands()期望一個字符串列表作為命令執行。這部分是為了方便,但也因為Paramiko不會在命令之間運行任何“狀態”更改(比如更改目錄),所以我們傳遞給Paramiko的每個命令都應該假定我們是在服務器的根目錄下工作的。我冒昧地說出了這樣三條命令:

remote.execute_commands(['cd /var/www/ && ls',
                        'tail /var/log/Nginx/access.log',
                        'ps aux | grep node'])

我可以通過將cd path/鏈接到/dir && ls來查看一個目錄的內容,但是運行cd path/to/dir后跟著ls會導致空無,因為ls第二次返回服務器根目錄下的文件列表。

通過SCP上傳(下載)文件

SCP既指用于將文件復制到遠程計算機的協議(安全復制協議),也指利用此協議的Python庫。我們已經安裝了SCP庫,所以請導入它。

SCP和Paramiko庫相互補充,使得通過SCP上傳非常容易。SCPClient()創建一個期望Paramiko進行“傳輸”的對象,我們通過self.conn.get_transport()提供了這個對象。從語法上講,創建SCP連接依賴于我們的SSH客戶機,但這些連接是獨立的。關閉SSH連接而保持SCP連接打開是可能的,所以不要這樣做。像這樣打開一個SCP連接:

self.scp = SCPClient(self.client.get_transport())

上傳單個文件很無聊,所以讓我們來上傳整個目錄的文件。Bulk_upload()接受文件路徑列表,然后調用_upload_single_file()

class RemoteClient:
    ...


    def bulk_upload(self, files):
        """
        Upload multiple files to a remote directory.


        :param files: List of paths to local files.
        :type files: List[str]
        """
        self.conn = self._connect()
        uploads = [self._upload_single_file(file) for file in files]
        logger.info(
f'Finished uploading {len(uploads)} files to {self.remote_path} on {self.host}')


    def _upload_single_file(self, file):
        """Upload a single file to a remote directory."""
        upload = None
        try:
            self.scp.put(
                file,
                recursive=True,
                remote_path=self.remote_path
            )
            upload = file
        except SCPException as error:
            logger.error(error)
            raise error
        finally:
            logger.info(f'Uploaded {file} to {self.remote_path}')
            return upload

我們的方法期望接收兩個字符串:第一個是文件的本地路徑,第二個是我們想要上傳的遠程目錄的路徑。

SCP的put()方法將把一個本地文件上傳到遠程主機。如果現有的文件恰好存在于我們指定的目標上,這將用相同的名稱替換它們。這就是所有需要的!

下載文件

與SCP的put()對應的是get()方法:

class RemoteClient:
    ...
    def download_file(self, file):
        """Download file from remote host."""
        self.conn = self._connect()
        self.scp.get(file)

分享到:
標簽:服務器
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定