AI Infra 训练营
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub

ssh —— 远程登录与远程执行

一句话定义

ssh 是 OpenSSH 客户端:在本机和远端之间建立加密通道,让你可以登录远端 shell、远程执行命令、转发端口、传文件(间接,通过 scp/sftp/rsync)。

典型场景

训练营里 ssh 是出现频次最高的命令(出场 70+ 次)。三种主用法:

  1. 登录式:ssh m1 进入交互 shell,慢慢敲命令
  2. 单次执行:ssh m1 'kubectl get nodes' 拿一次结果就走
  3. 批量脚本:ssh m1 'bash -s' <<EOF ... EOF —— 把一整段脚本喂给远端 bash 执行(Day0 装机脚本就是这种)

基础用法

1. 登录式

ssh user@host                    # 进入交互 shell
ssh m1                           # 用 ~/.ssh/config 里的 alias
ssh -p 26292 root@103.47.83.39   # 显式指定非默认端口

退出:exit 或 Ctrl-D 或 ~.(连字符断开都没反应时的"紧急断开")。

2. 单次执行

ssh m1 'kubectl get nodes'
ssh m1 hostname
ssh m1 'ls /var/log | head'

记得加引号。不加的话本地 shell 会先把命令解析一遍:

ssh m1 ls $HOME                  # ❌ $HOME 是本地的,不是远端的
ssh m1 'ls $HOME'                # ✅ 单引号阻止本地展开,远端 shell 才解析 $HOME

3. heredoc 批量脚本(重点)

ssh m1 'bash -s' <<'EOF'
echo "Hello AI-Infra"
hostname
kubectl get nodes
EOF

这条命令为什么能跑通?拆开看:

  1. bash -s:-s 让 bash 从 stdin 读脚本(而不是从命令行参数或文件)
  2. <<'EOF' ... EOF:本地 shell 的 heredoc,把中间这段文本作为本进程(ssh)的 stdin
  3. ssh 自动转发 stdin:ssh 把本地 stdin 通过加密通道塞给远端 bash -s 的 stdin
  4. 远端 bash 收到这段脚本就执行

所以这条命令的本质是:本地把一段脚本通过 ssh 管道喂给远端 bash 解释器。

关键细节:'EOF' vs EOF

NAME="local-machine"

# 引号包围 EOF —— 本地不展开
ssh m1 'bash -s' <<'EOF'
echo $NAME             # 远端展开 → 远端 $NAME(多半为空)
hostname               # 远端 hostname
EOF

# 不加引号 —— 本地先展开
ssh m1 'bash -s' <<EOF
echo $NAME             # 本地展开 → 字面输出 "local-machine"
hostname               # 还没到远端就被本地 shell 看见,没影响
EOF

绝大多数情况都该加单引号:

  • 加引号:脚本原文发到远端,由远端解释。预期最一致。
  • 不加引号:本地 shell 先 eval 一遍 $var 和 $(...),可能误把变量替换成空、误把 $(date) 替换成本地时间。

远程脚本最常见的坑:忘记加 'EOF' 的引号 → $(...) 在本地被求值为空 → 远端跑了一段空脚本却没报错。


核心参数

参数作用备注
-p <port>指定端口比 config 里的 Port 优先
-l <user>指定用户等价于 user@host 写法
-i <keyfile>指定私钥一次性用某把 key
-o KEY=VALUE临时覆盖一个配置项-o StrictHostKeyChecking=no
-v / -vv / -vvv日志级别排错神器
-N不执行远端命令,只建隧道配合 -L -R -D
-f进入后台常与 -N 配合做长期端口转发
-T不分配伪终端跑非交互脚本时建议加
-t强制分配伪终端远端要跑 vim/htop 时必须
-L L:H:R本地端口转发见下方
-R R:H:L远程端口转发
-D <port>动态转发(SOCKS5)
-J <jumphost>跳板机一步到位等价于 ProxyJump
-G不连接,打印生效配置见 ssh-config.md
-q静默模式抑制 warning

-o 的常见组合

