0%

使用 Dockerfile 定制镜像 搭建 php 站点

关于 Docker 系列其他文章的传送门

  1. 《 简单了解 Linux 容器是什么 以及 Docker 常用命令操作说明 》

  2. 《 Docker 中 4 种网络模型的概念和实现 》

  3. 《 关于 Docker 中镜像与存储卷的概念和详细使用方法 》

  4. 《 使用 Dockerfile 定制镜像 搭建 php 站点 》     您当前所在位置

  5. 《 使用 Harbor 创建本地 Docker 仓库 》



一、Dockerfile 中需要了解的一些东西

index_php_2

制作 Docker 镜像可以使用 docker commit 命令,需要先启动镜像,然后打包时,会把最上层可写层的数据,以及依赖的关系,完整保存在联合文件系统中。但其实这整个过程,可以通过另一种方式完成,就是利用制作镜像的相关源码中的指令来自定义镜像,而不用自己手工完成。

安装软件包、创建文件夹、从本地指定或者互联网复制文件等,这就是 Dockerfile,它会自动创建一个镜像,把之前需要手动操作的步骤,隐式完成,并打包成镜像。

1. Dockerfile 的一些特性

  • Dockerfile 其实就是构建 Docker 镜像的源码。
  • Dockerfile 能够通过读取 file 文件里的指令 (instruction) 来构建一个镜像。
  • Dockerfile 其实仅仅是个纯文本文件,只不过把文件里的内容作为 Dockerfile 的指令 (instruction) 来运行。
  • 这个指令 (instruction) 是有特定格式的,只能使用语法格式当中定义的,能被 docker 识别的指令。

2. Dockerfile 的工作环境

Dockerfile 在工作时,需要一个工作目录,我们称之为 工作车间

工作车间相当于是宿主机文件系统上的一个目录,这个目录内只放置构建镜像需要使用的 Dockerfile 文件,和打算复制的文件。

这个目录其实可以理解成 Docker 的存储卷,构建镜像时,从这个卷里找到文件,并复制到容器,所以需要注意的是,如果文件放在其他位置的话会无法复制,所以要复制的文件必须放在当前文件夹内。

同时,Dockerfile 文件本身也只能叫 Dockerfile。

3. 构建镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker image build Dockerfile 父目录/ -t IMAGES:TAG
-t 标签
--network 当需要联网时指定源
-q 不显示制作过程

# 示例:
file 有如下:
- /text/
- /text/Dockerfile

pwd 当前位置在这里:
/text/

docker image build Dockerfile /text/ -t test:1

需要注意的一些问题

  • 指令本身并不区分大小写,但是按照约定俗成的惯例,指令一般写成纯大写。
  • 没有多余的语法,所有指令按顺序执行。
  • 一般来讲,第一行是 FROM,但是对于新版的 file,这个不是强制要求的了。
  • 和手动构建镜像的区别是:每一条指令都会生成一个镜像层。
  • 最好把关系联系紧密的操作放在同一层。

关于镜像层的数量控制

分类 优点 缺点
镜像层多 可以控制的很精细,分发给其他容器时容易共享 第一次写入是 cow 机制,对性能略有影响
镜像层少 镜像层比较整体化,性能略优 不便于自由分发

4. 启动脚本 entrypoint.sh

大部分程序的定制是通过配置文件来实现的,而镜像中程序的配置文件已经写死在镜像中了,这就导致了如果有多个部署环境,需要为每个环境下的每个程序制作各不相同的 Docker 镜像,这就很头大了。

所以现在面向云原生开发的应用程序都支持 --> 容器启动时直接读取 Docker 宿主机的环境变量,然后根据环境变量把参数传入到配置文件内。

