Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)

一、构建镜像介绍

  • 我们可以定制属于自己的Docker镜像,然后将Docker镜像存储到存储库中
  • 构建镜像的方法有2种:
    • 使用docker commit命令
    • 使用docker build命令和Dockerfile文件
  • 不推荐使用docker commit命令,建议使用docker build命令(编写完Dockerfile然后使用docker build命令)
  • 备注:此处我们不是真正“创建”新镜像,而是对已有镜像进行修改构建。如果想要从0构建一个全新的镜像,可以参阅:https://docs.docker.com/articles/baseimages

创建Docker Hub账号

  • Docker Hub:https://hub.docker.com/
  • 我们可以自己注册一个Docker Hub账号,然后将构建好的镜像推送到Docker Hub或者用户自己的私有Registry中
  • 创建账号之后,可以输入下面的命令登录到Docker Hub
sudo docker login

  • 个人认证信息配置文件:登录完成之后会将认证信息保存起来以供以后使用,信息会保存到$HOME/.dockercfg文件中。从Docker 1.7.0开始,该文件变为了$HOME/.docker/config.json

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第1张图片

  • 退出登录的命令如下:
sudo docker logout

 

二、使用commit命令创建镜像

  • 可以使用docker commit命令来提交一个新镜像
  • docker commit提交的只是创建容器的镜像与容器的当前状态之间有差异的部分,这使得该更新非常轻量

演示案例

  • 第一步:运行一个带有ubuntu镜像的容器
sudo docker run -i -t ubuntu /bin/bash

  • 第二步:然后我们在该ubuntu镜像系统中安装Apache软件包
# 更新,-yqq忽略所有提示信息
apt-get -yqq update

# 安装apache2服务器
apt-get -y install apache2

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第2张图片

  • 第三步:现在我们在这个镜像中安装了Apache2的服务器,我们想把这个镜像的当前状态保存下来,这样就不必每次都创建一个新容器并在此安装了。可以执行下面的命令来提交指定的容器:
# 查看刚才那个容器的ID
sudo docker ps -a

# 提交定制容器,需要指定容器ID、镜像名
sudo docker commit f3f694f1fc97 jamtur01/apache2

  • 第四步:查看一下新创建的镜像
sudo docker images jamtur01/apache2

  • 第五步:可以通过dcoker inspect命令查看新创建的镜像的详细信息
sudo docker inspect jamtur01/apache2

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第3张图片

  • 可以尝试着以上面创建的新镜像运行一个容器
sudo docker run -i -t jamtur01/apache2

演示案例

  • 在提交新定制的镜像时还可以指定更定的信息选项,例如:
    • -m:用来指定提交时的备注信息
    • -a:列出该镜像的作者信息
    • 并且下面的镜像还给其指定了一个webserver标签
sudo docker commit -m"A new custom image" -a"dongyusheng" f3f694f1fc97 jamtur01/apache2:webserver

  • 查看一下新创建的镜像
sudo docker images jamtur01/apache2:webserver

  • 可以通过dcoker inspect命令查看新创建的镜像的信息(作者和备注)
sudo docker inspect --format='{{ .Author }} {{ .Comment }}' jamtur01/apache2:webserver

  • 可以尝试着以上面创建的新镜像运行一个容器
sudo docker run -i -t jamtur01/apache2:webserver /bin/bash

  • 现在我们本机一共有下面这些容器,图中圈出来的是新创建的镜像的名称
sudo docker ps -a

三、使用Dockerfile构建镜像

  • 上面介绍了使用docker commit的方法来构建镜像,不推荐使用这种方法
  • 下面介绍“Dockerfile + docker build”来构建镜像,比较推荐使用这种方式构建镜像,因此这种方法构建镜像更具备可重复性、透明性以及幂等性
  • Dockfile:
    • 就是一个文件,基于DSL(Domain Specific Language)语法的指令来构建一个Docker镜像。关于Dockfile的指令文章下面介绍
    • Dockfile由一系列指令和参数组成。每条指令(例如FROM)都必须为大写字母,且后面要跟随一个参数
    • Dockfile会按照文件中的指令从上到下进行执行,每执行一条指令都会创建一个新的镜像层并对镜像进行提交。Docker大题上按照如下流程执行Dockfile中的指令:
      • Docker从基础镜像运行一个容器
      • 执行一条指令,对容器做出修改
      • 执行类似docker commit的操作,提交一个新的镜像层
      • Docker再基于刚提交的镜像运行一个新容器
      • 执行Dockfile中的下一条指令,直到所有指令都执行完毕
    • Dockfile也支持注释,注意以#开头
  • 下面介绍一个演示案例

