有时,我们在编写 ansible playbook 时,需要配置一些任务去下载大型文件。这个过程可能会很缓慢,导致你不清楚是否仍在下载或是已经卡住了,这会引发焦虑,你可能会担心是否在浪费时间。如果能在前端显示下载文件的进度条,就能有效缓解这种困扰。这种需求早在 2014 年就有人提出了。
然而,这并不符合 ansible 的设计哲学,因此,尽管很多人通过 issue 来提出这个需求,并且有大量的社区用户加入讨论,官方还是拒绝实现这个需求,社区中也没有人去实现。有些人试图通过回调插件实现,这是一种常见的做法,但由于并非官方支持,我并不敢去尝试。
如图,有人建议添加 progress 关键字用于控制是否显示下载任务进度条。这个讨论的地址见
https://github.com/ansible/ansible/issues/41977
最近,我想到了一种方法,虽然不能实现在 issue 中提到的进度条样式,但是可以实现对下载任务进度的感知。接下来,我将分享并讲解我的实现方法的代码。
我先把代码先贴出来,涉及到两个文件,download.yml 是 playbook 文件,monitor_progress.sh 是 playbook 会异步调用的一个脚本。
- 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
#!/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
monitor_progress.sh 脚本和 wget 程序。为了保证我的 playbook 具有幂等性,如果之前有运行过 playbook 并且异常退出过,可能会有残留进程,所以会先查找是否有这种情况,有则 kill 掉。这里的 failed_when: result1.rc != 0 and result1.rc != -15 是因为我使用的 pkill 运行成功时返回码并不是 0,而是 15,所以返回 15 是正常的,而非失败。monitor_progress.sh 脚本,他的原理是,首先通过 wget --spider 命令计算要下载的文件的大小。然后我写死了一个 for 循环次数为 600 次( sleep 1 代表每秒循环一次 ),那么这个脚本的超时时间就为 600 秒。每秒都会去通过 du 命令统计下载中的文件的大小,然后除以一开始统计的"要下载的文件的大小",就可以算出文件下载了百分之多少。把结果保存在 /tmp/download_progress.txt 里。这个脚本的目的,仅仅是监视下载的进度,并保存结果到 /tmp/download_progress.txt,以便我后续查询。脚本是一个通用脚本,会有两个传参,一个是下载文件的保存目录,一个是下载链接。wget,也有两个传参,和上面 monitor_progress.sh 脚本的参数是一样的。这个是下载任务的主进程。 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/
加 -v 参数可以参看更详细的信息。
虽然方法很蠢,但就是能解决问题。
原文作者:芬达的学习笔记;如有侵权,请联系管理员删除。