Docker数据管理与Dockerfile镜像创建

前言

在容器化环境中,如何有效地管理和持久化数据成为了开发人员和运维团队面临的挑战之一;另一方面,镜像的创建是构建容器化应用的基础。优化的镜像设计可以提高部署效率和应用性能,减少资源消耗和运行成本。本文将介绍 Docker 中的数据管理策略,包括数据卷、数据卷容器、绑定挂载、持久化存储等技术;以及 Docker 镜像的构建过程,包括 Dockerfile 的编写、镜像层的管理和多阶段构建等技术。

目录

一、Docker 的数据管理

1. 数据卷

1.1 概述

1.2 创建数据卷

2. 数据卷容器

2.1 概述

2.2 创建数据卷容器

3. 容器互联(使用centos镜像)

二、Docker 镜像的创建

1. 基于现有镜像创建 

2. 基于本地模板创建

3. 基于 Dockerfile 创建

3.1 联合文件系统(UnionFS)

3.2 镜像的分层

3.3 镜像加载原理 

3.4 Dockerfile 概述

3.5 Dockerfile 操作常用的指令

3.5.1 FROM 镜像

3.5.2 MAINTAINER 名字

3.5.3 RUN 命令

3.5.4 ENTRYPOINT ["要运行的程序", "参数 1", "参数 2"]

3.5.5 CMD ["要运行的程序", "参数1", "参数2"] 

3.5.6 EXPOSE 端口号

3.5.7 ENV 环境变量变量值

3.5.8 ADD  源文件/目录  目标文件/目录

3.5.9 COPY  源文件/目录  目标文件/目录

3.5.10 VOLUME [“目录”]

3.5.11 USER 用户名/UID 

3.5.12 WORKDIR 路径

3.5.13 ONBUILD 命令

三、Dockerfile 案例

1. 建立 Dockerfile 遵循格式

2. 制作 apache 镜像

3. 制作 SSH 镜像

4. 制作 Systemctl 镜像

5. 制作 nginx 镜像

6. 制作 tomcat 镜像 

7. 制作 mysql 镜像

四、总结


一、Docker 的数据管理

管理 Docker 容器中数据主要有两种方式:数据卷(Data Volumes)和数据卷容器(DataVolumes Containers)。

1. 数据卷

1.1 概述

数据卷是 Docker 中用于持久化存储数据的一种机制,它可以在容器之间共享和重用数据。数据卷独立于容器的生命周期,即使容器被删除,数据卷中的数据仍然保留。数据卷可以用于存储应用程序数据、配置文件、日志等信息。

数据卷是一个供容器使用的特殊目录,数据卷实际上并不直接存储在容器内部,而是存储在宿主机的文件系统上。可将宿主机的目录挂载到数据卷上,对数据卷的修改操作立刻可见,并且更新数据不会影响镜像,从而实现数据在宿主机与容器之间的迁移。数据卷的使用类似于 Linux 下对目录进行的 mount 操作。

1.2 创建数据卷

[root@localhost ~]# docker run -v /var/www:/data1 --name a1 -it centos:7 /bin/bash
# 将宿主目录/var/www挂载到容器中的/data1,如果/var/www不存在可以自动创建
# -v 代表在容器内创建数据卷
[root@3fdee923a6c6 /]# echo web1 > /data1/1.txt
[root@3fdee923a6c6 /]# exit      # 返回宿主机进行查看
exit
[root@localhost ~]# cat /var/www/1.txt 
web1当容器a1退出后,根据路径/var/www新建容器,查看数据持久性:
[root@localhost ~]# docker run -v /var/www:/data2 --name b1 -it centos:7 /bin/bash
[root@6c974fc26dcd /]# cat data2/1.txt 
web1
# 数据卷是通过路径/var/www与容器关联的,因此,即使容器名称不同,只要挂载了宿主机同一个路径,那么数据卷就是相同的,数据也是共享的

2. 数据卷容器

2.1 概述

如果需要在容器之间共享一些数据,最简单的方法就是使用数据卷容器。数据卷容器是一个普通的容器,用于存储和管理数据卷,专门提供数据卷给其他容器挂载使用。通过创建一个数据卷容器,可以方便地将数据卷与其他容器共享,实现数据的集中管理和共享。

2.2 创建数据卷容器

[root@localhost ~]# docker run --name a2 -v /data1 -v /data2 -it centos:7 /bin/bash
# 启动一个名为a2的CentOS 7容器,并将主机上的/data1和/data2目录分别挂载到容器中
[root@2e82ed78cf61 /]# echo 111 > /data1/1.txt
[root@2e82ed78cf61 /]# echo 222 > /data2/2.txt
[root@2e82ed78cf61 /]# exit
exit
[root@localhost ~]# docker run -it --volumes-from a2 --name a3 centos:7 /bin/bash
# 使用--volumes-from来挂载a2容器中的数据卷到新的容器
[root@ecbac00e3384 /]# cat /data1/1.txt 
111
[root@ecbac00e3384 /]# cat /data2/2.txt 
222

3. 容器互联(使用centos镜像)

容器互联是一种在 Docker 中连接多个容器并实现它们之间通信的方法。通过容器互联,您可以轻松地创建一个网络,使得不同容器之间可以相互通信,共享数据和服务。即在源容器和接收容器之间建立一条隧道,接收容器可以看到源容器指定的信息。

[root@localhost ~]# docker run -itd -P --name web1 centos:7 /bin/bash
04dcedb3a41a52924363c094d1538210ea4fb516ca0b45fb0b553f74ec35b6ef
# 创建并运行源容器取名 web1
[root@localhost ~]# docker run -itd -P --name web2 --link web1:web111 centos:7 /bin/bash
d6883a2a4b8d30d11bd15c7486a96d0bb84f7460f3e3af773a7bd5e9492e4559
# 创建并运行接收容器取名web2,使用--link选项指定连接容器以实现容器互联
# --link容器名:连接的别名
[root@localhost ~]# docker exec -it web2 /bin/bash
[root@d6883a2a4b8d /]# ping web1
PING web111 (172.17.0.2) 56(84) bytes of data.
64 bytes from web111 (172.17.0.2): icmp_seq=1 ttl=64 time=0.490 ms
64 bytes from web111 (172.17.0.2): icmp_seq=2 ttl=64 time=0.150 ms
64 bytes from web111 (172.17.0.2): icmp_seq=3 ttl=64 time=0.191 ms
[root@d6883a2a4b8d /]# ping a1  
ping: a1: Name or service not known
[root@d6883a2a4b8d /]# ping a2
ping: a2: Name or service not known

即使其他容器处于 up 状态,只有建立了安全通道容器间才可以进行通讯。

二、Docker 镜像的创建

创建镜像有三种方法,分别为基于已有镜像创建、基于本地模板创建以及基于 Dockerfile 创建。

1. 基于现有镜像创建 

① 首先创建一个镜像,在容器里做修改