第一步:创建一个Dockfile文件

  • 第一步:自己建立一个目录(取名随意),然后进入目录创建一个文件名为Dockfile的文件(必须名为Dockerfile)
mkdir ~/static_web

cd ~/static_web

touch Dockerfile

  • 此处我们创建了一个名为static_web的目录来保存Dockfile,这个目录就是我们的构建环境,Docker称此环境为上下文或者构建上下文。Docker会在构建镜像时将构建上下文和该上下文的文件和目录上传到Docker守护进程。这样Docker守护进程就能直接访问用户想在镜像中存储的任何代码、文件或者其他数据
  • 第二步:编写上面创建的Dockfile文件,内容如下:
# Version: 0.0.1
FROM ubuntu:16.04
MAINTAINER dongyusheng "[email protected]"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第4张图片

  • 下面介绍上面Dockerfile文件使用到的指令
  • FROM指令:
    • 每个Dockerfile的第一条指令必须是FROM
    • 该指令指定了一个已经存在的镜像,后续指令都将基于该镜像进行,这个镜像备被称为基础镜像。在此演示案例中,我们以ubuntu:16.04作为基础镜像
  • MAINTRINER指令:该指令告诉Docker该镜像的作者是谁,以及作者的电子邮箱地址
  • RUN指令:
    • RUN指令就是来指定在镜像中执行的命令。例如在这个演示案例中,我们执行了两条命令
    • 备注:默认情况下,RUN指令会在shell里使用命令包装器/bin/sh -c来执行。如果在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令。演示案例如下:
# 用一个数组来指定要运行的命令和传递给该命令的每个参数
RUN { "apt-get", " install", "-y", "nginx" }
  • EXPOSE指令:
    • 该指令告诉Docker该容器内的应用程序将会使用容器的指定端口
    • 这并不意味着可以自动访问任意容器运行中的服务的端口(这里为80)
    • 处于安全的原因,Docker并不会自动打开该端口,而是需要用户在使用docker run运行容器时来指定需要打开哪些端口。下面我们会看到如何从这一镜像创建一个新容器
    • 可以指定多个EXPOSE指令来向外部公开多个端口
    • 备注:Docker也可以使用EXPOSE指令来帮助将多个容器链接,我们可以在“在测试中使用Docker”看到相关内容。用户可以在运行时以docker run命令通过--expose选项来指定对外部公开的端口

第二步:基于Dockerfile构建新镜像

  • 上面我们的Dockerfile已经书写好了,现在执行下面的命令来构建一个新镜像:
    • -t:新镜像的名称
    • .:表示Dockerfile文件的路径,此处我们在当前目录,因此就用了.表示
