Dockerfile实践指南之COPY vs ADD

在这里插入图片描述
在使用Dockerfile进行镜像构建是,COPY和ADD指令都可以将本地内容拷贝到镜像中,在具体使用中有哪些特点和限制,在这篇文章中将结合实际的例子进行说明。

构建上下文

在使用docker build命令进行构建的时候,都会有一行Sending build context to Docker daemon的提示信息,提示信息中的"build context"就是构建上下文,提示信息说的是将构建信息发送至Docker daemon,Docker daemon我们知道就是Docker的守护进程,而build context(构建上下文)是什么呢?构建上下文就是docker build命令所指定的构建目录下所有的文件的集合。构建镜像的能力是Docker的守护进程提供的,而触发构建则是Docker的客户端进行的,由于Docker的客户端和Docker守护进程可能会在不同机器上,所以这种机制就是为了保证构建镜像的Docker守护进程能够获取到所需要的本地文件。

命令格式

最为简单的使用命令如下所示:

使用命令:COPY/ADD 源文件或者目录 目的文件或者目录

缺省的当前目录

如果在Dockerfile中使用ADD my.cnf .或者COPY my.cnf .这样的语句的话, . 所指定的缺省的当前目录则是环境变量WORKDIR所指定的目录,两条命令都会将构建上下文中的my.cnf拷贝到WORKDIR所指定的目录中。

使用限制1

无路是COPY还是ADD,一般来说,要添加和拷贝的文件都需要在构建上下文之中,不然则会提示"no such file or directory"的错误。具体示例如下所示:

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

COPY /tmp/test .
liumiaocn:dockerfile liumiao$ ls
Dockerfile
liumiaocn:dockerfile liumiao$ touch /tmp/test
liumiaocn:dockerfile liumiao$

在构建上下文之外创建一个文件,然后使用docker build进行构建,会提示找不到此文件

liumiaocn:dockerfile liumiao$ docker build -t test:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/2 : COPY /tmp/test .
COPY failed: stat /var/lib/docker/tmp/docker-builder978982846/tmp/test: no such file or directory
liumiaocn:dockerfile liumiao$ 

ADD也会提示相同错误

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

ADD /tmp/test .
liumiaocn:dockerfile liumiao$ docker build -t test:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/2 : ADD /tmp/test .
ADD failed: stat /var/lib/docker/tmp/docker-builder613269509/tmp/test: no such file or directory
liumiaocn:dockerfile liumiao$ 

使用限制2

无路是COPY还是ADD,当拷贝源指定的为目录时,缺省状态下只会将目录下的文件拷贝过去,需要连同目录一起拷贝时需要在目标中指定目录名称,详细可参看如下Dockerfile示例, 事前准备如下所示:

liumiaocn:dockerfile liumiao$ tree .
.
├── Dockerfile
├── testdir
│   ├── testfile1
│   ├── testfile2
│   └── testfile3
└── testworkdir
    ├── testfile3
    └── testfile4

2 directories, 6 files
liumiaocn:dockerfile liumiao$ 

使用如下Dockerfile进行文件拷贝验证,并进行镜像构建

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

ADD testdir .
ADD testworkdir /tmp/testworkdir
ADD testworkdir /tmp/newdirname
liumiaocn:dockerfile liumiao$ 
liumiaocn:dockerfile liumiao$ docker build -t test:v2 .
Sending build context to Docker daemon  5.632kB
Step 1/4 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/4 : ADD testdir .
 ---> 43a7a545cdba
Step 3/4 : ADD testworkdir /tmp/testworkdir
 ---> fc63952baee5
Step 4/4 : ADD testworkdir /tmp/newdirname
 ---> c0be200a34d6
Successfully built c0be200a34d6
Successfully tagged test:v2
liumiaocn:dockerfile liumiao$

使用docker run命令启动容器以便确认结果