[root@localhost ~]# docker create -it centos:7 /bin/bash
3904598fe8087a267d8cb33a6dde41ea84fb6198b35c1483fc89ed939859ab24
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE      COMMAND       CREATED          STATUS    PORTS     NAMES
3904598fe808   centos:7   "/bin/bash"   10 seconds ago   Created             heuristic_leavitt

② 然后将修改后的容器提交为新的镜像,需要使用该容器的 ID 号创建新镜像

[root@localhost ~]# docker commit -m "newimage" -a "user" 3904598fe808 centos:new7
sha256:a1aae83766085d70ee4b2f2e5007fbf142426bc805a5dc80b0100c163828bf4c
# -m 说明信息
# -a 作者信息
# -p 生成过程中停止容器的运行
[root@localhost ~]# docker images | grep new7
centos               new7      a1aae8376608   32 seconds ago   204MB

③ 也可以使用 export 导出(迁移)、导入

导出:
docker export 容器id/名称 > 文件名
示例:
docker export id > centos7.tar导入:
cat 文件名 | docker import - 镜像名称:标签
示例:
cat centos7.tar | docker import - centos:new7
注意:导入会生成镜像,不会创建容器

2. 基于本地模板创建

① 通过导入操作系统模板文件可以生成镜像,模板可以从 OPENVZ 开源项目下载,下载地址为http://openvz.org/Download/template/precreated

[root@localhost opt]# wget http://download.openvz.org/template/precreated/debian-7.0-x86-minimal.tar.gz
[root@localhost opt]# ls
debian-7.0-x86-minimal.tar.gz

② 导入为镜像

[root@localhost opt]# cat debian-7.0-x86-minimal.tar.gz | docker import - debian:test
sha256:deaaddd9c659118872a8d29438258a680bf1f5907169960cf3fb3d0d52e30e03
[root@localhost opt]# docker images
REPOSITORY           TAG       IMAGE ID       CREATED          SIZE
debian               test      deaaddd9c659   33 seconds ago   215MB

3. 基于 Dockerfile 创建

3.1 联合文件系统(UnionFS)

联合文件系统(UnionFS)是一种文件系统技术,允许将多个不同的文件系统(类型)合并挂载为一个单一的文件系统。在容器技术中,UnionFS 用于构建容器镜像和实现容器的分层文件系统。

在 Docker 中,镜像是通过一层一层的文件系统(UnionFS)构建而成的。当下载 Docker 镜像时,实际上是在下载这些文件系统的不同层。每一层都包含了文件和目录的变化,使得镜像可以被构建和管理。实际上是在下载这些不同层的文件系统。如果之前已经下载过相同的基础镜像,那么只需要下载新的层,而不需要重新下载整个镜像。这种分层的设计使得镜像的构建、共享和更新变得高效和灵活。

具体来说,每个 Docker 镜像都由多个只读层(read-only layers)组成,其中最底层是基础镜像(Base Image),而其他层则包含了对基础镜像的修改。这些层按照顺序叠加在一起,形成一个联合的文件系统,最终呈现给用户的是一个完整的镜像。

3.2 镜像的分层

镜像不是一个单一的文件,而是有多层构成。容器其实是在镜像的最上面加了一层读写层,在运行容器里做的任何文件改动,都会写到这个读写层。如果删除了容器,也就删除了其最上面的读写层,文件改动也就丢失了。Docker 使用存储驱动管理镜像每层内容及可读写层的容器层。

  • Dockerfile 中的每个指令都会创建一个新的镜像层
  • 镜像层将被缓存和复用
  • 当Dockerfile 的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
  • 某一层的镜像缓存失效,它之后的镜像层缓存都会失效
  • 镜像层是不可变的,如果在某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件,只是这个文件在 Docker 容器中不可见了

① Kernel(内核层)

是操作系统的核心组件,负责管理硬件资源、提供系统服务,并为应用程序提供运行环境。在Docker场景下,提及 Kernel 通常是指 Docker 宿主机的 Kernel,而不是镜像内部的 Kernel;

② Base Image(基础镜像层)

是构建其他 Docker 镜像的基础。它通常是最底层的镜像,包含一个精简的操作系统环境(如Alpine Linux、Ubuntu、CentOS等)以及必要的系统工具;

③ Image(只读镜像层)

是 Docker 中用于创建容器的模板,它是由一系列分层组成的。每个镜像层代表了对前一层的增量更改,如安装一个软件包、添加文件或修改配置。镜像层是只读的,且每一层都有一个唯一的标识符;

④ Container(容器层)

是基于镜像实例化的、轻量级的、可执行的软件单元。容器包含了运行特定应用所需的所有依赖(代码、运行时、库、环境变量等),并利用Linux内核的隔离机制与其他容器及宿主机隔离。每个容器都从其对应的镜像顶部的读写层(也称为“容器层”或“Overlay层”)开始,在此之上进行运行时的写入操作;

⑤ Worke(工作节点)

在需要修改文件时创建文件的副本,而不是直接修改原始文件。在容器中,这意味着当容器试图修改镜像中的文件时,文件系统并不直接修改原始文件,而是在需要时将原始文件复制到新的位置,然后对副本进行修改。确保原始镜像层的完整性,同时允许容器在自己的文件系统视图中进行修改,而不会影响其他容器或原始镜像。

⑥ Merge(合并视图层)

对 Docker 镜像信息进行抽象、组织并展示给用户的逻辑层面;实际上是软件为用户提供的一种逻辑抽象和交互界面,用于展示和操作 Docker 镜像的相关信息。由于它是一个逻辑概念,且通过用户接口隐藏了底层细节,所以被称为“看不见的”。

3.3 镜像加载原理 

其实就是当容器启动时,镜像的各个层通过联合文件系统技术被叠加到一起,形成一个单独的文件系统视图。

① 分层结构:Docker镜像采用分层结构,每一层包含了文件系统的一部分和相关设置。这些层相互叠加,形成完整的镜像。

② 联合文件系统:Docker使用联合文件系统(UnionFS)技术将这些分层的只读文件系统叠加在一起,形成一个虚拟的文件系统视图。

③ 镜像加载:当容器被创建时,Docker会根据镜像的定义,将这些层加载到内存中,并创建一个可写的容器层,以便容器内的应用程序可以对其进行修改而不影响原始镜像。

④ 写时复制:对于容器内的文件修改,Docker使用写时复制(Copy-on-Write)技术,即只有在需要修改文件时才会复制底层数据,确保原始镜像层的完整性。

⑤ 一旦镜像的各个层被加载并合并到一起,容器运行时负责启动容器进程,并提供隔离的运行环境,使得应用程序能够在其中独立运行。

