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-config —— ~/.ssh/config 客户端配置文件

一句话定义

~/.ssh/config 是 OpenSSH 客户端(ssh、scp、sftp、rsync -e ssh、git over ssh 都共用它)的"连接档案"——把"用什么用户、走哪个端口、用哪把私钥、是否走跳板机、是否端口转发"统统压缩成一个别名。

典型场景

  • 训练营 5 节点 + 阿里云 10 节点 + 跳板机 + GitHub 多账号 = 15+ 条不同的连接配置。命令行里每次手写 ssh -i ~/.ssh/id_rsa -p 26292 root@103.47.83.39 是不可能的。
  • Day0-setup.md §2.4:给 5 节点集群写好 alias 之后,ssh m1 'kubectl get nodes' 直接出结果。

它和 /etc/hosts 完全不是一回事

这是新手最常误解的点。

维度/etc/hosts~/.ssh/config
管谁整个操作系统的名字解析(ping、curl、ssh 都生效)只管 OpenSSH 工具链
能做什么只能做一件事:主机名 → IP用户、端口、私钥、跳板、端口转发、保活…几十项
作用域系统级(要 sudo 改)用户级(自己改自己的,互不干扰)
写完之后ping m1 会通ping m1 不通,但 ssh m1 通

简单说:/etc/hosts 只解决"名字",ssh config 解决"怎么连"。 两者可以同时用 —— /etc/hosts 写 10.0.24.28 m1,让所有工具都能用 m1 这个名字;~/.ssh/config 再额外补上 User、IdentityFile 等。


配置文件位置和优先级

OpenSSH 找配置的顺序,前者优先:

  1. 命令行参数:ssh -p 2222 -i ~/.ssh/foo user@host —— 最高优先级
  2. ~/.ssh/config —— 当前用户的私人配置
  3. /etc/ssh/ssh_config —— 系统级,影响所有用户
  4. OpenSSH 内置默认值

权限要求严格:chmod 600 ~/.ssh/config(其他用户能读到你的配置 = 泄露你的内网拓扑,所以 ssh 会拒绝跑)。


基本语法:Host 段

Host <alias> [alias2 alias3 ...]
  Key1 Value1
  Key2 Value2
  ...

Host <another-alias>
  ...

规则:

  • Host 行下面缩进的字段,只对这个段生效(缩进只是习惯,OpenSSH 不强制,但强烈建议)
  • 字段不区分大小写:HostName / hostname / HOSTNAME 等价
  • 一行 Host 可以写多个别名,空格分隔(详见下一节)
  • 段之间用空行分隔(不强制,但便于阅读)

多别名写法(训练营常用)

Host k8s-cp-1 m1
  HostName 10.0.24.28
  User root
  IdentityFile ~/.ssh/id_rsa

Host k8s-cp-1 m1 表示 k8s-cp-1 和 m1 都是这个段的别名,下面的配置对两者都生效。所以 ssh k8s-cp-1 和 ssh m1 完全等价。

为什么这么做:

  • 正式名 + 短名共存:脚本里写 k8s-cp-1 自描述,手敲时用 m1 省力
  • 不需要写两遍配置:传统做法是 Host k8s-cp-1 和 Host m1 各写一段,重复且容易漂移

最常用字段表

完整列表见 man ssh_config,下面是 90% 场景会用到的。

字段作用示例
HostName真实的 IP 或域名(重点:可以和 Host 别名不同)HostName 154.201.73.31
User登录用户User root
PortSSH 端口(默认 22)Port 26292
IdentityFile用哪把私钥IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly只用 IdentityFile 指定的 key,忽略 ssh-agent 里的其它IdentitiesOnly yes
ProxyJump经过哪台跳板机(OpenSSH 7.3+ 推荐写法)ProxyJump bastion
ProxyCommand自定义连接命令(走 SOCKS5/HTTP 代理)ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p
LocalForward本地端口转发(外面访问 localhost:8080 = 远端的 80)LocalForward 8080 localhost:80
RemoteForward远程端口转发(远端访问 localhost:9000 = 本地服务)RemoteForward 9000 localhost:3000
DynamicForward把 ssh 当 SOCKS5 代理DynamicForward 1080
ServerAliveInterval多少秒没数据就发一次保活包(防 NAT 断流)ServerAliveInterval 30
ServerAliveCountMax连续多少次保活失败才放弃ServerAliveCountMax 3
StrictHostKeyChecking首次连接的指纹校验策略accept-new / no / yes
UserKnownHostsFile自定义 known_hosts 位置/dev/null(一次性机器,不留指纹)
PreferredAuthentications认证方式优先级publickey,password,keyboard-interactive
ForwardAgent把本地 ssh-agent 转发到远端(远端用本地 key)ForwardAgent yes(小心安全)

