0


Docker - 使用镜像


🐳 获取镜像


在 Docker 世界中,镜像是构建容器的基石。🔍 你可以想象镜像就像一个模板,我们可以用它来创建多个相似的容器。

  1. Docker Hub 宝库**:Docker Hub 上有大量的高质量的镜像,等待着你的探索和使用。
  2. 拉取命令:使用 docker pull 从 Docker 镜像仓库获取你所需的镜像。$ docker pull [选项][Docker Registry 地址[:端口号]/]仓库名[:标签] 例如:$ docker pull ubuntu:18.04

🔧 运行镜像


有了镜像后,就可以魔法一般地运行它,创建出一个真正的容器。

  1. 命令:使用 docker run 命令来运行镜像并创建容器。$ docker run -it--rm ubuntu:18.04 bash
  2. 参数解释:- -it:这是两个参数的组合。-i 代表交互式操作,-t 为我们提供一个终端。- --rm:完成后删除容器,保持清洁。- ubuntu:18.04:使用此镜像。- bash:我们想要运行的命令,这里是启动一个 bash shell。
  3. 在容器内部:一旦你进入容器,你就可以执行你需要的任何命令。例如,查看系统版本:root@e7009c6ce357:/# cat /etc/os-release
  4. 退出容器- 当你完成了容器中的工作,你可能会想退出并返回到主机。1. 退出命令:简单地在容器的终端中输入 exit。2. 记住:如果你在 docker run 命令中使用了 --rm 参数,容器将在你退出时被自动删除,保持你的工作环境整洁。

🖼️ 列出镜像


在你的 Docker 环境中,可能存在多个镜像。以下是如何列出它们:

  1. 基本命令:使用 docker image ls 来查看所有顶层镜像。$ docker image ls这将显示类似以下的输出:REPOSITORY TAG IMAGE ID CREATED SIZEredis latest 5f515359c7f8 5 days ago 183 MBnginx latest 05a60462f8ba 5 days ago 181 MBmongo 3.2 fe9198c04d62 5 days ago 342 MB...
  2. 虚悬镜像 🚫:它们没有标签和仓库名,因此可能不再需要。使用以下命令查看:$ docker image ls-fdangling=true要删除这些镜像,可以使用:$ docker image prune
  3. 中间层镜像 🍰:这些是 Docker 使用来加速构建的镜像。要看所有的,包括中间层镜像,使用:$ docker image ls-a
  4. 列出部分镜像 🎯:有时你只想查看特定的镜像。例如,要查看所有的 ubuntu 镜像,可以使用:$ docker image ls ubuntu
  5. 自定义格式 📝:如果你想以特定的格式显示镜像列表,可以使用 --format 参数。例如:$ docker image ls--format"{{.ID}}: {{.Repository}}"

📌 注意:Docker 镜像是构建和运行容器的基石。理解和管理你的镜像库是 Docker 使用中的一个关键部分。


🗑️ 删除本地镜像


当你的 Docker 环境中的镜像积累过多,或者你想释放一些磁盘空间时,你可能会想删除一些不再需要的镜像。以下是如何进行操作:

  1. 基本命令:使用 docker image rm 来删除指定的镜像。$ docker image rm<镜像名称或ID>
  2. 多种方式删除:可以使用镜像的完整 ID、短 ID、镜像名或摘要来指定要删除的镜像。例如,删除 redis:alpine 镜像:$ docker image rm redis:alpine也可以使用短ID:$ docker image rm501更精确的是使用镜像摘要:$ docker image rm node@sha256:<摘要>
  3. 注意删除的内容:删除镜像时,首先是取消该镜像的标签(Untagged),然后真实删除镜像内容(Deleted)。
  4. 容器的影响:如果一个容器基于某个镜像运行,那么该镜像在容器存在的情况下是不能被删除的。必须先删除容器,然后才能删除镜像。
  5. 结合 docker image ls 使用:要删除多个镜像,可以结合 docker image ls -q 来一次删除多个镜像。例如,删除所有名为 redis 的镜像:$ docker image rm$(docker image ls-q redis)删除在 mongo:3.2 之前的所有镜像:$ docker image rm$(docker image ls-q-fbefore=mongo:3.2)

📌 注意:删除操作是不可逆的。在删除任何镜像之前,确保你不再需要它,或者你有备份。


🎨 **利用

commit

理解镜像构成**


  • 🚀 1. 运行一个 Nginx 容器:$ docker run --name webserver -d-p80:80 nginx启动基于 nginx 镜像的 webserver 容器,并映射 80 端口。
  • 🛠️ 2. 修改容器内的文件: 进入容器并更改内容:$ dockerexec-it webserver bashroot@container_id:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • 🔍 3. 查看文件更改: 使用 docker diff 查看容器文件更改:$ dockerdiff webserver
  • 📦 4. 提交修改: 使用 docker commit 创建一个新镜像,基于原始 nginx 镜像并添加我们在容器内的更改:$ docker commit \--author"Your Name <[email protected]>"\--message"修改了默认网页"\ webserver \ nginx:v2
  • 📜 5. 查看镜像的历史记录: 用 docker history 查看新镜像的历史记录:$ dockerhistory nginx:v2

⚠️ **慎用

docker commit

**:

  • 使用 docker commit 会产生 “黑箱镜像”,维护和追溯困难。
  • 镜像的分层存储意味着使用 docker commit 会使镜像臃肿。

📖 Docker commit 命令解析**:

