运维自动化脚本编写是提升运维效率的关键。但很多运维新手在编写脚本时常常陷入选择困难:到底用 Shell 还是 Python?今天我们就从几个实际场景出发,边操作边解释,帮你掌握脚本编写的核心技巧,并理解两者之间的取舍。
从日志清理脚本看 Shell 的威力
假设你有一台服务器,日志文件不断增长,磁盘空间告警。你希望写一个脚本,定期清理超过 7 天的日志。这种任务 Shell 是天然的选择。
#!/bin/bash
LOG_DIR="/var/log/myapp"
find "$LOG_DIR" -type f -name "*.log" -mtime +7 -delete
这个脚本的核心是 find 命令,它通过 -mtime +7 筛选出修改时间超过 7 天的文件,然后 -delete 直接删除。整个过程简单直接,没有复杂的逻辑。Shell 的优势在于它直接调用系统命令,对于文件操作、文本处理、管道组合等任务,效率极高。
但这里有个常见误区:直接使用 find -delete 可能误删重要文件。建议先使用 -print 预览结果,确认无误后再加入 -delete。另外,如果日志文件正在被进程写入,删除后可能引发问题,可以考虑使用 logrotate 工具,但那是另一个话题。
当 Shell 力不从心时:Python 的用武之地
假设你需要从多个服务器拉取指标数据,进行格式转换,然后推送到监控系统,比如 Prometheus。这种任务涉及网络请求、JSON 解析、数据聚合,Shell 脚本会变得复杂且难以维护。此时 Python 是更好的选择。
import requests
import json
# 从各服务器获取指标
servers = ["http://server1/metrics", "http://server2/metrics"]
all_metrics = {}
for url in servers:
resp = requests.get(url)
if resp.status_code == 200:
data = resp.json()
all_metrics.update(data)
# 推送到 Prometheus 的 pushgateway
push_url = "http://pushgateway:9091/metrics/job/some_job"
resp = requests.post(push_url, data=json.dumps(all_metrics))
Python 拥有丰富的第三方库,如 requests、paramiko 等,能够轻松处理网络、SSH、数据解析等复杂任务。而且 Python 代码的可读性和可维护性远胜于复杂的 Shell 脚本。Prometheus 官方文档指出,它收集和存储的是带时间戳的时序数据,这种数据往往需要程序化处理,Python 正是合适的工具。
但要注意,Python 脚本需要解释器环境,依赖管理也可能成为问题。在资源受限的嵌入式系统或最小化容器中,可能没有 Python 环境,此时 Shell 更轻量。
批量部署:Shell 与 Python 的协作
实际运维中,我们经常需要批量在多个服务器上执行操作,比如部署应用、修改配置。纯粹的 Shell 脚本可以使用 ssh 循环执行,但处理错误和并行化比较麻烦。Python 结合 paramiko 或 fabric 库能提供更优雅的解决方案。
from fabric import Connection
servers = ["web1", "web2", "db1"]
for host in servers:
conn = Connection(host)
result = conn.run("systemctl restart nginx", warn=True)
if result.failed:
print(f"{host} 重启失败")
else:
print(f"{host} 重启成功")
这个示例中,我们使用 Fabric 库远程执行命令,并检查返回状态。相比 Shell 的 ssh host command,Python 代码可以更精细地处理错误、并行执行、收集结果。当然,你也可以在 Shell 中使用 pssh 等工具,但 Python 的灵活性更高。
这里的关键取舍是:如果任务简单且服务器数量少,Shell 足够;如果任务复杂、需要错误处理和结果汇总,Python 更合适。
错误处理与可观测性
脚本不仅要实现功能,还要考虑错误处理和可观测性。在 Shell 中,使用 set -e 可以在命令失败时立即退出,但有时你希望捕获错误并继续执行。Python 的异常处理机制更成熟。
try:
result = requests.get(url, timeout=5)
result.raise_for_status()
except requests.exceptions.RequestException as e:
logging.error(f"请求失败: {e}")
同时,脚本应该输出清晰的日志,方便排查问题。OpenTelemetry 作为可观测性框架,强调遥测数据的收集和导出,虽然它主要用于应用监控,但脚本同样可以借鉴其思路,将执行状态、耗时等作为指标输出,集成到 Prometheus 等监控系统中。
CI/CD 中的脚本编写
在现代 DevOps 流程中,脚本通常嵌入到 CI/CD 流水线中,例如 GitHub Actions。GitHub Actions 允许你在工作流中直接运行 Shell 或 Python 命令,实现自动化构建、测试和部署。
name: deploy
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run deploy script
run: |
python deploy.py
在编写 CI/CD 脚本时,要注意环境一致性,确保脚本在流水线环境中能正常运行。GitHub Actions 官方文档强调,你可以组合各种 actions 来执行任意任务,但脚本本身的可移植性同样重要。
常见误区与失败条件
- 误区一:忽视命令的退出码。在 Shell 中,如果忘记检查退出码,脚本可能在错误状态下继续执行,导致更严重的故障。
- 误区二:硬编码路径和变量。脚本中直接写死路径,换环境就失效。应该使用变量或配置文件。
- 误区三:没有考虑幂等性。脚本重复执行应该产生相同结果,否则可能造成重复部署或数据错误。
失败条件:脚本在遇到权限不足、网络超时、依赖缺失等情况下会失败,编写时要预判这些情况,并给出明确错误信息。
总结与选择建议
回到最初的问题:Shell 还是 Python?没有绝对的答案,取决于任务类型。简单文件操作、系统管理用 Shell;复杂逻辑、数据处理、跨平台用 Python。实际项目中,两者经常结合使用,Shell 负责轻量级任务,Python 处理复杂核心。
无论选择哪种,都要注重脚本的健壮性、可读性和可维护性。希望本文的实战示例能帮你写出更高效的运维自动化脚本。如果你对 CI/CD 流水线设计感兴趣,可以参考我们的相关文章,进一步探索自动化运维的深度实践。
参考资料
延伸阅读
