轻量级远程脚本执行工具scriptc:告别ssh循环与Ansible之重

scriptc远程脚本执行批量运维 本内容遵循CC 4.0 BY-SA版权协议
Nicholas Qin Nicholas Qin 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要

我一直在找一个足够轻量的远程脚本执行工具。日常维护二三十台服务器,经常要干的事情其实很简单:把某个脚本分发过去,在目标机器上跑一遍,再拿回执行结果。Ansible 对这个规模来说有点重,安装代理、维护 playbook 语法、管理模块体系,都是成本;直接在终端里写 for 循环加 ssh 又太原始,错误处理和结果回收都靠人工盯,一不留神就漏一台。后来我花了一个周末写了 scriptc,一个单文件二进制、零依赖、只管"把脚本送过去、跑起来、拿回结果"这三件事的命令行工具。这个项目解决的核心问题,就是让我不再为了"在 N 台机器上执行一条命令"这种需求去维护一套复杂的配置管理生态。如果你也跟我一样,卡在"ssh 循环不够用、Ansible 又太重"的中间地带,这篇文章里的设计思路和踩坑记录应该对你有用。

1. 为什么要做一个轻量级的 scriptc:重工具和裸 ssh 都不顺手

先说清楚这个工具到底填补了什么空白。很多人第一反应是:这不就是 Ansible 的 ad-hoc 命令吗?确实,目标相似,但使用场景和心智模型差很远。

1.1 Ansible 的"重"体现在哪里

Ansible 本身是很好的工具,我生产环境里也在用,但它的设计目标是配置管理:幂等、声明式、模块化、可扩展。这意味着你得学习 YAML 语法、模块参数、变量优先级、inventory 的写法、playbook 的执行策略,一整套知识体系。如果只是心血来潮想给十几台机器打个补丁、跑个磁盘清理脚本,写一个 playbook 的功夫,手动 ssh 已经干完了。

而且 Ansible 在控制机上对 Python 环境有要求,虽然现在控制端要求不算苛刻,但总归是多一个依赖。我见过不少团队,为了跑个 Ansible 还得先解决控制机上的 Python 版本问题,这就有种为了喝杯水把厨房重新装修一遍的感觉。

1.2 裸 ssh 循环的问题一点也不少

不用 Ansible 的很多人会退回到 shell 循环:

BASH
for host in $(cat hosts.txt); do
ssh user@$host "bash /path/to/script.sh"
done

这个写法看起来很直接,用起来全是坑。首先,脚本传上去要单独处理,scp 一条命令、ssh 执行一条命令,执行完还要再想办法把输出和退出码收回来。其次,任何一台机器网络抖动、认证失败、脚本中途退出,循环就断了,前面的结果和后面的结果搅在一起,你根本分不清哪台跑成功、哪台失败了。再者,所有输出不加前缀混在一起,二十台机器的日志交织滚动,基本等于没有日志。

1.3 scriptc 的核心定位

所以我做 scriptc 的时候,给自己的定位非常明确:它是一个面向"批量脚本执行"场景的专用工具,不是通用配置管理平台。它只做三件事:

  • 分发:把本地脚本推送到目标机器
  • 执行:在目标机器上运行脚本,支持 sudo、超时控制、并发控制
  • 回收:把标准输出、标准错误、退出码、耗时统一收回控制端并整理展示

类比一下,Ansible 像一个搬家公司的全套服务,能给你规划家具摆放、打包、运输、拆装;scriptc 则像一个跑腿小哥,你只需要说"把这个东西送到这几家,看着他们签收,回来告诉我结果"。范围小,所以它可以做得又轻又直接。

这个工具的适用人群也很清晰:运维工程师、后端开发、SRE,以及任何手里握着几台到几十台 Linux 服务器、需要定期执行脚本或批量排查问题的人。不需要掌握一套新语法,只要会写 shell 脚本、会用 ssh key 登录,五分钟就能上手。

2. 环境准备与快速上手:从拉取到第一次跑通

既然要讲实操,先把环境准备这一关说清楚。这部分内容不复杂,但有一些细节直接决定后面用得顺不顺。

2.1 安装方式与依赖说明

scriptc 我设计成单文件二进制,没有额外的运行时依赖。最初我考虑过用 Python 写,打包分发麻烦,而且目标机器上不一定要装 Python;用 Go 写就是编译出一个静态二进制,控制机上扔哪儿都能跑。