docker commit

允许从修改过的容器创建新镜像。

格式:

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
  • [选项]: 如 --author--message.
  • <容器ID或容器名>: 要提交的容器。
  • [<仓库名>[:<标签>]]: 新镜像名称和标签。

示例:
想保存

webserver

容器更改为

nginx_modified:v2

镜像:

$ docker commit --author "Your Name <[email protected]>" --message "修改了默认网页" webserver nginx_modified:v2

推荐做法:

  • 使用 Dockerfile 描述和构建镜像,以确保透明性和可维护性。

🤖 **使用

Dockerfile

定制镜像**


在之前的

docker commit

学习中,我们了解到镜像定制实际上是对每一层所添加的配置、文件的定制。而

Dockerfile

正是为此设计的。

Dockerfile 是一个包含用于组合镜像的指令的文本文档。每一条指令都会在镜像上添加一个新层,然后提交。


🚀 创建一个基于 nginx 的 Dockerfile

  1. 在一个空目录中创建 Dockerfile:
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
  1. Dockerfile 内容:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

📚 Dockerfile 指令

  • FROM: 指定基础镜像。每个 Dockerfile 都必须开始于此。
  • RUN: 执行命令。

🎓 更深入地了解指令

  • FROM: 您可以使用任何公开的镜像作为基础镜像,或者从零开始构建。- FROM scratch 是一个特殊的基础镜像,表示一个空白镜像。
  • RUN: 运行命令行命令。有两种格式:- Shell 格式:RUN <命令>- Exec 格式:RUN ["可执行文件", "参数1", "参数2"]

🛠️ 构建镜像

使用

docker build

命令构建一个镜像。其格式为:

docker build [选项] <上下文路径/URL/->

例如:

$ docker build -t nginx:v3 .

🌐 镜像构建上下文

  • 上下文是 docker build 命令所需的文件和目录。这些内容将被打包并发送到 Docker 守护进程以构建镜像。
  • 注意: 请确保不要包括不需要的文件到您的上下文中,因为这会使镜像变得臃肿。

🔍 其他构建方法

  1. 从 Git 仓库直接构建:
$ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
  1. 从给定的 tar 压缩包构建:
$ docker build http://server/context.tar.gz
  1. 从标准输入中读取 Dockerfile:
docker build - < Dockerfile

cat Dockerfile |docker build -
  1. 从标准输入中读取上下文压缩包:
$ docker build - < context.tar.gz

🔥 Dockerfile 指令详解


指令作用使用场景****注意事项

FROM

设置基础镜像创建新的镜像时必须是 Dockerfile 的第一条非注释指令

MAINTAINER

设置镜像的作者信息提供联系方式和作者信息已被

LABEL

替代,但仍可用

RUN

执行命令并创建新的镜像层安装软件包或执行其他配置命令应避免在一个

RUN

指令中运行多条命令,以减少镜像大小

CMD

提供容器的默认执行命令当容器启动时运行的默认命令如果 Dockerfile 中有多个

CMD

指令,只有最后一个会生效

ENTRYPOINT

配置容器启动后执行的命令当容器作为一个可执行程序可以结合

CMD

指令使用

COPY

从上下文目录中复制文件或目录到容器将本地文件复制到镜像中注意文件和目录的权限设置

ADD

从上下文目录复制文件,可以自动解压缩归档文件当需要自动解压缩归档文件时通常推荐使用

COPY

,除非需要自动解压缩功能

ENV

设置环境变量设置持久化的环境变量可以在

RUN

,

CMD

,

ENTRYPOINT

等指令中使用

ARG

设置构建参数传递给构建运行时的变量只在构建时生效,不会在容器运行时生效

VOLUME

创建挂载点并挂载文件系统当容器需要保存数据或共享数据时用于声明容器内的路径需要作为卷

EXPOSE

声明容器运行时监听的端口当容器运行服务时只是声明,不会自动在宿主机上映射端口

WORKDIR

设置工作目录改变后续指令的执行目录如果指定的目录不存在,会自动创建

USER

指定运行用户当容器的命令需要特定用户权限时用户必须在之前的层中定义,通常通过

RUN

指令与

useradd

命令

HEALTHCHECK

设置容器健康检查判断容器的状态是否正常检查命令的返回值为 0 表示健康,1 表示不健康

LABEL

为镜像添加元数据添加额外的元数据,如版本、描述等可用于存储如作者、版本、构建日期等信息的键值对

SHELL

指定

RUN

,

CMD

,

ENTRYPOINT

的 shell改变默认的

/bin/sh -c

注意替换的 shell 和其参数

ONBUILD

为子镜像提供触发指令当创建一个作为基础镜像使用的镜像时在构建基础镜像时不会执行,但在基于该基础镜像构建子镜像时会执行

☁️

FROM 指定基础镜像

🖋️ 格式:

FROM <image>[:<tag>] [AS <name>]

🚀 示例与详解:

FROM ubuntu:20.04 AS build-stage

这里使用 Ubuntu 20.04 作为基础镜像并为这个构建阶段命名为 “build-stage”。

📚 注意事项:

  1. FROM 必须是 Dockerfile 中的第一条非注释指令(除非在多阶段构建中使用了多个 FROM 指令)。
  2. 一个 Dockerfile 可以包含多个 FROM 指令,用于多阶段构建。
  3. 指定的基础镜像必须是一个已存在或可以从公有/私有仓库拉取的镜像。
  4. 如果 Dockerfile 只是基于一个已存在的镜像,而没有任何额外的定制化指令,那么这个 FROM 指令可以是 Dockerfile 中的唯一指令。

