五、Docker 镜像管理

a.Docker镜像简介

  • docker镜像基于union file system将多个目录合并挂载至一个目录给容器使用
  • docker镜像只有rootfs而没有内核、运行使用的是宿主机的bootfs
    • rootfs(root file system),文件系统
    • bootfs(boot file system),主要包含 bootloader 和 Kernel
  • 一个镜像是有一层或者多层合并而成,每一层称为是一个layer
  • 镜像可以基于其它镜像进行重新构建,被引用的镜像称为父镜像
  • 一个镜像可以同时被创建为多个容器
  • 镜像是只读的,在容器的任何更改都不会直接修改镜像

镜像即创建容器的模版,含有启动容器所需要的文件系统及所需要的内容,因此镜像主要用于方便和快速的创建并启动容器

b. Dockerfile简介

Dockerfile 可以理解为构建docker 镜像的源码。docker 可以通过读取一个dockerfile来自动构建docker image ,Dockerfile是一个文本文件,其中包含了按顺序排列的构建指定镜像所需要的全部命令, Dockerfile可以放在任意路径,但文件名必须为Dockerfile,且首字母D必须大写。使用Dockerfile构建镜像可以实现镜像的自动化构建,提高部署速度和环境的一致性。

c. Dockerfile 指令

Dockerfile格式要求

  • #号开头的行为注释行
  • 可以设置.dockeringore指定不打包进镜像的文件列表
  • docker build 中执行的shell命令是由基础镜像所包含的命令集合
  • 指令不区分大小写,一般默认都写成大写
  • 第一个非注释行,必须是“FROM”指令,指出基于哪个基础镜像

常用的Dockerfile指令及说明

指令 说明
FROM 设置镜像使用的基础镜像
MAINTAINER 设置镜像的作者信息,已被淘汰,建议使用LABEL标签代替
RUN 编译镜像时运行的脚本
CMD 设置容器的启动命令
LABEL 设置镜像的标签
EXPOESE 设置镜像暴露的端口,备注作用
ENV 设置容器的环境变量
ADD 编译镜像时复制文件到镜像中
COPY 编译镜像时复制文件到镜像中
ENTRYPOINT 设置容器的入口程序
VOLUME 设置容器的挂载卷
USER 设置运行RUN CMD ENTRYPOINT的用户名
WORKDIR 设置RUN CMD ENTRYPOINT COPY ADD指令的工作目录,类似cd命令
ARG 设置编译镜像时加入的参数
ONBUILD 设置镜像的ONBUILD指令

d. Dockerfile 相关指令详解

1、FROM: 指定基础镜像

FROM指令是最重要的一个并且必须为Dockerfile文件开篇的第一个非注释行,用于为镜像文件构建过程中指定基准镜像,后续的指令运行于此基础镜像所提供的运行环境。 基准镜像可以是任何可用的镜像文件,默认情况下,docker build时候会在docker主机上查找指定的镜像,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件,如果找不到指定的镜像文件,docker build 会返回一个错误信息

如何选择合适的镜像

对于不同的软件官方都提供了相关的docker镜像,比如: nginx、redis、mysql、httpd、tomcat、jdk等服务类的镜像,也有操作系统类,如: centos、ubuntu、debian等。建议使用官方镜像,比较安全。

格式:

FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
#说明: 
--platform 指定镜像的平台,比如: linux/amd64, linux/arm64, or windows/amd64
tag 和 digest是可选项,如果不指定,默认为latest

示例:

FROM scratch #所有镜像的起源镜像,相当于ObjectFROM ubuntu
FROM ubuntu:bionic
FROM centos:2009

2、LABEL: 指定镜像元数据

可以给镜像添加标签,来帮助项目组织镜像、记录许可信息、帮助自动化等。对于每个标签,添加一行以LABEL开头并带有一个或多个键值对的行

注意:

  1. 如果字符串中包含空格,则必须用双引号引起来或转义这个空格。如果字符串中包含双引号,必须转义
  2. 一个镜像可以有多个标签

格式:

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

示例:

LABEL "com.example.vendor"="ACME Incorporated"
LABEL "org.label-schema.name": "CentOS Base Image"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
#一行格式
LABEL multi.label1="value1" multi.label2="value2" other="value3"
#多行格式
LABEL multi.label1="value1" \
    multi.label2="value2" \
    other="value3"

docker inspect 命令可以查看LABEL

3、MAINTAINER: 指定维护者信息

此指令已过时,用LABEL代替

格式:

MAINTAINER <name>

示例:

MAINTAINER yuhao@20080808@qq.com
#用LABEL代替
LABEL maintainer="yuhao@20080808@qq.com"

4、RUN: 执行 shell命令

RUN 指令用来在构建镜像阶段需要执行 FROM 指定镜像所支持的Shell命令。
注意: RUN 可以写多个,每一个RUN指令都会建立一个镜像层,所以尽可能合并成一条指令,比如将多个shell命令通过 && 连接一起成为在一条指令
每个RUN都是独立运行的,和前一个RUN无关

格式:

#shell 格式: 相当于 /bin/sh -c <命令> 此种形式支持环境变量
RUN <命令>
#exec 格式: 此种形式不支持环境变量,注意:是双引号,不能是单引号
RUN ["executable","param1","param2"...]
#exec格式可以指定其它shell
RUN ["/bin/bash","-c","echo hello world"]

示例:

RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
RUN ["/bin/bash", "-c", "echo hello world"]
RUN yum -y install epel-release \
  && yum -y install nginx \
  && rm -rf /usr/share/nginx/html/*
  && echo "<h1> docker test nginx </h1>" > /usr/share/nginx/html/index.html

多个 前后RUN 命令独立无关和shell命令不同

#world.txt并不存放在/app内
RUN cd /app
RUN echo "hello" > world.txt

5、ENV: 设置环境变量

ENV 可以定义环境变量和值,会被后续指令(如:ENV,ADD,COPY,RUN等)通过$KEY或${KEY}进行引用,并在容器运行时保持

格式:

#变量赋值格式1
ENV <key> <value>       #此格式只能对一个key赋值,<key>之后的所有内容均会被视作其<value>的组成部分

#变量赋值格式2
ENV <key1>=<value1> <key2>=<value2> \  
<key3>=<value3> ...     #此格式可以支持多个key赋值,定义多个变量建议使用

#如果<value>中包含空格,可以以反斜线\进行转义,也可通过对<value>加引号进行标识;另外,反斜线也可用于续行
#只使用一次变量
RUN <key>=<value> <command>

#引用变量
RUN $key .....
#变量支持高级赋值格式
${key:-word} # 如果key变量值为空,返回word字符串
${key:+word} # 如果key变量为空,什么都不做,否则word返回

如果运行容器时如果需要修改变量,可以执行下面通过基于 exec 机制实现
注意: 下面方式只影响容器运行时环境,而不影响构建镜像的过程,即只能覆盖docker run时的环境变量,而不会影响docker build时环境变量的值

docker run -e|--env <key>=<value>
#说明
-e, --env list  #Set environment variables
  --env-file filename   #Read in a file of environment variables

示例:

#格式1
ENV myName="John Doe" myDog=Rex\ The\ Dog \
  myCat=fluffy

#格式2
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

6、COPY: 复制文件至容器内

复制本地宿主机的文件到容器中的

格式:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] #路径中有空白字符时,建议使用此格式

说明:

  • 可以是多个,可以使用通配符
  • 必须是build上下文中的路径(为 Dockerfile 所在目录的相对路径),不能是其父目录中的文件
  • 如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制
  • 如果指定了多个, 或在中使用了通配符,则必须是一个目 录,且必须以 / 结尾
  • 可以是绝对路径或者是 WORKDIR 指定的相对路径
  • 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等
  • 如果事先不存在,它将会被自动创建,这包括其父目录路径,即递归创建目录

示例:

COPY hom* /mydir/  
COPY hom?.txt /mydir/

7、ADD: 复制和解包文件

该命令可认为是增强版的COPY,不仅支持COPY,还支持自动解压缩。可以将文件复制指定的到容器中

格式:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

说明:

  • 可以是Dockerfile所在目录的一个相对路径;也可是一个 URL;还可是一个 tar 文件(自动解压)
  • 可以是绝对路径或者是 WORKDIR 指定的相对路径
  • 如果是目录,只复制目录中的内容,而非目录本身
  • 如果是一个 URL ,下载后的文件权限自动设置为 600
  • 如果为URL且不以/结尾,则指定的文件将被下载并直接被创建为,如果以 / 结尾,则文件名URL指定的文件将被直接下载并保存为/< filename>
  • 如果是一个本地文件系统上的打包文件,如: gz, bz2 ,xz ,它将被解包 ,其行为类似于”tar -x”命令,但是通过URL获取到的tar文件将不会自动展开
  • 如果有多个,或其间接或直接使用了通配符,则必须是一个以/结尾的目录路径;如果不以/结尾,则其被视作一个普通文件,的内容将被直接写入到

示例:

ADD test relativeDir/      # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/     # adds "test" to /absoluteDir/
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

8、 CMD: 容器启动命令

一个容器中需要持续运行的进程一般只有一个,CMD 用来指定启动容器时默认执行的一个命令,且其运行结束后,容器也会停止,所以一般CMD 指定的命令为持续运行且为前台命令

说明:

  • 如果docker run没有指定任何的执行命令或者Dockerfile里面也没有ENTRYPOINT命令,那么开启容器时就会使用执行CMD指定的默认的命令
  • 前面的 RUN 命令是在构建镜像时执行的命令,注意二者的不同之处
  • 每个 Dockerfile 只能有一条 CMD 命令。如指定了多条,只有最后一条被执行
  • 如果用户启动容器时用 docker run xxx 指定运行的命令,则会覆盖 CMD 指定的命令

格式:

# 使用 exec 执行,推荐方式,第一个参数必须是命令的全路径,此种形式不支持环境变量
CMD ["executable","param1","param2"]

# 在 /bin/sh 中执行,提供给需要交互的应用;此种形式支持环境变量
CMD command param1 param2

# 提供给 ENTRYPOINT 命令的默认参数
CMD ["param1","param2"]

示例:

CMD ["nginx", "-g", "daemon off;"]
CMD ["/bin/sh","-c","/opt/start.sh"]

9、ENTRYPOINT: 入口点

功能类似于CMD,配置容器启动后执行的命令及参数

格式:

# 使用 exec 执行
ENTRYPOINT ["executable", "param1", "param2"...]

# shell中执行
ENTRYPOINT command param1 param2

说明:

  • ENTRYPOINT 不能被 docker run 提供的参数覆盖,而是追加,即如果docker run 命令有参数,那么参数全部都会作为ENTRYPOINT的参数
  • 如果docker run 后面没有额外参数,但是dockerfile中有CMD命令(即上面CMD的第三种用法),即Dockerfile中即有CMD也有ENTRYPOINT,那么CMD的全部内容会作为ENTRYPOINT的参数
  • 如果docker run 后面有额外参数,同时Dockerfile中即有CMD也有ENTRYPOINT,那么docker run后面的参数覆盖掉CMD参数内容,最终作为ENTRYPOINT的参数
  • 可以通过docker run –entrypoint string 参数在运行时替换,注意string不要加空格
  • 使用CMD要在运行时重新写命令本身,然后在后面才能追加运行参数,ENTRYPOINT则可以运行时无需重写命令就可以直接接受新参数
  • 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效
  • 通常会利用ENTRYPOINT指令配合脚本,可以为CMD指令提供环境配置

示例1:

FROM centos:centos7.9-v10.0
LABEL maintainer="yuhao@20080808@qq.com"
ENV version=1.18.0
ADD nginx-$version.tar.gz /usr/local/
RUN cd /usr/local/nginx-$version && ./configure --prefix=/apps/nginx && make &&
make install && rm -rf /usr/local/nginx* && sed -i 's/.*nobody.*/user nginx;/'
/apps/nginx/conf/nginx.conf && useradd -r nginx
COPY index.html /apps/nginx/html
VOLUME ["/apps/nginx/html"]
EXPOSE 80 443
CMD ["-g","daemon off;"]
ENTRYPOINT ["/apps/nginx/sbin/nginx"]
#上面两条指令相当于ENTRYPOINT ["/apps/nginx/sbin/nginx","-g","daemon off;"]

