一、Docker 介绍
- Docker 可以把你的程序 + 依赖库 + 配置 + 环境全部打包成一个镜像(Image),然后在任何装了 Docker 的机器上一模一样地跑起来。
详细介绍 Docker 容器技术的核心概念、安装部署、镜像构建优化、私有仓库搭建(Registry/Harbor)、网络配置及数据卷管理。涵盖从基础命令到多阶段构建、安全架构及 Compose 编排的完整实战流程,帮助开发者实现环境统一与高效运维。

一句话解释:Docker 就是一个「轻量、便携、统一」的容器工具,用来打包、运行软件。
- 最形象的比喻传统部署:每台电脑环境不一样,程序经常「在我这能跑,在你那就崩」。Docker:像一个集装箱。不管船(服务器)是什么型号不管货(程序)是什么只要装进集装箱,到哪都能直接运行
在 RHEL 9.6(红帽企业版 Linux 9.6) 系统中部署 Docker CE(社区版)的完整步骤。
作用:创建自定义的 Docker 仓库配置文件(/etc/yum.repos.d/docker.repo)
# 利用阿里云部署软件仓库
[root@docker-node1 ~]# cat > /etc/yum.repos.d/docker.repo << EOF
[docker]
name = docker
baseurl = https://mirrors.aliyun.com/docker-ce/linux/rhel/9.6/x86_64/stable/
gpgcheck = 0
EOF
[docker]:仓库的唯一标识(可自定义)。name = docker:仓库的描述名称(无实际功能,仅便于识别)。baseurl:Docker 软件包的下载地址(阿里云针对 RHEL 9.6 x86_64 架构的稳定版仓库)。gpgcheck = 0:关闭 GPG 签名校验。作用:从配置的阿里云仓库拉取最新的软件包元数据(如版本、依赖关系),存入本地缓存,避免后续安装时重复下载。
[root@docker-node1 ~]# dnf makecache
正在更新 Subscription Management 软件仓库。
无法读取客户身份 本系统尚未在权利服务器中注册。可使用 "rhc" 或 "subscription-manager" 进行注册。
docker 7.3 kB/s | 46 kB 00:06
AppStream 3.1 MB/s | 3.2 kB 00:00
BaseOS 2.7 MB/s | 2.7 kB 00:00
元数据缓存已建立。
作用:验证仓库配置是否生效,同时查看可安装的 Docker 相关包。
[root@docker-node1 ~]# dnf search docker
正在更新 Subscription Management 软件仓库。
无法读取客户身份 本系统尚未在权利服务器中注册。可使用 "rhc" 或 "subscription-manager" 进行注册。
上次元数据过期检查:0:00:13 前,执行于 2026 年 03 月 14 日 星期六 14 时 55 分 07 秒。
====================================
名称 和 概况 匹配:docker
====================================
docker-buildx-plugin.x86_64 : Docker Buildx plugin for the Docker CLI
docker-ce-rootless-extras.x86_64 : Rootless support for Docker
docker-compose-plugin.x86_64 : Docker Compose plugin for the Docker CLI
docker-model-plugin.x86_64 : Docker Model Runner plugin for the Docker CLI
pcp-pmda-docker.x86_64 : Performance Co-Pilot (PCP) metrics from the Docker daemon
podman-docker.noarch : Emulate Docker CLI using podman
=======================================
名称 匹配:docker
========================================
docker-ce.x86_64 : The open-source application container engine
docker-ce-cli.x86_64 : The open-source application container engine
输出结果:
docker-buildx-plugin(构建多平台镜像)、docker-compose-plugin(容器编排)、podman-docker(Podman 兼容 Docker 命令的插件)等)。docker-ce(Docker 引擎主程序)、docker-ce-cli(Docker 命令行工具)—— 这是安装的核心目标。[root@docker-node1 ~]# dnf install docker-ce -y
作用:修改 Docker 服务的启动参数配置文件(docker.service 是 systemd 管理 Docker 服务的核心文件)。
[root@docker-node1 ~]# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --iptables=true
ExecStart:定义 Docker 守护进程(dockerd)的启动命令。-H fd://:Docker 守护进程的默认通信方式(通过文件描述符通信)。--containerd=/run/containerd/containerd.sock:指定 Docker 依赖的 containerd 运行时的套接字文件。--iptables=true:强制 Docker 使用 Linux iptables 管理容器网络(确保容器的端口映射、网络转发正常生效)。作用:确保 Linux 内核加载 br_netfilter 模块(网桥网络过滤模块),是 Docker 容器网络正常工作的核心依赖。
[root@docker-node1 ~]# echo br_netfilter > /etc/modules-load.d/docker_mod.conf
[root@docker-node1 ~]# modprobe -a br_netfilter
br_netfilter 写入 /etc/modules-load.d/docker_mod.conf,实现开机自动加载该模块。modprobe -a br_netfilter:立即手动加载该模块(无需重启系统)。作用:调整内核网络参数,解决 Docker 容器网络转发、iptables 规则生效的问题。
[root@docker-node1 ~]# cat > /etc/sysctl.d/docker.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
net.bridge.bridge-nf-call-iptables = 1:让网桥设备的数据包触发 iptables 规则(确保容器网络的防火墙规则生效)。net.bridge.bridge-nf-call-ip6tables = 1:同上,针对 IPv6 网络(若不用 IPv6 可设为 0)。net.ipv4.ip_forward = 1:开启 IPv4 转发(容器跨网络通信、端口映射的核心前提)。重新加载所有 /etc/sysctl.d/ 目录下的内核参数配置,使修改立即生效(无需重启系统)。
[root@docker-node1 ~]# sysctl --system
[root@docker-node1 ~]# systemctl enable --now docker
执行 systemctl status docker 可查看 Docker 服务状态(显示 active (running) 即为成功)。
执行 docker version 可查看 Docker 客户端 / 服务端版本,验证安装完整性。
作用:Docker 默认从官方镜像仓库拉取镜像,速度较慢,配置国内加速器可以大幅提升镜像下载速度。
# 1. 创建/编辑 Docker 守护进程配置文件 daemon.json
[root@docker-node1 ~]# cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": ["https://docker.1ms.run"] # 指定加速器地址
}
EOF
# 2. 重启 Docker 服务,使配置生效
[root@docker-node1 ~]# systemctl restart docker
# 3. 验证配置是否生效
[root@docker-node1 ~]# docker info
# 输出中会显示 Registry Mirrors: https://docker.1ms.run/,说明配置成功
Docker 核心操作围绕镜像(Image) 和容器(Container) 展开。
镜像是容器的模板,包含运行应用所需的所有文件、依赖和配置。
| 命令 | 作用 |
|---|---|
docker images | 查看本地已下载的镜像 |
docker search 镜像名 | 搜索 Docker 仓库中的镜像 |
docker pull 镜像名 [:标签] | 下载镜像(默认拉取 latest 最新标签) |
docker history 镜像名 [:标签] | 查看镜像的构建历史 |
docker save -o 本地文件名.tar 镜像名 [:标签] | 导出镜像到本地文件 |
docker rmi 镜像名 [:标签] | 删除本地镜像 |
docker load -i 本地 tar 文件 | 导入本地 tar 文件为镜像 |
docker commit -m "备注" 容器名 新镜像名 [:标签] | 将容器的修改提交为新镜像 |
# 镜像查看
[root@docker-node1 ~]# docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
# 搜索镜像
[root@docker-node1 ~]# docker search nginx
NAME DESCRIPTION STARS OFFICIAL
nginx Official build of Nginx. 21206
# 下载镜像
[root@docker-node1 ~]# docker pull nginx
# 查看镜像提交历史
[root@docker-node1 ~]# docker history busybox:latest
IMAGE CREATED CREATED BY SIZE COMMENT
b3255e7dfbcd 17 months ago BusyBox 1.37.0 (glibc), Debian 13 4.49MB
# 导出镜像
[root@docker-node1 ~]# docker save -o game2048-latest.tar example/game2048:latest
# 删除镜像
[root@docker-node1 ~]# docker rmi example/mario:latest
# 导入镜像
[root@docker-node1 ~]# docker load -i game2048-latest.tar
# 修改镜像
[root@docker-node1 ~]# docker commit -m "add file" test busybox-file:latest
sha256:31a32089d025a5a54f144f15319cc6fb55be1b41d049f8905a472d5a028e
容器是镜像的运行实例,是动态的、可交互的环境。
| 命令 | 作用 |
|---|---|
docker run -d --name 容器名 镜像名 [:标签] | 后台运行容器(-d:守护进程模式) |
docker run -it --name 容器名 镜像名 [:标签] | 交互模式运行容器(-i:交互;-t:终端) |
# 运行镜像
[root@docker-node1 ~]# docker run -d --name web nginx:1.26
f3e369725fab95d48779eaa556941b735aae841efe09bb1d28bca89923c44ee4
# 交互模式运行容器
[root@docker-node1 ~]# docker run -it --name busybox busybox:latest
| 命令 | 作用 |
|---|---|
docker ps | 查看运行中的容器 |
docker ps -a | 查看所有容器(运行中 + 已停止) |
docker inspect 容器名/ID | 查看容器详细信息(JSON 格式) |
# 查看运行容器
[root@docker-node1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f3e369725fab nginx:1.26 "/docker-entrypoint…" 2 seconds ago Up 2 seconds 80/tcp web
[root@docker-node1 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d1b27167a247 busybox:latest "sh" 2 minutes ago Up 1 second busybox
# 查看容器信息
[root@docker-node1 ~]# docker inspect busybox
| 命令 | 作用 |
|---|---|
docker start 容器名/ID | 启动已停止的容器 |
docker stop 容器名/ID | 优雅停止容器(发送终止信号,允许收尾) |
docker kill 容器名/ID | 强制杀死容器(立即终止,无收尾) |
docker attach 容器名/ID | 进入已运行的交互容器 |
docker rm [-f] 容器名/ID | 删除容器(-f:强制删除运行中的容器) |
[root@docker-node1 ~]# docker start busybox # 开启容器
[root@docker-node1 ~]# docker stop busybox # 停止容器
[root@docker-node1 ~]# docker kill busybox # 杀死容器,可以使用信号
[root@docker-node1 ~]# docker rm -f busybox # 容器删除
[root@docker-node1 ~]# docker attach busybox # 退出交互容器不对其停止
# [ctrl]+[p]+[q] # 按键
| 命令 | 作用 |
|---|---|
docker exec 容器名 命令 | 在运行中的容器执行非交互命令 |
docker exec -it 容器名 终端 | 在运行中的容器执行交互命令 |
docker cp 容器名:路径 本地路径 | 从容器复制文件到本地 |
docker cp 本地路径 容器名:路径 | 从本地复制文件到容器 |
# 在已经运行的容器中执行指定命令
[root@docker-node1 ~]# docker exec busybox touch /root/haha # 非交互
[root@docker-node1 ~]# docker exec busybox ls /root
file1 file2 haha
[root@docker-node1 ~]# docker exec -it web /bin/bash # 交互的
root@f3e369725fab:/#
[root@docker-node1 ~]# docker cp test:/root/file /mnt # 文件在镜像中的复制
Successfully copied 1.54kB to /mnt
[root@docker-node1 ~]# docker cp /etc/passwd test:/root/
Successfully copied 3.07kB to test:/root/
docker 目录作为构建上下文(镜像构建时,Docker 会读取该目录下的文件 / 目录);vim Dockerfile 用于编写镜像构建的指令集。# 建立构建目录
[root@docker-node1 ~]# mkdir docker
[root@docker-node1 ~]# cd docker/
# 编写构建规则文件
[root@docker-node1 docker]# vim Dockerfile
FROM 是 Dockerfile 的必备指令,指定构建镜像的基础镜像;latest 是镜像标签,代表最新版本。FROM busybox:latest
COPY 源文件 目标路径:将宿主机构建上下文内的文件,复制到镜像的 /root 目录下;docker build -t 镜像名:标签 构建上下文:核心构建命令,.必须加,代表当前目录是构建上下文(Docker 会读取该目录下的文件供构建使用)。# 先创建本地文件
[root@docker-node1 docker]# echo example > example
[root@docker-node1 docker]# cat example
example
# 修改 Dockerfile
[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
COPY example /root
# 构建镜像(-t 打标签,. 代表构建上下文为当前目录)
[root@docker-node1 docker]# docker build -t example:v1 .
[+] Building 0.2s (7/7) FINISHED
docker:default => [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 78B 0.0s
=> [internal] load metadata for docker.io/library/busybox:latest 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 46B 0.0s
=> [1/2] FROM docker.io/library/busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac9 0.0s
=> => resolve docker.io/library/busybox:latest@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac9 0.0s
=> [2/2] COPY example /root 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:3e240075ea92a383ccc7b8249faf4fbc049465ac3e490ddb90b6c759a35a2be 0.0s
=> => exporting config sha256:16a6f00150015d0df6a11f1c609afba2c28bdf3d984305922b440e52cd7f9dc2 0.0s
=> => exporting attestation manifest sha256:74b85b3b3cbdaa72964271d4d7c0fc371c727bb6070df262f 0.0s
=> => exporting manifest list sha256:0a7e2bc13bfdb457442d8bc653987c1a642f86858f6bc233dc120d6 0.0s
=> => naming to docker.io/library/example:v1 0.0s
=> => unpacking to docker.io/library/example:v1
LABEL KEY=VALUE:给镜像添加自定义元数据(如作者、版本、用途),方便后续管理 / 筛选镜像。
[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
COPY example /root
LABEL maintainer=admin # 新增标签:维护者=admin
[root@docker-node1 docker]# docker build -t admin:v1 .
# 1. 普通复制(和 COPY 一致)
[root@docker-node1 docker]# echo lee > lee
# 修改 Dockerfile
[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
LABEL Creater=lee
COPY example /root
ADD lee /root
# 构建镜像
[root@docker-node1 docker]# docker build -t lee:v2 .
ADD 功能 > COPY:
COPY 一致;ADD 会自动解压到目标路径(COPY 仅复制压缩包,不解压);ADD https://xxx/file.tar.gz /tmp)。# 2. 解压压缩包(COPY 不支持)
[root@docker-node1 docker]# tar zcf bin.tar.gz /bin # 打包/bin 目录
tar: 从成员名中删除开头的'/'
[root@docker-node1 docker]# ls bin.tar.gz Dockerfile lee example
# 修改 Dockerfile
[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
LABEL Creater=lee
COPY bin.tar.gz /root # 仅复制压缩包,不解压
ADD bin.tar.gz /mnt # 复制 + 自动解压到/mnt
# 构建并测试
[root@docker-node1 docker]# docker build -t lee:v3 .
[root@docker-node1 docker]# docker run -it --name test --rm lee:v3 /
# ls
bin dev etc home lib lib64 mnt proc root sys tmp usr var
/
# ls /root/
# COPY 的结果:仅压缩包 bin.tar.gz
/
# ls /mnt/
# ADD 的结果:解压后的 bin 目录
bin
ENV 变量名=值:在镜像内设置环境变量,后续指令(RUN/CMD/ENTRYPOINT)可通过 $变量名 引用;[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
LABEL Creater=lee
ENV NAME=example # 定义环境变量 NAME=example
RUN ["/bin/sh","-c", "touch /root/$NAME" ] # 使用变量:创建/root/example 文件
[root@docker-node1 docker]# docker build -t lee:v4 .
EXPOSE 端口:仅声明容器要暴露的端口(告诉使用者该容器会用这个端口),不自动映射(运行容器时需加 -p 宿主机端口:容器端口 才会映射);[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
LABEL Creater=lee
ENV NAME=example
EXPOSE 8080 # 声明暴露 8080/tcp 端口
RUN ["/bin/sh","-c","touch /root/$NAME" ]
[root@docker-node1 docker]# docker build -t lee:v5 .
# 查看镜像历史(验证 EXPOSE 指令)
[root@docker-node1 docker]# docker history lee:v5
IMAGE CREATED CREATED BY SIZE COMMENT
1391576721c7 2 minutes ago RUN /bin/sh -c touch /root/$NAME # buildkit 0B buildkit.dockerfile.v0 <missing>
2 minutes ago EXPOSE [8080/tcp] 0B buildkit.dockerfile.v0 <missing>
2 minutes ago ENV NAME=example 0B buildkit.dockerfile.v0 <missing>
2 minutes ago LABEL Creater=lee 0B buildkit.dockerfile.v0 <missing>
17 months ago BusyBox 1.37.0 (glibc), Debian 13
VOLUME "路径":给容器定义匿名数据卷,容器运行时,该目录会自动挂载到宿主机的 /var/lib/docker/volumes/ 下的随机目录;[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
LABEL Creater=lee
ENV NAME=example
EXPOSE 8080
VOLUME "/mnt" # 定义匿名卷:容器的/mnt 目录挂载到宿主机匿名卷
RUN ["/bin/sh","-c", "touch /root/$NAME" ]
[root@docker-node1 docker]# docker build -t lee:v6 .
# 测试卷挂载
[root@docker-node1 docker]# docker run -it --name test --rm lee:v6
# 宿主机查看挂载信息(grep 过滤 Mounts 段)
[root@docker-node1 ~]# docker inspect test | grep -i mounts -A10
"Mounts": [
{
"Type": "volume",
"Name": "951e0ad881eda84a037614657b89cae88adac7c600ac03cd9505c067cee04741",
"Source": "/var/lib/docker/volumes/951e0ad881eda84a037614657b89cae88adac7c600ac03cd9505c067cee04741/_data", # 宿主机实际路径
"Destination": "/mnt", # 容器内挂载点
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
# 宿主机往卷里写文件
[root@docker-node1 ~]# cd "/var/lib/docker/volumes/951e0ad881eda84a037614657b89cae88adac7c600ac03cd9505c067cee04741/_data"
[root@docker-node1 _data]# touch lee{1..5} # 容器内查看(数据持久化)
[root@docker-node1 docker]# docker run -it --name test --rm lee:v6 /
# ls /mnt/
lee1 lee2 lee3 lee4 lee5
WORKDIR 路径:设置容器启动后的默认工作目录(类似 cd 命令);[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
LABEL Creater=lee
ENV NAME=example
EXPOSE 8080
VOLUME "/mnt"
RUN ["/bin/sh","-c", "touch /root/$NAME" ]
WORKDIR "/mnt" # 设置容器启动后的默认工作目录
[root@docker-node1 docker]# docker build -t lee:v7 .
# 运行容器:默认进入/mnt 目录
[root@docker-node1 docker]# docker run -it --name test --rm lee:v7 /mnt
#
# 提示符显示当前目录是/mnt
CMD:定义容器启动时默认执行的命令;docker run 时若指定自定义命令,会覆盖CMD的默认命令;CMD ["/bin/echo","$NAME"] 不会解析环境变量(输出 $NAME),需通过 /bin/sh -c 包裹(如 CMD ["/bin/sh","-c","echo $NAME"])。写法 1:shell 格式
[root@docker-node1 docker]# vim Dockerfile
.....
VOLUME "/mnt"
RUN ["/bin/sh","-c","touch /root/$NAME" ]
WORKDIR "/mnt"
CMD echo $NAME # shell 格式
[root@docker-node1 docker]# docker build -t lee:v8 .
# shell 格式
写法 2:exec 格式(推荐)
[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
...
VOLUME "/mnt"
RUN ["/bin/sh","-c","touch /root/$NAME" ]
WORKDIR "/mnt"
CMD ["/bin/sh", "-c", "/bin/echo $NAME"] # exec 格式(JSON 数组)
[root@docker-node1 docker]# docker build -t lee:v9 .
# exec 格式
# 正常运行:执行 CMD 命令,输出 example
[root@docker-node1 docker]# docker run -it --name test --rm lee:v8 example
[root@docker-node1 docker]# docker run -it --name test --rm lee:v9 example
# 覆盖 CMD:运行时指定命令,会替换默认 CMD
[root@docker-node1 docker]# docker run -it --name test --rm lee:v9 echo haha
haha
ENTRYPOINT:定义容器启动时必须执行的核心命令,运行容器时指定的自定义命令无法覆盖它;[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
...
RUN ["/bin/sh","-c","touch /root/$NAME" ]
WORKDIR "/mnt"
ENTRYPOINT ["/bin/sh","-c", "echo $NAME"] # 替换 CMD 为 ENTRYPOINT
[root@docker-node1 docker]# docker build -t lee:v8 .
# 正常运行:输出 example
[root@docker-node1 docker]# docker run -it --name test --rm lee:v8 example
# 尝试覆盖(无效):仍执行 ENTRYPOINT 的命令,输出 example
[root@docker-node1 docker]# docker run -it --name test --rm lee:v8 echo haha
example
| 指令 | 核心作用 | 关键特性 |
|---|---|---|
| FROM | 指定基础镜像 | 必备,所有指令的基础 |
| COPY | 复制宿主机文件到镜像 | 仅复制,不解压 |
| ADD | 复制文件到镜像 | 支持解压压缩包、远程 URL |
| LABEL | 添加镜像元数据 | 方便镜像管理 / 筛选 |
| ENV | 设置环境变量 | 镜像 / 容器内可引用 |
| EXPOSE | 声明容器暴露端口 | 仅声明,不自动映射 |
| VOLUME | 定义数据卷 | 持久化数据,容器销毁数据不丢 |
| WORKDIR | 设置工作目录 | 后续指令的默认目录 |
| CMD | 容器启动默认命令 | 可被 docker run 命令覆盖 |
| ENTRYPOINT | 容器启动固定命令 | 不可被覆盖,核心逻辑固定 |
命令执行后,Docker 从官方仓库下载 CentOS 7 镜像,并输出下载摘要、状态等信息,确认镜像拉取成功。
[root@docker-node1 docker]# docker pull centos:7
7: Pulling from library/centos
2d473b07cdd5: Pull complete
Digest: sha256:be65f488b7764ad3638f236b7b515b3678369a5124c47b8d32916d6487418ea4
Status: Downloaded newer image for centos:7
docker.io/library/centos:7
[root@docker-node1 docker]# docker run -it --name centos centos:7 /bin/bash
[root@789703258e31 /]# uname -a # 查看容器内核版本
Linux 789703258e31 5.14.0-570.12.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Apr 4 10:41:31 EDT 2025 x86_64 x86_64 x86_64 GNU/Linux
[root@789703258e31 /]# cat /etc/centos-release # 验证容器内系统版本
CentOS Linux release 7.9.2009 (Core)
[root@789703258e31 /]# cd /etc/yum.repos.d/
[root@789703258e31 yum.repos.d]# ls # 查看 CentOS 默认 yum 仓库配置文件
CentOS-Base.repo CentOS-Debuginfo.repo CentOS-Sources.repo CentOS-fasttrack.repo
CentOS-CR.repo CentOS-Media.repo CentOS-Vault.repo CentOS-x86_64-kernel.repo
[root@789703258e31 yum.repos.d]# exit
exit # 删除临时容器(清理测试环境)
[root@docker-node1 docker]# docker rm centos centos
删除当前目录下无关的文件,仅保留后续构建需要的 Dockerfile,避免构建时引入冗余内容。
[root@docker-node1 docker]# ls bin.tar.gz Dockerfile lee example
# 删掉不需要的文件
[root@docker-node1 docker]# rm -fr bin.tar.gz
[root@docker-node1 docker]# ls Dockerfile lee example
[root@docker-node1 docker]# rm -rf lee example
[root@docker-node1 docker]# ls Dockerfile
docker images | awk '/lee/{print $1}' 筛选出名称包含 lee 的镜像(为之前实验残留的镜像);docker images | awk '/<lee>/{system("docker rmi " $1)}' 批量删除这些镜像,释放磁盘空间,避免与新构建镜像冲突。[root@docker-node1 docker]# docker images | awk '/lee/{print $1}'
WARNING: This output is designed for human readability. For machine-readable output, please use --format.
lee:v1 lee:v2 lee:v3 lee:v4 lee:v5 lee:v6 lee:v7 lee:v8 lee:v9 example/game2048:latest example/mario:latest
# 删掉之前实验做得文件
[root@docker-node1 docker]# docker images | awk '/<lee>/{system("docker rmi " $1)}'
WARNING: This output is designed for human readability. For machine-readable output, please use --format.
Untagged: lee:v1 Deleted: sha256:d53420bc3bac49eefebe3efb6cd52a23a81113615714247e96e5bbc3665a0d5
Untagged: lee:v2 Deleted: sha256:fdc8ae6d0590d0520326bd2eb3a6cf62293ba47dc6b10438a243225
... (省略部分输出)
[root@docker-node1 docker]# vim Dockerfile
FROM centos:7 # 基于官方 CentOS 7 镜像作为基础镜像
LABEL Creater=admin # 添加镜像标签(标注创建者,可选)
RUN ["/bin/bash","-c","rm -fr /etc/yum.repos.d/*"] # 清空默认 yum 仓库配置文件
# 构建镜像
[root@docker-node1 docker]# docker build -t centos-7:repo .
# 启动容器验证
[root@docker-node1 docker]# docker run -it --rm --name centos centos-7:repo /bin/bash
# 确认默认 repo 文件已被清空(目录为空)
[root@d7addd9a7f86 /]# ls /etc/yum.repos.d/
编辑 Dockerfile,新增 COPY 指令,将本地自定义 repo 文件复制到容器内
[root@docker-node1 docker]# vim Dockerfile
FROM centos:7
LABEL Creater=admin
RUN ["/bin/bash","-c","rm -fr /etc/yum.repos.d/*"]
COPY centos7.repo /etc/yum.repos.d/centos7.repo
创建自定义 centos7.repo 文件
[root@docker-node1 docker]# vim centos7.repo
[centos7]
name = centos7
baseurl = https://mirrors.aliyun.com/centos-vault/7.9.2009/os/x86_64/
gpgcheck = 0
重新构建镜像
[root@docker-node1 docker]# docker build -t centos-7:repo . # 后面有个小点不要忘了
# 启动容器
[root@docker-node1 docker]# docker run -it --rm --name centos centos-7:repo /bin/bash
验证仓库可用性:启动容器后执行 yum install gcc -y,测试 yum 能否正常安装软件(核心目的:验证自定义仓库可正常使用)
[root@c659055cb90e /]# yum install gcc -y
| 命令 | 作用 |
|---|---|
docker run -it --rm ... | --rm 表示容器退出后自动删除,适合临时测试 |
rm -fr /etc/yum.repos.d/* | 强制删除目录下所有文件(清空默认 repo) |
gpgcheck = 0 | 关闭软件包 GPG 签名校验(阿里云镜像源可信任,关闭后避免安装时校验报错) |
docker build -t centos-7:repo . | -t 指定镜像标签,. 表示从当前目录读取 Dockerfile 构建 |
[root@docker-node1 ~]# ls anaconda-ks.cfg centos7.tar game2048-latest.tar nginx-latest.tar busy-latest.tar docker mario-latest.tar
[root@docker-node1 ~]# mv docker/ /mnt/
[root@docker-node1 ~]# rm -fr *
[root@docker-node1 ~]# ls
[root@docker-node1 ~]# mv /mnt/docker/
访问 nginx.org 网站,找 download 复制连接
[root@docker-node1 docker]# wget http://nginx.org/download/nginx-1.26.3.tar.gz
[root@docker-node1 docker]# ls centos7.repo Dockerfile nginx-1.26.3.tar.gz
centos-7:repo 基础镜像,完成 Nginx 编译依赖安装、源码编译、安装,暴露 80 端口、定义数据卷、设置启动命令;[root@docker-node1 docker]# vim Dockerfile
FROM centos-7:repo
LABEL Creater=admin
ADD nginx-1.26.3.tar.gz /root
RUN yum install gcc make pcre-devel openssl-devel -y
WORKDIR /root/nginx-1.26.3
RUN ./configure --with-http_ssl_module --with-http_stub_status_module
RUN make
RUN make install
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
docker build 构建 webserver:v1 镜像,运行容器后通过 curl 验证 Nginx 可访问。[root@docker-node1 docker]# docker build -t webserver:v1 .
[root@docker-node1 docker]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
nginx:latest bc45d248c4e1 237MB 65.8MB
example/game2048:latest 8a34fb9cb168 77.2MB 17.8MB
example/mario:latest 7758988210df 298MB 73.7MB
webserver:v1 59310e4a1ce4 508MB 132MB
#有,后面优化后对比
[root@docker-node1 docker]# docker run -d --name webserver --rm webserver:v1
0432fd82cb974bbc7375839c3e4f850260c39ebb975f2bf75db915e26bb440f4
[root@docker-node1 docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0432fd82cb97 webserver:v1 "/usr/local/nginx/sb…" 6 seconds ago Up 6 seconds 80/tcp webserver
[root@docker-node1 docker]# docker inspect webserver
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"DriverOpts": null,
"GwPriority": 0,
"NetworkID": "b227442fc0626f7fa11fe74182ae82338564f45bce8bad7779b59d8709d",
"EndpointID": "5417be037de8148c9fcd579cff2fed90106021c8b22943885e8e27a9026e8b",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"MacAddress": "3a:e2:27:5c:2f:c2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": null
}
}
[root@docker-node1 docker]# curl 172.17.0.2
<p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>
构建出的 webserver:v1 镜像体积达 510MB(内容大小 132MB),核心原因:
RUN 指令生成独立镜像层,层越多镜像冗余越大;centos-7:repo 本身体积较大(299MB)。把产生的临时文件全部清理
[root@docker-node1 docker]# vim Dockerfile
FROM centos-7:repo
LABEL Creater=admin
ADD nginx-1.26.3.tar.gz /root
RUN yum install gcc make pcre-devel openssl-devel -y
WORKDIR /root/nginx-1.26.3
RUN ./configure --with-http_ssl_module --with-http_stub_status_module
RUN make
RUN make install
RUN rm -fr /root/nginx-1.26.3 # 优化地方,删除 Nginx 源码目录
RUN yum clean all # 优化地方,清理 yum 缓存
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
[root@docker-node1 docker]# docker build -t webserver:v1 .
镜像体积仍为 510MB,未优化成功。原因是:新增的 RUN 指令又生成了 2 个镜像层,抵消了清理文件的体积优化效果。
[root@docker-node1 docker]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
nginx:latest bc45d248c4e1 237MB 65.8MB
example/game2048:latest 8a34fb9cb168 77.2MB 17.8MB
example/mario:latest 7758988210df 298MB 73.7MB
webserver:v1 eb4c204aceb8 510MB 132MB
#没有改进,还是一样的,是因为改进后多了两层
第一次优化未成功,是因为多了两个镜像层,那么我就把镜像层缩减:Dockerfile 中合并多个 RUN 指令为一个(通过 && 串联命令),减少镜像层数,同时完成编译 + 清理操作,避免分层冗余。
[root@docker-node1 docker]# vim Dockerfile
FROM centos-7:repo
LABEL Creater=admin
ADD nginx-1.26.3.tar.gz /root
WORKDIR /root/nginx-1.26.3
RUN yum install gcc make pcre-devel openssl-devel -y && ./configure --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -fr /root/nginx-1.26.3 && yum clean all
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
# 优化后构建为 v2
[root@docker-node1 docker]# docker build -t webserver:v2 .
构建出 webserver:v2 镜像体积降至426MB(内容大小 110MB),相比 v1 明显减小,验证了「减少镜像层」的优化效果。
# 对比 v1 和 v2
[root@docker-node1 docker]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
nginx:latest bc45d248c4e1 237MB 65.8MB
example/game2048:latest 8a34fb9cb168 77.2MB 17.8MB
example/mario:latest 7758988210df 298MB 73.7MB
webserver:v1 eb4c204aceb8 510MB 132MB
webserver:v2 4961959ef185 426MB 110MB
#优化后依然可以用
[root@docker-node1 docker]# docker run -d --name webserver --rm webserver:v2
1048a0785474d756fc497e9cb94615a9c5e2ad780b1a49c69cfca6549f95155
[root@docker-node1 docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1048a0785474 webserver:v2 "/usr/local/nginx/sb…" 5 seconds ago Up 4 seconds 80/tcp webserver
利用 Docker多阶段构建特性,将「编译构建」和「最终运行」分离:
centos-7:repo 完成 Nginx 编译、安装、清理,仅保留编译后的产物;centos-7:repo,仅复制第一阶段的 Nginx 运行产物,舍弃编译依赖、源码等冗余文件。[root@docker-node1 docker]# docker rm -f webserver #先把之前的删掉 webserver
[root@docker-node1 docker]# vim Dockerfile
# 阶段 1:编译构建(命名为 admin)
FROM centos-7:repo AS admin
LABEL Creater=admin
ADD nginx-1.26.3.tar.gz /root
WORKDIR /root/nginx-1.26.3
RUN yum install gcc make pcre-devel openssl-devel -y && ./configure --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -fr /root/nginx-1.26.3 && yum clean all
# 阶段 2:最终运行镜像
FROM centos-7:repo
COPY --from=admin /usr/local/nginx /usr/local/nginx
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
构建出 webserver:v3 镜像体积降至308MB(内容大小 79MB),进一步精简了镜像(舍弃了编译阶段的依赖、工具等)。
[root@docker-node1 docker]# docker build -t webserver:v3 .
[root@docker-node1 docker]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
nginx:latest bc45d248c4e1 237MB 65.8MB
example/game2048:latest 8a34fb9cb168 77.2MB 17.8MB
example/mario:latest 7758988210df 298MB 73.7MB
webserver:v1 eb4c204aceb8 510MB 132MB
webserver:v2 4961959ef185 426MB 110MB
webserver:v3 28390cffd186 308MB 79MB
#可以看到比之前更小了
gcr.io/distroless/base-debian11(极简 Debian 镜像,仅含运行必需的库和文件,体积仅 47.9MB)替代 centos-7:repo;# 把 nginx-1.23.tar.gz 和 debian11.tar.gz 放到/root/目录下
[root@docker-node1 ~]# ls debian11.tar.gz docker nginx-1.23.tar.gz
[root@docker-node1 ~]# docker load -i debian11.tar.gz
Loaded image: gcr.io/distroless/base-debian11:latest
[root@docker-node1 ~]# docker load -i nginx-1.23.tar.gz
Loaded image: nginx:1.23
[root@docker-node1 ~]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
gcr.io/distroless/base-debian11:latest cac381e9184d 47.9MB 22.4MB
nginx:1.23 a087ed751769 301MB 147MB
nginx:latest bc45d248c4e1 237MB 65.8MB
example/game2048:latest 8a34fb9cb168 77.2MB 17.8MB
example/mario:latest 7758988210df 298MB 73.7MB
webserver:v1 eb4c204aceb8 510MB 132MB
webserver:v2 4961959ef185 426MB 110MB
webserver:v3 28390cffd186 308MB 79MB
[root@docker-node1 docker]# vim Dockerfile
# 阶段 1:基于 nginx:1.23 提取运行依赖
FROM nginx:1.23 AS admin
ARG TIME_ZONE
RUN mkdir -p /opt/var/cache/nginx && \
cp -a --parents /usr/lib/nginx /opt && \
cp -a --parents /usr/share/nginx /opt && \
cp -a --parents /var/log/nginx /opt && \
cp -aL --parents /var/run /opt && \
cp -a --parents /etc/nginx /opt && \
cp -a --parents /etc/passwd /opt && \
cp -a --parents /etc/group /opt && \
cp -a --parents /usr/sbin/nginx /opt && \
cp -a --parents /usr/sbin/nginx-debug /opt && \
cp -a --parents /lib/x86_64-linux-gnu/ld-* /opt && \
cp -a --parents /usr/lib/x86_64-linux-gnu/libpcre* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libc* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libdl* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libpthread* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcrypt* /opt && \
cp -a --parents /usr/lib/x86_64-linux-gnu/libssl.so.* /opt && \
cp -a --parents /usr/lib/x86_64-linux-gnu/libcrypto.so.* /opt && \
cp /usr/share/zoneinfo/${TIME_ZONE:-ROC} /opt/etc/localtime
# 阶段 2:基于极简镜像构建最终镜像
FROM gcr.io/distroless/base-debian11
COPY --from=admin /opt /
EXPOSE 80 443
ENTRYPOINT ["nginx", "-g", "daemon off;"]
构建出 webserver:v4 镜像体积仅67.9MB(内容大小 28.4MB),相比初始的 v1(510MB)缩减了 86% 以上,是最优的优化方案。
[root@docker-node1 docker]# docker build -t webserver:v4 .
[root@docker-node1 docker]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
gcr.io/distroless/base-debian11:latest cac381e9184d 47.9MB 22.4MB
nginx:1.23 a087ed751769 301MB 147MB
nginx:latest bc45d248c4e1 237MB 65.8MB
example/game2048:latest 8a34fb9cb168 77.2MB 17.8MB
example/mario:latest 7758988210df 298MB 73.7MB
webserver:v1 eb4c204aceb8 510MB 132MB
webserver:v2 4961959ef185 426MB 110MB
webserver:v3 28390cffd186 308MB 79MB
webserver:v4 b0773b4ea1d1 67.9MB 28.4MB
#比之前小了
测试
[root@docker-node1 docker]# docker run -d --name webserver --rm webserver:v4
03e04667199d0f47fe6caffaf485b5aff8c6ec0654e984fdf7483e74f424ebd2
[root@docker-node1 docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e04667199d webserver:v4 "nginx -g 'daemon of…" 51 seconds ago Up 51 seconds 80/tcp, 443/tcp webserver
[root@docker-node1 docker]# docker inspect webserver
[root@docker-node1 docker]# curl 172.17.0.2
此实验需要两台主机,docker-node1(172.25.254.10,作为仓库服务器)、docker-node2(172.25.254.20,作为客户端)
1.配置文件同步:将 node1 的 Docker 内核模块配置、系统参数配置、yum 源配置复制到 node2,保证两台主机 Docker 运行环境一致:
# 复制内核模块配置:
[root@docker-node1 docker]# scp /etc/modules-load.d/docker_mod.conf [email protected]:/etc/modules-load.d/docker_mod.conf
docker_mod.conf 100% 13 14.4KB/s 00:00
# 复制系统参数配置
[root@docker-node1 docker]# scp /etc/sysctl.d/docker.conf [email protected]:/etc/sysctl.d/docker.conf
docker.conf 100% 103 277.4KB/s 00:00
# 复制 Docker yum 源
[root@docker-node1 docker]# scp /etc/yum.repos.d/docker.repo [email protected]:/etc/yum.repos.d/docker.repo
docker.repo 100% 113 159.2KB/s 00:00
在 node2 上加载 br_netfilter(Docker 网络依赖模块):modprobe -a br_netfilter
# 加载模块
[root@docker-node2 ~]# cat /etc/modules-load.d/docker_mod.conf
br_netfilter
[root@docker-node2 ~]# modprobe -a br_netfilter
在 node2 上通过 dnf 安装 docker-ce:dnf install docker-ce -y
[root@docker-node2 ~]# dnf install docker-ce -y
registry.tar 包上传到 node1,通过 docker load -i registry.tar 加载镜像(最终得到 registry:latest 镜像)。[root@docker-node1 docker]# docker load -i registry.tar
Loaded image: registry:latest
docker images:确认镜像已加载,Registry 镜像大小约 77.3MB;
[root@docker-node1 docker]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest b3255e7dfbcd 6.7MB 2.22MB
centos-7:repo 55878ae91f0c 299MB 76.1MB
gcr.io/distroless/base-debian11:latest cac381e9184d 47.9MB 22.4MB
nginx:1.23 a087ed751769 301MB 147MB
nginx:latest bc45d248c4e1 237MB 65.8MB
registry:latest 6c5666b861f3 77.3MB 18.8MB
docker history registry:latest:查看镜像构建信息,确认 Registry 默认暴露 5000 端口(镜像仓库的默认通信端口)。
[root@docker-node1 docker]# docker history registry:latest
IMAGE CREATED CREATED BY SIZE COMMENT
6c5666b861f3 6 weeks ago CMD ["/etc/distribution/config.yml"] 0B buildkit.dockerfile.v0 <missing>
6 weeks ago ENTRYPOINT ["/entrypoint.sh"] 0B buildkit.dockerfile.v0 <missing>
6 weeks ago COPY entrypoint.sh /entrypoint.sh # buildkit 4.1kB buildkit.dockerfile.v0 <missing>
6 weeks ago EXPOSE map[5000/tcp:{}]
[root@docker-node1 docker]# docker run -d -p 5000:5000 --restart=always --name registry registry:latest
192c6b85055bac37f778533f5734dfdd9bd20cece020c54bc40798c6e6b1a59
-d:后台运行容器;-p 5000:5000:将主机 5000 端口映射到容器 5000 端口;--restart=always:容器异常退出时自动重启;--name registry:给容器命名为 registry。[root@docker-node1 docker]# docker inspect registry
# 部分
"Mounts": [
{
"Type": "volume",
"Name": "1e25784ae2b8f543f474160981b58eaad2f76de167433394ceffb195b7ad2ab3",
"Source": "/var/lib/docker/volumes/1e25784ae2b8f543f474160981b58eaad2f76de167433394ceffb195b7ad2ab3/_data",
"Destination": "/var/lib/registry",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
Docker 默认要求镜像仓库使用 HTTPS 协议通信,而我们搭建的是纯 HTTP 的非加密仓库,因此需要配置 insecure-registries(不安全仓库)来跳过 HTTPS 验证:
[root@docker-node1 docker]# vim /etc/docker/daemon.json
{
"insecure-registries" : ["http://172.25.254.10:5000"]
}
# 重启 Docker 使配置生效
[root@docker-node1 docker]# systemctl restart docker
# 给本地镜像打标签
[root@docker-node1 docker]# docker tag webserver:v4 172.25.254.10:5000/webserver:v4
# 推送镜像
[root@docker-node1 docker]# docker push 172.25.254.10:5000/webserver:v4
# 推送成功后验证
[root@docker-node1 docker]# curl 172.25.254.10:5000/v2/_catalog
{"repositories":["webserver"]}
# 表示仓库已存在该镜像
[root@docker-node1 docker]# vim /etc/docker/daemon.json
{
"insecure-registries" : ["http://172.25.254.10:5000"]
}
[root@docker-node2 ~]# systemctl restart docker
[root@docker-node2 ~]# docker info
Insecure Registries:
172.25.254.10:5000
127.0.0.0/8
::1/128
Live Restore Enabled: false
Firewall Backend: iptables
[root@docker-node2 ~]# docker pull 172.25.254.10:5000/webserver:v4
# 可看到该镜像,说明跨主机下载成功
[root@docker-node2 ~]# docker images i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
172.25.254.10:5000/webserver:v4 b0773b4ea1d1 67.9MB 28.4MB
清理之前可能存在的非加密仓库配置,避免冲突。
# 清空/重置 daemon.json(删除之前非加密仓库的配置)
[root@docker-node1 ~]# vim /etc/docker/daemon.json
[root@docker-node2 ~]# > /etc/docker/daemon.json
# 重启 Docker 使配置生效
[root@docker-node1 ~]# systemctl restart docker
[root@docker-node2 ~]# systemctl restart docker
CentOS7 自带的 OpenSSL 版本较低,直接使用 addext 参数会报错,因此改用配置文件方式生成证书。
# 创建证书存储目录
[root@docker-node1 ~]# mkdir -p /etc/docker/certs
# 编写证书配置文件 san.cnf(定义证书主体和备用名称)
[root@docker-node1 ~]# cat > /etc/docker/certs/san.cnf << EOF
[req]
distinguished_name = req_distinguished_name
# 禁用交互式输入
x509_extensions = v3_req
# 使用 v3 扩展
prompt = no
# 非交互式
[req_distinguished_name]
CN = registry.example.com
# 证书主域名(仓库域名)
[v3_req]
subjectAltName = DNS:registry.example.com
# 证书备用域名(和主域名一致)
EOF
# 生成 RSA 4096 位的证书(.key 私钥 + .crt 公钥证书)
[root@docker-node1 certs]# openssl req -newkey rsa:4096 \
> -nodes
# 私钥不加密
> -keyout registry.example.com.key
# 输出私钥文件
> -x509 -days 365
# 生成自签名证书,有效期 365 天
> -out registry.example.com.crt
# 输出证书文件
> -config san.cnf
# 指定配置文件(兼容旧版 OpenSSL)
> -sha256
# 哈希算法
查看证书详情,确认 subjectAltName 等关键信息是否正确。
[root@docker-node1 certs]# openssl x509 -in /etc/docker/certs/registry.example.com.crt -noout -text
--restart=always:Docker 重启后自动启动仓库容器;-v /opt/registry:/var/lib/registry:将仓库的镜像数据持久化到宿主机 /opt/registry;-e REGISTRY_HTTP_TLS_*:指定 TLS 证书和私钥,开启 HTTPS 加密。[root@docker-node1 certs]# docker run -d -p 443:443 --restart=always --name registry \
> -v /opt/registry:/var/lib/registry
# 挂载仓库数据目录(持久化镜像)
> -v /etc/docker/certs:/certs
# 挂载证书目录到容器内
> -e REGISTRY_HTTP_ADDR=0.0.0.0:443
# 仓库监听 443 端口(HTTPS 默认端口)
> -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.example.com.crt
# 证书路径
> -e REGISTRY_HTTP_TLS_KEY=/certs/registry.example.com.key
# 私钥路径
> registry
# 仓库镜像
将仓库域名 registry.example.com 解析到仓库所在主机(docker-node1,IP:172.25.254.10),避免 DNS 解析问题。
[root@docker-node1 ~]# vim /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 localhost6.localdomain6
172.25.254.10 docker-node1
172.25.254.10 registry.example.com
# 添加这个
[root@docker-node2 ~]# vim /etc/hosts
# 20 也添加
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 localhost6.localdomain6
172.25.254.20 docker-node2
172.25.254.10 registry.example.com
# 添加这个
Docker 客户端默认不信任自签名证书,推送时会报 x509: certificate signed by unknown authority。
# 失败原因:我的 docker 没有证书,之前生成的证书是给 registry 生成的
[root@docker-node1 ~]# docker push registry.example.com/webserver:v4
The push refers to repository [registry.example.com/webserver]
577c8ee06f39: Waiting af5aa97ebe6c: Waiting 5342a2647e87: Waiting bbb6cacb8c82: Waiting 1a73b54f556b: Waiting 2388d2e8e2b: Waiting 6a575081cb9a: Waiting ac805962e479: Waiting c04827a7d9f: Waiting 8451c71f8c1e: Waiting 6835249f77a: Waiting 7fe377719de: Waiting 9ed498e22b2: Waiting 4d92f83d9cf: Waiting 2a92d6ac9e4f: Waiting 24aacbf97031: Waiting failed to do request: Head "https://registry.example.com/v2/webserver/blobs/sha256:2a92d6ac9e4fcc274d5168b217ca4458a9fec6f094ead68d99c77073f08caac1": tls: failed to verify certificate: x509: certificate signed by unknown authority
# 创建证书信任目录(格式:/etc/docker/certs.d/仓库域名/)
[root@docker-node1 ~]# mkdir /etc/docker/certs.d/registry.example.org/ -p
# 复制证书到信任目录(重命名为 ca.crt,Docker 会自动识别)
[root@docker-node1 ~]# cp /etc/docker/certs/registry.example.com.crt /etc/docker/certs.d/registry.example.org/ca.crt
# 重启 Docker 加载证书
[root@docker-node1 ~]# systemctl restart docker
# 给镜像打标签(仓库域名/镜像名:版本)
[root@docker-node1 ~]# docker tag webserver:v4 registry.example.com/webserver:v4
# 推送镜像到私有仓库(此时证书信任,推送成功)
[root@docker-node1 ~]# docker push registry.example.com/webserver:v4
# 验证仓库镜像列表
[root@docker-node1 ~]# curl -k https://172.25.254.10/v2/_catalog
{"repositories":["webserver"]}
# 显示已推送的 webserver 镜像
# 安装 htpasswd(生成密码文件的工具)
[root@docker-node1 ~]# dnf install httpd-tools -y
# 创建认证目录
[root@docker-node1 ~]# mkdir /etc/docker/auth
# 生成用户密码文件(-B:bcrypt 加密,-c:创建新文件,用户 admin,密码手动输入)
[root@docker-node1 ~]# htpasswd -Bc /etc/docker/auth/htpasswd admin
# 先删除旧仓库容器
[root@docker-node1 ~]# docker rm -f registry
# 启动带认证的仓库(新增认证相关参数)
[root@docker-node1 ~]# docker run -d -p 443:443 --restart=always --name registry \
> -v /opt/registry:/var/lib/registry \
> -v /etc/docker/certs:/certs \
> -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
> -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.example.com.crt \
> -e REGISTRY_HTTP_TLS_KEY=/certs/registry.example.com.key \
> # 以下是认证新增参数
> -v /etc/docker/auth:/auth
# 挂载认证目录
> -e "REGISTRY_AUTH=htpasswd"
# 启用 htpasswd 认证
> -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm"
# 认证域(提示信息)
> -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
# 密码文件路径
> registry
# 未登录推送(失败,提示需要认证)
[root@docker-node1 ~]# docker push registry.example.com/webserver:v4
push access denied, repository does not exist or may require authorization: authorization failed: no basic auth credentials
# 登录仓库(输入用户 admin 和密码)
[root@docker-node1 ~]# docker login registry.example.com -u admin
Login Succeeded
# 登录后推送(成功)
[root@docker-node1 ~]# docker push registry.example.com/webserver:v4
# 其他节点(docker-node2)访问:先复制证书 + 登录
[root@docker-node1 ~]# scp -r /etc/docker/certs.d/ [email protected]:/etc/docker/certs.d
[root@docker-node2 ~]# docker login registry.example.com -u admin
[root@docker-node2 ~]# docker pull registry.example.com/webserver:v4
# 拉取成功
/etc/docker/certs.d/仓库域名/ca.crt 才能信任自签名证书;Harbor 对 Docker 版本有兼容性要求,我现在的 docker 版本 29 太高,不支持 harbor,所以我要给 docker 进行降级处理。
# 停止当前 Harbor 相关容器(若已有部署)
[root@docker-node1 harbor]# docker compose down
# 卸载现有 29 版本 Docker
[root@docker-node1 ~]# dnf remove docker-ce -y
# 安装兼容的 28 版本(3:28.5.2-1.el9 为具体版本号,适配 CentOS 9)
[root@docker-node1 ~]# dnf install docker-ce-3:28.5.2-1.el9 -y
# 修改 Docker 服务配置,启用 iptables(确保容器网络转发正常)
[root@docker-node1 ~]# vim /lib/systemd/system/docker.service
# 关键配置行:添加--iptables=true
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --iptables=true
# 重新加载系统服务配置并启动 Docker
[root@docker-node1 ~]# systemctl daemon-reload
[root@docker-node1 ~]# systemctl enable --now docker.service
# 创建数据目录,存放证书(Harbor 需要 HTTPS 证书)
[root@docker-node1 ~]# mkdir /data/
[root@docker-node1 ~]# cp -rp /etc/docker/certs /data/
[root@docker-node1 ~]# vim /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 localhost6.localdomain6
172.25.254.10 docker-node1
# 本机节点名
172.25.254.10 registry.example.com
# Harbor 访问域名(自定义)
[root@docker-node1 ~]# tar zxf harbor-offline-installer-v2.5.4.tgz
[root@docker-node1 ~]# cd /opt/
[root@docker-node1 opt]# cd harbor/
[root@docker-node1 harbor]# cp harbor.yml.tmpl harbor.yml
[root@docker-node1 harbor]# vim harbor.yml
hostname: registry.example.com
# Harbor 访问域名(与 hosts 解析一致)
certificate: /data/certs/registry.example.com.crt
# HTTPS 证书路径
private_key: /data/certs/registry.example.com.key
# 证书私钥路径
harbor_admin_password: admin
# Harbor 管理员密码(web 界面/登录用)
# 执行安装脚本
[root@docker-node1 harbor]# ./install.sh
# 停止 Harbor 容器
[root@docker-node1 harbor]# docker compose down
# 启动 Harbor 容器(后台运行)
[root@docker-node1 harbor]# docker compose up -d
[root@docker-node1 harbor]# docker login registry.example.com
Authenticating with existing credentials...
[Username: admin]
i Info → To login with a different account, run 'docker logout' followed by 'docker login'
Stored credentials invalid or expired
Username (admin): admin
Password: # 密码:admin(配置文件中设置)
WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'. Configure a credential helper to remove this warning. See https://docs.docker.com/go/credential-store/
Login Succeeded
# 示例 1:上传到 Harbor 默认的 library 项目
[root@docker-node1 harbor]# docker tag busybox:latest registry.example.com/library/busybox:latest
[root@docker-node1 harbor]# docker push registry.example.com/library/busybox:latest
# 示例 2:自定义镜像标签上传
[root@docker-node1 harbor]# docker tag busybox:latest registry.example.com/library/webserver:v1
[root@docker-node1 harbor]# docker push registry.example.com/library/webserver:v1
访问地址:http://172.25.254.10/harbor(或 https://registry.example.com/harbor);
登录账号:admin,密码:admin;
创建自定义项目(如 example):若未勾选'公开',该项目下的镜像需登录后才能下载;
上传自定义项目镜像:
# 上传,之前我们创建了 example,可以使用 example 进行上传
[root@docker-node1 ~]# docker tag busybox:latest registry.example.com/example/busybox:latest
[root@docker-node1 ~]# docker push registry.example.com/example/busybox:latest
[root@docker-node2 ~]# dnf remove docker-ce -y
[root@docker-node2 ~]# dnf install docker-ce-3:28.5.2-1.el9 -y
[root@docker-node2 ~]# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --iptables=true
[root@docker-node2 ~]# systemctl enable --now docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.
# 编辑 Docker 守护进程配置文件
[root@docker-node2 ~]# vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.example.com"]
# 指向 Harbor 域名
}
# 配置本地域名解析(与 docker-node1 一致)
[root@docker-node2 ~]# vim /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 localhost6.localdomain6
172.25.254.20 docker-node2
172.25.254.10 registry.example.com
[root@docker-node2 ~]# systemctl restart docker
# 验证加速器是否生效
[root@docker-node2 ~]# docker info
Registry Mirrors: https://registry.example.com/
# 下载 example 项目下的 busybox 镜像
[root@docker-node2 ~]# docker pull example/busybox
/etc/hosts 解析 Harbor 自定义域名;Harbor 域名/项目名/镜像名:版本 是上传 / 下载的核心;ip 命令,无法查看网络配置;ip 命令的 busybox 容器,共享 webserver 的网络命名空间,从而查看网络配置。--network container:webserver:核心参数!指定容器共享 webserver 的网络命名空间(即和 webserver 用同一个网络栈)
rickiechina/busyboxplus:latest:使用带 ip 命令的 busybox 增强版镜像
# 从本地 busyboxplus.tar 镜像包加载镜像
[root@docker-node1 ~]# docker load -i busyboxplus.tar
# 启动一个 Nginx 容器
[root@docker-node1 ~]# docker run -d --rm --name webserver --network bridge nginx:1.23
# 进入 webserver 容器的交互式终端
[root@docker-node1 ~]# docker exec -it webserver /bin/bash
root@3e2ed29f9282:/# ip a
bash: ip: command not found
root@3e2ed29f9282:/# exit
exit
# 启动 busybox 容器,共享 webserver 的网络命名空间
[root@docker-node1 ~]# docker run -it --name busybox --rm --network container:webserver rickiechina/busyboxplus:latest /bin/sh: shopt: not found
[ root@3e2ed29f9282:/ ]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: eth0@if198: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether a2:a4:30:19:b7:cb brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
inet 172.17.0.2/16:容器的 IP 地址(Docker bridge 网络默认的网段是 172.17.0.0/16);
这个 IP 实际是 webserver 容器的 IP—— 因为 busybox 共享了它的网络命名空间,所以看到的是同一个 IP。
将本地的 phpmyadmin-latest.tar.gz(phpMyAdmin 最新版镜像包)和 mysql-8.0.tar(MySQL 8.0 版本镜像包)导入到 Docker 环境中,替代从远程仓库拉取镜像,适合无外网或镜像定制化的场景。
[root@docker-node1 ~]# docker load -i phpmyadmin-latest.tar.gz
[root@docker-node1 ~]# docker load -i mysql-8.0.tar
docker run:创建并启动 Docker 容器的核心命令;-d:后台运行容器(守护进程模式),不占用当前终端;--name phpmyadmin:为容器命名为 phpmyadmin,方便后续管理(如启停、删除);-e PMA_ARBITRARY=1:设置环境变量 PMA_ARBITRARY=1,允许 phpMyAdmin 连接任意地址的 MySQL 服务器(而非仅限本地);-p 80:80:端口映射,将宿主机的 80 端口映射到容器内的 80 端口,外部可通过宿主机 80 端口访问 phpMyAdmin;phpmyadmin:latest:指定使用的镜像名称和版本。[root@docker-node1 ~]# docker run -d --name phpmyadmin -e PMA_ARBITRARY=1 -p 80:80 phpmyadmin:latest
--name mysql:为 MySQL 容器命名为 mysql;-e MYSQL_ROOT_PASSWORD='admin':设置 MySQL 的 root 用户密码为 admin(必须配置,否则 MySQL 容器无法启动);--network container:phpmyadmin:核心网络配置,让 MySQL 容器共享 phpmyadmin 容器的网络命名空间;
mysql:8.0:指定使用 MySQL 8.0 版本的镜像。[root@docker-node1 ~]# docker run -d --name mysql -e MYSQL_ROOT_PASSWORD='admin' --network container:phpmyadmin mysql:8.0
通过浏览器访问宿主机(docker-node1)的 IP 地址 172.25.254.10
http://172.25.254.10
基于 macvlan 网络驱动搭建跨主机通信环境
docker-node1、docker-node2)新增网卡,并将网卡模式设置为 host-only(仅主机模式)。作用:host-only 模式下的网卡仅用于主机间的私有网络通信,隔离外部网络,保证容器跨主机通信的网络环境独立、可控。
网卡的混杂模式(PROMISC) 是指网卡接收所有经过它的数据包(而非仅目标 MAC 地址匹配自身的数据包),这是 macvlan 网络实现跨主机通信的前提。
ip a s eth1:ip address show eth1 的简写,查看 eth1 网卡的配置信息;<BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP>:
BROADCAST:支持广播;MULTICAST:支持组播;PROMISC:已开启混杂模式;UP:网卡逻辑层面启用;LOWER_UP:网卡物理层面已连接(如网线插好);eth1 网卡均确认开启混杂模式,为后续网络通信铺路。[root@docker-node1 ~]# ip a s eth1
54: eth1: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:0c:29:21:54:d9 brd ff:ff:ff:ff:ff:ff altname enp19s0 altname ens224
[root@docker-node2 ~]# ip a s eth1
4: eth1: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:0c:29:cf:83:84 brd ff:ff:ff:ff:ff:ff altname enp19s0 altname ens224
macvlan是 Docker 的网络驱动,它允许容器直接使用主机网卡的 MAC 地址和 IP 地址,让容器像独立的物理设备一样在网络中通信,适合跨主机场景。
[root@docker-node1 ~]# docker network create \
-d macvlan
# 指定网络驱动为 macvlan
--subnet 1.1.1.0/24
# 设定子网网段(跨主机容器需在同一网段)
--gateway 1.1.1.1
# 设定网关地址
-o parent=eth1 example
# -o:指定驱动专属选项;parent=eth1:绑定到主机的 eth1 网卡;example:自定义网络名称
0cad05f438fbae92c0bb5b7119de158c9d81cfd3e588bfc14f980593447a8f9c
[root@docker-node1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
5b1ea5c41484 bridge bridge local
6c9d5c41484 host host local
0cad05f438fb example macvlan local
# 新增的 example 网络,驱动为 macvlan
dbc0eb18cd23 none null local
关键注意点:两台主机需创建完全相同配置的
macvlan网络(同网段、同网关、绑定同名称网卡、同网络名称),否则跨主机通信会失败。
docker-node1 上启动容器
--network example:将容器加入 example 这个 macvlan 网络;--ip 1.1.1.100:为容器指定固定 IP(需在子网 1.1.1.0/24 范围内);--rm:容器退出后自动删除;busybox:轻量级 Linux 镜像,用于测试网络通信。[root@docker-node1 ~]# docker run -it --name busybox --rm --network example --ip 1.1.1.100 --rm busybox /
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
55: eth0@if54: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 66:46:be:d5:ca:6a brd ff:ff:ff:ff:ff:ff inet 1.1.1.100/24 brd 1.1.1.255 scope global eth0 valid_lft forever preferred_lft forever
docker-node2 上启动容器
[root@docker-node2 ~]# docker run -it --name busybox --rm --network example --ip 1.1.1.200 --rm busybox /
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
5: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 96:d7:cb:bc:56:79 brd ff:ff:ff:ff:ff:ff inet 1.1.1.200/24 brd 1.1.1.255 scope global eth0 valid_lft forever preferred_lft forever
容器内执行 ip a 可看到:
eth0@if54(node1 容器)/ eth0@if4(node2 容器):容器的网卡绑定到主机的 eth1 网卡(@if54/@if4 对应主机 eth1 的网卡索引);1.1.1.100/24 和 1.1.1.200/24,与指定的一致,且在同一子网。在 node2 的容器中执行 ping 1.1.1.100:
/ # ping 1.1.1.100
PING 1.1.1.100 (1.1.1.100): 56 data bytes
64 bytes from 1.1.1.100: seq=0 ttl=64 time=0.276 ms
64 bytes from 1.1.1.100: seq=1 ttl=64 time=0.406 ms
能收到回包(64 bytes from 1.1.1.100),说明两台主机的 Docker 容器已成功实现跨主机通信
bind mount 是将宿主机的目录 / 文件直接挂载到容器内,是最基础的数据卷方式,支持读写 / 只读权限控制。
[root@docker-node1 ~]# docker volume prune
WARNING! This will remove anonymous local volumes not used by at least one container. Are you sure you want to continue? [y/N] y
[root@docker-node1 ~]# docker run -it --rm --name test \
-v /data:/data
# 宿主机/data 挂载到容器/data(默认读写)
-v /data1:/data1:ro
# 宿主机/data1 挂载到容器/data1(ro=只读)
-v /etc/passwd:/passwd:ro
# 宿主机/etc/passwd 文件挂载到容器/passwd(只读)
busybox:latest
/ # touch /data/file # 读写挂载
/ # ls /data
file
/ # touch /data1/file # 只读挂载
touch: /data1/file: Read-only file system
/ # > passwd
sh: can't create passwd: Read-only file system
Docker 自行管理的卷(无需指定宿主机路径,由 Docker 统一存储),路径默认在
/var/lib/docker/volumes/[卷名]/_data,更易管理。
[root@docker-node1 ~]# docker volume create example
example
[root@docker-node1 ~]# docker volume ls
DRIVER VOLUME NAME
local example
直接在宿主机卷目录创建文件,容器内可访问。
[root@docker-node1 volumes]# touch example/_data/file
[root@docker-node1 ~]# docker run -it --rm -v example:/data:ro busybox:latest
/ # touch data/file
touch: data/file: Read-only file system
/ # ls data/
file
[root@docker-node1 ~]# docker volume rm example
example
[root@docker-node1 ~]# docker volume ls
DRIVER VOLUME NAME
专门用于挂载数据卷的'模板容器',其他容器可通过
--volumes-from继承其所有挂载配置,实现数据共享。
容器内可操作 /data(创建 example、file 文件),但无法修改 /hosts(只读)。
[root@docker-node1 ~]# docker run -it --rm --name data \
-v /etc/hosts:/hosts:ro
# 挂载宿主机 hosts 文件(只读)
-v /data:/data
# 挂载宿主机/data 目录(读写)
busybox:latest
/ # cat /etc/host
cat: can't open '/etc/host': No such file or directory
/ # cat /hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 localhost6.localdomain6
172.25.254.10 docker-node1
172.25.254.10 registry.example.com
/ # > /hosts
sh: can't create /hosts: Read-only file system
/ # / # touch /data/example
/ # ls /data/
example
/ # touch /data/file
/ # ls /data/
file example
/hosts(和 data 容器内容一致);/data(可看到 data 容器创建的文件,且能删除 / 修改);[root@docker-node1 ~]# docker run -it --rm --name admin --volumes-from data busybox:latest
/ # ls bin dev home lib proc sys usr data etc hosts lib64 root tmp var
/ # cat hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 localhost6.localdomain6
172.25.254.10 docker-node1
172.25.254.10 registry.example.com
/ # ls /data/
example
/ # ls /data/
file example
/ # rm /data/file
/ #
基于数据卷实现容器数据的备份(打包)、恢复(解压),适用于数据迁移 / 容灾场景。
/data 挂载到容器 nginx 的网页根目录;[root@docker-node1 ~]# docker run -d --name webserver -p 80:80 -v /data:/usr/share/nginx/html nginx:1.23
[root@docker-node1 ~]# docker exec -it webserver bash
root@aa28e0ff63f0:/# cd /usr/share/nginx/html/
root@aa28e0ff63f0:/usr/share/nginx/html# ls
index.html example
root@aa28e0ff63f0:/usr/share/nginx/html# touch example{1..10}
root@aa28e0ff63f0:/usr/share/nginx/html# ls
index.html example1 example2 example4 example6 example8 example example10 example3 example5 example7 example9
root@aa28e0ff63f0:/usr/share/nginx/html# exit
exit
[root@docker-node1 ~]# docker run -it --rm \
--volumes-from webserver
# 继承 webserver 的挂载配置
-v $(pwd):/backup
# 宿主机当前目录挂载到容器/backup
busybox:latest
/ # ls backup dev home lib64 root tmp var bin etc lib proc sys usr
/ # ls /usr/share/nginx/html/
index.html example1 example2 example4 example6 example8 example example10 example3 example5 example7 example9
/ # tar zcf /backup/html.tar.gz /usr/share/nginx/
# 打包 nginx 数据到宿主机
tar: removing leading '/' from member names
/ # ls backup dev home lib64 root tmp var bin etc lib proc sys usr
/ # exit
[root@docker-node1 ~]# ls busyboxplus.tar harbor mysql-8.0.tar busy-latest.tar harbor-offline-installer-v2.14.0.tgz nginx-1.23.tar.gz debian11.tar.gz html.tar.gz #这里 phpmyadmin-latest.tar.gz docker mario.tar.gz
# 清空宿主机/data(模拟数据丢失)
[root@docker-node1 ~]# rm -fr /data/*
# 重新运行 nginx 容器,并挂载备份目录
[root@docker-node1 ~]# docker run -d --name webserver -p 80:80 -v /data:/usr/share/nginx/html -v $(pwd):/backup nginx:1.23
409fc810840b52522a5d9016c1e3b974a458f51c1c3d755110c69e6df51c1f54
# 容器内解压备份包恢复数据
[root@docker-node1 ~]# docker exec -it webserver bash
root@409fc810840b:/# tar zxf /backup/html.tar.gz -C /
root@409fc810840b:/# ls /usr/share/nginx/html/
index.html example1 example2 example4 example6 example8 example example10 example3 example5 example7 example9
| 数据卷类型 | 特点 | 适用场景 |
|---|---|---|
| bind mount | 直接挂载宿主机路径,灵活 | 宿主机和容器需强耦合的场景 |
| Docker 管理卷 | Docker 统一管理路径,无需关心宿主机位置 | 容器独立管理数据,解耦宿主机 |
| 数据卷容器 | 继承挂载配置,多容器共享 | 集群容器共享数据 |
| 数据备份 / 迁移 | 基于卷打包 / 解压,跨环境迁移 | 数据容灾、迁移、备份 |
核心目的
调整 Linux 系统的 cgroup(控制组)层级模式,从 cgroup2 切换回传统的 cgroup v1 分层架构。cgroup 是 Linux 内核用于限制、记录和隔离进程组资源使用(CPU、内存、IO 等)的机制,Docker 依赖 cgroup 实现容器资源管控。
[root@docker-node1 ~]# mount -t cgroup2 cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
- 修改内核参数
[root@docker-node1 ~]# grubby --update-kernel=/boot/vmlinuz-$(uname -r) \
--args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
[root@docker-node1 ~]# reboot
重启系统后,执行 mount -t cgroup 可看到 cgroup v1 被拆分为 cpu、memory、devices 等多个独立子系统挂载,每个子系统对应不同的资源管控维度。
[root@docker-node1 ~]# mount -t cgroup cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
通过 Docker 命令行参数限制容器对 CPU、内存、磁盘 IO 等资源的占用,避免单个容器耗尽宿主机资源,提升系统稳定性。
核心是通过 --cpu-period 和 --cpu-quota 控制容器的 CPU 时间片占比:
--cpu-period:CPU 调度周期(单位:微秒),默认 100000(100ms);--cpu-quota:容器在一个周期内可使用的 CPU 时间(单位:微秒)[root@docker-node1 ~]# docker load -i ubuntu-latest.tar.gz
f36fd4bb7334: Loading layer 80.56MB/80.56MB
Loaded image: ubuntu:latest
[root@docker-node1 ~]# docker run -it --rm ubuntu:latest
root@616ebe97890b:/# dd if=/dev/zero of=/dev/null &
[1] 10
root@616ebe97890b:/# top
top - 06:25:00 up 2:52, 0 user, load average: 0.23, 0.09, 0.09
Tasks: 3 total, 2 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 14.8 us, 26.3 sy, 0.0 ni, 58.3 id, 0.0 wa, 0.4 hi, 0.2 si, 0.0 st
MiB Mem : 1739.0 total, 613.8 free, 589.7 used, 693.1 buff/cache
MiB Swap: 4008.0 total, 4008.0 free, 0.0 used.
1149.2 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10 root 20 0 2736 1408 1408 R 99.7 0.1 0:08.87 dd
1 root 20 0 4588 3840 3328 S 0.0 0.2 0:00.02 bash
11 root 20 0 8844 5248 3200 R 0.0 0.3 0:00.00 top
# 资源限制
[root@docker-node1 ~]# docker run -it --rm --name test --cpu-period 100000 --cpu-quota 20000 ubuntu
root@a7fa69da9c0c:/# dd if=/dev/zero of=/dev/null &
[1] 9
root@a7fa69da9c0c:/# top
top - 06:28:23 up 2:56, 0 user, load average: 0.62, 0.32, 0.18
Tasks: 3 total, 2 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 14.9 us, 27.5 sy, 0.0 ni, 57.1 id, 0.0 wa, 0.6 hi, 0.0 si, 0.0 st
MiB Mem : 1739.0 total, 632.8 free, 570.6 used, 693.3 buff/cache
MiB Swap: 4008.0 total, 4008.0 free, 0.0 used.
1168.3 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9 root 20 0 2736 1408 1408 R 98.7 0.1 0:37.61 dd
1 root 20 0 4588 3840 3328 S 0.0 0.2 0:00.01 bash
10 root 20 0 8844 5248 3200 R 0.0 0.3 0:00.00 top
通过 --cpu-shares 设置容器的 CPU 共享权重(默认值 1024),仅在 CPU 资源竞争时生效:
--cpu-shares 100,另一个默认 1024,当 CPU 资源紧张时,前者获取的 CPU 资源约为后者的 1/10;dd if=/dev/zero of=/dev/null 模拟 CPU 满负载,结合 top 观察 CPU 占用率验证限制效果。[root@docker-node1 ~]# cd /sys/devices/system/cpu/
[root@docker-node1 cpu]# cd /sys/devices/system/cpu/
cpu0/ cpufreq/ hotplug/ smt/
cpu1/ cpuidle/ power/ vulnerabilities/
[root@docker-node1 cpu]# ls
cpu0 cpuidle isolated nohz_full possible smt
cpu1 crash_hotplug kernel_max offline power uevent cpufreq hotplug modalias online present vulnerabilities
[root@docker-node1 cpu]# cat cpu*/online
1
[root@docker-node1 cpu]# cat cpu0/online
cat: cpu0/online: 没有那个文件或目录
[root@docker-node1 cpu]# cat cpu1/online
1
[root@docker-node1 cpu]# echo 0 > cpu1/online
[root@docker-node1 cpu]# cat /proc/cpuinfo | grep cores
cpu cores : 1
[root@docker-node1 ~]# docker run -it --rm --name test ubuntu
root@100d1b161ffa:/# dd if=/dev/zero of=/dev/null &
[1] 8
root@100d1b161ffa:/# top
[root@docker-node1 ~]# docker run -it --rm --name test1 ubuntu
root@718fb12421d4:/# dd if=/dev/zero of=/dev/null &
[1] 8
root@718fb12421d4:/# top
# 资源限制
[root@docker-node1 ~]# docker run -it --rm --cpu-shares 100 ubuntu
root@7e5aee8f5814:/# dd if=/dev/zero of=/dev/null &
[1] 8
root@7e5aee8f5814:/# top
[root@docker-node1 ~]# docker run -it --rm ubuntu:latest
root@0d24e4f08288:/# dd if=/dev/zero of=/dev/null &
[1] 9
root@0d24e4f08288:/# top
通过 --memory 和 --memory-swap 限制容器的内存 + 交换分区使用量:
--memory 200M:限制容器最多使用 200MB 物理内存;--memory-swap 200M:限制容器总内存(物理 + 交换)为 200MB(若不设置,默认是 --memory 的 2 倍);cgexec 工具在容器的 cgroup 内存组中执行 dd 命令写入大文件;dd 进程被内核杀死,验证内存限制生效;/sys/fs/cgroup/memory/docker/<容器 ID>/memory.limit_in_bytes 可确认内存限制的字节数。[root@docker-node1 ~]# docker run -d --name test --memory 200M --memory-swap 200M nginx:1.23
4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4
# 安装检测工具
[root@docker-node1 ~]# rpm -ivh libcgroup-0.41-19.el8.x86_64.rpm
[root@docker-node1 ~]# rpm -ivh libcgroup-tools-0.41-19.el8.x86_64.rpm
[root@docker-node1 ~]# cgexec -g memory:docker/4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=100
记录了 100+0 的读入
记录了 100+0 的写出
104857600 字节(105 MB,100 MiB)已复制,0.2563 s,409 MB/s
[root@docker-node1 ~]# cgexec -g memory:docker/4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=150
记录了 150+0 的读入
记录了 150+0 的写出
157286400 字节(157 MB,150 MiB)已复制,0.174223 s,903 MB/s
[root@docker-node1 ~]# cgexec -g memory:docker/4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
已杀死
[root@docker-node1 ~]# cd /sys/fs/cgroup/memory/docker/4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4
[root@docker-node1 4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4]# cat memory.limit_in_bytes
209715200
[root@docker-node1 4b0db5efb9438afc69a5b3cdafd932fb0d6b03634f50f4c8f1d2c12b3c292af4]# cat memory.memsw.limit_in_bytes
209715200
#这里是查看正常情况下的速率
[root@docker-node1 ~]# docker run -it --name test --rm ubuntu:latest
root@59047bbc6025:/# dd if=/dev/zero of=/bigfile bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 2.24941 s, 466 MB/s
# 写入速率限制
[root@docker-node1 ~]# docker run -it --name test --device-write-bps /dev/nvme0n1:30M --rm ubuntu:latest
root@69563c8cae3c:/# dd if=/dev/zero of=/bigfile bs=1M count=100 oflag=direct
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 3.33747 s, 31.4 MB/s
默认情况下,Docker 容器处于受限的权限环境中,避免容器突破隔离层操作宿主机资源;可通过参数精细管控容器特权。
[root@docker-node1 ~]# docker run -it --name test --rm busybox:latest
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether d6:ee:cc:4a:b1:d3 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
/ # ip a a 1.2.3.4/24 eth0@if41
ip: either "local" is duplicate, or "eth0@if41" is garbage
/ # fdisk -l
/ # exit
--privileged 参数会赋予容器几乎与宿主机 root 相同的权限,容器可直接操作宿主机的硬件、磁盘、网络等资源:
[root@docker-node1 ~]# docker run -it --rm --privileged busybox:latest
/ # fdisk -l
Disk /dev/nvme0n1: 100 GB, 107374182400 bytes, 209715200 sectors
411206 cylinders, 255 heads, 2 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/nvme0n1p1 * 4,4,1 1023,254,2 2048 2099199 2097152 1024M 83 Linux
/dev/nvme0n1p2 1023,254,2 1023,254,2 2099200 10307583 8208384 4008M 82 Linux swap
/dev/nvme0n1p3 1023,254,2 1023,254,2 10307584 209715199 199407616 95.0G 83 Linux
/ # exit
不赋予全量特权,仅添加指定的内核能力(Capability),最小化容器权限:
# 开启指定白名单权限
[root@docker-node1 ~]# docker run -it --rm --cap-add CAP_NET_ADMIN busybox:latest
/ # fdisk -l
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 56:2d:fc:51:34:59 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
/ # ifconfig eth0
Link encap:Ethernet HWaddr 56:2D:FC:51:34:59
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:12 errors:0 dropped:0 overruns:0 frame:0 TX packets:3 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:1016 (1016.0 B) TX bytes:126 (126.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
/ # ip a a 1.2.3.4/24 dev 'eth0'
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 56:2d:fc:51:34:59 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever inet 1.2.3.4/24 scope global eth0 valid_lft forever preferred_lft forever
[root@docker-node1 ~]# mkdir /root/admin/
# 创建自定义工作目录
[root@docker-node1 ~]# cd /root/admin/
# 进入工作目录
[root@docker-node1 admin]# vim docker-compose.yml
# 编辑 compose 配置文件
services:
# 定义要编排的容器服务集合
web:
# 第一个服务:命名为 web(对应 Nginx)
image: nginx:1.23
# 使用的镜像:nginx 1.23 版本
ports:
# 端口映射:主机 80 端口 → 容器 80 端口
- "80:80"
volumes:
# 数据卷挂载(此处仅声明卷名 admin,未指定主机路径,会创建匿名卷)
- admin:/data
dbserver:
# 第二个服务:命名为 dbserver(对应 MySQL)
image: mysql:8.0
# 使用的镜像:mysql 8.0 版本
environment:
# 设置容器环境变量(MySQL 必填:root 密码)
MYSQL_ROOT_PASSWORD: admin
docker compose up:启动 Compose 配置中的所有服务;-d:后台运行(detach 模式),不加则会前台占用终端;admin-dbserver-1、admin-web-1)启动成功(命名规则:目录名 - 服务名 - 序号)。[root@docker-node1 admin]# docker compose up -d
[+] up 2/2 ✔ Container admin-dbserver-1 Started 0.3s ✔ Container admin-web-1 Started 0.3s
[root@docker-node1 admin]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bde022b1fe26 nginx:1.23 "/docker-entrypoint…" About a minute ago Up 2 seconds 0.0.0.0:80->80/tcp, [::]:80->80/tcp admin-web-1
b6bb2557840f mysql:8.0 "docker-entrypoint.s…" About a minute ago Up 2 seconds 3306/tcp, 33060/tcp admin-dbserver-1
docker compose down:停止并删除 Compose 管理的容器、默认网络(admin_default);
-v 参数,如 docker compose down -v);[root@docker-node1 admin]# docker compose down
[+] down 3/3 ✔ Container admin-web-1 Removed 0.1s ✔ Container admin-dbserver-1 Removed 1.5s ✔ Network admin_default Removed 0.1s
[root@docker-node1 admin]# docker compose ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
-f:指定自定义路径的 Compose 配置文件(默认只识别当前目录的 docker-compose.yml/docker-compose.yaml);-f 指向 admin 目录下的配置文件,实现跨目录启动。[root@docker-node1 ~]# docker compose -f /root/admin/docker-compose.yml up -d
[+] up 3/3 ✔ Network admin_default Created 0.1s ✔ Container admin-web-1 Started 0.4s ✔ Container admin-dbserver-1 Started 0.2s
docker compose stop:停止运行中的容器,但保留容器本身(可通过 start 重启);Exited (0) 表示正常停止。 [root@docker-node1 admin]# docker compose stop
[+] stop 2/2 ✔ Container admin-dbserver-1 Stopped 1.3s ✔ Container admin-web-1 Stopped 0.1s
重启通过 stop 停止的容器(无需重新创建,快速恢复运行)
[root@docker-node1 admin]# docker compose start
[+] start 2/2 ✔ Container admin-dbserver-1 Started 0.2s ✔ Container admin-web-1 Started 0.3s
docker compose restart:重启运行中的容器(先停止,再启动);docker compose ps 可看到容器恢复运行状态。[root@docker-node1 admin]# docker compose restart
[+] restart 0/2 ⠼ Container admin-dbserver-1 Restarting 1.4s ⠼ Container admin-web-1 Restarting 1.4s
再次执行 down 完成最终清理,移除容器和网络。
[root@docker-node1 admin]# docker compose down
[+] down 3/3 ✔ Container admin-web-1 Removed 0.1s ✔ Container admin-dbserver-1 Removed 1.4s ✔ Network admin_default Removed 0.1s
| 命令 | 作用 |
|---|---|
docker compose up -d | 后台启动所有服务(创建容器 + 网络) |
docker compose down | 停止并删除容器、网络(保留数据卷) |
docker compose stop | 停止容器(保留容器,不删除) |
docker compose start | 启动已停止的容器 |
docker compose restart | 重启运行中的容器 |
docker compose ps | 查看 Compose 管理的容器状态 |
docker compose -f | 指定非默认路径的配置文件 |

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online