☁️

RUN 执行命令

🖋️ 格式:

RUN <command> (shell 格式)

RUN ["executable", "param1", "param2"] (exec 格式)

🚀 示例与详解:

  1. Shell 格式:
RUN apt-get update && apt-get install -y curl

这里,我们使用的是默认的 shell 格式,运行命令更新系统软件包并安装 curl。

  1. Exec 格式:
RUN ["apt-get", "update"]

在这个例子中,我们使用 exec 格式执行相同的更新操作。

📚 注意事项:

  1. RUN 指令会在新的层上执行指定的命令,并提交结果。这个提交的层会成为下一条指令的基础层。
  2. 如果可能的话,尽量将多个相关的命令组合在一个 RUN 指令中,以减少创建的层数。
  3. 清理不必要的文件和数据,例如使用 apt-get install 后进行清理,以减少镜像大小。
  4. 要注意避免在一个 RUN 指令中执行多条无关的命令,因为这会使得缓存失效,增加构建时间。
  5. 当使用 exec 格式时,不能使用 shell 的特性,例如变量替换和通配符。

📁

COPY 复制文件

🖋️ 格式:

  1. 常规格式:COPY [--chown=<user>:<group>] <源路径>... <目标路径>
  2. JSON 数组格式:COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

🚀 示例与详解:

  1. 基本复制:示例:COPY package.json /usr/src/app/详解: 这个命令将当前上下文中的 package.json 文件复制到镜像的 /usr/src/app/ 目录中。
  2. 使用通配符:示例:COPY hom* /mydir/COPY hom?.txt /mydir/详解: 第一个命令将所有以 hom 开头的文件复制到 /mydir/ 目录。第二个命令将匹配 hom 后面跟一个字符并以 .txt 结尾的文件。
  3. 指定用户和组:示例:COPY --chown=55:mygroup files* /mydir/详解: 该命令将所有以 files 开头的文件复制到 /mydir/ 并更改其所有者为用户 ID 55 和组 mygroup
  4. 源路径是目录:示例:COPY ./mydir/ /targetdir/详解: 如果 ./mydir/ 是一个目录,则 Docker 会将该目录下的内容复制到 /targetdir/,而不是整个 mydir 目录。
  5. 使用 JSON 数组格式指定路径中包含空格的文件:示例:COPY ["my file.txt", "/path with spaces/"]详解: 当文件或路径名中包含空格时,需要使用 JSON 数组格式来确保文件名被正确解析。

📚 注意事项:

  • COPY 命令使用的所有文件和目录都应位于 Docker 构建的上下文中。
  • 使用 COPY 命令时,所有文件的元数据,如权限和修改时间,都会被保留。
  • 如果目标路径不存在,Docker 会自动创建。

📁

ADD 更高级的复制文件

🖋️ 格式:

  1. 常规格式:ADD [--chown=<user>:<group>] <源路径>... <目标路径>
  2. JSON 数组格式:ADD [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

🚀 示例与详解:

  1. 从URL添加文件:示例:ADD http://example.com/big.tar.xz /usr/src/big/详解: 这个命令尝试下载 http://example.com/big.tar.xz 并解压到 /usr/src/big/。但如上所述,这种方式并不推荐,因为它会使得 Dockerfile 的透明性和可重复性降低。
  2. 自动解压缩 tar 文件:示例:ADD local-big.tar.xz /usr/src/big/详解: 如果 local-big.tar.xz 是一个本地的tar文件,Docker将会自动解压这个tar文件到镜像的 /usr/src/big/
  3. 指定用户和组:示例:ADD --chown=55:mygroup files* /mydir/详解: 该命令将所有以 files 开头的文件复制到 /mydir/ 并更改其所有者为用户 ID 55 和组 mygroup

📚 注意事项:

  • COPY 类似,ADD 命令使用的所有文件和目录都应位于 Docker 构建的上下文中。
  • 除非有明确需求,否则推荐使用 COPY 指令,因为它的语义更明确。
  • 如果你只是希望添加一个压缩文件而不想解压,那么你应该使用 COPY
  • 使用 ADD 指令时,文件的元数据,如权限和修改时间,都会被保留。
  • 如果目标路径不存在,Docker会自动创建它。

🖥️

CMD容器启动命令

🖋️ 格式:

  1. Shell 格式:CMD <命令>
  2. Exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
  3. 参数列表格式 (与 ENTRYPOINT 配合使用):CMD ["参数1", "参数2"...]

🚀 示例与详解:

  1. 默认启动命令:示例:CMD /bin/bash详解: 当容器启动时,默认执行 /bin/bash。如果用户没有提供命令,例如 docker run -it ubuntu,则容器内部会运行 /bin/bash。但用户可以覆盖默认命令,例如:docker run -it ubuntu cat /etc/os-release
  2. 使用 exec 格式:示例:CMD ["echo", "$HOME"]详解: 推荐使用此格式,因为它更明确,不会受到 shell 的影响。但在此示例中,环境变量 $HOME 不会被解析,因为它不是在 shell 中执行的。
  3. 错误的后台运行方式:示例:CMD service nginx start详解: 这是一个常见的错误。此命令会使 nginx 作为后台守护进程启动,而容器的主进程会很快结束,导致容器也跟着结束。
  4. 正确的前台运行方式:示例:CMD ["nginx", "-g", "daemon off;"]详解: 此命令会使 nginx 以前台模式运行,这是容器的正确操作方式。容器的主进程是 nginx,当 nginx 运行时,容器也会运行。

📚 注意事项:

  • CMD 指令是用于指定容器启动时的默认命令及参数的。
  • 在一个 Dockerfile 中,可以有多个 CMD 指令,但只有最后一个 CMD 会生效。
  • CMDENTRYPOINT 的组合非常有用。ENTRYPOINT 定义了容器启动时运行的命令,而 CMD 则提供了默认的参数。
  • 容器的应用应该始终以前台模式运行。后台模式是传统虚拟机或物理服务器的概念,在容器技术中不适用。
  • 如果容器的主进程结束,容器也会结束。

🖥️

ENTRYPOINT 入口点

🖋️ 格式:

  1. Shell 格式:ENTRYPOINT <命令>
  2. Exec 格式:ENTRYPOINT ["可执行文件", "参数1", "参数2"...]

🚀 示例与详解:

  1. 基础使用:示例:ENTRYPOINT ["echo", "Hello"]CMD ["world"]详解: 当容器启动时,默认执行 echo Hello world。但如果用户提供了命令参数,例如 docker run <image_name> Docker,则会执行 echo Hello Docker
  2. 结合 CMD 使用:示例:ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]详解: 使用此镜像,你可以直接查询公网 IP。如果需要额外参数(例如 -i 来显示 HTTP 头),你可以执行 docker run <image_name> -i
  3. 预处理脚本:示例:FROM alpine:3.4RUN addgroup -S redis && adduser -S -G redis redisENTRYPOINT ["docker-entrypoint.sh"]CMD [ "redis-server" ]详解: 这是 redis 官方镜像的一个简化示例。在这里,创建了一个 redis 用户,然后使用 docker-entrypoint.sh 脚本作为 ENTRYPOINT。这个脚本检查 CMD 的内容并执行适当的操作。如果 CMD 是 redis-server,脚本会切换到 redis 用户并启动服务器。否则,它会以 root 用户身份执行命令。