sudo docker build -t="jamtur01/static_web" .

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第5张图片

  • 结果分析:
    • 从上图可以看到命令输入之后,会将构建上下文传到Docker守护进程中(第一行)。如果在构建上下文的根目录存在以.dockerignore命令的文件的话,那么该文件内容会按行进行分割,每一行都是一条文件过滤匹配模式。这非常像.gitignore文件,该文件用来设置哪些文件不会被当做构建上下文的一部分,因此可以防止它们被上传到Docker守护进程中去。该文件中的匹配模式的匹配规则采用了Go语言中的filepath(http://golang.org/pkg/path/filepath/#Match)
    • Dockerfile中的每条指令都会被顺序执行,每一条指令对应一条“Step”,然后返回该指令的新镜像的ID,最后所有指令都执行完成之后,最终镜像的ID为b22dcf7f0505
  • 附加:如果不设置标签,那么Docker会自动为镜像设置一个latest标签。我们也可以为镜像设置标签,例如下面将其标签设置为v1
sudo docker build -t="jamtur01/static_web":v1 .
  •  附加:docker build最后一个参数为Dockerfile文件的路径,除了在本地目录中查找,还可以指定一个Git仓库的源地址来告诉Dockerfile文件的位置。例如:
sudo docker build -t="jamtur01/static_web":v1 [email protected]:jamtur01/docker-static_web
  • -f选项:自Docker 1.5.0开始,可以通过-f选项指定一个区别于标准Dockerfile的构建源的位置。例如,下面的命令将当前路径下的file文件作为Dockerfile文件
sudo docker build -t="jamtur01/static_web":v1 ./file

第三步:查看新镜像

  • 现在可以看一下新构建的新镜像,可以使用以前介绍过的docker images命令来查看,如下所示:可以看到镜像ID与上面最后生成的镜像ID相同
sudo docker images jamtur01/static_web

  • 如果想要深入镜像是如何构建出来的,可以使用docker history命令,如下所示:
    • 从下面的结果可以看到新构建的jamtur01/static_web镜像的每一层,以及创建这些曾的Dockerfile指令
sudo docker history jamtur01/static_web

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第6张图片

第四步:从新镜像启动容器

  • 我们在镜像中安装了Nginx,现在我们可以用该镜像来运行一个容器,命令如下:
    • -d:这个参数在前面文章已经介绍过了,让该容器以守护进程的方式在宿主机中运行
    • -p:运行容器时,将容器的80端口映射到宿主机的一个随机端口上
    • 最后我们运行了nginx程序,并且将其配置文件的daemon参数改为了off,让Nginx在容器中已非守护进程的方式运行程序
sudo docker run -d -p 80 --name static_web jamtur01/static_web nginx -g "daemon off;"

  • 运行一个容器时,Docker可以通过两种方法来在宿主机上分配端口:
    • Docker可以在宿主机上随机选择一个位于32768~61000的一个比较大的端口号来映射到容器中的80端口上(上面的演示案例就是的,宿主机随意选择一个端口映射到Docker容器的80端口上)
    • 可以在Docker宿主机中指定一个具体的端口号来映射到容器的80端口上(下面有演示案例)
  • 现在我们可以来查看容器的端口分配情况,如下所示,宿主机的32768端口映射到了Docker容器的80端口上
sudo docker ps -l

  • 现在我们可以该地址和端口来访问容器内部的nginx服务器。例如:
curl localhost:32768

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第7张图片

  • 我们也可以docker port命令来查看容器的端口的映射情况,例如下面查看容器80端口的映射情况:
# 通过容器ID
sudo docker port 5c194f6d65b8 80

# 或者通过容器名称查看
sudo docker port static_web 80

附加:映射指定地址和端口

  • 在第四步中,我们将宿主机的随机端口映射到了容器的80端口上,我们还可以将容器的端口映射到主机的指定端口上
  • 例如,下面是将容器的80端口映射到本地宿主机的8080端口上:
sudo docker run -d -p 8080:80 --name static_web jamtur01/static_web nginx -g "daemon off;"
  • 也可以将容器的端口映射到宿主机指定的地址和端口上,例如下面将容器的80端口映射到本地宿主机127.0.0.1接口的80端口上
sudo docker run -d -p 127.0.0.1:80:80 --name static_web jamtur01/static_web nginx -g "daemon off;"
  • 还可以将容器的端口映射到宿主机指定接口的随机端口上,例如下面将容器的80端口映射到本地宿主机127.0.0.1接口的随机端口上
sudo docker run -d -p 127.0.0.1:80 --name static_web jamtur01/static_web nginx -g "daemon off;"
  • 也可以在端口绑定时使用/udp后缀来指定UDP端口
  • -P选项:
    • 该选项可以用来对外公开在Dockerfile中通过EXPOSE指令公开的所有端口
    • 例如,下面容器运行时,会将"jamtur01/static_web"镜像的Dockerfile中EXPOSE公开的端口映射到映射到宿主机的一个随机端口上
sudo docker run -d -P --name static_web jamtur01/static_web nginx -g "daemon off;"

四、镜像的推送(push)

  • 镜像构建完成之后,可以把镜像上传到Docker Hub上去,这样其他人就能使用这个镜像了。比如,我们可以在阻止内共享这个镜像,或者完全公开这个镜像
  • 备注:Docker Hub也提供了对私有仓库的支持,这是一个需要付费的功能,用户可以将镜像存储到私有仓库中,这样只有用户或者任何与用户共享这个私有仓库的人才能访问该镜像。这样用户就可以将机密信息或者代码放到私有镜像中,不必担心被公开访问了

演示案例

  • 例如上面我们将构建好的镜像推送到Docker Hub上,命令如下:
sudo docker push jamtur01/static_web

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第8张图片

  • 可以看到发生了错误,推送失败了,原因是:jamtur01/static_web仓库已经存在了

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第9张图片

  • 解决方法:更改仓库名和镜像名,改为自己的Docker Hub账号,例如我的Docker Hub账号为dongyusheng,因此就更改为dongyusheng/static_web
# 更改镜像名
docker tag jamtur01/static_web dongyusheng/static_web

  • 接着重新进行推送,成功:
sudo docker push dongyusheng/static_web

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第10张图片

  • 推送完成之后,在自己的Docker Hub中可以看到该仓库和镜像

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第11张图片

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第12张图片

五、自动构建

  • 除了从命令行构建和推送镜像,Docker Hub还允许我们定义自动构建(Automated Builds)
  • 实现方式:
    • 为了使用自动构建,我们只需要将Github或BitBucket中含有Dockerfile文件的仓库连接到Docker Hub即可
    • 向Github或BitBucket仓库推送代码时,将会触发一次镜像构建活动并创建一个新镜像
  • 在之前该工作机制被称为可信构建(Trusted Build)
  • 备注:自动构建同样支持私有Github和BitBucket仓库

演示案例

  • 待续,具体参阅《第一本Docker书》P90

六、删除镜像(rmi)

  • 如果不再需要一个镜像了,可以用docker rmi命令将其删除
  • 例如,下面删除上面我们构建的那个dongyusheng/static_web镜像。命令如下:
sudo docker rmi dongyusheng/static_web

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第13张图片

  • 上面只是将本地的镜像删除了,但是上面我们已经将其上传到了Docker Hub仓库中,下次运行时仍然可以从仓库中进行下载
  • 如果想要将Docker Hub中的镜像也删除,可以进入Docker Hub进行删除。如下图所示,进入仓库,点击“Manage Repository”,然后在“Settings”中点击“Delete repository”删除整个容器

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第14张图片

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第15张图片

附加:删除多个/全部镜像

  • 删除多个镜像,如下所示:
sudo docker rmi dongyusheng/static_web dongyusheng/apache2
  • 删除全部镜像,与docker rm一样,Docker不支持全部删除的命令,因此可以按照下面的形式进行删除、
sudo docker rmi 'docker images -a -q'

七、Dockerfile构建失败时会怎样?

  • 在“三”中,我们使用“Dockerfile + build”构建了一个新镜像,如果在构建的过程中,Dockerfile某一步骤出错会怎样?
  • 下面通过一个演示案例来看看

演示案例

  • 第一步:更改当前路径下的Dockerfile文件,将第一个RUN指令中的“nginx”误写为“ngix”,如下所示
# Version: 0.0.2
FROM ubuntu:16.04
MAINTAINER dongyusheng "[email protected]"
RUN apt-get update && apt-get install -y ngin
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第16张图片

  • 第二步:接着构建镜像,查看结果
sudo docker build -t="dongyusheng/static_web" .
  • 第三步:可以看到在构建第三步的时候出错了,因为没有“ngin”这个软件

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第17张图片

  • 第四步:我们说过,Dockerfile每执行一条指令就会构造一个新的镜像,构建的时候虽然第3步出错了,但是前2步没有出错,前2不最后构造的镜像的ID为e6a19e1529ce

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第18张图片

  • 然后通过docker images和docker ps -a查看,虽然镜像没有构建成功,但是构建了一个容器,容器ID为“e63cb83ef973”,镜像名为“e6a19e1529ce”

  • 第五步:我们可以尝试运行新创建的那个容器
sudo docker run -t -i e6a19e1529ce /bin/bash

  • 第六步:输入nginx命令,可以看到没有安装成功,因为上面“docker build”的第3步出错了,只执行成功了前2步

  • Dockerfile中途构建失败,仍然可以创建新的容器,我们可以基于该容器进行改造

八、Dockerfile和构建缓存

  • 由于每一步的构建过程都会将结果提交为镜像,所以Docker的构建镜像过程就显得非常聪明。它会将之前的镜像层看做缓存
  • 例如,在上面“七”的演示案例中,我们的Dockerfile第1步和第2步构建成功了,其中第1步构建的镜像会作为第2步构建过程中使用的镜像缓存,第1步构建的镜像会作为下面第3步构建的缓存
  • 有时候需要确保构建过程中不会使用缓存,那么可以在“docker build”时指定--no-cache选项。命令如下:
    • 也就是说指定了该选项,那么当再次“docker build”执行相同的Dockerfile文件时,相同的指令将不会再执行了,因此apt-get update命令也就不会执行,那么该镜像中的软件包就不会进行更新
sudo docker build --no-cache -t="dongshao/static_web" .

九、基于构建缓存的Dockerfile模板

  • 构建缓存带来的一个好处就是,我们可以实现简单的Dockerfile模板(比如在Dockerfile文件顶部增加仓库或者更新包,从而尽可能确保缓存命中)
  • 一般都会在自己的Dockerfile文件顶部使用相同的指令集模板,比如对ubuntu,Dockerfile文件如下所示:
    • 在这个Dockerfile文件中,我们使用ENV指令设置了一个名为“PEFRESHED_AT”的环境变量,这个环境变量用来表名该镜像模板最后的更新时间
    • 最后,我们使用RUN指令运行命令,该指令运行时将会刷新APT包的缓存,将要安装的每个软件包更新到最新版本
# Version: 0.0.3
FROM ubuntu:16.04
MAINTAINER dongyusheng "[email protected]"
ENV PEFRESHED_AT 2020-07-16
RUN apt-get -qq update
  • 有了上面那个模板,如果想刷新一个构件,只需要修改ENV指令中的日期。这使Docker在命令ENV指令时开始重置这个缓存,并运行后续指令而无须依赖该缓存。也就是说,RUN apt-get update这条指令将会被再次执行,包缓存也将会被刷新为最新内容。例如:
    • 下面将PEFRESHED_AT修改为“2020-07-18”,那么执行到ENV时,发现日期改变了,那么就会重新继续执行ENV及后面的所有指令
# Version: 0.0.3
FROM ubuntu:16.04
MAINTAINER dongyusheng "[email protected]"
ENV PEFRESHED_AT 2020-07-18
RUN apt-get -qq update
  • 可以扩展此模板,比如适配到不同的平台或者添加额外的需求。比如,可以向下面的Dockerfile文件为例来支持一个fedora镜像
FROM fedora:20
MAINTAINER dongyusheng "[email protected]"
ENV PEFRESHED_AT 2020-07-16
RUN yum -q makecache

十、Dockerfile指令

  • 在上面我们已经介绍过Dockerfile的RUN、EXPOSE等指令。实际上,Dockerfile可以放入很多其他指令,完整的指令可以参阅:https://docs.docker.com/engine/reference/builder/
  • 在后面的文章中,我们还会学习更多的Dockerfile的知识

CMD指令

介绍

  • CMD指令用于指定一个容器启动时要运行的命令

Dockerfile中只能有一条CMD指令

  • Dockerfile中只能有一条CMD指令
  • 如果指定了多条CMD指令,那么只有最后一条CMD指令会被使用
  • 如果想要在启动容器时运行多个进程或者多条命令,可以考虑使用类似Supervisor这样的服务管理工具

与RUN指令的区别

  • RUN指令是在使用"docker build"命令构建镜像时,让镜像执行的命令
  • CMD指令是使用该镜像运行容器时,让容器运行的命令

CMD指令与"docker run"的关系

  • 我们知道,在执行"docker run"让容器运行时,可以在"docker run"命令的最后加上命令让容器去运行
  • CMD指令与上面的功能是一样的

演示案例

  • 下面是Dockerfile中的1条CMD指令,其让容器运行时执行/bin/bash命令启动一个shell
CMD ["/bin/bash"]
  • 上面的命令的功能等同于:
sudo docker run -i -t dongshao/images /bin/bash
  • 当然,也可以为要运行的命令指定相关的参数与选项
    • 备注:这种运行的方式是把要运行的命令存放在一个数组结构中,这告诉Docker按指定的原样来运行该命令。当然也可以不使用数组而是指定CMD指令,这时候Docker会在执行的命令前加上/bin/sh -c。这在执行该命令的时候可能会导致意料之外的行为,所以当命令有选项和参数时建议使用数组形式来存放要执行的命令
CMD ["bin/bash", "-l"]

"docker run"会覆盖CMD指令

  • 概念:如果我们在Dockerfile中设置了CMD命令,但是同时执行"docker run"运行容器时指定了容器要运行的命令,那么"docker run"的命令会覆盖Dockerfile中的CMD指令,从而只执行"docker run"指定的命令
  • 例如:下面的Dockerfile设置了一条CMD指令,让容器运行时运行/bin/bash
CMD ["/bin/bash"]
  • 但是,我们在使用"docker run"运行容器时指定了容器要运行的命令"/bin/ps",那么CMD的/bin/bash命令就不会执行,只执行/bin/ps
sudo docker run -i -t dongshao/images /bin/ps

与ENTRYPOINT指令之间的相互关系

  •  CMD指令与ENTRYPOINT指令之间有相互配合的关系,见下面ENTRYPOINT指令的介绍

ENTRYPOINT指令

介绍

  • ENTRYPOINT指令与CMD指令非常类似,也是用来在运行容器时执行命令,但是与CMD有许多不同的地方,看下面的一一介绍

演示案例

  • 例如,我们在Dockerfile中设置下面的ENTRYPOINT指令,那么容器在运行时就会执行/usr/sbin/nginx,从而运行nginx服务器
ENTRYPOINY ["/usr/sbin/nginx"]
  • 与CMD一样,当命令有参数和选项时,也可以以数组的形式设置命令。例如,下面让容器运行时执行"/usr/sbin/nginx  -g  daemon off"命令
ENTRYPOINY ["/usr/sbin/nginx", "-g", "daemon off;"]

"docker run"不会覆盖ENTRYPOINT指令,反而是传递给ENTRYPOINT使用

  • "docker run"不会覆盖ENTRYPOINT指令,反而会将"docker run"最后的命令传递给ENTRYPOINT使用
  • 例如:下面我们在Dockerfile中设置了如下的ENTRYPOINT指令,让其运行Nginx
ENTRYPOINY ["/usr/sbin/nginx"]
  • 我们在启动一个容器时,在最后执行了-g "daemon off;",那么:
    • "docker run"最后的一些参数与选项会传递给ENTRYPOINT指令
    • 因此整个容器执行的命令是/usr/sbin/nginx -g daemon off
sudo docker run -i -t dongshao/images -g "daemon off;

但是"docker run"的--entrypoint选项会覆盖ENTRYPOINT指令

  • 如果"docker run"命令带有--entrypoint选项,那么"docker run"也会覆盖Dockerfile中的ENTRYPOINT指令

ENTRYPOINT指令与RUN指令的使用

  • 我们知道RUN指令与"docker run"最后输入的命令作用是相同的,因此RUN指定的命令也会接在ENRRYPOINT的命令后面
  • 例如,下面的Dockerfile指定了ENTRYPOINT指令和CMD指令,那么容器运行时最终运行的命令是/usr/sbin/nginx  -h
ENTRYPOINY ["/usr/sbin/nginx"]

CMD ["-h"]
  • RUN指令有时是作为ENTRYPOINT的默认参数使用的。例如,下面我们的Dockerfile执行了下面的命令
ENTRYPOINY ["/usr/sbin/nginx"]

CMD ["-h"]
  • 但是"docker run"时的命令是下面这样的
sudo docker run -i -t dongshao/images -g "daemon off;
  • 那么运行容器时的意思就是,如果"docker run"最后没有指定命令,那么容器就执行/usr/sbin/nginx -h命令,如果"docker run"最后指定了命令,那么容器运行时执行的命令就是/usr/sbin/nginx -g daemon off

WORKDIR指令

介绍

  • WORKDIR指令用来执行容器运行时,执行容器运行时的工作目录,ENTRYPOINT指令和/和CMD指令运行的命令就是在这个指令所指定的工作目录下执行的

演示案例

  • 一个Dockerfile如下所示,其将容器的工作路径切换到/opt/webapp/db目录下,然后执行了RUN指令
WORKDIR /opt/webapp/db

RUN bundle install
  • 例如下面是使用多个WORKDIR:先将工作目录切换到/opt/webapp/db执行RUN指令,然后再将工作目录切换到/opt/webapp执行rackup命令
WORKDIR /opt/webapp/db
RUN bundle install

WORKDIR /opt/webapp/
ENTRYPOINT [ "rackup" ]

-w选项会覆盖WORKDIR指令

  • "docker run"命令中的-w选项会覆盖Dockerfile中的WORKDIR指令
  • 例如,下面设置了当前的工作路径为/var/log
sudo docker run -it -w /var/log dongshao/images pwd

ENV指令

介绍

  • ENV指令用来在镜像构建中设置环境变量

演示案例

  • 例如下面设置了一个环境变量RVM_PATH,其值为/home/rmv
ENV RVM_PATH /home/rmv
  • 从Docker 1.4开始,可以指定多个环境变量。例如,下面设置了两个环境变量
ENV RVM_PATH=/home/rmv RVM_ARCHFLAGS="-arch i386"

一些注意点

  • 当设置了环境变量之后,后面的任何RUN指令等操作都是基于这个环境变量进行操作
  • 如果需要,可以通过在环境变量前加上一个反斜线来进行转义
  • 该指令设置的环境变量,会在镜像中永久保存,因此基于该镜像创建的任何容器都会使用这个环境变量

-e选项

  • ENV设置的环境变量是在镜像中永久保存的,因此每次创建容器,进入系统之后变量都会在系统中
  • "docker run"的-e选项可以指定:只在本次创建的容器运行时有效,容器退出或删除后环境变量失效
  • 例如,下面启动时指定容器的WEN_PORT环境变量为8080,当该容器退出后变量失效
sudo docker run -it -e "WEB_PORT=8080" dongshao/images

USER指令

介绍

  • USER指令可以用来指定该镜像会以什么样的用户去运行
  • 如果没有指定USER指令,默认的用户为root,因此容器运行时很多命令都不需要加上sudo

演示案例

  • 例如的Dockerfile指定容器运行时,以nginx的身份去运行容器
USER nginx

还可以指定UID以及组或GID

  • USER指令还可以指定用户名或UID以及组或GID,甚至是两者的组合
  • 例如:
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

VOLUME指令

介绍

  • USER指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能:
    • 卷可以在容器间共享和重用
    • 一个容器可以不是必须和其他容器共享卷
    • 对卷的修改是立即生效的
    • 对卷的修改不会对更新镜像产生影响
    • 卷会一直存在直到没有任何容器再使用它
  • 卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库

演示案例

  • 下面这条指令会为基于此镜像创建的任何容器创建一个名为/opt/project的挂载点
VOLUME ["/opt/project"]
  • 也可以以数组的形式指定多个卷。例如:
VOLUME ["/opt/project", "/data"]

docker cp命令

  • docker cp和VOLUME指令相关并且也是很实用的命令。该命令允许从容器复制文件和复制文件到容器上。可以从Docker命令行文档中获取更多信息:http://docs.docker.com/engine/reference/commandline/cp

相关说明与演示案例

  • 卷的介绍:https://blog.csdn.net/qq_41453285/article/details/107397371
  • 卷的备份:https://blog.csdn.net/qq_41453285/article/details/107609836

ADD指令

介绍

  • ADD指令用来将Dockerfile构建环境下的文件和目录复制到镜像中

演示案例

  • 下面的Dockerfile中有如下的ADD指令,当基于该镜像的容器运行时,会将与当前Dockerfile同一路径中的softwarte.lic文件(宿主机中的)复制到容器的/opt/application/目录下,名为software.lic(名字其实可以自己指定的)
ADD software.lic /opt/application/software.lic

格式说明

  • 在ADD文件时,Docker通过目的地址(后面的路径)参数末尾的字符来判断文件源(前面的路径)是目录还是文件
  • 如果目标地址以/结尾,那么Docker就认为源位置指向的是一个目录
  • 如果目标地址不是以/结尾,那么Docker就认为源位置指向的是文件

文件源也可以是URL格式的

  • 例如,下面将一个网页中的文件下载到/root/目录下,名为wordpress.zip
ADD http://wordpress.org/latest.zip /root/wordpress.zip

对归档文件有特殊处理

  • 如果文件源是本地归档文件(tar archive)时有特殊的处理,如果将一个归档文件(合法的归档文件包括gzip、bzip2、xz)复制到容器中,那么Docker会自动将归档文件解压
  • 如果目的位置已经存在了归档文件同名的文件或者目录,那么目录位置中的文件或者目录不会被覆盖
  • 目前Docker还不支持以URL方式制定的源位置中使用归档文件。这种行为稍显有点儿不统一,在以后的版本中应该会有所变化
  • 例如,一个Dockerfile中的ADD指令如下
    • 下面将当前构建目录下的latest.tar.gz文件解压之后复制到容器的/var/ww/wordpress/目录下
    • 行为等同于带-x选项的tar命令一样
ADD latest.tar.gz /var/ww/wordpress/

如果目的位置不存在会怎么样?

  • 如果目的位置不存在的话,Docker将会为我们在容器中创建这个全路径,包括路径中的任何目录
  • 新创建的文件和目录的模式为0775,并且UID和GID都是0

ADD指令会使Dockerfile缓存失效

  • 如果通过ADD指令向镜像添加一个文件或者目录,那么这将使Dockerfile中的后续指令都不能继续使用之前的构建缓存

COPY指令

介绍

  • COPY指令非常类似于ADD指令,它们本根的不同是COPY只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decomposition)的工作

