跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Shell / Bash

Docker 容器技术入门与实战指南

详细介绍 Docker 容器技术的核心概念、安装部署、镜像构建优化、私有仓库搭建(Registry/Harbor)、网络配置及数据卷管理。涵盖从基础命令到多阶段构建、安全架构及 Compose 编排的完整实战流程,帮助开发者实现环境统一与高效运维。

邪神洛基发布于 2026/3/27更新于 2026/5/3124 浏览
Docker 容器技术入门与实战指南

一、Docker 介绍

  1. Docker 可以把你的程序 + 依赖库 + 配置 + 环境全部打包成一个镜像(Image),然后在任何装了 Docker 的机器上一模一样地跑起来。
  1. Docker 是一款开源的容器化技术工具,核心价值是解决软件运行环境不一致的问题:它将应用程序及其依赖(库、配置、运行环境等)打包成标准化的镜像(Image),这个镜像可理解为'可执行的安装包';基于镜像启动的容器(Container) 是轻量级、隔离的运行实例,能在任何安装了 Docker 的机器(Windows/Linux/Mac)上无差异运行;相比虚拟机,Docker 无需模拟完整操作系统,启动快(秒级)、资源占用少,是目前 DevOps、微服务部署的核心工具。

一句话解释:Docker 就是一个「轻量、便携、统一」的容器工具,用来打包、运行软件。

  1. 最形象的比喻传统部署:每台电脑环境不一样,程序经常「在我这能跑,在你那就崩」。Docker:像一个集装箱。不管船(服务器)是什么型号不管货(程序)是什么只要装进集装箱,到哪都能直接运行
  1. 核心概念
  • 镜像(Image):程序的「安装包 / 模板」。
  • 容器(Container):镜像跑起来后的实例,一个隔离的小环境。
  • Dockerfile:用来告诉 Docker 怎么打包镜像。
  • 仓库(Registry):存放镜像的地方(比如 Docker Hub)。
  1. 运维 / 开发为什么爱用它?
  • 环境统一:开发、测试、线上完全一样
  • 秒级启动:比虚拟机快得多
  • 轻量:不占太多资源
  • 可移植:一次打包,到处运行

二、部署 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 相关软件包

作用:验证仓库配置是否生效,同时查看可安装的 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 周边插件(如 docker-buildx-plugin(构建多平台镜像)、docker-compose-plugin(容器编排)、podman-docker(Podman 兼容 Docker 命令的插件)等)。
  • 名称精确匹配:核心包 docker-ce(Docker 引擎主程序)、docker-ce-cli(Docker 命令行工具)—— 这是安装的核心目标。

(四)安装 Docker CE 主程序

[root@docker-node1 ~]# dnf install docker-ce -y

(五)修改 Docker 服务启动配置

作用:修改 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

(八)启动并设置 Docker 开机自启

[root@docker-node1 ~]# systemctl enable --now docker

(九)验证是否部署成功

执行 systemctl status docker 可查看 Docker 服务状态(显示 active (running) 即为成功)。

执行 docker version 可查看 Docker 客户端 / 服务端版本,验证安装完整性。

三、docker 的常规使用方法

(一)配置 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 常用命令

Docker 核心操作围绕镜像(Image) 和容器(Container) 展开。

1. 镜像(Image)操作命令

镜像是容器的模板,包含运行应用所需的所有文件、依赖和配置。

命令作用
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
2. 容器(Container)操作命令

容器是镜像的运行实例,是动态的、可交互的环境。

  1. 容器创建 / 运行
命令作用
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
  1. 容器状态查看
命令作用
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
  1. 容器启停 / 控制
命令作用
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] # 按键
  1. 容器内操作
命令作用
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

(二)镜像构建是用到的参数

1. FROM:指定基础镜像
  • FROM 是 Dockerfile 的必备指令,指定构建镜像的基础镜像;
  • latest 是镜像标签,代表最新版本。
FROM busybox:latest
2. COPY:复制文件到镜像内
  • 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