📚 注意事项:

  • ENTRYPOINT 指令用于指定容器启动时的默认命令。
  • ENTRYPOINTCMD 可以结合使用。当这两者都存在时,CMD 的内容会作为参数传递给 ENTRYPOINT
  • 如果需要覆盖 ENTRYPOINT,可以使用 docker run--entrypoint 参数。
  • ENTRYPOINT 通常用于指定一个固定的程序(及其参数),而 CMD 用于指定默认的参数。
  • 一些常见的用例包括使用 ENTRYPOINT 为容器提供一个主应用程序,并允许用户使用 CMD 来提供应用程序的默认参数。
  • 使用 ENTRYPOINT 的一个常见场景是容器化的应用程序需要执行一些启动前的初始化任务。这可以通过将初始化脚本设置为 ENTRYPOINT,然后在脚本的末尾执行主应用程序来实现。

🌍

ENV 设置环境变量

🖋️ 格式:

  1. 单一变量设置:ENV <key> <value>
  2. 多变量设置:ENV <key1>=<value1> <key2>=<value2>...

🚀 示例与详解:

  1. 基础使用:示例:ENV MY_VERSION 1.0详解: 通过这种方式,我们设置了一个名为 MY_VERSION 的环境变量,其值为 1.0。此后,该变量可在 Dockerfile 的后续指令或容器内部使用。
  2. 多变量设置:示例:ENV NAME="John Doe" AGE=30 COUNTRY="United States"详解: 在一个 ENV 指令中,我们可以同时设置多个环境变量。这里我们设置了 NAME, AGECOUNTRY 三个环境变量。
  3. 环境变量使用:示例:ENV APP_PATH /usr/local/appWORKDIR $APP_PATH详解: 一旦设置了环境变量,就可以在后续的 Dockerfile 指令中使用它们。在这个示例中,我们首先设置了 APP_PATH 变量,然后在 WORKDIR 指令中使用它,这样 WORKDIR 就会设置为 /usr/local/app

📚 注意事项:

  • ENV 指令不仅可以在 Dockerfile 中设置环境变量,这些变量还会被复制到最终的镜像中,因此在容器运行时也可以使用这些环境变量。
  • 使用环境变量可以提高 Dockerfile 的可读性和可维护性。例如,如果你的应用有一个版本号需要在多个地方使用,你可以将版本号设置为一个环境变量,这样当需要更新版本时,只需修改 ENV 指令即可。
  • ENV 指令在构建过程中创建的每个环境变量都会增加镜像层的数量,因此,为了减少镜像层的数量,建议在一个 ENV 指令中设置多个环境变量。
  • 当你运行一个容器时,也可以通过 -e 参数来设置新的环境变量或覆盖已有的环境变量。

🌍

ARG 构建参数

🖋️ 格式:

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

🚀 示例与详解:

  1. 基础使用:示例:ARG MY_VERSION=1.0详解: 这里定义了一个名为 MY_VERSION 的构建参数,并为其设置了默认值 1.0。在接下来的构建过程中,你可以引用这个变量。
  2. 使用构建参数:示例:ARG BASE_IMAGE_VERSION=latestFROM my-base-image:${BASE_IMAGE_VERSION}详解: 通过这种方式,你可以在构建时通过 --build-arg BASE_IMAGE_VERSION=2.0 来选择不同版本的基础镜像进行构建。
  3. 多阶段构建中的构建参数:示例:ARG DOCKER_USERNAME=libraryFROM ${DOCKER_USERNAME}/alpine AS stage1RUN echo "This is stage 1"ARG DOCKER_USERNAME=libraryFROM ${DOCKER_USERNAME}/alpine AS stage2RUN echo "This is stage 2"详解: 在多阶段构建中,每个阶段都可以有其自己的构建参数。这些参数必须在每个阶段中重新声明,以便在该阶段中使用。

