docker-study-3

docker-study-3

Scroll Down

基于**Dockerfile**构建新镜像

执行 docker build 命令时,Dockerfile 中的所有指令都会被执行并且提交,并且在该命令成功结束后返回一个新镜像

                         

使用了 docker build 命令来构建新镜像。我们通过指定 - t 选项为新镜像设置了仓库和名称,本例中仓库为 myhub,镜像名为 static_web。强烈建议各位为自己的镜像设置合适的名字以方便追踪和管理

                       

                       

**Dockerfile**和构建缓存

由于每一步的构建过程都会将结果提交为镜像,所以 Docker 的构建镜像过程就显得非常聪明。它会将之前的镜像层看作缓存。( Docker 会将之前构建时创建的镜像当做缓存并作为新的开始点,即当第二次构建同一镜像时,Docker只会从首次构建到现在发生变化的部分开始,没有变化的部分不再构建 )

然而,有些时候需要确保构建过程不会使用缓存。如果已经缓存了前面构建构成的某一步,比如apt-get update,那么 Docker 将不会再次刷新 APT 包的缓存。这时用户可能需要取得每个包的最新版本。要想略过缓存功能,可以使用 docker build 的 --no-cache 标志

**从构建的镜像启动一个容器**

   使用 docker run 命令,基于刚才构建的镜像的名字,启动了一个名为 static_web 的新容器。我们同时指定了 -d 选项,告诉 Docker 以分离(detached)的方式在后台运行。这种方式非常适合运行类似 Nginx 守护进程这样的需要长时间运行的进程。我们也指定了需要在容器中运行的命令:nginx -g "daemon off;"。这将以前台运行的方式启动 Nginx,来作为我们的 Web 服务器

   这里也使用了一个新的 -p 标志,该标志用来控制 Docker 在运行时应该公开哪些网络端口给外部(宿主机)。运行一个容器时,Docker 可以通过两种方法来在宿主机上分配端口。

  • 可以在 Docker 宿主机中指定一个具体的端口号来映射到容器中的 80 端口上。

  Docker 可以在宿主机上随机选择一个比较大的端口号来映射到容器中的 80 端口上

可以通过 docker port 来查看容器的端口映射情况

物理主机查看:

-p选项还为我们在将容器端口向宿主机公开时提供了一定的灵活性。比如,可以指定将容器中的端口映射到 Docker 宿主机的某一特定端口上

    上面的命令会将容器内的 80 端口绑定到本地宿主机的 80 端口上。很明显,我们必须非常小心地使用这种直接绑定的做法:如果要运行多个容器,只有一个容器能成功地将端口绑定到本地宿主机上。这将会限制 Docker 的灵活性。

为了避免这个问题,可以将容器内的端口绑定到不同的宿主机端口上去,如下

这里,我们将容器内的 80 端口绑定到了本地宿主机的 127.0.0.1 这个 IP 的 80 端口上。我们也可以使用类似的方式将容器内的 80 端口绑定到一个宿主机的随机端口上,如下

我们并没有指定具体要绑定的宿主机上的端口号,只指定了一个 IP 地址 127.0.0.1,这时我们可以使用 docker inspect 或者 docker port 命令来查看容器内的 80 端口具体被绑定到了宿主机的哪个端口上

Docker 还提供了一个更简单的方式,即-P参数,该参数可以用来对外公开在Dockerfile中通过EXPOSE指令公开的所有端口,如下

该命令会将容器内的 80 端口对本地宿主机公开,并且绑定到宿主机的一个随机端口上。该命令会将用来构建该镜像的 Dockerfile 文件中 EXPOSE 指令指定的其他端口也一并公开

Dockerfile指令(必须大写)

我们已经看过了一些 Dockerfile 中可用的指令,如 RUN 和 EXPOSE。但是,实际上还可以在 Dockerfile 中放入很多其他指令

CMD

CMD指令用于指定一个容器启动时要运行的命令。这有点儿类似于RUN指令,只是RUN指令是指定镜像被构建时 (即 docker build 时) 要运行的命令,而CMD是指定容器被启动时 (即 docker run 时) 要运行的命令。和使用docker run命令启动容器时指定要运行的命令非常类似