liumiaocn:dockerfile liumiao$ docker run --rm -it test:v2 sh
/ # ls
bin        etc        proc       sys        testfile2  tmp        var
dev        home       root       testfile1  testfile3  usr
~ # cd /tmp
/tmp # ls
newdirname   testworkdir
/tmp # ls newdirname/
testfile3  testfile4
/tmp # 
/tmp # ls testworkdir/
testfile3  testfile4
/tmp #

可以看到缺省目录为根目录,第一行ADD testdir .的结果导致testdir目录下的三个文件均被拷贝至镜像根目录下(缺省的当前目录),第2行为保留目录的方式,第3行稍作变化进行了同时更改目录名称的操作。
使用COPY会有同样结果,这里将WORKDIR稍作修改,结合使用修改Dockerfile并进行验证,执行结果如下所示

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

COPY testdir .
COPY testworkdir /tmp/testworkdir
WORKDIR /usr/local
COPY testworkdir ./newdirname
liumiaocn:dockerfile liumiao$ tree .
.
├── Dockerfile
├── testdir
│   ├── testfile1
│   ├── testfile2
│   └── testfile3
└── testworkdir
    ├── testfile3
    └── testfile4

2 directories, 6 files
liumiaocn:dockerfile liumiao$ docker build -t test:v3 .
Sending build context to Docker daemon  5.632kB
Step 1/5 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/5 : COPY testdir .
 ---> 3c337f30fbec
Step 3/5 : COPY testworkdir /tmp/testworkdir
 ---> ee48f32a6974
Step 4/5 : WORKDIR /usr/local
Removing intermediate container e271d35bd256
 ---> d51afa135050
Step 5/5 : COPY testworkdir ./newdirname
 ---> 41925848d344
Successfully built 41925848d344
Successfully tagged test:v3
liumiaocn:dockerfile liumiao$ 
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v3 sh
/usr/local # ls
newdirname
/usr/local # ls /tmp
testworkdir
/usr/local # ls /
bin        etc        proc       sys        testfile2  tmp        var
dev        home       root       testfile1  testfile3  usr
/usr/local # 

可以看到执行结果与ADD完全一致,需要注意的是WORKDIR起效的地方与Dockerfile设定的位置有关,两个COPY的当前位置一个是在根目录一个是在/usr/local下。

对tar文件的处理

对普通文件或者目录的操作,COPY和ADD基本一致,限制也都相同。而对于tar文件或者tar.gz之类的文件处理,则能看到两者的区别了,首先我们准备相关的tar和tar.gz文件,其文件构成如下所示:

liumiaocn:dockerfile liumiao$ tar tf testworkdir.tar.gz 
testworkdir/
testworkdir/testfile3
testworkdir/testfile4
liumiaocn:dockerfile liumiao$ tar tf testdir.tar 
testdir/
testdir/testfile1
testdir/testfile2
testdir/testfile3
liumiaocn:dockerfile liumiao$

首先使用COPY命令并构建和确认,可以看到文件正常拷贝到目标目录,没有任何惊喜。

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

WORKDIR /tmp
COPY testdir.tar .
COPY testworkdir.tar.gz .
liumiaocn:dockerfile liumiao$ docker build -t test:v4 .
Sending build context to Docker daemon  6.656kB
Step 1/4 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/4 : WORKDIR /tmp
Removing intermediate container ac522708a11f
 ---> c2c0081de0b0
Step 3/4 : COPY testdir.tar .
 ---> 8f81fd097c94
Step 4/4 : COPY testworkdir.tar.gz .
 ---> 88889bce1a3d
Successfully built 88889bce1a3d
Successfully tagged test:v4
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v4 sh
/tmp # ls
testdir.tar         testworkdir.tar.gz
/tmp # exit
liumiaocn:dockerfile liumiao$ 

然后改为ADD命令并进行确认,执行日志如下所示:

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