3. LABEL:添加镜像元数据(标签)

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 .
4. ADD:复制(支持解压 / 远程 URL)
# 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 一致;
  • 若源文件是压缩包(tar.gz/tar.bz2 等),ADD 会自动解压到目标路径(COPY 仅复制压缩包,不解压);
  • 支持远程 URL(如 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
5. ENV:设置环境变量
  • 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 .
6. EXPOSE:声明容器暴露的端口
  • EXPOSE 端口:仅声明容器要暴露的端口(告诉使用者该容器会用这个端口),不自动映射(运行容器时需加 -p 宿主机端口:容器端口 才会映射);
  • 主要作用是文档化,方便协作(比如开发者知道容器要映射 8080 端口)。
[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
7. VOLUME:定义匿名卷(持久化数据)
  • 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
8. WORKDIR:设置工作目录
  • WORKDIR 路径:设置容器启动后的默认工作目录(类似 cd 命令);
  • 后续的 RUN/CMD/ENTRYPOINT 指令都会在该目录下执行。
[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
9. CMD:容器启动默认命令(可被覆盖)
  • CMD:定义容器启动时默认执行的命令;
  • 关键特性:docker run 时若指定自定义命令,会覆盖CMD的默认命令;
  • 注意:exec 格式中,直接写 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
10. ENTRYPOINT:容器启动固定命令(不可被覆盖)
  • 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容器启动固定命令不可被覆盖,核心逻辑固定

五、构建 CentOS 可用仓库

(一)基础环境准备:拉取并验证 CentOS 7 镜像

1. 拉取官方 CentOS 7 镜像

命令执行后,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
  1. 启动容器验证镜像信息
[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

(二)清理本地冗余文件 / 镜像

1. 清理本地文件

删除当前目录下无关的文件,仅保留后续构建需要的 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
2. 清理历史镜像
  • 先用 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
... (省略部分输出)

(三)构建自定义 CentOS 7 镜像

1. 清空默认 yum 仓库配置
[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/
2. 添加阿里云 CentOS 7 镜像源

编辑 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 构建

六、镜像构建的优化方案(以 Nginx 为例)

(一)基础构建:未优化的 Nginx 镜像

1. 操作流程
  1. 准备环境:清理服务器文件,下载 Nginx-1.26.3 源码包;
[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
  1. 编写 Dockerfile:基于 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;"]
  1. 构建并验证:执行 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>
2. 问题

构建出的 webserver:v1 镜像体积达 510MB(内容大小 132MB),核心原因:

  • 编译过程产生的临时文件(如源码包、编译中间产物)未清理;
  • 每个 RUN 指令生成独立镜像层,层越多镜像冗余越大;
  • 基础镜像 centos-7:repo 本身体积较大(299MB)。

(二)第一次优化:清理临时文件(未达预期)

1. 优化点
把产生的临时文件全部清理
[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 .
2. 结果

镜像体积仍为 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
#没有改进,还是一样的,是因为改进后多了两层

(三)第二次优化:缩减镜像层

1. 核心思路——层越少,构建的软件越小

第一次优化未成功,是因为多了两个镜像层,那么我就把镜像层缩减:Dockerfile 中合并多个 RUN 指令为一个(通过 && 串联命令),减少镜像层数,同时完成编译 + 清理操作,避免分层冗余。

2. 优化后的 Dockerfile
[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 .
3. 结果

构建出 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

(四)第三次优化:多阶段构建

1. 核心思路

利用 Docker多阶段构建特性,将「编译构建」和「最终运行」分离:

  • 第一阶段(构建阶段):基于 centos-7:repo 完成 Nginx 编译、安装、清理,仅保留编译后的产物;
  • 第二阶段(运行阶段):基于 centos-7:repo,仅复制第一阶段的 Nginx 运行产物,舍弃编译依赖、源码等冗余文件。
2. 优化后的 Dockerfile 结构
[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;"]
3. 结果

构建出 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
#可以看到比之前更小了

(五)第四次优化:基于极简基础镜像的多阶段构建

1. 核心思路
  • 替换基础镜像:使用 gcr.io/distroless/base-debian11(极简 Debian 镜像,仅含运行必需的库和文件,体积仅 47.9MB)替代 centos-7:repo;
  • 精准复制依赖:在构建阶段仅复制 Nginx 运行所需的二进制文件、系统库、配置文件,舍弃所有无关内容。
# 把 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
2. 优化后的 Dockerfile
[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;"]
3. 结果

构建出 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

七、非加密仓库构建 - 搭建简单的 Registry 仓库

(一)环境准备(两台主机初始化)

此实验需要两台主机,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
  1. 加载内核模块

在 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
  1. 安装 Docker

在 node2 上通过 dnf 安装 docker-ce:dnf install docker-ce -y

[root@docker-node2 ~]# dnf install docker-ce -y

(二)部署 Registry 镜像仓库(node1 操作)

  1. 导入 Registry 镜像:将 registry.tar 包上传到 node1,通过 docker load -i registry.tar 加载镜像(最终得到 registry:latest 镜像)。
[root@docker-node1 docker]# docker load -i registry.tar
Loaded image: registry:latest
  1. 查看 Registry 镜像信息:

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:{}]
  1. 启动 Registry 容器
[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。
  1. 查看容器挂载目录
[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": ""
 }
],

(三)解决 HTTPS 默认限制,配置非加密仓库

Docker 默认要求镜像仓库使用 HTTPS 协议通信,而我们搭建的是纯 HTTP 的非加密仓库,因此需要配置 insecure-registries(不安全仓库)来跳过 HTTPS 验证:

1. node1(仓库端)配置
[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
2. 上传镜像到私有仓库
# 给本地镜像打标签
[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"]}
# 表示仓库已存在该镜像

(四)跨主机下载私有仓库镜像(node2 操作)

1. 配置非加密仓库
[root@docker-node1 docker]# vim /etc/docker/daemon.json
{
 "insecure-registries" : ["http://172.25.254.10:5000"]
}
[root@docker-node2 ~]# systemctl restart docker
2. 验证配置
[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
3. 拉取镜像
[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

八、仓库的加密传输及用户认证

(一)前置清理:重置 Docker 非加密配置

清理之前可能存在的非加密仓库配置,避免冲突。

# 清空/重置 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

(二)生成 TLS 证书(解决 OpenSSL 版本兼容问题)

  1. 问题背景

CentOS7 自带的 OpenSSL 版本较低,直接使用 addext 参数会报错,因此改用配置文件方式生成证书。

  1. 操作步骤
# 创建证书存储目录
[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
# 哈希算法
  1. 验证证书

查看证书详情,确认 subjectAltName 等关键信息是否正确。

[root@docker-node1 certs]# openssl x509 -in /etc/docker/certs/registry.example.com.crt -noout -text

(三)启动加密的 Docker Registry 仓库

  • --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
# 仓库镜像

(四)配置域名解析(本地 hosts)

将仓库域名 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
# 添加这个

(五)解决证书信任问题(推送镜像失败的修复)

1. 初始推送失败原因

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
2. 修复操作
# 创建证书信任目录(格式:/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
3. 验证推送
# 给镜像打标签(仓库域名/镜像名:版本)
[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 镜像

(六)添加用户认证(限制仓库访问权限)

1. 安装工具 + 创建认证文件
# 安装 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
2. 重启带认证的仓库容器
# 先删除旧仓库容器
[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
3. 认证验证
# 未登录推送(失败,提示需要认证)
[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
# 拉取成功

(七)总结

  • 加密:通过自签名 TLS 证书实现仓库 HTTPS 加密传输,解决 OpenSSL 版本兼容问题;
  • 信任:Docker 客户端需将证书放到 /etc/docker/certs.d/仓库域名/ca.crt 才能信任自签名证书;
  • 认证:通过 htpasswd 实现用户名密码认证,限制仓库的推送 / 拉取权限;
  • 跨节点访问:其他节点需同步证书并登录,才能访问私有仓库。

九、Harbor 仓库构建

(一)Docker 版本降级

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 部署准备

1. 目录与证书准备
# 创建数据目录,存放证书(Harbor 需要 HTTPS 证书)
[root@docker-node1 ~]# mkdir /data/
[root@docker-node1 ~]# cp -rp /etc/docker/certs /data/
2. 本地域名解析
[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 访问域名(自定义)
3. 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 基本操作:启动 / 停止 / 登录 / 镜像上传

1. 容器管理
# 停止 Harbor 容器
[root@docker-node1 harbor]# docker compose down
# 启动 Harbor 容器(后台运行)
[root@docker-node1 harbor]# docker compose up -d
2. 登录 Harbor 仓库
[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
3. 镜像打标签并上传
# 示例 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

(四)Harbor 图形化界面操作

访问地址: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

(五)多节点配置:另一台服务器(docker-node2)使用 Harbor 仓库

1. 同步 Docker 版本(与 docker-node1 一致)
[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.
2. 配置 Harbor 为 Docker 镜像加速器
# 编辑 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/
3. 从 Harbor 下载镜像
# 下载 example 项目下的 busybox 镜像
[root@docker-node2 ~]# docker pull example/busybox

(六)总结

  • 版本兼容:Harbor 对 Docker 版本敏感,需降级到兼容版本(如 28.5.2);
  • 域名解析:所有节点需配置 /etc/hosts 解析 Harbor 自定义域名;
  • 镜像命名规则:Harbor 域名/项目名/镜像名:版本 是上传 / 下载的核心;
  • 多节点复用:其他服务器需同步 Docker 版本 + 配置 Harbor 为镜像加速器,才能便捷拉取镜像;
  • 权限控制:Harbor 项目可通过'公开 / 私有'控制镜像访问权限,私有镜像需登录后才能下载。

十、joined 容器网络

(一)操作背景

  1. 启动一个基础的 Nginx 容器(webserver);
  2. 由于 Nginx 容器内未预装 ip 命令,无法查看网络配置;
  3. 启动另一个带 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。


十一、joined 网络企业实战

(一)导入镜像

将本地的 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

(二)运行 php-myadmin 容器

  • 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

(三)运行 mysql

  • --name mysql:为 MySQL 容器命名为 mysql;
  • -e MYSQL_ROOT_PASSWORD='admin':设置 MySQL 的 root 用户密码为 admin(必须配置,否则 MySQL 容器无法启动);
  • --network container:phpmyadmin:核心网络配置,让 MySQL 容器共享 phpmyadmin 容器的网络命名空间;
    • 效果:两个容器处于同一网络环境,MySQL 容器可被 phpMyAdmin 容器直接访问(无需暴露 MySQL 端口到宿主机,更安全);
  • 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

(四)访问 phpmyadmin

通过浏览器访问宿主机(docker-node1)的 IP 地址 172.25.254.10

http://172.25.254.10

十二、容器跨主机通信

基于 macvlan 网络驱动搭建跨主机通信环境

(一)设定硬件

  • 核心操作:为两台 Docker 主机(命名为 docker-node1、docker-node2)新增网卡,并将网卡模式设置为 host-only(仅主机模式)。

作用:host-only 模式下的网卡仅用于主机间的私有网络通信,隔离外部网络,保证容器跨主机通信的网络环境独立、可控。

(二)开启新加网卡的混杂模式

1. 混杂模式说明

网卡的混杂模式(PROMISC) 是指网卡接收所有经过它的数据包(而非仅目标 MAC 地址匹配自身的数据包),这是 macvlan 网络实现跨主机通信的前提。

2. 代码解释
  • 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

(三)配置 Docker 自建网络(macvlan 驱动)

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 容器通信

1. 启动容器(指定 macvlan 网络和固定 IP)

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
2. 容器网络信息验证

容器内执行 ip a 可看到:

  • eth0@if54(node1 容器)/ eth0@if4(node2 容器):容器的网卡绑定到主机的 eth1 网卡(@if54/@if4 对应主机 eth1 的网卡索引);
  • 容器 IP 分别为 1.1.1.100/24 和 1.1.1.200/24,与指定的一致,且在同一子网。
3. 跨主机通信测试

在 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 容器已成功实现跨主机通信


十三、Docker 数据卷管理及优化

(一)bind mount 数据卷(绑定挂载)

bind mount 是将宿主机的目录 / 文件直接挂载到容器内,是最基础的数据卷方式,支持读写 / 只读权限控制。

  1. 清理无用数据卷
[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
  1. 创建并运行容器(挂载宿主机路径)
[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
  1. 权限认证
/ # 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 Managed Volume(Docker 管理卷)

Docker 自行管理的卷(无需指定宿主机路径,由 Docker 统一存储),路径默认在 /var/lib/docker/volumes/[卷名]/_data,更易管理。

  1. 创建数据卷
[root@docker-node1 ~]# docker volume create example
example
[root@docker-node1 ~]# docker volume ls
DRIVER VOLUME NAME
local example
  1. 宿主机操作卷内容

直接在宿主机卷目录创建文件,容器内可访问。

[root@docker-node1 volumes]# touch example/_data/file
  1. 容器挂载(只读)
[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
  1. 删除数据卷
[root@docker-node1 ~]# docker volume rm example
example
[root@docker-node1 ~]# docker volume ls
DRIVER VOLUME NAME

(三)数据卷容器(Volumes-from)

专门用于挂载数据卷的'模板容器',其他容器可通过 --volumes-from 继承其所有挂载配置,实现数据共享。

  1. 创建数据卷容器(data)

容器内可操作 /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
  1. 继承数据卷容器
  • 无需重复挂载配置,直接访问 /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
/ #

(四)数据的备份和迁移

基于数据卷实现容器数据的备份(打包)、恢复(解压),适用于数据迁移 / 容灾场景。

1. 准备测试容器(nginx)
  • 宿主机 /data 挂载到容器 nginx 的网页根目录;
  • 容器内创建测试文件(example1~10),模拟业务数据。
[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
2. 数据备份(打包)
[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
3. 数据恢复(解压)
# 清空宿主机/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 统一管理路径,无需关心宿主机位置容器独立管理数据,解耦宿主机
数据卷容器继承挂载配置,多容器共享集群容器共享数据
数据备份 / 迁移基于卷打包 / 解压,跨环境迁移数据容灾、迁移、备份

十四、Docker 安全架构

(一)更改系统 cgroup(企业中非必须)

核心目的

调整 Linux 系统的 cgroup(控制组)层级模式,从 cgroup2 切换回传统的 cgroup v1 分层架构。cgroup 是 Linux 内核用于限制、记录和隔离进程组资源使用(CPU、内存、IO 等)的机制,Docker 依赖 cgroup 实现容器资源管控。

  1. 查看当前 cgroup 挂载类型
[root@docker-node1 ~]# mount -t cgroup2 cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
  1. 修改内核参数
[root@docker-node1 ~]# grubby --update-kernel=/boot/vmlinuz-$(uname -r) \
--args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
  1. 必须重启
[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 的资源限制

通过 Docker 命令行参数限制容器对 CPU、内存、磁盘 IO 等资源的占用,避免单个容器耗尽宿主机资源,提升系统稳定性。

1. cpu 的用量

核心是通过 --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
2. CPU 优先级限制

通过 --cpu-shares 设置容器的 CPU 共享权重(默认值 1024),仅在 CPU 资源竞争时生效:

  • 权重越高,容器获取的 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
3. 内存使用限制

通过 --memory 和 --memory-swap 限制容器的内存 + 交换分区使用量:

  • --memory 200M:限制容器最多使用 200MB 物理内存;
  • --memory-swap 200M:限制容器总内存(物理 + 交换)为 200MB(若不设置,默认是 --memory 的 2 倍);
  • 验证方式:
    1. 启动带内存限制的 Nginx 容器;
    2. 通过 cgexec 工具在容器的 cgroup 内存组中执行 dd 命令写入大文件;
    3. 当写入数据量超过 200MB 时,dd 进程被内核杀死,验证内存限制生效;
    4. 查看 /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
4. 磁盘 IO 限制
#这里是查看正常情况下的速率
[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 容器处于受限的权限环境中,避免容器突破隔离层操作宿主机资源;可通过参数精细管控容器特权。

  1. 无特权容器(默认)
[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
  1. 特权容器(--privileged)

--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
3. 精细化权限管控(--cap-add)

不赋予全量特权,仅添加指定的内核能力(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

十五、Docker Compose 容器编排工具

(一)基础环境准备

[root@docker-node1 ~]# mkdir /root/admin/
# 创建自定义工作目录
[root@docker-node1 ~]# cd /root/admin/
# 进入工作目录

(二)编辑 compose 配置文件

[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

(三)命令演示

1. 启动容器(后台运行)
  • 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
2. 查看运行中的容器
[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
3. 停止并删除容器 / 网络

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
4. 查看 Compose 管理的容器状态
[root@docker-node1 admin]# docker compose ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5. 指定配置文件启动(-f 参数)
  • -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
6. 停止容器(保留容器,不删除)
  • 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
7. 启动已停止的容器

重启通过 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
8. 重启容器
  • 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
9. 最终清理(再次 down)

再次执行 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指定非默认路径的配置文件

目录

  1. 一、Docker 介绍
  2. 二、部署 docker
  3. (一)配置软件仓库
  4. 利用阿里云部署软件仓库
  5. (二)刷新本地软件包缓存
  6. (三)搜索 Docker 相关软件包
  7. (四)安装 Docker CE 主程序
  8. (五)修改 Docker 服务启动配置
  9. (六)加载网桥网络模块
  10. (七)配置内核网络参数
  11. (八)启动并设置 Docker 开机自启
  12. (九)验证是否部署成功
  13. 三、docker 的常规使用方法
  14. (一)配置 docker 加速器
  15. 1. 创建/编辑 Docker 守护进程配置文件 daemon.json
  16. 2. 重启 Docker 服务,使配置生效
  17. 3. 验证配置是否生效
  18. 输出中会显示 Registry Mirrors: https://docker.1ms.run/,说明配置成功
  19. (二)docker 常用命令
  20. 1. 镜像(Image)操作命令
  21. 镜像查看
  22. 搜索镜像
  23. 下载镜像
  24. 查看镜像提交历史
  25. 导出镜像
  26. 删除镜像
  27. 导入镜像
  28. 修改镜像
  29. 2. 容器(Container)操作命令
  30. 运行镜像
  31. 交互模式运行容器
  32. 查看运行容器
  33. 查看容器信息
  34. [ctrl]+[p]+[q] # 按键
  35. 在已经运行的容器中执行指定命令
  36. 四、容器镜像构建
  37. (一)基础环境准备
  38. 建立构建目录
  39. 编写构建规则文件
  40. (二)镜像构建是用到的参数
  41. 1. FROM:指定基础镜像
  42. 2. COPY:复制文件到镜像内
  43. 先创建本地文件
  44. 修改 Dockerfile
  45. 构建镜像(-t 打标签,. 代表构建上下文为当前目录)
  46. 3. LABEL:添加镜像元数据(标签)
  47. 4. ADD:复制(支持解压 / 远程 URL)
  48. 1. 普通复制(和 COPY 一致)
  49. 修改 Dockerfile
  50. 构建镜像
  51. 2. 解压压缩包(COPY 不支持)
  52. 修改 Dockerfile
  53. 构建并测试
  54. ls
  55. ls /root/
  56. COPY 的结果:仅压缩包 bin.tar.gz
  57. ls /mnt/
  58. ADD 的结果:解压后的 bin 目录
  59. 5. ENV:设置环境变量
  60. 6. EXPOSE:声明容器暴露的端口
  61. 查看镜像历史(验证 EXPOSE 指令)
  62. 7. VOLUME:定义匿名卷(持久化数据)
  63. 测试卷挂载
  64. 宿主机查看挂载信息(grep 过滤 Mounts 段)
  65. 宿主机往卷里写文件
  66. ls /mnt/
  67. 8. WORKDIR:设置工作目录
  68. 运行容器:默认进入/mnt 目录
  69. 提示符显示当前目录是/mnt
  70. 9. CMD:容器启动默认命令(可被覆盖)
  71. shell 格式
  72. exec 格式
  73. 正常运行:执行 CMD 命令,输出 example
  74. 覆盖 CMD:运行时指定命令,会替换默认 CMD
  75. 10. ENTRYPOINT:容器启动固定命令(不可被覆盖)
  76. 正常运行:输出 example
  77. 尝试覆盖(无效):仍执行 ENTRYPOINT 的命令,输出 example
  78. (三)总结
  79. 五、构建 CentOS 可用仓库
  80. (一)基础环境准备:拉取并验证 CentOS 7 镜像
  81. 1. 拉取官方 CentOS 7 镜像
  82. (二)清理本地冗余文件 / 镜像
  83. 1. 清理本地文件
  84. 删掉不需要的文件
  85. 2. 清理历史镜像
  86. 删掉之前实验做得文件
  87. (三)构建自定义 CentOS 7 镜像
  88. 1. 清空默认 yum 仓库配置
  89. 构建镜像
  90. 启动容器验证
  91. 确认默认 repo 文件已被清空(目录为空)
  92. 2. 添加阿里云 CentOS 7 镜像源
  93. 启动容器
  94. (四)命令说明
  95. 六、镜像构建的优化方案(以 Nginx 为例)
  96. (一)基础构建:未优化的 Nginx 镜像
  97. 1. 操作流程
  98. 2. 问题
  99. (二)第一次优化:清理临时文件(未达预期)
  100. 1. 优化点
  101. 2. 结果
  102. (三)第二次优化:缩减镜像层
  103. 1. 核心思路——层越少,构建的软件越小
  104. 2. 优化后的 Dockerfile
  105. 优化后构建为 v2
  106. 3. 结果
  107. 对比 v1 和 v2
  108. (四)第三次优化:多阶段构建
  109. 1. 核心思路
  110. 2. 优化后的 Dockerfile 结构
  111. 阶段 1:编译构建(命名为 admin)
  112. 阶段 2:最终运行镜像
  113. 3. 结果
  114. (五)第四次优化:基于极简基础镜像的多阶段构建
  115. 1. 核心思路
  116. 把 nginx-1.23.tar.gz 和 debian11.tar.gz 放到/root/目录下
  117. 2. 优化后的 Dockerfile
  118. 阶段 1:基于 nginx:1.23 提取运行依赖
  119. 阶段 2:基于极简镜像构建最终镜像
  120. 3. 结果
  121. 七、非加密仓库构建 - 搭建简单的 Registry 仓库
  122. (一)环境准备(两台主机初始化)
  123. 复制内核模块配置:
  124. 复制系统参数配置
  125. 复制 Docker yum 源
  126. 加载模块
  127. (二)部署 Registry 镜像仓库(node1 操作)
  128. 部分
  129. (三)解决 HTTPS 默认限制,配置非加密仓库
  130. 1. node1(仓库端)配置
  131. 重启 Docker 使配置生效
  132. 2. 上传镜像到私有仓库
  133. 给本地镜像打标签
  134. 推送镜像
  135. 推送成功后验证
  136. 表示仓库已存在该镜像
  137. (四)跨主机下载私有仓库镜像(node2 操作)
  138. 1. 配置非加密仓库
  139. 2. 验证配置
  140. 3. 拉取镜像
  141. 可看到该镜像,说明跨主机下载成功
  142. 八、仓库的加密传输及用户认证
  143. (一)前置清理:重置 Docker 非加密配置
  144. 清空/重置 daemon.json(删除之前非加密仓库的配置)
  145. 重启 Docker 使配置生效
  146. (二)生成 TLS 证书(解决 OpenSSL 版本兼容问题)
  147. 创建证书存储目录
  148. 编写证书配置文件 san.cnf(定义证书主体和备用名称)
  149. 禁用交互式输入
  150. 使用 v3 扩展
  151. 非交互式
  152. 证书主域名(仓库域名)
  153. 证书备用域名(和主域名一致)
  154. 生成 RSA 4096 位的证书(.key 私钥 + .crt 公钥证书)
  155. 私钥不加密
  156. 输出私钥文件
  157. 生成自签名证书,有效期 365 天
  158. 输出证书文件
  159. 指定配置文件(兼容旧版 OpenSSL)
  160. 哈希算法
  161. (三)启动加密的 Docker Registry 仓库
  162. 挂载仓库数据目录(持久化镜像)
  163. 挂载证书目录到容器内
  164. 仓库监听 443 端口(HTTPS 默认端口)
  165. 证书路径
  166. 私钥路径
  167. 仓库镜像
  168. (四)配置域名解析(本地 hosts)
  169. 添加这个
  170. 20 也添加
  171. 添加这个
  172. (五)解决证书信任问题(推送镜像失败的修复)
  173. 1. 初始推送失败原因
  174. 失败原因:我的 docker 没有证书,之前生成的证书是给 registry 生成的
  175. 2. 修复操作
  176. 创建证书信任目录(格式:/etc/docker/certs.d/仓库域名/)
  177. 复制证书到信任目录(重命名为 ca.crt,Docker 会自动识别)
  178. 重启 Docker 加载证书
  179. 3. 验证推送
  180. 给镜像打标签(仓库域名/镜像名:版本)
  181. 推送镜像到私有仓库(此时证书信任,推送成功)
  182. 验证仓库镜像列表
  183. 显示已推送的 webserver 镜像
  184. (六)添加用户认证(限制仓库访问权限)
  185. 1. 安装工具 + 创建认证文件
  186. 安装 htpasswd(生成密码文件的工具)
  187. 创建认证目录
  188. 生成用户密码文件(-B:bcrypt 加密,-c:创建新文件,用户 admin,密码手动输入)
  189. 2. 重启带认证的仓库容器
  190. 先删除旧仓库容器
  191. 启动带认证的仓库(新增认证相关参数)
  192. 挂载认证目录
  193. 启用 htpasswd 认证
  194. 认证域(提示信息)
  195. 密码文件路径
  196. 3. 认证验证
  197. 未登录推送(失败,提示需要认证)
  198. 登录仓库(输入用户 admin 和密码)
  199. 登录后推送(成功)
  200. 其他节点(docker-node2)访问:先复制证书 + 登录
  201. 拉取成功
  202. (七)总结
  203. 九、Harbor 仓库构建
  204. (一)Docker 版本降级
  205. 停止当前 Harbor 相关容器(若已有部署)
  206. 卸载现有 29 版本 Docker
  207. 安装兼容的 28 版本(3:28.5.2-1.el9 为具体版本号,适配 CentOS 9)
  208. 修改 Docker 服务配置,启用 iptables(确保容器网络转发正常)
  209. 关键配置行:添加--iptables=true
  210. 重新加载系统服务配置并启动 Docker
  211. (二)Harbor 部署准备
  212. 1. 目录与证书准备
  213. 创建数据目录,存放证书(Harbor 需要 HTTPS 证书)
  214. 2. 本地域名解析
  215. 本机节点名
  216. Harbor 访问域名(自定义)
  217. 3. Harbor 安装包解压与配置
  218. Harbor 访问域名(与 hosts 解析一致)
  219. HTTPS 证书路径
  220. 证书私钥路径
  221. Harbor 管理员密码(web 界面/登录用)
  222. 执行安装脚本
  223. (三)Harbor 基本操作:启动 / 停止 / 登录 / 镜像上传
  224. 1. 容器管理
  225. 停止 Harbor 容器
  226. 启动 Harbor 容器(后台运行)
  227. 2. 登录 Harbor 仓库
  228. 3. 镜像打标签并上传
  229. 示例 1:上传到 Harbor 默认的 library 项目
  230. 示例 2:自定义镜像标签上传
  231. (四)Harbor 图形化界面操作
  232. 上传,之前我们创建了 example,可以使用 example 进行上传
  233. (五)多节点配置:另一台服务器(docker-node2)使用 Harbor 仓库
  234. 1. 同步 Docker 版本(与 docker-node1 一致)
  235. 2. 配置 Harbor 为 Docker 镜像加速器
  236. 编辑 Docker 守护进程配置文件
  237. 指向 Harbor 域名
  238. 配置本地域名解析(与 docker-node1 一致)
  239. 验证加速器是否生效
  240. 3. 从 Harbor 下载镜像
  241. 下载 example 项目下的 busybox 镜像
  242. (六)总结
  243. 十、joined 容器网络
  244. (一)操作背景
  245. (二)操作过程
  246. 从本地 busyboxplus.tar 镜像包加载镜像
  247. 启动一个 Nginx 容器
  248. 进入 webserver 容器的交互式终端
  249. 启动 busybox 容器,共享 webserver 的网络命名空间
  250. 十一、joined 网络企业实战
  251. (一)导入镜像
  252. (二)运行 php-myadmin 容器
  253. (三)运行 mysql
  254. (四)访问 phpmyadmin
  255. 十二、容器跨主机通信
  256. (一)设定硬件
  257. (二)开启新加网卡的混杂模式
  258. 1. 混杂模式说明
  259. 2. 代码解释
  260. (三)配置 Docker 自建网络(macvlan 驱动)
  261. 指定网络驱动为 macvlan
  262. 设定子网网段(跨主机容器需在同一网段)
  263. 设定网关地址
  264. -o:指定驱动专属选项;parent=eth1:绑定到主机的 eth1 网卡;example:自定义网络名称
  265. 新增的 example 网络,驱动为 macvlan
  266. (四)测试跨主机 Docker 容器通信
  267. 1. 启动容器(指定 macvlan 网络和固定 IP)
  268. ip a
  269. ip a
  270. 2. 容器网络信息验证
  271. 3. 跨主机通信测试
  272. 十三、Docker 数据卷管理及优化
  273. (一)bind mount 数据卷(绑定挂载)
  274. 宿主机/data 挂载到容器/data(默认读写)
  275. 宿主机/data1 挂载到容器/data1(ro=只读)
  276. 宿主机/etc/passwd 文件挂载到容器/passwd(只读)
  277. (二)Docker Managed Volume(Docker 管理卷)
  278. (三)数据卷容器(Volumes-from)
  279. 挂载宿主机 hosts 文件(只读)
  280. 挂载宿主机/data 目录(读写)
  281. (四)数据的备份和迁移
  282. 1. 准备测试容器(nginx)
  283. 2. 数据备份(打包)
  284. 继承 webserver 的挂载配置
  285. 宿主机当前目录挂载到容器/backup
  286. 打包 nginx 数据到宿主机
  287. 3. 数据恢复(解压)
  288. 清空宿主机/data(模拟数据丢失)
  289. 重新运行 nginx 容器,并挂载备份目录
  290. 容器内解压备份包恢复数据
  291. (五)总结
  292. 十四、Docker 安全架构
  293. (一)更改系统 cgroup(企业中非必须)
  294. (二)Docker 的资源限制
  295. 1. cpu 的用量
  296. 资源限制
  297. 2. CPU 优先级限制
  298. 资源限制
  299. 3. 内存使用限制
  300. 安装检测工具
  301. 4. 磁盘 IO 限制
  302. 写入速率限制
  303. (三)容器特权管控
  304. 3. 精细化权限管控(--cap-add)
  305. 开启指定白名单权限
  306. 十五、Docker Compose 容器编排工具
  307. (一)基础环境准备
  308. 创建自定义工作目录
  309. 进入工作目录
  310. (二)编辑 compose 配置文件
  311. 编辑 compose 配置文件
  312. 定义要编排的容器服务集合
  313. 第一个服务:命名为 web(对应 Nginx)
  314. 使用的镜像:nginx 1.23 版本
  315. 端口映射:主机 80 端口 → 容器 80 端口
  316. 数据卷挂载(此处仅声明卷名 admin,未指定主机路径,会创建匿名卷)
  317. 第二个服务:命名为 dbserver(对应 MySQL)
  318. 使用的镜像:mysql 8.0 版本
  319. 设置容器环境变量(MySQL 必填:root 密码)
  320. (三)命令演示
  321. 1. 启动容器(后台运行)
  322. 2. 查看运行中的容器
  323. 3. 停止并删除容器 / 网络
  324. 4. 查看 Compose 管理的容器状态
  325. 5. 指定配置文件启动(-f 参数)
  326. 6. 停止容器(保留容器,不删除)
  327. 7. 启动已停止的容器
  328. 8. 重启容器
  329. 9. 最终清理(再次 down)
  330. (四)总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • NLP 国内外大模型汇总列表(含文心一言、通义千问等)
  • 大疆无人机日志导出与解析方法
  • 无人机智能巡检在城市生命线管理中的应用实践
  • Cursor Agent Skills 实战指南:打造专属前端 AI 助手
  • GLM-4-9B 开源发布:支持 26 种语言与 128K 上下文,性能优于 Llama-3-8B
  • RAG 入门:LangChain Embedding 介绍与使用
  • 访问控制失效漏洞解析:WebGoat 实战演练
  • 程序员必知的 10 大代码共享与协作平台
  • 零基础网络安全入门学习指南
  • 九么 1.0.31:AI 辅助 Python 数据处理实战
  • AI 辅助编程:工具使用与实战技巧
  • NISP 证书含金量深度解析与报考指南
  • 企业微信群机器人 Webhook 配置与消息发送流程
  • 二分查找实战:x 的平方根与搜索插入位置解析
  • Python 反爬实战:动态渲染与高可用 IP 代理集群方案
  • Linux 环境 OpenClaw 安装、初始化与 Web UI 配置指南
  • 渗透测试实战:HackMyVM Hundred 靶场攻防演练
  • LLM 局限性解析与 LangChain 框架初探
  • AI 时代为何“人人都是产品经理”成为现实
  • Ubuntu 22.04 下 PX4 无人机仿真环境搭建 (ROS2 Humble + Micro XRCE-DDS)

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online