📚 注意事项:

  • ARG 设置的变量仅在构建期间存在,并不会保留在最终的镜像中。这与 ENV 指令不同,后者设置的环境变量会被保留在最终的镜像中。
  • 虽然 ARG 设置的构建参数在最终的镜像中不可见,但是在构建过程中(例如 docker history 的输出中)可能仍然可以看到这些值,因此不应使用 ARG 来存储敏感信息。
  • 如果你在 FROM 指令之前定义了一个构建参数,那么这个参数只能在 FROM 指令中使用。要在后续指令中使用它,你需要在每个构建阶段重新声明它。
  • 使用构建参数可以提高 Dockerfile 的灵活性,使你能够用同一个 Dockerfile 构建不同配置或版本的镜像。

📦

VOLUME 定义匿名卷

🖋️ 格式:

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

或者

VOLUME <路径>

🚀 示例与详解:

  1. 基础使用:示例:VOLUME /app/data详解: 这里定义了一个名为 /app/data 的匿名卷。在容器运行期间,这个目录的内容将存储在 Docker 主机上的某个位置,而不是在容器的文件系统中。这有助于保持容器的无状态性。
  2. 多路径匿名卷:示例:VOLUME ["/app/data", "/app/config"]详解: 这里定义了两个匿名卷,分别为 /app/data/app/config。两者都会在容器运行时自动挂载为匿名卷。

📚 注意事项:

  • VOLUME 指令创建的卷是匿名的,这意味着它们没有一个易于识别的名称。但是,Docker 会为它们分配一个随机字符串作为名称。
  • 使用 VOLUME 为容器数据提供了一个永久存储解决方案,因为匿名卷存储在 Docker 主机上,与容器的生命周期是独立的。这意味着,即使容器被删除,卷中的数据仍然存在。
  • 尽管 VOLUME 提供了一个方便的方法来定义匿名卷,但在实践中,使用命名卷(如上面的 mydata 示例)通常更受欢迎,因为它们更容易管理和识别。
  • 如果你在 Dockerfile 中使用了 VOLUME 指令,但在运行容器时又使用 -v--volume 选项来挂载一个卷,那么运行时的设置会覆盖 Dockerfile 中的设置。

🚪

EXPOSE 暴露端口

🖋️ 格式:

EXPOSE <端口1> [<端口2>...]

🚀 示例与详解:

  1. 基础使用:示例:EXPOSE 80详解: 这里声明了容器意图使用80端口提供服务。但这仅仅是一个声明,并不意味着当容器运行时,主机会自动映射到这个端口。
  2. 声明多个端口:示例:EXPOSE 80 443详解: 这里声明了容器打算使用80端口和443端口。这可能意味着容器可能既提供HTTP服务也提供HTTPS服务。

📚 注意事项:

  • EXPOSE 只是一个元数据指令,它告诉使用镜像的人这个容器内部的应用监听的端口。但它并不会使容器的端口在主机上可访问。
  • 要使容器的端口在主机上可访问,你需要在 docker run 命令中使用 -p 参数来发布并映射一个或多个端口,或者使用 -P 参数来发布所有 EXPOSE 指令中声明的端口。
  • 即使没有在 Dockerfile 中使用 EXPOSE,你仍然可以在 docker run 命令中使用 -p 参数来发布容器的任何端口。
  • 为了容器的可读性和可维护性,建议在 Dockerfile 中为应用程序使用的每个端口都使用一个 EXPOSE 指令。

📁

WORKDIR 指定工作目录

🖋️ 格式:

WORKDIR <工作目录路径>

🚀 示例与详解:

  1. 基础使用:示例:WORKDIR /app详解: 此指令会将当前工作目录设置为容器中的 /app。如果 /app 目录不存在,Docker 将创建它。所有后续指令(如 RUN, CMD, ENTRYPOINT 等)都将在此目录中执行。
  2. 相对路径:示例:WORKDIR /aWORKDIR bRUN pwd详解:pwd 的输出将是 /a/b。这是因为第二个 WORKDIR 指令使用了相对路径,所以它是相对于前一个 WORKDIR 指令设置的路径。
  3. 链式目录:示例:WORKDIR /a/b/cRUN pwd详解:pwd 的输出将是 /a/b/c。此指令将工作目录设置为 /a/b/c,并确保这个路径存在。

📚 注意事项:

  • WORKDIR 不仅仅是为了指定 RUN, CMDENTRYPOINT 的工作目录。它也为后续的指令(如 COPYADD)提供了上下文。
  • 如果你在 Dockerfile 中多次更改工作目录,每个 WORKDIR 会为镜像添加新的层。尽管这些层很小,但最好减少不必要的层。
  • 通常建议在 Dockerfile 的开始部分设置一个工作目录,并在此目录中执行所有后续操作,以保持清晰和组织良好。
  • 不推荐使用 RUN cd /some/path,因为这样的改变只在那个特定的 RUN 指令中生效,对后续的指令没有影响。

