目录
一、Docker减小镜像大小的方式
1、基础镜像选择
2、减少镜像层数
3、清理无用文件和缓存
4、优化文件复制(COPY和ADD指令)
二、Docker镜像多阶段构建
1、什么是dockers镜像多阶段构建
1.1 概念介绍
1.2 构建过程和优势
2、怎样在Dockerfile中实现多阶段构建
2.1 基本语法和步骤
2.2 示例:构建一个 Java 应用程序的多阶段镜像
2.3 注意事项
三、使用Dockerfile构建MySQL8.0.11
1、创建Dockerfile
2、启动新容器并创建用户
3、创建新容器并使用之前的数据
一、Docker减小镜像大小的方式
1、基础镜像选择
- 选择合适的基础镜像:
- 从官方仓库选择精简的基础镜像,如alpine。alpine是一个轻量级的 Linux 发行版,它的镜像体积通常很小。例如,构建一个 Python 应用,使用python:alpine作为基础镜像,相比python:ubuntu等镜像,体积会小很多。因为alpine在设计上注重小巧和简单,包含的软件包都是最基本的,没有过多的额外组件。
- 基于特定场景定制基础镜像:
- 如果对基础系统有特殊要求,可以基于更底层的基础镜像(如scratch)来构建。scratch是一个空白的镜像,几乎没有任何内容。这种情况下,需要自己将应用所需的最小运行时环境打包进去。例如,对于一个静态编译的 Go 程序,可以使用scratch作为基础镜像,将编译好的二进制文件直接复制到镜像中,这样构建出来的镜像体积会非常小。
2、减少镜像层数
- 合并RUN指令:
- 在Dockerfile中,每一个RUN指令都会创建一个新的镜像层。为了减少层数,应尽量合并相关的命令。例如,不要使用多个RUN指令分别安装软件包,而是将它们合并在一个RUN指令中。比如,不是这样写:
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
3、清理无用文件和缓存
- 在构建过程中清理缓存:
- 对于基于apt(Debian/Ubuntu)或yum(CentOS 等)的系统,在安装完软件包后,清理软件包缓存。例如,在RUN指令中安装完apt - get软件包后,使用RUN apt-get clean来清理apt缓存。同样,对于yum,可以使用RUN yum clean all来清理yum缓存。这样可以减少镜像中不必要的文件占用空间。
- 删除临时文件和无用文件:
- 在构建过程中,如果生成了一些临时文件或者不再需要的文件,应该及时删除。例如,在编译软件时,可能会生成一些中间的编译文件,这些文件在软件安装完成后就不再需要,可以在Dockerfile中添加命令来删除它们。比如,在构建一个 C/C++ 应用的镜像时,在RUN指令中编译完程序后,可以使用RUN rm -rf /path/to/temporary/compilation/files来删除编译过程中的临时文件。
4、优化文件复制(COPY和ADD指令)
- 只复制必要的文件:
- 在使用COPY或ADD指令时,只复制应用真正需要的文件和目录到镜像中。避免将整个项目目录(包括开发工具、测试文件等)都复制进去。例如,如果是一个 Web 应用,只需要将生产环境下的 HTML、CSS、JavaScript 文件和服务器端代码复制到镜像中,而不需要复制开发过程中的单元测试文件等。
- 使用.dockerignore文件:
- 创建一个.dockerignore文件,在这个文件中列出不需要复制到镜像中的文件和目录模式。例如,如果项目目录中有.git文件夹(包含版本控制信息)、node_modules(对于 Node.js 应用,如果已经有package.json和package - lock.json,可以在镜像构建过程中重新安装依赖,不需要复制本地的node_modules)等,可以在.dockerignore文件中添加.git和node_modules,这样在构建镜像时,Docker会自动忽略这些文件和目录,从而减小复制到镜像中的文件体积。
二、Docker镜像多阶段构建
1、什么是dockers镜像多阶段构建
1.1 概念介绍
Docker 多阶段构建是一种在构建 Docker 镜像时使用的技术,它允许将镜像构建过程划分为多个不同的阶段。每个阶段都可以基于不同的基础镜像或者具有不同的构建目的,并且可以在不同阶段之间传递文件。通过这种方式,可以构建出更加精简、高效且安全的 Docker 镜像。
1.2 构建过程和优势
- 减小镜像体积:
- 在传统的单阶段构建中,如果要构建一个包含编译环境和运行环境的应用镜像,例如一个 Go 语言应用,构建过程中需要安装 Go 编译器和相关的编译工具来编译代码,同时还需要将运行应用所需的库和二进制文件打包到镜像中。这会导致镜像中包含大量的编译工具和中间文件,使镜像体积变大。
- 而在多阶段构建中,可以先使用一个包含 Go 编译器的基础镜像来编译应用,生成可执行文件。然后在第二阶段,使用一个更精简的基础镜像(如alpine),只将编译好的可执行文件复制到这个镜像中,抛弃了编译环境相关的工具和文件,从而大大减小了最终镜像的体积。
- 提高安全性:
- 以一个包含敏感信息(如数据库连接密码)的应用为例。在单阶段构建中,如果构建过程中涉及到在镜像中设置密码等操作,这些敏感信息可能会留在最终的镜像中,存在安全风险。
- 在多阶段构建中,可以在一个阶段(如开发环境阶段)处理敏感信息相关的操作,然后在构建最终生产环境镜像的阶段,只将必要的、经过处理的应用组件复制过来,避免敏感信息泄露到生产环境镜像中,提高了镜像的安全性。
- 分离关注点:
- 对于复杂的应用构建,不同的构建阶段可能有不同的目标。例如,在第一阶段进行代码编译、测试,在第二阶段进行打包和配置运行环境。多阶段构建可以清晰地将这些不同的关注点分开,使得构建过程更加易于理解和维护。
- 示例
- 以构建一个 Node.js 应用为例,第一阶段可以使用node:14 - builder(假设这是一个包含构建工具的 Node.js 镜像)来安装项目依赖并构建应用:
FROM node:14 - builder
WORKDIR /app
COPY package*.json./
RUN npm install
COPY..
RUN npm run build
2、怎样在Dockerfile中实现多阶段构建
2.1 基本语法和步骤
- 多个FROM指令:
- 在Dockerfile中,通过使用多个FROM指令来划分不同的构建阶段。每个FROM指令开始一个新的构建阶段,并且可以基于不同的基础镜像。例如,第一阶段可能基于一个包含编译工具的基础镜像,用于构建应用程序;第二阶段可以基于一个更精简的运行时基础镜像,用于运行最终的应用。
- 阶段命名(可选):
- 可以为每个阶段指定一个名称,这在需要引用特定阶段时非常有用。命名是在FROM指令后使用AS关键字来实现的。例如,FROM node:14 - builder AS builder_stage,这里将第一个阶段命名为builder_stage。
- 文件复制(COPY或ADD)在阶段之间:
- 使用COPY或ADD指令在不同阶段之间传递文件。关键是要使用--from选项来指定从哪个阶段复制文件。例如,COPY --from = builder_stage /app/dist.表示从名为builder_stage的阶段复制/app/dist目录下的文件到当前阶段的当前目录(.)。
2.2 示例:构建一个 Java 应用程序的多阶段镜像
- 第一阶段:构建应用(使用maven构建工具):
FROM maven:3.8.4 - openjdk - 11 AS build
WORKDIR /app
COPY pom.xml.
RUN mvn dependency:go-off-line
COPY src./src
RUN mvn package - DskipTests
2.3 注意事项
- 缓存利用:
- 在多阶段构建中,每个阶段的构建缓存是独立的。Docker 会根据每个阶段的FROM指令和后续的构建指令来判断是否可以使用缓存。因此,合理安排构建顺序和指令内容可以更好地利用缓存,提高构建效率。
- 基础镜像选择:
- 每个阶段要根据其功能选择合适的基础镜像。如在编译阶段选择包含完整编译工具链的镜像,在运行阶段选择只包含运行时必要组件的镜像,这样才能充分发挥多阶段构建减小镜像体积的优势。
三、使用Dockerfile构建MySQL8.0.11
1、创建Dockerfile
# 使用官方MySQL 8.0镜像作为基础镜像
FROM mysql:8.0
# 设置环境变量,用于初始化数据库
ENV MYSQL_DATABASE=db_name
ENV MYSQL_USER=user_name
ENV MYSQL_PASSWORD=user_password
ENV MYSQL_ROOT_PASSWORD=root_password
# 复制初始化脚本到容器内
COPY ./init.sql /docker-entrypoint-initdb.d/
# 容器将以daemon模式运行MySQL服务
CMD ["mysqld"]
命令示例:
cat >Dockerfile <<'eof'
FROM mysql:8.0.11
ENV MYSQL_DATABASE=MineGi
ENV MYSQL_USER=test11
ENV MYSQL_PASSWORD=123456
ENV MYSQL_ROOT_PASSWORD=666666
COPY ./init.sql /docker-entrypoint-initdb.d/
CMD ["mysqld"]
eof
cat >init.sql <<'eof'
CREATE TABLE test11 (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
eof
ls
docker build -t mysql:v8 .
输出结果:
[root@MineGi ~]# cat >Dockerfile <<'eof'
> FROM mysql:8.0.11
> ENV MYSQL_DATABASE=MineGi
> ENV MYSQL_USER=test11
> ENV MYSQL_PASSWORD=123456
> ENV MYSQL_ROOT_PASSWORD=666666
> COPY ./init.sql /docker-entrypoint-initdb.d/
> CMD ["mysqld"]
> eof
[root@MineGi ~]# cat >init.sql <<'eof'
> CREATE TABLE test11 (
> id INT AUTO_INCREMENT PRIMARY KEY,
> username VARCHAR(50) NOT NULL,
> created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
> );
> eof
[root@MineGi ~]# ls
Dockerfile init.sql
[root@MineGi ~]# docker build -t mysql:v8 .
[+] Building 0.1s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 222B 0.0s
=> [internal] load metadata for docker.io/library/mysql:8.0.11 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 187B 0.0s
=> [1/2] FROM docker.io/library/mysql:8.0.11 0.0s
=> CACHED [2/2] COPY ./init.sql /docker-entrypoint-initdb.d/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:10b79382712d1c5e721fd28cab3194c6818606784110fa68520bce220c33474d 0.0s
=> => naming to docker.io/library/mysql:v8 0.0s
[root@MineGi ~]#
2、启动新容器并创建用户
命令示例:
docker run --name my_mysql -v mysql_data:/var/lib/mysql -d mysql:v8
docker exec -it my_mysql mysql -uroot -p666666
create user 'test-1'@'%' identified by '123456';
grant all on MineGi.* to 'test-1'@'%';
flush privileges;
exit
docker stop my_mysql
docker rm my_mysql
输出结果:
[root@MineGi ~]# docker run --name my_mysql -v mysql_data:/var/lib/mysql -d mysql:v8
c397aefbc2b61dba29ba48c3c32322058b052b5c45cd5c1c7162734d076bd09d
[root@MineGi ~]# docker exec -it my_mysql mysql -uroot -p666666
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.11 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create user 'test-1'@'%' identified by '123456';
Query OK, 0 rows affected (0.11 sec)
mysql> grant all on MineGi.* to 'test-1'@'%';
Query OK, 0 rows affected (0.03 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> exit
Bye
[root@MineGi ~]# docker stop my_mysql
my_mysql
[root@MineGi ~]# docker rm my_mysql
my_mysql
[root@MineGi ~]#
3、创建新容器并使用之前的数据
命令示例:
mkdir -p /data/mysql/data
cp -a /var/lib/docker/volumes/mysql_data/_data/* /data/mysql/data
docker run --name my_mysql -v /data/mysql/data:/var/lib/mysql -d mysql:v8
docker exec -it my_mysql mysql -utest-1 -p123456
show databases;
show tables in MineGi;
输出结果:
[root@MineGi ~]# mkdir -p /data/mysql/data
[root@MineGi ~]# cp -a /var/lib/docker/volumes/mysql_data/_data/* /data/mysql/data
[root@MineGi ~]# docker run --name my_mysql -v /data/mysql/data:/var/lib/mysql -d mysql:v8
e5b8cec1fda1bd811c3e2828369b5380b623f02e4c1b9d8cde47ca87aaa3d82e
[root@MineGi ~]# docker exec -it my_mysql mysql -utest-1 -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.11 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| MineGi |
| information_schema |
+--------------------+
2 rows in set (0.01 sec)
mysql> show tables in MineGi;
+------------------+
| Tables_in_MineGi |
+------------------+
| test11 |
+------------------+
1 row in set (0.01 sec)
mysql>
情况说明:对于数据库的数据目录。可以直接使用Bind Mount方式挂载
关于这方面的解释如下:
- 理解数据持久化的概念
- 当你将 Docker 容器中的数据库目录持久化到宿主机时,实际上是将容器内的数据存储位置映射到了宿主机的文件系统中。
- 例如,对于 MySQL 容器,其内部存储数据的目录(如/var/lib/mysql)通过-v(volume)参数挂载到宿主机的某个目录。
- 容器启动时的数据加载
- 如果在容器启动之前,挂载的宿主机目录中已经存在有效的数据库文件,容器内的数据库服务在启动时会加载这些文件。
- 以 MySQL 为例,当容器中的 MySQL 服务启动时,它会检查数据目录(现在已经挂载到宿主机)中的文件。如果存在数据库文件(如.frm、.ibd等 MySQL 数据文件格式),它会读取这些文件来恢复数据库的状态,包括数据库、表结构和数据等内容。
- 数据共享和同步机制
- 这种持久化方式使得容器和宿主机之间的数据共享成为可能。在容器运行期间,对数据库的任何写入操作(如插入、更新数据),实际上是在宿主机挂载的目录中对应的文件上进行写入。
- 这是因为容器内的数据库进程认为数据目录就是它正常存储数据的地方,而这个目录已经被挂载到宿主机,所以数据会持久地保存在宿主机上,并且在容器重新启动等情况下可以被再次加载和使用。
- 容器镜像初始内容的复制
- 当你使用docker run命令并挂载了一个宿主机上不存在的目录(如/data/test01)到容器内的/var/lib/mysql(MySQL 数据存储目录)时,Docker 会自动将容器内部/var/lib/mysql目录中的初始内容复制到宿主机挂载点。
- 对于mysql:v8这个镜像,其内部/var/lib/mysql目录已经包含了 MySQL 初始化所需要的一些系统文件,如配置文件(auto.cnf)、证书文件(ca.pem、client - cert.pem等)以及数据库初始文件(ibdata1、ib_logfile0等)。这些文件在容器启动并挂载卷时被复制到了宿主机的/data/test01目录。
- 数据库文件系统的初始化机制
- MySQL 等数据库在启动时会检查数据目录是否为空。如果数据目录不存在某些关键文件,它会尝试初始化这些文件。
- 由于容器镜像中已经有了这些初始化文件,当通过挂载卷的方式将容器内的数据目录和宿主机目录关联起来时,这些初始化文件就被复制到了宿主机,使得在宿主机挂载点看起来有了内容。这些文件是数据库正常运行所必需的,后续数据库的操作(如创建表、插入数据等)也会将数据存储在这个挂载的目录中,保证数据的持久化。