先手动验证 ssh 连接是否可靠:ssh -i ./id_rsa -o stricthostkeychecking=no user@host "echo ok";再用 os/exec 调用 ssh/scp,注意参数拆分、权限 0600、multiwriter 实时捕获日志、scp 路径绝对化、命令链式执行、flag fallback 环境变量。

用 os/exec 执行远程命令前,先确认 SSH 连接是否可靠
直接调用 ssh 命令跑部署脚本,最常卡在连接阶段——比如私钥权限不对、known_hosts 冲突、或目标主机没开 SSH。别急着写 Go 逻辑,先手动跑通这行命令:
ssh -i ./id_rsa -o StrictHostKeyChecking=no user@host "echo ok"
如果失败,Go 脚本里再怎么封装也白搭。常见坑包括:
-
id_rsa文件权限必须是0600,否则 OpenSSH 拒绝读取 -
StrictHostKeyChecking=no要显式加,否则首次连接会阻塞等待输入 - Go 中用
exec.Command("ssh", ...)时,参数不能合并成一个字符串(比如"-i ./id_rsa user@host cmd"),必须拆成独立string元素
用 io.MultiWriter 同时捕获 stdout 和 stderr 并实时输出
部署过程需要看到进度,但默认 cmd.Output() 是等命令结束才返回,无法流式观察。正确做法是用 cmd.Stdout 和 cmd.Stderr 分别赋值,并共享一个 io.Writer:
var buf bytes.Buffer mw := io.MultiWriter(os.Stdout, &buf) cmd.Stdout = mw cmd.Stderr = mw
这样终端能实时刷日志,同时 &buf 还能供后续判断成败(比如检查是否含 "error:" 或退出码非 0)。注意:cmd.Run() 才真正执行并阻塞,别误用 cmd.Start() 后忘了 Wait()。
部署包上传用 scp 而不是自己实现 SFTP
有人想用 golang.org/x/crypto/ssh 手写 SFTP 上传,其实大可不必。只要本地装了 scp,就复用它更稳:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
cmd := exec.Command("scp", "-i", "./id_rsa", "-o", "StrictHostKeyChecking=no", "./app.tar.gz", "user@host:/tmp/")
关键点:
- 路径必须是绝对路径,
~/在scp里不展开 - 如果目标目录不存在,
scp不会自动创建,得先用ssh创建好(比如mkdir -p /opt/myapp) - 传完立刻解压,别分开两步——网络中断后状态难恢复,建议打包成单条命令:
ssh ... "tar -xzf /tmp/app.tar.gz -C /opt/myapp && systemctl restart myapp"
用 flag 包支持环境变量 fallback,避免硬编码 host/user
脚本要能切测试/生产环境,但又不想每次改代码。用 flag.String 定义参数,再 fallback 到环境变量:
host := flag.String("host", os.Getenv("DEPLOY_HOST"), "target host")
user := flag.String("user", os.Getenv("DEPLOY_USER"), "login user")
这样既可以用 ./deploy -host prod.example.com -user admin 显式指定,也能提前设好环境变量批量执行:
DEPLOY_HOST=prod.example.com DEPLOY_USER=admin ./deploy
容易忽略的是:flag 解析必须在所有逻辑前调用 flag.Parse(),且未定义的 flag 会导致 flag.ErrHelp 自动打印帮助——这点比手写 os.Args 解析更健壮。
真实部署链路比想象中脆弱:网络抖动、磁盘满、systemd unit 文件路径错位、甚至 tar 版本不兼容导致解压失败。别指望一次写完就稳,重点是让每一步都可重入、可观察、可中断回退。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