$ sudo docker run -i -t  myhub/static_web  /bin/true
#
# Dockerfile中
CMD ["/bin/true"]

可以认为上述代码所示的命令和在 Dockerfile 中的 CMD 指令是等效的。

CMD 命令的参数格式,一般写成字符串数组的方式,如上面的例子。如:

CMD  ["echo","hello world"]

虽然也可写成 CMD echo “hello world” 方式,但这样 docker 会在指定的命令加  /bin/sh -c 执行,有时有可能会出问题。 所以推荐采用数据结构的方式来存放命令

如果在 docker run 命令末尾指定了要运行的命令,将会覆盖掉 Dockerfile CMD 所指定的命令(使用 docker run 命令可以覆盖 CMD 指令。如果我们在 Dockerfile 里指定了 CMD 指令,而同时在 docker run 命令行中也指定了要运行的命令,命令行中指定的命令会覆盖 Dockerfile 中的 CMD 指令)

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

ENTRYPOINT

ENTRYPOINT指令与CMD指令区别在于,ENTRYPOINT 指令提供的命令则不会在启动容器时被覆盖

实际上,docker run 命令行中指定的任何参数都会被当做参数再次传递给 ENTRYPOINT 指令中指定的命令

ENTRYPOINT ["/usr/sbin/nginx"]

    类似于CMD指令,我们也可以在该指令中通过数组的方式为命令指定相应的参数

ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

   我们指定了-g "daemon off ; "参数,这个参数会传递给用ENTRYPOINT指定的命令,在这里该命令为/usr/sbin/nginx -g "daemon off;"。该命令会以前台运行的方式启动 Nginx 守护进程,此时这个容器就会作为一台 Web 服务器来运行

我们也可以组合使用 ENTRYPOINT CMD 指令来完成一些巧妙的工作

ENTRYPOINT ["/usr/sbin/nginx"] 

CMD ["-h"]

此时当我们启动一个容器时,任何在命令行中指定的参数都会被传递给 Nginx 守护进程。比如,我们可以指定-g "daemon off;"参数让 Nginx 守护进程以前台方式运行。如果在启动容器时不指定任何参数,则在 CMD 指令中指定的 - h 参数会被传递给 Nginx 守护进程,即 Nginx 服务器会以 / usr/sbin/nginx -h 的方式启动,该命令用来显示 Nginx 的帮助信息。

  这使我们可以构建一个镜像,该镜像既可以运行一个默认的命令,同时它也支持通过 docker run 命令行为该命令指定可覆盖的选项或者标志

Docker 使用 nginx 为什么要加上 -g daemon off?

  Docker 容器启动时,默认会把容器内部第一个进程,也就是 pid=1 的程序,作为 docker 容器是否正在运行的依据,如果 docker 容器 pid=1 的进程挂了,那么 docker 容器便会直接退出。

   Docker 未执行自定义的 CMD 之前,nginx 的 pid 是 1,执行到 CMD 之后,nginx 就在后台运行,bash 或 sh 脚本的 pid 变成了 1。所以一旦执行完自定义 CMD,nginx 容器也就退出了

   "daemon off ;" 选项阻止 Nginx 进入后台,强制其在前台运行。这是因为要想保持 Docker 容器的活跃状态,需要其中运行的进程不能中断。默认情况下,Nginx 会以守护进程的方式启动,这会导致容器只是短暂运行,在守护进程被 fork 启动后,发起守护进程的原始进程就会退出,这时容器就停止运行了

WORKDIR

WORKDIR 指令的用处是:使用镜像创建一个新容器时,在容器内部设置一个工作目录,ENTRYPOINT 或 CMD 指定的程序会在这个目录下执行。

  我们可以使用该指令为 Dockerfile 中后续的一系列指令设置工作目录,也可以为最终的容器设置工作目录

# 例:

WORKDIR /opt/webapp/db 

RUN bundle install 

WORKDIR /opt/webapp 

ENTRYPOINT ["rackup"]

