目录
Docker镜像概述
- 镜像是Docker容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。
- 镜像是一种轻量级、可执行的独立软件包,包含运行某个软件所需的所有内容,一个可交付的运行环境(包括代码、运行时所需的库、环境变量和配置文件等)
base镜像
- 不依赖其他镜像,从scratch构建(指完全从零开始创建镜像,不依赖任何现有操作系统层或工具链);比如各种Linux发行版本的Docker镜像,CentOS、Ubuntu等
- 其他镜像可以已之为基础进行扩展
从 scratch 构建镜像是对 Docker 镜像分层的极致利用,通过剥离冗余内容实现"仅需即所得"。但需权衡开发调试的便利性,适合对安全、体积敏感且技术栈可控的场景。
与常规基础镜像的区别
对比项 | scratch | Alpine/Centos |
---|---|---|
文件系统 | 完全空白 | 包含最小化的 Linux 发行版 rootfs |
镜像体积 | 仅包含用户添加的文件(可低至几 KB) | 通常 5MB(Alpine)至 200MB(CentOS) |
适用场景 | 静态编译的单一程序 | 需要系统工具或动态库支持的复杂应用 |
镜像的分层结构
Docker支持通过扩展现有镜像,创建新的镜像。实际上,Docker Hub中99%的镜像都是通过在base镜像中安装和配置需要的软件构建出来的。新镜像是从base镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
- Docker镜像实际上由一层层的文件系统组成,这种层级的文件系统就是 UnionFS
- bootfs(boot file system) 主要包括 bootloader 和 kernel,bootloader 主要是引导加载 kernel,Docker镜像的最底层就是引导文件 bootfs。
- rootfs(root file system),在bootfs之上,就是 Linux 系统的 /dev、/proc、/etc 等标准目录和文件。rootfs 就是各种不同操作系统发行版,如:ubuntu、centos 等。
为什么使用这种分层结构?
最大好处就是共享资源,方便复制迁移
镜像的理解
- 当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称为容器层,容器层之下的都叫镜像层。
- 容器的Copy-on-Write特性,使得对文件的修改会被限制在单个容器内。
- 所有对容器的改动,无论添加、删除,还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
镜像的构建
docker commit 制作镜像
docker commit命令是创建新镜像最直观的方法,其过程包含三个步骤:
- 运行容器
- 修改容器
- 将容器保存为新的镜像
powershell
docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:TAG
Docker并不建议用户通过这种方式构建镜像。原因如下:
- 手动创建,容易出错,效率低且可重复性弱
- 使用者并不知道镜像是如何创建出来的,里面是否有恶意程序,无法对镜像进行审计,存在安全隐患。
Dockerfile
Dockerfile是一个文本文件,记录了镜像构建的所有步骤,类似于 Linux 中的 Shell 脚本文件。推荐使用这种方式构建镜像,其实它的底层也是docker commit一层一层构建新镜像的。
powershell
docker build --no-cache --force-rm -t 镜像名称:TAG .
# --no-cache : 表示构建的时候不使用之前的缓存
# --force-rm:删除构建过程中的中间容器层
# .:表示构建环境的上下文,通常而言是 . ,表示以 Dockerfile 所在的目录作为构建的起点
- 每条保留字指令都必须为大写字母,且后面跟至少一个参数
- 指令从上到下依次执行
- #表示注释
- 每条指令都会创建一个新的镜像层并对镜像进行提交
Docker 执行 Dockerfile 的大致流程:
- 从基础镜像上运行一个容器
- 执行一条指令并对容器进行修改
- 执行类似 docker commit 的操作提交一个新的镜像层
- Docker 再基于刚才提交的镜像运行一个新的容器
- 依次类推,直到 Dockerfile 文件中的指令全部执行完成
Dockerfile 指令
FROM
指定基础镜像。eg:如果是 JS 模块化应用,可以选择 nodejs 基础镜像;FROM nodejs:18.20.1
LABEL
用来标注镜像的一些说明信息,常常用来指定维护者的信息。eg: LABEL maintainer=xxx
RUN
- 在当前镜像层顶部的新层执行任何命令,并提交结果,生成新的镜像层(多个 RUN 指令都是在同一个目录操作的)
- 生成的提交镜像将用于 Dockerfile 中的下一步,分层运行 RUN 指令并生成提交符合 Docker 的核心概念
- 由于 [] 不是 shell 形式,所以不能输出变量信息,而是输出 $msg 。其他任何 /bin/sh -c 的形式都可以输出变量信息
构建期是指使用 Dockerfile 构建镜像的整个过程时期,如:docker build 等。
运行期是指使用之前构建的镜像启动容器的过程,如:docker start 、docker run 等。
powershell
# shell 形式,/bin/bash -c 的方式运行,可以破坏 shell 字符串 eg:RUN /bin/sh -c 'echo $msg'
RUN <command>
# exec 的形式 eg:RUN ["/bin/sh","-c","echo $msg"]
RUN ["executable", "param1", "param2"]
# 在 RUN 中可以使用 \ 将一条 RUN 指令继续到下一行。
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
# 等同于
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
ARG
- 定义一个变量,可在构建时使用 --build-arg name=value 传递,docker build 命令会将其传递给构建器
- --build-arg 指定参数会覆盖 Dockerfile 中指定的同名参数
- 如果用户指定了 未在 Dockerfile 中定义的构建参数 ,则构建会输出 警告
- ARG 只在构建时期有效,运行时期无效
- 不建议使用构建时变量来传递注入 github 密码、用户凭据等机密,因为构建时变量的值可以通过 docker history 来观察到
- ARG 变量定义从 Dockerfile 定义的行开始生效
- 使用 ENV 指定定义的环境变量始终会覆盖同名的 ARG 指令
powershell
# 语法
ARG name=defaultValue
ARG param="Hi Docker"
RUN echo ${param}
ENV
- 跟 ARG 类似,但是 ENV 在构建期和运行期都有效,并且使用 ENV 指定定义的环境变量始终会覆盖同名的 ARG 指令
- 可以使用 docker run -e name=value 修改 ENV 定义的环境变量
- ENV 在构建期就会被解析并持久化,可以通过 docker inspect image 查看
powershell
# 语法
ENV name=value
ENV app=taobao
ADD
- 将上下文指定的内容添加(复制)到镜像中,如果是压缩包,会自动解压;如果是远程 URL,会自动下载;但是 ADD 并没有自动下载远程压缩文件并解压的功能
src 路径必须在构建的上下文,不能使用 .../.../xxx 这种方式,因为 Docker 构建的第一步是将上下文目录(包括子目录)发送给 Docker 的守护进程
如果 src 是 URL ,并且 dest 不以 / 结尾,那么就会从 URL 下载文件并将其复制为 dest(名称)
如果 src 是 URL ,并且 dest 以 / 结尾,会自动推断出文件的名称(URL 的最后一部分)并保存到 dest(目录)中
如果 src 是目录,则将复制目录的整个内容,包括文件系统元数据
powershell
# 语法
ADD src dest
ADD https://download.redis.io/releases/redis-6.2.6.tar.gz /dest
ADD redis-6.2.6.tar.gz /dest/
COPY
- COPY 和 ADD 类似,但是,没有像 ADD 的自动下载和解压压缩文件的功能
- --chown 功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,而在 Windows 容器上不起作用
powershell
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
WORKDIR
- 为 Dockerfile 中跟随它后面的 RUN、CMD、ENTRYPOINT、COPY、ADD 指令设置工作目录
- 可在 Dockerfile 中多次使用。如果提供了相对路径,则它将相对于上一个 WORKDIR 指令的路径
powershell
WORKDIR /a/b/c
# 路径 /a/b/c
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 路径 /path/$DIRNAME
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
USER
- USER 指令和 WORKDIR 指令类似,都是改变环境状态并影响以后的层,WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN 、CMD 、以及 ENTRYPOINT 这类命令的身份
- USER 只是帮助我们切换到指定的用户而已,这个用户必须事先建立好的,否则无法切换
powershell
USER <user>[:<group>]
USER <UID>[:<GID>]
USER test:test
VOLUME
- 挂载 容器指定的文件夹,如果不存在,会自动创建
- 指定了 VOLUME 指令后,即使启动容器的时候没有指定 -v 参数,也会自动进行匿名卷挂载
- 用 VOLUME 声明了卷,那么以后对于卷内容的修改会被丢弃,所以,一定要在 volume 声明之前修改内容
powershell
# JSON数组形式
VOLUME ["/var/log/"]
# 直接写
VOLUME /var/log
# 空格分割多个
VOLUME /var/log /var/db
EXPOSE
- 通知 Docker 容器在运行的时候在指定的网络端口上进行侦听,可以指定端口是侦听 TCP 还是 UDP ,如果没有指定,默认就是 TCP
- 实际上不会发布端口,它充当了构建镜像人员和运行容器人员之间的一种文档,即打算发布那些端口的信息,要在运行容器时映射端口,需要使用 docker run -p xxx:xxx 或 docker run -P 的命令。
powershell
EXPOSE <port> [<port>/<protocol>...]
EXPOSE [80,443]
EXPOSE 80/tcp
CMD 和 ENTRYPOINT
- Dockerfile 文件中,使用多个 CMD 或 ENTRYPOINT 作为唯一的入口,即写多个 CMD 或 ENTRYPOINT ,则会产生覆盖现象,只有最后一个生效
- shell 方式是可以读取环境变量的值的(如:${xxx}),默认情况下,exec 的方式是读取不了环境变量值的,但是 exec 方式的 ["/bin/sh","-c","xxx"] 等同于 shell 方式,也是可以读取环境变量值
- 推荐使用 RUN 、CMD 以及 ENTRYPOINT 使用 exec 的方式
- 既有 CMD 的 exec 方式,又有 ENTRYPOINT 的 exec 方式,那么 CMD 是作为 ENTRYPOINT 的参数的
- 使用 docker run -d xxx CMD 命令是可以覆盖 Dockerfile 中的 CMD 指令的,不是覆盖 exec 方式数组中的一个,而是全部
如果docker run指定了其他命令,CMD指定的默认命令将被忽略
ENTRYPOINT看上去与CMD很像,它们都可以指定要执行的命令及其参数。不同的地方在于ENTRYPOINT不会被忽略,一定会被执行,即使运行docker run时指定了其他命令
powershell
# exec 方式, 首选方式
CMD ["executable","param1","param2"]
# shell 形式
CMD command param1 param2
# exec 方式, 首选方式
ENTRYPOINT ["executable", "param1", "param2"]
# shell 形式
ENTRYPOINT command param1 param2
# docker run -it [image] 启动时 输出: Hello world
# docker run -it [image] JS 启动时 输出: Hello JS
ENTRYPOINT ["/bin/echo", "Hello"] CMD ["world"]