Skip to content

Latest commit

 

History

History
371 lines (295 loc) · 13.2 KB

14-remote-ops.md

File metadata and controls

371 lines (295 loc) · 13.2 KB

14. 远程访问与文件传输

服务器没有显示器。你和大侠的机器之间只有一条 SSH 连接 —— 这条连接必须安全、可靠、能穿透。

这一层解决什么问题

  1. 怎么安全登录远端(认证与加密)
  2. 怎么在两端搬文件(scp / rsync / sftp)
  3. 怎么穿透网络限制(端口转发、跳板机)
  4. 怎么让会话不因断网丢失(tmux / screen)

一、SSH 原理

SSH = 加密隧道 + 认证 + 多路复用。

sequenceDiagram
    participant C as 客户端
    participant S as 服务端 (sshd:22)
    C->>S: TCP 连接
    S->>C: 发送主机公钥
    C->>C: 校验主机指纹(known_hosts)
    C->>S: 密钥交换(DH/ECDH)→ 生成会话密钥
    Note over C,S: 此后全部加密
    C->>S: 认证(公钥 / 密码 / OTP)
    S->>C: 认证通过,建立会话
Loading

两个"密钥"要分清(新手最常混淆):

主机密钥 用户密钥
谁的 服务器的身份 你的身份
存哪 服务端 /etc/ssh/ssh_host_*,客户端记在 ~/.ssh/known_hosts 私钥在客户端 ~/.ssh/id_*,公钥在服务端 ~/.ssh/authorized_keys
作用 防止中间人(确认连的是真服务器) 让你免密登录
变了怎么办 报 REMOTE HOST IDENTIFICATION HAS CHANGED 重新生成并部署公钥

二、SSH 客户端

2.1 基本用法

$ ssh user@host
$ ssh -p 2222 user@host                  # 指定端口
$ ssh user@host uptime                    # 直接执行命令(不进交互 shell)
$ ssh -i ~/.ssh/special_key user@host     # 指定私钥
$ ssh -v user@host                        # 调试(-vvv 更详细)← 连不上第一命令
$ ssh -o ConnectTimeout=5 user@host
$ ssh -t user@host sudo systemctl restart nginx   # 强制分配终端(sudo 需要)

2.2 密钥认证:从零到免密

# ① 生成密钥(ed25519 是现代推荐,比 RSA 更短更安全)
$ ssh-keygen -t ed25519 -C "axu@laptop"
# 也可生成 RSA(兼容老系统)
$ ssh-keygen -t rsa -b 4096 -C "axu@laptop"

# ② 部署公钥到服务端
$ ssh-copy-id user@host
# 手工方式:
$ cat ~/.ssh/id_ed25519.pub | ssh user@host 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'

# ③ 验证
$ ssh -o PreferredAuthentications=publickey user@host

服务端权限要求(权限不对,SSH 会静默拒绝公钥认证 —— 这是最常见的坑):

$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/id_ed25519          # 私钥,客户端侧
$ chmod 644 ~/.ssh/id_ed25519.pub
$ chmod 700 ~                        # 家目录权限也别太开放

2.3 ssh-agent 与密钥管理

$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_ed25519
$ ssh-add -l                            # 已加载的密钥
$ ssh-add -D                            # 全部清除
$ ssh-add -t 3600 ~/.ssh/id_ed25519     # 1 小时后自动失效
$ ssh-keygen -p -f ~/.ssh/id_ed25519    # 改私钥密码

keyring/agent 转发:ssh -A host 可以把 agent 转发到远端,让你在跳板机上用本地密钥。但这是安全风险 —— 跳板机的 root 可以用你的 agent 登录任何你有权限的机器。谨慎使用。

2.4 ~/.ssh/config:配置即代码

# ~/.ssh/config  ← 管理多台机器的核心文件
Host github.com
    HostName ssh.github.com        # 有些网络封 22 端口,走 443
    Port 443
    User git
    IdentityFile ~/.ssh/id_ed25519

Host pihole
    HostName 192.168.1.2
    User pi
    IdentityFile ~/.ssh/id_ed25519
    ForwardAgent no

Host vps
    HostName vps.example.com
    User ubuntu
    IdentityFile ~/.ssh/cloud.pem
    ServerAliveInterval 60         # 每 60 秒发心跳,防止 NAT 超时断开

Host internal
    HostName 10.0.0.5
    User deploy
    ProxyJump bastion              # ← 通过跳板机连接
    # 老写法等价于:ProxyCommand ssh -W %h:%p bastion

Host *
    ServerAliveInterval 30
    ServerAliveCountMax 3
    AddKeysToAgent yes
    HashKnownHosts yes

配好之后:ssh pihole、ssh internal 直接可用。

2.5 安全加固(服务端 /etc/ssh/sshd_config)

