shell 下 docker 鏡像依賴處理和並行編譯的實現

shell 下 docker 鏡像依賴處理和並行編譯的實現

最近在作一系列的 docker 的鏡像編譯腳本時,想到能不能經過並行編譯加快速度,查了一下資料,最後經過 shell 的 job control 實現了並行編譯多個 docker 鏡像。docker

具體要實現的目標包括:shell

  1. 處理在一個目錄內的 docker 鏡像的 Dockerfile ,根據依賴關係逐個編譯 docker 鏡像
  2. 爲加快速度,不存在依賴關係的鏡像可進行並行編譯(並行度可設置)

首先,若是要在腳本文件中啓用 job control ,須要在腳本文件開頭處,加上如下的代碼:ubuntu

#!/bin/bash

# 容許腳本使用 job control
set -m

shell 的 job control 容許之後臺方式,執行程序時帶 '&' 會讓進程在後臺運行,可經過 jobs 指令檢查有多少後臺任務在執行中,經過 wait 指令等待進程結束,並立刻經過 $? 得到 wait 目標進程的返回值。數組

這一系列的 docker 鏡像是存在依賴關係的,依賴的關係容易處理,直接查 Dockerfile 的 FROM 指令,處理其父鏡像的 tag 後獲得依賴關係:bash

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 數量進行調整,在寫腳本時,要當心父鏡像編譯錯誤的問題。性能

相關文章
相關標籤/搜索