使用Emacs Lisp管理和部署服务器

最近一段时间都在做各种项目部署、服务器部署,顺便分享下我是如何用Emacs以及编写Emacs Lisp代码来管理和部署数台服务器的。

说到自动化,可能平时大家爱用比如pdsh命令,或者Python的Fabric框架,再或者是tmux、screen,这些都可以很方便地为我们做很多自动化的事情。Emacs内置了多种交互式shell方案,eshell、shell和ansi-term,我们让shell交互式运行在Emacs中,就可以像操作Emacs Buffer那样方便地来操控shell了。平时我常用shell(注,这里的shell指的是在Emacs中执行:M-x shell),然后我可以很方便地写Emacs Lisp代码来自动化一些事情。

现在,有N台服务器,我想在Emacs中开启多个Buffer,并在每个Buffer中打开一个shell,然后ssh到远程服务器上。并且每个Buffer名就是主机名,这样,我要管理某台主机,直接C-x b,然后输入主机名补全,就可以切换到对应那台主机的shell了。代码如下:

(defun ssh->hosts ()
  "SSH到列表中的主机"
  (dolist (host *hosts*)
    (shell host)
    (insert (format "ssh %s" host))
    (comint-send-input)))

上面代码做了以下事情:

  1. 遍历*hosts*变量,*hosts*变量是我定义的一个全局列表,里面每个元素是服务器的IP地址和主机名;
  2. 执行shell函数,shell函数会新打开一个Buffer,并在Buffer中创建一个交互式shell环境(支持bash、zsh等)。Buffer的名字就是shell函数的第一个参数,即主机名或IP地址。
  3. 然后在新建的shell下,执行一条shell命令:ssh 主机地址。comint-send-input函数是回车并执行shell。

以下就是我定义的*hosts*变量:

(defvar *hosts* '("172.17.0.5" "172.17.0.8"))

现在,我要部署服务器,希望在每台服务器上,都批量执行同样的命令,于是又写了一个函数:

(defun run (command)
  "执行shell命令"
  (let ((current-buf (current-buffer)))
      (dolist (host *hosts*)
	(shell host)
	(insert command)
	(comint-send-input))
      (switch-to-buffer current-buf)))

run函数遍历*hosts*里对应的每个Buffer,然后执行我们指定的shell命令。比如以下调用:

(run "sudo apt-get upgrade")

就可以批量升级系统。

这里,我录了一段视频来演示。视频中,我用Docker启动了两台虚拟机,演示了如何在两台机器上同步执行命令(注意视频右边上下两个Window,分别是不同主机的ssh)。

接着,我新建了个叫“hello”的Buffer,然后重新绑定了回车键,在Buffer中输入的每一行内容都作为要执行的命令,按下回车后,会在两台虚拟机上执行。

(视频地址:http://www.tudou.com/programs/view/CEuYyiO-f8Y/

视频里的两个函数newline-and-submit和current-line-string代码如下:

(defun newline-and-submit ()
  "获取并作为shell命令执行当前行的内容,然后插入换行"
  (interactive)
  (let ((line (current-line-string)))
    (run line)
    (reindent-then-newline-and-indent)))

(defun current-line-string ()
  "获得当前行的字符串内容"
  (interactive)
  (buffer-substring-no-properties (line-beginning-position) (line-end-position)))