ITPub博客

首页 > 数据库 > 数据库开发技术 > 在ansible-playbook里如何实现下载进度条

在ansible-playbook里如何实现下载进度条

原创 数据库开发技术 作者:芬达的学习笔记 时间:2023-07-28 18:00:00 0 删除 编辑

 playbook 

背景

有时,我们在编写 ansible playbook 时,需要配置一些任务去下载大型文件。这个过程可能会很缓慢,导致你不清楚是否仍在下载或是已经卡住了,这会引发焦虑,你可能会担心是否在浪费时间。如果能在前端显示下载文件的进度条,就能有效缓解这种困扰。这种需求早在 2014 年就有人提出了。

然而,这并不符合 ansible 的设计哲学,因此,尽管很多人通过 issue 来提出这个需求,并且有大量的社区用户加入讨论,官方还是拒绝实现这个需求,社区中也没有人去实现。有些人试图通过回调插件实现,这是一种常见的做法,但由于并非官方支持,我并不敢去尝试。

如图,有人建议添加 progress 关键字用于控制是否显示下载任务进度条。这个讨论的地址见

https://github.com/ansible/ansible/issues/41977

最近,我想到了一种方法,虽然不能实现在 issue 中提到的进度条样式,但是可以实现对下载任务进度的感知。接下来,我将分享并讲解我的实现方法的代码。

代码展示

我先把代码先贴出来,涉及到两个文件,download.yml 是 playbook 文件,monitor_progress.sh 是 playbook 会异步调用的一个脚本。

download.yml

- name: Copy and make the progress monitoring script executable (delegate_to localhost)
  ansible.builtin.copy:
    src: ../files/monitor_progress.sh
    dest: /tmp/monitor_progress.sh
    mode: '0755'
  run_once: true

- name: Kill the progress monitor script if it exists
  ansible.builtin.raw: |
    if pgrep -f "monitor_progress.sh"; then
      pkill -f "monitor_progress.sh" || true
    fi
  run_once: true
  register: result1
  failed_when: result1.rc != 0 and result1.rc != -15

- name: Kill the wget process if it exists
  ansible.builtin.raw: >
    if pgrep -f "wget -c -P {{ download_dir }} {{ mysql_shell_download_url }}"; then
      pkill -f "wget -c -P {{ download_dir }} {{ mysql_shell_download_url }}" || true
    fi
  run_once: true
  register: result2
  failed_when: result2.rc != 0 and result2.rc != -15

- name: Start the progress monitor script(delegate_to localhost)
  ansible.builtin.shell: >
    ./monitor_progress.sh "{{ download_dir }}" "{{ mysql_shell_download_url }}" & echo $! > monitor_progress.pid
  args:
    chdir: /tmp
  run_once: true
  async: 3600
  poll: 0

- name: Start the wget command(delegate_to localhost)
  ansible.builtin.shell: >
      nohup wget -c -P "{{ download_dir }}" "{{ mysql_shell_download_url }}" > /dev/null 2>&1 & echo $! > wget_progress.pid
  args:
    chdir: /tmp
  run_once: true
  async: 3600
  poll: 0

- name: Check if the monitor script is still running(delegate_to localhost)
  ansible.builtin.shell: >
    sleep 5 && kill -0 $(cat /tmp/monitor_progress.pid)
  run_once: true

- name: Download progress has reached 1%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 1
  retries: 20
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 5%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 5
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 10%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 10
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 20%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 20
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 40%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 40
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 60%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 60
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 80%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 80
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

- name: Download progress has reached 100%(delegate_to localhost)
  ansible.builtin.shell: cat /tmp/download_progress.txt
  register: progress
  until: progress.stdout | int >= 100
  retries: 600
  delay: 2
  changed_when: false
  ignore_errors: true
  run_once: true

monitor_progress.sh

#!/bin/bash

download_dir=$1
url=$2

file_name=$(basename "$url")
file_size=$(wget --spider $url 2>&1 | grep Length | cut -d' ' -f2)

for i in {1..600}
do
  download_size=$(du -b $download_dir/$file_name | cut -f1)
  progress=$((100 * download_size / file_size))
  echo $progress > /tmp/download_progress.txt
  if (( $progress == 100 )); then
    break
  fi
  sleep 1
done

代码原理讲解(按任务发生顺序讲解)

  1. 首先我会拷贝脚本到目标服务器上
  2. 脚本后续会打开两个异步程序,分别是 monitor_progress.sh 脚本和 wget 程序。为了保证我的 playbook 具有幂等性,如果之前有运行过 playbook 并且异常退出过,可能会有残留进程,所以会先查找是否有这种情况,有则 kill 掉。这里的 failed_when: result1.rc != 0 and result1.rc != -15 是因为我使用的 pkill 运行成功时返回码并不是 0,而是 15,所以返回 15 是正常的,而非失败。
  3. 异步调用 monitor_progress.sh 脚本,他的原理是,首先通过 wget --spider 命令计算要下载的文件的大小。然后我写死了一个 for 循环次数为 600 次( sleep 1 代表每秒循环一次 ),那么这个脚本的超时时间就为 600 秒。每秒都会去通过 du 命令统计下载中的文件的大小,然后除以一开始统计的"要下载的文件的大小",就可以算出文件下载了百分之多少。把结果保存在 /tmp/download_progress.txt 里。这个脚本的目的,仅仅是监视下载的进度,并保存结果到  /tmp/download_progress.txt,以便我后续查询。脚本是一个通用脚本,会有两个传参,一个是下载文件的保存目录,一个是下载链接。
  4. 异步调用 wget,也有两个传参,和上面 monitor_progress.sh 脚本的参数是一样的。这个是下载任务的主进程。
  5. 通过 kill -0 来判断 monitor_progress.sh 是否还在运行,否则报错。
  6. 通过循环 cat /tmp/download_progress.txt 来检查下载任务是否达到 1%、5%、10%、20%、40%、60%、80%、100% 的条件值。(除了第一个任务超时是 40 秒,其他设置超时是 1200 秒,可以按需调整)

结果展示

我已经把代码集成到到 dbops 里安装 mysqlshell 的逻辑里了。

dbops 是一款提供生产级别 MySQL 部署的 playbook 工具,由芬达个人开发。
地址: https://gitee.com/fanderchan/dbops/

ansible-playbook install_mysqlshell.yml

ansible-playbook install_mysqlshell.yml -v

加 -v 参数可以参看更详细的信息。

总结

虽然方法很蠢,但就是能解决问题。


原文作者:芬达的学习笔记;如有侵权,请联系管理员删除。

上一篇: 芬达不是汽水
请登录后发表评论 登录
全部评论

注册时间:2022-06-10

  • 博文量
    68
  • 访问量
    34071