scp / rsync —— 远程文件拷贝
一句话定义
两个工具都走 SSH 传文件:
scp—— 简单粗暴,全量复制rsync—— 智能增量、断点续传、保留权限 / 时间戳
rsync 在几乎所有方面都胜过 scp——除了"我只想一次拷一个小文件"。生产 / 大文件 / 同步目录用 rsync。
OpenSSH 9.0+ 已经 deprecate
scp协议(背后改用 SFTP)。但命令本身还在、还能用。
典型场景
- 把本机文件传到服务器:
scp file user@host:/path - 同步整个目录:
rsync -avz dir/ host:/dir/ - 增量推 K8s manifests:每次只传变化的
- 拉远端日志:
rsync -avz host:/var/log/ ./logs/
scp 基础
# 本地 → 远端
scp file.txt user@host:/path/
# 远端 → 本地
scp user@host:/path/file.txt ./
# 远端 → 远端(中转走本地)
scp user@host1:/file user@host2:/file
# 递归目录
scp -r mydir/ user@host:/path/
# 指定端口
scp -P 2222 file user@host:/
# 用 ssh config 的 alias
scp file m1:/tmp/ # m1 在 ~/.ssh/config 里
scp 常用 flag
| flag | 作用 |
|---|---|
-r | 递归目录 |
-P <port> | SSH 端口(大写 P,注意 ssh 是小写 -p) |
-i <key> | 私钥 |
-p | 保留时间戳 / 权限(小写 p) |
-v | verbose |
-C | 压缩传输 |
-q | quiet |
-l <kbps> | 限速 |
-3 | 强制走本地中转(默认两台远端直接连) |
scp 经典坑
scp -P 22 -p file user@host:/ # 大小写 P 不一样
# -P = 端口
# -p = 保留时间戳
很多人混淆。rsync 没这坑——下面看。
rsync —— 现代首选
rsync -avz source/ destination/
万能套路:-avz 三个字。
| flag | 作用 |
|---|---|
-a | archive(递归 + 保留权限 / 时间戳 / 软链等) |
-v | verbose |
-z | 压缩 |
加起来:递归同步、保留元信息、压缩传输、可读输出。默认套路。
本地 → 远端
rsync -avz manifests/ m1:/root/manifests/
远端 → 本地
rsync -avz m1:/var/log/ ./logs/
-e 自定义 SSH 命令
rsync -avz -e "ssh -p 2222 -i ~/.ssh/special-key" file m1:/tmp/
或者让 rsync 用 ~/.ssh/config(默认就用)。
rsync 路径末尾的 / —— 经典坑
rsync -avz src dst # src 这个**目录**被放到 dst 下 → dst/src/
rsync -avz src/ dst # src 里的**内容**复制到 dst → dst/file1, dst/file2
rsync -avz src/ dst/ # 同上
核心:
src不带/—— "拷这个目录"src/带/—— "拷里面的东西"
# 想把 manifests/ 里的所有内容同步到 m1:/root/manifests/
rsync -avz manifests/ m1:/root/manifests/
# ✅ 正确
rsync -avz manifests m1:/root/manifests/
# 结果:/root/manifests/manifests/... (多了一层)
这个 / 错了 90% 人都踩过。
--delete —— 双向同步(镜像)
rsync -avz --delete src/ dst/
# dst 里"src 里没有"的文件被删
# 让 dst 真正"等于" src
GitOps 部署 manifests 经常用:
rsync -avz --delete --exclude='.git' manifests/ m1:/root/manifests/
# m1 上 manifests 和本地完全镜像、含删除已删的
--delete是核选项——目标目录不在 src 里的文件就被干掉。先--dry-run看:
rsync -avzn --delete src/ dst/ # -n = dry-run
--dry-run / -n —— 必备
rsync -avzn --delete src/ dst/
# 显示会发生什么,但不真做
所有破坏性的 rsync 操作之前先 -n。
--exclude / --include
rsync -avz --exclude='*.log' src/ dst/
rsync -avz --exclude='*.log' --exclude='node_modules' src/ dst/
rsync -avz --exclude-from=.gitignore src/ dst/ # 从文件读
--exclude='*.log' 跳过所有 .log 文件。--exclude='dir/' 跳整个目录。
rsync -avz --include='*.yaml' --exclude='*' src/ dst/
# 只同步 yaml、其它全跳
# --include 先生效、--exclude 后兜底
--progress —— 看进度
rsync -avz --progress big-file m1:/
# 显示每个文件的传输百分比 + 速度
或者 -P 等价 --partial --progress:
rsync -avzP big-file m1:/
# --partial 留下未传完的部分(断点续传)
# --progress 显示进度
大文件 / 不稳定网络永远 -avzP——网络断了下次接着传。
限速 --bwlimit
rsync -avz --bwlimit=10M src/ dst/ # 限 10 MB/s
避免大同步把带宽吃光(K8s 节点之间 / 共享网络)。
实战场景
1. 把 manifests 同步到节点
rsync -avz --delete \
--exclude='.git' \
--exclude='*.bak' \
manifests/ m1:/root/k8s/manifests/
或者写到 Makefile:
sync:
rsync -avz --delete --exclude='.git' manifests/ m1:/root/k8s/manifests/
2. 拉远端日志做分析
rsync -avz m1:/var/log/audit/ ./audit-logs/m1/
rsync -avz m2:/var/log/audit/ ./audit-logs/m2/
# ...
# 或并行 + 多节点
for h in m1 m2 m3 m4 m5; do
rsync -avz $h:/var/log/audit/ ./audit-logs/$h/ &
done
wait
3. 跨集群迁移数据
# 在源节点
rsync -avzP --bwlimit=50M /data/ user@new-cluster:/data/
4. 用 scp 一次性小文件
scp /tmp/cert.crt m1:/etc/ssl/
scp m1:/var/log/kubelet.log .
小文件 / 一次性场景 scp 够用。
5. 拷到多台机器
# scp 一次只能一台 → 写循环
for h in m1 m2 m3 m4 m5; do
scp config.yaml $h:/etc/
done
# rsync 也是
for h in m1 m2 m3 m4 m5; do
rsync -avz config.yaml $h:/etc/
done
# 并行
echo m1 m2 m3 m4 m5 | xargs -n 1 -P 5 -I {} rsync -avz config.yaml {}:/etc/
rsync 的"魔法":增量算法
rsync 不是简单全量复制——它检查文件块的哈希,只传输有变化的块。
# 第一次:全量传
rsync -avz big.bin m1:/
# 第二次:big.bin 改了一点
rsync -avz big.bin m1:/ # 只传改了的块
对大文件 / 重复内容场景大幅省时省带宽。git 仓库 / 数据库快照 / 镜像同步等场景受益最大。
常见踩坑
坑 1:路径末尾 / 错
rsync -avz src dst # 多了一层
记忆:src/ = "里面的东西",src = "目录本身"。
坑 2:--delete 删了不该删的
rsync -avz --delete /tmp/myapp/ m1:/etc/myapp/
# m1 上 /etc/myapp/ 里的所有"本地 /tmp/myapp 没有的"都被删
--dry-run 先看:
rsync -avzn --delete /tmp/myapp/ m1:/etc/myapp/
坑 3:scp 端口 -P 大小写
scp -p 2222 file host:/ # ❌ 小 p = 保留时间戳,不是端口
scp -P 2222 file host:/ # ✅ 大 P
坑 4:跨主机 scp 慢
scp -3 host1:/file host2:/file # 走本地中转
scp host1:/file host2:/file # 默认两台直连,可能不通 / 慢
更现代:用 rsync + ssh-pipe(极少需要)。
坑 5:rsync 远端找不到
rsync -avz file m1:/path/
# rsync: bash: command not found
# rsync error: ...
远端没装 rsync。装:
ssh m1 'apt install -y rsync'
两端都要装 rsync。scp 不要求(只要 ssh + scp)。
坑 6:rsync 时遇到符号链接
rsync -avz src/ dst/ # -a 保留 symlink(默认)
rsync -avzL src/ dst/ # -L 跟随 symlink(拷实际文件)
-L(link)让 rsync 解引用 symlink、把目标文件拷过去。需要时再加。
坑 7:rsync 不传隐藏文件
rsync -avz src/* dst/ # ❌ shell glob 不含 .开头
rsync -avz src/ dst/ # ✅ src/ 含所有(隐藏 + 普通)
src/* 是 shell 在 rsync 之前展开 → 缺隐藏。src/ 是把目录给 rsync、它处理。
坑 8:rsync 时 SELinux 让权限丢
rsync -avz src/ dst/
# 文件 owner / mode 对、但 SELinux context 没了
加 -X(保留扩展属性)+ -A(保留 ACL):
rsync -avzXA src/ dst/
K8s 节点上影响不大、SELinux / AppArmor 场景需要。
坑 9:rsync 用错了 sudo 权限
rsync file m1:/etc/ # m1 上写 /etc 要 root
# Permission denied
rsync -e "ssh -t" --rsync-path="sudo rsync" file m1:/etc/
--rsync-path="sudo rsync" 让远端用 sudo 跑 rsync。
坑 10:大量小文件 rsync 慢
rsync -avz lots-of-small-files/ m1:/
# 每个文件单独 metadata 同步、慢
打包后传:
tar czf - lots-of-small-files/ | ssh m1 'tar xzf - -C /'
tar + ssh 流式比 rsync 大量小文件快很多。
scp vs rsync 总结表
| 维度 | scp | rsync |
|---|---|---|
| 简单 | ✅ 简单 | 复杂参数 |
| 增量 | ❌ 全量 | ✅ 只传变化 |
| 大文件中断恢复 | ❌ | ✅ -P |
| 保留权限 | -p | -a 自动 |
| 双向同步 | ❌ | ✅ --delete |
| dry-run | ❌ | ✅ -n |
| 限速 | -l kbps | --bwlimit |
| 跨主机 | -3 | 不直接支持 |
| 进度 | OpenSSH 8+ | --progress |
| 两端要装 | 不要 | 要装 rsync |
90% 场景用 rsync。
关联命令
- ssh —— scp / rsync 底层都是 SSH
- ssh-config —— alias 让 scp / rsync 也短
- tar —— tar + ssh 流式拷大量小文件
- find ——
find ... | rsync --files-from=-精确选文件 wget/curl—— HTTP / FTP 下载croc/magic-wormhole—— 端到端文件传输(避开 SSH key 管理)