Dockerfile

docker build 命令用于使用 Dockerfile 创建镜像。

  • -f:指定要使用的Dockerfile路径;
  • -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
# 示例,使用当前目录的 Dockerfile 创建镜像,标签为test/ubuntu:v1
docker build -t test/ubuntu:v1 . 

# 使用-f指定路径下dockerfile文件,如果文件名就叫Dockerfile那么文件名可省略,否则需指定完整路径/文件名
docker build -f /xxx/yyy/zzz -t name:tag .

Dockerfile

Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。
参考文档

Dockerfile的基本结构

Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。

1. 基础镜像信息		# FROM   
2. 维护者信息		# MAINTAINER   
3. 镜像操作指令		# RUN、COPY、ADD、EXPOSE等   
4. 容器启动时执行指令	# CMD、ENTRYPOINT

Dockerfile文件说明

  • Docker以从上到下的顺序运行Dockerfile的指令。
  • 为了指定基本映像,第一条指令必须是FROM。
  • 以 # 开头的行是注释,而在其他位置出现的 # 会被当成参数。
  • 可以在Docker文件中使用FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD等指令。
  • 错误的指令会被忽略。
  • 注意:Dockerfile文件应存放在独立目录中,除了构建镜像所需要的文件,尽量不要有其它文件,构建镜像时dockerfile将以该构建上下文目录发送到Docker守护进程,意思就是会将该目录内的所有文件都扫描一下,如果有不相干的大文件,会非常影响构建镜像的速度。

.dockerignore 文件

dockerignore 文件的作用类似于 git 工程中的 .gitignore 。不同的是 .dockerignore 应用于 docker 镜像的构建,它存在于 docker 构建上下文的根目录,用来排除不需要上传到 docker 服务端的文件或目录。
.dockerignore 文件的写法和 .gitignore 类似,支持正则和通配符,具体规则如下:

  • 每行为一个条目;
  • 以 # 开头的行为注释;
  • 空行被忽略;
  • 构建上下文路径为所有文件的根路径;

文件匹配规则具体语法如下:
image

参数详细说明

FROM:指定基础镜像,必须为第一个命令。

# 格式
FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>

# tag和digest是可选的,如果不使用,默认使用latest版本,示例:
FROM nginx
FROM nginx:1.22.1

MAINTAINER:维护者信息

# 官方已弃用,但是还可以使用,建议使用LABEL替代
# 格式
MAINTAINER <name>
# 示例,可使用多次,一次一个信息
MAINTAINER TanDabao
MAINTAINER xxx@163.com
# 并一条就好
MAINTAINER TanDabao<xxx@163.com>

LABEL:给镜像以键值对的形式添加一些元数据(metadata)。

格式:
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
LABEL author="dabao" \
    version="1.0" \
    description="这是一个Web服务器" \
    by="IT笔录"

注1:使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
注2:value务必使用双引号而不是单引号。
# 可使用如下命令查看该镜像label信息
docker image inspect --format="{{json .ContainerConfig.Labels}}" 镜像名

ENV:设置环境变量

下列指令可以支持环境变量展开:
ADD、COPY、ENV、EXPOSE、FROM、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD、RUN。

# 格式:
# <key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
ENV <key> <value>  
# 可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
ENV <key1>=<value1> <key2>=<value2>... 

# 示例,设置多个变量时使用 \ 换行:

ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

RUN:构建镜像时执行的命令

RUN用于在镜像容器中执行命令,其有以下两种命令执行方式:

  • shell执行:RUN 命令
  • exec执行:RUN ["可执行文件或命令", "参数1", "参数2"]

shell 形式,命令在 shell 中运行,默认/bin/sh -c在 Linux 或cmd /S /C Windows 上;
exec 形式,命令是直接运行的,容器不调用shell程序,即容器中没有shell程序,因为exec格式不会在shell中执行,所以环境变量的参数不会被替换。
RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache

# 示例,shell执行,命令行过长使用右斜杠换行,多行命令尽量使用&&符号连接一次性执行完,因为多个RUN会形成多层镜像。
RUN yum -y install wget \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && tar -xvf redis.tar.gz