演示案例

  • 例如,下面是将宿主机当前构建目录下的conf.d/目录拷贝到镜像的/etc/apache2/目录下
COPY conf.d/ /etc/apache2/

相关说明

  • 文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。不能复制该目录之外的任何文件,因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中运行的。任何位于构建环境之外的东西都是不可用的
  • COPY指令的目的位置必须是容器内部的一个绝对路径
  • 与ADD指令一样,如果目的位置不存在,那么也会进行创建,任何由该指令创建的文件或者目录的UID和GID都会设置为0
  • 如果源路径是一个目录,那么这个目录将整个复制到容器中,包括文件系统元数据
  • 如果源路径是一个文件,则该文件会随同元数据一起被复制

LABEL指令

介绍

  • LABEL指令用于为Docker镜像添加元数据。元数据以键值对的形式展现
  • LABEL指令是在Docker 1.6版本中加入的

演示案例

  • 例如下面构建镜像时,会添加一个元数据version
LABEL version="1.0"
  • 当然,你也可以在一条指令中指定多个元数据,不同的元数据用空格分隔
LABEL location="New York" type="Data Center" role="Web Server"
  • 我们建议所有的元数据都放到一条LABEL指令中,以防止不同的元数据指令创建过多镜像层

查看镜像标签

  •  通过docker inspect令来查看某个Docker镜像的标签信息。例如:
