1 篇 = 100 篇!Docker 核心知识点 + 实战案例全汇总
Docker
一、Docker原理


1、hellowold

2、client / server / hub

二、常用命令
1、帮助启动类命令
这类命令主要用于控制 Docker 服务本身的启停,以及查看 Docker 的系统级信息。
systemctl start/stop/restart docker:启动 / 停止 / 重启 Docker 后台服务。systemctl status docker:查看 Docker 服务的运行状态。docker info:查看 Docker 系统的详细信息(包括有多少个镜像、容器,存储驱动等)。docker --help:万能救命命令,不知道某个命令怎么用时,加上它就能看说明。
2、镜像命令
镜像是静态的只读模板。这类命令是对这些“模板”进行的操作。
docker search [镜像名]:去 Docker Hub 仓库搜索有没有你想要的镜像。docker pull [镜像名]:把远端仓库的镜像下载(拉取)到你的本地服务器。docker images:列出你本地目前已经下载的所有镜像。docker commit:提交容器副本使之成为一个新的镜像。 当你在一个容器里做了一些修改(比如安装了新软件),可以用这个命令把它“保存”成一个新的镜像,方便以后直接使用。docker import:从一个.tar压缩包文件中导入内容并创建一个全新的文件系统镜像(通常和下面的export配合使用)。
3、容器命令
容器是镜像运行起来的实体。
1. 核心运行命令:run 及重要 Options (参数)
docker run 的作用是:根据镜像,新建并启动一个容器。 它的参数非常多,你提到的都是最核心的:
-it(前台交互式):-i(interactive):以交互模式运行容器,通常与-t同时使用。-t(tty):为容器重新分配一个伪输入终端。- 组合作用:启动容器并立刻进入容器内部,你可以像操作一台新 Linux 机器一样输入命令(比如配合
/bin/bash使用)。
-d(后台守护式):- 作用:启动容器并在后台运行(不占用你当前的命令行窗口)。适合跑数据库(如 MySQL、Redis)或 Web 服务。
-v(数据卷挂载):- 作用:将宿主机(你的真实机器)的目录,和容器内的目录进行“绑定”。哪怕容器被删除了,存在宿主机上的数据也不会丢。实现数据的持久化和双向同步。
--volumes-from:- 作用:让新建的容器继承另一个已经存在的容器的数据卷挂载规则。适合做容器间的数据共享。
--privileged=true:- 作用:扩大容器的权限。有时候你挂载了目录,但在容器里操作时报
Permission denied(权限拒绝),加上这个参数,容器内的 root 用户才拥有真正的 root 权限。
- 作用:扩大容器的权限。有时候你挂载了目录,但在容器里操作时报
2. 日常管理命令
docker ps:列出当前正在运行的容器。(加上-a参数可以看所有容器,包括已经停止的)。docker stop [容器ID]:温柔地停止一个正在运行的容器。docker rm [容器ID]:删除一个已经停止的容器(不能删正在跑的,除非用-f强制删除)。docker cp:在宿主机和容器之间拷贝文件或目录。docker export:把整个容器当前的系统状态作为一个.tar文件导出到宿主机上(配合前面的import实现容器的迁移)。
3. 运维排错命令
docker inspect:查看容器内部的细节(这是个非常底层的命令,会返回一堆 JSON 数据,包含容器的网络、挂载、IP地址等一切信息)。docker top:查看容器内部正在运行的进程信息(类似于 Linux 的top或ps命令)。docker logs:查看容器的运行日志。如果后台运行的容器突然挂了,用这个命令看报错信息最管用。
三、Docker 镜像
1、什么是 Docker 镜像
通俗定义
Docker 镜像 = 一个只读的、完整的软件运行「安装包 / 模板」
它包含了软件运行需要的所有内容:代码、运行环境、依赖库、配置文件、系统工具等。
官方 / 核心定义
镜像是一个静态的、只读的文件集合,是创建 Docker 容器的模板。
镜像 = 模具
容器 = 用模具造出来的运行实例
关键特点
只读不可修改:镜像本身不能直接改,要改只能重新构建新镜像
静态文件:不运行,只是一堆文件
跨平台通用:一次构建,到处运行(Windows/Mac/Linux 都能用)
容器的源头:没有镜像,就无法创建容器
举个例子
nginx 镜像:自带 Nginx 服务器 + 运行环境
mysql 镜像:自带 MySQL 数据库 + 配置
你自己打包的 Java/Node/Python 项目镜像:自带代码 + 运行环境
2、分层的镜像(Docker 最经典的设计)
Docker 镜像不是一个单独的大文件,而是由多层只读层叠加而成的,这就是分层镜像。
核心原理
镜像 = 多层只读层堆叠
每一层都是独立的、只读的文件增量。
下层共享,上层叠加
下层被所有上层镜像复用,上层只存储和下层不一样的内容。
联合文件系统(UnionFS)
Docker 把多层文件 “粘合” 成一个整体,用户看起来就是一个文件系统。
3、UnionFS(联合文件系统)
第一步:Linux 操作系统的启动
传统的 Linux 系统启动时,文件系统的加载确实分为两层:
- bootfs (Boot File System - 引导文件系统)
- 包含什么:
bootloader(引导加载程序,如 GRUB)和kernel(操作系统内核,如 vmlinuz)。 - 生命周期: 当系统加电启动时,
bootloader会将操作系统的kernel加载到内存中。一旦内核完全加载到内存并启动完成,bootfs的历史使命就结束了。Linux 会将bootfs卸载(Unmount) 以释放内存空间。这就是你印象中“bootfs 消失了”的过程。
- 包含什么:
- rootfs (Root File System - 根文件系统)
- 包含什么: 就是我们进入 Linux 后看到的标准目录结构,比如
/dev、/proc、/bin、/etc、/lib、/usr等,以及各种基础命令和运行库。 - 生命周期: 在 bootfs 卸载后,内核会挂载
rootfs。但在 Linux 刚启动的极短时间内,rootfs是以**只读(Read-Only)模式挂载的。内核进行完自检后,才会将其重新挂载为读写(Read-Write)**模式,之后系统才算彻底准备就绪。
- 包含什么: 就是我们进入 Linux 后看到的标准目录结构,比如
第二步:Docker 与 UnionFS 的巧妙结合
Docker 容器并不是一个完整的虚拟机,它不需要自己启动内核,而是直接复用宿主机(Host OS)的内核。因此:
- Docker 根本不需要
bootfs。 容器直接利用宿主机已经加载在内存中的 kernel。 - Docker 镜像实际上只打包了
rootfs。 比如你拉取一个 Ubuntu 镜像,它里面其实只有 Ubuntu 版本的/bin、/etc等文件结构,大小只有几十兆,因为最占地方和耗时的内核部分被剥离了。
UnionFS 是如何管理 rootfs 的?
这里就是 Docker 镜像分层技术的灵魂所在。UnionFS 的核心能力是:将不同物理位置的目录,联合挂载到同一个目录下,对外呈现出一个统一的文件系统。
Docker 对 rootfs 的管理不再像传统 Linux 那样最终变成全局可读写,而是这样做的:
- 只读的镜像层(Image Layers): Docker 镜像是由多层构成的(比如一层基础 OS,一层 Java 环境,一层你的 Spring Boot 代码)。这些层都是基础的
rootfs的一部分。Docker 利用 UnionFS 将这些层一层一层叠加起来,并且所有这些镜像层都是绝对只读(Read-Only)的。 - 可读写的容器层(Container Layer): 当你启动一个容器时(
docker run),Docker 会在所有只读的镜像层最上方,动态添加一个读写层(Read-Write Layer)。
写时复制机制(Copy-on-Write, CoW)
这时候你会问:底层全是只读的,那我在容器里修改文件怎么办?这就用到了 UnionFS 的核心机制 —— 写时复制(CoW):
- 读取文件: 系统从上往下依次查找,在最上层找到就直接读,找不到就去下一层找。
- 新增文件: 直接在这个最上层的“可读写层”创建。
- 修改文件(重点): 如果你想修改底层某个只读层里的文件,UnionFS 会先把这个文件从只读层复制一份到最上层的可读写层,然后在这个副本上进行修改。底层的原文件依然原封不动地躺在那里,只是被上层的修改版“遮盖”住了。
- 删除文件: 会在最上层的可读写层创建一个特殊的“删除标记”(whiteout 文件),用来遮挡底层的文件,而不是真的去物理删除底层只读文件。
总结一下:
底层有一个 bootfs 负责启动并最终退场,留下 rootfs 来管理文件。但在 Docker 和 UnionFS 的世界里,bootfs 被直接省略(复用宿主机),rootfs 被切分成多个只读层,并在最顶端盖上了一个读写层,所有的修改都只发生在最顶层。
四、容器数据卷
备份???NO
1. 核心概念与底层机制 (Bind Mount)
非同步,而是共享:宿主机与容器、或容器与容器之间的文件并非在进行“复制同步”,而是直接共享同一份物理文件。
底层机制 (Bind Mount):依赖 Linux 操作系统的绑定挂载(Bind Mount)功能。
指向同一 Inode:宿主机的目录和各个容器内的目录,仅仅是不同的“入口”,它们在底层操作系统层面共同指向同一个 Inode(物理磁盘块)。
-
知识点:宿主机与容器并非在进行“复制同步”,而是通过 Linux 的 Bind Mount 机制,直接共享同一块物理磁盘空间(同一个 Inode)。
-
命令示例: 假设你在宿主机新建了一个目录
/home/ubuntu/workspace,现在启动一个名为my_container的容器,并将该目录映射到容器内的/app/workspace:# -v 宿主机绝对路径:容器内绝对路径 docker run -it --name my_container -v /home/ubuntu/workspace:/app/workspace ubuntu:24.04 /bin/bash执行后,无论你在宿主机的
/home/ubuntu/workspace还是在容器内的/app/workspace创建文件,双方都会瞬间看到,因为它们推开的是不同的“门”,但进的是同一个“房间”。
2. 读写权限(ro/rw)控制边界
容器端限制:Docker 默认挂载为 rw(读写)。如果设置为 ro(只读),仅限制该容器内部对这部分文件的修改权限。
宿主机无视限制:Docker 作为宿主机上的一个进程,无权反向限制宿主机操作系统的行为。即使容器设置为 ro,宿主机依然可以随意读写。
限制宿主机的唯一途径:若需让宿主机也只读,必须脱离 Docker,直接在宿主机的 Linux 系统层面修改目录权限(例如使用 chmod 命令)或更改底层文件系统的挂载属性。
-
知识点:Docker 默认挂载为
rw(读写)。如果设置为ro(只读),仅限制该容器内部对这部分文件的修改权限。宿主机作为“房东”,依然可以随意读写。 -
命令示例: 启动一个新容器,并在映射路径末尾加上
:ro标签:
加上 :ro (read-only) 限制容器内为只读
docker run -it --name my_ro_container -v /home/ubuntu/config:/app/config:ro ubuntu:24.04 /bin/bash
现象:
- 如果你在容器内执行
touch /app/config/test.txt,系统会报错:Read-only file system。 - 但你回到宿主机,执行
touch /home/ubuntu/config/test.txt,文件会成功创建,并且容器内也能立刻查看到这个新文件。Docker 无法反向限制宿主机。
3. 数据卷继承与生命周期
继承传递:容器之间可以通过相互继承(如 --volumes-from)来获取相同的目录映射关系。
节点独立,网络网状:继承建立后,宿主机、源容器(u1)、继承容器(u2)三者直接互通,共同操作同一磁盘块。
生命周期解耦:中间节点(u1)挂掉,不会切断其余节点(u2 和宿主机)的映射通道。u1 重新上线后,打开目录看到的自然是磁盘上未曾丢失的最新状态。
-
知识点:容器之间可以通过
--volumes-from相互继承目录映射关系。继承建立后,各个节点地位平等,共同操作同一磁盘块。中间节点挂掉,不影响其他节点。 -
命令示例:
-
首先,创建一个基础容器 u1(带有数据卷):
docker run -it --name u1 -v /home/ubuntu/data:/data ubuntu:24.04 /bin/bash -
然后,创建容器 u2,并继承 u1 的数据卷规则:
# 使用 --volumes-from 继承 u1 的数据卷 docker run -it --name u2 --volumes-from u1 ubuntu:24.04 /bin/bash
现象与验证:
- 此时,宿主机的
/home/ubuntu/data、u1的/data、u2的/data三端完全互通。 - 假设执行
docker stop u1把 u1 停掉。 - 在宿主机修改文件,然后进入
u2查看,文件依然是最新的。这证明映射通道依然存在。 - 当执行
docker start u1重启 u1 后,进入 u1 的/data目录,看到的数据也是最新的,因为它重新挂载回了那块物理磁盘。
-
4. 性能与认知
- 原生 I/O 性能:因为数据卷的操作本质上就是 Linux 本地文件系统的 I/O 操作,绕过了 Docker 引擎的干预,所以读写性能极高,几乎零损耗。
- 前置知识要求:理解 Docker 数据卷的核心在于理解操作系统级别的文件系统挂载概念即可,完全不需要涉及底层的汇编原理。
“挂载”是操作系统中非常核心的一个动作。它的字面意思可以理解为**“接入”或“嫁接”**。
我们可以通过以下三个层面来通俗且客观地理解它:
1. 从日常经验理解:U盘与文件夹
在 Windows 系统中,当你插入一个 U 盘,系统会自动给它分配一个盘符(比如
E:盘)。你想看 U 盘里的内容,直接双击E:盘进去即可。但在 Linux 系统中,没有盘符的概念。Linux 的所有文件和目录都是一棵以根目录
/为起点的巨大的树。 当你把 U 盘插到 Linux 机器上时,系统识别到了硬件,但你无法直接访问它。你必须在现有的目录树中找一个空文件夹(比如/mnt/usb),然后命令操作系统:“把这个 U 盘的存储空间,接入到/mnt/usb这个文件夹上。”这个“接入”的动作,就叫做挂载。被选用的那个文件夹(
/mnt/usb),就被称为挂载点(Mount Point)。挂载完成后,你往/mnt/usb里读写文件,实际上就是在往 U 盘里读写。2. 从操作系统层面理解:目录与磁盘块的绑定
顺着上一个例子深入,在 Linux 的世界里:
- 目录(文件夹):只是一个逻辑上的路径名称。
- 磁盘(或具体的文件数据):是物理存在的存储介质。
挂载,本质上就是给“物理存储介质”分配一个“逻辑路径名称”的过程。 就像是给一个没有门牌号的仓库,强行钉上了一个写着“/app/data”的门牌。不管这个仓库原本是属于宿主机上的哪个角落,只要挂载成功,系统看到这个门牌,就会把读写请求引流到对应的物理仓库去。
3. 从 Docker 容器层面理解:打破隔离的“传送门”
现在回到 Docker 容器数据卷(
-v参数)。容器在启动时,利用 Linux 的 Namespace 技术做了一个障眼法(隔离),容器内部以为自己拥有一个完整、独立的文件系统(自己有一套独立的
/根目录树)。它默认是完全看不到宿主机(也就是你原本的 Ubuntu 系统)上的任何文件的。当我们执行
-v /home/ubuntu/workspace:/app/workspace这个命令时,就是在执行一次挂载动作:
- Docker 告诉 Linux 内核:“在容器这棵封闭的目录树里,找到
/app/workspace这个节点(挂载点)。”- “然后,把宿主机上的
/home/ubuntu/workspace这个目录所对应的底层物理磁盘区域,**挂载(嫁接/绑定)**到容器的这个节点上。”总结: 在 Docker 的语境下,“挂载”就是在容器那层封闭的“隔离罩”上开一个口子,并将外部(宿主机)的一块真实存储空间,像接水管一样,对接到容器内部的某个指定目录上。 使得容器内部可以通过自己熟悉的路径(
/app/workspace),直接操作外部的物理磁盘。
五、常用软件安装
1、MySQL
中文乱码问题 - 修改字符集
数据备份问题 - 容器数据卷
2、redis
数据备份问题
六、复杂安装
1、MySQL主从复制
2、基于docker容器下Redis三主三从集群环境的搭建以及扩容和缩容
(1) 问题引入:海量数据存储难题
当面临 1-2亿条海量数据 的缓存需求时,单机单台 Redis 是绝对无法承载的,必须通过分布式存储来解决。Redis 落地分布式存储的演进过程,主要经历了以下三种核心分区方案。
(2) 核心演进:三种分布式存储方案对比
方案一:哈希取余分区
最基础的分区方式,通过公式对数据的 Key 进行计算,映射到对应的节点上。
-
基础逻辑:
hash(key)(modN) hash(key) \pmod{N} hash(key)(modN)
(N 为 Redis 服务器数量)。 -
优点: 算法简单,数据分布相对平均,自带基础的负载均衡效果。
-
致命缺陷与原理解析: 极难应对节点的扩容与缩容。当节点宕机时,会导致毁灭性的后果。这里涉及到一个核心的“除数 NNN 困境”:
- 如果 NNN 随存活节点变动: 假设 3 台变 2 台,公式由 3 变为2。这将导致全局哈希映射关系完全打乱,引发全局缓存雪崩,所有请求瞬间击穿到数据库。
- 如果 NNN 固定不变: 假设固定为 (mod3)\pmod{3}(mod3),某一台节点宕机后,原本应该落在该宕机节点上的那 1/31/31/3 数据将永远无法写入和读取,导致局部数据永久失效。
方案二:一致性哈希算法
为了解决哈希取余在节点上下线时造成的全局雪崩,一致性哈希应运而生。
-
基础逻辑: 构建一个范围是 000 到 232−12^{32}-1232−1 的闭合“哈希环”。将节点的 IP 或端口计算哈希值后映射在环上;接着将数据的 Key 也映射在环上,按顺时针方向查找,遇到的第一个节点即为数据的存储节点。
-
优点: 完美避免了节点宕机或扩容带来的大规模数据迁移问题,只会影响哈希环上一小段区间的数据。
-
致命缺陷与原理解析(数据倾斜):
当节点较少时,极易产生数据倾斜。由于节点在哈希环上的落点是随机的,几个节点可能物理上靠得非常近。这会导致环上极大片区域的数据在顺时针查找时,全部命中同一个节点。结果是该节点承担了全网极大部分的流量和数据(被流量打死),而其他节点极度空闲。
-
解决方案(虚拟节点): 为每个真实的物理节点在环上分配大量的“虚拟节点”(例如 NodeA-1, NodeA-2…),让节点在环上交错且密集地分布,从而打散聚集效应,彻底解决数据倾斜问题。
方案三:哈希槽分区(Redis Cluster 官方默认)
融合了前两者的优点,是目前通用且成熟的分布式集群方案。
-
基础逻辑: Redis 集群引入了 16384 个哈希槽(Hash Slot)的概念。将这些槽位平均分配给集群中的各个主节点。
-
计算公式:
HASH_SLOT=CRC16(key)(mod16384) HASH\_SLOT = CRC16(key) \pmod{16384} HASH_SLOT=CRC16(key)(mod16384)
。系统先计算 Key 的 CRC16 值,再对 16384 取模,算出该数据属于哪个槽,最后根据槽位的归属将数据存入对应节点。 -
底层原理解析:为什么偏偏是 16384 个槽?
这涉及到底层数据结构 Bitmap(位图) 与 Gossip 通信协议 的网络性能博弈。
- Bitmap 数据结构: Redis 用一串连续的二进制位(0和1)来记录节点负责的槽位状态。非常节省内存。
- 网络性能权衡: 集群去中心化,节点间每秒都在频繁互相发送 PING/PONG 心跳包,每次心跳都必须携带记录槽位的 Bitmap。Redis 作者评估,Redis 集群节点极难超过 1000 个。对于 1000 个以内的节点,16384 个槽位足以保证分布均匀。如果强行使用 65536 个槽,每次心跳包体积膨胀 4 倍(达到 8KB),在成千上万并发的心跳交互下,会白白吃掉极其宝贵的内网带宽和 CPU 序列化资源。这是典型的“够用就好,权衡利弊”的架构思想。
-
读写与容错: 数据直接写到主节点,从节点进行同步备份。主节点宕机时,从节点立刻顶替成为新主节点;原主节点恢复后,将降级为新主节点的从节点。
(3) 集群运维:主从扩缩容机制
在使用 Docker 搭建的三主三从集群中,面对业务量变化时的扩缩容操作有着严格的执行顺序。
1. 主从扩容(Scale-out)
核心逻辑:加主节点 —> 分配槽号 —> 加从节点
- 加入新节点: 使用
add-node命令将新的空节点加入集群。 - 重新分配槽号(Reshard): 此时新节点没有槽号,无法接收数据。使用
reshard命令进行分配。- 交互选择:输入需要分配的槽号数量(例如 4 个主节点,平分就是 4096 个)。
- 指定接收方:输入新加入主节点的 ID。
- 指定源节点:输入
all,这意味着将从集群现有的所有主节点中,均匀地抽取槽号凑齐数量分配给新节点。
- 配置从节点: 新主节点就绪后,再次加入一个新节点,并利用
--cluster-slave等命令将其挂载为新主节点的从库。
2. 主从缩容(Scale-in)
核心逻辑:删从节点 —> 迁移槽号 —> 删空主节点
- 下线从节点: 首先使用
del-node命令,安全移除准备下线主节点对应的从节点。 - 清空主节点槽号(Reshard): 一个带有槽号的主节点是无法被直接删除的。必须使用
reshard命令将它的槽号全部交出去。- 交互选择:输入该节点拥有的全部槽号数量。
- 指定接收方:输入集群中任意一个存活主节点的 ID。
- 指定源节点:输入要下线的这个主节点 ID。
- 确认执行:输入
done。系统收到指令后,开始将该节点的槽号和对应数据全部迁移走。
- 下线主节点: 当该主节点的槽号被彻底清空后,再次使用
del-node命令将其安全移除。至此,集群平滑恢复到原有的节点规模。
七、Dockerfile
1、 核心概念与构建流水线
Dockerfile 是用来构建 Docker 镜像的文本文件,包含了一系列构建镜像所需的指令和参数。
核心流水线:
- 编写
Dockerfile文件。 - 构建
docker build -t 镜像名:标签 .(注意最后的.代表当前上下文目录)。 - 运行
docker run 镜像名:标签依镜像运行容器实例。 简言之:Dockerfile -> (docker build) -> Docker 镜像 -> (docker run) -> Docker 容器。
2、 基础语法
- 指令大小写:
- 每条保留字指令都必须为大写字母。
- Docker 其实是大小写不敏感的。但强烈建议(也是官方规范)全部使用大写,目的是为了和后面的参数内容做明显区分,提高可读性。
- 执行顺序: 指令严格按照从上到下的顺序执行。
- 注释:
#表示注释。 - 镜像层生成:
- 每条指令都会创建一个新的镜像层。
- 并非所有指令都会创建镜像层!只有修改了容器文件系统的指令(如
RUN,COPY,ADD)才会创建新的镜像层。像ENV,EXPOSE,WORKDIR这些指令只是向镜像中添加元数据,不会增加新的层。为了减小镜像体积,实际开发中常利用&&将多条RUN命令合并为一层。
3、 Docker 执行 Dockerfile 的底层逻辑
你总结的“启动基础容器 -> 执行修改 -> commit -> 再启动新容器”是 Docker 经典的构建模型,非常准确。
详细流程梳理:
- Docker 从
FROM指定的基础镜像启动一个临时容器。 - 执行下一条指令,对容器做出修改。
- 执行类似
docker commit的操作,提交一个新的镜像层。 - Docker 再基于刚提交的镜像层运行一个新的临时容器。
- 继续执行下一条指令,直到所有指令执行完成。
- 最后删除所有中间产生的临时容器。
4、 核心保留字指令对比与补充
- FROM: 指定基础镜像(必须是 Dockerfile 的第一条非注释指令)。
- MAINTAINER: ⚠️ 已废弃。官方现在推荐使用
LABEL maintainer="xxx@email.com"来替代。 - WORKDIR: 指定工作目录(登录容器后的默认路径)。如果目录不存在会自动创建。
- ENV: 设置环境变量,后续指令(如
RUN)以及运行时的容器都可以使用。 - EXPOSE: 声明容器对外暴露的端口(仅声明,真正的端口映射要在
docker run -p时做)。 - VOLUME: 定义匿名数据卷,防止数据丢失和容器膨胀。
5、两对“易混淆”指令的终极对决
1. ADD vs COPY (推荐使用 COPY)
- COPY: 纯粹的复制,将宿主机的文件/目录拷贝到镜像中。
- ADD: 除了具备 COPY 的功能外,还会自动处理 URL 并且能自动解压 tar 压缩包。
- 最佳实践: 除非明确需要解压缩功能,否则尽量使用
COPY,因为它的行为更透明可控。
2. CMD vs ENTRYPOINT
-
CMD: 指定容器启动时默认执行的命令。
- 一个 Dockerfile 中可以有多个 CMD,但只有最后一个生效。
- 会被覆盖:如果在
docker run后面加了命令(例如docker run ubuntu /bin/bash),则 CMD 指定的命令会被直接忽略替换。
-
ENTRYPOINT: 指定容器启动时必定执行的命令。
- 不会被覆盖:
docker run后面的参数不会替换 ENTRYPOINT 的命令,而是作为参数追加给 ENTRYPOINT 指定的程序。
- 不会被覆盖:
-
最佳组合实践: 经常将它们结合使用,
ENTRYPOINT负责写死核心程序,CMD负责提供默认参数。Dockerfile
ENTRYPOINT ["nginx", "-g"] CMD ["daemon off;"] # 这样既能保证启动nginx,又允许用户在 run 的时候传入其他参数覆盖 CMD
6、 虚悬镜像 (Dangling Image)
- 概念: 仓库名 (Repository) 和 标签 (Tag) 都是
<none>的镜像。通常是因为新构建的镜像与旧镜像同名,旧镜像的标签被剥夺后产生的。 - 查看:
docker image ls -f dangling=true - 清理:
docker image prune(建议定期清理释放磁盘空间)。
7、 实战演练:微服务 JAR 包部署最佳实践
基于 Spring Boot 3 的项目(通常需要 Java 17 或更高版本),一个标准的生产级 Dockerfile 应该长这样:
# 1. 指定基础镜像 (选择轻量级的 alpine 版本减小镜像体积)
FROM eclipse-temurin:17-jre-alpine
# 2. 维护者信息 (使用 LABEL 替代 MAINTAINER)
LABEL maintainer="developer"
# 3. 设置工作目录
WORKDIR /app
# 4. 将构建好的微服务 jar 包拷贝进镜像 (推荐使用 COPY)
# 假设宿主机的 target 目录下有 app.jar
COPY target/my-microservice-1.0.jar /app/app.jar
# 5. 暴露微服务端口 (例如 8080)
EXPOSE 8080
# 6. 容器启动时执行的命令 (使用 ENTRYPOINT 确保 Java 进程作为 PID 1 运行)
ENTRYPOINT ["java", "-jar", "app.jar"]
操作全流程:
- 将打好的 JAR 包和这个
Dockerfile放在 Linux/Ubuntu 宿主机的同一目录下。 - 构建镜像:
docker build -t my-microservice:v1.0 . - 运行容器:
docker run -d -p 8080:8080 --name my-service my-microservice:v1.0
八、Docker Network
第一部分:Docker 的本质与现代架构演进
在深入网络之前,必须先纠正一个常见的认知误区,并理解 Docker 是如何运行的。
1. 核心纠错:Docker 并不是“小型 Linux”
- 误区:认为容器是一个轻量级的虚拟机(VM),内部跑着一个完整的操作系统。
- 真相:容器没有自己的操作系统内核。所有的容器都直接共享宿主机的 Linux 内核。它本质上只是宿主机上的一个被高度隔离的普通进程。
- 隔离技术的底层支撑:
- Namespace(命名空间):欺骗进程,为其提供独立的网络(Network Namespace)、文件系统、进程编号等视角。
- Cgroups(控制组):限制进程的物理资源使用量(CPU、内存等)。
2. 现代 Docker 的运行架构 (Daemonless 架构)
早期的 Docker 是一个庞大的单体架构,一旦 Docker Daemon 崩溃,所有容器都会死亡。现代 Docker 遵循 OCI 标准,进行了极致的解耦:
- Docker Client:用户敲指令的客户端,发送 REST API。
- dockerd (Docker Daemon):处理上层逻辑(网络、数据卷、镜像构建等)。
- containerd:高级容器运行时,负责管理容器的生命周期。
- containerd-shim (垫片):核心精妙设计! 它充当真正业务进程的“保姆”。有了它,即使
dockerd或containerd重启或崩溃,正在运行的容器也完全不受影响(热升级的基础)。 - runc:低级容器运行时。拿着图纸去跟 Linux 内核交互,配置好 Namespace 和 Cgroups,把容器进程启动起来后,立刻自杀退出。
第二部分:网络基础设施与“软件路由器”本质
Docker 在宿主机上其实扮演了一个**“软件 NAT 路由器”**的角色。
1. 宿主机的网络初始状态
在 Docker 启动前或未启动时,执行 ip addr 通常看到:
ens33/eth0:宿主机的真实物理网卡。lo:本地回环地址(127.0.0.1)。virbr0:如果有的话,通常是 KVM/libvirt 等虚拟机软件创建的网桥,提供 NAT 访问外网。
当 Docker 启动后,会自动多出一个 docker0,这是一个虚拟网桥,相当于 Docker 在宿主机里虚拟出来的一台“局域网交换机”。
2. 核心网络概念的层级关系(办公楼类比)
- Network Driver (网络驱动):施工队/图纸。负责执行网络搭建的具体代码逻辑。
- Bridge (网桥,如 docker0):楼层交换机。把多个设备连接在同一个局域网子网下(如
172.17.0.0/16)。 - Network Interface (网络接口/网卡):网线与插口。最典型的是 veth pair(虚拟以太网对),它像一根无形的网线,一头插在容器里(
eth0),另一头插在docker0交换机上。 - IP (IP 地址):门牌号。定位网络中的具体机器(容器)。
- PORT (端口):部门分机号。定位机器内部具体处理业务的应用程序进程。
3. 端口映射的底层原理 (NAT)
当我们执行 docker run -p 8083:8080 时,并不是 Docker 施展了魔法,而是它修改了 Linux 内核的 iptables 防火墙规则。
请求流转过程:
- 外部访问
宿主机IP:8083。 - 宿主机内核拦截请求,触发 DNAT(目标地址转换),将目标地址改写为
容器IP:8080。 - 数据包被转发给
docker0网桥。 docker0顺着veth pair网线,将包准确送入容器内部的 8080 端口。
第三部分:Docker 的四大网络模式
1. Bridge 模式(桥接模式 - 默认)
- 机制:为容器分配独立的 Network Namespace,配置独立的 IP,并通过
veth pair连接到docker0网桥上。 - 特点:容器之间可以通过内网 IP 互通(因为都在
docker0这个交换机下),外部需要通过-p端口映射才能访问容器。 
2. Host 模式(主机模式)
- 机制:容器不会虚拟自己的网卡,不拥有独立的 Network Namespace,而是直接与宿主机共享网络空间。
- 特点:
- 跳过了
docker0(但docker0本身依然存在于宿主机上)。 - 容器直接使用宿主机的 IP 和端口。
- 注意:
-p端口映射会完全失效。如果容器内的应用(如 Spring Boot)监听 8080 端口,而宿主机的 8080 已被占用,容器会直接启动失败报错,端口绝对不会自动递增。 
- 跳过了
3. None 模式(禁用模式)
- 机制:容器拥有独立的 Network Namespace,但 Docker 不会为其配置任何网络接口(除了本地的
lo回环网卡)。 - 特点:绝对的物理隔离,没有网关,没有对外 IP。通常用于运行对安全性要求极高、不需要联网的批处理任务或密码学计算。
4. Container 模式(容器共享模式)
- 机制:新创建的容器不会配置自己的网卡和 IP,而是和一个指定的、已存在的容器共享同一个 Network Namespace。
- 特点:两个容器共享同一套 IP 和端口范围(它们之间可以通过
localhost高效通信),但它们的文件系统、进程列表等依然是隔离的。如果那个被依赖的“主容器”死了,这个新容器的网络也就断了(只剩下lo)。 
第四部分:微服务通信与自定义网络 (Custom Bridge)
1. 为什么容器之间需要互联?
现代软件开发采用松耦合的微服务架构。我们不会把 Vue 前端、Spring Boot 后端、Redis 缓存和 MySQL 数据库塞进一个容器里。标准的做法是“一个容器跑一个服务进程”。因此,后端容器必须能够通过网络访问数据库容器的 3306 端口。
2. 默认 Bridge 网络的痛点
在默认的 docker0 桥接网络下,容器每次重启,分配到的 IP 地址可能会发生变化。在实际项目中,我们绝对不能把连接 MySQL 的 IP 地址写死(比如写成 172.17.0.4),否则容器一重启系统就崩溃了。
3. 终极解决方案:自定义网络
执行 docker network create my-net 创建自定义网络,并在 docker run 时通过 --network my-net 将容器加入该网络。
- 核心优势:自定义网络内部嵌入了 DNS 解析服务。
- 效果:它自动维护了“容器名/服务名”与“动态 IP”之间的映射关系。你的后端项目配置中,数据库地址可以直接写成
jdbc:mysql://mysql-container:3306/db。哪怕 MySQL 容器重启 IP 变了,通过mysql-container这个名字依然能瞬间 ping 通!
Q1:不同模式下的容器底层到底怎么通信的?
- Bridge:走虚拟网线(
veth pair) ->docker0交换机 -> 另一个容器。 - Host:完全没有隔离,就是宿主机上的两个普通进程在用
localhost通信。 - Container:两个容器被塞进同一个网络命名空间,就像住在一个宿舍里,互相之间直接用
localhost喊话。
Q2:libcontainer、graph driver、exec driver 是什么?
- 这些是你查阅到的早期 Docker(1.11 版本之前)的核心架构组件包。在现代 Docker 中,它们的功能已经被 OCI 标准下的
containerd和runc完美平替和升级了,但“通过驱动(Driver)去执行具体操作”的核心设计思想得以保留。
Q3:Host 模式下,查看 IP 时 docker0 会消失吗?
- 绝对不会。
docker0是 Daemon 启动时全局建立的基建设备。Host 模式只是代表你“当前正在启动的这个容器”不去连接这个网桥,不代表网桥被拆了。
九、Docker-Compose 容器编排
1、 Docker Compose 核心概念与作用
- 痛点: 部署微服务时,每个服务单独写 Dockerfile、单独
docker run构建镜像和启动容器,还要人为控制先后顺序(如先启 MySQL 再启微服务),过程极其繁琐。 - 解决方案: Docker 提供的开源项目,用于实现容器集群的快速编排。
- 核心要素:
- 配置文件:
docker-compose.yml(YAML格式)。 - 工程 (Project): 由一组关联的应用容器组成的完整业务单元。
- 服务 (Service):
services标签下的一个个应用容器实例。
- 配置文件:
- 实战三步曲:
- 编写 Dockerfile 定义微服务镜像。
- 编写
docker-compose.yml安排好整体应用中的各个容器服务。 - 执行
docker compose up -d完成一键部署上线(等价于一次性运行了多个docker run命令)。
2、 实战疑问与核心机制解答 (Q&A)
❓ 疑问一:关于容器的自动命名 (container_name)
我的问题:
如果配置了
container_name那就按照配置的来,但如果没有配置,执行docker-compose up -d后,容器名好像会自动加上前缀和后缀,比如mydocker_mysql_1、mydocker_redis_1。这是莫名其妙加上去的吗?需要怎么理解?
✅ 核心解答(防冲突机制): 这不是莫名其妙加的,而是 Compose 为了防止容器名称冲突而设计的默认命名规则。 当你没有明确指定 container_name 时,命名公式为:<项目名称>_<服务名称>_<序号>
- 项目名称 (mydocker): 默认取
docker-compose.yml所在的目录名称。 - 服务名称 (mysql/redis): 取自 YML 文件中
services下定义的子标签名。 - 序号 (1): 默认为 1。如果后续动态扩容(比如启动 3 个 redis),序号会依次递增,防止重名。
- 💡 总结建议: 生产环境中不推荐写死
container_name,让它自动命名能保证后续扩容的灵活性。
❓ 疑问二:网络配置中的 IP 替换与服务发现
我的问题:
微服务项目中经常配置 MySQL 和 Redis 的 IP,比如
spring.datasource.url=jdbc:mysql://192.168.111.169:3306/...。但现在用 Compose 好像不用写具体的 IP 或 localhost 了,直接写mysql或redis就可以替换。 这和我们 Docker 实际生成的容器名字(比如mydocker_mysql_1)也对应不上啊,这是根据 YML 文件中services下的子标签来决定的吗?
✅ 核心解答(服务发现与内嵌 DNS): 直觉非常准!这正是由 services 下的子标签名称决定的。 这背后依赖的是 Docker 强大的内嵌 DNS 服务器和服务发现机制。
-
痛点: 容器每次重启,内部 IP 都可能改变,写死 IP 会导致服务直接宕机。
-
工作原理:
- 当你执行
docker-compose up时,它会默认创建一个自定义的桥接网络,并把所有容器放进去。 - Docker 在每个加入该网络的容器里,塞了一个隐形的 DNS 服务器(地址固定为
127.0.0.11)。 - 它会将 YML 中的服务名(如
mysql)与该容器当前的动态 IP 偷偷绑定。 - 当微服务代码请求
mysql:3306时,底层会问127.0.0.11获取到正确的实时 IP,从而建立连接。
- 当你执行
-
💡 总结: 服务名相当于一个不会变的“域名”,彻底屏蔽了底层 IP 变化的干扰。注意,这个机制只有在自定义网络下才生效(Compose 默认就是自定义网络)。
-
核心避坑点:自定义网络
内嵌 DNS 和服务名解析机制,只在“自定义网络 (User-defined networks)”下生效!
- 简单的单体
docker run(如果不加--network参数),容器默认挂载在自带的bridge网络上,无法通过名字互通。 - Compose 的强大之处在于:执行
up命令时,它会自动为当前工程创建一个专用的自定义网络,并将所有定义的服务放进去,从而全面激活服务发现功能,彻底屏蔽了 IP 变化的困扰。
- 简单的单体
❓ 疑问三:服务停止时的顺序
我的问题:
执行
docker-compose stop的时候,日志显示: Stopping ms01 … done Stopping mydocker_mysql_1 … done Stopping mydocker_redis_1 … done 停止的时候好像也是按照某种固定的顺序进行的?
✅ 核心解答(依赖排序与优雅停机): 观察非常细致!Compose 是严格基于 depends_on 属性的有向图排序来控制启停的。
-
启动顺序: 先启动被依赖的基础设施(先 MySQL+Redis),后启动业务微服务。这就解决了你提到的“先后顺序要求固定”的问题。
-
停止顺序(相反): 先停止最上层的业务微服务(ms01),再停止底层的数据库(MySQL/Redis)。
-
💡 总结: 这是为了实现优雅停机。先切断业务微服务的外部流量,等它安全关闭后,再关数据库,防止微服务运行中突然连不上数据库而引发大量报错。
-
优雅停机与依赖排序
Compose 在执行启动(
up)和停止(stop/down)时,遵循基于depends_on属性的有向图排序。- 启动顺序: 优先启动被依赖的基础设施服务(如先启动 MySQL 和 Redis),最后启动依赖它们的上层微服务。
- 停止顺序: 完全相反。先停止上层微服务以切断外部流量,然后再停止底层的数据库和缓存,从而实现优雅停机,避免服务运行期间产生大量数据库断连报错。
3、 标准 YAML 模板参考
version: '3.8'
services:
microService:
image: zzz_docker:1.6
# container_name: ms01 # 推荐注释掉,让其自动命名
ports:
- "6001:6001"
volumes:
- /app/microService:/data
networks:
- zzz_net
depends_on: # 决定了启动和停止的顺序
- redis
- mysql
redis:
image: redis:6.0.8
ports:
- "6379:6379"
volumes:
- /app/redis/redis.conf:/etc/redis/redis.conf
- /app/redis/data:/data
networks:
- zzz_net
command: redis-server /etc/redis/redis.conf
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: '123456'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
MYSQL_DATABASE: 'db2021'
networks:
- zzz_net
# 必须在顶层显式声明使用的自定义网络,否则服务发现可能不生效
networks:
zzz_net:
十、Docker 轻量级可视化工具 Portainer
1、 Portainer 简介
- 定位:Portainer 是一款轻量级的应用。
- 核心功能:它提供了一个直观的图形化界面 (Web UI)。
- 适用范围:用于方便地管理 Docker 环境,无论是单机环境(Local)还是集群环境(Docker Swarm)。
2、 安装与运行 (包含 --restart=always)
Portainer 是直接通过 docker 命令进行安装和运行的。
- 关于
--restart=always:在通过docker run启动 Portainer 容器时,加上--restart=always是非常关键的最佳实践。- 它的作用:这是一个重启策略(Restart Policy)。它告诉 Docker 守护进程,无论容器是因为什么原因退出(报错崩溃),或者 Docker 服务本身被重启了(比如服务器重启),都要自动重新启动这个容器。
- 为什么重要:因为 Portainer 是你的“管理控制台”,你肯定希望它随时保持在线状态。
3、 初始化与配置步骤
根据思维导图和截图,初次使用的流程如下:
- 访问 Web 界面:容器启动后,通过浏览器访问服务器 IP 地址加端口号(通常是
http://xxx.xxx.xxx.xxx:9000)。 - 创建管理员账号:首次登录时,系统会强制要求设置
admin用户的密码。 - 选择环境:登录后,选择 Local 选项卡,连接并接管本地的 Docker 守护进程(通常通过挂载
/var/run/docker.sock实现)。 - 进入概览:连接成功后,即可展示本地 Docker 的详细资源使用情况。
4、 核心功能展示与对应命令
Portainer 把以下原本需要敲命令的操作变成了点击:
- Endpoint / Dashboard (仪表盘)
- 界面展示:直观统计了当前系统中运行的 Stacks(栈)、Containers(容器数量,包含健康/运行/停止状态)、Images(镜像数量与占用空间)、Volumes(数据卷数量)和 Networks(网络)。
- 对应命令:类似于
docker info或docker system df。
- Images (镜像管理)
- 界面展示:列出了所有已下载的镜像及其 Tag、占用大小(Size)和创建时间。带有
Unused标签的表示当前没有容器在使用该镜像。 - 对应命令:
docker images。删除Unused镜像则相当于docker image prune或docker rmi <image_id>。
- 界面展示:列出了所有已下载的镜像及其 Tag、占用大小(Size)和创建时间。带有
- Containers (容器管理)
- 界面展示:列表展示了所有容器的名字、状态(running / stopped / created)、使用的镜像、端口映射(Published Ports)等。并且可以直接通过顶部的按钮进行
Start(启动)、Stop(停止)、Restart(重启)、Remove(删除) 等快捷操作。 - 对应命令:
docker ps(查看运行中) 和docker ps -a(查看所有)。启动/停止等操作对应docker start/stop/rm <container_name>。
- 界面展示:列表展示了所有容器的名字、状态(running / stopped / created)、使用的镜像、端口映射(Published Ports)等。并且可以直接通过顶部的按钮进行
总结来说: Portainer 极大地降低了 Docker 的使用门槛。对于日常的运维排查、容器启停、日志查看以及无用资源的清理,使用 Portainer 的图形界面比记忆和拼写各种 Docker 命令行参数要高效和安全得多。
5、 核心定位的区别:集装箱 vs 港口调度系统
K8s(Kubernetes)和 Docker 并不是严格意义上“非此即彼”的替代关系,而是“统帅与士兵”的关系。
准确地说,达到一定规模后,推荐使用 K8s 来“编排和管理”容器,而不再仅仅依靠原生的 Docker 命令或单机的 Portainer。
- Docker(容器引擎):它的核心任务是**“打包和运行”**。它把你的应用及其依赖环境打包成一个标准的“集装箱”(镜像),确保这个集装箱在任何机器上都能跑起来。
- K8s(容器编排系统):它的核心任务是**“调度和管理”**。当你有成百上千个“集装箱”散落在几十台服务器上时,K8s 就像是一个智能的港口调度系统,负责决定哪个集装箱放在哪台服务器上、某个服务器宕机了怎么把集装箱转移走、流量大了如何自动增加集装箱的数量。
6、 为什么规模大了必须上 K8s?
在开发初期或单体架构下,使用 Docker 和 Docker Compose 完全足够。但当你采用微服务架构(比如使用 Spring Boot 3 配合 Spring Cloud Alibaba 生态)构建企业级系统时,随着业务和流量的增长,单纯的 Docker 会面临巨大的挑战:
- 高可用与故障自愈 (自我恢复)
- Docker:如果一台服务器物理宕机,上面跑的所有 Docker 容器全挂,
--restart=always也没用,因为宿主机死了。你需要手动去另一台机器拉取镜像并启动。 - K8s:如果 K8s 集群中的某个节点(Node)宕机,K8s 的控制面板会立刻察觉,并自动将该节点上的服务调度到其他健康的节点上重新启动,整个过程无需人工干预。
- Docker:如果一台服务器物理宕机,上面跑的所有 Docker 容器全挂,
- 弹性伸缩 (Auto-scaling)
- Docker:遇到突发的高并发,你需要手动敲命令去启动更多的容器实例,甚至还要手动去 Nacos 里确认服务有没有注册成功。
- K8s:你可以配置 HPA(Horizontal Pod Autoscaler)。当 CPU 或内存使用率达到阈值时,K8s 会自动帮你把实例从 2 个扩容到 20 个;流量洪峰过去后,再自动缩容回 2 个,极其契合高并发场景。
- 跨主机网络与负载均衡
- Docker:单机网络很简单,但如果要让不同物理机上的 Docker 容器互相通信,配置起来非常繁琐。
- K8s:天生支持集群级别的网络。它有自己的 Service 概念,能够自动为后端的多个容器实例提供统一的访问入口和负载均衡。
- 无缝滚动更新
- 当你发布新版本时,K8s 可以做到平滑的“滚动更新”(Rolling Update)——先启动几个新版本的实例,确认健康后,再停掉几个老版本的实例,逐步替换,用户完全感知不到系统正在升级。
7、 历史遗留的误解:K8s 弃用 Docker?
-
K8s 弃用的仅仅是 dockershim(一个让 K8s 能够直接调用 Docker API 的中间件),目的是为了减轻自身的代码维护负担。
-
K8s 现在默认使用的是更底层、更轻量的容器运行时,比如 containerd(其实 containerd 本来也就是 Docker 捐赠给开源社区的核心组件)。
-
结论是:你依然可以使用 Dockerfile 来构建镜像,你构建出的 Docker 镜像依然可以完美地在 K8s 集群中运行。
-
开发/测试/小规模部署:继续用 Docker + Docker Compose + Portainer,轻量、敏捷、心智负担低。
-
企业级微服务/大规模/生产环境:毫不犹豫地走向 K8s。它虽然学习曲线陡峭,但它是目前云原生架构不可撼动的基石。
十一、Docker容器监控之CAdvisor+InfluxDB+Granfana
1. 为什么需要 CIG 架构?(解决痛点)
原生的 docker stats 命令虽然可以方便地查看当前宿主机上所有容器的 CPU、内存、网络流量等实时状态,对于极小规模的公司可能够用,但它存在明显的致命缺点:
- 只能看当前宿主机的容器数据。
- 数据纯实时,没有持久化存储。
- 缺乏健康指标过线预警等高级功能。
为了解决这些问题,引入了 CIG 架构。
2. 核心架构一句话总结
- CAdvisor -> 负责监控收集 (Collects)
- InfluxDB -> 负责存储数据 (Stores)
- Grafana -> 负责展示图表 (Visualizes)
3. 三大组件详细拆解
① 数据采集引擎:CAdvisor
- 定位:Google 开源的容器资源监控工具,能够监控容器的 CPU、内存、网络 IO、磁盘 IO 等。
- 特点:
- 自带一个 Web 页面用于查看实时运行状态。
- 局限性:默认只在单机保存最近 2分钟 的数据。
- 功能:展示 Host (宿主机) 和容器两个维度的监控数据。
- 扩展性:提供了数据集成接口,支持将监控数据发往 InfluxDB、Redis、Kafka 等数据库进行持久化。
② 时序数据库:InfluxDB
- 定位:使用 Go 语言编写的开源分布式时序、事件和指标数据库,无需外部依赖。
- 作用:完美弥补 CAdvisor 只能存储 2 分钟数据的短板。CAdvisor 在启动时只需指定配置,即可将数据持久化到 InfluxDB 中。
- 核心特性:
- 基于时间序列,支持时间相关的聚合函数(如最大值、最小值、求和等)。
- 可度量性强,支持对大量数据进行实时计算。
- 基于事件驱动。
③ 可视化与告警面板:Grafana
- 定位:开源的数据监控分析可视化平台。
- 作用:读取 InfluxDB 中的数据,并通过酷炫的仪表盘进行展示。
- 核心特性:
- 支持多种数据源(InfluxDB、MySQL、Elasticsearch 等)。
- 提供灵活丰富的图形化选项,支持混合多种风格。
- 支持白天/夜间模式切换。
- 支持丰富的插件、模板、图表权限控制以及最重要的报警功能(弥补了
docker stats无预警的缺陷)。
4. 部署实战路线 (基于 Docker Compose)
工程化部署思路:
- 新建目录:准备一个专属的工作目录。
- 编写配置:新建包含 CAdvisor、InfluxDB、Grafana 三件套组合的
docker-compose.yml编排文件。 - 一键启动:执行命令启动 docker-compose 文件。
- 状态检查:查看这三个服务容器是否已经成功挂载并启动。
- 页面测试:进入 Web 端测试面板连通性及数据收集情况。
version: '3.1'
volumes:
grafana_data: {}
services:
influxdb:
image: tutum/influxdb:0.9
restart: always
environment:
- PRE_CREATE_DB=cadvisor
ports:
- "8083:8083"
- "8086:8086"
volumes:
- ./data/influxdb:/data
cadvisor:
image: google/cadvisor
links:
- influxdb:influxsrv
command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086
restart: always
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
grafana:
user: "104"
image: grafana/grafana
restart: always
links:
- influxdb:influxsrv
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- HTTP_USER=admin
- HTTP_PASS=admin
- INFLUXDB_HOST=influxsrv
- INFLUXDB_PORT=8086
5.配置内容详细解释
这个编排文件定义了三个服务(services),它们相互配合完成数据的采集 -> 存储 -> 展示。
1. 基础配置
version: '3.1': 指定 docker-compose 语法的版本。volumes: grafana_data: {}: 声明了一个由 Docker 管理的具名数据卷(Named Volume),专门用来持久化 Grafana 的数据(如图表配置、面板等)。
2. InfluxDB 服务 (时序数据库)
这是整个架构的数据存储中心。
image: tutum/influxdb:0.9: 使用的镜像版本(注意这是一个比较老的经典版本)。environment: PRE_CREATE_DB=cadvisor: 环境变量,非常关键。它告诉 InfluxDB 在启动时自动创建一个名为cadvisor的数据库,省去了手动进去建库的麻烦。ports:8083:8083: InfluxDB 的 Web 管理控制台端口。8086:8086: InfluxDB 的 HTTP API 数据交互端口(CAdvisor 就是往这个端口发数据)。
volumes: 将宿主机的./data/influxdb目录挂载到容器的/data,确保采集到的监控数据持久化到宿主机上,容器重启数据不丢。
3. CAdvisor 服务 (数据采集器)
负责收集宿主机和所有容器的资源使用情况。
links: - influxdb:influxsrv: 容器链接配置。它允许 cadvisor 容器通过influxsrv这个别名来访问 influxdb 容器(相当于在内部做了个 DNS 映射)。command: ...: 覆盖容器启动后的默认命令。这里是告诉 CAdvisor 把收集到的数据往哪里存:-storage_driver=influxdb: 指定存储驱动为 InfluxDB。-storage_driver_db=cadvisor: 指定写入刚刚 InfluxDB 自动创建的cadvisor数据库。-storage_driver_host=influxsrv:8086: 指定 InfluxDB 的地址,使用了上面links定义的别名。
volumes(核心挂载): CAdvisor 需要极其底层的权限才能监控容器,所以它挂载了宿主机的核心目录:/:/rootfs:ro和/sys:/sys:ro: 以只读 (ro) 方式挂载宿主机根目录和系统内核目录,用于获取宿主机硬件及进程状态。/var/run:/var/run:rw: 读写挂载,通常是为了访问 Docker 的 socket 文件。/var/lib/docker/:/var/lib/docker:ro: 只读挂载 Docker 的数据目录,用于获取各个容器的具体信息。
4. Grafana 服务 (可视化面板)
负责把 InfluxDB 里的冷冰冰的数据变成高大上的图表。
user: "104": 指定容器以 UID 为 104 的用户身份运行(通常是为了解决某些特定的文件权限问题)。links: - influxdb:influxsrv: 同样设置别名,方便 Grafana 连接数据库。ports: - "3000:3000": 映射 Grafana 的 Web 访问端口。启动后在浏览器访问http://你的IP:3000即可进入控制台。volumes: - grafana_data:/var/lib/grafana: 使用了文件开头声明的具名卷,保存你辛苦配置好的仪表盘。environment:HTTP_USER=admin / HTTP_PASS=admin: 初始化 Grafana 的登录账号和密码。INFLUXDB_HOST和INFLUXDB_PORT: 提前把 InfluxDB 的连接信息通过环境变量注入进去。
💡 小提示: 我们的配置使用了 links 来打通容器间的网络,这是早期 Docker Compose 的做法。在较新的实践中,我们更推荐使用自定义的网络(networks)来实现容器互通。
址,使用了上面 links 定义的别名。
volumes(核心挂载): CAdvisor 需要极其底层的权限才能监控容器,所以它挂载了宿主机的核心目录:/:/rootfs:ro和/sys:/sys:ro: 以只读 (ro) 方式挂载宿主机根目录和系统内核目录,用于获取宿主机硬件及进程状态。/var/run:/var/run:rw: 读写挂载,通常是为了访问 Docker 的 socket 文件。/var/lib/docker/:/var/lib/docker:ro: 只读挂载 Docker 的数据目录,用于获取各个容器的具体信息。
更多推荐



所有评论(0)