# exec执行,exec格式中的参数会当成JSON数据被Docker解析,故必须使用双引号而不能使用单引号。
RUN ["/bin/bash", "-c", "echo hello"]
# 在JSON格式中,需要对反斜杠进行转义。比如Windows:
RUN ["c:\\windows\\system32\\tasklist.exe"]

COPY:将从本地目录中源路径的文件或目录复制到新的一层的镜像内的目标路径位置。

# 格式
COPY [--chown=用户:属主] 源路径 目标路径

# 示例
COPY package.json /usr/src/app/        # 目标路径以/结尾,将package.json文件复制到app目录下
COPY package.json /usr/src/app         # 目标路径不以/结尾,将package.json文件复制到src目录下并重命名为app
COPY test/ /tmp/                       # 将test目录下的所有文件复制到tmp目录下
COPY test/ /tmp/test/                  # 将test目录下的所有文件复制到test目录下,test目录不存在将被创建

# <源路径> 可以是多个,甚至可以是通配符。
COPY hom* /mydir/
COPY hom?.txt /mydir/

# 使用该指令的时候还可以加上--chown=<user>:<group> 选项来改变文件的所属用户及所属组,默认是保留源文件的属性。
COPY --chown=55:mygroup files* /mydir/

ADD:和COPY 的格式和性质基本一致。

但是在COPY 基础上增加了一些功能,源路径可以是一个URL,然后类似wget将其下载后复制到容器中,下载后的文件权限自动设置为600;如果源路径为一个 tar 压缩文件的话,压缩格式为gzip,bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到目标路径去。
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。

# 示例,推荐仅在需要解压缩时替代COPY指令使用
ADD jdk-8u11-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.22.tar.gz /usr/local/

# 在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组
ADD --chown=55:mygroup files* /mydir/

CMD:构建容器后调用,也就是在容器启动时才进行调用。

  1. CMD指令分为shell、exec两种格式,官方推荐exec格式,使用该格式可以在docker run的时候传参覆盖使用。
  2. CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
  3. 如果定义了多个CMD,只有最后一个会执行。
  4. 如果docker run的时候指定了其它命令,CMD命令将被覆盖。
  5. Dockerfile 应至少指定一个CMD命令或ENTRYPOINT命令。
# exec 格式,推荐方式,数组内的执行文件和参数应用双引号而非单引号
CMD ["可执行文件", "参数1", "参数2"...]
# 示例
CMD ["nginx", "-g", "daemon off;"]

# 参数列表格式,作为ENTRYPOINT指令的默认参数。
CMD ["参数1", "参数2"...]

# shell 格式,实际的命令执行会被包装为sh -c的参数的形式进行执行
CMD echo $HOME    # 等价于 CMD [ "sh", "-c", "echo $HOME" ]

ENTRYPOINT:目的和CMD一样,都是在指定容器启动程序及参数。

  1. ENTRYPOINT分为shell、exec两种格式,官方推荐exec格式。
  2. 同时指定CMD和ENTRYPOINT指令时,CMD会作为ENTRYPOINT指令的默认参数使用,如果docker run的时候指定了参数则该参数会覆盖掉CMD指令作为ENTRYPOINT的参数。通常两者配合使用,ENTRYPOINT指定固定执行的命令或文件,CMD指定默认参数,这样CMD的参数是可变的,如果docker run时没有指定则使用CMD的默认参数,如docker run指定了参数则使用该参数。
  3. 如果定义了多个ENTRYPOINT,只有最后一个会执行。
  4. docker run时使用--entrypoint可以覆盖掉Dockerfire里的ENTRYPOINT指令。
# 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
ENTRYPOINT "<CMD>"

# exec格式,推荐方式,数组内的执行文件和参数应用双引号而非单引号
ENTRYPOINT ["可执行文件", "参数1", "参数2"]

# shell格式
ENTRYPOINT 命令 参数1 参数2

# 示例
FROM centos:7
ENTRYPOINT ["ls"]
CMD ["/var"]