ssh -o StrictHostKeyChecking=accept-new \
    -o UserKnownHostsFile=/dev/null \
    -o BatchMode=yes \
    -o ConnectTimeout=5 \
    root@$ip 'echo OK'
选项用途
StrictHostKeyChecking=accept-new首次自动信任、之后严格(写脚本的最佳默认值)
UserKnownHostsFile=/dev/null临时实例,不污染主机的 known_hosts
BatchMode=yes禁用一切交互(密码框、确认框),失败就失败 —— 脚本里必加
ConnectTimeout=5默认是 TCP 重试好几分钟,脚本里要短超时

进阶用法

端口转发:把远端服务"拉到本地"

K8s dashboard、Grafana、Argo CD 这些通常只监听 cluster 内部。要在本地浏览器看,最简单的办法是 ssh 端口转发:

# 远端 kubectl proxy 监听 m1:8001,本地访问 localhost:8001 就能看到
ssh -N -L 8001:localhost:8001 m1

# 后台跑,不占终端
ssh -f -N -L 8001:localhost:8001 m1

-L L:H:R 的意思:本地 L 端口 → 通过 ssh 加密隧道 → 远端从远端视角访问 H:R。

H 是 远端视角下的地址。localhost 指远端自己。如果想转发到远端能访问、但你不能直接访问的内网机器:

ssh -L 5432:db-internal.local:5432 bastion
# 本地 localhost:5432 → bastion → 内网 db-internal.local:5432

远程端口转发:把本机服务推给远端用

# 让远端 m1 通过 localhost:3000 访问到你本机的开发服
ssh -R 3000:localhost:3000 m1

调试 webhook 类场景常用(让公网机器能回调你本地的服务)。

动态转发:把 ssh 当 SOCKS5 代理

ssh -D 1080 -N bastion

本地 localhost:1080 就是 SOCKS5 代理。配合浏览器 SOCKS 设置,访问公司内网网站。

跳板机一步到位

ssh -J bastion dev-01            # 命令行写法

等价于 config 里的 ProxyJump bastion。多级跳:-J b1,b2。

-t 远端跑交互工具

ssh m1 'sudo vim /etc/hosts'     # ❌ vim 屏幕花掉
ssh -t m1 'sudo vim /etc/hosts'  # ✅ 强制分配伪终端

ssh 默认在"非交互命令"模式下不分配 PTY;vim/htop/sudo 提示密码这些需要 PTY 才能工作。-t 强制分配。

-T 跑非交互脚本

ssh -T m1 < my-big-script.sh

与 -t 反向 —— 明确告诉 ssh 不要 PTY。能让 stderr/stdout 行为更可预测,写脚本时建议加。


真实场景:批量并行执行

训练营 5 台机器、阿里云 10 台机器,要每台都跑同一段配置。三种做法对比:

# 1. 串行 for 循环 —— 简单粗暴,5 台机器 5×30s = 2.5 分钟
for h in m1 m2 m3 m4 m5; do
  ssh "$h" 'apt-get update -qq && apt-get install -y curl jq'
done

# 2. 后台并行 —— 5 台机器 ~30s 全部完成
for h in m1 m2 m3 m4 m5; do
  ssh "$h" 'apt-get update -qq && apt-get install -y curl jq' &
done
wait                                    # 等所有后台 ssh 跑完

# 3. parallel-ssh / ansible —— 真正的生产做法
# Ansible 一行: ansible all -i hosts -m apt -a "name=curl,jq state=present"

并行版的注意点:& 让 shell 把每个 ssh 放后台、立即继续;最后 wait 同步。但输出会混在一起(多个 ssh 同时写 stdout),需要的话用 &> /tmp/log-$h.log 各自重定向到文件。


调试:连不上怎么办

第 1 步:用 -v 看握手

ssh -v m1

-v 一级日志通常够用。看几个关键行:

debug1: Reading configuration data /home/u/.ssh/config
debug1: /home/u/.ssh/config line 3: Applying options for m1
debug1: Connecting to 10.0.24.28 [10.0.24.28] port 22.
debug1: Connection established.
debug1: Offering public key: /home/u/.ssh/id_rsa RSA SHA256:...
debug1: Authentications that can continue: publickey,password
debug1: Server accepted key: /home/u/.ssh/id_rsa
debug1: Authenticated to 10.0.24.28