示例2:

利用脚本实现指定环境变量动态生成配置文件内容

# 编辑Dockerfile文件,内容如下
[root@centos7 ~]#cat Dockerfile
FROM nginx:1.16-alpine
LABEL maintainer="yuhao@20080808@qq.com"
ENV DOC_ROOT='/data/website/'
ADD index.html ${DOC_ROOT}
ADD entrypoint.sh /bin/
EXPOSE 80/tcp 8080
#HEALTHCHECK --start-period=3s CMD wget -0 - -q http://${IP:-0.0.0.0}:
{PORT:-80}/
CMD ["/usr/sbin/nginx","-g", "daemon off;"]  #CMD指令的内容都成为了ENTRYPOINT的参数
ENTRYPOINT [ "/bin/entrypoint.sh"]

#编辑entrypoint.sh脚本文件
[root@centos7 ~]#cat entrypoint.sh
#!/bin/sh
cat > /etc/nginx/conf.d/www.conf <<EOF
server {
 server_name ${HOSTNAME};
 listen ${IP:-0.0.0.0}:${PORT:-80};
 root  ${DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "$@"

## 构建镜像
[root@centos7 ~]# chmod +x entrypoint.sh
[root@centos7 ~]# docker build -t nginx:v1.0 .
[root@centos7 ~]# docker run --name n1 --rm -P -e "PORT=8080" -e "HOSTNAME=www.yuhao.org" nginx:v1.0

10、ARG: 构建参数

ARG指令在build 阶段指定变量,和ENV不同的是,容器运行时不会存在这些环境变量

格式:

ARG <name>[=<default value>]

说明:

  • 如果和ENV同名,ENV覆盖ARG变量
  • 可以用 docker build –build-arg <参数名>=<值> 来覆盖

示例:

FROM busybox
ARG author="yuhao@20080808@qq.com"
LABEL maintainer="${author}"

11、VOLUME: 匿名卷

在容器中创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等,默认会将宿主机上的目录挂载至VOLUME 指令指定的容器目录。即使容器后期被删除,此宿主机的目录仍会保留,从而实现容器数据的持久保存。而匿名卷则是由Docker自动生成的,并且没有自定义名字,只能通过自动分配的ID进行引用。匿名卷在容器删除时会被自动删除

为了保留数据,可以使用命名卷代替匿名卷。

docker run -v my_volume:/path/in/container my_image

匿名卷格式:

VOLUME <容器内路径>
VOLUME ["<容器内路径1>", "<容器内路径2>"...]

注意:
<容器内路径>如果在容器内不存在,在创建容器时会自动创建
<容器内路径>如果是存在的,同时目录内有内容,将会把此目录的内容复制到宿主机的实际目录

注意:

  • Dockerfile中的VOLUME实现的是匿名数据卷,无法指定宿主机路径和容器目录的挂载关系
  • 通过docker rm -fv <容器ID> 可以删除容器的同时删除VOLUME指定的卷

示例:

[root@centos7 ~]#cat Dockerfile
FROM alpine
LABEL maintainer="yuhao20080808@qq.com"
COPY repositories /etc/apk/repositories
VOLUME [ "/test1","/test2" ]

12、 EXPOSE: 暴露端口

指定服务端的容器需要对外暴露(监听)的端口号,以实现容器与外部通信。EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会真正暴露端口,即不会自动在宿主进行端口映射。因此,在启动容器时需要通过 -P 或 -p ,Docker 主机才会真正分配一个端口转发到指定暴露的端口才可使用

注意: 即使 Dockerfile 没有 EXPOSE 端口指令,也可以通过docker run -p 临时暴露容器内程序真正监听的端口,所以EXPOSE 相当于指定默认的暴露端口,可以通过docker run -P 进行真正暴露

格式:

EXPOSE <port>[/ <protocol>] [<port>[/ <protocol>] ..]

#说明
<protocol>用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议

示例:

[root@centos7 dockerfile]# echo Website in Dockerfile > index.html

[root@centos7 dockerfile]# vim Dockerfile
FROM busybox
LABEL maintainer="yuhao20080808@qq.com"
COPY index.html /data/website/
EXPOSE 80

[root@centos7 dockerfile]# docker build -t test:v1 .
[root@centos7 dockerfile]# docker run --rm -P --name test1 test:v1 /bin/httpd -f -h /data/website

13、WORKDIR: 指定工作目录

为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录,当容器运行后,进入容器内WORKDIR指定的默认目录

WORKDIR 指定工作目录(或称当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会自行创建,相当于创建一个目录然后cd到了创建的目录

格式:

WORKDIR /path/to/workdir

示例:

#如果想实现相同目录可以使用WORKDIR
WORKDIR /app
RUN echo "hello" > world.txt

可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#则最终路径为 /a/b/c

14、USER: 指定当前用户

指定运行容器的用户名或 UID,在后续dockerfile中的 RUN ,CMD和ENTRYPOINT指令时使用此用户
当服务不需要管理员权限时,可以通过该命令指定运行用户
这个用户必须是事先建立好的,否则无法切换。如果没有指定 USER,默认是 root 身份执行

格式:

USER <user>[:<group>]
USER <UID>[:<GID>]

示例:

RUN useradd loan -u 1911 
RUN groupadd -r mysql && useradd -r -g mysql mysql
USER mysql

15、HEALTHCHECK: 健康检查

检查容器的健康性

格式:

HEALTHCHECK [选项] CMD <命令> #设置检查容器健康状况的命令,如果命令执行失败,则返回1,即
unhealthy
HEALTHCHECK NONE #如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持下列选项: 
--interval=<间隔>  #两次健康检查的间隔,默认为 30 秒
--timeout=<时长>   #健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默30 秒
--retries=<次数>   #当连续失败指定次数后,则将容器状态视为 unhealthy,默认3次
--start-period=<FDURATION> #default: 0s
#检查结果返回值:
0  #success  the container is healthy and ready for use
1  #unhealthy  the container is not working correctly
2  #reserved  do not use this exit code

示例:

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/

e. Dockerfile 缓存机制

Dockerfile的缓存机制是指Docker在构建镜像时会缓存之前处理过的层(layer)以提高构建效率。具体地说,每条Dockerfile指令都会生成一个层(layer),并只有在这个层发生改变时才会重新运行该指令。因此,如果构建过程中某些指令没有改变,则Docker会直接使用之前生成的缓存层,从而加速构建过程。这种缓存机制对于频繁构建、频繁调试的场景尤其有用,它可以避免重复地执行较慢的构建命令,从而节省时间和资源。

注意:Dockerfile的缓存机制只是适用于构建过程中的单个镜像,对于多个镜像之间的依赖关系无法处理。如果多个镜像之间存在依赖关系,则需要手动保证每个镜像都重新构建以确保依赖关系正确。为了进一步减小缓存失效的影响,可以将经常变化的指令往后放置,将不变的指令往前放置,以提高重用率。

f、构建镜像docker build 命令

docker build命令使用Dockerfile文件创建镜像

命令格式:

docker build [OPTIONS] PATH | URL | -
说明: 
PATH | URL | -   #可以使是本地路径,也可以是URL路径。若设置为 - ,则从标准输入获取Dockerfile的内容

-f, --file string  #Dockerfile文件名,默认为 PATH/Dockerfile
--force-rm  #总是删除中间层容器,创建镜像失败时,删除临时容器
--no-cache  #不使用之前构建中创建的缓存
-q  --quiet=false  #不显示Dockerfile的RUN运行的输出结果
--rm=true  #创建镜像成功时,删除临时容器
-t --tag list  #设置注册名称、镜像名称、标签。格式为 <注册名称>/<镜像名称>:<标签>(标签默认为latest)

示例:

docker build .  #构建出REPOSITORY和TAG都为<none>的镜像
docker build /usr/local/src/nginx   #指定Dockerfile路径的方式
docker build -f /path/to/a/Dockerfile . # 指定Dockerfile绝对路径
docker build -t test/myapp . #默认标签为latest
docker build -t test/myapp:1.0.1 . #指定单个标签
docker build -t test/myapp:1.0.2 -t shykes/myapp:latest .  #为同一个镜像创建两个标签

g、Dockerfile 制作基于基础镜像的Base镜像

准备目录结构,下载镜像并初始化系统

#按照业务类型或系统类型等方式划分创建目录环境,方便后期镜像比较多的时候进行分类
[root@ubuntu1804 ~]#mkdir /data/dockerfile/{web/{nginx,jdk},system/{centos,ubuntu}} -p

[root@ubuntu1804 ~]#tree /data/dockerfile/
/data/dockerfile/
├── system
│  ├── centos
│  └── ubuntu
└── web
 ├── jdk
 ├── nginx

#下载基础镜像
[root@ubuntu1804 ~]#docker pull centos:centos7.9.2009

#先制作基于基础镜像的系统base镜像
[root@ubuntu1804 ~]#cd /data/dockerfile/system/centos/
[root@ubuntu1804 centos]#vim Dockerfile
FROM centos:centos7.9.2009
LABEL maintainer="yuhao20080808@qq.com"
RUN yum -y install wget && rm -f /etc/yum.repos.d/* &&  \
wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo \
&& wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo \
&& sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/Centos-7.repo \
&& yum -y install vim-enhanced tcpdump lrzsz tree telnet bash-completion net-tools wget curl bzip2 lsof zip unzip nfs-utils gcc make gcc-c++ glibc glibc-devel pcre pcre-devel openssl openssl-devel systemd-devel zlib-devel \
&& yum clean all \
&& rm -f /etc/localtime \
&& ln -s ../usr/share/zoneinfo/Asia/Shanghai /etc/localtime

[root@ubuntu1804 centos]# docker build -t centos7-base:v1 .
[root@ubuntu1804 centos]# docker images
REPOSITORY                            TAG              IMAGE ID       CREATED          SIZE
centos7-base                          v1               19deb727d5c7   21 seconds ago   433MB
centos                                centos7.9.2009   eeb6ee3f44bd   20 months ago    204MB
作者:于浩  创建时间:2023-03-19 21:15
最后编辑:于浩  更新时间:2024-05-20 16:57