⚠️ 改 sshd_config 前必须:保留一个已登录会话;先在另一个终端验证新配置;sshd -t 检查语法。

Port 2222                          # 换端口(减少扫描噪音,不是真安全措施)
PermitRootLogin no                 # 禁止 root 直接登录 ← 基本要求
PasswordAuthentication no          # 只允许密钥 ← 防暴力破解最有效
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
X11Forwarding no                   # 不需要就别开
AllowUsers deploy axu              # 白名单
ClientAliveInterval 300
ClientAliveCountMax 2
$ sudo sshd -t                      # 语法检查 ← 必须做
$ sudo systemctl reload sshd        # reload 而非 restart(不断开现有连接)

改完 sshd 但你没验证:reload 不会断开已建立的连接,所以你的当前会话还活着。用第二个终端测试能不能新登录,确认后再关掉旧会话。

更进一步:用 fail2ban 或 sshguard 自动封禁爆破 IP(见 09 章)。

三、文件传输

3.1 scp(简单场景)

注意:OpenSSH 9.0 起 scp 默认改用 SFTP 协议(老的 -O 可回退)。

$ scp file.txt user@host:/tmp/
$ scp user@host:/tmp/file.txt ./
$ scp -r dir/ user@host:/tmp/            # 递归
$ scp -P 2222 file user@host:/tmp/       # 注意是大写 P(ssh 是小写 p)
$ scp -C bigfile user@host:/tmp/         # 压缩传输
$ scp -l 1000 file user@host:/tmp/       # 限速 Kbit/s
$ scp user1@h1:/f user2@h2:/f            # 两端都是远端(需 agent)

3.2 rsync(推荐主力)

rsync 的核心优势:增量传输 + 断点续传 + 保留属性 + 灵活过滤。

$ rsync -avz src/ user@host:/dest/            # 最常用组合
$ rsync -avz --delete src/ dest/              # 让目标与源完全一致(⚠️ 会删目标多余文件)
$ rsync -avz --dry-run src/ dest/             # 先演练,看会做什么 ← 好习惯
$ rsync -avzP big.iso user@host:/data/        # -P = 进度 + 断点续传
$ rsync -avz -e "ssh -p 2222" src/ host:/dest/
$ rsync -avz --exclude='*.log' --exclude='.git/' src/ dest/
$ rsync -avz --exclude-from=.rsyncignore src/ dest/
$ rsync -avz --bwlimit=5000 src/ dest/        # 限速 KB/s
$ rsync -avz --partial --progress src/ dest/
$ rsync -n -avzi src/ dest/                   # -i 显示变更项明细

参数速记:

参数 含义
-a 归档模式(= -rlptgoD,保留链接/权限/时间/属主)
-v 详细输出
-z 传输时压缩(局域网可不加,还费 CPU)
-P --partial --progress
-n / --dry-run 演练
--delete 删除目标端多余文件(镜像同步必用,手滑必伤)
--exclude 排除
-e 指定远程 shell
--bwlimit 限速

⚠️ 尾部斜杠的语义差异是 rsync 最经典的坑:

  • rsync -av src/ dest/ → 把 src 的内容放进 dest
  • rsync -av src dest/ → 在 dest 里创建 src 目录

另外 --delete 配合错的源路径等于删库。永远先 --dry-run。

3.3 sftp / 挂载

$ sftp user@host
sftp> put local.txt /remote/path/
sftp> get /remote/file.txt
sftp> ls -l ; cd ; lcd ; pwd ; bye

$ sshfs user@host:/remote/path /mnt/point -o reconnect   # 把远端目录挂载到本地
$ fusermount -u /mnt/point

3.4 大文件/多文件的取舍

场景 推荐
单文件、一次 scp
目录、重复同步 rsync
增量备份 rsync --link-dest(硬链接快照式备份)
极大量小文件 先 tar 打包再传(省握手开销)
两端都是远端 rsync + agent,或先拉到本地再推
# tar 流式传输(不落中间文件)← 很实用
$ tar czf - /data/dir | ssh user@host 'tar xzf - -C /backup/'
# rsync 增量备份(每天一份硬链接快照)
$ rsync -a --link-dest=/backup/prev /data/ /backup/today/

四、端口转发与隧道

这是 SSH 被严重低估的能力:它可以把任意 TCP 流量塞进加密隧道。

类型 参数 方向 用途
本地转发 -L 本地端口 → 远端目标 访问远端内网服务
远程转发 -R 远端端口 → 本地目标 把本地服务暴露给远端
动态转发 -D 本地 SOCKS 代理 全局代理上网
跳板 -J / -W — 穿过堡垒机
# 本地转发:把远端 5432 映射到本地 15432
$ ssh -L 15432:localhost:5432 user@dbhost
$ psql -h 127.0.0.1 -p 15432                  # 现在本地就能连远端数据库

# 穿过跳板访问内网第三台机器
$ ssh -L 8080:internal-web:80 user@bastion

