Docker-DockerFile的使用

在使用DockerFile定制镜像之前,我们先来了解一下镜像的构成:镜像是容器的基础,每次执行docker run命令的时候都会指定哪个镜像作为容器运行的基础。在之前的栗子中,我们使用的镜像都是来着Docker Hub的镜像。直接使用这些镜像为基础运行容器可以一定程度上满足我们的需求,可是当这些镜像都无法直接满足需求时,我们就需要定制镜像!

docker commit构建

现在我们以定制一个Web服务器为例:

root@ubuntu:~# docker run --name webserver -d -p 80:80 nginx
f0a2ad681b692642be0b825631d8716ae3d8c1fb5ad48148362331df3fae9775

此时,我们在浏览器中访问本机地址,例如以本机为例192.168.0.89,如图:

image

这条命令会以Nginx为镜像创建并启动一个容器,映射了80端口,这样我们可以在浏览器中访问这个Nginx服务器!假设,我们现在不喜欢这个欢迎页面的样式,想把它改为“Hello Docker!”,我们可以使用docker exec命令,进入容器,修改器内容:

root@ubuntu:~# docker exec -it webserver bash
root@f0a2ad681b69:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@f0a2ad681b69:/# exit

此时,我们再刷新浏览器的话,发现内容已经发生了改变,如图:

image

改变了容器的文件,也就是说,改变了容器的存储层,我们可以使用docker diff来查看文件的修改情况:

root@ubuntu:~# docker diff webserver
C /root
A /root/.bash_history
C /run
A /run/nginx.pid
C /usr/share/nginx/html/index.html
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

现在,我们定制好了变化,希望能将其保存下来形成镜像。要知道,我运行一个容器的时候,我们对任何文件的修改操作都会记录在容器的存储层里。Docker提供了一个docker commit命令,可以将容器的存储层保存为镜像。docker commit的语法格式为:

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

下面,我们将上面那个容器保存为镜像,命令如下:

root@ubuntu:~# docker commit --author "ycx <ycx921101@163.com>" --message "修改了Nginx欢迎页面" webserver nginx:v1.0
sha256:1b6b342acb5e91a5da9a564818e94ec1041f7a152b249075f109b0c259d580f0

--author指定修改的作者,--message为记录本次修改的内容,webserver为容器名,也可以传入容器ID,nginx:v1.0为仓库名和标签。保存成功之后,会返回一个摘要!接下来,我们使用docker image ls命令来查看当前宿主机上所有的镜像:

root@ubuntu:~# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v1.0                1b6b342acb5e        5 seconds ago       109MB
nginx               latest              5699ececb21c        5 days ago          109MB

我们刚刚保存的镜像已经出现在上面了!!!

我们还可以使用docker history命令来查看镜像内的历史记录,接下来,我们查看以下刚刚保存的镜像内的历史记录,如下:

docker history nginx:v1.0
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
1b6b342acb5e        4 minutes ago       nginx -g daemon off;                            97B                 修改了Nginx欢迎页面
5699ececb21c        5 days ago          /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B                  
<missing>           5 days ago          /bin/sh -c #(nop)  STOPSIGNAL [SIGTERM]         0B                  
<missing>           5 days ago          /bin/sh -c #(nop)  EXPOSE 80/tcp                0B                  
<missing>           5 days ago          /bin/sh -c ln -sf /dev/stdout /var/log/nginx…   22B                 
<missing>           5 days ago          /bin/sh -c set -x  && apt-get update  && apt…   53.7MB              
<missing>           5 days ago          /bin/sh -c #(nop)  ENV NJS_VERSION=1.15.0.0.…   0B                  
<missing>           5 days ago          /bin/sh -c #(nop)  ENV NGINX_VERSION=1.15.0-…   0B                  
<missing>           5 days ago          /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B                  
<missing>           5 days ago          /bin/sh -c #(nop)  CMD ["bash"]                 0B                  
<missing>           5 days ago          /bin/sh -c #(nop) ADD file:28fbc9fd012eef727…   55.3MB 