可以理解成一开始内核里什么都没有,操作一个命令下载 centos7,这时就会在内核上面加了一层基础镜像;再安装一个 nginx,会在基础镜像上叠加一层 image;接着再安装一个 tomcat,又会在 image 上面再叠加一层 image。最后它们看起来就像一个文件系统即容器的 rootfs(是容器启动时所使用的根文件系统。容器的文件系统是从镜像的 rootfs 开始构建的,然后通过联合文件系统等技术叠加其他层来形成完整的容器文件系统视图。)。在 Docker的体系里把这些 rootfs 叫做 Docker 的镜像。但是,此时的每一层 rootfs 都是 read-only 的,此时还不能对其进行操作。当创建一个容器,也就是将 Docker 镜像进行实例化(指根据某个模板或类来创建一个具体的实例或对象的过程),系统会在一层或是多层 read-only的 rootfs 之上分配一层空的 read-write 的 rootfs。 

3.4 Dockerfile 概述

Dockerfile 是一个文本文件,包含了一系列用于构建 Docker 镜像的指令和配置。通过编写 Dockerfile,可以将应用程序、依赖项和配置打包到一个镜像中,实现应用程序的快速部署和移植。可以使用 docker build 命令根据该文件构建出一个镜像,然后使用该镜像创建和运行容器。

  • 镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
  • Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。有了Dockerfile,当我们需要定制自己额外的需求时,只需在Dockerfile上添加或者修改指令,重新生成 image 即可, 省去了敲命令的麻烦。
  • 除了手动生成Docker镜像之外,可以使用Dockerfile自动生成镜像。Dockerfile是由多条的指令组成的文件,其中每条指令对应 Linux 中的一条命令,Docker 程序将读取Dockerfile 中的指令生成指定镜像。
  • Dockerfile结构大致分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。Dockerfile每行支持一条指令,每条指令可携带多个参数,支持使用以“#“号开头的注释。

3.5 Dockerfile 操作常用的指令

Dockerfile 中的指令可以定义基础镜像、安装软件、复制文件、设置工作目录、配置容器启动命令、声明端口、设置环境变量、挂载点和元数据等。

3.5.1 FROM 镜像

第一条指令必须为 FROM 指令。并且,如果在同一个 Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。

格式:
FROM  <image>  或  FROM  <image>:<tag>
示例:
FROM  centos:7
3.5.2 MAINTAINER 名字

说明新镜像的维护人信息。

格式:
MAINTAINER	<name>	
示例:
MAINTAINER  this  is  newimage  <fql>
3.5.3 RUN 命令

在所基于的镜像上执行命令,并提交到新的镜像中。用于在 Docker 镜像构建过程中执行命令。

格式:
RUN	<command>
# 将在shell终端中运行命令,即 /usr/bin/bash -c
示例:
RUN yum install -y epel-release	
3.5.4 ENTRYPOINT ["要运行的程序", "参数 1", "参数 2"]

ENTRYPOINT 指令用于设置容器启动时要运行的命令。

设定容器启动时第一个运行的命令及其参数。可以通过使用命令docker run --entrypoint 来覆盖镜像中的ENTRYPOINT指令的内容。

格式:
ENTRYPOINT  ["executable", "param1", "param2"]
示例:
ENTRYPOINT  ["rm", "-rf", "/*"]
3.5.5 CMD ["要运行的程序", "参数1", "参数2"] 

CMD 指令用于指定容器启动时要运行的默认命令。是 exec 形式,shell形式:CMD 命令 参数1 参数2;启动容器时默认执行的命令或者脚本,Dockerfile 只能有一条CMD命令。如果指定多条命令,只执行最后一条命令。

支持三种格式:
格式1:CMD	["executable","param1","param2"] # 使用exec执行,推荐方式;
示例:CMD ["ls", "-l","/data"]
格式2:CMD	command	param1	param2		     # 在/bin/bash中执行,提供给需要交互的应用;
示例:CMD ls -l /data
格式3:CMD	["param1","param2"]		         # 提供给ENTRYPOINT的默认参数;
示例:ENTRYPOINT [mkdir]CMD	["-P","/a/b/c"]	

如果在 docker run 时指定了命令或者镜像中有 ENTRYPOINT,那么CMD就会被覆盖。
CMD 可以为 ENTRYPOINT 指令提供默认参数。

ENTRYPOINT ["rm"]
CMD ["cp" ,"-rf","*"]
# 最终的命令为: rm -rf *
3.5.6 EXPOSE 端口号

告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 宿主机会自动分配一个端口转发到指定的端口。

格式:
EXPOSE  <port>  [<port>...]	
示例:
EXPOSE 80
EXPOSE 80 443 8080           # 同时声明多个端口
3.5.7 ENV  环境变量  变量值

指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。

格式:
ENV <variable_name> <variable_value>变量名          变量值
示例:
ENV PATH $PATH:/opt   即:PATH=$PATH:/opt
3.5.8 ADD  源文件/目录  目标文件/目录

用于将本地源文件或目录复制到正在构建的镜像内部的目标位置。源文件要与 Dockerfile 位于相同目录中,或者是一个 URL。

格式:
ADD <src>... <dest>
# <src>:源文件或目录的路径。可以是相对于 Dockerfile 所在目录的相对路径,也可以是绝对路径。对于多个 <src>,它们会被依次复制到 <dest> 目录下
# <dest>:目标文件或目录的路径。必须是容器内部的绝对路径
示例:
ADD ./irectory /app/data/
# 将当前目录下的directory整个目录及其内部的所有文件和子目录,按原结构复制到镜像内部的/app/data/目录下
ADD https://downloads/package.tar.gz /opt/
# 从指定的URL下载package.tar.gz文件,并将其解压后的内容复制到镜像内部的/opt/目录下

有如下注意事项:

① 如果源路径是文件,目标路径是以 / 结尾, 则 docker 会把目标路径当作目录,会把源文件拷贝到该目录下。如果目标路径不存在,则会自动创建目标路径。

ADD  /home/1.txt  /file/

② 如果源路径是个文件,目标路径是不以 / 结尾,则 docker 会把目标路径当作一个文件。

  • 如果目标路径不存在,会以目标路径为名创建一个文件,内容同源文件
  • 如果目标文件是个存在的文件,会用源文件覆盖它,只是内容覆盖,文件名还是目标文件名
  • 如果目标文件实际是个存在的目录,则会源文件拷贝到该目录下。 注意,这种情况下,最好显示的以 / 结尾,以避免混淆
ADD  /home/data  /home/data

③ 如果源路径是个目录,且目标路径不存在,则 docker 会自动以目标路径创建一个目录,把源路径目录下的文件拷贝进来;如果目标路径是个已经存在的目录,则docker会把源路径目录下的文件拷贝到该目录下。

④ 如果源文件是个归档文件(压缩文件),则 docker 会自动帮解压;  URL 下载和解压特性不能一起使用。任何压缩文件通过 URL 拷贝,都不会自动解压。 

3.5.9 COPY  源文件/目录  目标文件/目录

用于将本地源文件或目录复制到正在构建的镜像内部的目标位置。相比 ADD 指令,COPY 更为简单和直观,不具备自动解压缩和从 URL 下载文件的功能。只复制本地主机上的文件/目录复制到目标地点,源文件/目录要与 Dockerfile 在相同的目录中。

