shell 下 docker 镜像依赖处理和并行编译的实现
shell 下 docker 镜像依赖处理和并行编译的实现
最近在做一系列的 docker 的镜像编译脚本时,想到能不能通过并行编译加快速度,查了一下资料,最后通过 shell 的 job control 实现了并行编译多个 docker 镜像。
具体要实现的目标包括:
- 处理在一个目录内的 docker 镜像的 Dockerfile ,根据依赖关系逐个编译 docker 镜像
- 为加快速度,不存在依赖关系的镜像可进行并行编译(并行度可设置)
首先,如果要在脚本文件中启用 job control ,需要在脚本文件开头处,加上以下的代码:
#!/bin/bash
# 允许脚本使用 job control
set -m
shell 的 job control 允许以后台方式,执行程序时带 '&' 会让进程在后台运行,可通过 jobs
指令检查有多少后台任务在执行中,通过 wait
指令等待进程结束,并马上通过 $?
获得 wait 目标进程的返回值。
这一系列的 docker 镜像是存在依赖关系的,依赖的关系容易处理,直接查 Dockerfile 的 FROM
指令,处理其父镜像的 tag 后得到依赖关系:
all_dockerfiles=$(find <目录> -name Dockerfile -type f)
# 关联数组保存镜像名和对应的路径
declare -A all_images=()
# 保存镜像名和对应的父镜像名
declare -A depends=()
for dockerfile in ${all_dockerfiles[@]}; do
image_dir=${dockerfile%/*}
image_name=${image_dir##*/}
all_images[${image_name}]=${image_dir}
parent_image_name=$(cat ${dockerfile} | awk -F '[ :]' '/^FROM/{print $2}')
# 这里可做个判断,当镜像名不在目录中时可去除,如 ubuntu:14.04 之类的父镜像不需要记录
depends[${image_name}]=${parent_image_name}
done
有了 all_images 和 depends 两个关联数组,就可以根据这两个数据,结合 job control 来进行并行处理。在决定是否编译一个镜像前,需要判断其父镜像是否编译成功,因此,增加两个数组分别记录编译成功和编译失败的镜像名,使用一个数组记录当前在编译中的镜像名。
# 允许的最大进程数量
workers=4
# 已编译的镜像
declare -a builts=()
# 错误的镜像
declare -a errors=()
# 正在运行的任务,[pid]=镜像名
declare -A runnings=()
while [ ${#all_images[@]} -gt 0 ] || [ ${#runnings[@] -gt 0 } ]; do
# 检查是否有编译任务已完成
if [ $(jobs | wc -l) -lt ${#runnings[@]} ]; then
# 检查是哪个编译任务已完成
for pid in ${!runnings[@]}; do
if ! (jobs -l | grep ${pid} > /dev/null); then
# 获取进程返回值
wait ${pid}
return_code=$?
# 成功还是失败
if [ ${return_code} -ne 0 ]; then
errors=( ${errors[@]} ${runnings[${pid}]} )
else
builts=( ${builts[@]} ${runnings[${pid}]} )
fi
unset runnings[${pid}]
fi
done
fi
# 加入新的编译任务, 从 all_images 中挑一个镜像
for image_name in ${!all_images[@]}; do
# 是否还有 worker 可用
if [ ${#runnings[@]} -ge ${workers} ]; then
break
fi
# 父镜像是否已编译
parent_image_name=${depends[${image_name}]}
# 父镜像失败则本镜像失败
for i in ${errors[@]}; do
if [ ${i} == ${parent_image_name} ]; then
errors=( ${errors[@]} ${image_name} )
unset all_images[${image_name}]
continue
fi
done
# 父镜像成功,则本镜像可开始编译
for i in ${builts[@]}; do
if [ ${i} == ${parent_image_name} ]; then
docker build -q ${all_images[${image_name}]} -t "${image_name}:latest" &
runnings[$!]=${image_name}
unset all_images[${image_name}]
fi
done
done
# sleep 一会
sleep 0.2
done
worker 的数量可根据网络的性能和主机的 cpu 数量进行调整,在写脚本时,要小心父镜像编译错误的问题。