如果有兴趣的朋友,再用docker history命令来查看以下nginx:latest镜像内的历史记录,对比以下会发现,nginx:v1.0多了一层我们刚刚提交的那一层!!!

新的镜像定制好了之后,我们再以这个镜像为基础,运行一个容器,如下:

root@ubuntu:~# docker run --name webserver2 -it -d -p 81:80 nginx:v1.0
092f8e20bc6cf6c73c79bd66b9cd0f26cc81ddba153db90107e477c5f558ed91

这里,我们将新启动的容器命名为webserver2,并且映射到81端口。此时,我们再次在浏览器输入192.168.0.89:81时,会发现与之前修改后的效果是一样的,如图:

image

使用docker commit命令虽然可以帮助理解镜像分层存储的概念,但是实际开发中并不推荐这样使用!!!它有下面这几点危害:

  • 首先,如果你仔细观察docker diff webserver的结果,你会发现除了仅仅被我们修改的/usr/share/nginx/html/index.html文件,还有很多文件被改动或添加了。这还仅仅只是最简单的操作,如果是安装软件包、编译构建,那会有大量无关的内容被添加进来,如果不小心清理,那将会导致镜像极为臃肿
  • 使用docker commit命令执行的操作都是黑箱操作,生成的镜像也被称之为黑箱镜像!换言之,除了镜像的制作人之外,其他使用该镜像的人根本无从得知执行过什么命令,怎么生成的镜像。而且,即使是制作该镜像的本人,过了一段时间之后也许也无法记得具体执行什么操作!这种黑箱镜像的维护工作是非常痛苦的!!!
  • 根据镜像分层的概念,除当前层外,之前的每一层都是不会被修改的,换句话说,任何的修改结果只会在当前层进行标记、删除、修改,而不会改动到上一层。而使用docker commit命令制作镜像,以及后期的修改,每一次的操作都会让镜像更加臃肿一次。而且所删除的上一层的东西并不会丢失,会如影随形的跟着这个镜像。

使用DockerFile构建

我们可以把每一层的安装、修改、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前我们提及的无法重复的问题、镜像构建透明性的问题、体积的问题都会解决,这个脚本就是DockerFile!下面,我们将上面的镜像构建改成用DockerFile来进行:

  • 使用vi编辑器,打开一个空白文件Dockerfile
FROM nginx
RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
  • Dokcerfile文件同级目录下,使用docker build命令进行构建:
docker build -t nginx:v1.1 .
  • 此时,我们再使用docker image ls命令来查看当前宿主机下的镜像列表时,刚刚生成的nginx:v1.1已经出现在上面:
root@ubuntu:~/DockerFiles# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v1.1                a473544051ce        4 seconds ago       109MB
nginx               latest              5699ececb21c        6 days ago          109MB

这里我们使用docker build命令来进行构建镜像,其命令格式为:

docker build [OPTIONS] PATH | URL | -

在这里,我们指定了最终镜像的名称nginx:v1.1-t为执行镜像的名称,千万不要忽略结尾处的.,它表示当前目录。构建成功后,我们可以像之前一样在指定基础镜像之后,创建并启动一个容器。

镜像构建上下文

在上面的构建命令中,会看到在docker build命令的结尾处有一个.,表示当前目录,而Dockerfile就在当前目录,因此不少初学者以为这个就是指定Dockerfile所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你就会发现,这其实是在指定上下文路径。

Docker在运行时分为Docker引擎和客户端工具。Docker引擎提供了一组REST API,被称为Docker Remote API,而Docker命令这样的客户端工具,则是通过Docker Remote API来与Docker 引擎进行交互,从而完成各种功能的。虽然,表面上我们是在本机上完成各种Docker功能,但是实际上我们是通过Docker Remote API与Docker引擎交互,在Docker引擎中完成的各种功能。正是因为这种C/S设计,使得我们操作远程服务器的Docker引擎变得轻而易举。