这里,我们将工作目录切换为 /opt/webapp/db 后运行了 bundle install 命令,之后又将工作目录设置为 / opt/webapp,最后设置了 ENTRYPOINT 指令来启动 rackup 命令。

  可以通过 - w 标志在运行时覆盖工作目录

ENV

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

这个新的环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样,指定的这些环境变量也会被持久保存到从我们的镜像创建的任何容器中

USER

USER 指令用来指定该镜像会以什么用户去运行,如果不通过USER指令指定用户,默认用户为root

也可以在docker run命令中通过-u标志来覆盖该指令指定的值

VOLUME(卷功能在后边实例有详细描述)

VOLUME 指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。

    ▲卷可以在容器间共享和重用。

    ▲一个容器可以不是必须和其他容器共享卷。

    ▲对卷的修改是立时生效的。

    ▲对卷的修改不会对更新镜像产生影响。

    ▲卷会一直存在直到没有任何容器再使用它。

 卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库

VOLUME ["/opt/project"]

这条指令将会为基于此镜像创建的任何容器创建一个名为/opt/project的挂载点

也可以通过指定数组的方式指定多个卷

VOLUME ["/opt/project", "/data"]

ADD

ADD 指令用来将构建环境下的文件和目录复制到镜像中。比如,在安装一个应用程序时。ADD 指令需要源文件位置和目的文件位置两个参数

ADD  software.lic  /opt/application/software.lic

这里的 ADD 指令将会将构建目录(Dockerfile所在处)下的 software.lic 文件复制到镜像中的 / opt/application/software.lic。指向源文件的位置参数可以是一个 URL,或者构建上下文或环境中文件名或者目录。不能对构建目录或者上下文之外的文件进行 ADD 操作。

( 这里的构建环境、构建目录、构建上下文是一个意思,都是 docker 对 Dockerfile 文件所在位置的一个叫法。  所以 ADD 指令的源文件位置只能是 Dockerfile 文件所在目录下的文件、文件夹或一个 URL

Docker 通过目的地址参数末尾的字符来判断文件源是目录还是文件:如果目标地址以 / 结尾,那么 Docker 就认为源位置指向的是一个目录。如果目的地址不以 / 结尾,那么 Docker 就认为源位置指向的是文件

文件源也可以使用 URL 的格式:

ADD http://wordpress.org/latest.zip /root/wordpress.zip

最后值得一提的是,ADD在处理本地归档文件(tar archive)时还有一些小魔法。如果将一个归档文件(合法的归档文件包括 gzip、bzip2、xz等)指定为源文件,Docker 会自动将归档文件解开(unpack)

ADD latest.tar.gz /var/www/wordpress/

这条命令会将归档文件 latest.tar.gz 解开到 / var/www/wordpress / 目录下。Docker 解开归档文件的行为和使用带 - x 选项的 tar 命令一样:该指令执行后的输出是原目的目录已经存在的内容加上归档文件中的内容。如果目的位置的目录下已经存在了和归档文件同名的文件或者目录,那么目的位置中的文件或者目录不会被覆盖

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

COPY

COPY 指令类似于 ADD,区别在于 COPY 只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decompression)的工作

COPY conf.d/ /etc/apache2/

这条指令将会把本地 conf.d 目录中的文件复制到 / etc/apache2 / 目录中

COPY 与 ADD 指令的源文件的来源要求相同,文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。不能复制该目录之外的任何文件,因为构建环境将会上传到 Docker 守护进程,而复制是在 Docker 守护进程中进行的。任何位于构建环境之外的东西都是不可用的。COPY指令的目的位置则必须是容器内部的一个绝对路径。

任何由该指令创建的文件或者目录的 UID 和 GID 都会设置为 0。

如果源路径是一个目录,那么这个目录将整个被复制到容器中,包括文件系统元数据;如果源文件为任何类型的文件,则该文件会随同元数据一起被复制。在这个例子里,源路径以/结尾,所以 Docker 会认为它是目录,并将它复制到目的目录中

如果目的位置不存在,Docker 将会自动创建所有需要的目录结构,就像 mkdir -p 命令那样

LABEL