StrictHostKeyChecking 三个取值

值行为适用
yes严格:远端 key 必须在 known_hosts 里,否则拒连生产 / 敏感环境
accept-new首次自动信任并写 known_hosts(TOFU),之后严格推荐默认 —— 平衡安全和体验
no不校验也不写 known_hosts一次性的临时机器(云厂商动态实例)

阿里云那种"今天创建、明天销毁"的临时实例,写 no + UserKnownHostsFile /dev/null 可以避免每次 IP 变了都要手动清 known_hosts。


通配符与字段继承

匹配规则:自上而下扫描,所有命中的段合并;同一个字段,第一次出现的值生效(不是最后一次!)。

通配符 * ?

Host *.prod.example.com
  User deploy
  IdentityFile ~/.ssh/id_prod

Host web-? db-?
  User admin
  ProxyJump bastion
  • * 匹配任意字符(含空)
  • ? 匹配单个字符
  • web-? 能匹配 web-1、web-a,匹配不了 web-12

继承("全局兜底"模式)

Host github.com
  HostName github.com
  User git
  ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p

Host *
  ServerAliveInterval 30
  ServerAliveCountMax 3

Host * 段放在文件末尾或开头都行(OpenSSH 是合并而不是覆盖),它给所有连接补充保活配置。GitHub 段已经有 User,Host * 不会覆盖它("第一次出现的值生效"原则)。

⚠️ 反直觉的地方:先写的优先。如果你想让某台机器的 User 被全局规则覆盖,办法是去掉这台机器的 User 字段、让它"继承"全局。

Match 块(更精细的条件)

Match host *.internal user deploy
  IdentityFile ~/.ssh/id_deploy

Match 比 Host 强大得多 —— 可以按 user、host、exec(执行外部命令)等多条件匹配。日常用得少,记得它存在即可。


实战示例

场景 1:K8s 训练营 5 节点

来自 Day0-setup.md:

Host k8s-cp-1 m1
  HostName 10.0.24.28
  User root
  IdentityFile ~/.ssh/id_rsa

Host k8s-cp-2 m2
  HostName 10.0.24.29
  User root
  IdentityFile ~/.ssh/id_rsa

# ... 后续 3 个节点同理

Host *
  ServerAliveInterval 30
  ServerAliveCountMax 3

用法:

ssh m1                           # 登 cp-1
ssh m4 'kubectl get nodes -o wide'
scp -r ./manifests m1:/root/     # scp 也会读 ssh config
rsync -av ./data m2:/data/       # rsync 默认走 ssh,也认 config

场景 2:跳板机(公司常见)

公司机器只能从跳板机进,传统做法是先 ssh bastion,再 ssh dev-01,烦。用 ProxyJump 一步到位:

Host bastion
  HostName 124.236.26.209
  Port 2224
  User huoyuanjun

Host dev-01
  HostName 10.1.0.34
  User root
  ProxyJump bastion              # 关键

ssh dev-01 —— OpenSSH 自动先连 bastion,再从 bastion 内开一条到 10.1.0.34 的隧道。中间所有流量都被 ssh 加密包装。

也可以多级跳:ProxyJump bastion1,bastion2。

场景 3:GitHub 多账号

一台机器,要在两个 GitHub 账号(个人 + 公司)下提交。两套 key、两套 user.email。

# 个人账号
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes

# 公司账号(注意 Host 是自定义别名)
Host github-work
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes

clone 时:

git clone git@github.com:me/personal-repo.git          # 走个人 key
git clone git@github-work:company/work-repo.git        # 走公司 key(注意 host 是 github-work)

IdentitiesOnly yes 必须加。否则 ssh 会先把 ssh-agent 里所有 key 都试一遍,GitHub 看到第一把不对的 key 直接拒连(API rate-limit 还会被 ban)。

场景 4:通过 SOCKS5 代理访问 GitHub

