shell 下 docker 镜像依赖处理和并行编译的实现

shell 下 docker 镜像依赖处理和并行编译的实现

最近在做一系列的 docker 的镜像编译脚本时,想到能不能通过并行编译加快速度,查了一下资料,最后通过 shell 的 job control 实现了并行编译多个 docker 镜像。

具体要实现的目标包括:

  1. 处理在一个目录内的 docker 镜像的 Dockerfile ,根据依赖关系逐个编译 docker 镜像
  2. 为加快速度,不存在依赖关系的镜像可进行并行编译(并行度可设置)

首先,如果要在脚本文件中启用 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 数量进行调整,在写脚本时,要小心父镜像编译错误的问题。

posted @ 2017-12-24 00:52 drop * 阅读(...) 评论(...) 编辑 收藏