# 将其打包成镜像
docker build -t centos-test:v1 .
# 根据该镜像创建容器时不传参则会在容器内默认执行ls /var
docker run centos-test:v1
# 如传参则会覆盖掉CMD的参数,如下则会执行ls /tmp
docker run centos-test:v1 /tmp

EXPOSE:指定于外界交互的端口

EXPOSE 指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。
要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口。
在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

# 格式
EXPOSE <port> [<port>...]

# 示例
EXPOSE 80 443
EXPOSE 8080
EXPOSE 11211/tcp 11211/udp

VOLUME:定义匿名卷

在 Dockerfile 中,可以事先使用 VOLUME 指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

# 格式,如要定义多个数据卷则使用[]扩起来,单个目录则直接写上即可
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

# 示例,如下两行定义的目录,当docker run未指定数据卷时,则会创建一串随机字符的数据卷用于映射以下目录
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
VOLUME /data

# 运行容器时指定-v参数映射VOLUME定义过的目录则可以覆盖这个挂载设置。
# 比如:$ docker run -d -v mydata:/data xxxx,在这行命令中,就使用了 mydata 这个命名卷挂载到了/data这个位置,替代了Dockerfile中定义的匿名卷的挂载配置。

WORKDIR:指定工作目录,类似于cd命令

通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。

# 格式
WORKDIR /path/to/workdir

# 示例,使用相对路径时所切换的路径与之前的WORKDIR有关
WORKDIR /a  (这时工作目录为/a)
WORKDIR b  (这时工作目录为/a/b)
WORKDIR c  (这时工作目录为/a/b/c)

USER:指定当前用户

USER指令和WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR是改变工作目录,USER则是改变之后层的执行RUN,CMD以及ENTRYPOINT这类命令的身份。
当服务不需要管理员权限时,可以通过该命令指定运行用户。
使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。
USER 只是帮助切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。

# 格式
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

# 示例
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

# 如果以root执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用su或者sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。
# 示例,建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

ARG:构建参数

构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

# 格式
ARG <参数名>[=<默认值>]

# ARG 指令有生效范围,如果在 FROM 指令之前指定,那么只能用于 FROM 指令中。
# 只在 FROM 中生效
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine


# 在FROM 之后使用变量,必须在每个阶段分别指定
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine



ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME} 

FROM ${DOCKER_USERNAME}/alpine

ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

ONBUILD:设置镜像触发器,其参数是任意一个Dockerfile 指令。

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
简单来说,使用ONBUILD指令构建一个基础A镜像,使用A镜像创建容器时不会执行ONBUILD指令。再基于A镜像构建一个B镜像,使用B镜像创建容器时,在FROM指令后就会立即执行ONBUILD指令里的命令。
ONBUILD指令就是单纯为了制作基础镜像存在的,在此基础镜像上再次构建出B、C、D镜像,这些镜像都共同使用基础镜像A里的一些配置。

# 格式
ONBUILD <其它指令>

# 示例
https://yeasy.gitbook.io/docker_practice/image/dockerfile/onbuild
https://www.cnblogs.com/51kata/p/5265107.html

HEALTHCHECK:健康检查

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。
当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。
HEALTHCHECK 支持下列选项:

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

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

# 格式
HEALTHCHECK [选项] CMD <命令>

# 示例,一个最简单的Web服务,使用curl命令来帮助判断
# 这里设置了每5秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过3秒没响应就视为失败,并且使用curl加||命令作为健康检查命令
# 用双竖线‘||’分割的多条命令,执行的时候遵循如下规则,如果前一条命令为真,则后面的命令不会执行,如果前一条命令为假,则继续执行后面的命令。
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -fs http://localhost/ || exit 1
  
# 使用 docker build 来构建这个镜像
docker build -t myweb:v1 .
# 启动一个容器
docker run -d --name web -p 80:80 myweb:v1

# 运行该镜像后,通过 docker container ls 看到最初的状态为 (health: starting)
$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   3 seconds ago       Up 2 seconds (health: starting)   80/tcp, 443/tcp     web