当我们进行镜像构建时,并非所有定制都会通过RUN命令完成,经常我们需要将一些本地文件复制至镜像,比如通过COPY指令、ADD指令等。可是镜像的构建是在Docker引擎中完成的,我们该如何将本地的文件上传到Docker引擎中呢?这就引入上下文概念,当镜像构建时会指定上下文路径,docker build得知这个路径时,会将该路径下的所有内容打包,上传给Docker引擎。这样Docker引擎接收到这个上下文包之后,展开就会获得构建镜像的一切所需文件。如果你在Dockerfile中,这样写:

COPY ./package.json /app/

这并不是复制docker build执行命令所在路径下的package.json,也不是Dockerfile所在目录下的package.json,而是复制上下文路径下的package.json。因此,COPY这类指令中的源文件路径都是相对路径,这也就是为什么一些初学者中常遇到的问题:COPY /opt/xxx /app无法工作的原因,因为这已经超出了上下文的范围,Docker引擎无法获得这些位置的路径。如果真的需要这些文件,应该将其复制到上下文路径当中。

通常情况下,如果不额外指定Dockerfile的话,会将上下文路径当中名称为Dockerfile的文件作为Dockerfile。正因为这样,一些小伙伴因为定义的名称并非是Dockerfile,而导致上面的构建命令执行不通!这只是默认行为,实际上Dockerfile的文件名并不要求必须是Dockerfile,也并不要求必须将Dockerfile文件置于上下文目录当中,你可以添加-f参数来指定Dockerfile的文件,例如:

root@ubuntu:~/DockerFiles/docker# docker build -f ../DockerNginx -t nginx:v1.2 .
Sending build context to Docker daemon  1.583kB
Step 1/2 : FROM nginx
 ---> 5699ececb21c
Step 2/2 : RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Using cache
 ---> a473544051ce
Successfully built a473544051ce
Successfully tagged nginx:v1.2

此时,我们再来看一下,当前存在的镜像列表:

root@ubuntu:~# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v1.1                a473544051ce        3 days ago          109MB
nginx               v1.2                a473544051ce        3 days ago          109MB
nginx               latest              5699ececb21c        10 days ago         109MB

可以,看到,刚刚我们构建的镜像已经存在在上面了。而我们的Dockerfile文件,名称也并不是叫Dockerfile,并且文件也并不是在上下文路径当中!!!

docker build其他用法

  • 从远程仓库中进行构建

这里,我从我的GitHub中创建了一个仓库,并简单写了一个Dockerfile,下面我们就就尝试从远程仓库中进行构建:

root@ubuntu:~# docker build https://github.com/Mr-ycx/docker.git#:test -t nginx:v1.3
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 5699ececb21c
Step 2/2 : RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in 25ea95c42a08
Removing intermediate container 25ea95c42a08
 ---> fa8d8e64f43f
Successfully built fa8d8e64f43f
Successfully tagged nginx:v1.3

下面,我们再次使用docker image ls命令来列出,当前宿主机上存在的镜像:

root@ubuntu:~# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v1.3                fa8d8e64f43f        3 minutes ago       109MB
nginx               v1.1                a473544051ce        3 days ago          109MB
nginx               v1.2                a473544051ce        3 days ago          109MB
nginx               latest              5699ececb21c        10 days ago         109MB
ubuntu              12.04               5b117edd0b76        15 months ago       104MB

docker build指定了构建所需的Git Repository,并且指定默认的master分支,构建目录为/test/。然后Docker会自己去git clone这个项目,然后切换到指定分支,最后进入指定目录下进行构建。

  • 用远程tar压缩包进行构建

这里,我自己新建一个了Web项目,将制作好的压缩包置于编写好的接口中提供下载。下面我们就尝试从远程的压缩文件中进行构建:

root@ubuntu:~# docker build http://192.168.0.89:5000/docker.tar -t nginx:v1.4
Step 1/2 : FROM nginx to Docker daemon  10.24kB://192.168.0.89:5000/docker.tar [==================================================>]  10.24kB/10.24kB
 ---> 5699ececb21c