安装就两条路:

BASH
# 方式一:直接下载对应平台的二进制
curl -LO https://example.com/releases/scriptc-v0.4.2-linux-amd64.tar.gz
tar -xzf scriptc-v0.4.2-linux-amd64.tar.gz
sudo mv scriptc /usr/local/bin/
 
# 方式二:有 Go 环境的话直接装
go install github.com/scriptc/scriptc@latest

装完验证一下:

BASH
scriptc version

能看到版本号就说明环境没问题。这里有个小提醒:如果你在 macOS 上使用,首次执行如果遇到"无法验证开发者"的提示,需要去"系统设置 - 隐私与安全性"里点一下"仍要打开",这是系统对未签名二进制文件的常规拦截。Windows 用户我建议直接装在 WSL 里,毕竟这个工具的目标机器基本是 Linux,在 WSL 里用最顺手。

2.2 准备目标机器清单

scriptc 用 inventory 文件来管理目标机器列表。格式很简单,每行一台机器,支持三种写法:

TEXT
# 方式一:直接用地址
192.168.1.10
192.168.1.11
 
# 方式二:地址加用户名
root@192.168.1.12
ops@192.168.1.13
 
# 方式三:地址加端口(非默认 SSH 端口时用)
192.168.1.14:2222

我自己的习惯是创建一个 hosts.txt 文件放在项目的 deploy/ 目录下,跟脚本、配置文件放一起,版本管理也方便。这里有个很重要的实践:inventory 文件里不要写密码,只写主机地址和用户名。凭证问题我后面专门用一个章节讲,这里先埋个伏笔。

2.3 第一次执行:跑个最简单的命令

先试一下最基础的 run 命令,在目标机器上执行一段内联命令:

BASH
scriptc run -i hosts.txt -u ops -c "hostname && uname -a"

执行完会看到类似这样的输出:

TEXT
[192.168.1.10] OK (0s) | output:
web-server-01
Linux web-server-01 5.15.0-86-generic #94-Ubuntu SMP ...
[192.168.1.11] OK (1s) | output:
db-server-01
Linux db-server-01 5.15.0-91-generic #96-Ubuntu SMP ...

每一台机器的输出都有主机名前缀,成功失败一眼就能看出来。这比 for 循环里裸 ssh 的输出体验好太多了。如果你只想看失败的结果,加个 --only-failed 参数:

BASH
scriptc run -i hosts.txt -u ops --only-failed -c "systemctl is-active nginx"

什么也不输出就是全部正常,只看到有输出的就是有问题的机器。这个参数在批量巡检的时候格外好用。

2.4 首次上手最容易忽略的细节

新手第一次用,最容易在三个地方卡住。

第一,确认控制机能免密登录目标机器。scriptc 默认走 SSH key 认证,如果你还没有配置免密登录,先在控制机上执行 ssh-copy-id user@host,把公钥装到目标机器上。这一步不准备好,后面全白搭。

第二,确认目标机器的 shell 环境。scriptc 默认通过 bash -c 执行命令,但有些精简系统默认 shell 可能是 sh,语法支持有差异,脚本里写的 [[ ]] 这类 bash 特性会报错。我的建议是在脚本开头加一行 #!/bin/bash,让脚本自己指定解释器。

第三,注意控制机和目标机器的系统差异。如果你的控制机是 macOS,核心命令参数(比如 sed -i)和 Linux 有差异,但 scriptc 的分发和回收是在控制机上完成的,命令会直接传给目标机器执行,这个差异一般不影响使用。

3. 核心场景实操:从简单的远程命令到完整脚本分发执行

run 命令只是开胃菜,日常用途里更常见的是执行一个完整的脚本文件。这一节我把实际工作中最常用的几个场景挨个过一遍,包括命令参数、执行逻辑和容易踩坑的地方。

3.1 scriptc run:远程执行命令的完整参数拆解

run 命令的完整参数比最开始演示的更丰富,我整理了一张表:

参数 作用 备注
-i, --inventory 指定目标机器清单文件 必填
-u, --user SSH 登录用户名 可被 inventory 中的用户名覆盖
-p, --password SSH 密码 不推荐,建议用 key 认证
--port SSH 端口 默认 22,也可在 inventory 中单独指定
-c, --command 要执行的命令 与 -f 互斥
-f, --file 要分发并执行的脚本 与 -c 互斥
--sudo 用 sudo 执行 非常常用
--sudo-user sudo 切换到指定用户 默认 root
--timeout 单台机器执行超时时间 默认 30s,单位秒
--concurrency 并发执行的机器数 默认 10
--only-failed 只显示失败机器 巡检时候的利器
--output-dir 结果输出目录 用于落盘保存

