<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          巧用Python登陸遠(yuǎn)程服務(wù)器

          共 13970字,需瀏覽 28分鐘

           ·

          2021-06-12 20:35

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          作者丨Huangwei AI

          來源丨Python學(xué)會(huì)


          用Python進(jìn)行遠(yuǎn)程登陸服務(wù)器

          這篇文章介紹如何通過使用Paramiko和SCP Python庫自動(dòng)化遠(yuǎn)程服務(wù)器任務(wù)。使用Python來SSH到主機(jī),執(zhí)行任務(wù),傳輸文件等。


          paramiko和scp是兩個(gè)Python庫,我們可以一起使用它們來自動(dòng)化我們想要在遠(yuǎn)程主機(jī)上運(yùn)行的任務(wù),比如重新啟動(dòng)服務(wù)、進(jìn)行更新或獲取日志文件。

          設(shè)置SSH密鑰

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


          $ ssh-keygen -t rsa


          這將提示我們?yōu)槊荑€提供一個(gè)名稱。隨便你怎么說:


          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):


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


          現(xiàn)在我們有了密鑰,我們需要將其復(fù)制到遠(yuǎn)程主機(jī)。最簡單的方法是使用ssh-copy-id:


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



          驗(yàn)證SSH密鑰


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


          $ cd ~/.ssh


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


          -----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----


          構(gòu)造腳本


          讓我們安裝庫。


          $ pip3 install paramiko scp


          在我們編寫一些有意義的Python代碼之前,還有一件事要做!創(chuàng)建一個(gè)配置文件來保存連接到主機(jī)所需的變量。下面是我們進(jìn)入服務(wù)器所需要的基本內(nèi)容:


          • Host:我們試圖訪問的遠(yuǎn)程主機(jī)的IP地址或URL。

          • Username:這是您用于SSH到服務(wù)器的用戶名。

          • Passphrase(可選):如果您在創(chuàng)建ssh密鑰時(shí)指定了一個(gè)Passphrase,請?jiān)谶@里指定。請記住,您的SSH密鑰密碼短語與您的用戶密碼不同。

          • SSH Key:我們前面創(chuàng)建的密鑰的文件路徑。在OSX上,它們存在于系統(tǒng)的~/.ssh文件夾。我們目標(biāo)的SSH密鑰必須有一個(gè)附帶的密鑰,文件擴(kuò)展名為.pub。這是我們的公鑰;如果您遵循前面的步驟,那么應(yīng)該已經(jīng)為您生成了這個(gè)文件。


          如果你試圖從遠(yuǎn)程主機(jī)上傳或下載文件,你需要包含兩個(gè)額外的變量:


          • Remote Path:文件傳輸目標(biāo)的遠(yuǎn)程目錄的路徑。我們可以上傳東西到這個(gè)文件夾或者下載它的內(nèi)容。

          • Local Path:與上述想法相同,但相反。為了方便起見,我們將使用的本地路徑是簡單的/data,并包含可愛的狐貍gif的圖片。


          現(xiàn)在我們有了創(chuàng)建一個(gè)config.py文件所需的一切:


          """Remote host configuration."""from os import environ, pathfrom dotenv import load_dotenv# Load environment variables from .envbasedir = path.abspath(path.dirname(__file__))load_dotenv(path.join(basedir, '.env'))# Read environment variableshost = 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客戶端

          我們將創(chuàng)建一個(gè)名為RemoteClient的類來處理與遠(yuǎn)程主機(jī)的交互。在我們搞得太復(fù)雜之前,讓我們先用config.py中創(chuàng)建的變量實(shí)例化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


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


          """Client to handle connections and actions executed against a remote host."""from paramiko import SSHClient, AutoAddPolicy, RSAKeyfrom paramiko.auth_handler import AuthenticationException, SSHExceptionclass 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()


          我們已經(jīng)添加了三個(gè)新東西來實(shí)例化我們的類:


          self.client = None: self.Client最終將在我們的類中充當(dāng)連接對象,類似于處理數(shù)據(jù)庫庫中的conn等術(shù)語。在顯式連接到遠(yuǎn)程主機(jī)之前,我們的連接將為None。


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


          Self._upload_ssh_key()不是一個(gè)變量,而是一個(gè)在客戶機(jī)實(shí)例化時(shí)自動(dòng)運(yùn)行的函數(shù)。調(diào)用_upload_ssh_key()是告訴我們的RemoteClient對象在創(chuàng)建時(shí)立即檢查本地ssh密鑰,以便我們可以嘗試將它們傳遞到遠(yuǎn)程主機(jī)。否則,我們根本無法建立聯(lián)系。


          上傳SSH密鑰到遠(yuǎn)程主機(jī)


          RemoteClient將從兩個(gè)私有方法開始:_get_ssh_key()和_upload_ssh_key()。前者將獲取本地存儲(chǔ)的公鑰,如果成功,后者將把這個(gè)公鑰傳遞給我們的遠(yuǎn)程主機(jī),作為訪問的橄欖枝。一旦本地創(chuàng)建的公鑰存在于遠(yuǎn)程機(jī)器上,該機(jī)器將永遠(yuǎn)信任我們的連接請求:不需要密碼。我們將在此過程中包括適當(dāng)?shù)娜罩居涗?,以防我們遇到任何麻?


          """Client to handle connections and actions executed against a remote host."""from os import systemfrom paramiko import SSHClient, AutoAddPolicy, RSAKeyfrom paramiko.auth_handler import AuthenticationException, SSHExceptionfrom scp import SCPClient, SCPExceptionfrom .log import loggerclass 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()非常簡單:它驗(yàn)證SSH密鑰是否存在于我們在配置中指定的用于連接到主機(jī)的路徑上。如果該文件確實(shí)存在,我們很樂意設(shè)置self.ssh_key變量,這樣我們的客戶端就可以上傳和使用這個(gè)密鑰了。Paramiko為我們提供了一個(gè)名為RSAKey的子模塊,可以輕松處理所有與RSA密鑰相關(guān)的事情,比如將一個(gè)私鑰文件解析為一個(gè)可用的連接身份驗(yàn)證。這就是我們得到的:


          RSAKey.from_private_key_file(self.ssh_key_filepath)


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


          連接到客戶端

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

          ...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()為創(chuàng)建代表SSH客戶機(jī)的對象奠定了基礎(chǔ)。以下幾行將配置此對象,使其更有用。

          • load_system_host_keys()指示客戶機(jī)查找我們過去連接過的所有主機(jī),方法是查看系統(tǒng)的known_hosts文件并找到主機(jī)所期望的SSH密鑰。我們過去從未連接到我們的主機(jī),所以我們需要顯式地指定SSH密鑰。

          • set_missing_host_key_policy()告訴Paramiko在出現(xiàn)未知密鑰對時(shí)該怎么做。這需要Paramiko內(nèi)置一個(gè)“策略”,我們將具體到AutoAddPolicy()。將我們的策略設(shè)置為“自動(dòng)添加”意味著如果我們試圖連接到一個(gè)無法識(shí)別的主機(jī),Paramiko將自動(dòng)在本地添加丟失的密鑰。

          • connect()是SSHClient最重要的方法(正如您可能想象的那樣)。我們終于能夠傳遞我們的主機(jī)、用戶和SSH密鑰來實(shí)現(xiàn)我們一直在等待的東西:到我們的服務(wù)器的一個(gè)漂亮的SSH連接!connect()方法也通過大量可選關(guān)鍵字參數(shù)數(shù)組提供了極大的靈活性。我碰巧在這里傳遞了一些:將look_for_keys設(shè)置為True將允許Paramiko在~/中查看。ssh文件夾發(fā)現(xiàn)自己的ssh密鑰,設(shè)置超時(shí)將自動(dòng)關(guān)閉我們可能忘記關(guān)閉的連接。如果選擇以這種方式連接到主機(jī),我們甚至可以傳遞端口和密碼等變量。


          斷開連接


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


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


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

          執(zhí)行Unix命令

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


          我們可以修復(fù)這個(gè)問題,并最終開始使用一個(gè)全新的方法來執(zhí)行命令,我將適當(dāng)?shù)貙⑵涿麨閑xecute_commands()(正確地說,“命令”可能不止一個(gè),我們稍后將討論這個(gè)問題)。所有這些工作都是由Paramiko客戶端內(nèi)置的exec_command()方法完成的,它接受一個(gè)字符串作為命令并執(zhí)行它:


          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}')


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



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


          我可以通過將cd path/鏈接到/dir && ls來查看一個(gè)目錄的內(nèi)容,但是運(yùn)行cd path/to/dir后跟著ls會(huì)導(dǎo)致空無,因?yàn)閘s第二次返回服務(wù)器根目錄下的文件列表。

          通過SCP上傳(下載)文件

          SCP既指用于將文件復(fù)制到遠(yuǎn)程計(jì)算機(jī)的協(xié)議(安全復(fù)制協(xié)議),也指利用此協(xié)議的Python庫。我們已經(jīng)安裝了SCP庫,所以請導(dǎo)入它。


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


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


          上傳單個(gè)文件很無聊,所以讓我們來上傳整個(gè)目錄的文件。Bulk_upload()接受文件路徑列表,然后調(diào)用_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


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


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


          下載文件


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


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


          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 30
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  污污在线无码 | a视频在线播放 | 亚洲三级片无码高清 | 国产精品久久久久久久久动漫 | 青青草免费在线公开视频 |