sudo docker inspect --format='{{ .ContainerConfig.Labels }}' dongshao/consul

STOPSIGNAL指令

  • STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器。这个信号必须是内核系统调用表中合法的数,比如9,或者SIGNAME格式中的信号名称,如SIGKILL
  • STOPSIGNAL指令是在Docker 1.9版本中引入的

ARG指令

介绍

  • ARG指令用来定义可以在docker build构建镜像时传递构建运行时的变量,定义之后在执行"docker build"时使用--build-arg就可以使用该变量了
  • ARG指令是在Docker 1.9版本中引入的,可以在Docker文档阅读详细的说明:https://docs.docker.com/engine/reference/builder/#arg

演示案例

  • 例如,下面定义了两个变量,其中第二个变量拥有默认值
ARG build
ARG webapp_user=user
  • 当我们基于该Dockerfile构建镜像时,就可以输入下面的命令:
    • 其中将build变量设置为1234
    • 因为webapp_user有默认值了,因此下面就没有再指定
sudo docker build --build-arg build=1234 -t dongshao/webapp .

不要传递证书或者秘钥

  • 有些人可能会使用ARG来传递证书或者秘钥之类的信息,但是千万别这么做,因为机密信息会在构建过程中以及镜像的构建历史中被暴露出来

预定义的ARG变量

  • Dockerfile预定义了一组ARG变量,可以在构建时直接使用,而不必再到Dockerfile中自行定义了
  • 预定义的ARG变量如下