🧑

USER 指定当前用户

🖋️ 格式:

USER <用户名>[:<用户组>]

🚀 示例与详解:

  1. 基础使用:示例:USER daemon详解: 此指令会更改所有后续指令的运行用户为 daemon。这意味着所有后续的 RUN, CMD, 和 ENTRYPOINT 指令都将以 daemon 用户的身份执行。
  2. 指定用户和用户组:示例:USER redis:redis详解: 在此示例中,所有后续指令都将以 redis 用户及其对应的 redis 用户组的身份执行。

📚 注意事项:

  • USER 指令定义的用户必须在之前的层中预先创建,否则构建将失败。例如,要使用名为 redis 的用户,你可能需要先执行如下的 RUN 指令来创建它:RUN useradd -ms /bin/bash redis
  • 在设计 Dockerfile 时,应考虑在其中运行的应用程序或服务不以 root 用户身份运行,这是为了增加容器的安全性。
  • 如果需要临时切换回 root 用户来执行某些命令(例如,安装软件包),你可以再次使用 USER 指令来切换回 root,然后再次切换到另一个用户。
  • 如果在脚本中需要切换用户,使用 gosu 是一个好选择,因为它的行为比 susudo 更容易预测,并且不需要额外的配置。

🏥

HEALTHCHECK 健康检查

🖋️ 格式:

  1. 定义检查命令:
HEALTHCHECK [选项] CMD <命令>
  1. 取消基础镜像的健康检查:
HEALTHCHECK NONE

🚀 示例与详解:

  1. 基础使用:示例:HEALTHCHECK CMD curl --fail http://localhost/ || exit 1详解: 使用 curl 命令检查本地 web 服务是否响应。如果响应失败 (curl --fail), 命令返回非零状态,这将被视为容器健康检查失败。
  2. 使用选项:示例:HEALTHCHECK --interval=5m --timeout=3s --retries=3 \CMD curl --fail http://localhost/ || exit 1详解: 每隔5分钟执行一次健康检查,如果健康检查命令超过3秒没有响应, 则视为失败。如果连续3次检查都失败, 则容器状态会变为 unhealthy

📚 注意事项:

  • 如果基础镜像定义了 HEALTHCHECK 指令,可以使用 HEALTHCHECK NONE 来取消它。
  • 健康检查命令应该尽可能地快速执行,以避免误判。
  • 健康检查命令的输出可以帮助排查健康检查失败的原因。这些输出可以使用 docker inspect 命令来查看。
  • 如果容器的主进程挂掉,容器会立即变为 unhealthy,不需要等待健康检查。
  • HEALTHCHECK 是 Docker 1.12 之后引入的功能,确保使用的 Docker 版本支持此指令。

🛠️

ONBUILD 为他人作嫁衣裳

🖋️ 格式:

ONBUILD

指令后面跟随的是另一个 Dockerfile 指令。这个指令不会在当前的镜像构建中执行,但会在使用当前镜像作为基础镜像的任何后续构建中执行。

ONBUILD <INSTRUCTION>

🚀 示例与详解:

  1. 基础镜像 Dockerfile:FROM node:slimRUN mkdir /appWORKDIR /appONBUILD COPY ./package.json /appONBUILD RUN [ "npm", "install" ]CMD [ "npm", "start" ]详解: 这是一个 Node.js 基础镜像。它创建了一个工作目录 /app 并设置了默认的启动命令。然而,与其立即执行 npm install,不如等到一个衍生镜像构建时再执行。这正是 ONBUILD 指令的功能。
  2. 衍生镜像 Dockerfile:FROM my-node详解: 当这个 Dockerfile 被用来构建一个镜像时,它实际上是基于 my-node 镜像。由于 my-node 镜像包含 ONBUILD 指令,所以当这个衍生镜像被构建时,ONBUILD 之后的指令(即 COPYRUN)会被触发。

📚 注意事项:

  • 预测性: 使用 ONBUILD 可能会导致镜像构建的不可预测性,尤其是当你不确定基础镜像中的 ONBUILD 指令是什么时。
  • 透明性: 如果你使用了一个包含 ONBUILD 指令的基础镜像,务必确保你知道这些指令的具体内容和行为。
  • 过度使用: 避免过度使用 ONBUILD。尽管它可以使某些情境变得简单,但不恰当的使用可能会带来更多的复杂性。

🏷️

LABEL 为镜像添加元数据

🖋️ 格式:

LABEL <key1>=<value1> <key2>=<value2> ...

🚀 示例与详解:

  1. 设置作者和版本信息:示例:LABEL maintainer="[email protected]" version="1.0"详解: 为镜像设置 maintainerversion 两个标签,分别表示维护者的邮箱和镜像的版本。
  2. 设置多个标签:示例:LABEL description="This is a custom image" usage="docker run <image>" version="1.0"详解: 设置了三个标签:描述、使用方法和版本。
  3. 设置开放容器标准标签:示例:LABEL org.opencontainers.image.authors="[email protected]" \ org.opencontainers.image.documentation="https://example.com/docs"详解: 使用开放容器标准的标签,表示作者和文档的地址。

📚 注意事项:

  • LABEL 是用来为镜像添加元数据的。这些元数据对构建或运行镜像没有直接影响,但可以为使用和维护镜像的人提供有用的信息。
  • LABEL 的值可以在后续的 Dockerfile 指令中使用,也可以在运行容器时使用。
  • 如果需要更新 LABEL 的值,需要重新构建镜像。
  • 使用开放容器标准的标签可以提高跨不同容器技术的互操作性。
  • LABEL 也可以在 docker build 命令中通过 --label 选项来设置。