# 在等待几秒钟后,再次 docker container ls,就会看到健康状态变化为了 (healthy)
$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   18 seconds ago      Up 16 seconds (healthy)   80/tcp, 443/tcp     web

# 如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)。
# 为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看。
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
    "FailingStreak": 0,
    "Log": [
        {
            "End": "2016-11-25T14:35:37.940957051Z",
            "ExitCode": 0,
            "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
            "Start": "2016-11-25T14:35:37.780192565Z"
        }
    ],
    "Status": "healthy"
}

Dockerfire示例

示例一:使用python镜像运行Flask

创建一个flask的示例demo文件

# vim app.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
   return 'Hello Docker'

if __name__ == '__main__':
   app.run(host='0.0.0.0')

编写dockerfile

# vim Dockerfile
FROM python:3.6
# 标签
LABEL description="flask示例demo"
# 安装flask框架
RUN pip install flask
# 将本地的app.py文件移动到容器内的app目录,如app目录不存在则会创建
COPY app.py /app/
# 切换到app目录
WORKDIR /app/
# 建议映射端口5000
EXPOSE 5000
# 创建容器时默认运行app.py文件
CMD ["python","app.py"]

生成镜像

docker build -t flask-demo/python .

没有下载过python的镜像生成速度会比较慢,使用国内源也需要几分钟
image
启动一个容器

docker run -d -p 5000:5000 flask-demo/python

查看信息
image
本地访问
image

示例2:使用ubuntu镜像安装stress命令传参使用

vim Dockerfile
FROM ubuntu
# 安装stress命令,这里apt-get update报错了,用了这个方法临时解决的,如果不update又不能直接安装stress工具
RUN apt-get -o Acquire::Check-Valid-Until=false -o Acquire::Check-Date=false update && apt-get install -y stress
# 运行容器时固定执行stress命令
ENTRYPOINT ["/usr/bin/stress"]
# CMD不传参,使用docker run时给定的参数
CMD []

生成镜像

docker build -t ubuntu-stress .

根据镜像生成容器,该容器就相当于使用stress工具,直接传参就能使用

docker run -ti -m 200M -c 5 ubuntu-stress -m 1 -v --vm-bytes 200M -c 1

stress [options]

  • -m:产生 N 个进程,每个进程不断分配和释放内存
  • --vm-bytes B:指定分配内存的大小
  • -c:产生 N 个进程,每个进程都反复不停的计算随机数的平方根(压测CPU,直至占满)
  • -v:显示详细的信息
  • -t:在 N 秒后结束程序

上述命令行就是产生两个进程,一个进程不停使用再释放200M内存,一个进程一直高频使用CPU
使用该命令可测试容器资源限制,-m、-c,内存和cpu的分配。

# 如下分配200M然后使用500M会直接报错,容器退出
docker run -ti -m 200M ubuntu-stress -m 1 -v --vm-bytes 500M

# 如下启用两个容器根据cpu权重分配,通过top命令查看,容器2的进程会保持在容器1进程cpu使用率的2倍左右
docker run -ti -c 5 ubuntu-stress -v -c 1
docker run -ti -c 10 ubuntu-stress -v -c 1

示例3:运行jar包

JRE 和 JDK 的区别:
JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。
JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
就是JDK比JRE要大,如果只是想单纯的运行jar包,使用JRE即可。

# 下载jre镜像,根据jar包需求的版本
FROM openjdk:8-jre
# 创建个路径变量
ENV APP_PATH=/apps
# 切换到工作目录
WORKDIR $APP_PATH
# 将本地jar包移动到容器内工作目录并改名
ADD ems-0.0.1-SNAPSHOT.jar /apps/apps.jar
# 建议映射端口8989
EXPOSE 8989
# 运行容器时固定执行java -jar命令
ENTRYPOINT ["java","-jar"]
# 默认为运行apps.jar包
CMD ["apps.jar"]
posted @ 2023-03-10 23:27  待满茶杯  阅读(87)  评论(0编辑  收藏  举报