实际使用中,--sudo 和 --timeout 是我用得最多的两个参数。批量重启服务、修改系统配置,都需要 root 权限;而脚本一旦卡在某个交互确认上,没有超时控制就会一直挂到天荒地老。

3.2 scriptc push:只分发不执行

有些场景只要求把脚本推送到目标机器,后续手工在目标机器上执行,比如调试一个脚本、或者脚本需要配合人工操作逐步观察。这种场景用 push:

BASH
scriptc push -i hosts.txt -u ops -f cleanup_tmp.sh -d /tmp/scriptc_tasks/

这个命令会把 cleanup_tmp.sh 推送到所有目标机器的 /tmp/scriptc_tasks/ 目录下,目录不存在会自动创建。push 执行完,你可以再手动 ssh 上去执行,也可以在拿到结果后再用 run 配合远程路径执行。

3.3 scriptc run -f:分发加执行一步到位

如果要"送过去 + 跑起来 + 拿回结果"一步到位,用 -f 参数:

BASH
scriptc run -i hosts.txt -u ops -f deploy_nginx.sh --sudo --timeout 60

这个命令的实际执行逻辑是这样的:

  1. 控制机将 deploy_nginx.sh 通过 SFTP 推送到每台目标机器的临时目录,文件名带上随机后缀防止冲突
  2. 在目标机器上执行 bash /tmp/scriptc_tasks/deploy_nginx.sh.xxxx,如果需要 sudo 会附加 sudo
  3. 收集标准输出、标准错误和退出码
  4. 执行结束自动清理目标机器上的临时脚本文件
  5. 控制端汇总输出结果

我写过的一个比较典型的运维脚本是清理日志和临时文件:

BASH
# !/bin/bash
# 清理超过 7 天的日志和临时文件,保留最近 7 天的数据用于排查
LOG_DIR="/var/log/myapp"
TMP_DIR="/tmp/myapp_cache"
 
# 防止误删非空目录,加一层判断
if [ ! -d "$LOG_DIR" ]; then
echo "日志目录不存在,跳过清理"
exit 0
fi
 
# 删除超过 7 天的 .log 文件
find "$LOG_DIR" -type f -name "*.log" -mtime +7 -delete
echo "已清理 $LOG_DIR 中超过 7 天的日志文件"
 
# 清理临时文件
find "$TMP_DIR" -type f -mtime +3 -delete
echo "已清理 $TMP_DIR 中超过 3 天的临时文件"
 
df -h / | tail -1

执行命令就是:

BASH
scriptc run -i hosts.txt -u ops -f clean_cache.sh --sudo --timeout 60

配合 --only-failed 看结果,非常清爽。

3.4 并发控制:别让小任务变成大事故

批量执行最怕的就是"一把梭"导致目标机器压力过大。我在设计并发参数的时候,默认值取 10,意思是同时最多有 10 台机器在执行任务。

这个值是经验值,不是我拍脑袋定的。对大多数脚本来说,10 台并发不会对控制机造成压力,也不会对目标机器造成太大瞬时负载。但如果你的脚本需要执行重计算、大量磁盘 IO,或者目标机器本身负载就很高,建议手动调低:

BASH
scriptc run -i hosts.txt -u ops -f heavy_task.sh --concurrency 3 --timeout 300

有个细节值得注意:--timeout 的单位是秒,而且超时是"整机超时"而不是"单命令超时"。如果脚本内部有多个命令,每个都很短,但总体上超过 timeout 才返回,scriptc 会杀进程并上报超时错误。我之前在批量数据迁移的时候,脚本内部要跑二十分钟,忘记了调高 timeout,结果全被误杀,幸好脚本有断点续跑的能力。现在我都会养成一个习惯,拿不准执行时长就先 --timeout 600 起步,跑完看耗时再优化。

3.5 拿回结果:标准输出、退出码与落盘

执行完成后,结果回收是 scriptc 做的最好的部分之一。控制端会按照"主机排序 - 成功失败分类"来展示结果,每台机器显示 [主机地址] OK/FAIL (耗时) | output: ...。

对于需要留档的场景,用 --output-dir 参数把结果保存到本地目录:

BASH
scriptc run -i hosts.txt -u ops -f audit.sh --output-dir ./audit_results/

执行完成后,每个目标机器会生成一个以主机名命名的文件,内容包含标准输出、标准错误、退出码、执行耗时。这个文件是我做周报、月报的重要数据来源。有次排查某台机器上面突然多了个大文件,我就是靠 scriptc 的批量执行加文件落盘,把二十几台机器的情况统一收回来,快速定位到了那台机器上了一个测试脚本没回收、产出了几十 G 的中间数据。

4. 配置与权限管理:密钥、免密 sudo 和 inventory 文件的进阶用法

批量执行工具绕不开权限问题。这一节内容是我自己使用过程中踩坑最多的地方,包括 SSH 密钥管理、sudo 提权、以及 inventory 文件的进阶写法和最佳实践。

4.1 为什么说密码认证是下策

scriptc 支持 -p/--password 参数直接传 SSH 密码,但我强烈不建议在生产环境这么用。原因有三层:

第一,密码会泄露在 shell 历史记录和进程列表里。你执行 scriptc run -p "password123" ...,在 /var/log/auth.log 或者审计日志里都可能留下痕迹,同一台机器上其他用户通过 ps aux | grep scrip 也能看到参数。

第二,每台机器同一个密码是安全灾难,每台机器不同密码又没法管理。密码的轮换也是一件麻烦事,三十台机器改一圈密码,如果不用配置管理工具,靠人肉是真的会漏。

第三,密码认证天然不方便实现免密 sudo。你要是在命令行里传了 SSH 密码,默认无法在目标机器上再非交互式地输入 sudo 密码。脚本里涉及提权操作,要么配 sudo 免密,要么就得另想办法,非常拧巴。

所以我的原则:SSH 一律用 key 认证,sudo 一律配置免密提权(在可信环境内)。这样 scriptc 的命令就变得非常干净,不需要传任何敏感信息。

4.2 配置 SSH key 认证的正确姿势

SSH key 的配置比较基础,但有些细节仍然值得单独说一下。

控制机上生成密钥:

BASH
ssh-keygen -t ed25519 -C "scriptc-deploy-key"

为什么不推荐传统的 RSA 2048/4096?因为 ed25519 密钥更短、生成更快、安全性不输 RSA,而且现代 OpenSSH 版本都支持。生成后你会有两个文件:私钥 ~/.ssh/id_ed25519 和公钥 ~/.ssh/id_ed25519.pub。

然后把公钥分发到目标机器:

BASH
ssh-copy-id -i ~/.ssh/id_ed25519.pub ops@192.168.1.10

对多台机器,可以用一个简单的 shell 循环批量执行:

BASH
for host in $(cat hosts.txt); do ssh-copy-id -i ~/.ssh/id_ed25519.pub $host 2>&1 | tee -a ssh_copy.log; done

建议维护一个专门的部署公钥,而不是直接用控制机的个人公钥。好处是万一某台机器被攻破、公钥泄露,你可以只吊销部署用公钥,不牵连个人登录凭证。这个隔离思路跟云平台上的 Access Key 按项目隔离是一个道理。

4.3 sudo 免密的配置和风险权衡

批量执行提权命令是刚需。我上线的系统里,运维账号 ops 是可以直接 sudo 到 root 而不需要密码的。配置方法是在目标机器的 /etc/sudoers.d/ 下放一个文件,比如 /etc/sudoers.d/ops-nopasswd:

TEXT
ops ALL=(ALL) NOPASSWD: ALL

配好了之后,scriptc 的 --sudo 参数就畅通无阻:

BASH
scriptc run -i hosts.txt -u ops --sudo -c "systemctl restart nginx"

这里要提醒一句:给配置管理账号配 sudo 免密,本质上是拿可控性换操作性。如果目标机器上有未知攻击者已经拿到了 ops 权限,让他免密 root 等同于一马平川。所以这个模式适合内网、可信环境、且有基线安全审计的服务器集群。如果你的机器是公网暴露的,建议还是用 Ansible 那种更精细的提权机制,或者至少给运维账号加上登录 IP 白名单和双因子认证。

还有个细节:--sudo 默认是切换到 root 用户,跟直接在命令行写 sudo 完全等价。注意 sudo -u 切换到的用户必须有执行脚本的权限,否则会报 permission denied。比如:

BASH
scriptc run -i hosts.txt -u ops --sudo --sudo-user www -c "whoami"

如果 www 用户没有 /bin/bash 权限,这个命令会报错。常规的做法是把脚本文件权限放开,或者在 sudoers 里给 www 用户授权特定的执行路径。

4.4 inventory 文件的进阶写法

前面介绍过 inventory 三种基础写法,进阶场景下有几个技巧值得掌握:

按组分配机器。用方括号分组:

TEXT
[web]
192.168.1.10
192.168.1.11
 
[db]
192.168.1.12
192.168.1.13

配合 --group 参数只跑某个分组:

BASH
scriptc run -i hosts.txt --group web -c "uptime"

变量覆盖。inventory 里可以给单台机器指定不同的用户名或端口:

TEXT
ops@192.168.1.10
ops@192.168.1.11
root@192.168.1.12:2222

这样你调用 -u ops 时,前面两台用 ops 登录,但第三台用 root 和 2222 端口登录,不需要单独拆分命令。

注释和空行的处理。inventory 文件支持 # 注释和空行,方便写说明。我一般会在文件头部加上几行注释说明这是哪套环境的机器清单、密钥是哪个、模板命令是什么:

TEXT
# 生产环境 - 数据库集群
# 登录用户: ops (sudo免密)
# 密钥: ~/.ssh/id_ed25519
ops@db-cluster-01.internal
ops@db-cluster-02.internal

4.5 多环境管理:dev、staging、prod 的隔离

最后再聊聊多环境管理。很多人一开始只维护一个 hosts.txt,慢慢地 dev、staging、prod 的机器混在一起用,非常危险。我的做法是建一个目录,按环境区分 inventory:

TEXT
deploy/
├── hosts-dev.txt
├── hosts-staging.txt
├── hosts-prod.txt
└── scripts/
├── deploy_frontend.sh
└── clean_logs.sh

执行的时候显式指定环境文件,最大程度避免误操作生产环境:

BASH
scriptc run -i deploy/hosts-prod.txt -u ops -f deploy_frontend.sh --sudo --timeout 120

还有一个保护习惯:如果在 inventory 里混用多个环境的机器,建议在脚本开头加一个环境自检逻辑,比如检查某个只有生产环境才存在的标记文件:

BASH
if [ ! -f /etc/env_prod_marker ]; then
echo "非生产环境,拒绝执行"
exit 1
fi

这种保护看起来很笨,但关键时刻能救命。批量执行的可怕之处就在于你以为在 dev 机器上测试,手滑把命令传给了 prod 机器,指令飞快,等发现已经晚了。脚本执行前的这层检查,就是我给自己留的一条安全线。

5. 排错记录:我在 scriptc 使用中踩过的几个坑和完整排查过程

工具用久了,总会遇到奇奇怪怪的问题。这一节我把自己在 scriptc 实际使用中踩过的坑、排查过程和最终解决方案完整整理出来,大部分问题在其他远程执行工具里也有共性,相信对你有参考价值。

5.1 坑一:脚本明明有报错,退出码却是 0

这个坑我印象最深。有次写了个检查磁盘空间的脚本,在单台机器手动执行时能明显看到某个目录空间不足的告警,但通过 scriptc 批量跑完,所有机器全部显示 OK,退出码 0。

一开始怀疑是 scriptc 收集结果有问题,但我在本地把输出重定向到文件里查看,发现告警信息确实在标准输出里。随后我手动在目标机器上模拟执行:

BASH
bash /tmp/scriptc_tasks/check_disk.sh.xxxx; echo "exit=$?"

退出码确实是 0。这就奇怪了,脚本里明明有判断空间不足的 exit 1。

排查到这里我才发现:脚本里的判断只针对了某个特定目录,而那个目录在目标机器上不存在,于是 df -h "$target_dir" 失败了,但整个脚本没有捕捉这个失败,继续往后执行到最后一个命令 echo "检查完成",最终退出码是 0。

问题不在 scriptc,我的脚本逻辑有 bug。这给了一个重要的经验:脚本最后一个命令不应该是纯 echo 之类的成功结果,而应该让关键判断的退出码成为脚本的最终退出码。或者说,脚本本身应该更严格地检查每个关键操作:

BASH
# !/bin/bash
TARGET_DIR="/data/app"
if [ ! -d "$TARGET_DIR" ]; then
echo "目录不存在: $TARGET_DIR,请检查挂载"
exit 1
fi
USAGE=$(df -h "$TARGET_DIR" | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt 85 ]; then
echo "磁盘使用率超过 85%: ${USAGE}%"
exit 1
fi
echo "磁盘使用率正常: ${USAGE}%"

还有一个小细节:如果脚本最后一条命令是某条可能成功也可能失败的命令,可以显式地把关键判断变量保存下来,最后通过 exit $ret 返回:

BASH
ret=0
if [ "$USAGE" -gt 85 ]; then
echo "磁盘告警"
ret=1
fi
exit $ret

这样 scriptc 对失败机器的识别才会准确。

5.2 坑二:sudo 权限下脚本里居然没有 PATH 环境变量

有一次通过 scriptc 跑一个自动化部署脚本,脚本里调用了 docker compose,手动在目标机器上执行没问题,但通过 scriptc 的 --sudo 执行就报 docker: command not found。

排查链路是这样的:

  1. 先手动在目标机器上执行 scriptc run -i 单台 -u ops --sudo -c "docker --version",复现失败
  2. 直接 sudo bash -c 'docker --version',也失败
  3. 直接 bash -c 'docker --version',成功
  4. 对比环境变量发现,sudo 下的 PATH 跟普通用户不一样,/usr/bin 都没有 docker 所在的路径

问题的根源是:sudo 执行时会重置环境变量,PATH 被重置为系统默认值,而 docker 实际安装在 /opt/docker/bin 这种非标准路径下。解决办法有两个:

一是用绝对路径调用命令,比如把 docker 写成 /opt/docker/bin/docker。这个在脚本里比较复杂,因为 docker 还会 fork 子命令。

二是在脚本开头显式设置 PATH:

BASH
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/docker/bin"

我后来把这类环境变量设置统一放在了脚本开头,就再没遇到过 sudo 环境缺少 PATH 的问题。如果你用 scriptc 跑一些需要特定环境变量的脚本,建议在脚本开头做好 export,不要在脚本里隐式依赖用户的默认环境。这是远程批量执行和手动执行最大的差别之一:手动执行时有交互式 shell 的环境变量,脚本通过 sudo 执行时,这些都会被重置。

5.3 坑三:脚本超时偶尔比预期更早触发

有段时间批量做数据库备份,脚本逻辑是先压缩、再上传到对象存储、最后本地清理。用 --timeout 300 跑,偶尔会有机器 100 秒就显示超时。看输出文件,脚本正在上传大文件。

通过排查发现,超时的触发条件我理解错了:超时不是"命令执行的总时长",而是"脚本运行的总时长"。这个逻辑下,如果目标机器上脚本执行被 IO 阻塞拖慢了,可能确实会在 300 秒之前就被判定超时。scriptc 的超时实现是在控制端等待目标机器返回,超过设定时间就主动断开 SSH 连接并清理远端进程。

这里有一个很重要的经验:如果你脚本内部有多个步骤,总时长接近 timeout,建议把 timeout 设置为预估时长的 1.5 倍以上。比如预估脚本要跑 5 分钟,--timeout 至少给 450 秒;如果脚本内部有长时间网络传输,再往上加。脚本本身也可以用一些手段降低执行时长,比如备份时先压缩再上传、限制 find 的扫描深度,这些是另一个话题了,不展开。总之,脚本内部的耗时你必须有预估,留给执行层的余量要充足。

5.4 坑四:批量执行时部分机器连接挂死

有一次跑一批机器的配置同步脚本,有二三十台机器,其中两三台一直显示"SSH 连接中",既不成功也不失败。Concurrency 是 10,其他机器都正常跑完了,就那几台吊在那里。

排查第一步,控制机尝试手动 ssh 登录那几台机器:

BASH
ssh -o ConnectTimeout=10 ops@问题机器

发现能登录,但登录后 shell 交互很慢,输入命令要卡好几秒才响应。再进一步,用 top 和 free 看那几台机的负载,发现是磁盘 IO 被某个日志进程打满了,导致 SSH 连接建立后交互非常迟缓。

解决方案有两个层面。一个是操作层面,给 scriptc 跑的时候加上 --connect-timeout(连接超时),比如连接超过 10 秒直接判失败,避免无限等待:

BASH
scriptc run -i hosts.txt -u ops -f sync_config.sh --connect-timeout 10 --timeout 120

另一个是硬件层面,那几台机器磁盘 IO 有问题,该加资源加资源,该换盘换盘,这是运维的基本常识了。