Step 2/2 : RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Using cache
 ---> fa8d8e64f43f
Successfully built fa8d8e64f43f
Successfully tagged nginx:v1.4

下面,我们再次使用docker image ls命令来列出,当前宿主机上存在的镜像:

root@ubuntu:~# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v1.3                fa8d8e64f43f        31 minutes ago      109MB
nginx               v1.4                fa8d8e64f43f        31 minutes ago      109MB
nginx               v1.1                a473544051ce        3 days ago          109MB
nginx               v1.2                a473544051ce        3 days ago          109MB
nginx               latest              5699ececb21c        10 days ago         109MB
ubuntu              12.04               5b117edd0b76        15 months ago       104MB

如果给出的URL不是一个Git Repository,而是一个tar压缩包,那么Docker会自己去下载这个压缩包,将其展开并在里面将其作为上下文,进行构建。

值得注意的是,那么Docker会自己去下载这个压缩包并解压,并以解压至的目录为上下文目录!!!假如你的Dockerfile文件在某个文件夹下面,或者你的Dockerfile文件名不为Dockerfile,那么需使用-f命令指定Dockerfile文件,否则将无法定位到,如下:

root@ubuntu:~# docker build http://192.168.0.89:5000/docker2.tar -f ./docker2/Dockerfile -t nginx:v1.5
Step 1/2 : FROM nginx to Docker daemon  10.24kB://192.168.0.89:5000/docker2.tar [==================================================>]  10.24kB/10.24kB
 ---> 5699ececb21c
Step 2/2 : RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Using cache
 ---> fa8d8e64f43f
Successfully built fa8d8e64f43f
Successfully tagged nginx:v1.5
  • 读取Dockerfile进行构建

我们可以将一个文件的内容读取出来,而后将其当成Dockerfile进行构建,假设有这样一个文件DockerInfo,内容如下:

FROM nginx
RUN echo '<h1>Hello Docker!</h1>' > '/usr/share/nginx/html/index.html'

现在,我们以读取文件内容,然后进行构建的方式来构建镜像:

root@ubuntu:~# docker build -< DockerInfo -t nginx:v1.6
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 5699ececb21c
Step 2/2 : RUN echo '<h1>Hello Docker!<h1>' > '/usr/share/nginx/html/index.html'
 ---> Running in a9bff86a9e6c
Removing intermediate container a9bff86a9e6c
 ---> 69e0d5145220
Successfully built 69e0d5145220
Successfully tagged nginx:v1.6

也可以使用如下命令进行构建:

cat DockerInfo | docker build - -t nginx:v1.7

值得注意的是,这种读取文本文件内容,然后将其当成Dockerfile来进行构建的方式,由于是直接读取出文本文件的内容,它没有上下文,因此这种方式不可以使用将本地文件COPY至镜像之类的事情

  • 用本地tar压缩包进行构建

在上面,我们介绍了使用远程tar压缩包进行构建的方式,既然可以使用远程tar压缩包,那么就一定可以使用本地压缩包进行构建,如下:

root@ubuntu:~# docker build - < docker.tar -t nginx:v1.8
Sending build context to Docker daemon  10.24kB
Step 1/2 : FROM nginx
 ---> 5699ececb21c
Step 2/2 : RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Using cache
 ---> fa8d8e64f43f
Successfully built fa8d8e64f43f
Successfully tagged nginx:v1.8

如果文件格式是 gzip 、 bzip2 以及 xz 的话,将会使其为上下文压缩包,将其展开并在里面将其作为上下文。同样,假如你的Dockerfile文件在某个文件夹下面,或者你的Dockerfile文件名不为Dockerfile,那么需使用-f命令指定Dockerfile文件,否则将无法定位到

发布了158 篇原创文章 · 获赞 112 · 访问量 52万+
展开阅读全文

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

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览