企業主機服務器日常運維工作中,經常需要登錄並以 root 方式執行系統操作,如果在主機數量少的情況下,手工方式登錄並執行效率尚可,但如果主機數量龐大(如筆者運維的國外客戶服務器數量達 2000+),依次對一台台服務器進行手工操作工作量巨大且出錯概率與主機數量成線性增大。 本文分析了在大數量企業服務器情況下,利用 shell 管道,Java SSHD 開源包,Expect 腳本三種方式實現自動登錄並執行系統運維操作,三種方式分別適用於不同的場景,可以滿足絕大多數企業主機服務器自動化運維的工作內容,大大減輕了系統管理員的工作量,同時降低了操作失誤的風險。 本文中的三種方式的代碼示例稍作修改,即可直接用於實際的生產主機的運維工作。
采用自動化腳本進行企業服務器運維的原因
傳統 Unix 主機服務器手工運維方式的問題
大型企業的 IT 基礎設施中,Unix 主機服務器占據重要的地位。在日常運維工作中,經常需要登錄並以 root 方式執行系統操作。如果在主機數量少的情況下,手工方式登錄並執行運維工作的效率尚可,但如果主機數量龐大(如筆者運維的國外客戶服務器數量達 2000+),依次一台台服務器的登錄和操作工作量巨大,且出錯的概率隨服務器數量的增加的遞增。
采用自動化腳本進行運維的特點及好處
在大數量 Unix 企業服務器情況下,為了提高運維工作效率,減低手工操作而出錯的風險,采用自動化腳本進行運維是一個很好的方式。通過向腳本提供主機列表,用戶名賬戶名密碼等輸入參數方式,讓其自動登錄遠程目標主機並執行相應的運維操作,批量處理所有涉及的企業服務器主機。該方式下系統管理及運維人員只需要在自動化腳本中提供一次系統運維操作的步驟命令,設定主機列表及正確的帳號密碼輸入參數,利用 nohup 方式後台運行該腳本,在完成後監控該腳本的輸出日志就可以完成上千台服務器的重復運維工作,並大大減輕了系統管理及運維人員的工作量,降低了重復操作中出錯的風險。
本文介紹了利用 shell 管道,Java SSHD 開源包,Expect 腳本三種方式實現自動登錄並執行系統運維操作的案例。三種方式分別適用於不同的場景,如 shell 管道方式適用於 Telnet/FTP 協議登錄的普通賬戶,SSHD 開源包適用於 ssh1/ssh2 安全登錄協議下的登錄運維,Expect 腳本適用於 SSHD 協議且需要 sudo 切換到 root 帳號權限的運維操作。
自動化腳本運維的具體實現
利用 shell 管道進行自動化運維
在企業 Unix 服務器上如果開放了 telnet 或者 FTP 協議,通常可以利用 shell 的 EOF 和 << 管道功能將後續的輸入作為子命令或子 Shell 的輸入,直到遇到 EOF 為止,再返回到主調 Shell,當 s h e l l 看到 < < 的時候,它就會知道下一個詞是一個分界符。在該分界符以後的內容都被當作輸入,直到 shell 又看到該分界符 ( 位於單獨的一行 )。這個分界符可以是你所定義的任何字符串
例如:
<
(你需要執行的操作內容)
EOF
利用該功能,可以將需要 ftp 或者 telnet 登錄的運維操作做成自動化腳本,將本來需要交互式輸入的帳號和密碼及登錄後需要的操作指令包在 EOF 和 << 管道符中,以實現自動化 ftp 或者 telnet 到多台服務器並執行。
考慮如下場景:一批客戶的 Unix 服務器主機要打 patch,需要將 patch 包用 ftp 上載到服務器指定目錄,服務器數量巨大(超過 1000+),單個手工的上載操作是不現實的,因此我們使用 shell EOF 和管道功能編寫自動化 ftp 腳本
autoftp 腳本示例如下:
清單 1. autoftp.sh 腳本示例
#!/bin/bash # 指定 ftp 服務器的 i serverip=192.168.1.159 # 指定 ftp 服務器的 ftp 用戶 ftpuser=ftptest # 指定 ftp 服務器的 ftp 用戶密碼 ftppwd=123456 # 指定 client 主機本地下載文件存放的目錄 localdir=/home/xiutuo/ftp # 指定 server 主機的 ftp 目錄 remotedir=/opt/IBM/DB2/ # 登錄 server 主機的 ftp -v -n $serverip << EOF > /tmp/autoftp.log.2010.XX.XX 2>&1 set head off set echo off set wrap off # 指定 ftp 用戶和密碼 $ftpuser $ftppwd # 指定 server 主機的 ftp 目錄和本地目錄 lcd $localdir cd $remotedir bin # 上傳 patch 包文件至 server 主機的指定目錄 put patchXXX.tar.gz EOF
如上可以看到 FTP 登錄和上傳不再需要手工與每台 server 交互。讀者可以修改該腳本,讓服務器主機 IP 或主機名通過讀配置文件循環獲得,從而實現對多台服務器主機的操作。也可以修改 ftp 用戶 / 密碼部分代碼,改成讀取輸入參數,以增強安全性。
Java SSHD 開源包自動化運維
上述的 Ftp,telnet 管道方式的運維是簡單可行的,但是現在業界大型的企業處於安全性的考慮,逐步淘汰此類協議的登陸方式,而改用基於公鑰體系的 SSHD 登陸協議。關於 SSHD 協議具體內容已超出本文涉及的范圍,請各位感興趣的讀者參考 open-ssh 官方網站。
在 SSHD 下的登陸是不允許 shell 管道方式的(如果允許的話意味著 ssh 跟 telnet,ftp 一樣喪失了安全性),在此種情況下如果系統管理員要進行自動化運維操作,可以采用 Java 開源的 SSHD 包來進行。
Java ganymed 開源包是成熟的 SSHD 客戶端,采用封裝 socket 編程方式進行底層的 ssh 通信協議,用戶調用其 API 與自己使用 SSHD 命令行登陸服務器的步驟和方法都一致,很容易理解和掌握。開源包的很多 Demo 實例,使即使對 Java 編程不熟悉的系統管理人員,也可以通過簡單的修改 demo 代碼來實現自身需求的自動化運維操作。
考慮如下案例:一個企業的所有服務器需要將 /etc/services 文件備份至 /usr/local/etc/ 特定邏輯卷目錄,企業服務器都采用 SSH2 安全協議,不允許 telnet,ftp 登錄。
用 ganymed 的 SSH2 開源包編寫自動化登陸腳本,以管理員賬號和密碼登陸企業 Unix 服務器,執行 cp 操作進行備份。
ganymed 的 Java 代碼示例如下:
清單 2. AutoCp.java 類代碼示例
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import ch.ethz.ssh2.Connection; import ch.ethz.ssh2.Session; import ch.ethz.ssh2.StreamGobbler; public class AutoCopy { public static void main(String[] args) { String hostname = "9.212.XXX.XXX; String username = "SolarisAdmin"; String password = "********"; try { /* 創建 SSH2 連接實例 */ Connection conn = new Connection(hostname); /* 打開主機 ssh 端口連接(默認 22) */ conn.connect(); /* 認證方式為 user/passwd */ boolean isAuthenticated = conn.authenticateWithPassword(username, password); if (isAuthenticated == false) throw new IOException("Authentication failed."); /* 已連接到遠程主機,打開 session 會話 */ Session sess = conn.openSession(); /* 執行備份操作 */ sess.execCommand(\ "cp /etc/services /usr/local/etc/services; \ ls -lt /usr/local/etc/|grep -i services >&2"); /* 遠程主機輸入輸出流 */ InputStream stdout = new StreamGobbler(sess.getStdout()); InputStream stderr = new StreamGobbler(sess.getStderr()); BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(stdout)); BufferedReader stderrReader = new BufferedReader(new InputStreamReader(stderr)); System.out.println("process result on remote server:"); while (true) { String line = stdoutReader.readLine(); if (line == null) break; System.out.println(line); } System.out.println("Process error from remote server:"); while (true) { String line = stderrReader.readLine(); if (line == null) break; System.out.println(line); } /* 關閉會話 */ sess.close(); /* 關閉連接 */ conn.close(); } catch (IOException e) { e.printStackTrace(System.err); System.exit(2); } } }
以上可以看到,ganymed 的 SSH2 開源包使用簡單,建立 connection 並打開會話 session 後,都是普通的對 session 的 IO 操作,讀操作則可以獲取服務器端的輸出,而寫操作則對應對服務器端的敲入命令,一目了然,簡便實用。
熟悉 Java 編程的系統管理員可以很快上手,即使是沒有 Java 基礎的管理員也可簡單的修改示例中的 sess.execCommand("cp /etc/services /usr/local/etc/services; ls -lt /usr/local/etc/|grep -i services >&2"); 行操作代碼來實現自己的業務需求。
具體的 ganymed API 請參考 ganymed project 官方網站。
Expect 腳本自動化運維
考慮如下案例情況:有一批客戶服務器需要做一個變更,將 sudoer 配置文件從 /etc/sudoer 目錄搬移備份到 /usr/local/etc/sudoer 目錄,由於該配置文件比較重要,需要 root 權限才能執行搬移操作,該客戶服務器也采用 SSHD 協議,不允許 ftp/telnet 等的登錄。
該情況下上文所提到的第二種方式 Java 開源包已不適用,因在 shell 進程下當切換用戶尤其是 sudo 到 root 時,會 fork 一個新的進程,sudo 到 root 的會話在新的進程中進行,而 Java 開源包是限定在一個會話中的,執行 sudo 操作後,新的進程已經脫離了 Java 開源包的控制,這時候再試圖用 Java 開源來執行後續命令,將會報錯。
此類需要切換用戶的情況下我們采用 Expect 腳本來進行模擬交互。
在這裡將 Expect 腳本簡介如下:
Expect 使用 Tcl 作為語言核心。不僅如此,不管程序是交互和還是非交互的,Expect 都能運用。這是一個小語言和 Unix 的其他工具配合起來產生強大功能的經典例子。Expect 是一個控制交互式程序的工具。它解決了上述需要用戶角色轉換的問題,用非交互的方式實現了所有交互式的功能。
Expect 被設計成專門針和交互式程序的交互。一個 Expect 程序員可以寫一個腳本來描述程序和用戶的對話。接著 Expect 程序可以非交互的運行“交互式”的程序。寫交互式程序的腳本和寫非交互式程序的腳本一樣簡單。Expect 還可以用於對對話的一部分進行自動化,因為程序的控制可以在鍵盤和腳本之間進行切換。
簡單的說,Expect 腳本是用一種解釋性語言寫的。( 也有 C 和 C++ 的 Expect 庫可供使用,但這超出了本文的范圍 ).Expect 提供了創建交互式進程和讀寫它們的輸入和輸出的命令。它是在 Tcl 基礎上創建起來的,並提供了一些 Tcl 所沒有的命令。
編寫 Expect 腳本的基本方式如下:
表 1. Expect 腳本基本使用示例
Expect 命令 作用 使用示例 spawn 激活一個 Unix 程序來進行交互式的運行 . spawn ssh 192.168.1.2 send 向進程發送字符串 send "sudo -s\r" set 給 Expect 腳本中的變量賦值 set username “joe” expect 等待進程收到的遠程主機的輸出,並匹配對應的字符串 , 一旦匹配,執行後續的操作 expect { "yes/no" send "yes" ;}Expect 還能理解一些特殊情況,如超時和遇到文件尾。 :set timeout 60 ;expect eof
我們以上述的實例作為例子,來看看 Expert 腳本如何實現自動化登陸並 sudo 到 root,然後搬移文件的功能。
Expect 腳本 autoMove 示例如下:
清單 3. autoMove.sh 腳本示例
#!/usr/bin/expect # 導入 Expect 類庫 set hostname [lindex $argv 0] # 設置操作的遠程主機,$argv 類似 Shell 函數中的接收參數 [lindex $argv 0] # 則表示第一個接收參數 , 例如 expectExample.sh host1 set username [lindex $argv 1] # 同上,第二個接收參數為登陸用戶名 set passwd [lindex $argv 2] # 同上,第三個接收參數為登陸用戶密碼 set timeout 60 # 設置等待超時為 60 秒 spawn ssh $username@$hostname # 使用 spawn 命令來激活 ssh 程序,模擬終端的輸出將能夠被 Expect 所讀取,模擬終端也能從 send 輸入到遠程主機 expect { "yes/no" {send "yes ";exp_continue} "Password:" {send "$passwd ";} } #Expect 語句等待遠程主機的字符串匹配,當匹配到了“yes/no” #則執行後面的操作 .expect 搜索模式"*password:",其中 * 允許匹配 # 任意輸入,所以對於避免指定所有細節而言是非常有效的。如果遠程主機沒有 action, #所以 Expect 檢測到該模式後就繼續運行。 一旦接收到提示後,下一行就就把密碼送給當前進程。 send "sudo -s\r" expect "Password:" {send "$newpasswd\r"} # 執行 sudo 用戶角色轉換操作 send "copy /etc/sudoers /usr/local/etcsudoers\r" # 執行實際運維操作 send "exit\r " send "exit\r " expect eof {exit 1}
由上我們可以看出 Expect 使用偽終端來和派生的進程相聯系。偽終端提供了終端語義以便程序認為他們正在和真正的終端進行 I/O 操作。使用 Expect 等待遠程主機的響應並匹配需要的字符串,當匹配到後執行 send 操作向遠程主機發送命令,set 操作為賦值,腳本的編寫於通常的 Shell 腳本很類似,相當簡潔和實用。
在 AIX ,Solairs 的 Unix 平台環境下 Expect 是默認安裝的,Linux 需要安裝對應的 rpm 包。
總結和展望
以上分析了大型企業服務器的自動化腳本運維,通過不同的案例分別介紹了 shell 管道,Java SSHD 開源包和 Expect 腳本三種方式的自動化運維。三種方式針對不同的業務需求及客戶服務器實際環境,有很強的實用性和操作性。可以滿足絕大多數企業服務主機的自動化運維工作內容,三種方式的代碼示例稍作修改,即可直接用於實際的生產主機日常運維工作中。
如今隨著 IT 運維管理工作的復雜度和難度的大大增加,將純粹的人工操作變為一定程度的自動化管理是一個必然趨勢。未來的 IT 自動化運維必將更加專業化、標准化和流程化。通過自動化運維監控,系統能及時發現故障隱患,主動的告訴用戶需要關注的資源,以達到防患於未然。通過自動化運維診斷,能最大限度地減少維修時間,提高服務質量。