但是一些老牌的程序,并不支持从环境变量中传入配置,为了解决这个问题,便引入了指定环境变量的方法,如果程序不支持直接获取宿主机环境变量,比如 mysql 的镜像启动时,就需要使用 -e MYSQL_ROOT_PASSWORD 的选项来指定 root 密码,这就解决了老的程序不支持读取宿主机环境变量的问题,就像有句名言是这么说的 计算机科学领域中任何两个不衔接的层,都可以通过增加一个间接的中间层来解决,这个脚本就是衔接新老程序的中间层。但是还有一个问题,如果参数太多了,就需要在 shell 中输入很长的参数,这也很头大。

所以 Docker 支持在启动时使用脚本,作为容器启动的第一个进程,脚本运行完后,就把原本要运行的程序启动,然后自动退出,这个脚本就叫 entrypoint.sh,文件本身也叫这个。

5. 设置忽略的文件

如有在 Dockerfile 的工作车间目录下,有不希望在构建镜像时复制的文件,可以通过 .dockerignore 文件配置,Docker 会自动忽略这个文件以及文件内所有指定的文件。



二、Dockerfile 文件的指令

1. FROM

FROM 指令是最重要的一个且必须为 Dockerfile 文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境。

在实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build 会在 docker 主机上查找指定的镜像文件,在其不存在时,则会从 Docker Hub Registry 上拉取所需的镜像文件。如果找不到指定的镜像文件,docker build 会返回一个错误信息。

语法

1
2
3
4
FROM <repository>[:<tag>] 或
FROM <resository>@<digest>
<reposotiry>: 指定作为 "base image" 的名称;
<tag>: "base image" 的标签,为可选项,省略时默认为 "latest"

2. LABEL

用于让 Dockerfile 制作者提供本人的详细信息,Dockerfile 并不限制 label 指令可在出现的位置,但推荐将其放置于 FROM 指令之后。

语法

1
LABEL <key>=<value> <key>=<value> <key>=<value> ...

需要注意的是

  • 要在 LABEL 值中包含空格,请使用引号和反斜杠。
  • 图像可以有多个标签。
  • 可以在一行中指定多个标签。

3. COPY

用于从 Docker 主机复制文件至创建的新映像文件。

语法

1
2
3
4
copy <src> <dest> 或
copy ["<src>" "<dest>"] 如果有空白字符,通常使用第二种格式
<src>:要复制的源文件或目录,支持使用通配符
<dest>:目标路径,即正在创建的 image 的文件系统路径,建议为 <dest> 使用绝对路径,否则,COPY 指定则以 WORKDIR 为其起始路径

需要注意的是

  • src 必须是 build 上下文中的路径,不能是其父目录中的文件。
  • 如果 src 是目录,则其内部文件或子目录会被递归复制,src 目录自身不会被复制
  • 如果指定了多个 src,或在 src 中使用了通配符,则 dest 必须是一个目录,且必须以 / 结尾。
  • 如果 dest 事先不存在,它将会被自动创建,也包括其父目录路径。

4. ADD

ADD 指令类似于 COPY 指令,ADD 支持使用 TAR 文件和 URL 路径。

语法

1
2
ADD <src> ... <dest> 或
ADD ["<src>",... "<dest>"]

操作准则基本同 COPY 指令,但需要注意的是

  1. 如果 src 为 URL 且 dest 不以 / 结尾,则 src 指定的文件将被下载并直接被创建为 dest
  2. 如果 dest/ 结尾,则文件名 URL 指定的文件将被直接下载并保存为 dest/filename
  3. 如果 src 是一个本地系统上的压缩格式的 tar 文件,它将被展开为一个目录,其行为类似于 tar -x 命令。然而,通过 URL 获取到的 tar 文件将不会自动展开。
  4. 如果 src 有多个,或其间接或直接使用了通配符,则 dest 必须是一个以 / 结尾的目录路径。
  5. 如果 dest 不以 / 结尾,则其被视作一个普通文件,src 的内容将被直接写入到 dest

5. WORKDIR

用于为 Dockerfile 中所有的 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指定工作目录。

语法

1
WORKDIR <dirpath>

