使用安全 Shell (SSH) 在远程 UNIX® 系统中运行命令,并使用一些简单的脚本构成一个系统,该系统允许您在一台计算机中同时管理许多系统,而无需直接登录到计算机本身。本文还介绍了分布式管理系统的基础知识,以及使用该技术的某些脚本和解决方案。
关于本系列
典型的 UNIX® 管理员拥有一套经常用于辅助管理过程的关键实用工具、诀窍和系统。存在各种用于简化不同过程的关键实用工具、命令行链和脚本。其中一些工具来自于操作系统,而大部分的诀窍则来源于长期的经验积累和减轻系统管理员工作压力的要求。本系列文章主要专注于最大限度地利用各种 UNIX 环境中可用的工具,包括简化异构环境中的管理任务的方法。
对远程登录进行简化
安全 Shell (SSH) 工具为远程主机的登录和交换信息提供了一种安全的方法。其中提供了大量不同的工具,包括通用的 SSH 工具(它提供了远程终端连接)、SCP(一种安全的、主机到主机的、复制解决方案)和 SFTP(一种安全的文件复制解决方案,采用了与标准 FTP 工具类似的工作方式)。
所有这些工具都是安全的,因为对交换的信息进行了加密。此外,使用了公钥或者私钥机制确保连接的身份验证的安全。SSH 的主要优点之一是,通过将您的公钥复制到一台远程计算机,您可以绕过正常的登录和密码交换。
尽管使用 SSH 登录到远程计算机是非常有用的(因为这样做意味着您无需提供密码),不过在执行远程管理方面,它更有价值。如果必须输入密码,将使得无法实现远程管理(例如,通过 cron 运行一个命令)的自动化,因为在自动化的脚本中,您无须输入密码!
如果不交换您的公钥,那么使用 SSH 在多台计算机中运行相应的命令时,您必须为每台计算机输入您的密码。
要完成这种设置,有一种快速而简单的方法,即创建一个公钥: $ ssh-keygen -t rsa
请按照屏幕上的说明执行相应的操作,但是在提示设置密码时,不要进行设置,因为您需要在每次输入密码时使用这个密钥。这个操作将创建一个私钥和一个公钥文件。现在您只需要追加 .ssh/id_rsa.pub 中公钥文件的内容即可,并将其追加到远程主机的 .ssh/authorized_key 文件和您在登录时希望使用的用户。您需要将这个公钥文件的内容追加到您希望自动地进行登录的每台计算机。
运行远程命令
您可以通过许多方式来运行远程命令。
通过将您希望 SSH 运行的命令添加在登录或者主机信息的后面,您可以运行单个远程命令。例如,要获取一台远程主机的磁盘信息,您可以使用下面清单 1 中的这个命令,并获得相应的输出结果。
清单 1. 通过 SSH 运行一个简单的命令
$ ssh mc@gentoo.vm df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hda3 14544820 3611520 10194464 27% /
udev 128044 564 127480 1% /dev
/dev/hdc1 1968872 50340 1818516 3% /var/tmp
/dev/hdc2 1968904 1482220 386668 80% /usr/portage
/dev/hdc3 1968904 35760 1833128 2% /home/build
shm 128044 0 128044 0% /dev/shm
请记住,如果您尚未与这台远程主机交换您的公钥,那么清单 1 中的序列需要您输入一个密码。
通过使用分号隔开每个命令,然后将整个命令序列用引号引起来(以便能够将其识别为单个参数),您还可以执行一系列的命令。
清单 2 中显示了一个执行磁盘检查和正常运行时间检查的示例。
清单 2. 执行磁盘检查和正常运行时间检查
$ ssh mc@gentoo.vm "df;uptime"
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hda3 14544820 3611520 10194464 27% /
udev 128044 564 127480 1% /dev
/dev/hdc1 1968872 50340 1818516 3% /var/tmp
/dev/hdc2 1968904 1488100 380788 80% /usr/portage
/dev/hdc3 1968904 35760 1833128 2% /home/build
shm 128044 0 128044 0% /dev/shm
14:31:27 up 12 min, 2 users, load average: 0.01, 0.05, 0.06
在这个操作中,您可以将希望执行的许多命令联接成字符串。还可以进行筛选工作,例如使用 grep 或者其他工具,但是您需要确保将整个远程命令表达式嵌入到引号中(请参见清单 3)。
清单 3. 使用 grep 进行筛选
$ ssh root@gentoo.vm "cat /var/log/messages|grep 'su/['"
Dec 17 18:05:37 localhost su[19218]: pam_authenticate: Permission denied
Dec 17 18:05:37 localhost su[19218]: FAILED su for root by mc
Dec 17 18:05:37 localhost su[19218]: - pts/1 mc:root
Dec 17 18:06:31 localhost su[19221]: pam_authenticate: Permission denied
Dec 17 18:06:31 localhost su[19221]: FAILED su for root by mc
Dec 17 18:06:31 localhost su[19221]: - pts/1 mc:root
Dec 17 18:06:40 localhost su[19222]: pam_authenticate: Permission denied
Dec 17 18:06:40 localhost su[19222]: FAILED su for root by mc
...
关于清单 3,需要说明的第一项内容是,您作为 root 用户直接登录到远程计算机。这是因为您希望查看只有超级用户才能够访问的文件。您必须确保对您的系统进行相应的配置,以允许远程 root 登录执行这样的操作。
关于这个示例,第二项需要说明的重要内容是,您以远程的方式执行了 grep 操作。实际上,您并不需要这样做。已经将远程主机的标准输入和输出复制到了本地计算机,所以可以在本地对该命令的输出进行筛选,如清单 4 中所示。
清单 4. 在本地对输出进行筛选
$ ssh root@gentoo.vm "cat /var/log/messages" | grep 'su/['
Dec 17 18:05:37 localhost su[19218]: pam_authenticate: Permission denied
Dec 17 18:05:37 localhost su[19218]: FAILED su for root by mc
Dec 17 18:05:37 localhost su[19218]: - pts/1 mc:root
Dec 17 18:06:31 localhost su[19221]: pam_authenticate: Permission denied
Dec 17 18:06:31 localhost su[19221]: FAILED su for root by mc
Dec 17 18:06:31 localhost su[19221]: - pts/1 mc:root
Dec 17 18:06:40 localhost su[19222]: pam_authenticate: Permission denied
Dec 17 18:06:40 localhost su[19222]: FAILED su for root by mc
Dec 17 18:06:40 localhost su[19222]: - pts/1 mc:root
当然,其效果基本上是相同的。
当您希望通过管道进行传输的信息或者命令是远程的,那么使用远程管道方法是非常有用的。例如,使用清单 5 中的命令,您可以将 ls 与 du 组合使用,以确定不同目录的磁盘使用情况。
清单 5. 确定不同命令的磁盘使用情况
ssh root@gentoo.vm "ls -d /usr/local/* |xargs du -sh "
Password:
4.0K /usr/local/bin
4.0K /usr/local/games
4.0K /usr/local/lib
0 /usr/local/man
4.0K /usr/local/sbin
12K /usr/local/share
4.0K /usr/local/src
在继续将这些技术重新分布到多台计算机之前,这里将介绍一个直接运行远程交互会话的技巧,而无需首先进行登录。
直接的交互会话
如前所述,您可以直接运行许多不同的命令和命令链。SSH 解决方案的优点之一是,尽管命令本身是以远程的方式执行的,但是命令的输入和输出都来源于调用的计算机。您可以将它作为一种在两台计算机(与您希望执行的命令相关)之间交换信息的方法。
您可以执行各种各样不同的命令。然而,因为您直接从命令行运行这些命令,所以对于直接使用该方法所能执行的操作,通常是有限制的。例如,使用上面介绍的这种方法和技术,尝试使用某种编辑器来编辑一个远程文件,通常都会失败(请参见清单 6)。
清单 6. 编辑一个远程文件失败
$ ssh root@gentoo.vm "emacs /etc/amavisd.conf"
emacs: standard input is not a tty
通过强制 SSH 分配一个伪 tty 设备(以便您可以直接与远程应用程序进行交互),就可以解决这个问题。
在多台计算机中运行远程的命令
到目前为止,您主要关注于在一台远程计算机中运行单个的命令或者命令字符串。尽管在直接使用 SSH 执行远程管理时,交互会话技巧非常有用,但是您可能希望实现这个过程的自动化,这意味着交互的组成部分未必非常有用。
要在多台计算机中以远程的方式运行相同的命令,您需要为 SSH 命令和远程命令(您希望运行的远程命令,以便在每台远程计算机上重复该过程)构建一个简单的包装。
您可以使用一个非常简单的 for 循环来完成这项任务,如下面的清单 7 所示。
清单 7. 用于以远程的方式运行命令的 for 循环
for remote in mc@gentoo.vm mc@redhat; do echo $remote; ssh $remote 'df -h'; done
mc@gentoo.vm
Filesystem Size Used Avail Use% Mounted on
/dev/hda3 14G 4.1G 9.2G 31% /
udev 126M 564K 125M 1% /dev
/dev/hdc1 1.9G 56M 1.8G 4% /var/tmp
/dev/hdc2 1.9G 1.3G 558M 70% /usr/portage
/dev/hdc3 1.9G 35M 1.8G 2% /home/build
shm 126M 0 126M 0% /dev/shm
mc@redhat
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00
7.1G 5.5G 1.3G 82% /
/dev/hda1 99M 13M 82M 14% /boot
none 125M 0 125M 0% /dev/shm
您可以很容易地将其转换为一个简单脚本,如清单 8 所示。
清单 8. 将 for 循环简化为简单的命令
#!/bin/bash
# Script to run a command across multiple machines
# Global options
TIMEOUT=10
ERRLOG=/tmp/remote-err-$$.log
OUTLOG=/tmp/remote-out-$$.log
# Extract the command line
MACHINES=$1;shift
COMMAND=$1;shift
for machine in $MACHINES
do
echo $machine
ssh -oConnectTimeout=$TIMEOUT $machine $COMMAND >>$OUTLOG 2 >>$ERRLOG
done
cat $OUTLOG
cat $ERRLOG >&2
rm -f $OUTLOG $ERRLOG
MACHINES 和 COMMAND 是从命令行中“按原样”提取的内容。在使用这个脚本时,您必须将用户或者主机组合和命令放到双引号中,以确保能够将其识别为单个参数。
唯一的其他附加内容是 TIMEOUT 选项。它为 SSH 设置了 ConnectTimout 选项,使得在运行一个命令时,您需要等待连接到一台不可用的主机。在这个脚本的开头设置了缺省值,应该可以确保您不会等待太久。
在运行这个命令时,您将输出发送到两个日志文件,一个用于标准输出,另一个用于标准错误。然后,您将这些内容分别输出到合适的位置。这是 SSH 的一个突出的优点,远程计算机重定向到相同的位置(标准输出、标准错误),所以您可以在本地进行重定向,同时保持输出结果的含义。
例如,您可以使用这个脚本重复进行 df 检查: $ runremote.sh "gentoo redhat" "df -h"
因为您对标准输出和错误进行了重定向,所以您甚至可以为整个过程生成日志: $ runremote.sh "gentoo redhat" "df -h" 2>/tmp/error.log
使用远程执行进行性能监视
在使用 runremote.sh 时,您可能希望使用超时值的确切值,并且您甚至可能希望更改这个值,这取决于您所执行的操作。例如,如果您正使用这个脚本通过在多台计算机中执行 uptime 命令来获得当前状态的快照,那么您一定不希望为连接和命令的执行而等待太久,否则快照就会不精确。
另外,这个脚本实际上是按顺序运行该命令。如果有大量的计算机,这种方式不仅很费时间,而且执行所选命令的第一台和最后一台计算机之间的时间延迟可能非常明显,以至于无法相互关联这些计算机。
对这个脚本稍作修改可得到 runremote2.sh,如清单 9 所示。这样可以几乎同时地执行这个远程命令(通过在后台运行它),然后同样通过管道将输出传输到各自的日志文件。
清单 9. 几乎同时执行远程命令的脚本
#!/bin/bash
# Script to run a command across multiple machines
# Global options
TIMEOUT=10
ERRLOG=/tmp/remote-err-$$.log
OUTLOG=/tmp/remote-out-$$.log
# Extract the command line
MACHINES=$1;shift
COMMAND=$1;shift
for machine in $MACHINES
do
echo $machine >>$OUTLOG.$machine
ssh -oConnectTimeout=$TIMEOUT $machine $COMMAND >>$OUTLOG.$machine
2>>$ERRLOG.$machine &
done
# Wait for children to finish
wait
cat $OUTLOG.*
cat $ERRLOG.* >&2
rm -f $OUTLOG.* $ERRLOG.*
在这个脚本中,您还将计算机名回显到命令日志(对所提供的每台计算机来说是唯一的)。要确保该脚本不会在执行所有的远程命令之前退出,您需要添加一个 wait 命令,以等待该脚本的所有子操作执行结束。
现在您可以使用该脚本同时检查多台计算机(请参见清单 10)。
清单 10. 使用这个脚本同时检查多台计算机
$ runremote2.sh "narcissus gentoo.vm droopy@nostromo mcbrown@nautilus" 'uptime'
droopy@nostromo
19:15 up 9 days, 23:42, 1 user, load averages: 0.01 0.03 0.00
gentoo.vm
18:10:23 up 1 day, 10:02, 2 users, load average: 1.72, 1.84, 1.79
mcbrown@nautilus
19:15 up 10:08, 4 users, load averages: 0.40 0.37 0.29
narcissus
19:15 up 8 days, 7:04, 4 users, load averages: 0.53 0.54 0.57
当您希望获得网络的整体运行情况时,这种监视工作可能是非常有价值的,例如,要检查运行 Web 或者数据库服务的一组或者计算机集群中的问题,并希望同时为该组中的多台计算机确定潜在的峰值或者问题。
然而请注意,其中仍然存在一定的延迟,尤其是在某台计算机特别繁忙的情况下,在不同的计算机中建立连接和执行命令所需的时间可能会产生非常明显的时间延迟。
在多台计算机上运行相同的操作
在大量的计算机中创建用户可能是一件非常辛苦的工作。很显然,有许多解决方案可用于解决使用单点登录实用工具所带来的困难,如 Network Information Service (NIS) 或者基于 LDAP 的解决方案,但是您并不是必须使用这种方式对用户进行同步。
您可以使用 SSH 在多台计算机上运行 adduser 命令,以完成这项任务。但是在 Solaris 中,该命令的名称是 useradd。命令行选项基本上是相同的,所以您可以两次使用 run-remote.sh(请参见清单 11)。
清单 11. 运行 run-remote.sh 两次
$ runremote.sh "gentoo redhat" "adduser -u 1000 -G sales,marketing mcbrown"
$ runremote.sh "solaris solaris-x86" "useradd -u 1000 -G sales,marketing mcbrown"
现在,您已经在大量的计算机中使用相同的组和相同的用户 ID 创建了相同的用户,但是这样做并不实用。
更好的方法是,使用文章“System Administration Toolkit: Standardizing your UNIX command-line tools”中介绍的技巧(请参见参考资料)在多台计算机上使用相同的命令: $ runremote.sh "gentoo solaris" "adduser.sh -u 1000 -G sales,marketing mcbrown"
总结
在本文中,您研究了一种用于在远程计算机中运行命令的简单但功能强大的方法。尽管这个过程的基本思想非常简单,不过您还可以创建附加的功能以完成某些可靠的、自动化远程管理任务(例如,可以重定向并通过管道传输远程本地输入)。通过实现一些简单的 Shell 脚本技巧,您甚至可以使用该系统同时对大量计算机进行远程管理,从而简化各种重复的任务和性能监视工作。