利用dockerignore加速镜像构建

目前对于项目中构建镜像较慢的问题,经常让人等的着急。是目录结构安排的不合理,还是手法的不准确?

经常的构建一个镜像在发送Context至daemon花费大量时间,这个过程在官方文档build镜像中有说明, 不过比较简要。在构建镜像时,需要向docker引擎发送上下文,比如你执行docker bulid -t img:tag -f Dockerfile . 时,便会默认将当前目录下的所有文件都发向引擎。 这显然在很多时候都是无必要的,优化空间巨大。官方此时提到了dockerignore文件,没错!它就是今天的主角。

我们知道一个Dockerfile它依赖的文件是可预期的。一般来说所在ADDCOPY命令的东西,都是构建时需要的。所以优化关键是以白名单模式去代替dockerignore的黑名单。默认禁掉所有文件,只有从Dockerfile中提取到的文件才能加入Context。

所以只要在准备构建镜像前,动态去计算并生成准确的.dockerignore文件,便可以使构建过程提速。具体提速效果视项目工程而定,显然它是有百利而无一害的。

最终的脚本很简单。 当然为了便利添加了一些小特性。 执行:

sh docker_build.sh shortname tag [build or not]

即可快速在当前目录查找名为Dockerfile-[shortname]的Dockerfile然后构建出名为shortname:tag的镜像。

文末直接附上源码。

话说同时有几个疑点,欢迎留言探讨。

  1. 目录层级:项目中如果构建dockerfile中依赖的包在公共目录,它的层级较上,这时使用软链接方式似乎docker构建报错,只能在上层去build?
  2. Dockerfile本身:对于某些命令,我们可以放在Dockerfile中使用RUN或者CMD去执行,也可以在镜像启动时通过入口执行脚本方式去做。比如说corefile的开启,需要修改系统的某些文件。类似这种工作,应试归属于哪个步骤更加合理呢?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/bin/bash
#file: docker_build.sh

# 1. 根据name匹配到合适的dockerfile
# 2. 根据dockerfile计算文件依赖, 生成临时.dockerignore
# 3. 根据tag生成对应的镜像

#Usage: ./docker_build name tag [skip_build|build]
if [ $# -lt 2 ]; then
    echo "Usage: $0 name tag [skip_build|build]"
    exit -1
fi

name=$1
tag=$2
skip_build=$3

echo ${skip_build:="build"} > /dev/null

g_docker_file=


## Error
function error(){
    echo -e "\033[31m\033[01m${cur_date} [error] $1 \033[0m"
}

## warning
function warn(){
    echo -e "\033[33m\033[01m${cur_date} [warn] $1 \033[0m"
}

## info
function info(){
    echo -e "\033[32m\033[01m${cur_date} [info] $1 \033[0m"
}


function find_dockerfile()
{
    result_cnt=`find . -maxdepth 1 -type f -name "Dockerfile*$1*" | wc -l`
    if [ $result_cnt -gt 1 ]; then
        warn "Find many dockerfile match with [Dockerfile*$1*]"
        return 1
    elif [ $result_cnt -eq 0 ]; then
        error "Not find any dockerfile match with [Dockerfile*$1*]"
        return 2
    fi
    result=`find . -maxdepth 1 -type f -name "Dockerfile*$1*"`
    g_docker_file=$result
    return 0
}

function generate_docker_ignore()
{
    docker_file=$1
    ignore_file=$2

    :> $ignore_file
    echo "*" >> $ignore_file
    echo "!${ignore_file}" >> $ignore_file
    echo "!${g_docker_file}" >> $ignore_file
    cat $g_docker_file | egrep "^COPY|^ADD"  | awk '{printf("!%s\n", $2)}' >> $ignore_file

    sed  -i 's/^!\.\//!/' $ignore_file
}


# 1.根据name匹配到合适的dockerfile
find_dockerfile $name
if [ $? -ne 0 ]; then
    exit -1
fi
info "================ $g_docker_file ==========="
cat $g_docker_file


ignore_file=.dockerignore
# 2. 根据dockerfile计算文件依赖, 生成临时.dockerignore
info "================ .dockerignore ==========="
generate_docker_ignore $g_docker_file $ignore_file
cat $ignore_file
info "==========================================="

# 3. 根据tag生成对应的镜像
if [ $skip_build == "build" ]; then
    docker build -t $name:$tag -f $g_docker_file .
    if [ $? -eq 0 ]; then
        info "build image succ!"
    else
        warn "build image failed"
    fi
else
    warn "skip build image!"
fi