LABEL 指令用于为 Docker 镜像添加元数据。元数据以键值对的形式展现

LABEL version="1.0"

LABEL location="New York" type="Data Center" role="Web Server"

LABEL 指令以 label="value" 的形式出现。可以在每一条指令中指定一个元数据,或者指定多个元数据,不同的元数据之间用空格分隔。推荐将所有的元数据都放到一条 LABEL 指令中,以防止不同的元数据指令创建过多镜像层。可以通过 docker inspect 命令来查看 Docker 镜像中的标签信息

**STOPSIGNAL**

STOPSIGNAL 指令用来设置停止容器时发送什么系统调用信号给容器。这个信号必须是内核系统调用表中合法的数,如 9,或者 SIGNAME 格式中的信号名称,如 SIGKILL (在 shell 脚本中有关于 LINUX 信号的描述)

这里贴出 linux 的退出状态码:

 

returned a non-zero code: 127

returned a non-zero code: 2

**ARG**

ARG 指令用来定义可以在 docker build 命令运行时传递给构建运行时的变量,只需要在构建时使用 --build-arg 标志即可。用户只能在构建时指定在 Dockerfile 文件中定义过的参数。

ARG build

ARG webapp_user=user

上面例子中第二条 ARG 指令设置了一个默认值,如果构建时没有为该参数指定值,就会使用这个默认值。

下面这个例子将会显示如何在 docker build 中使用这些参数

$ docker  build  --build-arg  build=1234  -t  myhub/webapp  .

这里构建 myhub/webapp 镜像时,build 变量将会设置为 1234,而 webapp_user 变量则会继承设置的默认值 user

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

HTTP_PROXY

http_proxy

HTTPS_PROXY

https_proxy

FTP_PROXY

ftp_proxy

NO_PROXY

no_proxy

要想使用这些预定义的变量,只需要给 docker build 命令传递 --build-arg = 标志就可以了

**ONBUILD**

ONBUILD 指令能为镜像添加触发器(trigger)。当一个已经创建好的镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行

触发器会在构建过程中插入新指令,可以认为这些指令是紧跟在 FROM 之后指定的,触发器可以是任何构建指令(前面的 12 种指令)

ONBUILD ADD . /app/src 

ONBUILD RUN cd /app/src && make

上面的代码将会在创建的镜像中加入 ONBUILD 触发器,ONBUILD 指令可以在镜像上运行 docker inspect 命令来查看

( debain 与 centos 安装 apache 的包名不一样,debain 下叫 apache2,centos 下叫 httpd )  RUN 是在 Build 时运行的,先于 CMD 和 ENTRYPOINT,Build 完成了,RUN 也运行完成后,再运行 CMD 或者 ENTRYPOINT
image.png

image.png

问题解决:

第一次尝试运行报错,returned a non-zero code: 1

image.png

image.png

查询资料得出解决方案:

需要执行:

yum update -y && yum -y install httpd

说明yum update过程中需要确认

添加上 yum update -y之后再次运行:
image.png

通过docker inspect 命令查看详情

image.png

image.png

在新构建的镜像中包含一条ONBUILD指令,该指令会使用ADD指令将构建环境所在的目录下的内容全部添加到镜像中的/var/www/目录下。我们可以轻而易举地将这个Dockerfile作为一个通用的Web应用程序的模板,可以基于这个模板来构建Web应用程序

下面我们通过构建一个名为webapp的镜像来看看如何使用镜像模板功能:

webapp的Dockerfile:

( 注意第一行的FROM镜像是来自刚才build的apache_server )
image.png

image.png

ONBUILD触发器会按照在父镜像中指定的顺序执行,并且只能被继承一次(也就是说只能在子镜像中执行,而不会在孙子镜像中执行)。如果我们再基于myhub/webapp构建一个镜像,则新镜像是myhub/apache2的孙子镜像,因此在该镜像的构建过程中,ONBUILD触发器是不会被执行的。

注意:
这里有好几条指令是不能用在ONBUILD指令中的,包括FROM、MAINTAINER和ONBUILD本身。之所以这么规定是为了防止在 Dockerfile构建过程中产生递归调用的问题。