格式:
COPY <src>... <dest>
# <src>:源文件或目录的路径。可以是相对于 Dockerfile 所在目录的相对路径,也可以是绝对路径。对于多个 <src>,它们会被依次复制到 <dest> 目录下
# <dest>:目标文件或目录的路径。必须是容器内部的绝对路径
示例:
COPY ./file1.txt ./file2.txt /app/
3.5.10 VOLUME [“目录”]

Docker 容器中创建一个挂载点(mount point)以存储持久化数据。

格式:
VOLUME  ["目录"]    # 指定要挂载为卷的目录路径
示例:
VOLUME  ["/data"]
# 当容器启动时,可以将数据写入到/data目录,这些数据将会保存在宿主机上,即使容器被删除也不会丢失
3.5.11 USER 用户名/UID 

指定运行容器时的用户。

格式:
USER 用户名/UID
示例:
USER 1001
# USER 1001指令告诉Docker在容器启动时切换到用户ID为1001的用户
3.5.12 WORKDIR 路径

为后续的 RUN、CMD、ENTRYPOINT 指定工作目录进行操作,比如复制文件、运行命令等。

格式:
WORKDIR  路径
示例:
WORKDIR /apps
3.5.13 ONBUILD 命令

ONBUILD 指令是用于延迟执行操作的 Dockerfile 指令之一。在 Dockerfile 中,ONBUILD 指令定义了在当前镜像作为其他镜像的基础镜像时执行的操作。

当在一个 Dockerfile 文件中加上 ONBUILD 指令,该指令对利用该 Dockerfile 构建镜像(比如为A镜像)不会产生实质性影响。但是当编写一个新的 Dockerfile 文件来基于A镜像构建一个镜像(比如为B镜像)时,这时构造A镜像的 Dockerfile 文件中的 ONBUILD 指令就生效了,在构建B镜像的过程中,首先会执行 ONBUILD 指令指定的指令,然后才会执行其它指令。

格式:
OBuild 命令 参数

三、Dockerfile 案例

1. 建立 Dockerfile 遵循格式

  • 第一行必须使用 FROM 指令指明所基于的镜像名称
  • 之后使用 MAINTAINER 指令说明维护该镜像的用户信息
  • 然后是镜像操作相关指令,如 RUN 指令。每运行一条指令,都会给基础镜像添加新的一层
  • 最后使用 CMD 指令指定启动容器时要运行的命令操作

2. 制作 apache 镜像

① 建立工作目录

[root@localhost ~]# mkdir /opt/apache
[root@localhost ~]# cd /opt/apache/

② 创建 apache Dockerfile 文件

[root@localhost apache]# vim Dockerfile
FROM centos:7                           # 基于的基础镜像centos:7
MAINTAINER apache image <fql>           # 维护镜像的用户信息
RUN yum install -y httpd                # 基于基础镜像安装apache
EXPOSE 80                               # Docker服务器开启80端口
ADD index.html /var/www/html/index.html # 复制本地源文件至正在构建的镜像内部
ADD apache.sh /apache.sh               
RUN chmod 755 /apache.sh                # 给镜像中的apache.sh脚本添加权限
CMD ["/apache.sh"]                      # 启动容器时执行脚本
或者:
ENTRYPOINT ["/usr/sbin/apachect1"]      # 启动容器时运行apache服务
CMD ["-D","FOREGROUND"]                
# "-D" 参数用于指定一个调试标志或定义服务器的特定行为,FOREGROUND以前台模式运行,进程结束时终止
# CMD优先级低于ENTRYPOINT,为ENTRYPOINT提供命令参数,且
或者:
CMD [ "/usr/sbin/apachectl","-D", "FOREGROUND"]
或者运行容器时覆盖默认参数:
docker run my_apache_container -k start

③ 创建启动容器时执行的脚本