WORKDIR /tmp
ADD testdir.tar .
ADD testworkdir.tar.gz .
liumiaocn:dockerfile liumiao$ docker build -t test:v5 .
Sending build context to Docker daemon  6.656kB
Step 1/4 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/4 : WORKDIR /tmp
 ---> Using cache
 ---> c2c0081de0b0
Step 3/4 : ADD testdir.tar .
 ---> 3aa868ff6d23
Step 4/4 : ADD testworkdir.tar.gz .
 ---> 2baed95cb350
Successfully built 2baed95cb350
Successfully tagged test:v5
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v5 sh
/tmp # ls
testdir      testworkdir
/tmp # ls testdir
testfile1  testfile2  testfile3
/tmp # ls testworkdir
testfile3  testfile4
/tmp #

可以看到ADD的对象如果是tar或者tar.gz时,构建时会自动进行解压,只是想把文件拷贝过去,没想到帮着给解压了,是不是意外的惊喜。如果不了解这个机制,在后续还加入了解压的命令,自然就会出错了。这种黑魔法如果不是使用者所期待的,那有的时候就是惊吓了,毕竟解压只是一个很简单的操作,不用这么贴心也没有太大关系。压缩包有很多种,比如zip文件,就不会给解开,命令所在镜像中有unzip命令

liumiaocn:dockerfile liumiao$ ls
Dockerfile  testdir.zip
liumiaocn:dockerfile liumiao$ docker build -t test:v6 .
Sending build context to Docker daemon  3.584kB
Step 1/3 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/3 : WORKDIR /tmp
 ---> Using cache
 ---> c2c0081de0b0
Step 3/3 : ADD testdir.zip .
 ---> 55b0ba77137e
Successfully built 55b0ba77137e
Successfully tagged test:v6
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v6 sh
/tmp # ls
testdir.zip
/tmp # pwd
/tmp
/tmp # which zip
/tmp # which unzip
/bin/unzip
/tmp #

指定URL作为源文件

ADD还有一个较为鸡肋的功能,就是可以像curl或者wget那样,可以将文件下载到指定目录,从ADD的功能考虑,就是将URL的源文件拷贝至目标目录下,比如如下的Dockerfile

liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

WORKDIR /usr/local
ADD http://apache.mirror.cdnetworks.com/maven/maven-3/3.6.2/binaries/apache-maven-3.6.2-bin.tar.gz .
liumiaocn:dockerfile liumiao$ 
liumiaocn:dockerfile liumiao$ docker build -t test:v6 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/3 : WORKDIR /usr/local
Removing intermediate container e210061aece1
 ---> d1abb594e4bb
Step 3/3 : ADD http://apache.mirror.cdnetworks.com/maven/maven-3/3.6.2/binaries/apache-maven-3.6.2-bin.tar.gz .
Downloading [==================================================>]  9.142MB/9.142MB
 ---> f9a3c05e1964
Successfully built f9a3c05e1964
Successfully tagged test:v6
liumiaocn:dockerfile liumiao$ 
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v6 sh
/usr/local # ls
apache-maven-3.6.2-bin.tar.gz
/usr/local #

但是无论是ADD还是COPY,都会生成一个新的层.

liumiaocn:dockerfile liumiao$ docker history test:v6
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
f9a3c05e1964        3 minutes ago       /bin/sh -c #(nop) ADD b7b2bf32bed1c8b064d784…   9.14MB              
d1abb594e4bb        3 minutes ago       /bin/sh -c #(nop) WORKDIR /usr/local            0B                  
19485c79a9bb        2 months ago        /bin/sh -c #(nop)  CMD ["sh"]                   0B                  
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:9151f4d22f19f41b7…   1.22MB              
liumiaocn:dockerfile liumiao$ 
liumiaocn:dockerfile liumiao$ docker history test:v5
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
2baed95cb350        23 minutes ago      /bin/sh -c #(nop) ADD file:7a9a17d0315c64301…   0B                  
3aa868ff6d23        23 minutes ago      /bin/sh -c #(nop) ADD file:99a88ebf8e66f29a2…   0B                  
c2c0081de0b0        25 minutes ago      /bin/sh -c #(nop) WORKDIR /tmp                  0B                  
19485c79a9bb        2 months ago        /bin/sh -c #(nop)  CMD ["sh"]                   0B                  
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:9151f4d22f19f41b7…   1.22MB              
liumiaocn:dockerfile liumiao$

