Ansible playbook:仅当文件在目标机器上未发生更改时才从存储库下载文件

Ansible playbook:仅当文件在目标机器上未发生更改时才从存储库下载文件

我正在设置一个带有 PXE 启动的自动安装环境。要以这种方式安装 Linux,基本上需要下载linuxinitrd.gz文件,它们是深埋于地下在存储库结构中,并提供将引用这些文件的 pxelinux 和/或 pxegrub 配置。这是针对 Debian 的;其他发行版应该类似。我之前手动做过这个。

我编写了以下基本剧本来从服务器下载文件并将其放入 tftp 服务器:

- name: test download
  hosts: test-netinstsrv
  gather_facts: false
  vars:
    netboot:
      base: https://deb.debian.org/debian/dists/bullseye/main/installer-amd64/current/images/
      files_base: netboot/debian-installer/amd64/
      files: [ linux, initrd.gz ]
  tasks:
  - name: Create a temporary directory
    ansible.builtin.tempfile:
      state: directory
      suffix: netboot
    register: tmpdir
    delegate_to: localhost
    run_once: true
    changed_when: false
  - name: Set permissions
    ansible.builtin.file:
      path: "{{ tmpdir.path }}"
      mode: 0755
    delegate_to: localhost
    run_once: true
    changed_when: false

  - name: Download files
    ansible.builtin.uri:
      url: "{{ netboot.base }}{{ netboot.files_base }}{{ item }}"
      dest: "{{ tmpdir.path }}/{{ item }}"
      method: "get"
    loop: "{{ netboot.files }}"
    delegate_to: localhost
    run_once: true
    register: downloaded_file
    changed_when: false

  - name: Copy files to the remote
    ansible.builtin.copy:
      src: "{{ tmpdir.path }}/{{ item }}"
      dest: "/tmp/"
    loop: "{{ netboot.files }}"
  - name: Remove temporary directory
    ansible.builtin.file:
      path: "{{ tmpdir.path }}"
      state: absent
    delegate_to: localhost
    run_once: true
    changed_when: false

(实际剧本要庞大得多,这只是摘录。)

这很有效,但效率低下的是,每次播放时它总是下载两个文件,即使没有任何变化。我不得不把changed_when几乎所有地方都放进去,以免在播放重演中看到虚假的变化;唯一真正的变化可能是一个copy模块可以做的。由于我计划将其扩展到 Debian 的几个版本(目前支持所有版本)并添加其他发行版,因此下载量将变得过多。

我使用一个uri模块来下载文件,但我没有找到在文档中如何使此下载有条件。我想要实现的是,让下载模块发出类似于 If-Modified-Since 请求,存储库 Web 服务器可以返回 304 Not Modified,这意味着跳过目标服务器上的启动映像更新,从而节省下载时间。

由于目标服务器无法访问互联网,因此这有点复杂。因此,所有下载都必须由控制机器完成,所以我将大多数任务委托给本地主机。

一个想法是首先从我的目标服务器之一获取这些文件到控制器,然后希望 uri 模块能够检测到文件已经存在,并且除非文件发生变化,否则不会重新下载它们。这样可行吗?还有其他方法可以实现吗?


更新

我尝试使用get_url模块(甚至在答案出现之前)。剧本的更改基本上是:

  - name: Create required subdirectories
    ansible.builtin.file:
      path: "{{ tmpdir.path }}/{{ netboot.files_base }}"
      state: directory
    delegate_to: localhost
    run_once: true

  - name: Download files
    ansible.builtin.get_url:
      url: "{{ netboot.base }}{{ netboot.files_base }}{{ item }}"
      dest: "{{ tmpdir.path }}/{{ netboot.files_base }}{{ item }}"
      checksum: "sha256:{{ netboot.base }}SHA256SUMS"
    loop: "{{ netboot.files }}"
    delegate_to: localhost
    run_once: true
    register: downloaded_file

失败了:

failed: [test-netinstsrv -> localhost] (item=linux) => {"ansible_loop_var": "item", "changed": false, "item": "linux", "msg": "Unable to find a checksum for file 'linux' in 'https://deb.debian.org/debian/dists/bullseye/main/installer-amd64/current/images/SHA256SUMS'"}
failed: [test-netinstsrv -> localhost] (item=initrd.gz) => {"ansible_loop_var": "item", "changed": false, "item": "initrd.gz", "msg": "Unable to find a checksum for file 'initrd.gz' in 'https://deb.debian.org/debian/dists/bullseye/main/installer-amd64/current/images/SHA256SUMS'"}

可能,因为SHA256SUMS具有以下结构:

...
52eb21964231223563a59656708270c5708c8dcf5b3a1c5cccb1924af9964332  ./netboot/debian-installer/amd64/initrd.gz
b00b339f8b1aada1841d86650377dd8e7299eaa7f34d0bbf21deb561467015cd  ./netboot/debian-installer/amd64/linux
...

可能,如果我下载该文件并重新格式化它,它会有所帮助......

答案1

ansible.builtin.get_url 是一个通用的文件下载模块,它的作用就是在满足某些条件的情况下跳过下载。

根据目标文件修改时间,自标头以来已修改。如果响应标头显示未修改,则跳过下载(并返回未更改的模块)。

此外,提供校验和参数。如果目标文件存在且匹配,则不会下载。此外,如果下载的(临时)文件与提供的校验和不匹配,则模块失败。后一种用例可能有助于固定已知版本,这样下载文件的任何更改都是显而易见的。

答案2

get_url无法识别 SHA256SUMS 文件的格式,这很令人失望,因为这种格式在校验和文件中很常见。此外,它要求文件存在于本地,这也不太有效。

通过以下任务集我能够实现我想要的目标:

  - name: Download SHA256SUMS file to compare against
    ansible.builtin.get_url:
      url: "{{ netboot.base }}SHA256SUMS"
      dest: "{{ tmpdir.path }}/"
    delegate_to: localhost
    run_once: true
    become: false
    changed_when: false
    
  - name: Find checksums of files on the server
    ansible.builtin.stat:
      path: "{{ netboot.remote_location }}{{ item }}"
      checksum_algorithm: sha256
      get_checksum: yes
    loop: "{{ netboot.files }}"
    register: remote_stat
    changed_when: false
    
  - name: Compare server checksums against SHA256SUMS file
    command: "grep -E '{{ item.stat.checksum }}  ./{{ netboot.files_base }}{{ item.item }}' {{ tmpdir.path }}/SHA256SUMS"
    loop: "{{ remote_stat.results }}"
    when: item.stat.exists
    delegate_to: localhost
    become: false
    ignore_errors: true
    changed_when: false
    register: comparison
    
  - name: Download missing or different files
    ansible.builtin.get_url:
      url: "{{ netboot.base }}{{ netboot.files_base }}{{ item.item.item }}"
      dest: "{{ tmpdir.path }}/{{ item.item.item }}"
    loop: "{{ comparison.results }}"
    when: (item.skipped is defined) or item.failed
    delegate_to: localhost
    run_once: true
    become: false
    
  - name: Copy files to the remote
    ansible.builtin.copy:
      src: "{{ tmpdir.path }}/{{ item.item.item }}"
      dest: "{{ netboot.remote_location }}"
    loop: "{{ comparison.results }}"
    when: (item.skipped is defined) or item.failed

它会下载 SHA256SUMS,对照它检查服务器上的文件,然后只下载缺失或不同的文件。理想情况下,它只会下载这个小文件,只有在出现问题时才会移动其他文件;甚至不需要将文件提取到控制器机器上。

它还可以通过修复 SHA256SUMS 文件本身的校验和来“固定”某些内容。

我还不确定当游戏中有多台服务器时它是否会工作(我只在一台服务器上测试过)。可能应该run_once从上面的倒数第二个任务中删除。此外,我希望可以让它足够通用,以便它能够接受其他发行版使用的其他目录结构。

相关内容