前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dockerfile语法及构建简单镜像

Dockerfile语法及构建简单镜像

作者头像
互联网-小阿宇
发布2022-11-21 19:58:52
4510
发布2022-11-21 19:58:52
举报
文章被收录于专栏:互联网-小阿宇

Dockerfile语法及构建简单镜像

前面使用过docker commit去构建镜像

Docker并不建议用户通过这种方式构建镜像。原因如下:

这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vi,还得重复前面的所有步骤。 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。 既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?

原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。

准备构建镜像

需要创建一个Dockerfile文件,文件名必须是这个

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile
# 添加
FROM centos
RUN yum -y install httpd
RUN yum -y install net-tools
RUN yum -y install elinks
CMD ["/bin/bash"]

构建镜像


使用docker build进行镜像的构建,最后需要指定Dockerfile文件所在路径

代码语言:javascript
复制
[root@localhost ~]# docker build -t chai/centos-http-net /root
Successfully built 09266c896243
Successfully tagged chai/centos-http-net:latest

看到最后输出两条Successfully则构建成功。

它会根据文件中写的内容,使用centos镜像实例化一个容器,进入容器中执行三个yum命令

查看已经构建好的镜像

代码语言:javascript
复制
[root@localhost ~]# docker images
REPOSITORY             TAG       IMAGE ID       CREATED          SIZE
chai/centos-http-net   latest    09266c896243   10 seconds ago   581MB

使用新的镜像实例化容器

代码语言:javascript
复制
[root@localhost ~]# docker run -it --rm --name test chai/centos-http-net /bin/bash
[root@50da58a03736 /]# ifconfig  # 命令可以运行即成功

镜像构建过程

在构建命令执行时输出的一大堆信息中,是执行Dockerfile中的每一行,最关键的几行信息如下

代码语言:javascript
复制
Step 1/5 : FROM centos  # 调用centos
 ---> 5e35e350aded   # centos镜像id

Step 2/5 : RUN yum install httpd -y
 ---> Running in a16ddf07c140  # 运行一个临时容器来执行install httpd
Removing intermediate container a16ddf07c140  # 完成后删除临时的容器id
 ---> b51207823459  # 生成一个镜像

Step 3/5 : RUN yum install net-tools -y
 ---> Running in 459c8823018a # 运行一个临时容器执行install net-tools
Removing intermediate container 459c8823018a # 完成后删除临时容器id
 ---> 5b6c30a532d4  # 再生成一个镜像

Step 4/5 : RUN yum install elinks -y
 ---> Running in a2cb490f9b2f  # 运行一个临时容器执行install elinks
Removing intermediate container a2cb490f9b2f # 完成后删除临时容器id
 ---> 24ba4735814b # 生成一个镜像

Step 5/5 : CMD ["/bin/bash"]
 ---> Running in 792333c88ba8  # 运行临时容器,执行/bin/bash
Removing intermediate container 792333c88ba8  # 完成后删除临时容器id
 ---> 09266c896243  # 生成镜像
Successfully built 09266c896243  # 最终成功后的镜像id就是最后生成的镜像id

每一步生成一个镜像,都属于一个docker commit的执行结果

在这个过程中一共生成了三个镜像层,都会被存储在graph中,包括层与层之间的关系,查看docker images中生成的镜像id是否为最后生成的镜像id,FROM和CMD都不算做镜像层

代码语言:javascript
复制
[root@localhost ~]# docker images
REPOSITORY             TAG       IMAGE ID       CREATED          SIZE
chai/centos-http-net   latest    09266c896243   10 seconds ago   581MB

通过docker history也可以看到简单的构建过程,这几个过程的size容量加起来也就是最终生成镜像的大小,也可将这里的镜像id和上面过程中的id进行对比,我们所看到的是三个yum就是形成的三个镜像层