🐚

SHELL 指令

🖋️ 格式:

SHELL ["executable", "parameters"]

🚀 示例与详解:

  1. 设置默认 shell:示例:SHELL ["/bin/sh", "-c"]详解: 这是 Dockerfile 在 Linux 中的默认 shell。它使用 /bin/sh 作为 shell,并使用 -c 选项来执行后续的命令。
  2. 修改 shell 参数:示例:SHELL ["/bin/sh", "-cex"]详解: 使用 -cex 选项,-c 是传递命令的参数,-e 是遇到错误时退出,-x 是打印出执行的每条命令。
  3. SHELL 与 RUN:示例:SHELL ["/bin/sh", "-cex"]RUN lll ; ls详解: 由于 SHELL 指令已经将 shell 设置为 /bin/sh 并添加了 -cex 参数,因此 RUN 指令在执行时会打印出每个命令,并在遇到错误时退出。
  4. SHELL 与 ENTRYPOINT 和 CMD:示例:SHELL ["/bin/sh", "-cex"]ENTRYPOINT nginxCMD nginx详解: 当 ENTRYPOINTCMD 指令以 shell 格式指定时,SHELL 指令所指定的 shell 也会成为这两个指令的 shell。这意味着 ENTRYPOINTCMD 指令都会在 /bin/sh 下执行,并使用 -cex 参数。

📚 注意事项:

  • SHELL 指令会影响后续的所有 RUNCMDENTRYPOINT 指令。
  • 如果需要在特定的 RUNCMDENTRYPOINT 指令中使用不同的 shell 或参数,你需要在该指令中明确指定。
  • 更改 SHELL 会持续影响后续的所有层,除非再次更改。
  • 在构建多阶段 Dockerfile 时,每个阶段都可以有自己的 SHELL 指令。
  • SHELL 指令主要用于当你需要使用不同的 shell(例如, /bin/bash)或默认 shell 的不同参数时。

🌐参考文档

  1. Dockerfile 官方文档:- 链接: https://docs.docker.com/engine/reference/builder/- 描述: 这是 Docker 的官方文档,详细描述了 Dockerfile 的各种指令和其用法。对于希望深入了解 Dockerfile 的读者来说,这是一个必读的资源。
  2. Dockerfile 最佳实践文档:- 链接: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/- 描述: 这份文档提供了关于如何编写高质量 Dockerfile 的最佳实践。它涵盖了一系列的推荐做法,帮助开发者避免常见的错误和陷阱。
  3. Docker 官方镜像 Dockerfile:- 链接: https://github.com/docker-library/docs- 描述: 这是 Docker 官方镜像的 GitHub 仓库,其中包含了许多流行软件的 Dockerfile。对于希望学习如何为特定软件编写 Dockerfile 的读者来说,这是一个很好的参考资源。

📊 Dockerfile 多阶段构建

在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式:

全部

分散
  1. 全部放入一个 Dockerfile 📄- 优点: 所有的构建过程都包含在一个 Dockerfile 中。- 缺点: - 镜像层次多,镜像体积较大,部署时间变长 🐢- 源代码存在泄露的风险 🔓# Dockerfile.one 文件FROM golang:alpineRUN apk --no-cache add git ca-certificatesWORKDIR /go/src/github.com/go/helloworld/COPY app.go .RUN go get -d -v github.com/go-sql-driver/mysql \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \ && cp /go/src/github.com/go/helloworld/app /root WORKDIR /root/CMD ["./app"]构建镜像:$ docker build -t go/helloworld:1 -f Dockerfile.one .
  2. 分散到多个 Dockerfile 📂- 优点: 可以很好地规避第一种方式存在的风险。- 缺点: 明显部署过程较复杂 🛠# Dockerfile.build 文件FROM golang:alpineRUN apk --no-cache add gitWORKDIR /go/src/github.com/go/helloworldCOPY app.go .RUN go get -d -v github.com/go-sql-driver/mysql \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .``````# Dockerfile.copy 文件FROM alpine:latestRUN apk --no-cache add ca-certificatesWORKDIR /root/COPY app .CMD ["./app"]构建脚本:# build.sh 文件#!/bin/shecho Building go/helloworld:builddocker build -t go/helloworld:build .-f Dockerfile.builddocker create --name extract go/helloworld:builddockercp extract:/go/src/github.com/go/helloworld/app ./appdockerrm-f extractecho Building go/helloworld:2docker build --no-cache -t go/helloworld:2 .-f Dockerfile.copyrm ./app运行脚本:$ chmod +x build.sh$ ./build.sh对比两种方式生成的镜像大小:$ docker image ls📌 结果:REPOSITORY TAG IMAGE ID CREATED SIZEgo/helloworld 2 f7cf3465432c 22 seconds ago 6.47MBgo/helloworld 1 f55d3e16affc 2 minutes ago 295MB
  3. 使用多阶段构建 🚀- 优点: 体积小且避免了上述的问题。# Dockerfile 文件FROM golang:alpine as builderRUN apk --no-cache add gitWORKDIR /go/src/github.com/go/helloworld/RUN go get -d -v github.com/go-sql-driver/mysqlCOPY app.go .RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .FROM alpine:latest as prodRUN apk --no-cache add ca-certificatesWORKDIR /root/COPY --from=0 /go/src/github.com/go/helloworld/app .CMD ["./app"]...构建镜像:$ docker build -t go/helloworld:3 .对比三个镜像大小:$ docker image ls📌 结果:REPOSITORY TAG IMAGE ID CREATED SIZEgo/helloworld 3 d6911ed9c846 7 seconds ago 6.47MBgo/helloworld 2 f7cf3465432c 22 seconds ago 6.47MBgo/helloworld 1 f55d3e16affc 2 minutes ago 295MB
  4. 只构建某一阶段的镜像 🎯使用 as 来为某一阶段命名:FROM golang:alpine as builder构建指定阶段的镜像:$ docker build --target builder -t username/imagename:tag .
  5. 构建时从其他镜像复制文件 🔄可以复制任意镜像中的文件:COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

