写了三年Shell脚本,我是怎么被Ansible“打脸”救命的?

老实交代,以前我对自动化运维工具有点“迷之自信”。

那会儿刚入行,觉得自己那一手行云流水的Shell脚本简直无敌:for循环批量操作,sed正则替换配置,awk处理日志,这多极客啊?直到那个凌晨两点的“事故现场”,彻底改变了我的看法。

当时我们要紧急给生产环境的50台Web服务器打一个补丁,更新Nginx配置。我自信满满地敲下了那个用来追加配置的Shell脚本回车键。结果因为网络波动,脚本在一部分机器上中断了,我下意识地按了“重试”。

悲剧发生了——脚本里用的是echo >>追加写入,重试导致配置文件里出现了双份配置,Nginx重启直接报错,全线业务挂了15分钟。

那一刻我才明白,写脚本和做工程是两码事。也是从那时候起,我开始硬着头皮啃Ansible。今天我想以一个“过来人”的身份,不讲枯燥的概念,只聊聊如果你想摆脱“人肉运维”的苦海,Ansible Playbook(剧本)到底该怎么写,以及怎么避免我当年踩过的那些坑。

告别“一次性”脚本,拥抱“幂等性”

很多做开发或者运维刚转型的朋友,最大的思维误区就是把Ansible当成“批量执行Shell命令的工具”。如果你只是在Playbook里用shell模块跑命令,那你真的亏大了。

Ansible最核心的价值在于幂等性(Idempotency)。啥意思?就是同一个操作,无论你执行一次还是执行一百次,结果都是一样的,且不会产生副作用。

回到我那个惨痛的Nginx案例。如果用Shell写,你需要写大量的if判断来检查配置是否存在。但用Ansible的原生模块,画风是这样的:

- name: 确保Nginx配置文件包含安全策略
  blockinfile:
    path: /etc/nginx/nginx.conf
    marker: "# {mark} ANSIBLE MANAGED BLOCK"
    block: |
      server_tokens off;
      add_header X-Frame-Options SAMEORIGIN;

这个写法的妙处在哪?

  1. 自动判断:它会自动检查/etc/nginx/nginx.conf里有没有这段内容。
  2. 安全重试:如果有,它就什么都不做;如果没有,它就加上;如果内容不一样,它就修正。
  3. 结果导向:你不用管它是怎么写入的,你只需要告诉Ansible:“我要这个文件长这样”。

我有个同事大刘,之前死活不愿意学Ansible,觉得写YAML麻烦。后来有次因断电导致脚本跑了一半,他花了一整天去排查哪台跑了哪台没跑。后来我让他试着用上面的逻辑跑了一遍Playbook,5分钟搞定。从那以后,他再也没提过用Shell批量改配置的事。

让代码像“说明书”一样易读

大家回想一下,当你接手前任留下的几百行Shell脚本时,是什么心情?

是不是充满了各种var1temp_file这种莫名其妙的变量?代码逻辑像迷宫一样?我曾经接手过一个项目,光是读懂那个初始化环境的脚本就花了我整整两天。

Ansible Playbook 强迫你把运维操作变成文档。

配图

这就好比做菜。Shell脚本像是那种只有大厨看得懂的“少许、适量、火候自便”;而Playbook则是标准的工业化SOP(标准作业程序)。

来看看这个对比:

普通脚本风格:

# 安装软件
yum install -y httpd
# 启动
systemctl start httpd

你不知道这步是为了干啥,也不知道失败了会怎样。

Playbook风格:

- name: 部署Web服务全流程
  hosts: webservers
  tasks:
    - name: 第一步:安装Apache服务
      yum:
        name: httpd
        state: present

    - name: 第二步:启动服务并设置开机自启
      service:
        name: httpd
        state: started
        enabled: yes

配图

我现在的习惯是,每周五下午进行复盘时,只要把Playbook打开,对着YAML文件给新人讲一遍,他们基本就能明白整个系统的部署架构。代码即文档,这句话真不是虚的,它能帮你省下大量写Wiki和解释的时间。

变量与模板:不要把配置“写死”

刚开始写Playbook时,我最爱干的事就是Hardcode(硬编码)。比如把数据库IP直接写在配置文件任务里。

结果到了年底,公司做架构调整,数据库迁移了。我不得不打开十几个Playbook文件,一个个查找替换。那种绝望感,谁试谁知道。

真正的自动化,是把“逻辑”和“数据”剥离开。

这时候,Ansible的Jinja2模板引擎就派上用场了。

举个真实场景:我们要给开发环境(Dev)、测试环境(QA)和生产环境(Prod)部署同一套应用,但它们的端口和数据库地址不一样。

做法是这样的:

  1. 创建一个模板文件 app_config.j2

    [database]
    host = 
    port = 
    
  2. 在Playbook里调用:

    - name: 分发配置文件
      template:
        src: app_config.j2
        dest: /opt/app/config.ini
    
  3. 在变量文件里定义不同环境的值。

这套方法落地后,效果立竿见影。上个月我们做灾备演练,需要把全套环境切换到备用机房。我只需要修改group_vars里的几个IP变量,运行一遍Playbook,10分钟内,30多个微服务的配置全部自动更新并重启完毕。

要是放在以前用脚本处理,我估计得拉上整个运维组通宵加班。

别光看不练,给你3个落地建议

说了这么多,我知道很多朋友还在犹豫:“看起来好难,我还是用老办法吧。”

其实技术债都是这么欠下的。与其等到像我当年那样半夜炸雷,不如现在就开始填坑。Ansible其实是DevOps工具链里门槛最低的一个,不需要Agent,只要能SSH就能跑。

如果你想开始尝试,我建议你这周只做这三件事:

  1. 环境准备:在你的笔记本或者一台跳板机上装好Ansible(pip install ansible 或者 yum install ansible),不需要复杂的配置。
  2. 写第一个Hello World:不要试图上来就搞复杂的部署。写一个Playbook,只做一件事:在所有目标服务器的 /tmp 目录下创建一个叫 success.txt 的文件。 跑通这个,你就理解了核心流程。
  3. 重构一个小脚本:找一个你平时最常用的、不超过20行的Shell脚本(比如清理日志、同步时间),把它翻译成Ansible Playbook,并设定为每天自动运行。

当你第一次看着屏幕上那一排绿色的“OK”和黄色的“Changed”滚动闪过,相信我,那种掌控感会让你上瘾的。

最后留个问题:你们现在的日常运维中,哪一个重复性操作让你最头疼?或者如果你已经用了Ansible,踩过什么印象深刻的“坑”?欢迎在评论区聊聊,我们一起避雷。