boto3 SSM如何按序向远程实例发送多条命令并获取各命令输出
使用SSM向远程实例按顺序发送数十条命令时,如何实现在执行下一条命令前获取到当前命令的输出?
通过boto3 SSM的send_command接口可以在EC2实例上远程执行命令,基础调用示例如下:
resp = client.send_command( DocumentName="AWS-RunShellScript", # AWS预配置的官方执行文档 Parameters={'commands': commands}, InstanceIds=instance_ids, )
- 该接口的同步返回结果中不包含命令的实际执行输出
- 常规获取输出的方式是调用
get_command_invocation接口,但需要持续轮询该接口直到远程命令执行完成,才能拿到完整结果,流程繁琐且容易触发SSM接口限流。
在不使用SSH的前提下,远程按顺序执行多条命令(包含向实例上传文件的操作),除反复调用send_command并轮询get_command_invocation的方案外,是否存在更优的实现方式?
方案1:单脚本封装全流程+CloudWatch Logs实时回传(推荐,运维成本最低)
不需要逐条发送命令轮询结果,把所有需要按顺序执行的逻辑、文件上传步骤全部封装到一份Shell脚本中,作为单条命令传给send_command,每执行完一个步骤就把输出、执行状态打印到标准输出,同时配置SSM命令输出直接投递到CloudWatch Logs:
- 文件上传不需要单独调用接口:100KB以内的小文件直接将内容base64编码后嵌入脚本,在实例端解码落盘即可;大文件提前上传到S3,脚本中加一行
aws s3 cp命令拉取即可,全程不需要SSH支持 - 顺序控制直接在Shell脚本内实现:上一步命令返回非0退出码时直接终止后续流程,打印错误信息,所有输出会近实时推送到CloudWatch Logs,延迟通常在1-2秒
- 不需要轮询
get_command_invocation:本地只需要定时拉取对应日志流的新增事件即可拿到每一步的输出,接口配额占用远低于轮询invocation接口,几乎不会触发限流
方案2:Session Manager交互式长连接(适合需要动态决策后续命令的场景)
如果需要根据上一条命令的输出动态调整后续要执行的命令,不要使用单次执行的send_command模式,直接调用SSM的start_session接口和目标实例建立长连接:
- 连接基于WebSocket通道,不需要实例开放22端口、不需要管理SSH密钥,交互逻辑和本地SSH终端一致,输入命令后可以实时读取输出,天然满足“执行完上一条拿到结果再发下一条”的需求
- 文件传输直接使用SSM Session自带的文件传输能力,不需要额外开发上传逻辑
方案3:官方Waiter替代自定义轮询(兼容现有逐条发命令的逻辑)
如果业务逻辑已经固定为逐条发送命令的模式,不要自己写死循环轮询get_command_invocation,直接使用boto3 SSM封装好的waiter:client.get_waiter('command_executed')。Waiter内部已经实现了合理的轮询间隔、异常重试、状态判断逻辑,比自定义轮询代码更稳定,也能避免轮询频率过高触发限流。
注意:单条
send_command传入的命令参数总大小不能超过SSM的64KB限制,如果脚本逻辑比较复杂,不要把全部脚本内容直接塞到参数里,可以把脚本存在S3或者Systems Manager Parameter Store,在命令里加一行拉取脚本后执行的逻辑即可。
内容的提问来源于stack exchange,提问作者theahura