这个坑提醒我:批量执行工具把几十台机器的任务并发出去,任何一台机器的异常都会被放大成一个"任务挂死",而不会像单机执行那样立刻暴露出来。所以控制面必须有连接超时和执行超时两个维度的兜底。你要是用 scriptc 做生产环境的变更,这两个参数最好都显式写上,别省事留默认值。

5.5 坑五:中文输出乱码

还有一个比较常见的问题:脚本里 echo 中文,在部分机器上回传回来是乱码。

这个问题的根源通常是目标机器的 locale 设置不是 UTF-8。有些精简系统的 /etc/locale.conf 没有配置,默认是 POSIX/C,导致脚本输出非 ASCII 字符时直接变成奇怪字符。

解决办法要么在脚本里显式设置 locale:

BASH
export LC_ALL=C.UTF-8
export LANG=C.UTF-8

要么在目标机器上确认 locale 配置。没有特别好的统一方式,因为机器环境不归我管,所以我在自己的脚本开头会默认加上这两行。当然后续 scriptc 也可以在收集输出时强制按 UTF-8 解码,但源头如果是乱码,解码也救不回来。

5.6 排查方法论:错误信息永远只看一半

总结这些坑,我最大的体会是:排错时永远不要把错误信息当最终答案。错误信息指出的是"现象"而不是"原因"。比如 sudo 环境下找不到 docker,错误信息是 command not found,但真正的原因是 PATH 重置;超时是"现象",但底层原因是 IO 阻塞拖慢了脚本执行。

我的排查套路是四步:

  1. 单台复现:把批量任务缩小到一台机器上,手动执行同一脚本,确认问题是否稳定出现
  2. 分层缩短链路:scriptc 完整链路是 控制机-ssh-远端shell-脚本,逐层缩短:先 scriptc 跑一次,再壳里直接 ssh 跑一次,再在目标机器上手动跑一次。这样能快速定位问题出现在哪一层
  3. 对比环境差异:比较手动执行的 shell、sudo 执行的 shell、目标机器的 PATH/locale 等环境变量差异
  4. 换参数复跑:把超时、并发等参数调低或调高,观察现象是否随之变化

按这套思路基本能把 99% 的问题定位到具体环节。

6. 脚本化与扩展玩法:把 scriptc 嵌进自己的工作流

工具本身只是起点。真正让 scriptc 发挥价值的是把它嵌到自动化流程里,跟现有工具链协作。这个章节分享一些我自己常用的扩展玩法,你可以根据自己的工作节奏直接抄。

6.1 用脚本包装 scriptc 命令

我几乎不会在命令行直接敲完整的 scriptc 命令,而是写一个 shell 函数或者小脚本,把 inventory、用户、sudo 等公共参数封装好。比如在 ~/.bashrc 或 ~/.zshrc 里加一段:

BASH
run_task() {
local env_file="$1" # e.g. prod
local task_name="$2"
scriptc run -i ~/deploy/hosts-${env_file}.txt -u ops --sudo --timeout 120 -f ~/deploy/scripts/${task_name}.sh
}

用的时候就很顺手:

BASH
run_task prod clean_cache

这比每次敲一长串参数安全得多,也减少出错概率。特别是有时我临时想跑一个命令,会担心参数写错导致影响面扩大,封装后相当于多了一层"固定入口"。

6.2 结合 CI/CD 做发布前巡检

我在 CI 流水线里加了一步"代码发布前集群自检"。流程是这样的:代码构建完,先在测试环境的机器上跑一遍自检脚本,确认基础状态正常再接下去,而不是直接推生产。

最简单的实现就是在 CI 脚本里调用 scriptc:

BASH
scriptc run -i deploy/hosts-staging.txt -u ops -f selfcheck.sh --only-failed --timeout 60
if [ $? -ne 0 ]; then
echo "自检出现异常,停止发布"
exit 1
fi
# 继续后续部署步骤

这里有一个细节:--only-failed 模式下,如果全部机器正常,退出码是 0;有任何一台异常,退出码非 0,CI 直接断掉。这样我可以把"批量自检"当成一个标准步骤插在发布流程中,靠脚本的退出码驱动发布的门禁判断。

6.3 结果文件的后续处理

前文提到 --output-dir 会把每台机器结果落盘。落盘文件再结合一个简单的处理命令,能做出一个轻量级的"执行报告"。

