Ansible shell模块结合disown失效:后台进程终止且无输出
问题分析与解决方案:Ansible后台启动进程失效的原因及修复
为什么你的任务失效?
Ansible SSH会话的特殊性:
Ansible使用非交互式SSH会话执行任务,任务完成后会立即终止会话,同时关闭所有关联的文件描述符(包括后台进程的stdout/stderr)。
你用disown -h %1只是让进程忽略SIGHUP信号,但进程的输出流仍然绑定到Ansible的会话终端。当会话关闭时,这些流被切断:- Python的
print输出因为没有合法的输出目标而丢失; - 部分进程会因为尝试写入已关闭的文件描述符而直接退出,无法留存。
- Python的
本地/直接SSH执行正常的原因:
直接SSH登录属于交互式会话,执行命令后会话不会立刻终止,后台进程的输出流仍然关联到终端,disown后进程能正常存活并输出内容。
可行的解决方案
方案1:用nohup正确重定向输出
解决输出丢失和进程退出的核心是给后台进程的输出指定持久化目标,同时让进程忽略SIGHUP。修改任务如下:
- name: Start services with nohup ansible.builtin.shell: executable: /bin/bash cmd: | nohup python3 <<-'EOF' > /var/log/{{ item }}_service.out 2>&1 & {{ lookup("ansible.builtin.template", "../scripts/server_start.tmpl", template_vars={"service": item}) }} EOF loop: "{{ hostvars[inventory_hostname].services }}"
> /var/log/{{ item }}_service.out 2>&1:把stdout和stderr都重定向到日志文件,避免输出丢失;nohup:让进程忽略SIGHUP信号,即使终端关闭也能继续运行。
方案2:用systemd托管进程(生产环境推荐)
这是最稳定的生产级方案,把Python脚本做成systemd服务,由系统进程管理器负责生命周期管理:
- 先准备systemd服务模板(比如
service_template.tmpl):
[Unit] Description={{ service_name }} TCP Server After=network.target [Service] User=ubuntu # 替换为实际运行用户 ExecStart=/usr/bin/python3 /opt/services/{{ service_name }}.py Restart=always # 进程意外退出时自动重启 StandardOutput=journal+console StandardError=journal+console [Install] WantedBy=multi-user.target
- 编写Ansible任务部署并启动服务:
# 第一步:生成并部署Python脚本 - name: Deploy templated Python service script ansible.builtin.copy: content: "{{ lookup('ansible.builtin.template', '../scripts/server_start.tmpl', template_vars={'service': item}) }}" dest: "/opt/services/{{ item }}.py" mode: '0755' loop: "{{ hostvars[inventory_hostname].services }}" # 第二步:部署systemd服务文件 - name: Deploy systemd service file ansible.builtin.template: src: "../templates/service_template.tmpl" dest: "/etc/systemd/system/{{ item }}.service" mode: '0644' loop: "{{ hostvars[inventory_hostname].services }}" notify: Restart and enable service # 触发handler执行 - name: Flush handlers to apply changes ansible.builtin.meta: flush_handlers # 定义handler handlers: - name: Restart and enable service ansible.builtin.systemd: name: "{{ item }}" daemon_reload: yes state: restarted enabled: yes loop: "{{ hostvars[inventory_hostname].services }}"
优势:
- 进程由systemd托管,异常退出自动重启;
- 输出可以通过
journalctl -u {{ item }}.service查看; - Ansible可以直接管理服务的启动/停止/状态。
方案3:用screen临时托管进程(仅测试用)
如果只是临时测试,可以用screen创建后台会话运行脚本:
- name: Start services in screen session ansible.builtin.shell: executable: /bin/bash cmd: | screen -dmS {{ item }}_service python3 <<-'EOF' {{ lookup("ansible.builtin.template", "../scripts/server_start.tmpl", template_vars={"service": item}) }} EOF loop: "{{ hostvars[inventory_hostname].services }}"
screen -dmS:创建一个分离的后台会话;- 后续可以通过
screen -r {{ item }}_service连接会话查看输出。
注意:生产环境不推荐,因为screen进程本身可能被终止,无自动重启机制。
内容的提问来源于stack exchange,提问作者Filip
相关产品推荐
相关产品推荐