国内访问 GitHub 经常超时。如果你已经有本地 Clash/V2Ray 在 7890 端口提供 SOCKS5 代理:

Host github.com
  HostName github.com
  User git
  ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p

nc -X 5 -x 127.0.0.1:7890 %h %p 让 nc 通过 SOCKS5(-X 5)连到 %h:%p(OpenSSH 把 hostname 和 port 替换进去)。SSH 把 nc 的 stdin/stdout 当成隧道用。


端口转发(一句话理解)

方向字段谁能访问用途
本地 → 远程LocalForward L:R本机访问 localhost:L → 远端的 R把远端服务"拉到"本地用
远程 → 本地RemoteForward R:L远端访问 localhost:R → 本机的 L把本机服务"推给"远端用
动态(SOCKS)DynamicForward P本机的 SOCKS5 代理在 P 端口把 SSH 当翻墙工具

典型例子:从本地访问远端 K8s dashboard:

Host k8s-cp-1 m1
  HostName 10.0.24.28
  User root
  LocalForward 8001 localhost:8001

ssh m1 后挂着不退出,本地浏览器开 http://localhost:8001 就是远端 kubectl proxy 的页面。

也可以临时用命令行:ssh -L 8001:localhost:8001 m1,效果一样、但不持久。


调试:ssh -G 和 ssh -v

ssh -G <alias> —— 看最终生效配置

不实际连接,把所有 Host 段合并后的最终值打印出来。改 config 之后必跑这个,比 ssh <alias> 试错快得多 —— 连不上你不知道是网络问题还是配置写错。

ssh -G m1 | grep -E "^(hostname|user|identityfile|port)"
# hostname 10.0.24.28
# user root
# port 22
# identityfile ~/.ssh/id_rsa

ssh -v / -vv / -vvv —— 看握手过程

ssh -v m1 'echo OK'

-v 显示连接日志(哪把 key 被尝试、远端响应什么)。90% 的"为什么连不上"问题用 -v 看完就懂了。

最常见的两类信号:

  • Offering public key: /home/.../id_rsa 然后 server refused our key → key 没在远端 authorized_keys
  • Authentications that can continue: password → 远端禁了 pubkey 认证(见 sshd.md)

常见踩坑

坑 1:明明写了 IdentityFile,还是用了别的 key

原因:ssh-agent 里有其它 key。OpenSSH 的默认行为是先尝试 agent 里所有 key,再用 IdentityFile。

修复:

IdentitiesOnly yes

加上这个,强制只用 IdentityFile 指定的那把。

坑 2:Permissions are too open 拒连

Bad permissions for ~/.ssh/config

修复:

chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/id_*           # 私钥同样要 600
chmod 700 ~/.ssh                 # 目录 700

坑 3:Host * 写在最前面,全局配置覆盖不掉特定段

不会被覆盖(OpenSSH 是"先到先得"),但很多人以为会,结果配置混乱。

正确做法:

  • 把特定段(具体的 Host)写在前面
  • 把通配段(Host *、Host *.example.com)写在后面
  • 这样阅读顺序和优先级一致,不绕。

坑 4:改了 config 但 git 还是用错的 key

git 是 fork 出来跑 ssh 的,所以 git 默认会读 ~/.ssh/config。但有几种例外:

  • 你用的是 GUI 客户端(SourceTree / Tower),可能有自己的 ssh 集成
  • 你在 ~/.gitconfig 里设了 core.sshCommand = /usr/bin/ssh -i ~/.ssh/some_key(会覆盖 config)

排查:GIT_SSH_COMMAND='ssh -v' git fetch 看实际跑的命令。

坑 5:alias 和 HostName 同名导致 DNS 解析意外发生

Host github.com               # alias = github.com
  HostName github.com         # 这里又写一遍真实 hostname
  ProxyCommand ...

这种重名是允许的,OpenSSH 不会混淆 —— 它先用 alias 匹配段,然后用 HostName 解析。但读起来容易绕,建议给 alias 起个能区分的名字(如 github-personal),除非你确实想让 ssh github.com 直接用这段。


关联命令

  • ssh —— 客户端命令本身
  • ssh-keygen —— 生成 IdentityFile 指向的密钥
  • sshd —— 远端守护进程(很多"连不上"的根因在远端)
  • man ssh_config —— 完整字段参考
在 GitHub 上编辑此页