服务器部署的决策与实践(4) 环境部署实践
一套环境通常不会只部署一遍,你会遇到各种需要重复部署环境的情况:也许是一个项目需要多台服务器来运行;也许是同时需要管理多个项目;也许是需要生产环境和开发、测试环境的一致性。总之,掌握高效的方法和工具是非常必要的。
这周我来讲讲自己在服务器环境部署上经历的四个阶段,以及选择背后的原因。
本文是 服务器部署的决策与实践 系列的一部分。
第一阶段
在最初的阶段,我还完全是个新手,对服务器管理的技巧和相关软件配置的方法都还在逐步深入了解的过程中,需要许多摸索和尝试,说是跌跌撞撞才部署完一台服务器也不为过。等到一段时间后,又要一台相同环境的服务器时,早忘了上次是怎么做的。先勉强凭着记忆把关键的步骤给走下来,却总是难免遗漏几处配置,有时甚至应用运行到一半报错了,才能发现少了必要的组件,通过一系列修补才终于把新服务器搞定。最痛苦的是明明记得要做某个步骤,却想不起细节,隐约记得是在一个什么网站上看到过,又无论如何翻找不到。
第二阶段
经过上个阶段的磨炼,对常用软件的安装和配置已有心得,无谓的尝试也逐渐减少。而且也学聪明了,安装的过程会写个简单的文档记录下来,下次照做错漏自然更少。文档也有不同的记录方式,尝试下来渐进式记录效益最高。在安装时边调边记会容易影响思路的连贯性,还会发生记了几步到后来却要推翻的情况。所以在配置完后马上尽可能依靠记忆写下一份文档,待下次再部署时发现问题再行补充,耗费最少。再部署的过程也可交由他人执行,更容易发现错漏之处。直到最后可以完全依照文档的记录来完整部署一套环境为止。
第三阶段
再之后就要考虑自动化了,因为环境通常不会只部署一遍,虽有文档指引,但完全人工处理即费时费力又易出错。早期的愿景是将原先要人工执行的所有命令记录在一个 shell 脚本里,部署新服务器时把脚本丢上去运行就好了。事实证明用这个方法执行几个包管理器命令或启动脚本是绰绰有余的,而且实现起来也是轻而易举。但随着情况变得复杂,脚本越来越难以维护:有些配置可以用查找替换进行设置,有些整个覆盖更省事;有些命令是异步的,没运行完上一个,下一个就要开始了,但事务上确是有前后逻辑的;有时候命令执行到一半会失败,但前面运行过的那些怎么办?这些都是要考虑的,对 shell 编程的要求也越来越高,我可没有成为这方面专家的打算。到最后维护起来实在有些心累,加之服务器数量也不是特别多,于是这个阶段的尝试,最终退回到半自动的路子上:先挑其中容易实现或者性价比高的软柿子让 shell 脚本搞定,再辅以文档指引下的人工操作完成剩下的部分。
第四阶段
随着新工具的诞生,终于走上了全自动部署的光明大道。此工具者 Ansible 是也。
Ansible
对于 Ansible 的使用方法,网上已有许多介绍性的文章,我当时看的是 Ansible入门 。
这里我只将其与编写 shell 脚本的经历进行对比,来说明一下它的进步之处。
本地运行
Ansible 在本地运行,运行时连接到目标服务器执行指令,而无需将脚本拷贝到目标服务器上,这给调试和修改脚本带来诸多便利,即使我日常使用 vim 作为主要编辑器,也深感在服务器上编辑文件有诸多不便,更何况许多用户的主力编辑器不是 vim 或 emacs ,在服务器上直接修改文件更是痛苦。这个便利性,是初期编写和测试的过程更加顺畅。
理论上 shell 脚本也能连接到远程服务器上执行,但是逻辑会变得更加复杂,本就难以维护的脚本变得更难写,足以让人头大。
跳过已经成功的步骤
对于使用包管理器安装软件等指令,Ansible 可以判断软件是否已经完成安装成功,如果因故需要重新执行 Ansible 脚本,无需对这种情况特殊处理,直接执行完整的脚本即可,不会浪费很多时间在重复安装已有的软件上。
这一点上,shell 脚本要么需要编写额外逻辑进行判定,要么干脆重新安装一次,或者把脚本拆成几个部分来跳过完成的部分,相比之下就逊色不少。
通用性和移植性
Ansible 的功能通过模块实现,而模块对底层操作进行了一定的封装,比如说同样是用包管理器安装软件,RHEL/CentOS 是用 yum 或 dnf,Debian/Ubuntu 是用 apt-get,Ansible 的 package 模块可以用相同脚本的在不同系统中调用相应的工具完成软件的安装或卸载。当然,这只是提供了一种可能性,而不是一种保证,因为同个软件在不同系统中包名或者配置文件位置可能是不相同的。
但比起 shell 只能明确指定特定工具还是多了点想象空间。
编组
Ansible 的编组功能是多方面的,服务器可以通过 Host Inventory 进行编组达到批量管理的目的,Playbook 脚本可以通过 include 语句进行组合达到重用的目的,任务可以通过标签进行编组,实现选择性执行的目的。
文件拷贝
Ansible 有一个专门的模块用来拷贝文件,这对管理配置文件非常有帮助,只要指定本地文件的路径和目标位置,就可以直接将文件投送到服务器上的指定目录,完成软件的配置工作。
掌控全局
Ansible 还有很多强大的功能,不再一一列举,最后让我以一个最基础,但也最佳体验的功能结束这一节。前面已经提到,Ansible 可以用 Host Inventory 对服务器进行编组,而 Ansible 的脚本都可以指定一组或多组服务器进行批量操作。Ansible 会同时连接组内的所有服务器,对他们执行脚本指定的任务,并将所有服务器上的执行结果汇总回来。对着一个窗口,气定神闲的观察所有服务器安装进度的实时反馈,可以体会到一种令人满足的掌控感,而不是一次开许多个窗口,这里看看,那里敲敲的焦灼和疲劳。
适用情况以及 Docker 去哪了
提到批量部署或者环境的一致性,肯定少不了容器技术和其中的代表 Docker ,为什么我不用 Docker 作为保证环境一致性的手段呢?
这当然和我的工作环境有关,我认为所有人的技术经验和倾向,都来自实际工作的需要,环境造就了我们和我们的价值取向(技术路线)。就像一个公司如果有自己的框架,那肯定是一个和自己的业务紧密联系的框架。
一方面,我经常需要在一个服务器上部署多个应用,而每个应用的环境从开发阶段起就是受到了控制的,在应用之间是一致的,在开发环境和生成环境之间也是一致的,所以没必要再给每个应用都带一套环境。
另一方面,从实际情况出发,现阶段针对特定问题,还免不了在生产环境上直接调试,不少客户都有部分接口或数据只在内网环境中可用,我们在自己的开发环境中只能进行有限的模拟,考虑到技术上的复杂性和成本上的权衡,只能违背理论上的最佳实践。既然要调试了,在主机上总比容器中容易,要不然就得把容器变成包含调试工具和编辑器的臃肿系统。既然如此,不如干脆免除容器,把复杂度降低。
讲到这里,顺便一提,即使是在生产环境进行的调试,解决问题后,代码最终还是要立刻走一遍从开发环境到生产环境的过程,以免下次的更新把这次的调整给覆盖。
回到适用情况,如果你的环境不像我的一样具有自主可控性,那么容器就会是一个好选择,比如客户提供给你什么操作系统都由不得你,自然需要容器来进行缓冲了。
事实上,我的环境也没有那么可控,甚至遇到过要在 Windows 7 上部署,也正是基于这个考量,虚拟机反而比 Docker 更有用武之地,至于虚拟机环境怎么装,Ansible 还有一战之力的。
实际上 Ansible 和容器技术并不互斥,虽然部分作用有点重复,但两者仍是相辅相成的,你可以搜搜 Ansible 和 k8s ,会发现很多结合使用的例子。
好了,有关服务器部署的决策与实践的系列文章暂时就到这里了,过段时间还会有更多其他关于选择和决策的问题可以聊!