10年前,有一个问题:在许多服务器上通过 SSH 自动运行命令。
我基本上有相同的任务,但我需要使用(可能不同的)参数运行命令/脚本,并且需要停止长时间运行的任务。另外,我更喜欢现代监视器(例如至少 Web UI 或弹性输出),这样我就可以监视哪些脚本正在运行和/或完成以及结果如何。最终最好让他们排队,或者给予一些时间限制。另外,我无法将我的公钥添加到所有计算机,但我可以(让某人)在那里安装一些软件。
这主要用于人工智能训练过程,但也适用于升级框架并最终发送(下载)新的脚本和数据。
在上面的链接中,大家建议使用 Ansible,我认为自动化方式是现代方式,但是还有其他方式吗?
一位朋友还建议了 CI/CD(gitlab 操作),但这似乎有点太多了,而且用于代码测试等其他目的。我还得到了 AutoML 的提示,但这是一个完整的 AI 框架,我不需要它,因为我还需要运行带有各种参数的多个命令/脚本。
答案1
将参数放入字典中。
- 例如,让我们从默认开始
shell> cat group_vars/all/scripts.yml
scripts:
default:
script: /root/bin/default.sh
params: p1 p2 p3
timeout: 30
retries: 10
delay: 3
log: /tmp/ansible_script.log
给定控制器上的脚本
shell> tree files
files
├── default.sh
├── script_A.sh
├── script_B.sh
└── script_C.sh
shell> cat files/default.sh
#!/bin/sh
echo $1 $2 $3
echo finished > /tmp/ansible_script.log
exit 0
下面的剧本
shell> cat pb.yml
- hosts: all
vars:
my_script: "{{ scripts[inventory_hostname]|d(scripts['default']) }}"
_script: "{{ my_script.script|d(scripts.default.script) }}"
_params: "{{ my_script.params|d(scripts.default.params) }}"
_timeout: "{{ my_script.timeout|d(scripts.default.timeout) }}"
_retries: "{{ my_script.retries|d(scripts.default.retries) }}"
_delay: "{{ my_script.delay|d(scripts.default.delay) }}"
_log: "{{ my_script.log|d(scripts.default.log) }}"
tasks:
- debug:
msg: |-
_script: {{ _script }}
_params: {{ _params }}
_timeout: {{ _timeout }}
_retries: {{ _retries }}
_delay: {{ _delay }}
_log: {{ _log }}
when: debug|d(false)|bool
- name: Copy script
block:
- file:
state: directory
path: "{{ _script|dirname }}"
mode: 0750
- copy:
src: "{{ _script|basename }}"
dest: "{{ _script }}"
mode: 0550
when: copy_script|d(false)|bool
- name: Run script
block:
- command:
cmd: "{{ _script }} {{ _params }}"
async: "{{ _timeout }}"
poll: 0
register: cmd_async
- debug:
var: cmd_async.ansible_job_id
when: debug|d(false)|bool
- name: Read log until finished
block:
- command:
cmd: "cat {{ _log }}"
register: cmd_log
until: cmd_log.stdout == 'finished'
retries: "{{ _retries }}"
delay: "{{ _delay }}"
- debug:
var: cmd_log.stdout
when: debug|d(false)|bool
when: read_log_fin|d(false)|bool
- name: Check async script
block:
- async_status:
jid: "{{ cmd_async.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: "{{ _retries }}"
delay: "{{ _delay }}"
- debug:
msg: >-
{{ job_result.start }}
{{ job_result.end }}
rc: {{ job_result.rc}}
when: debug|d(false)|bool
给出
shell> ansible-playbook pb.yml -e debug=true -e copy_script=true -e read_log_fin=true
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: |-
_script: /root/bin/default.sh
_params: p1 p2 p3
_timeout: 30
_retries: 10
_delay: 3
_log: /tmp/ansible_script.log
ok: [test_12] =>
msg: |-
_script: /root/bin/default.sh
_params: p1 p2 p3
_timeout: 30
_retries: 10
_delay: 3
_log: /tmp/ansible_script.log
ok: [test_13] =>
msg: |-
_script: /root/bin/default.sh
_params: p1 p2 p3
_timeout: 30
_retries: 10
_delay: 3
_log: /tmp/ansible_script.log
TASK [file] **********************************************************************************
ok: [test_13]
ok: [test_12]
ok: [test_11]
TASK [copy] **********************************************************************************
ok: [test_12]
ok: [test_11]
ok: [test_13]
TASK [command] *******************************************************************************
changed: [test_12]
changed: [test_11]
changed: [test_13]
TASK [debug] *********************************************************************************
ok: [test_11] =>
cmd_async.ansible_job_id: '754707567219.90860'
ok: [test_12] =>
cmd_async.ansible_job_id: '148176661548.90862'
ok: [test_13] =>
cmd_async.ansible_job_id: '688240445475.90861'
TASK [command] *******************************************************************************
changed: [test_13]
changed: [test_11]
changed: [test_12]
TASK [debug] *********************************************************************************
ok: [test_11] =>
cmd_log.stdout: finished
ok: [test_12] =>
cmd_log.stdout: finished
ok: [test_13] =>
cmd_log.stdout: finished
TASK [async_status] **************************************************************************
changed: [test_12]
changed: [test_13]
changed: [test_11]
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: '2022-08-01 16:02:50.287027 2022-08-01 16:02:50.320177 rc: 0'
ok: [test_12] =>
msg: '2022-08-01 16:02:49.770331 2022-08-01 16:02:49.801347 rc: 0'
ok: [test_13] =>
msg: '2022-08-01 16:02:50.189800 2022-08-01 16:02:50.343773 rc: 0'
PLAY RECAP ***********************************************************************************
test_11: ok=9 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_12: ok=9 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=9 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- 迭代期间无法显示中间日志。回调插件在迭代后将所有结果一起显示。如果你想观察中间日志,你必须走出 Ansible。例如,获取日志文件
- name: Fetch log until finished
fetch:
dest: /tmp/ansible/
src: "{{ _log }}"
until: lookup('file', my_logfile) == 'finished'
retries: "{{ _retries }}"
delay: "{{ _delay }}"
vars:
my_logfile: "/tmp/ansible/{{ inventory_hostname}}/tmp/ansible_script.log"
when: fetch_log_fin|d(false)|bool
这将在控制器上创建定期更新的文件
shell> tree /tmp/ansible/
/tmp/ansible/
├── test_11
│ └── tmp
│ └── ansible_script.log
├── test_12
│ └── tmp
│ └── ansible_script.log
└── test_13
└── tmp
└── ansible_script.log
在控制器上显示文件。例如,使用手表
shell> watch cat /tmp/ansible/test_11/tmp/ansible_script.log
为了测试它,下面的脚本以 $2 间隔向日志写入 $1 次
shell> cat files/script_A.sh
#!/bin/sh
for i in $(seq 1 $1); do
echo step $i > /tmp/ansible_script.log
sleep $2
done
echo finished > /tmp/ansible_script.log
exit 0
更新字典让楼主测试_11运行脚本
shell> cat group_vars/all/scripts.yml
scripts:
default:
script: /root/bin/default.sh
params: p1 p2 p3
timeout: 30
retries: 10
delay: 3
log: /tmp/ansible_script.log
test_11:
script: /root/bin/script_A.sh
params: 7 3
该剧本有删节。 (再次运行 playbook 之前请删除获取的文件。否则,任务将在最后一个文件上跳过。)
shell> ansible-playbook pb.yml -e debug=true -e fetch_log_fin=true
...
TASK [Fetch log until finished] **************************************************************
ok: [test_12]
FAILED - RETRYING: [test_11]: Fetch log until finished (10 retries left).
ok: [test_13]
FAILED - RETRYING: [test_11]: Fetch log until finished (9 retries left).
FAILED - RETRYING: [test_11]: Fetch log until finished (8 retries left).
FAILED - RETRYING: [test_11]: Fetch log until finished (7 retries left).
FAILED - RETRYING: [test_11]: Fetch log until finished (6 retries left).
changed: [test_11]
TASK [async_status] **************************************************************************
changed: [test_13]
changed: [test_12]
changed: [test_11]
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: '2022-08-01 18:00:13.304133 2022-08-01 18:00:34.768385 rc: 0'
ok: [test_12] =>
msg: '2022-08-01 18:00:13.413492 2022-08-01 18:00:13.480142 rc: 0'
ok: [test_13] =>
msg: '2022-08-01 18:00:13.537767 2022-08-01 18:00:13.731926 rc: 0'
PLAY RECAP ***********************************************************************************
test_11: ok=6 changed=3 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0
test_12: ok=6 changed=2 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0
test_13: ok=6 changed=2 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0
如果您阅读日志,输出将大致相同
shell> ansible-playbook pb.yml -e debug=true -e read_log_fin=true
...
TASK [Read log until finished] ***************************************************************
FAILED - RETRYING: [test_11]: Read log until finished (10 retries left).
changed: [test_12]
changed: [test_13]
FAILED - RETRYING: [test_11]: Read log until finished (9 retries left).
FAILED - RETRYING: [test_11]: Read log until finished (8 retries left).
FAILED - RETRYING: [test_11]: Read log until finished (7 retries left).
FAILED - RETRYING: [test_11]: Read log until finished (6 retries left).
changed: [test_11]
TASK [debug] *********************************************************************************
ok: [test_11] =>
cmd_log.stdout: finished
ok: [test_12] =>
cmd_log.stdout: finished
ok: [test_13] =>
cmd_log.stdout: finished
答案2
你的问题似乎有两个部分:
- 如何停止/启动/管理在(大)服务器列表上运行的脚本/进程?
- 如何跟踪或监视同一服务器列表上长时间运行的脚本/进程的状态?
Ansible 和其他配置管理软件(Puppet、Chef、Saltstack 等)可能是问题#1 的良好候选者,但不提供对它们启动/停止的进程的跟踪/监视,特别是如果您正在设想进度图或警告脚本出错。 (IMO) 监测系统将更好地服务于此类状态监测和报告。
回到问题#1,Ansible 以其在多个服务器上运行命令/脚本的能力而闻名,无论是预先配置的服务器还是您在命令行中当场键入的“临时”命令/脚本。 Saltstack 具有类似的功能,尽管它可能无法像 Ansible 那样处理复杂的临时命令。其他配置管理包在目标服务器上执行命令的直接方法较少,并且可能无法轻松配置来解决您在此处描述的问题。
关于问题#2,选择监控系统需要考虑很多因素,例如是否已经存在用于服务器基础知识(CPU、内存、磁盘)的监控系统,以及如何轻松地扩展它来跟踪脚本/进程。此外,如果这些服务器专用于运行您的脚本/进程,或者也运行其他东西。这是一个足够大的主题,可能值得单独提出一个问题,其中包含您想要监视的内容、您希望如何查看状态以及您可能喜欢什么类型的警报的详细信息。
答案3
傀儡螺栓相对轻松地完成您所描述的内容。我们已将其用于我所描述的复杂工作流程,并在短时间内与 terraform(配置)和 serviceNow(CR 验证)等其他工具绑定在一起。您的用例可能不需要集成 - 但最好了解一下这些需求的变化。
OP 提到不要将公钥放在目标节点上……我不确定我是否完全理解,但从表面上接受这一点,可以使用用户名和密码通过 Bolt 进行身份验证。
CI 工具(就像你的同事建议的那样)将是一种通过网络查看所有内容的优雅方式。