📝 总结: 多阶段构建为我们提供了一个优雅且高效的方式来构建和部署 Docker 镜像。


⚙️ 构建多种系统架构支持的 Docker 镜像


在构建多种系统架构支持的 Docker 镜像时,我们可以使用 Docker Manifest 来管理不同架构的镜像,并使 Docker 引擎能够自动选择合适的镜像。

以下是完整的步骤和示例:

  1. 🏗️ 构建镜像首先,在不同的系统架构下构建不同的镜像。例如,在 Linux x86_64 架构下构建 username/x8664-test 镜像,以及在 Linux arm64v8 架构下构建 username/arm64v8-test 镜像。构建并推送这些镜像到 Docker Hub。
  2. 📋 创建 manifest 列表使用 docker manifest create 命令创建一个 manifest 列表,将不同架构的镜像关联起来。在这个示例中,我们将创建一个 username/test 的 manifest 列表,关联了 username/x8664-testusername/arm64v8-test 镜像。$ docker manifest create username/test \ username/x8664-test \ username/arm64v8-test
  3. 📝 设置 manifest 列表使用 docker manifest annotate 命令设置 manifest 列表的属性,包括操作系统和架构。对于不同的镜像,需要分别设置其属性。$ docker manifest annotate username/test \ username/x8664-test \--os linux --arch x86_64``````$ docker manifest annotate username/test \ username/arm64v8-test \--os linux --arch arm64 --variant v8
  4. 👀 查看 manifest 列表使用 docker manifest inspect 命令来查看 manifest 列表的配置和信息。$ docker manifest inspect username/test
  5. 🚀 推送 manifest 列表最后,将 manifest 列表推送到 Docker Hub,以便 Docker 引擎可以自动根据系统架构选择合适的镜像。$ docker manifest push username/test
  6. 🧪 测试现在,在不同的系统架构下运行 docker run 命令来使用 username/test 镜像,Docker 引擎会自动选择合适的镜像运行。$ docker run -it--rm username/test这样,你可以在不同的系统架构上轻松使用相同的 manifest 列表来管理多种镜像,而无需手动区分不同的镜像名称。

🔥官方博客: 详细了解 manifest 可以阅读 Docker 官方博客 获取更多信息。


🔄 其它制作镜像的方式


当你使用 Docker 构建和管理镜像时,有多种方法可供选择,以满足特殊需求和历史原因。除了标准的 Dockerfile 构建方法外,以下是其他两种方法:

📦 从 rootfs 压缩包导入

使用

docker import

命令,你可以从 rootfs 压缩包导入镜像。这个压缩包可以是本地文件、远程 Web 文件,甚至可以从标准输入中获取。压缩包将在镜像的根目录中展开,并直接成为镜像的第一层。

例如,如果我们想要创建一个名为

openvz/ubuntu:16.04

的 Ubuntu 16.04 镜像,可以使用以下命令:

$ dockerimport\
    http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz \
    openvz/ubuntu:16.04

这条命令会自动下载

ubuntu-16.04-x86_64.tar.gz

文件,然后将其解压并导入为名为

openvz/ubuntu:16.04

的镜像。

📤 Docker 镜像的导入和导出

Docker 还提供了

docker save

docker load

命令,用于将镜像保存为归档文件,然后在另一个位置加载。尽管这是在没有 Docker Registry 时的一种做法,但不再被推荐使用。现在,镜像迁移应该直接使用 Docker Registry,无论是使用 Docker Hub 还是内部私有 Registry。

保存镜像

使用

docker save

命令可以将镜像保存为归档文件。例如,如果要保存名为

alpine

的镜像:

$ docker image ls alpine
$ docker save alpine -o filename
filename

可以是任意文件名,但其实质是一个归档文件。如果希望使用 gzip 压缩,可以使用以下命令:

$ docker save alpine |gzip> alpine-latest.tar.gz

加载镜像

要加载已保存的镜像,可以使用

docker load

命令。例如:

$ docker load -i alpine-latest.tar.gz

这会将镜像加载回 Docker 中。如果要从一个机器迁移到另一个机器,还可以使用 SSH 结合

docker save

docker load

命令,甚至可以使用工具如

pv

来显示进度条。

这些方法可以用于特殊情况下的镜像导入和导出,但在实际生产环境中,通常建议使用 Docker Registry 进行镜像的管理和迁移。 🚢


标签: docker 容器 运维

本文转载自: https://blog.csdn.net/yueerba126/article/details/133747498
版权归原作者 yueerba126 所有, 如有侵权,请联系我们删除。

“Docker - 使用镜像”的评论:

还没有评论