Ansible.builtin.copy校验和一致却报告文件变更问题求助
问题:Ansible copy任务重复报告文件变更,但SHA256校验一致
我在自动化拉取OS镜像并添加cloud-init的user-data文件,流程是将原始镜像复制到工作目录修改,再复制到输出目录。但最后一步copy任务每次都报告文件变更,用sha256sum ubuntu-2204.customized.img*检查备份文件与新文件,校验和完全匹配。
相关任务代码:
- name: Copying modified image to output directory ansible.builtin.copy: src: "{{ working.path }}/{{ pi_image }}.img" dest: "output/{{ pi_image }}.customized.img" mode: preserve remote_src: true backup: true
完整代码参考
groups/all/raspberry_pi.yml
pi_username: james pi_password: password pi_hostname: management01 pi_public_key_path: /home/james/.ssh/id_rsa.pub pi_supported_images: ubuntu-2204: download_url: https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.5-preinstalled-server-arm64+raspi.img.xz download_checksum: sha256:fd7687c5c9422a6c7ba4717c227bf6473fe4e0c954d5a9f664201dcecc63e822 rocky-9: download_url: https://dl.rockylinux.org/pub/sig/9/altarch/aarch64/images/RockyLinuxRpi_9-latest.img.xz download_checksum: sha256:1ba3c7649279ef1b3b48cd2caa3a8b0ce4483accc095ba6f88661f55f8f91138 pi_image: ubuntu-2204
Playbook
- name: Build Raspberry Pi Server ISO hosts: localhost become: true force_handlers: true tasks: - name: "Fetching {{ pi_image }}" ansible.builtin.get_url: url: "{{ pi_supported_images[pi_image].download_url }}" checksum: "{{ pi_supported_images[pi_image].download_checksum }}" dest: "output/{{ pi_image }}.img.xz" mode: u=r,g=r,o=r - name: Generating user-data file ansible.builtin.template: src: templates/user-data.jinja dest: "output/{{ pi_image }}-user-data" mode: u=r,g=r,o=r - name: Making working directory ansible.builtin.file: state: directory path: "working/{{ pi_image }}-{{ ansible_date_time.epoch }}" mode: u=xrw,g=xr,o=xr register: working notify: Remove working directory - name: Copying compressed image to working directory ansible.builtin.copy: src: "output/{{ pi_image }}.img.xz" dest: "{{ working.path }}/{{ pi_image }}.img.xz" mode: u=r,g=r,o=r - name: Extracting compressed image ansible.builtin.command: argv: - xz - --keep - --decompress - "{{ working.path }}/{{ pi_image }}.img.xz" register: xz_result changed_when: xz_result.rc == 0 - name: Copying extracted image to output directory ansible.builtin.copy: src: "{{ working.path }}/{{ pi_image }}.img" dest: "output/{{ pi_image }}.img" mode: preserve remote_src: true - name: Setting up loopback device ansible.builtin.command: argv: - losetup - --show - --partscan - --find - "{{ working.path }}/{{ pi_image }}.img" register: losetup_attach changed_when: losetup_attach.rc == 0 notify: Remove loopback device - name: Mounting boot partition ansible.posix.mount: src: "{{ losetup_attach.stdout }}p1" path: "{{ working.path }}/{{ pi_image }}-boot-partition" state: ephemeral fstype: vfat notify: Remove mount - name: Placing user-data file ansible.builtin.command: argv: - "cp" - "--archive" - "output/{{ pi_image }}-user-data" - "{{ working.path }}/{{ pi_image }}-boot-partition/user-data" register: cp_user_data changed_when: cp_user_data.rc == 0 - name: Copying modified image to output directory ansible.builtin.copy: src: "{{ working.path }}/{{ pi_image }}.img" dest: "output/{{ pi_image }}.customized.img" mode: u=r,g=r,o=r backup: true handlers: - name: Remove mount ansible.posix.mount: path: "{{ working.path }}/{{ pi_image }}-boot-partition" state: unmounted - name: Remove loopback device ansible.builtin.command: "losetup --detach {{ losetup_attach.stdout }}" register: losetup_detach changed_when: losetup_detach.rc == 0 - name: Remove working directory ansible.builtin.debug: msg: "Removing working directory..." # ansible.builtin.file: # path: "{{ working.path }}" # state: absent
templates/user-data.jinja
#cloud-config chpasswd: expire: false users: - name: {{ pi_username }} password: {{ pi_password }} type: text {% if pi_hostname %} hostname: {{ pi_hostname }} {% endif %} users: - name: {{ pi_username }} shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL ssh-authorized-keys: - {{ lookup('ansible.builtin.file', pi_public_key_path, errors='strict') }} package_update: true package_upgrade: true
排查原因
Ansible的copy模块判断文件是否变更,不只是校验文件内容,还会检查文件元数据(权限、时间戳、所有者/组)。即使内容一致,只要元数据有差异,就会标记为变更。结合你的场景,可能的触发点:
- 权限强制修改:最后一步copy任务指定了
mode: u=r,g=r,o=r,但工作目录内的镜像文件权限与该配置不符,每次复制都会强制修改权限,触发变更标记。 - 时间戳差异:工作目录的镜像文件是解压或修改后生成的,时间戳与输出目录已存在的文件不同,Ansible默认会通过时间戳判断是否需要更新。
- 备份操作干扰:开启
backup: true后,每次复制都会生成备份文件,部分场景下Ansible会因备份操作本身标记为变更。
解决方案
方案1:保持权限一致
将最后一步copy任务的mode改回preserve,让目标文件继承源文件的权限,避免强制修改权限触发变更:
- name: Copying modified image to output directory ansible.builtin.copy: src: "{{ working.path }}/{{ pi_image }}.img" dest: "output/{{ pi_image }}.customized.img" mode: preserve remote_src: true backup: true
方案2:强制校验文件内容
使用checksum参数,指定用SHA256校验判断文件是否需要更新,忽略元数据差异:
- name: Copying modified image to output directory ansible.builtin.copy: src: "{{ working.path }}/{{ pi_image }}.img" dest: "output/{{ pi_image }}.customized.img" mode: u=r,g=r,o=r remote_src: true backup: true checksum: "{{ lookup('ansible.builtin.sha256', working.path + '/' + pi_image + '.img') }}"
方案3:改用系统命令复制
如果不想依赖Ansible的判断逻辑,直接用cp --archive --update命令,仅在源文件更新时复制:
- name: Copying modified image to output directory ansible.builtin.command: argv: - cp - --archive - --update - "{{ working.path }}/{{ pi_image }}.img" - "output/{{ pi_image }}.customized.img" register: cp_result changed_when: cp_result.rc == 0 and cp_result.stdout != ''
额外优化:清理工作目录
启用Remove working directory handler的实际删除逻辑,避免每次运行生成大量冗余工作目录:
- name: Remove working directory ansible.builtin.file: path: "{{ working.path }}" state: absent
内容的提问来源于stack exchange,提问作者James Ayres
相关产品推荐
相关产品推荐

