ansible 中使用 ansible.posix.synchronize 模块来做文件同步
rsync 的一些参数
-av 很顺手,是最常用的参数了,归档模式,相当于 -rlptgoD。
默认情况下,rsync 仅同步源路径最末级的文件或目录名,-R,--relative 是 rsync 中用于保留源文件的完整相对路径结构的关键参数,其核心作用是确保同步时不仅复制文件内容,还会在目标路径中重建源文件在命令行中指定的完整目录层级。
例子:
# /data/temp/openssl-3.5.0/util/perl/TLSProxy/Alert.pm --> /data/backup/temp/test1/Alert.pm
rsync -av /data/temp/openssl-3.5.0/util/perl/TLSProxy/Alert.pm /data/backup/temp/test1
# /data/temp/openssl-3.5.0/util/perl/TLSProxy/Alert.pm --> /data/backup/temp/test2/data/temp/openssl-3.5.0/util/perl/TLSProxy/Alert.pm
rsync -avR /data/temp/openssl-3.5.0/util/perl/TLSProxy/Alert.pm /data/backup/temp/test2
# /data/temp/openssl-3.5.0/util/perl/TLSProxy/Alert.pm --> /data/backup/temp/test3/openssl-3.5.0/util/perl/TLSProxy/Alert.pm
rsync -avR /data/temp/./openssl-3.5.0/util/perl/TLSProxy/Alert.pm /data/backup/temp/test3
--exclude-from=FILE 选项用于指定一个包含排除规则的文件,rsync 会根据该文件中的规则排除指定的文件或目录,通常用于排除不需要同步的文件或目录。
--exclude-from=FILE 的规则文件示例:
# exclude-list.txt 内容示例
# 源目录为 /data/temp/
*.log # 排除所有 .log 文件
temp/ # 排除名为 temp 的目录
cache/* # 排除 cache 目录内容(但保留空目录)
aaa/** # 这里使用 ** 来匹配任何内容,排除 aaa 目录及其所有子目录q
/docs/*.tmp # 排除 /docs 下所有 .tmp 文件
/public_html/ # 排除 public_html 目录
-
排除规则中的路径是相对于源目录的,比如上面的
/public_html/对应/data/temp/public_html/。 -
若
temp/写成temp,则代表排除名为temp的文件或目录。
--filter=filter 选项用于指定一个自定义的文件过滤规则,rsync 会根据该规则排除指定的文件或目录,可以从文件读取多个过滤规则:
rsync -av --filter='merge /path/to/filter_rules.txt' src/ dest/
使用 --include='*/' --exclude='*' 来确保 rsync 只复制目录结构,不复制任何文件:
rsync -av --include='*/' --exclude='*' /data/temp/ /data/backup/temp/
--out-format=FORMAT 用于自定义 rsync 输出的信息格式,通过占位符组合控制输出内容,常用的占位符有:
-
%n文件名(包括路径),如redis-6.1.0/redis.c。 -
%f基础文件名(不含路径),如file.txt。 -
%b传输字节数,如1024。 -
%o操作类型。-
send表示发送文件。 -
recv表示接收文件。 -
del表示删除文件。 -
link表示创建符号链接。
-
-
%t时间戳(修改时间),如2023/10/15 10:00:00。 -
%L链接目标(如果是符号链接),使用%o %L %n的话如send -> libcrypto.so.3 openssl-3.5.0/libcrypto.so。 -
%i变更摘要,如<f+++++++++,rsync的选项-i, --itemize-changes默认为--out-format='%i %n%L'
-i, --itemize-changes 选项用于输出文件的变更摘要,包括文件名、操作类型和链接目标,其默认为 --out-format='%i %n%L'。
输出格式中的 %i 占位符的解释:
YXcstpoguax path/to/file
|||||||||||
||||||||||╰- x: The extended attribute information changed
|||||||||╰-- a: The ACL information changed
||||||||╰--- u: The u slot is reserved for future use
|||||||╰---- g: Group is different
||||||╰----- o: Owner is different
|||||╰------ p: Permission are different
||||╰------- t: Modification time is different
|||╰-------- s: Size is different
||╰--------- c: Different checksum (for regular files), or
|| changed value (for symlinks, devices, and special files)
|╰---------- the file type:
| f: for a file,
| d: for a directory,
| L: for a symlink,
| D: for a device,
| S: for a special file (e.g. named sockets and fifos)
╰----------- the type of update being done::
<: file is being transferred to the remote host (sent)
>: file is being transferred to the local host (received)
c: local change/creation for the item, such as:
- the creation of a directory
- the changing of a symlink,
- etc.
h: the item is a hard link to another item (requires
--hard-links).
.: the item is not being updated (though it might have
attributes that are being modified)
*: means that the rest of the itemized-output area contains
a message (e.g. "deleting")
-
更新类型
Y:-
<文件从源端发送到目标端(新增或修改)。 -
>文件从目标端发回源端(反向同步时出现)。 -
c文件因属性变化,被标记为更新(如权限、时间戳)。 -
h通过硬链接创建文件副本。 -
.项未被更新(尽管它可能有属性被修改)。 -
*附加消息(如错误提示)、特殊标记。 -
*deleting表示删除文件。 -
*sending incremental file list表示开始发送增量文件列表。
-
-
文件类型
X:-
f普通文件。 -
d目录。 -
L符号链接。 -
D设备文件。 -
S特殊文件(如命名管道、套接字)。
-
-
属性变化标记(后 9 位符号):
-
c校验和变化(文件内容改变)。 -
s文件大小变化。 -
t修改时间(mtime)变化。 -
p权限变化。 -
o所有者改变。 -
g组改变。 -
u保留位,暂未使用。 -
aACL 信息变化。 -
x扩展属性信息(xattr)变化。
-
例子:
>f+++++++++ some/dir/new-file.txt
.f....og..x some/dir/existing-file-with-changed-owner-and-group.txt
.f........x some/dir/existing-file-with-changed-unnamed-attribute.txt
>f...p....x some/dir/existing-file-with-changed-permissions.txt
>f..t..g..x some/dir/existing-file-with-changed-time-and-group.txt
>f.s......x some/dir/existing-file-with-changed-size.txt
>f.st.....x some/dir/existing-file-with-changed-size-and-time-stamp.txt
cd+++++++++ some/dir/new-directory/
.d....og... some/dir/existing-directory-with-changed-owner-and-group/
.d..t...... some/dir/existing-directory-with-different-time-stamp/
*deleting some/dir/existing-file-to-be-deleted.txt
cL+++++++++ some/dir/link-to-new-file.txt -> some/dir/new-file.txt
-
>f+++++++++-
>从源端发送到目标端。 -
f普通文件。 -
+++++++++所有属性均为新增。
-
-
.f....og..x普通文件已在目标路径存在,无内容变化,属主、属组和扩展属性变化。 -
.f........x普通文件已在目标路径存在,无内容变化,扩展属性变化。 -
>f...p....x发送普通文件,无内容变化,权限和扩展属性变化。 -
>f..t..g..x发送普通文件,无内容变化,修改时间、数组和扩展属性变化。 -
>f.s......x发送普通文件,文件内容改变导致大小变化。 -
>f.st.....x发送普通文件,文件内容改变导致大小和修改时间都变化。 -
cd+++++++++目录新增。 -
.d....og...目录的属主和属组变化。 -
.d..t......目录修改时间变更。 -
*deleting目标上的该文件将被删除。 -
cL+++++++++ some/dir/link-to-new-file.txt -> some/dir/new-file.txt在目标位置创建了一个名字为link-to-new-file.txt的符号链接,该符号链接指向some/dir/new-file.txt。
--rsync-path=PROGRAM 用于指定在远程主机上执行 rsync 命令的路径或自定义命令。
-
它的值可以是 文件路径、shell 命令或 命令组合,灵活用于适应不同的远程环境(如非标准安装路径、权限限制、自定义脚本等)。
-
指定远程
rsync的路径。 -
支持在远程主机执行 任意 shell 命令,甚至是包含逻辑的脚本,只要它不会破坏 rsync 用于通信的标准输入和标准输出:
-
rsync -av --rsync-path='sh -c "cd /remote/dir && exec rsync"' remote:subdir/ /local/ -
rsync -av --rsync-path="/usr/local/scripts/rsync-pre.sh" remote:/src/ /local/-
rsync-pre.sh是一个脚本,用于在 rsync 运行前执行一些操作,比如检查权限、设置环境变量等。#!/bin/bash # 先压缩文件,再调用 rsync tar -czf /tmp/temp.tar.gz "$@" exec rsync --files-from=/tmp/temp.tar.gz "$@"
-
-
-
在远程设置临时环境变量,如指定
PATH。-
rsync -av --rsync-path="PATH=/usr/local/bin:$PATH rsync" remote:/src/ /local/
-
需要注意的是使用 --rsync-path 要避免不安全的 shell 命令:
-
尽量不使用包含
;、&、|等符号的命令,防止远程代码执行漏洞。 -
推荐用
&&替代;,并用单引号包裹命令(避免本地 shell 提前解析变量):
--rsync-path='sh -c "cd /dir && exec rsync"' # 安全
--rsync-path="cd /dir; rsync" # 不安全(可能注入)
--super 选项表示当接收端(目标主机)的 rsync 未以 root 身份运行时,尝试通过 sudo 或类似机制临时获取超级用户权限,以保留文件的原始属性(如所有者、权限、特殊标志等):
rsync -av --super --rsync-path="sudo rsync" /local/data/ user@remote:/backup/
ansible.posix.synchronize 的使用
ansible.posix.synchronize 模块基于 rsync 实现高效的文件同步,支持在本地与远程主机之间或两台远程主机之间复制文件,支持多种同步模式,如单向同步、双向同步、单向同步并删除目标主机上不存在的文件等。
ansible.posix.synchronize 模块常见的几个参数:
-
src源路径,注意带/与不带的区别。 -
dest目标路径。 -
mode同步模式。-
默认为
push,即从控制机推送到远程机器。 -
pull模式指从远程机器拉取到控制机。
-
-
delete是否删除dest中在src不存在的文件或目录,默认为false。-
该选项需要设置
recursive=true。 -
该选项会忽略被排除的文件,类似于
rsync使用选项--delete-after。
-
-
archive类似于rsync的-a, --archive选项,即-rlptgoD,启用递归、链接、权限、时间、所有者、组标志以及-D,默认为true。-
以下几个在
archive选项启用下默认也设置为true:-
recursive是否递归同步目录。 -
links保留符号链接本身。 -
perms保留权限。 -
times保留修改时间。 -
owner保留文件所有者(仅限超级用户)。 -
group保留组。
-
-
-
compress在传输过程中压缩文件数据,默认为true。- 在大多数情况下该选项建议启用,除非真的有问题。
-
copy_links复制符号链接时,复制的是它们所指向的项目(被引用对象),而不是符号链接本身,默认为false。 -
private_key指定用于基于 SSH 的rsync连接的私钥(如~/.ssh/id_rsa)。 -
checksum是否使用校验和而非时间戳比较文件,默认为false。 -
dest_port目标主机上用于 SSH 的端口号。-
Ansible 2.0 前的版本中
ansible_ssh_port的优先值高于它。 -
此参数默认为
ansible_port或remote_port配置设置的值,若前两者均未设置,则为 ssh 客户端配置中的值。
-
-
rsync_path用于指定在远程主机上执行rsync命令的路径或自定义命令,参阅rsync手册页上的--rsync-path。- 要指定在本地主机上运行的
rsync命令,需要在任务变量ansible_rsync_path中进行设置。
- 要指定在本地主机上运行的
-
rsync_opts用于指定额外的rsync选项,可以使用这个灵活运用rsync的其它参数。
ansible.posix.synchronize 模块使用的注意事项:
-
Playbook 执行用户需有
src目录读权限(通过lookup('env','USER')可打印)。 -
目标主机必须有
dest目录写权限(由ansible_user_id指定)。 -
若目标路径需
root写入,需启用become并配置远程sudo免密。 -
启用
checksum时,会显著降低速度,非必要不开启。 -
远程主机必须安装
rsync包,否则任务失败。 -
当管理大量主机时,
ansible.posix.synchronize模块同步文件或目录会带来显著的性能开销,如果在 playbook 中不需要使用同步文件或目录,可以通过设置gather_facts: false来禁用事实收集。 -
尽量避免在一个 playbook 中多次手动调用
ansible.posix.synchronize模块,如果需要更新文件或目录,可以使用meta: refresh_inventory来刷新缓存的文件或目录数据。 -
在 playbook 中自定义的变量名可能会与
ansible.posix.synchronize模块同步文件或目录时收集到的事实变量名冲突,导致变量值被覆盖,影响任务的执行结果。
使用一个与当前 playbook 执行用户不同的用户做同步:
- name: Print local user
hosts: localhost
connection: local
tasks:
- name: Print local user
debug:
msg: "Local user: {{ lookup('env', 'USER') }}" # 执行此任务的用户 xxx
- name: Sync local directory to remote host
hosts: all_test_servers
gather_facts: false
vars:
source_dir: /data/temp/
remote_dir: /data/backup/temp/test
remote_ssh_user: testuser
remote_ssh_port: 3600
remote_ssh_key: "~/.ssh/testuser.pri"
exclude_from_file: excludes.list
filter_file: filters.list
tasks:
- name: Get remote user via command
command: id -un
register: actual_user
changed_when: false
- name: Set source_dir readable to ansible user
file:
path: "{{ source_dir }}"
mode: 0755
recurse: yes
state: directory
become: true
delegate_to: localhost
- name: Print remote user
debug:
msg: "Remote user: {{ actual_user.stdout }}@{{ inventory_hostname }}" # xxx@ip
- name: Sync directory with exclusions
ansible.posix.synchronize:
src: "{{ source_dir }}"
dest: "{{ remote_dir }}"
# dest_port: "{{ remote_ssh_port }}"
# private_key: "{{ remote_ssh_key }}"
archive: true
mode: push
set_remote_user: false
rsync_opts:
- "--filter='merge {{ filter_file }}'"
- "-e 'ssh -l {{ remote_ssh_user }} -i {{ remote_ssh_key }} -p {{ remote_ssh_port }}'" # 注意这里的 rsync 连接远程主机的用户是 testuser 而不是 xxx
使用 --dry-run 模拟同步操作,打印差异:
- name: Use synchronize to check the differences between the src and dest directories
hosts: all_test_servers
gather_facts: false
vars:
source_dir: /data/temp/
remote_dir: /data/backup/temp/test
remote_ssh_user: testuser
remote_ssh_port: 3600
remote_ssh_key: "~/.ssh/testuser.pri"
exclude_from_file: excludes.list
summary_output: "logs/rsync_summary.out"
rsync_log: "logs/rsync.log"
added_list: "logs/added.list"
changed_list: "logs/changed.list"
deleted_list: "logs/deleted.list"
tasks:
- name: Use the --dry-run option to check for differences
ansible.posix.synchronize:
src: "{{ source_dir }}"
dest: "{{ remote_dir }}"
archive: true
mode: push
set_remote_user: false
delete: true
rsync_opts:
- "--dry-run"
- "--itemize-changes" # 详细输出变更类型
- "--delete"
- "--exclude-from={{ exclude_from_file }}"
- "-e'ssh -l {{ remote_ssh_user }} -i {{ remote_ssh_key }} -p {{ remote_ssh_port }}'"
- "--log-file={{ rsync_log }}"
register: sync_result
- name: Print the result of the dry run
debug:
msg: "{{ sync_result.msg }}"
# 解析输出并生成报告
- name: Parse output and generate reports
block:
- name: Extract added files
shell: |
echo "$(grep -c '^?f' {{ summary_output }})" > {{ added_list }}
grep '^?f' {{ summary_output }} | cut -d' ' -f2- >> {{ added_list }}
args:
executable: /bin/bash
when: sync_result.msg != ""
delegate_to: localhost
- name: Extract changed files
shell: |
echo "$(grep -c '<f..' {{ summary_output }})" > {{ changed_list }}
grep '<f..' {{ summary_output }} | cut -d' ' -f2- >> {{ changed_list }}
when: sync_result.msg != ""
delegate_to: localhost
- name: Extract deleted files
shell: |
echo "$(grep -c '^*deleting' {{ summary_output }})" > {{ deleted_list }}
grep '^*deleting' {{ summary_output }} | awk '{$1=""; print $0}' | sed 's/^ //' >> {{ deleted_list }}
when: sync_result.msg != ""
delegate_to: localhost
always:
- name: Save raw rsync output
copy:
content: "{{ sync_result.msg }}"
dest: "{{ summary_output }}"
force: true
backup: true
delegate_to: localhost