HTTP_PROXY
http_proxy

HTTPS_PROXY
https_proxy

FTP_PROXY
ftp_proxy

NO_PROXY
no_proxy
  • 想要使用这些预定义的变量,在"docker build"命令中用--build-arg =来指定就可以了

ONBUILD指令

介绍

  • ONBUILD指令能为镜像添加触发器
  • 当一个镜像被用作其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行
  • 触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的
  • 触发器可以是任何构建指令
  • 例如,下面的Dockerfile定义了2条ONBUILD指令
ONBUILD ADD . /app/src
ONBUILD RUN cd /app/src && make
  • 当然,可以在使用"docker inspect"来查看镜像或者容器的ONBUILD指令
  • 有几条指令不能用在ONBUILD中,包括:FROM、MAINTRINER和ONBUILD本身。这么做的原因是为了防止Dockerfile构建过程中产生递归调用的问题

演示案例

  • 下面创建一个apache2目录,然后进入创建一个Dockerfile文件

  • Dockerfile内容如下,其中安装了apache2服务器
FROM ubuntu:16.04
MAINTAINER dongshao "https://blog.csdn.net/qq_41453285"

RUN apt-get update && apt-get install -y apache2

ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2

ONBUILD ADD . /var/www/

EXPOSE 80

ENTRYPOINT ["/usr/sbin/apache2"]