常见信号:

看到含义怎么修
Connection timed outTCP 都没通检查 IP、端口、防火墙;用 nc -zv host port 验证
Connection refused端口 reachable 但远端没有 sshd 监听检查 sshd 服务、确认端口号
Permission denied (publickey)TCP 通了、ssh 通了、但 key 不被认见下面
Authentications that can continue: password远端拒绝 pubkey 认证见 sshd.md

第 2 步:Permission denied (publickey) 的排查链

  1. 你用的是哪把 key?ssh -v m1 看 Offering public key 行
  2. 这把 key 的公钥在远端 ~/.ssh/authorized_keys 里吗?
    ssh m1 'cat ~/.ssh/authorized_keys'   # 还能登录的话直接看
    
  3. 权限对吗?
    ssh m1 'ls -ld ~/.ssh ~/.ssh/authorized_keys'
    # ~/.ssh             必须 700
    # ~/.ssh/authorized_keys  必须 600 或 644
    
  4. 远端 sshd 允许 pubkey 认证吗?
    ssh m1 'sshd -T | grep -i pubkey'
    # pubkeyauthentication yes  ← 应该是 yes
    
  5. 远端有没有 SELinux/AppArmor 阻拦?(CentOS 系常见)
    ssh m1 'getenforce'         # SELinux 状态
    

第 3 步:用 ssh -G 看配置生效

参数行为对不上配置预期时:

ssh -G m1 | grep -E 'hostname|user|port|identityfile'

不连接就能看到最终用什么 IP、什么 user、哪把 key。详细见 ssh-config.md。


常见踩坑

坑 1:Host key verification failed

远端 IP 变了(云厂商重新分配、机器重装系统),本地 known_hosts 里记的旧指纹对不上。

# 删除这个 host 的旧指纹(用 IP 或别名都行)
ssh-keygen -R 10.0.24.28
ssh-keygen -R m1

或者临时跳过校验(仅限一次性机器):

ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$ip

坑 2:长时间空闲就掉线(NAT/防火墙超时)

中间 NAT、运营商防火墙会定期清空闲的 TCP 连接。解决方法:

# ~/.ssh/config
Host *
  ServerAliveInterval 30      # 每 30s 发一次保活包
  ServerAliveCountMax 3       # 连续 3 次失败才断开

坑 3:heredoc 远程脚本里的引号被错误展开

ssh m1 'bash -s' <<EOF        # 注意:EOF 没引号
echo "now: $(date)"           # ❌ $(date) 在本地执行,远端打印的是本地时间
EOF

ssh m1 'bash -s' <<'EOF'      # ✅ 加引号
echo "now: $(date)"           # 远端执行 date,打印远端时间
EOF

默认习惯写 <<'EOF',需要本地变量时再有意识地去掉引号。

坑 4:脚本里 ssh 卡在密码提示

写在 cron 里的脚本,一旦 key 认证失败就会卡死等密码,没人能输。

ssh -o BatchMode=yes m1 'command'    # 禁掉所有交互、失败就立即退出

坑 5:ssh 进程不退出(端口转发挂着)

ssh -L 8001:localhost:8001 m1 跑完想要的事之后没退出。如果是 -f 后台模式,ssh 进程留在系统里:

ps aux | grep ssh                            # 找到 PID
kill <pid>
# 或者按转发端口找:
lsof -i :8001

坑 6:Pseudo-terminal will not be allocated

ssh m1 'sudo vim /etc/hosts'
# Pseudo-terminal will not be allocated because stdin is not a terminal.

加 -t:ssh -t m1 'sudo vim /etc/hosts'。


关联命令

  • ssh-config —— ~/.ssh/config 把这些 ssh 参数压缩成 alias
  • ssh-keygen —— 生成本地密钥对
  • sshpass —— 首次推 key 的密码自动化
  • sshd —— 服务端配置(很多"连不上"问题在远端)
  • scp / rsync —— 远程拷贝(共用 ssh 配置)
在 GitHub 上编辑此页