ansible 中使用 ansible.posix.synchronize 模块来做文件同步

7 阅读6分钟

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 保留位,暂未使用。

    • a ACL 信息变化。

    • 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_portremote_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