比如收集磁盘使用率:

BASH
scriptc run -i hosts-prod.txt -u ops -c "df -h / | tail -1 | awk '{print \$5}'" --output-dir ./disk_usage/
 
# 然后用 awk 合并所有结果到一个 summary 文件
for f in ./disk_usage/*; do
echo "$(basename $f): $(cat $f)" >> disk_usage_summary.txt
done

这个汇总文件,我可以直接用来看当前所有生产机器的磁盘水位,也可以进维护工单日期。某种程度上,scriptc 摇身一变成了一个"数据采集器",虽然不是专门干这个的,但足够轻量、足够灵活。

6.4 定时任务:定时巡检脚本

配合 cron 可以实现简单的定时巡检。crontab 里加一行:

TEXT
0 * * * * /usr/bin/scriptc run -i /home/ops/deploy/hosts-prod.txt -u ops -f /home/ops/deploy/scripts/health_check.sh --only-failed --timeout 90 --output-dir /home/ops/deploy/check_results/ >> /var/log/scriptc_check.log 2>&1

这个每小时跑一次的巡检,把结果写到一个固定目录下,异常自动在控制端显示。配合运维值班看板,故障能第一时间暴露。这里注意:cron 的环境变量和交互式 shell 的差异很大,所以 cron 里脚本跟手动执行一样,最好在脚本开头固定 PATH,以防依赖某个特定环境变量导致命令找不到。

6.5 面向团队的约定

最后,如果是团队一起用这个工具,我建议约定三条军规:

  • inventory 文件必须按环境隔离,生产环境的机器列表单独管理,改动走评审
  • 禁止在命令行直接传密码和明文密钥,全部走 SSH key + sudo 免密
  • 脚本必须先在小范围试跑,再放大并发,变更类任务遵循灰度逻辑

这三条看起来是常识,但真的执行起来能拦住一大批事故。团队里新同学刚开始用批量工具时,容易觉得"不就是多台机器执行一下吗",但一旦命令错误,影响面是成倍放大的。用 scriptc 这类工具,一定要在机制层面做好护栏。

7. 下一步:scriptc 还可以怎么长

我已经把 scriptc 用成了日常运维的一部分,但它的演进空间仍然不小。如果你也想自己维护或扩展一个类似的小工具,这几条路线值得考虑。

  • 支持动态 inventory:现在 inventory 是静态文件,下一步可以对接云平台 API,每次执行前自动拉取最新的机器列表,这对弹性伸缩场景非常重要。
  • 增加任务编排能力:目前 scriptc 只解决单脚本分发执行的场景,如果要串多个脚本、有依赖关系,就得自己做编排。可以引入简单的依赖描述,让一个"任务"由多个"步骤"组成,步骤内部维护执行顺序。
  • 结果展示的 TUI 增强:现在的终端输出已经够用了,但真要面向大规模集群,可以引入类似进度条、展开折叠的分组视图,让几十台机器的执行状态一目了然。
  • 批量日志采集与聚合:把 scriptc 的执行结果统一上报到日志平台,结合搜索和告警,能做出更主动的运维能力。

这些方向的核心都指向同一个理念:保持轻量,只做跟自己场景最匹配的向下兼容。我不打算把 scriptc 做成 Ansible 那样的平台,只希望它在"脚本分发执行"这件事上做到极致,并且能跟其他工具顺畅协作。

用到现在,scriptc 给我最大的收益不是省了那几分钟敲命令的时间,而是让批量操作从"人肉循环盯日志"变成了"结构化执行、结果可回收、过程可追溯"。我手里的机器规模不算大,正好是可以靠这种小工具覆盖的范围。如果你也在这个规模区间,不妨自己也写一个类似的,或者直接拿 scriptc 的思路去改一版更适合自己习惯的。工具不在于多大,能帮你在十几台、几十台机器之间来去自如,就已经值回票价了。

相关推荐

基于Python+Open CV的手势识别算法设计

伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。

努力学习前端的小陈

ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像

ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)

Willis Wang

计算机视觉图像处理-CV2与图像插值算法

用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int

weixin_38697274

基于水平集CV模型的图像分割(Matlab代码)

基于水平集CV模型的图像分割(Matlab代码),全套代码

小宇QAQ

导向滤波(guide_filter)的python实现

导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

zachzhan_g
返回顶部
Nicholas Qin
博客等级 码龄7年 34粉丝 8,421原创