# 远程转发:把本地 3000 暴露到远端的 8080(内网穿透常用)
$ ssh -R 8080:localhost:3000 user@public-host
$ # 需要在远端 sshd 里允许 GatewayPorts(如需绑 0.0.0.0)

# 动态转发:本地 SOCKS5 代理
$ ssh -D 1080 user@host
$ curl --socks5 127.0.0.1:1080 https://example.com
$ # 浏览器/系统代理设为 socks5://127.0.0.1:1080 即可全局走隧道

# 跳板机(推荐写法)
$ ssh -J bastion internal-host
$ ssh -J user1@bastion1,user2@bastion2 target       # 多级跳

# 保持连接的完整写法
$ ssh -N -f -L 15432:localhost:5432 user@dbhost     # -N 不执行命令,-f 后台
$ autossh -M 0 -N -L 15432:localhost:5432 user@dbhost   # 断线自动重连

在 ~/.ssh/config 里固化:

Host db-tunnel
    HostName dbhost
    User axu
    LocalForward 15432 localhost:5432
    ExitOnForwardFailure yes
    ServerAliveInterval 30

Host socks
    HostName gateway
    User axu
    DynamicForward 1080

五、会话保持:tmux / screen

SSH 断线后进程会被 SIGHUP 杀死。长期任务(编译、备份、部署)必须放进终端复用器。

$ tmux                             # 新建会话
$ tmux new -s deploy               # 命名会话
$ tmux ls                          # 列出会话
$ tmux attach -t deploy            # 重新接入
$ tmux kill-session -t deploy

tmux 快捷键(默认前缀 Ctrl+b):

快捷键 作用
Ctrl+b d detach(脱离,会话继续跑)
Ctrl+b c 新窗口
Ctrl+b n/p 下/上一个窗口
Ctrl+b % / " 垂直/水平分屏
Ctrl+b 方向键 切换窗格
Ctrl+b [ 进入复制模式(可滚动查看历史)
Ctrl+b , 重命名窗口
# 脚本里用 tmux 跑长任务
$ tmux new -d -s backup 'rsync -avzP /data/ /backup/'
$ tmux ls
$ # 更规范的做法:交给 systemd-run
$ systemd-run --unit=backup --property=Type=oneshot rsync -avz /data/ /backup/

nohup cmd & 也有用,但不方便回看输出。 长任务优先 tmux 或 systemd。

六、排障入口

症状 原因
Connection refused sshd 没跑 / 端口不对 / 防火墙拦
Connection timed out 网络不通 / 防火墙 DROP / IP 错
Permission denied (publickey) 公钥没部署 / 权限不对(~/.ssh 或家目录太开放)/ 用户错
REMOTE HOST IDENTIFICATION HAS CHANGED 服务器重装或换了密钥。确认无误后 ssh-keygen -R host
Host key verification failed 首次连接未确认(非交互场景)
连上就断 / 卡住 ServerAliveInterval 没配;MTU 问题;-v 看卡在哪步
Too many authentication failures agent 里密钥太多,SSH 逐个试超限:IdentitiesOnly yes
sudo 报 no tty present ssh -t 分配终端
传文件中断 用 rsync -P 断点续传
-L 转发失败 AllowTcpForwarding no;端口被占
$ ssh -vvv user@host 2>&1 | tail -40         # 定位认证卡在哪步
$ sudo sshd -T | grep -i -E "password|permitroot|pubkey"   # 看实际生效的配置
$ sudo journalctl -u ssh -n 50
$ sudo journalctl -u ssh -f | grep -i "invalid user"        # 看爆破尝试
$ ss -tlnp | grep :22

常见误区

  • 换 SSH 端口不是安全措施(只是减少日志噪音),密钥认证 + fail2ban 才是。
  • ~/.ssh 权限必须严格,否则密钥认证静默失效。
  • -A(agent 转发)有风险,别在不可信机器上用。
  • rsync 尾斜杠语义不同,且 --delete 方向搞反会删数据。
  • scp -P 是大写,ssh -p 是小写。
  • 主机密钥变更可能是中间人攻击,确认原因再 known_hosts 清理。
  • 长任务不放 tmux = 断网就白跑。

待补充

  • SSH 证书认证(CA 签发,适合大规模)
  • ProxyCommand 高级用法(nc、openssl s_client 穿透)
  • Mosh:移动网络下的稳定远程
  • WireGuard / Tailscale 作为 SSH 的替代路径
  • 堡垒机方案(JumpServer、Teleport)
  • rsync 快照式备份完整方案
  • SSH 多路复用(ControlMaster、ControlPersist)加速
  • 二进制文件差分传输(zsync、bita)
  • 2FA / U2F 硬件密钥登录 SSH

← 上一章:容器 · 返回目录 · 下一章:日志、排障与性能观测 →