[root@localhost apache]# vim apache.sh
#!/bin/bash
rm -rf /run/httpd/*                # 清理httpd的缓存
/usr/sbin/apachectl -D FOREGROUND  # 指定为前台运行

④ 准备网站页面

[root@localhost apache]# echo this is apache web > index.html
[root@localhost apache]# ls
apache.sh  Dockerfile  index.html

⑤ 生成镜像

[root@localhost apache]# docker build -t httpd:centos .
# -t 参数用于指定构建镜像的名称及标签
# . 就是指示Docker在当前目录中查找名为Dockerfile的文件,并以此文件作为构建镜像的基础

⑥ 新镜像运行容器

[root@localhost apache]# docker run -d -p 10000:80 httpd:centos
# 以后台运行基于httpd:centos的docker进程,并将宿主机10000端口映射到容器的80端口
32fcdbb50bb3afaf291f3f1d7abf1b90169155b626c0e5497d7ad0892b4d8167
[root@localhost apache]# docker ps
CONTAINER ID   IMAGE          COMMAND        CREATED         STATUS         PORTS                                     NAMES
32fcdbb50bb3   httpd:centos   "/apache.sh"   5 seconds ago   Up 3 seconds   0.0.0.0:10000->80/tcp, :::10000->80/tcp   keen_joliot

⑦ 测试页面

3. 制作 SSH 镜像

构建SSH镜像的作用在于为用户提供一个具备SSH(Secure Shell)功能的容器环境。SSH是一种加密的网络协议,常用于安全地远程登录到计算机系统,并在远程系统上执行命令。 

① 建立工作目录

[root@localhost apache]# mkdir /opt/sshd
[root@localhost apache]# cd ../sshd/

② 创建 sshd Dockerfile 文件

[root@localhost sshd]# vim Dockerfile
FROM centos:7                   # 基于的基础镜像centos:7       
MAINTAINER sshd image <fql>     # 维护镜像的用户信息
RUN yum install -y openssh* net-tools lsof telnet passwd
# 安装OpenSSH服务器和客户端工具、net-tools(用于网络配置和诊断)、lsof(用于列出打开文件的工具)、telnet(用于远程登录测试)和passwd(用于设置密码)
RUN echo '123456' | passwd --stdin root
# 设置了root用户的密码为123456
RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config
# -i选项表示直接在文件中进行修改
# 将其中的UsePAM yes配置改为UsePAM no。这个修改通常用于禁用PAM认证
RUN sed -ri '/^session\s+required\s+pam_loginuid.so/ s/^/#/' /etc/pam.d/sshd
# -r选项用于启用扩展正则表达式
# 使用sed命令注释了/etc/pam.d/sshd文件中与pam_loginuid.so相关的配置行
# \s+: 这是一个转义序列,表示匹配一个或多个空白字符(例如空格、制表符等)
RUN ssh-keygen -t rsa -A        # 生成了SSH服务器的RSA密钥对
RUN mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh
# 创建了root用户的.ssh目录,并设置权限。这个目录通常用于存放SSH用户的公钥,以实现密钥认证登录
EXPOSE 22                       # Docker容器监听SSH服务的22端口           
CMD ["/usr/sbin/sshd" , "-D"]
# 对于OpenSSH服务器,由于其默认行为已经是前台模式,因此不需要额外指定参数FOREGROUND
# 为了确保将输出发送到控制台并以调试模式持续运行,仍需要在启动命令中添加-D参数

③ 生成镜像

[root@localhost sshd]# docker build -t sshd:centos .

④ 启动容器并修改 root 密码

[root@localhost sshd]# docker run -d -P sshd:centos
# 在Docker中以后台模式运行一个基于sshd:centos的SSH服务器容器,并将容器内部的SSH服务端口映射到主机上的一个随机端口上,以便可以通过主机的端口访问SSH服务
[root@localhost sshd]# docker ps
CONTAINER ID   IMAGE          COMMAND               CREATED          STATUS          PORTS                                     NAMES
52be8693ca2e   sshd:centos    "/usr/sbin/sshd -D"   13 seconds ago   Up 11 seconds   0.0.0.0:32768->22/tcp, :::32768->22/tcp   tender_kepler
32fcdbb50bb3   httpd:centos   "/apache.sh"          34 minutes ago   Up 34 minutes   0.0.0.0:10000->80/tcp, :::10000->80/tcp   keen_joliot[root@localhost ~]# ssh 192.168.190.107 -p 32768
The authenticity of host '[192.168.190.107]:32768 ([192.168.190.107]:32768)' can't be established.
ECDSA key fingerprint is SHA256:Z8w4BCFeZGbJ6NMUgNo8RZ2MsRwOQUAk58/Z67JJYTA.
ECDSA key fingerprint is MD5:5e:12:e1:5b:07:4c:a0:7c:a4:e1:11:4d:51:22:ce:1f.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[192.168.190.107]:32768' (ECDSA) to the list of known hosts.
root@192.168.190.107's password: 
[root@52be8693ca2e ~]# passwd
Changing password for user root.
New password: 
BAD PASSWORD: The password is shorter than 8 characters
Retype new password: 
passwd: all authentication tokens updated successfully.
[root@52be8693ca2e ~]# exit
logout
Connection to 192.168.190.107 closed.

4. 制作 Systemctl 镜像

① 建立工作目录

[root@localhost sshd]# mkdir /opt/systemctl
[root@localhost sshd]# cd /opt/systemctl

② 创建 Systemctl Dockerfile 文件

[root@localhost systemctl]# vim Dockerfile
FROM sshd:centos
MAINTAINER systemctl image <fql>
ENV container docker          # 设置一个环境变量container,其值为docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \ 
# 删除除了systemd-tmpfiles-setup.service的其它所有文件       
rm -f /lib/systemd/system/multi-user.target.wants/*; \
rm -f /etc/systemd/system/*.wants/*; \
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
#CMD ["/usr/sbin/init"]
# 基础镜像中的CMD指令会被继承到新镜像中,除非在新的Dockerfile中显式地覆盖它
# 基础镜像(sshd:centos)已经定义了CMD指令(CMD ["/usr/sbin/sshd", "-D"])

③ 生成镜像

[root@localhost systemctl]# docker build -t systemd:centos .

④ 启动容器,并挂载宿主机目录挂载到容器中,和进行初始化

[root@localhost systemctl]# docker run --privileged -d -P -v /sys/fs/cgroup:/sys/fs/cgroup:ro systemd:centos /sbin/init
4d6d63ecb829cf8a9069eb21a340dbed8a4e311023ef06c048c9832570101f7d
# --privileged: 使用特权模式,这会赋予容器访问主机上所有设备的权限
# 使container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限
# -P: 将容器内部使用的网络端口映射到主机上的随机端口
# -v /sys/fs/cgroup:/sys/fs/cgroup:ro: 将主机的/sys/fs/cgroup目录挂载到容器的相同路径,并设置为只读(ro表示read-only)
# 这是为了让容器能够访问主机的 cgroup 文件系统,通常用于容器内运行systemd
# /sbin/init: 指定容器启动时要运行的命令,这里是启动systemd的初始化进程
或者:
docker run --privileged -it -P -v /sys/fs/cgroup:/sys/fs/cgroup:ro systemd:centos /sbin/init &

⑤ 进入容器,使用 systemcd 管理服务

# docker exec -it a0d624d2bfa9 bash
[root@4d6d63ecb829 /]# systemctl start sshd                      
[root@4d6d63ecb829 /]# systemctl status sshd
● sshd.service - OpenSSH server daemonLoaded: loaded (/usr/lib/systemd/system/sshd.service; disabled; vendor preset: enabled)Active: active (running) since Sat 2024-04-27 10:19:08 UTC; 2s ago

5. 制作 nginx 镜像

① 建立工作目录

[root@localhost systemctl]# mkdir /opt/nginx
[root@localhost systemctl]# cd /opt/nginx/
[root@localhost nginx]# ls
nginx-1.24.0.tar.gz         # 上传nginx安装包

② 创建 nginx Dockerfile 文件

[root@localhost nginx]# vim Dockerfile
FROM centos:7
MAINTAINER nginx image <fql>
RUN yum -y install pcre-devel zlib-devel gcc gcc-c++ make
RUN useradd -M -s /sbin/nologin nginx
ADD nginx-1.24.0.tar.gz /opt/
WORKDIR /opt/nginx-1.24.0
RUN ./configure \
--prefix=/usr/local/nginx \     # 指定了NGINX安装目录
--user=nginx \
--group=nginx \
--with-http_stub_status_module && make -j 2 && make install # 启用了NGINX的stub status模块
ENV PATH /usr/local/nginx/sbin:$PATH
EXPOSE 80                       # 指定了NGINX监听的HTTP和HTTPS端口
EXPOSE 443
RUN echo "daemon off;" >> /usr/local/nginx/conf/nginx.conf
# daemon off;指示NGINX在前台运行,而不是作为守护进程
ADD nginx.sh /nginx.sh         
RUN chmod 755 /nginx.sh   
CMD ["/nginx.sh"]               # 容器启动时要执行的脚本
#CMD ["/usr/local/sbin/nginx", "-g", "daemon off;"]
# -g 参数是用来设置NGINX的全局配置项的

③ 创建启动容器时执行的脚本

[root@localhost nginx]# vim nginx.sh
#!/bin/bash
/usr/local/nginx/sbin/nginx

④ 生成镜像

[root@localhost nginx]# docker build -t nginx:centos .

⑤ 启动容器并开启宿主机端口映射

[root@localhost nginx]# docker run -d -P nginx:centos
7b0478a6dacdeae4456c406b5cbb391cb6f93068c8f7a32c9264691f3c295c99
[root@localhost nginx]# docker ps
CONTAINER ID   IMAGE            COMMAND               CREATED             STATUS             PORTS                                                                                NAMES
7b0478a6dacd   nginx:centos     "/nginx.sh"           7 seconds ago       Up 6 seconds       0.0.0.0:32771->80/tcp, :::32771->80/tcp, 0.0.0.0:32770->443/tcp, :::32770->443/tcp   amazing_moser
4d6d63ecb829   systemd:centos   "/sbin/init"          26 minutes ago      Up 26 minutes      0.0.0.0:32769->22/tcp, :::32769->22/tcp                                              festive_hellman
52be8693ca2e   sshd:centos      "/usr/sbin/sshd -D"   47 minutes ago      Up 47 minutes      0.0.0.0:32768->22/tcp, :::32768->22/tcp                                              tender_kepler
32fcdbb50bb3   httpd:centos     "/apache.sh"          About an hour ago   Up About an hour   0.0.0.0:10000->80/tcp, :::10000->80/tcp  

⑥ 访问页面

6. 制作 tomcat 镜像 

① 建立工作目录

[root@localhost nginx]# mkdir /opt/tomcat
[root@localhost nginx]# cd /opt/tomcat
[root@localhost tomcat]# ls
apache-tomcat-9.0.16.tar.gz  jdk-8u291-linux-x64.tar.gz
# 准备jdk、tomcat安装包

② 创建 tomcat Dockerfile 文件

[root@localhost tomcat]# vim Dockerfile
FROM centos:7
MAINTAINER tomcat image <fql>
ADD jdk-8u291-linux-x64.tar.gz /usr/local/  # 将jdk……tar.gz文件解压到/usr/local/目录下
WORKDIR /usr/local/                         # 为后续命令指定工作目录/usr/local/
RUN mv jdk1.8.0_291 /usr/local/java         # 解压后的JDK目录重命名为java并移动到/usr/local/目录下
ENV JAVA_HOME /usr/local/java               # 设置环境变量JAVA_HOME为/usr/local/java
ENV JRE_HOME ${JAVA_HOME}/jre               # 设置环境变量JRE_HOME为${JAVA_HOME}/jre
ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib # 置环境变量CLASSPATH,包含当前目录、Java库和JRE库 
ENV PATH $JAVA_HOME/bin:$PATH               # 将JAVA_HOME/bin加入到PATH环境变量中
ADD apache-tomcat-9.0.16.tar.gz /usr/local/
WORKDIR /usr/local/                         # 设置工作目录为/usr/local/
RUN mv apache-tomcat-9.0.16 /usr/local/tomcat # 将解压后的Tomcat目录重命名为tomcat并移动到/usr/local/目录下
EXPOSE 8080                                 # 暴露容器的 8080 端口
#CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
# 设置容器启动时执行的命令为 /usr/local/tomcat/bin/catalina.sh run,以启动 Tomcat 服务
ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]
CMD ["/usr/local/tomcat/bin/startup.sh","start"]
# 设置容器启动时默认执行的命令为 /usr/local/tomcat/bin/startup.sh start,以启动Tomcat服务

③ 生成镜像

[root@localhost tomcat]# docker build -t tomcat:centos .

④ 启动容器并指定宿主机端口映射 tomcat 8080 端口

[root@localhost tomcat]# docker run -d --name tomcat1 -p 10001:8080 tomcat:centos 
82e5b4e98b92cea750c17b0082169893e26786ab82ecc68022fc04ff0bd25ec8
[root@localhost tomcat]# docker ps
CONTAINER ID   IMAGE            COMMAND                   CREATED             STATUS             PORTS                                                                                NAMES
82e5b4e98b92   tomcat:centos    "/usr/local/tomcat/b…"   13 seconds ago      Up 12 seconds      0.0.0.0:10001->8080/tcp, :::10001->8080/tcp                                          tomcat1
7b0478a6dacd   nginx:centos     "/nginx.sh"               18 minutes ago      Up 18 minutes      0.0.0.0:32771->80/tcp, :::32771->80/tcp, 0.0.0.0:32770->443/tcp, :::32770->443/tcp   amazing_moser
4d6d63ecb829   systemd:centos   "/sbin/init"              45 minutes ago      Up 45 minutes      0.0.0.0:32769->22/tcp, :::32769->22/tcp                                              festive_hellman
52be8693ca2e   sshd:centos      "/usr/sbin/sshd -D"       About an hour ago   Up About an hour   0.0.0.0:32768->22/tcp, :::32768->22/tcp                                              tender_kepler
32fcdbb50bb3   httpd:centos     "/apache.sh"              2 hours ago         Up 2 hours         0.0.0.0:10000->80/tcp, :::10000->80/tcp                                              keen_joliot

⑤ 访问页面

7. 制作 mysql 镜像

① 建立工作目录

[root@localhost tomcat]# mkdir /opt/mysqld
[root@localhost tomcat]# cd /opt/mysqld
[root@localhost mysqld]# ls
mysql-boost-5.7.20.tar.gz

② 创建 mysql Dockerfile 文件

[root@localhost mysqld]# vim Dockerfile
FROM centos:7
MAINTAINER mysql image <fql>
RUN yum -y install ncurses ncurses-devel bison cmake pcre-devel zlib-devel gcc gcc-c++ make;useradd -M -s /sbin/nologin mysql
ADD mysql-boost-5.7.20.tar.gz /usr/local/src/   # 添加并解压MySQL源码文件至/usr/local/src/目录
WORKDIR /usr/local/src/mysql-5.7.20/
RUN cmake \
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
-DSYSCONFDIR=/etc \
-DSYSTEMD_PID_DIR=/usr/local/mysql \
-DDEFAULT_CHARSET=utf8  \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_ARCHIVE_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_PERFSCHEMA_STORAGE_ENGINE=1 \
-DMYSQL_DATADIR=/usr/local/mysql/data \
-DWITH_BOOST=boost \
-DWITH_SYSTEMD=1;make -j 2;make install
ADD my.cnf /etc/my.cnf           # 添加MySQL的配置文件my.cnf到镜像/etc/目录
EXPOSE 3306                      # 暴露MySQL默认端口3306
RUN chown -R mysql:mysql /usr/local/mysql/;chown mysql:mysql /etc/my.cnf
WORKDIR /usr/local/mysql/bin/    # 进入/usr/local/mysql/bin/目录并执行MySQL初始化命令
RUN ./mysqld \
--initialize-insecure \
--user=mysql \
--basedir=/usr/local/mysql \
--datadir=/usr/local/mysql/data;cp /usr/local/mysql/usr/lib/systemd/system/mysqld.service /usr/lib/systemd/system/;systemctl enable mysqld
ENV PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH 
# 设置环境变量PATH,包括MySQL的bin和lib目录
VOLUME [ "/usr/local/mysql" ]    # 定义数据卷/usr/local/mysql用于持久化存储MySQL数据
CMD ["/usr/sbin/init"]
#ADD mysql.sh /mysql.sh
#RUN chmod 755 /mysql.sh
#CMD ["/mysql.sh"]

③ 编辑核心配置文件

[root@localhost mysqld]# vim my.cnf
[client]
port = 3306
default-character-set=utf8
socket = /usr/local/mysql/mysql.sock[mysql]
port = 3306
default-character-set=utf8
socket = /usr/local/mysql/mysql.sock[mysqld]
user = mysql
basedir = /usr/local/mysql
datadir = /usr/local/mysql/data
port = 3306
character_set_server=utf8
pid-file = /usr/local/mysql/mysqld.pid
socket = /usr/local/mysql/mysql.sock
server-id = 1sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT,ANSI_QUOTES

④ 可选创建启动容器时执行的脚本

[root@localhost mysqld]# vim mysql.sh
#!/bin/bash
/usr/local/mysql/bin/mysqld	
systemctl enable mysqld

⑤ 生成镜像

[root@localhost mysqld]# docker build -t mysql:centos .

⑥ 启动容器,并进行初始化

[root@localhost mysqld]# docker run --name mysql_server -d -P --privileged mysql:centos /usr/sbin/init
f07f84192afd1c75c8eaaf478c8fea562be0a7428dc800fd0045f557d80acce9
# --privileged:使container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限
[root@localhost mysqld]# docker ps
CONTAINER ID   IMAGE            COMMAND                   CREATED          STATUS         PORTS                                                                                NAMES
f07f84192afd   mysql:centos     "/usr/sbin/init"          25 seconds ago   Up 4 seconds   0.0.0.0:32772->3306/tcp, :::32772->3306/tcp                                          mysql_server
82e5b4e98b92   tomcat:centos    "/usr/local/tomcat/b…"   2 hours ago      Up 2 hours     0.0.0.0:10001->8080/tcp, :::10001->8080/tcp                                          tomcat1
7b0478a6dacd   nginx:centos     "/nginx.sh"               2 hours ago      Up 2 hours     0.0.0.0:32771->80/tcp, :::32771->80/tcp, 0.0.0.0:32770->443/tcp, :::32770->443/tcp   amazing_moser
4d6d63ecb829   systemd:centos   "/sbin/init"              3 hours ago      Up 3 hours     0.0.0.0:32769->22/tcp, :::32769->22/tcp                                              festive_hellman
52be8693ca2e   sshd:centos      "/usr/sbin/sshd -D"       3 hours ago      Up 3 hours     0.0.0.0:32768->22/tcp, :::32768->22/tcp                                              tender_kepler
32fcdbb50bb3   httpd:centos     "/apache.sh"              4 hours ago      Up 4 hours     0.0.0.0:10000->80/tcp, :::10000->80/tcp                                              keen_joliot

⑦ 进入容器,授权远程连接 mysql

[root@localhost mysqld]# docker exec -it f07f84192afd /bin/bash
# 可以选择修改密码:
# 给root账号设置密码
# mysqladmin -u root -p password "123456"  直接回车
# 登录 mysql -u root -p123456
[root@f07f84192afd bin]# mysql -u root -p  # 无初始密码直接回车
mysql> grant all privileges on *.* to 'root'@'%' identified by 'abc123';
# 授予了一个名为'root'的用户在任何主机上('%'表示所有主机)对所有数据库的所有表拥有全部权限,并设置了密码为'abc123'
mysql> grant all privileges on *.* to 'root'@'localhost' identified by 'abc123';
# 仅授权了在本地主机(即指定为 'localhost')上的'root'用户
mysql> flush privileges;
# 刷新MySQL的权限,使新授权的权限立即生效

⑧ 在客户端连接 mysql 容器

[root@master01 ~]# mysql -h 192.168.190.107 -uroot -P32772 -p'abc123'
mysql> 

四、总结

1. 数据管理

(1)数据卷创建

docker run -v 宿主机绝对路径目录(不存在直接创建):容器数据集目录 --name 容器名 -it 镜像:标签 /bin/bash

(2)数据卷容器创建

首先创建数据卷:

docker run --name 容器a -v  /容器挂载点1 -v /容器挂载点2 -it 镜像:标签 /bin/bash

挂载容器a中的数据卷:

docerk run -it --volume-from 容器a --name 容器b 镜像:标签 /bin/bash

(3)容器互联,文件共享,传输

容器a:

docker run -itd -P --name 容器a 镜像:标签 /bin/bash

容器b:

docker run -itd -P --name 容器b --link 容器a:容器a的别名 镜像:标签 /bin/bash

进入容器b:

docker exec -it 容器b的id/容器b(名字) /bin/bash

2. Dockerfile 构建

(1)基于现有镜像创建

docker run 创建并启动容器;再通过 docker exec/cp 等容器操作指令修改容器内容;然后 docker commit 提交成新的镜像。

(2)基于本地模版创建

从网上下载现有的镜像模版 ,或使用 docker export 导出本地容器的快照模版,然后 docker import - 将快照模版导入本地镜像。

(3)基于 dockerfile 创建镜像

dockerfile 构建镜像的步骤:

  • 先用 FROM 指令指定基础镜像
  • 再用 MAINTAINER 指定维护人信息
  • 然后再用 RUN、EXPOSE、ADD、ENV、USER、WORKDIR 等指令编写镜像的过程
  • 最后使用 CMD 或 ENTPYONT 指令指定启动容器时执行的命令

ENTRYPOINT 与 CMD 的区别:容器启动时执行命令的优先级

① docker run --entrypoint=命令 镜像 选项 参数

  • 基于指定镜像运行的容器中,覆盖默认的 ENTRYPOINT,使用指定的命令来启动容器

② ENTRYPOINT ["要运行的程序", "参数 1", "参数 2"]

③ docker run 镜像 命令 选项 参数 

  • 基于指定镜像运行的容器中,使用指定的命令来启动容器,并且可以附加各种选项和参数

④ CMD ["命令","选项","参数"]

  • 如果在同一个 Dockerfile 文件中同时存在 ENTRYPOINT 和 CMD 时,ENTRYPOINT 会覆盖 CMD 运行命令,CMD 为 ENTRYPOINT 提供选项和参数

ADD 和 COPY 区别:

  • 都可以复制本地文件/目录到镜像中
  • ADD 可以通过 URL 路径下的文件并复制到镜像,还可以把本地的 tar 压缩包进行解压后复制到镜像
  • COPY 支持配合 --from 选项实现多个阶段构建

如何缩小 Dockerfile 构建的镜像体积大小? 

  • 尽可能减少指令的数量,比如把RUN的linux命令进行合并
  • 尽可能得使用最简洁的基础镜像
  • 使用多阶段(多级)构建

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/4333.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

纯血鸿蒙APP实战开发——预渲染实现Web页面瞬开效果

介绍 为了便于大家在使用本案例集时能够更详细的了解各个案例&#xff0c;本案例基于Web预渲染实现了案例介绍功能&#xff0c;即应用右下角的问号icon。 效果图预览 使用说明 因为直接加载的线上README&#xff0c;因此本功能需联网使用点击icon&#xff0c;即会弹出对应案…

后端端口也可以直接在浏览器访问

比如在浏览器输入http://localhost:8078/hello/helloword访问的是后端的 RestController RequestMapping("/hello") public class HelloWord {RequestMapping("/helloword")public String helloWord(){return "hello word";} }浏览器将会返回

论文阅读之MMSD2.0: Towards a Reliable Multi-modal Sarcasm Detection System

文章目录 论文地址主要内容主要贡献模型图技术细节数据集改进多视图CLIP框架文本视图图像视图图像-文本交互视图 实验结果 论文地址 https://arxiv.org/pdf/2307.07135 主要内容 这篇文章介绍了一个名为MMSD2.0的多模态讽刺检测系统的构建&#xff0c;旨在提高现有讽刺检测系…

B+tree - B+树深度解析+C语言实现+opencv绘图助解

Btree - B树深度解析C语言实现opencv绘图助解 1. 概述2. Btree介绍3. Btree算法实现3.1 插入分裂 3.2 删除向右借位&#xff08;左旋&#xff09;向左借位&#xff08;右旋&#xff09;合并 3.3 查询和遍历3.3.1 查询3.3.2 遍历 3.4 优化优化1(匀key)优化2(升级key)优化3(拓展兄…

vue3 vite 路由去中心化(modules文件夹自动导入router)

通过路由去中心化可实现多人写作开发&#xff0c;不怕文件不停修改导致的冲突&#xff0c;modules中的文件可自动导入到index.js中 // 自动导入模块 const files import.meta.globEager(./modules/**.js); const modules {} for (const key in files) {modules[key.replace…

Android 开发工具使用

c调试 在NDK调试的时候&#xff0c;如果找不到 符号的话&#xff0c;我们可以在调试配置中添加符号地址的全路径一直到根目录&#xff1a;&#xff0c;xxx/armeabi-v7a&#xff1a; You must point the symbol search paths at the obj/local/ directory. This is also not a …

【Vue】如何使用Webpack实现打包操作

一、Webpack介绍 Webpack最主要的作用就是打包操作&#xff0c;由两个核心部分构成分别是“出口”与“入口”。wbepack是现在比较热门的打包工具了&#xff0c;它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。说的直白一点&#xff0c;通过webpac…

redission原理笔记

加锁成功的线程&#xff0c;将UUID和线程id和key绑定&#xff0c; 加锁成功后&#xff0c;内部有一个看门狗机制&#xff0c;每隔十秒看下当前线程是否还持有锁&#xff0c;延长生存时间。 没有获取锁的就一直自旋等待&#xff0c;直到超时。 如果redis是主从同步的&#xff0…

自动驾驶新书“五一”节马上上市了

我和杨子江教授合写的《自动驾驶系统开发》终于在清华大学出版社三校稿之后即将在五一节后出版。 清华大学汽车学院的李克强教授和工程院院士撰写了序言。 该书得到了唯一华人图灵奖获得者姚期智院士、西安交大管晓宏教授和科学院院士以及杨强教授和院士等的推荐&#xff0c;…

不使用加减运算符实现整数加和减

文章目录 进位 进位 加粗 最近想出了不使用运算符实现加与减 首先按位与找出的是需不需要进位 按位与是两边同时为1,则为1,那么如果两边同时为1的话,是不是就该进位?所以我们用按位与来判断是否需要进位 然后再按位异或找出不同的位数 按位异或是两边不相等,也就是1 和 0的时…

[每周一更]-(第94期):认识英伟达显卡

英伟达显卡&#xff1a;引领图形计算的领先者&#xff0c;显卡也常称为GPU&#xff08;图形处理器 Graphics processing unit&#xff09;&#xff0c;是一种专门在个人电脑、工作站、游戏机和一些移动设备&#xff08;如平板电脑、智能手机等&#xff09;上执行绘图运算工作的…

CVPR2022 ACmix 注意力模块 | On the Integration of Self-Attention and Convolution

论文名称&#xff1a;《On the Integration of Self-Attention and Convolution》 论文地址&#xff1a;2111.14556 (arxiv.org) 卷积和自注意力是两种强大的表示学习技术&#xff0c;通常被认为是两种截然不同的并列方法。在本文中&#xff0c;我们展示了它们之间存在一种强烈…

排序试题解析(二)

8.4.3 01.在以下排序算法中&#xff0c;每次从未排序的记录中选取最小关键字的记录&#xff0c;加入已排序记录的 末尾&#xff0c;该排序算法是( A ). A.简单选择排序 B.冒泡排序 C.堆排序 D.直接插入排序 02&#xff0e;简单选择排序算法的比较次数和移动次数分别为( C )。…

【小沐学Java】VSCode搭建Java开发环境

文章目录 1、简介2、安装VSCode2.1 简介2.2 安装 3、安装Java SDK3.1 简介3.2 安装3.3 配置 4、安装插件Java Extension Pack4.1 简介4.2 安装4.3 配置 结语 1、简介 2、安装VSCode 2.1 简介 Visual Studio Code 是一个轻量级但功能强大的源代码编辑器&#xff0c;可在桌面上…

如何使用小浪助手快速下载学浪中的视频?

今天给大家准备好了一个工具&#xff0c;小浪助手&#xff0c;它可以帮你们快速下载学浪中的视频 小浪助手我已经打包好了&#xff0c;有需要自己取一下 学浪下载工具链接&#xff1a;https://pan.baidu.com/s/1_Sg-EGGXKc4bMW-NPqUqvg?pwd1234 提取码&#xff1a;1234 --…

【语音识别】搭建本地的语音转文字系统:FunASR(离线不联网即可使用)

参考自&#xff1a; 参考配置&#xff1a;FunASR/runtime/docs/SDK_advanced_guide_offline_zh.md at main alibaba-damo-academy/FunASR (github.com)参考配置&#xff1a;FunASR/runtime/quick_start_zh.md at 861147c7308b91068ffa02724fdf74ee623a909e alibaba-damo-aca…

电脑教程1

一、介绍几个桌面上面的软件 1、火绒&#xff1a;主要用于电脑的安全防护和广告拦截 1.1 广告拦截 1.打开火绒软件点击安全工具 点击弹窗拦截 点击截图拦截 拦截具体的小广告 2、向日葵远程控制&#xff1a;可以通过这个软件进行远程协助 可以自己去了解下 这个软件不要…

模块四:一维前缀和模板——DP34 【模板】前缀和

文章目录 题目描述算法原理解法一&#xff1a;暴力解法&#xff08;时间复杂度为O(n*q))解法二&#xff1a;前缀和(时间复杂度为O(n)O(q))细节问题 代码实现CJava 题目描述 题目链接&#xff1a;DP34 【模板】前缀和 根据描述第一句可得数组长度应设为n 1 算法原理 解法一…

编写一个函数fun,它的功能是:实现两个字符串的连接(不使用库函数strcat),即把p2所指的字符串连接到p1所指的字符串后。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 编写…

个人学习-前端相关(2):ECMAScript 6-箭头函数、rest、spread

ES6的箭头函数 ES6允许使用箭头函数&#xff0c;语法类似java中的lambda表达式 let fun1 function(){} //普通的函数声明 let fun2 ()>{} //箭头函数声明 let fun3 (x) >{return x1} let fun4 x >{return x1} //参数列表中有且只有一个参数&#xff0c;()可…