需要注意的是
在 Dockerfile 文件中,WORKDIR 指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个 WORKDIR 指令指定的路径。另外,WORKDIR 也可调用由 ENV 指定定义的变量。

例如:

1
2
WORKDIR /var/log
WORKDIR $STATEPATH

6. VOLUME

VOLUME 是用于在 image 中创建一个挂载点目录,以挂载 Docker host 上的卷或其它容器上的卷。

语法

1
2
VOLUME <mountpoint> 或
VOLUME ["<mountpoint>"]

需要注意的是

  • 如果挂载点目录路径下此前在文件存在,docker run 命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。
  • 这里定义的 VOLUME,只能用 容器管理的卷 的方式挂载,如果想指定目录,必须通过 docker run 时用 -v 指定才可以。

7. EXPOSE

用于为容器打开指定要监听的端口以实现与外部通信。

语法

1
2
EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
<protocol> 用于指定传输层协议,可为 tcp 或 udp 二者之一,默认为 TCP 协议

需要注意的是

  • EXPOSE 指令可一次指定多个端口,例如: EXPOSE 11211/udp 11211/tcp
  • 因为网络暴露存在风险,所以就算在 Dockerfile 中指定了端口,而且 inspect 查看镜像信息也确实有 EXPOSE 信息,但是容器并不会真的暴露端口,而需要在 docker run 时 使用大写 P -P 来暴露端口,这时不用加任何参数,且只能使用 动态端口 的模式。

8. ENV

用于为镜像定义所需的环境变量,并可被 Dockerfile 文件中位于其后的其它指令(如 ENV、ADD、COPY 等)所调用,调用格式为 $variable_name${variable_name}

当运行容器时,环境变量将保持不变。可以使用 docker inspect 查看值,如果对变量进行更改请使用 docker run --env|-e <key>=<value>

语法

1
2
ENV <key> <value> 或
ENV <key>=<value> ...

需要注意的是

  • 第一种格式中,<key> 之后的所有内容均会被视作其 <value> 的组成部分,因此,一次只能设置一个变量。
  • 第二种格式可用一次设置多个变量,每个变量为一个 <key>=<value> 的键值对,如果 <value> 中包含空格,可以以反斜线 \ 进行转义,也可通过对 <value> 加引号进行标识。另外,反斜线也可用于续行。
  • 定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能。

注意:ENV 定义的变量,会在容器启动后传入到容器内。

注意:使用 ENV 指令定义的环境变量始终覆盖同名的 ARG 指令。

9. ARG

ARG 是 Docker 新版本中支持的参数,能够在 build 阶段使用 --build-arg key="value" 定义一个变量,替换 Dockerfile 中 ARG。

注意:ARG 定义的变量,容器启动后不会传到容器内。

注意:建议在 build 阶段使用 ARG,比如在 Dockerfile 中定义了 ARG,那么 build 阶段就可以使用 --build-arg key="value" 进行指定了,由于容器启动后不会传到容器内,那么只有在 build 阶段的指令 COPY、ADD 等会受益。而 ENV 则建议在 RUN 阶段使用,由于 ENV 可以传入到容器内,那么 build 和 run 阶段的所有指令,包括 CMD、ENTYRPOINT 等都将受益,启动容器时就可以通过 --env-e 来指定新变量,传入到容器内。

注意:使用 ENV 指令定义的环境变量始终覆盖同名的 ARG 指令。

ARG 的构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。

语法

1
2
3
ARG <name1>[=<default value1>]
ARG <name2>[=<default value2>]
ARG <name3>[=<default value3>]

10. RUN

用于指定 docker build 过程中运行的程序,其可以是任何命令。

两种使用形式

1
2
3
4
5
# shell 形式
RUN <command> # 该命令在 shell 中运行,默认情况下 /bin/sh -c

# exec 形式
RUN ["<executable>", "<param1>", "<param2>"]