如果只是将文件从远端URL下载至镜像中,一般来说,一定会有后续的解压和删除原始文件的动作,所以一般必然会通过RUN命令结合起来,而使用RUN就可以完全实现的功能这里需要使用ADD和RUN结合才能实现,而且层数也会多一个。

实践原则1: 尽可能使用COPY

ADD实际上是COPY的超集,除去COPY的功能之外还有解压tar包和URL源文件支持等功能,但是解压功能也并非针对于所有的压缩包,拷贝的同时进行解压,对于用户来说还是一个黑箱功能的存在,比如在解压之前如需要进行其他操作或者完成性检查等操作都无法实现,Docker官方的最佳实践中也是建议,除非必须使用ADD的情况,尽可能的使用COPY。

实践原则2: 按照文件的变化频度的可能性进行分层添加

根据文件变化的频度和可能性进行添加,变化频度最低和最不容易变化的添加内容放在最低层,这样做有有如下好处:

  • 能够减少层数的变化
  • 合理使用缓存,提高镜像构建的速度

比如如下构建内容,首次构建信息如下所示,可以看到testfile1到testfile5都是创建了新的层,并没有使用缓存。

liumiaocn:dockerfile liumiao$ ls
Dockerfile testfile1  testfile2  testfile3  testfile4  testfile5
liumiaocn:dockerfile liumiao$ cat Dockerfile 
FROM busybox:latest

WORKDIR /usr/local
COPY testfile1 .
COPY testfile2 .
COPY testfile3 .
COPY testfile4 .
COPY testfile5 .
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker build -t test:v7 .
Sending build context to Docker daemon  4.608kB
Step 1/7 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/7 : WORKDIR /usr/local
 ---> Using cache
 ---> d1abb594e4bb
Step 3/7 : COPY testfile1 .
 ---> fe55ef082bd6
Step 4/7 : COPY testfile2 .
 ---> 35ae0f479e7b
Step 5/7 : COPY testfile3 .
 ---> 2548899f3cd2
Step 6/7 : COPY testfile4 .
 ---> d446641e7071
Step 7/7 : COPY testfile5 .
 ---> 7dfab6c86f2b
Successfully built 7dfab6c86f2b
Successfully tagged test:v7
liumiaocn:dockerfile liumiao$

假设testfile4所在的层发生了变化,重新构建的时候会发现,原来testfile1-testfile3所在的层都未发生变化,只有testfile4和testfile5发生了变化。构建的时候也直接使用了缓存,在testfile1-testfile3内容很大的时候能很好地提高构建的效率。详细如下所示

liumiaocn:dockerfile liumiao$ docker build -t test:v7 . 
Sending build context to Docker daemon   5.12kB
Step 1/7 : FROM busybox:latest
 ---> 19485c79a9bb
Step 2/7 : WORKDIR /usr/local
 ---> Using cache
 ---> 57300405bf4d
Step 3/7 : COPY testfile1 .
 ---> Using cache
 ---> afe826dc92bf
Step 4/7 : COPY testfile2 .
 ---> Using cache
 ---> 17fe013367a0
Step 5/7 : COPY testfile3 .
 ---> Using cache
 ---> 3a80bf8883ce
Step 6/7 : COPY testfile4 .
 ---> af00236f1ac5
Step 7/7 : COPY testfile5 .
 ---> 852902540610
Successfully built 852902540610
Successfully tagged test:v7
liumiaocn:dockerfile liumiao$ 
发布了920 篇原创文章 · 获赞 1260 · 访问量 389万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览