代码语言:javascript
复制
[root@localhost ~]# docker history chai/centos-http-net:latest 
IMAGE         CREATED          CREATED BY                                      SIZE    COMMENT
09266c896243  17 minutes ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
24ba4735814b  17 minutes ago   /bin/sh -c yum install elinks -y                121MB               
5b6c30a532d4  18 minutes ago   /bin/sh -c yum install net-tools -y             112MB               
b51207823459  18 minutes ago   /bin/sh -c yum install httpd -y                 145MB               
5e35e350aded  4 months ago     /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>     4 months ago     /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B                  
<missing>     4 months ago     /bin/sh -c #(nop) ADD file:45a381049c52b5664…   203MB

镜像的缓存特性


之前文档中说到,nginx和httpd两个镜像都是基于debian系统制作的镜像,所以会使用相同的一部分镜像层去安装,而这个镜像被docker所共享,只需要下载一次即可

还是重新下载这两个镜像看一下是怎么进行使用

下载nginx镜像

代码语言:javascript
复制
[root@localhost ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
68ced04f60ab: Pull complete 
28252775b295: Pull complete 
a616aa3b0bf2: Pull complete 

下载httpd镜像

代码语言:javascript
复制
[root@localhost ~]# docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
68ced04f60ab: Already exists  # 已经存在
35d35f1e0dc9: Pull complete 
8a918bf0ae55: Pull complete 
d7b9f2dbc195: Pull complete 
d56c468bde81: Pull complete 

仔细观察下载的内容中,有两个id是一样的68ced04f60ab,在下载httpd的时候,会报出已经存在,因为在下载nginx时已经下载过了,这是因为这两个程序都是基于debian操作系统制作的镜像,这里就将这个id的镜像共享使用了,也就时使用了同一个镜像的缓存

自己构建的镜像也是具有这个缓存的特性的,在前面构建了一个镜像chai/centos-http-net,那我们根据构建这个镜像的Dockerfile文件的基础上进行一点点小的修改

效仿hello-world最小镜像的方法,也就是构建一个文档的镜像

代码语言:javascript
复制
[root@localhost ~]# vim testfile
# 添加内容
This is a FeiYi's file

然后将这个文件COPY到Dockerfile文件中,在之前的Dockerfile进行修改

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile
FROM centos
RUN yum -y install httpd
RUN yum -y install net-tools
RUN yum -y install elinks
COPY testfile /   # 将物理机当前目录的testfile文件复制到镜像中的根目录
CMD ["/bin/bash"]

将以上Dockerfile进行构建镜像,发现构建的很快,也没有很多的输出信息,而且除了新加的复制文件以外,其他的三个RUN镜像层,都多了一行Using cache,这是因为他们形成的镜像层已经作为缓存被构建的这个镜像使用

代码语言:javascript
复制
[root@localhost ~]# docker build -t test /root
Sending build context to Docker daemon  4.334MB
Step 1/6 : FROM centos
 ---> 5e35e350aded
Step 2/6 : RUN yum -y install httpd
 ---> Using cache
 ---> f01998ebc3ff
Step 3/6 : RUN yum -y install net-tools
 ---> Using cache
 ---> a3c39d101d8b
Step 4/6 : RUN yum -y install elinks
 ---> Using cache
 ---> 7c705388a610
Step 5/6 : COPY testfile /
 ---> 2111837dd86c
Step 6/6 : CMD ["/bin/bash"]
 ---> Running in 36782884ce16
Removing intermediate container 36782884ce16
 ---> 7b529720bf53
Successfully built 7b529720bf53
Successfully tagged test:latest

镜像层还有一个特点

如果改变了Dockerfile中镜像层的顺序,

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile
FROM centos
RUN yum -y install httpd
COPY testfile /
RUN yum -y install net-tools
RUN yum -y install elinks
CMD ["/bin/bash"]

然后进行构建,就会发现所有的内容全部重新构建

代码语言:javascript
复制
[root@localhost ~]# docker build -t test2 /root

这个特点就时层与层之间的关系,他们第一次构建时的顺序可以理解为他们之间的层级关系,如果执行顺序不一样,那么他们之间的层级关系也不一样,就不会去使用缓存构建。他们之间的关系被存储在graphDB中,如果读取不到相同的关系,是不会去使用缓存的。

–no-cache

–no-cache可以指定你构建镜像时,不适用已经存在的镜像层,也就是不使用缓存的特性

使用该参数重新构建刚才的Dockerfile

代码语言:javascript
复制
[root@localhost ~]# docker build -t test3 /root --no-cache

这样就会将所有的Dockerfile中的镜像层重新下载构建,而不是使用缓存。

Dockerfile文件排错方法

当个构建镜像时Dockerfile中报错,先来制作一个错误的Dockerfile

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c echo "continue to build..."
COPY testfile /

使用这个Dockerfile去构建镜像

代码语言:javascript
复制
[root@localhost ~]# docker build -t testerror /root
Sending build context to Docker daemon  4.336MB
Step 1/4 : FROM busybox
 ---> 83aa35aa1c79
Step 2/4 : RUN touch tmpfile
 ---> Running in 41a8dad29cd6
Removing intermediate container 41a8dad29cd6
 ---> 8cd5c9a720bb
Step 3/4 : RUN /bin/bash -c echo "continue to build..."
 ---> Running in bc1849fa8144
/bin/sh: /bin/bash: not found
The command '/bin/sh -c /bin/bash -c echo "continue to build..."' returned a non-zero code: 127

发现构建镜像过程中出现了报错/bin/sh: /bin/bash: not found

可以看到报错信息是从第三步才开始的,说明前两步是没有问题的,可以通过进入前两步最后结束的镜像id中去查看错误,进入前两层的镜像id是一个正常的容器环境,将第三步无法执行的命令,在容器中运行,将会看到真正的错误是没有/bin/bash这个环境

代码语言:javascript
复制
[root@localhost ~]# docker run -it  8cd5c9a720bb
/ # /bin/bash -c echo "continue to build..."
sh: /bin/bash: not found

因为构建这个镜像使用的是busybox,它使用的环境是/bin/sh

修改后,重新构建成功

代码语言:javascript
复制
[root@localhost ~]# docker build -t testerror /root
Successfully built ae5870fff063
Successfully tagged testerror:latest

运行容器后,可以看到创建的tmpfile和复制testfile

代码语言:javascript
复制
[root@localhost ~]# docker run -it testerror 
/ # ls
bin       etc       proc      sys       tmp       usr
dev       home      root      testfile  tmpfile   var

Dockerfile文件语法

常用构建镜像指令

代码语言:javascript
复制
FROM  # 指定base镜像
MAINTAINER # 指定镜像作者,后面根任意字符串
COPY # 把文件从host复制到镜像内
  COPY src dest
  COPY ["src","dest"]
  src:只能是文件
ADD # 用法和COPY一样,唯一不同时src可以是压缩包,表示解压缩到dest位置,src也可以是目录
ENV # 设置环境变量可以被接下来的镜像层引用,并且会加入到镜像中
  ENV MY_VERSION 1.3
  RUN yum -y install http-$MY_VERSION
  # 当进入该镜像的容器中echo $MY_VERSION会输出1.3
EXPOSE # 指定容器中的进程监听的端口(接口),会在docker ps -a中的ports中显示
  EXPOSE 80
VOLUME # 容器卷,后面会讲到,把host的路径mount到容器中
  VOLUME /root/htdocs /usr/local/apahce2/htdocs
WORKDIR # 为后续的镜像层设置工作路径
        # 如果不设置,Dockerfile文件中的每一条命令都会返回到初始状态
        # 设置一次后,会一直在该路经执行之后的分层,需要WORKDIR /回到根目录
CMD # 启动容器后默认运行的命令,使用构建完成的镜像实例化为容器时,进入后默认执行的命令
    # 这个命令会被docker run启动命令替代
    # 如:docker -it --rm centos echo "hello"
    # echo "hello"会替代CMD运行的命令
  CMD ["nginx", "-g", "daemon off"]  # 该镜像实例化后的容器,进入后运行nginx启动服务
ENTRYPOINT # 容器启动时运行的命令,不会被docker run的启动命令替代

RUN/CMD/ENTRYPOINT区别

在语法中说到CMD和ENTRYPOINT是容器启动后和容器启动时,运行的命令,RUN是构建镜像时运行的命令。三者的区别究竟在什么地方,通过一个例子来看

使用这三者来构建一个镜像

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile 
# 添加
FROM centos
RUN yum -y install net-tools
CMD echo "hello chai"
ENTRYPOINT echo "hello mupei"
[root@localhost ~]# docker build -t chai /root
Successfully built 10dec59bba24
Successfully tagged chai:latest

构建完成后,运行镜像

代码语言:javascript
复制
[root@localhost ~]# docker run -it chai
hello mupei

很明显在构建构成中RUN已经完成了它的工作

RUN:执行命令并创建新的镜像层,主要用于安装软件包

而在运行镜像后,只输出了hello mupei,是ENTRYPOINT来执行的命令

这两个都算作是启动指令,也就是必须启动容器才会去执行的指令,一般用来启动运行程序使用

结论:当ENTRYPOINT和CMD同时存在时,ENTRYPOINT生效

ENTRYPOINT和CMD使用格式

shell和exec两种格式

shell格式

CMD command、ENTRYPOINT command

shell格式会始终调用一个shell程序去执行命令

通过一个例子来看

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile 
FROM centos
RUN yum -y install net-tools
ENV name chai
ENTRYPOINT echo "hello $name"
[root@localhost ~]# docker build -t pei /root
Successfully built cf475e27d587
Successfully tagged pei:latest
[root@localhost ~]# docker run -it pei
hello chai

当指令执行时,shell会调用/bin/bash

exec格式

CMD [“命令”, “选项”, “参数”]、ENTRYPOINT [“命令”, “选项”, “参数”]

exec格式下无法去调用ENV定义的变量,如果非要让exec格式去读取变量的话,它的命令的位置就要使用一个shell环境。因为变量的读取就是使用shell去读取的。 如:ENTRYPOINT ["/bin/sh", “-c”, “echo hello,$变量名”]

再看一个例子

代码语言:javascript
复制
[root@localhost ~]# vim Dockerfile 
FROM centos
RUN ["yum", "-y", "install", "net-tools"]
CMD ["/bin/echo", "hello chai"]
ENTRYPOINT ["/bin/echo", "hello world"]
[root@localhost ~]# docker build -t feiyi /root
Successfully built 52189c01eaf5
Successfully tagged feiyi:latest

运行容器后,只有ENTRYPOINT的命令是正常执行了,输出了我们需要的hello world,

ENTRYPOINT ["/bin/echo", “hello world”]:这一行中,/bin/echo是命令,hello world是执行的参数

而CMD中的/bin/echo和hello chai都做为结果输出,并没有被当做命令

代码语言:javascript
复制
[root@localhost ~]# docker run -it feiyi
hello world /bin/echo hello chai

结论:当使用exec格式时,ENTRYPOINT的第一个参数被识别为命令,CMD的参数按顺序变为ENTRYPOINT命令的参数

这个结论相当于Dockerfile文件中的以下两行=echo “hello world /bin/echo hello chai”

CMD ["/bin/echo", “hello chai”]

ENTRYPOINT ["/bin/echo", “hello world”]

即:

代码语言:javascript
复制
[root@localhost ~]# docker run -it feiyi
hello world /bin/echo hello chai
[root@localhost ~]# echo "hello world /bin/echo hello chai"
hello world /bin/echo hello chai
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Dockerfile语法及构建简单镜像
    • 准备构建镜像
      • 构建镜像
        • 镜像构建过程
          • 镜像的缓存特性
            • 镜像层还有一个特点
              • –no-cache
                • Dockerfile文件排错方法
                  • Dockerfile文件语法
                    • RUN/CMD/ENTRYPOINT区别
                      • ENTRYPOINT和CMD使用格式
                        • shell格式
                        • exec格式
                    相关产品与服务
                    命令行工具
                    腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档