CMD ["-D", "FOREGROUND"]
  • 我们使用上面的Dockerfile来构建一个镜像
sudo docker build -t="dongshao/apache2" .

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第19张图片

  • 查看创建好的镜像
sudo docker images dongshao/apache2

  • 现在我们创建好了一个镜像,这个镜像中创建了apache2服务器,因此我们可以把这个镜像当做一个Web应用程序的模板,其他镜像可以以这个镜像为基础镜像来构建自己的Web应用程序
  • 现在我们准备以上面的镜像为基础镜像来创建一个属于自己的镜像(添加自己想要的功能)。首先回到前一目录,创建一个自己Webapp的目录,然后创建一个Dockerfile
cd ..

mkdir webapp

cd webapp

touch Dockerfile

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第20张图片

  • Dockerfile的内容如下
FROM dongshao/apache2

MAINTAINER dongshao "https://blog.csdn.net/qq_41453285"

ENV APPLICATION_NAME webapp
ENV ENVIRONMENT development
  • 上面的Dockerfile使用了我们之前的dongshao/apche2镜像,又因为dongshao/apche2镜像的Dockerfile中设置了一条“ONBUILD ADD . /var/www/”指令,因此当我们构建镜像时就会执行这条命
  • 因此,下面来构建看看,如下所示:
    • 因为该镜像依赖于dongshao/apache2镜像,因此Step第一步的时候执行了dongshao/apache2镜像Dockerfile中的ONBUILD指令
sudo docker build -t="dongshao/webapp" .

Docker:06---镜像与仓库之(镜像的构建、构建历史、推送、删除(commit、build、history、push、rmi)、Dockerfile使用语法/构建缓存)_第21张图片

总结

  • 这种机制使得我们每次构建镜像时都会执行ONBUILD所指定的命令,因此可以将这种之前的镜像作为一种镜像模板
  • ONBILD触发器会按照在父镜像中指定的顺序执行,并且只能被继承一次(也就是说只能在子镜像中执行,而不会在孙子镜像中执行)。例如,如果我们基于上面的"dongshao/webapp"再构建一个镜像,那么"dongshao/apache2"中的ONBUILD指令是不会在"dongshao/webapp"的子镜像中执行的

你可能感兴趣的:(Docker)