一、Dockerfile 是什么?
Dockerfile 是用于构建Dokcer镜像的一种文本文件,该文件由一条条命令语句组成,记录着构建的每一个步骤。
基于Dockerfile 构建镜像可以使用docker build 命令。docker build 命令中使用-f 可以指定具体的dockerfile文件。构建操作统一由Docker daemon
进行,它会先对文件内容语法进行初步验证(语法不对就会返回错误信息),然后逐一运行指令。
每次生成一个新的镜像层,直到执行完所有的指令,就构建出最终的镜像。
二、如何通过Dockerfile 构建镜像?
通过 docker build
命令可以从文本、http、stdin等方式读取Dockerfile文件来构建镜像。
docker build -t phpfpm:1.0 -f ./Dockerfile .
-t 指定标签
-f 指定具体所使用的的Dockerfile
其中最后一个`.`,表示使用当前目录作为构建目录
Tip: 不要使用根目录作为PATH构建上下文,因为它会导致构建将硬盘驱动器的全部内容传输到Docker
构建完成后,可以使用docker images 进行查看。
三、为什么要用dockerfile?
在dockerhub中官方提供很多镜像已经能满足我们99%的服务了,为什么还需要自定义镜像?
解析:是,官方的镜像很多,同时也可以满足我们99%的服务;但是,如果有的公司要做自己的产品,那么,官网的镜像肯定是没有的,这时候就需要用到自定义镜像,而自定义镜像就是dockerfile构建成的;
我们可以用dockerfile自定义写需要的操作,来用dockerfile的指令来实现,最终采用docker build来构建镜像,构建完镜像可以采用docker save 命令打成tar包,以便于日后在其他服务器上使用,也可以采用docker push提交到私有镜像仓库或dockerhub中。
如上图所示,Dockerfile是独立于本地docker实例的一个文本文件,用于自动化地构建具有特定功能的docker镜像。
Dockerfile镜像构建三部曲:
(1)构建Dockerfile文件;
(2)采用docker build命令构建镜像;
(3)采用docker run命令依据镜像运行容器实例。
Dockerfile、Docker镜像和Docker容器的关系
从应用软件开发角度来看,它们分别表示软件开发的三个阶段:
- (1)Dockerfile是软件开发的原材料;
- (2)Docker镜像是软件的交付品;
- (3)Docker容器是Docker交付镜像的实例化,代表软件的实际运行过程。
总结:Dockerfile面向开发,Docker镜像为交付标准,Docker容器与部署、运维相关,三则相辅相成缺一不可,他们是Docker的三大基石。Docker在实际运行中,Dockerfile、Docker镜像、Docker容器三者的运作内容如下所示:
1、Dockerfile定义了进程需要的一切内容,包括:代码执行、文件/环境变量、依赖包、运行环境、操作系统发行版本、服务进程、内核进程等等,很多与操作系统底层相关的内容。
2、通过docker build指令会生成一个Docker镜像,它是为用户提供各种服务的基础;
3、Docker容器则是一个实例化的服务进程。
DockerFile需要注意的编写规范
- 1.# 代表注释
- 2.指令必须要大写,后面最少需要带一个参数,最多无限制;
- 3.执行dockerfile的时候,指令是按照从上到下的顺序执行的;
四、Dockerfile 基本组成
Dockerfile 由一行行命令语句组成,并且支持以 # 开头的注释行。
一般的,Dockerfile 分为四部分:
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动时执行指令。
指令一般格式为 INSTRUCTION arguments,指令包括 FROM
五、常用 Dockerfile 操作指令
注意:指令全部都必须为大写,后面跟的是你要执行的操作命令
ARG—— 定义创建镜像过程中使用的变量 ,唯一一个可以在 FROM 之前定义 。
FROM——基于某个镜像, FROM前面只能有一个或多个ARG指令 。
MAINTAINER(已弃用) —— 镜像维护者姓名或邮箱地址 。
VOLUME —— 指定容器挂载点到宿主机自动生成的目录或其他容器
RUN——执行镜像里的命令,跟在 liunx 执行命令一样,只需要在前面加上 RUN 关键词就行。
COPY——复制本地(宿主机)上的文件到镜像。
ADD——复制并解压(宿主机)上的压缩文件到镜像。
ENV——设置环境变量。
WORKDIR —— 为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录,就是切换目录 。
USER —— 为 RUN、CMD、和 ENTRYPOINT 执行命令指定运行用户。
EXPOSE —— 声明容器的服务端口(仅仅是声明) 。
CMD—— 容器启动后执行的命令 ,多个 CMD 只会执行最后一个,跟 ENTRYPOINT 的区别是,CMD 可以作为 ENTRYPOINT 的参数,且会被 yaml 文件里的 command 覆盖。
ENTRYPOINT—— 容器启动后执行的命令 ,多个只会执行最后一个。
HEALTHCHECH —— 健康检查 。
ONBUILD——它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
LABEL——LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式 ,替换 MAINTAINER
1)镜像构建(docker build)
在有配置文件或者安装包的时候,尽量使Dockerfile和这些文件处于同一目录下
docker build -t text:v1 . --no-cache
# 要在构建后将映像标记到多个存储库中,请在运行命令-t时添加多个参数
docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
### 参数解释
-t:指定镜像名称
. :当前目录Dockerfile
-f:指定Dockerfile路径
--no-cache:不缓存
2)运行容器测试(docker run)
# 非交互式运行
docker run centos:7.4.1708 /bin/echo "Hello world"
### 交互式执行
-t: 在新容器内指定一个伪终端或终端。
-i: 允许你对容器内的标准输入 (STDIN) 进行交互。
# 会登录到docker环境中,交互式
docker run -it centos:7.4.1708 /bin/bash
# -d:后台执行,加了 -d 参数默认不会进入容器
docker run -itd centos:7.4.1708 /bin/bash
### 进入容器
# 在使用 -d 参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入:
#docker exec -it :推荐大家使用 docker exec -it 命令,因为此命令会退出容器终端,但不会导致容器的停止。
#docker attach:容器退出,会导致容器的停止。
docker exec -it b2c0235dc53 /bin/bash
docker attach b2c0235dc53
3)ARG
构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。唯一一个可以在 FROM 之前定义 。构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。
语法格式:
ARG <参数名>[=<默认值>]
示例:
# 在FROM之前定义ARG,只在 FROM 中生效
ARG VERSION=laster
FROM centos:${VERSION}
# 在FROM之后使用,得重新定义,不需要赋值
ARG VERSION
RUN echo $VERSION >/tmp/image_version
4)FROM
定制的镜像都是基于 FROM 的镜像 ,【必选项】
语法格式:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
如果引用多平台图像,可选--platform标志可用于指定图像的平台。FROM例如,linux/amd64、 linux/arm64或windows/amd64。默认情况下,使用构建请求的目标平台。全局构建参数可用于此标志的值,例如允许您将阶段强制为原生构建平台 ( --platform=$BUILDPLATFORM),并使用它交叉编译到阶段内的目标平台。
示例:
ARG VERSION=latest
FROM busybox:$VERSION
# FROM --platform="linux/amd64" busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
5)MAINTAINER(已弃用)
镜像维护者信息
语法格式:
MAINTAINER <name>
示例:
LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"
6)VOLUME
定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
作用:
- 避免重要的数据,因容器重启而丢失,这是非常致命的。
- 避免容器不断变大。
- 在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。
语法格式:
# 后面路径是容器内的路径,对应宿主机的目录是随机的
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
示例:
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
7)RUN
用于执行后面跟着的命令行命令。
语法格式:
RUN
(shell形式,命令在 shell 中运行,默认/bin/sh -c
在 Linux 或cmd /S /C
Windows 上)RUN ["executable", "param1", "param2"]
(执行形式)
示例:
# 以下三种写法等价
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "source $HOME/.bashrc; echo $HOME"]
8)COPY
拷贝(宿主机)文件或目录到容器中,跟 ADD 类似,但不具备自动下载或解压的功能 。所有新文件和目录都使用 0 的 UID 和 GID 创建,除非可选
--chown
标志指定给定的用户名、组名或 UID/GID 组合以请求复制内容的特定所有权。
语法格式:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
示例:
# 添加所有以“hom”开头的文件:
COPY hom* /mydir/
# ?替换为任何单个字符,例如“home.txt”。
COPY hom?.txt /mydir/
# 使用相对路径,并将“test.txt”添加到<WORKDIR>/relativeDir/:
COPY test.txt relativeDir/
# 使用绝对路径,并将“test.txt”添加到/absoluteDir/
COPY test.txt /absoluteDir/
# 修改文件权限
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
9)ADD
拷贝文件或目录到容器中,如果是 URL 或压缩包便会自动下载或自动解压 。
ADD 指令和 COPY 的使用格类似(同样需求下,官方推荐使用 COPY)。功能也类似,不同之处如下:
- ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
- ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
语法格式:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
示例:
# 通配符
ADD hom* /mydir/
# 相对路径,拷贝到WORKDIR目录下relativeDir/
ADD test.txt relativeDir/
# 绝对路径
ADD test.txt /absoluteDir/
# 更改权限
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
ADD 和 COPY 的区别和使用场景:
- ADD 支持添加远程 url 和自动提取压缩格式的文件,COPY 只允许从本机中复制文件
- COPY 支持从其他构建阶段中复制源文件(--from)
- 根据官方 Dockerfile 最佳实践,除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD,其他情况一律使用 COPY
【Docker资料合集】链接:https://pan.baidu.com/s/1e8C89zyyDbepJoY_2aBW8w?pwd=rcg9 提取码:rcg9
10)ENV
设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
语法格式:
ENV <key1>=<value1> <key2>=<value2>...
# 省略"="此语法不允许在单个ENV指令中设置多个环境变量,并且可能会造成混淆。
ENV <key> <value>
示例:
ENV JAVA_HOME=/usr/local/jdk
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy
# 此语法不允许在单个ENV指令中设置多个环境变量,并且可能会造成混淆。
ENV JAVA_HOME /usr/local/jdk
11)WORKDIR
指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。
语法格式:
WORKDIR <工作目录路径>
示例:
FROM busybox
ENV FOO=/bar
WORKDIR ${FOO} # WORKDIR /bar
12)USER
用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
语法格式:
USER <用户名>[:<用户组>]
USER <UID>[:<GID>]
示例:
FROM busybox
RUN groupadd --system --gid=9999 admin && useradd --system --home-dir /home/admin --uid=9999 --gid=admin admin
USER admin:admin
# USER 9999:9999
13)EXPOSE
暴露端口 ,仅仅只是声明端口。
作用:
- 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
- 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
语法格式:
# 默认情况下,EXPOSE假定 TCP。
EXPOSE <port> [<port>/<protocol>...]
示例:
EXPOSE 80/TCP 443/TCP
EXPOSE 80 443
EXPOSE 80/tcp
EXPOSE 80/udp
14)CMD
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:CMD 在构建镜像时不会执行,在容器运行 时运行。
语法格式:
CMD <shell 命令>
CMD ["<可执行文件或命令>","<param1>","<param2>",...]
CMD ["<param1>","<param2>",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
推荐使用第二种格式,执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh。
示例:
CMD cat /etc/profile
CMD ["/bin/sh","-c","/etc/profile"]
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
15)ENTRYPOINT
类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。在 k8s 中 command 也会覆盖 ENTRYPOINT 指令指定的程序
语法格式:
# exec形式,这是首选形式:
ENTRYPOINT ["executable", "param1", "param2"]
# 外壳形式:
ENTRYPOINT command param1 param2
示例:
FROM ubuntu
ENTRYPOINT ["top", "-b"]
# CMD作为ENTRYPOINT参数
CMD ["-c"]
# 与下面的等价
ENTRYPOINT ["top", "-b -c"]
ENTRYPOINT top -b -c
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
16)HEALTHCHECK
用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
语法格式:
HEALTHCHECK [OPTIONS] CMD command(通过在容器内运行命令检查容器运行状况)
HEALTHCHECK NONE(禁用从基础映像继承的任何运行状况检查)
选项CMD
有:
--interval=DURATION
(默认30s
:):间隔,频率--timeout=DURATION
(默认30s
:):超时时间--start-period=DURATION
(默认0s
:):为需要时间引导的容器提供初始化时间, 在此期间探测失败将不计入最大重试次数。--retries=N
(默认3
:):重试次数
命令的exit status
指示容器的运行状况。可能的值为:
- 0:健康状态,容器健康且已准备完成。
- 1:不健康状态,容器工作不正常。
- 2:保留,不要使用此退出代码。
示例:
FROM nginx
MAINTAINER Securitit
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
CMD ["usr/sbin/nginx", "-g", "daemon off;"]
17)ONBUILD
ONBUILD
是一个特殊的指令,它后面跟的是其它指令,比如RUN
,COPY
等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
语法格式:
ONBUILD <其它指令>
示例:
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
18)LABEL
LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式。用来替代 MAINTAINER。
语法格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:比如我们可以添加镜像的作者
LABEL org.opencontainers.image.authors="runoob"
六、ARG 和 ENV 的区别
ARG 定义的变量只会存在于镜像构建过程,启动容器后并不保留这些变量
ENV 定义的变量在启动容器后仍然保留
常见的误解:
这是一些简单的汇总:
- .env 文件,仅在compose.yml文件工作时的预处理步骤过程使用。诸如$HI之类的美元符号变量将替换为同一目录中“ .env”命名文件中包含的值。
- ARG仅在构建Docker映像期间(RUN等)可用,而在创建映像并从其启动容器(ENTRYPOINT,CMD)之后不可用。您可以使用ARG值设置ENV值来解决此问题。
- ENV值可用于容器,但在Docker构建期间,从引入它们的行开始,还可以使用RUN样式的命令。
- 如果使用bash 在中间容器中设置环境变量(RUN export VARI = 5 &&…),则该变量将不会保留在下一个命令中。有一种解决方法。
- env_file,是在一个批次要通过许多环境变量一个命令的便捷途径。请勿将此文件与.env文件混淆。
- 设置ARG和ENV值会在Docker映像中留下痕迹。请勿将它们用于加密数据(好吧,多阶段构建可以使您满意)。
概述
好吧,让我们开始详细。该指南分为以下主题:
- Dot-Env环境文件(.env)
- ARG和ENV的可用性
- 设置ARG值
- 设置ENV值
- 覆盖ENV值
Dot-Env环境文件(.env)
这是一个非常简单的操作,并且由于示例错误以及使用相同格式的相似概念而令人困惑,听起来很像。重要的是env前面的点 - .env,而不是“ env_file”。
如果项目中有一个名为.env的文件,则该文件仅用于将值放入同一文件夹中的docker-compose.yml文件。这些与Docker Compose和Docker Stack一起使用。它与ENV,ARG或上述任何特定于Docker的内容无关。它完全是docker-compose.yml的东西。
.env
文件中的值用以下符号表示:
VARIABLE_NAME=some value
OTHER_VARIABLE_NAME=some other value, like 5
这些键值对用于替换docker-compose.yml文件中的美元符号变量。这是一个预处理步骤,并且使用生成的临时文件。这是避免对值进行硬编码的好方法。您也可以通过替换字符串来使用它来设置环境变量的值,但这不会自动发生。
这是一个示例docker-compose.yml文件,它依赖于.env文件提供的值:
version: '3'
services:
plex:
image: linuxserver/plex
environment:
- env_var_name=${VARIABLE_NAME} # here it is
提示:使用.env文件时,您可以非常轻松地调试docker-compose.yml文件。只需输入docker-compose config即可。这样,您将看到执行替换步骤后不运行任何其他操作的docker-compose.yml文件内容。
避坑:主机上的环境变量可以覆盖 .env文件中的值。在这里阅读更多。
ARG和ENV的可用性
使用Docker时,我们区分两种不同类型的变量-ARG和ENV。
ARG也称为构建时变量。它们只有在使用ARG指令在Dockerfile中“定义”之时才可用,直到构建映像为止。正在运行的容器无法访问ARG变量的值。这也适用于CMD和ENTRYPOINT指令,它们仅告诉容器默认情况下应运行的内容。如果您告诉Dockerfile期望使用各种ARG变量(无默认值),但在运行build
命令时未提供任何变量,则会出现错误消息。
但是,在构建镜像后,可以通过查看镜像 docker history 来轻松检查ARG值。因此,对于敏感数据而言,它们是一个糟糕的选择。
一旦引入ENV指令,ENV变量在构建过程中也可用。但与ARG不同,从最终映像开始的容器也可以访问它们。启动容器时,可以覆盖ENV值,更多信息请参见下文。
这是围绕从Dockerfile构建Docker映像并运行容器的过程中ARG和ENV可用性的简化概述。它们重叠,但是不能从容器内部使用ARG。
ARG和ENV可用性概述。
设置ARG值
因此,您有了Dockerfile,该文件定义了ARG和ENV值。如何设置它们,在哪里?您可以在Dockerfile中将它们保留为空白,或设置默认值。如果您没有为没有默认值的预期ARG变量提供值,则会收到错误消息。
这是一个Dockerfile示例,包含默认值和不包含默认值的示例:
# 使用 ARG 指令定义构建参数 some_variable_name
ARG some_variable_name
# 或者使用一个硬编码的默认值:
# ARG some_variable_name=default_value
# 在 RUN 指令中使用构建参数
# 你也可以使用大括号来引用构建参数 - ${some_variable_name},效果相同
RUN echo "Oh dang look at that $some_variable_name"
从命令行构建Docker映像时,可以使用–build-arg来设置ARG值:
docker build --build-arg some_variable_name=a_value
使用上述Dockerfile运行该命令将导致打印以下行(以及其他内容):
Oh dang look at that a_value
那么,这如何转换为使用docker-compose.yml文件?使用docker-compose时,您可以在args块中指定要传递给ARG的值:
(docker-compose.yml文件)
version: '3'
services:
somename:
build:
context: ./app
dockerfile: Dockerfile
args:
some_variable_name: a_value
当您尝试设置Dockerfile中未提及的ARG变量时,Docker会抱怨。
设置ENV值
那么,如何设置ENV值?您可以在启动容器时做到这一点(我们将在下面进行介绍),但是您也可以通过对它们进行硬编码来直接在Dockerfile中提供默认的ENV值。另外,您可以为环境变量设置动态默认值!
如上所述,构建图像时,您只能提供ARG值。您不能直接提供ENV变量的值。但是,ARG和ENV都可以一起工作。您可以使用ARG设置ENV变量的默认值。这是使用硬编码默认值的基本Dockerfile:
# 无默认值
ENV hey
# 有默认值
ENV foo /bar
# 或 ENV foo=/bar
# ENV 的值可以在构建过程中使用
ADD . $foo
# 或 ADD . ${foo}
# 最终相当于:ADD . /bar
以下是使用动态构建环境值的Dockerfile的代码段:
# 希望一个构建时变量
ARG A_VARIABLE
# 使用该值设置 ENV 变量的默认值
ENV an_env_var=$A_VARIABLE
# 如果没有被覆盖,an_env_var 的值将可用于您的容器!
生成映像后,您可以通过命令行或使用docker-compose.yml文件以三种不同的方式启动容器并提供ENV变量的值。所有这些都将覆盖Dockerfile中的任何默认ENV值。与ARG不同,您可以将各种环境变量传递给容器。甚至那些未在Dockerfile中明确定义的文件。是否执行任何操作取决于您的应用程序。
1.一个一个设置变量
在命令行中,使用-e标志:
docker run -e "env_var_name=another_value" alpine env
从docker-compose.yml文件中:
version: '3'
services:
plex:
image: linuxserver/plex
environment:
- env_var_name=another_value
2.从主机传递环境变量值
与上述方法相同。唯一的区别是,您不提供值,而只是命名变量。这将使Docker访问主机环境中的当前值并将其传递给容器。
docker run -e env_var_name alpine env
对于docker-compose.yml文件,省略方程式符号及其后的所有内容以达到相同的效果。
version: '3'
services:
plex:
image: linuxserver/plex
environment:
- env_var_name
3.从文件(env_file)中获取值
我们可以指定一个文件来读取值,而不是将变量写出来或对它们进行硬编码(根据12个因子的人的看法,它们的味道不好)。这样的文件的内容如下所示:
env_var_name=another_value
上面的文件称为env_file_name(任意名称),它位于当前目录中。您可以引用文件名,将对文件名进行解析以提取要设置的环境变量:
docker run --env-file=env_file_name alpine env
对于docker-compose.yml文件,我们仅引用一个env_file,Docker会对其进行解析以设置变量。
version: '3'
services:
plex:
image: linuxserver/plex
env_file: env_file_name
这是一份简短的备忘单,其中概述了ARG和ENV的可用性以及从命令行进行设置的常用方法。
ARG和ENV可用性概述。
在继续之前:常见问题,如果您是Docker的新手,并且不习惯思考映像和容器:如果尝试从RUN语句( 例如)中设置环境变量的值RUN export VARI=5 && ...
,则您将无权访问下一个RUN语句中的任何一个。这样做的原因是,对于每个RUN语句,都会从中间映像启动一个新容器。在命令末尾将保存图像,但是环境变量不会以这种方式持久存在。
如果您对映像感到好奇,并且想在启动容器之前知道它是否提供了默认的ENV变量值,则可以检查映像,并查看默认情况下设置了哪些ENV条目:
# 首先,获取系统中的 Docker 镜像及其 ID
docker images
# 使用其中一个镜像的 ID 来仔细查看
docker inspect image-id
# 查看 "Env" 条目
# 请注意 "Env" 条目
ew,那是相当多的事情。剩下的唯一一件事是-如果您有太多不同的方法来设置ENV变量的值,那么哪些方法会覆盖其他变量?
覆盖ENV值
假设您有一个从Dockerfile构建的映像,该映像提供了默认的ENV值。从它开始的容器可以访问Dockerfile中定义的ENV变量。但是,可以通过提供单个环境变量或env_files 覆盖这些值,从中解析环境变量并将其传递到容器中。
一旦进程在容器中运行,或者在评估命令时,它们便可以自行更改环境值。像这样的东西:
docker run myimage SOME_VAR=hi python app.py
将会完全覆盖您可能另外为app.py脚本设置的所有SOME_VAR,即使在最终命令之前有一些带有-e标志的值。
优先级从强到弱:填充容器化的应用程序集,单个环境条目中的值,env_file中的值,最后是Dockerfile默认值。
结论
这是对构建Docker映像和启动容器时可以设置ARG和ENV变量的所有方式的全面了解。到目前为止,您应该对.env文件的构建时参数,环境变量,env_files和docker-compose模板有了一个很好的概览。希望您能从中获得很多价值,并且可以在将来使用这些知识来节省大量错误。
要真正掌握这些概念,仅了解它们是不够的。您必须看到它们的实际作用,并将其应用于自己的工作中,才能真正使它们成为工具带的一部分。确保您能够利用这些信息的最佳方法是边做边学–继续尝试当前项目中的某些技巧!
七、制作wordpress镜像
制作一个wordpress的镜像
手动构建
# 1.运行基础容器
[root@docker01 ~]# docker run -it -p 80:80 centos:7 /bin/bash
# 2.换源
[root@ccc7b676aca1 /]# curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
[root@ccc7b676aca1 /]# curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo
[root@ccc7b676aca1 /]# sed -i '/aliyuncs/d' /etc/yum.repos.d/CentOS-Base.repo
# 3.安装数据库
[root@ccc7b676aca1 /]# yum install -y mariadb-server
# 4.拷贝php
[root@docker01 ~]# docker cp nginx_php.tgz ccc7b676aca1:/root
# 5.安装nginx和php
[root@ccc7b676aca1 ~]# yum localinstall -y *.rpm
# 6.初始化mariadb //查看system管理文件即可知道命令
[root@ccc7b676aca1 ~]# /usr/libexec/mariadb-prepare-db-dir %n
# 7.启动数据库
[root@ccc7b676aca1 ~]# mysqld_safe --defaults-file=/etc/my.cnf &
# 8.nginx配置文件
cat > /etc/nginx/conf.d/default.conf << 'EOF'
server {
listen 80;
server_name _;
root /code/wordpress;
location / {
index index.php index.html;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
}
EOF
# 启动nginx 启动php //常规启动即可
常规启动:
nginx
php-fpm
前台运行:
nginx -g 'daemon off;'
/usr/sbin/php-fpm --nodaemonize
#编写启动脚本
vim /.start.sh
#!/bin/bash
/sbin/nginx
/sbin/php-fpm
/bin/mysqld_safe --defaults-file=/etc/my.cnf
# 9.部署代码
[root@ccc7b676aca1 ~]# mkdir /code
[root@ccc7b676aca1 code]# curl -o /code/wordpress-6.2.2.tar.gz https://wordpress.org/wordpress-6.2.2.tar.gz
[root@ccc7b676aca1 code]# tar xf wordpress-6.2.2.tar.gz
# 10.封装镜像
[root@docker01 ~]# docker commit ccc7b676aca1 wp:v1
#再启一个窗口启动镜像
[root@docker01 ~]# docker run --name wp -p 88:80 -d wp:v1 /bin/sh /start.sh
## 手动打镜像的缺陷
1)不支持环境变量
2)容器启动时,自动启动服务
3)不暴露端口
#浏览器访问
10.0.0.101
使用Dockerfile自动构建
#环境准备
[root@docker wordpress]# ll
total 42692
-rw-r--r-- 1 root root 430 Sep 11 09:53 blog.conf
-rw-r--r-- 1 root root 582 Sep 11 11:13 dockerfile
-rw-rw-rw- 1 root root 20682659 May 10 11:02 nginx_php.tgz
-rw-r--r-- 1 root root 83 Sep 11 10:10 start.sh
-rw-r--r-- 1 root root 23018916 Sep 11 10:25 wordpress-6.2.2.tgz
# 2.编写dockerfile
[root@docker01 wordpress]# vim Dockerfile
FROM centos:7
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
RUN curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo
RUN sed -i '/aliyuncs/d' /etc/yum.repos.d/CentOS-Base.repo
RUN yum install -y mariadb-server
ADD nginx_php.tgz /opt
RUN yum localinstall -y /opt/*.rpm
RUN /usr/libexec/mariadb-prepare-db-dir %n
RUN rm -f /etc/nginx/conf.d/*
COPY blog.conf /etc/nginx/conf.d/
COPY start.sh /
RUN mkdir /code
ADD wordpress-6.2.2.tgz /code
EXPOSE 80/tcp 443/tcp
CMD ["/bin/sh","/start.sh"]
VOLUME /code/wordpress
# 3.构建镜像
[root@docker wordpress]# docker build -t wp:v2 .
[+] Building 3.8s (18/18) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 633B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/centos:7 0.0s
=> [ 1/13] FROM docker.io/library/centos:7 0.0s
=> [internal] load build context 0.3s
=> => transferring context: 23.02MB 0.3s
=> CACHED [ 2/13] RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7 0.0s
=> CACHED [ 3/13] RUN curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo 0.0s
=> CACHED [ 4/13] RUN sed -i '/aliyuncs/d' /etc/yum.repos.d/CentOS-Base.repo 0.0s
=> CACHED [ 5/13] RUN yum install -y mariadb-server 0.0s
=> CACHED [ 6/13] ADD nginx_php.tgz /opt 0.0s
=> CACHED [ 7/13] RUN yum localinstall -y /opt/*.rpm 0.0s
=> CACHED [ 8/13] RUN /usr/libexec/mariadb-prepare-db-dir %n 0.0s
=> [ 9/13] RUN rm -f /etc/nginx/conf.d/* 0.5s
=> [10/13] COPY blog.conf /etc/nginx/conf.d/ 0.0s
=> [11/13] COPY start.sh / 0.0s
=> [12/13] RUN mkdir /code 0.3s
=> [13/13] ADD wordpress-6.2.2.tgz /code 1.5s
=> exporting to image 1.0s
=> => exporting layers 1.0s
=> => writing image sha256:9f10914556c1a682b4f7bb930f03dc9a179d36361d5b437ad59db7bc1493a634 0.0s
=> => naming to docker.io/library/wp:v2 0.0s
[root@docker wordpress]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wp v2 9f10914556c1 8 seconds ago 1.04GB #//自动构建
wp v1 5acaa4428eec 8 minutes ago 854MB #//手动构建
nginx alpine cc44224bfe20 20 months ago 23.5MB
mysql 5.7 c20987f18b13 20 months ago 448MB
alpine latest c059bfaa849c 21 months ago 5.59MB
centos 7 eeb6ee3f44bd 24 months ago 204MB
这种方式的自动构建,会使镜像包过大,使用体验极差;所有,我们需要在一定程度上进行优化Dockerfile编写
优化方式:
- 1)使用用完的安装包,可以删除
- 2)叠层,减少层数,将所有的RUN合并
- 3)清除yum缓存
#优化后dockerfile
# 使用基础镜像
FROM centos:7
# 设置镜像源并删除 aliyuncs 相关行,安装软件并清理 yum 缓存
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo && \
sed -i '/aliyuncs/d' /etc/yum.repos.d/CentOS-Base.repo && \
yum install -y mariadb-server && \
yum clean all
# 安装 Nginx 和 PHP,然后清理安装包
ADD nginx_php.tgz /opt/
RUN yum localinstall -y /opt/*.rpm && \
rm -rf /opt/*.rpm /opt/nginx_php.taz
# 准备初始化 MariaDB 数据库
RUN /usr/libexec/mariadb-prepare-db-dir %n
# 清理 Nginx 配置
RUN rm -f /etc/nginx/conf.d/*
# 复制配置文件和启动脚本
COPY blog.conf /etc/nginx/conf.d/
COPY start.sh /
# 创建目录并添加 WordPress
RUN mkdir /code && \
curl -o /code/wordpress-6.2.2.tgz https://wordpress.org/wordpress-6.2.2.tar.gz && \
tar -xf /code/wordpress-6.2.2.tgz -C /code/ && \
rm -f /code/wordpress-6.2.2.tgz
# 暴露端口
EXPOSE 80/tcp 443/tcp
# 设置数据卷
VOLUME /code/wordpress
# 启动容器时运行的命令
CMD ["/bin/sh", "/start.sh"]
再次构建:
[root@docker wordpress]# docker build -t wp:v3 .
[+] Building 201.8s (14/14) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.23kB 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/centos:7 0.0s
=> CACHED [1/9] FROM docker.io/library/centos:7 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 93B 0.0s
=> [2/9] RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo && 87.5s
=> [3/9] ADD nginx_php.tgz /opt/ 0.2s
=> [4/9] RUN yum localinstall -y /opt/*.rpm && rm -rf /opt/*.rpm /opt/nginx_php.taz 100.5s
=> [5/9] RUN /usr/libexec/mariadb-prepare-db-dir %n 2.1s
=> [6/9] RUN rm -f /etc/nginx/conf.d/* 0.6s
=> [7/9] COPY blog.conf /etc/nginx/conf.d/ 0.0s
=> [8/9] COPY start.sh / 0.0s
=> [9/9] RUN mkdir /code && curl -o /code/wordpress-6.2.2.tgz https://wordpress.org/wordpress-6.2.2. 6.3s
=> exporting to image 4.4s
=> => exporting layers 4.4s
=> => writing image sha256:f9cb9505e8e98e3ada56506aa9e5ed76171b04f260700b248fe537e2e09b17ec 0.0s
=> => naming to docker.io/library/wp:v3
#可以看出再步骤上已经减少了许多
[root@docker wordpress]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wp v3 f9cb9505e8e9 47 seconds ago 848MB #优化构建
wp v2 9f10914556c1 14 minutes ago 1.04GB #自动轨迹
wp v1 5acaa4428eec 22 minutes ago 854MB #手动构建
nginx alpine cc44224bfe20 20 months ago 23.5MB
mysql 5.7 c20987f18b13 20 months ago 448MB
alpine latest c059bfaa849c 21 months ago 5.59MB
centos 7 eeb6ee3f44bd 24 months ago 204MB
使用Docker运行Mysql示例:
## 官方案例
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
## 常规启动
docker run --name mysql57 -e MYSQL_ROOT_PASSWORD=123 -d mysql:5.7
## 需要数据 //映射数据文文到本地,使其同步
docker run --name mysql57 -v /data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 -d
mysql:5.7
## 环境变量 // -e指定变量
docker run \
--name mysql57 \
-v /data/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123 \
-e MYSQL_DATABASE='wordpress' \
-e MYSQL_USER='wp_user' \
-e MYSQL_PASSWORD='111' \
-p 3306:3306 \
-d mysql:5.7
八、容器通信
容器的单向通信
什么是docker的link机制?
同一个宿主机上的多个docker容器之间如果想进行通信,可以通过使用容器的ip地址来通信,也可以通过宿主机的ip加上容器暴露出的端口号来通信,前者会导致ip地址的硬编码,不方便迁移,并且容器重启后ip地址会改变,除非使用固定的ip,后者的通信方式比较单一,只能依靠监听在暴露出的端口的进程来进行有限的通信。通过docker的link机制可以通过一个name来和另一个容器通信,link机制方便了容器去发现其它的容器并且可以安全的传递一些连接信息给其它的容器。其使用方式如下:
1.运行一个容器,通过–name指定一个便于记忆的名字,这个容器被称为source container,也就是要连接的容器
docker run --name db -e MYSQL_ROOT_PASSWORD=server -d mysql
上面通过传递环境变量MYSQL_ROOT_PASSWORD=server,来设置mysql服务的密码为server
2.运行另外一个容器,并link到上面启动的容器,这个容器被称为received container
docker run -d --name web --link db:aliasdb nginx
上面通过--link连接名为db的容器,并为其设置了别名aliasdb
完成了上面的两个步骤后,在nginx的容器中就可以使用db或者aliasdb作为连接地址来连接mysql服务,即使容器重启了,地址发生了变化,不会影响两个容器之间的连接。
link机制的连接信息传递
虽然通过使用link机制nginx可以和mysql进行通信了,但是如何知道mysql的端口是多少呢,虽然说是固定的是
3306,但是也不排除更改端口号的问题,并且对应一些非固定端口的应用来说,只要要连接的容器的端口信息也是尤为重要的,link机制通过环境变量的方式提供了这些信息,除此之外像db的密码这些信息也会通过环境变量提供,docker将source container中定义的环境变量全部导入到received container中,在received container中可以通过环境变量来获取连接信息下面是db中提供的环境变量:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/mysql/bin:/usr/local/mysql/scripts
HOSTNAME=c1a7c7f091eb
MYSQL_ROOT_PASSWORD=server
MYSQL_MAJOR=5.5
MYSQL_VERSION=5.5.48
HOME=/root
注: 使用docker exec db env
命令来获得上面的结果
下面我们来看看在web这个容器中,这些变量是如何被导入的。
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=6337c0044215
ALIASDB_PORT=tcp://172.17.0.8:3306
ALIASDB_PORT_3306_TCP=tcp://172.17.0.8:3306
ALIASDB_PORT_3306_TCP_ADDR=172.17.0.8
ALIASDB_PORT_3306_TCP_PORT=3306
ALIASDB_PORT_3306_TCP_PROTO=tcp
ALIASDB_NAME=/web/aliasdb
ALIASDB_ENV_MYSQL_ROOT_PASSWORD=server
ALIASDB_ENV_MYSQL_MAJOR=5.5
ALIASDB_ENV_MYSQL_VERSION=5.5.48
NGINX_VERSION=1.9.10-1~jessie
HOME=/root
上面的变量被分成了五个部分:
- 第一个部分是web容器自身提供的一些环境变量,如NGINX_VERSION,HOSTNAME,HOME,PATH等.
- 第二个部分则是ALIASDB_ENV开头的变量,这些都是从source container中导入的,变量来源于Dockerfile中使用ENV命令定义的变量,或者是docker run的时候通过-e 添加的环境变量。
- 第三个部分是ALIASDB_NAME 这个变量,这变量记录了link的两个容器的组合,这里就是/web/db
- 第四个部分就是ALIASDB_PORT开头的一系列变量,这些变量会有很组,每组变量的命名格式如下
<alias>_PORT_<port>_<protocol>
<alias>_PORT_<port>_<protocol>_PORT
<alias>_PORT_<port>_<protocol>_PROTO
<alias>_PORT_<port>_<protocol>_ADDR
其中<port>
是在Dockerfile中使用EXPOSE导出的端口,还有docker run 的时候使用-p导出的端口。<protocol>
则是这些端口对应的协议。
- 第五个部分就是ALIASDB_PORT这个变量,这个变量是EXPOSE导出端口中的第一个端口对应的连接url,
如果有EXPOSE导出的端口,还有docker run -p指定导出的端口,那么通过-p指定的端口是第一个被导出的端口
link机制和/etc/hosts
使用了link机制后,可以通过指定的名字来和目标容器通信,这其实是通过给/etc/hosts
中加入名称和IP的解析关系来实现的,下面是名为web的容器中的/etc/hosts
信息.
172.17.0.10 6337c0044215
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.8 aliasdb c1a7c7f091eb db
通过上面的信息可以看出,link机制给received container(这里是名为web的容器)添加了一条关于db容器的名称解析。有了这个名称解析后就可以不使用ip来和目标容器通信了,除此之外当目标容器重启,docker会负责更新/etc/hosts文件,因此可以不用担心容器重启后IP地址发生了改变,解析无法生效的问题。但是很不幸的是,环境变量无法更新,上文中提到了link机制会通过环境变量将一些db容器的信息导入到web容器中,这种导入是一次性的,此后这个容器更新了环境变量的信息是无法在web容器中更新的
link机制和网络新特性
通过上文中对link机制的介绍,可以发现link机制提供了如下几个功能
- 名称解析
- 对link的容器可以使用别名
- 安全的容器间连接通信
- 环境变量的注入
安全的容器间连接通信,这个需要结合docker daemon的-icc=false 这个选项,默认同一个宿主机上的所有容器可以互相通信,当使用-icc=false 的时候所有容器之间是无法进行互相通信的(具体原因会单独出篇文章分析),但是使用link机制后,即使使用了-icc=false 两个容器之间也可以进行基于端口的通信。很不幸的是当docker引入网络新特性后,link机制变的有些多余,但是为了兼容早期版本,–link机制在默认网络上的功能依旧没有发生变化,docker引入网络新特性后,内置了一个DNS Server,但是只有用户创建了自定义网络后,这个DNS Server才会起作用。在网络新特性为未引入之前,有三种网络,第一种就是docker0这种桥接网络,用的也是最多的,第二个则是复用主机网络,称为HOST网络,第三种就是none网络,只创建了一个空的网络命名空间,没有网络接口,无法和外界通信,可以让使用者自己去构建网络。当网络新特性引入后,有了overlay网络,有了用户自定义网络。用户自定义网络下,用户可以通过docker的network子命令创建一个自定义的桥接网络,这个自定义桥接的网络和默认的docker0桥接网络基本功能都是一致的,只是在这个自定义桥接网络中拥有一些特性,可以替代link机制。这些特性包括如下几个方面:
- 基于DNS的名称自动解析
- 安全的隔离环境
- 动态的附加或者脱离一个网络
- 支持使用–link设置别
在用户自定义网络下,不使用link机制就可以实现名称解析功能了,不再是通过link机制追加名称解析关系到/etc/hosts文件中了。并且在默认的docker0桥接网络和自定义网络下使用link机制的效果是不一样的,在自定义网络中link机制只是负责设置别名的,不再提供环境变量注入的功能了。自定义网络中同时也提供了--net-alias功能和link机制提供别名功能是一样的。保留link机制目的是为了兼容。