注意:与 shell 不同,exec 不会调用 shell 命令。这意味着不会发生正常的 shell 处理。例如,RUN ["echo", "$HOME"] 不会对 $HOME 进行变量替换。如果你想要 shell 处理,那么要么使用 shell ,要么直接执行 shell,例如:RUN [ "sh", "-c", "echo $HOME" ]。当使用 exec 并直接执行 shell 时,就像 shell 的情况一样,它是执行环境变量扩展的 shell,而不是 docker。

注意:exec 形式被解析为 JSON,要使用双引号。

注意:如果要运行的不同的 shell 的话,可以将其替换为类似下面的格式。

1
RUN ["/bin/bash", "-c", "<executable>", "<param1>"]`

镜像构建有两个阶段:

  1. BUILD

    • COPYADDRUN 等,发生在镜像制作阶段
  2. RUN

    • CMDENTRYPOINT 等,发生在容器启动时

build_run

11. CMD

类似于 RUN 指令,CMD 指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同:

指令 运行时机 有多行指令时
RUN 映像文件构建过程中 全部生效
CMD 基于 Dockerfile 构建出的新映像文件启动一个容器时 仅最后一个会生效

注意:不要混淆使用 RUNCMDRUN 实际上是运行一个命令并提交结果到构建的镜像中。而 CMD 在构建时不执行任何操作,当启动容器时才运行指定命令。

CMD 指令的首要目的在于为启动的容器指定默认要运行的程序

不过,CMD 指定的命令其可以被 docker run 的 command 选项所覆盖。

在 Dockerfile 中可以存在多个 CMD 指令,但仅最后一个会生效。

CMD 有三种使用形式:

1
2
3
CMD ["executable","param1","param2"] # 使用 exec 执行,推荐方式
CMD ["param1","param2"] # 作为 ENTRYPOINT 的默认参数
CMD command param1 param2 (shell form) # 在 /bin/sh 中执行,提供给需要交互的应用

注意:如果 CMD 用于为 ENTRYPOINT 指令提供默认参数,则应使用 JSON 形式

注意exec 形式被解析为 JSON,要使用双引号。

注意:与 shell 不同,exec 不会调用 shell 命令。这意味着不会发生正常的 shell 处理。例如,CMD ["echo", "$HOME"] 不会对 $HOME 进行变量替换。如果你想要 shell 处理,那么要么使用 shell ,要么直接执行 shell,例如:CMD [ "sh", "-c", "echo $HOME" ]。当使用 exec 并直接执行 shell 时,就像 shell 的情况一样,它是执行环境变量扩展的 shell,而不是 docker。

12. ENTRYPOINT

类似 CMD 指令的功能,运行时机也是一样,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序。

指令 运行时机 有多行指令时 覆盖方式
CMD 启动容器时 仅最后一个会生效 会被 docker run 中的 [COMMAND] 覆盖 – docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
ENTRYPOINT 启动容器时 仅最后一个会生效 只有 docker run 命令的 --entrypoint 选项的参数可覆盖,而 [COMMAND] 中的内容会被当成 ENTRYPOINT 程序的参数传入

两种使用形式

1
2
ENTRYPOINT ["executable", "param1", "param2"]  # exec 形式,首选
ENTRYPOINT command param1 param2 # shell 形式

注意:当两者同时出现时,CMD 中的内容会被当做 ENTRYPOINT 中指定程序的参数。

注意shell 形式可以防止任何 CMDRUN 作为命令行参数传入,但缺点是你的 ENTRYPOINT 将作为 /bin/sh -c 的子命令启动,这意味着此进程在容器中的 PID 不为 1,不能接收 Unix 信号,因此,当使用 docker stop <container> 命令停止容器时,此进程接收不到 SIGTERM 信号。

注意exec 形式被解析为 JSON,要使用双引号。

注意:与 shell 不同,exec 不会调用 shell 命令。这意味着不会发生正常的 shell 处理。例如,CMD ["echo", "$HOME"] 不会对 $HOME 进行变量替换。如果你想要 shell 处理,那么要么使用 shell ,要么直接执行 shell,例如:CMD [ "sh", "-c", "echo $HOME" ]。当使用 exec 并直接执行 shell 时,就像 shell 的情况一样,它是执行环境变量扩展的 shell,而不是 docker。

13. USER

用于指定运行 image 时的或运行 Dockerfile 中任何 RUN、CMD 或 ENTRYPOINT 指令指定的程序时的用户名或 UID,默认情况下,container 的运行身份为 root 用户。

语法

1
USER <UID>|<UserName>

需要注意的是,<UID> 可以为任意数字,但实践中其必须为 /etc/passwd 中某用户的有效 UID,否则,docker run 命令将运行失败。

14. HEALTHCHECK

HEALTHCHECK 指令告诉 Docker 应该如何进行判断容器的状态是否正常。

在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。

而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。

当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。

语法

1
HEALTHCHECK [OPTIONS] CMD

HEALTHCHECK 支持下列选项:

  • --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
  • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
  • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。

和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。

  • 命令的返回值决定了该次健康检查的成功与否:

    • 0:成功;
    • 1:失败;
    • 2:保留,不要使用这个值。

假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:

1
2
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

15. ONBUILD

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。

语法

1
ONBUILD <其它指令>

三、关于镜像的其他操作

把镜像打包成本地 tar 文件:

1
docker image save IMAGE

从本地 tar 文件导入镜像:

1
docker image load IMAGE

查看镜像历史 BUILD 信息:

1
docker image history IMAGE

删除所有当前未被使用的镜像(慎用):

1
docker image prune IMAGE

四、使用 Dockerfile 来手动制作镜像

这里使用 Dockerfile,来手动定制一个 httpd + php module 的镜像。

为了充分学习,本文将通过两种方式展示,第一种相对简单,第二种使用 ENTRYPOINT 加上脚本的方式。

第一种

创建工作目录 /root/httpd,然后创建 index.php 首页文件。

1
2
3
4
-> cat index.php
<?php
phpinfo();
?>

创建 Dockerfile 文件,内容为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM centos:7.6.1810         # 指定 base 镜像

LABEL maintainer="monster modify nginx for centos <admin@monster.com>" # 作者信息

ARG webdir=/var/www/html/ # web 页面目录


RUN yum install httpd php -y # 安装 httpd 和 php module

COPY index.php ${webdir} # 拷贝主页文件到 web 目录

CMD /usr/sbin/httpd -DFOREGROUND # 以前台的方式运行 httpd

EXPOSE 80/tcp # 开放本机的 80 端口

构建镜像,标签为 httpd:1

1
docker build /root/httpd -t httpd:1

运行容器,指定 -d 在后台运行,指定大写 -P 发布 80 端口,这是在 Dockerfile 中定义过的。

1
docker run --name httpd1 -d -P httpd:1

查看端口发布情况。

第一种:

  • 在宿主机查看端口发布在 32791
1
2
3
-> ss -tnlp | grep docker
LISTEN 0 128 :::32791 :::* users:(("docker-proxy",pid=29029,fd=4))

第二种:

  • 也可以这样查看
1
2
-> docker container port httpd1
80/tcp -> 0.0.0.0:32791

访问发布后的地址,宿主机 IP + 端口 http://192.168.50.11:32791/index.php,可以看到 phpinfo 的页面了。

index_php_1

查看容器内进程,httpd 进程的 PID 为 1

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@e2b06113d109 ~]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 7 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 8 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 9 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 10 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 11 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 12 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 13 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
apache 14 1 0 16:12 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
root 15 0 0 16:12 pts/0 00:00:00 /bin/bash
root 29 15 0 16:23 pts/0 00:00:00 ps -ef

这就完成最基本的 Linux + HTTP + PHP

但是这都是默认配置,用起来可能不合自己的心意,比如要想加个虚拟主机,还能从变量获取 ip 地址和端口,或者改改默认配置,怎么办呢?这就需要用到 ENTRYPOINT 指定启动脚本了。

第二种 ENTRYPOINT

在第一种的基础上,添加一个 ENTRYPOINT,将 CMD 和 ENTRYPOINT 结合使用,CMD 中的所有命令都会变成 ENTRYPOINT 中的命令的参数,所以:
ENTRYPOINT ["/root/entrypoint.sh"]
CMD ["/usr/sbin/httpd","-DFOREGROUND"]
就等于
/root/entrypoint.sh /usr/sbin/httpd -DFOREGROUND

Dockerfile 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM centos:7.6.1810

LABEL maintainer="monster modify nginx for centos <admin@monster.com>"

WORKDIR /root/

ARG webdir=/var/www/html/

RUN yum install httpd php -y

COPY index.php ${webdir}

COPY entrypoint.sh /root/ # 拷贝 entrypoint.sh 脚本

ENTRYPOINT ["/root/entrypoint.sh"] # 下面的 CMD 作为 entrypoint.sh 的参数

CMD ["/usr/sbin/httpd","-DFOREGROUND"]

EXPOSE 8080/tcp

entrypoint.sh 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-> # cat entrypoint.sh
#!/bin/bash
#
#

cat > /etc/httpd/conf.d/vm.conf <<EOF
Listen ${LISTEN_PORT:-80}
<VirtualHost *:${LISTEN_PORT:-80}>
ServerName "${SERVER_NAME:-localhost}"
DocumentRoot "${DOC_ROOT:-/var/www/html}"
<Directory "${DOC_ROOT:-/var/www/html}">
Options none
AllowOverride none
Require all granted
</Directory>
</VirtualHost>
EOF

exec "$*" # 以这个脚本的所有参数为命令,代替当前shell运行

entrypoint.sh 脚本中用到的 shell 变量语法提示

  • ${variable:-word}:表示如果设置了变量,那么结果将是该值。如果未设置变量,那么 word 将是结果。
  • ${variable:+word}:表示如果设置了变量,那么 word 将是结果,否则结果是空字符串。
      
    构建镜像
1
docker build /root/httpd -t httpd:1

根据镜像启动容器,但是这次启动容器时我们通过定义环境变量的方式,将监听端口改成 8080,方法是这样:

entrypoint.sh 中,我们定制了一些 httpd 的配置,比如监听端口 ${LISTEN_PORT:-80},如果不指变量,默认就是 80,如果想换成 8080,那么从哪里获得变量呢?

上文说过,ENTRYPOINT 脚本是在容器启动后运行的,那么在这个脚本中,需要引用到变量的话,那必然只有 3 个地方可以获得,1、Dockerfile 中通过 ENV 设定;2、entrypoint.sh 脚本中事先定义了;3、容器内系统的环境变量。

而 1 和 2 我们都没有定义过,那么可以使用的就只有 --> 3、环境变量。

所以我们就可以通过指定 ENV 环境变量的方式,在启动容器时,使用 --env-e 把 8080 传递到容器中。

1
docker run --rm --name httpd2 -d --env LISTEN_PORT=8080 -p 8080 httpd:1

容器启动后登录查看 /etc/httpd/conf.d/vm.conf 文件,看到监听端口确实变成了我们指定的 8080

1
2
3
4
5
6
7
8
9
10
11
12
root@node1 [11:50:18 PM] [~] -> docker exec -it httpd2 /bin/bash
[root@f3aa0cb8d6db ~]# cat /etc/httpd/conf.d/vm.conf
Listen 8080
<VirtualHost *:8080>
ServerName "localhost"
DocumentRoot "/var/www/html"
<Directory "/var/www/html">
Options none
AllowOverride none
Require all granted
</Directory>
</VirtualHost>

通过 env 命令可以看到容器内有一个我们传入的环境变量
listen

查看端口发布信息:

1
2
-> docker port httpd2
8080/tcp -> 0.0.0.0:32811

通过页面访问 http://192.168.50.11:32811/index.php

index_php_2

完毕~

参考:
https://docs.docker.com/engine/reference/builder/#cmd