如何在EC2远程实例上启动后台TCP服务器避免SSH阻塞
问题:SSH启动远程TCP服务器会话阻塞,无法批量启动集群节点服务
我正在搭建EC2实例集群用于大数据建模,希望贴近底层实现,不使用dask、DataBricks等框架。目前已能用boto3创建实例,通过paramiko实现SSH连接和文件传输。现在要在每个工作节点上启动TCP服务器,但执行BASH启动命令时,SSH会话会挂起,等待服务器进程结束,导致无法循环启动下一个实例的服务。
简化后的实现代码如下:
import paramiko import os def start_server(instance, server_code_fname: str, port: int, rsa_key): """ 参数: instance: AWS EC2实例的boto3 'Instance'句柄。 server_code_fname: 包含要在远程机器上运行的TCP服务器代码的文件名。 port: 远程机器监听的端口。 rsa_key: SSH连接所需的安全密钥。 预期效果: 应在远程机器上启动服务器并快速返回。 实际效果: 服务器启动,但SSH会话挂起——推测是在无限等待服务器进程结束,阻碍我在下一台机器上启动服务器。 """ dest = "/tmp/" + str(os.path.basename(server_code_fname)) copy_local_file_to_remote( instance, local_path=server_code_fname, remote_path=dest, rsa_key=rsa_key) ip = instance.private_ip_address # ---------------------------------------------------------------------- # 此命令会导致会话阻塞 command = f"nohup python3 {dest} --port {port} --ip {ip} & " # ---------------------------------------------------------------------- run_bash_command(instance, command, rsa_key) def copy_local_file_to_remote(instance, local_path: str, remote_path: str, rsa_key): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname=instance.public_dns_name, username="ec2-user", pkey=rsa_key, port=22) scp = ssh.open_sftp() scp.put(local_path, remote_path) scp.close() def run_bash_command(instance, command, rsa_key): ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_client.connect(hostname=instance.public_dns_name, username="appropriate_username", pkey=rsa_key) _, stdout, stderr = ssh_client.exec_command(command) ssh_client.close() return stdout, stderr
解决方案
核心问题原因
使用nohup加&后仍阻塞,是因为paramiko的exec_command默认会监听远程进程的标准输出和标准错误流,即使进程后台运行,只要这些流未被关闭,会话就会保持连接状态,直到流结束。
具体修复方法
1. 重定向所有输出到/dev/null
修改启动命令,将标准输出、标准错误全部重定向到/dev/null,彻底切断流的连接:
command = f"nohup python3 {dest} --port {port} --ip {ip} > /dev/null 2>&1 &"
> /dev/null:丢弃标准输出(stdout)2>&1:将标准错误(stderr)重定向到标准输出,最终一起丢弃&:让进程在后台运行
2. 确保命令执行完成后再关闭SSH客户端
原代码中在exec_command后立即关闭ssh_client,可能导致进程未完全启动就被终止。可以先等待命令执行完成(后台启动命令本身是即时完成的),再关闭客户端:
def run_bash_command(instance, command, rsa_key): ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_client.connect(hostname=instance.public_dns_name, username="appropriate_username", pkey=rsa_key) _, stdout, stderr = ssh_client.exec_command(command) # 等待后台启动命令执行完成(会快速返回) stdout.channel.recv_exit_status() ssh_client.close() return stdout, stderr
recv_exit_status()会等待远程命令的退出状态,后台启动命令会快速返回0,这样既不会阻塞,又能确保启动逻辑执行完成。
3. 可选:使用screen/tmux实现更可靠的后台进程管理
如果需要后续查看服务器日志或管理进程,可以用screen或tmux替代nohup,启动命令示例:
command = f"screen -dmS server_{port} python3 {dest} --port {port} --ip {ip}"
进程会在screen会话中后台运行,后续可通过screen -r server_{port}重新连接查看输出。
内容的提问来源于stack exchange,提问作者Steven Scott
相关产品推荐
相关产品推荐

