前言
在容器化环境中,如何有效地管理和持久化数据成为了开发人员和运维团队面临的挑战之一;另一方面,镜像的创建是构建容器化应用的基础。优化的镜像设计可以提高部署效率和应用性能,减少资源消耗和运行成本。本文将介绍 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 daemon
Loaded: 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
WORKDIR /opt/nginx-1.12.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 = 1
sql_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命令进行合并
- 尽可能得使用最简洁的基础镜像
- 使用多阶段(多级)构建