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 的 topps 命令)。
  • 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 系统启动时,文件系统的加载确实分为两层:

  1. bootfs (Boot File System - 引导文件系统)
    • 包含什么: bootloader(引导加载程序,如 GRUB)和 kernel(操作系统内核,如 vmlinuz)。
    • 生命周期: 当系统加电启动时,bootloader 会将操作系统的 kernel 加载到内存中。一旦内核完全加载到内存并启动完成,bootfs 的历史使命就结束了。Linux 会将 bootfs 卸载(Unmount) 以释放内存空间。这就是你印象中“bootfs 消失了”的过程。
  2. rootfs (Root File System - 根文件系统)
    • 包含什么: 就是我们进入 Linux 后看到的标准目录结构,比如 /dev/proc/bin/etc/lib/usr 等,以及各种基础命令和运行库。
    • 生命周期: 在 bootfs 卸载后,内核会挂载 rootfs。但在 Linux 刚启动的极短时间内,rootfs 是以**只读(Read-Only)模式挂载的。内核进行完自检后,才会将其重新挂载为读写(Read-Write)**模式,之后系统才算彻底准备就绪。

第二步:Docker 与 UnionFS 的巧妙结合

Docker 容器并不是一个完整的虚拟机,它不需要自己启动内核,而是直接复用宿主机(Host OS)的内核。因此:

  • Docker 根本不需要 bootfs 容器直接利用宿主机已经加载在内存中的 kernel。
  • Docker 镜像实际上只打包了 rootfs 比如你拉取一个 Ubuntu 镜像,它里面其实只有 Ubuntu 版本的 /bin/etc 等文件结构,大小只有几十兆,因为最占地方和耗时的内核部分被剥离了。
UnionFS 是如何管理 rootfs 的?

这里就是 Docker 镜像分层技术的灵魂所在。UnionFS 的核心能力是:将不同物理位置的目录,联合挂载到同一个目录下,对外呈现出一个统一的文件系统。

Docker 对 rootfs 的管理不再像传统 Linux 那样最终变成全局可读写,而是这样做的:

  1. 只读的镜像层(Image Layers): Docker 镜像是由多层构成的(比如一层基础 OS,一层 Java 环境,一层你的 Spring Boot 代码)。这些层都是基础的 rootfs 的一部分。Docker 利用 UnionFS 将这些层一层一层叠加起来,并且所有这些镜像层都是绝对只读(Read-Only)的
  2. 可读写的容器层(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 相互继承目录映射关系。继承建立后,各个节点地位平等,共同操作同一磁盘块。中间节点挂掉,不影响其他节点。

  • 命令示例

    1. 首先,创建一个基础容器 u1(带有数据卷):

      docker run -it --name u1 -v /home/ubuntu/data:/data ubuntu:24.04 /bin/bash
      
      
    2. 然后,创建容器 u2,并继承 u1 的数据卷规则:

      # 使用 --volumes-from 继承 u1 的数据卷
      docker run -it --name u2 --volumes-from u1 ubuntu:24.04 /bin/bash
      

    现象与验证

    • 此时,宿主机的 /home/ubuntu/datau1/datau2/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 这个命令时,就是在执行一次挂载动作:

  1. Docker 告诉 Linux 内核:“在容器这棵封闭的目录树里,找到 /app/workspace 这个节点(挂载点)。”
  2. “然后,把宿主机上的 /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 数据将永远无法写入和读取,导致局部数据永久失效。
方案二:一致性哈希算法

为了解决哈希取余在节点上下线时造成的全局雪崩,一致性哈希应运而生。

  • 基础逻辑: 构建一个范围是 000232−12^{32}-12321 的闭合“哈希环”。将节点的 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)

核心逻辑:加主节点 —> 分配槽号 —> 加从节点

  1. 加入新节点: 使用 add-node 命令将新的空节点加入集群。
  2. 重新分配槽号(Reshard): 此时新节点没有槽号,无法接收数据。使用 reshard 命令进行分配。
    • 交互选择:输入需要分配的槽号数量(例如 4 个主节点,平分就是 4096 个)。
    • 指定接收方:输入新加入主节点的 ID。
    • 指定源节点:输入 all,这意味着将从集群现有的所有主节点中,均匀地抽取槽号凑齐数量分配给新节点。
  3. 配置从节点: 新主节点就绪后,再次加入一个新节点,并利用 --cluster-slave 等命令将其挂载为新主节点的从库。
2. 主从缩容(Scale-in)

核心逻辑:删从节点 —> 迁移槽号 —> 删空主节点

  1. 下线从节点: 首先使用 del-node 命令,安全移除准备下线主节点对应的从节点。
  2. 清空主节点槽号(Reshard): 一个带有槽号的主节点是无法被直接删除的。必须使用 reshard 命令将它的槽号全部交出去。
    • 交互选择:输入该节点拥有的全部槽号数量。
    • 指定接收方:输入集群中任意一个存活主节点的 ID。
    • 指定源节点:输入要下线的这个主节点 ID。
    • 确认执行:输入 done。系统收到指令后,开始将该节点的槽号和对应数据全部迁移走。
  3. 下线主节点: 当该主节点的槽号被彻底清空后,再次使用 del-node 命令将其安全移除。至此,集群平滑恢复到原有的节点规模。

七、Dockerfile

1、 核心概念与构建流水线

Dockerfile 是用来构建 Docker 镜像的文本文件,包含了一系列构建镜像所需的指令和参数。

核心流水线:

  1. 编写 Dockerfile 文件。
  2. 构建 docker build -t 镜像名:标签 . (注意最后的 . 代表当前上下文目录)。
  3. 运行 docker run 镜像名:标签 依镜像运行容器实例。 简言之:Dockerfile -> (docker build) -> Docker 镜像 -> (docker run) -> Docker 容器。

2、 基础语法

  1. 指令大小写:
    • 每条保留字指令都必须为大写字母。
    • Docker 其实是大小写不敏感的。但强烈建议(也是官方规范)全部使用大写,目的是为了和后面的参数内容做明显区分,提高可读性。
  2. 执行顺序: 指令严格按照从上到下的顺序执行。
  3. 注释: # 表示注释。
  4. 镜像层生成:
    • 每条指令都会创建一个新的镜像层。
    • 并非所有指令都会创建镜像层!只有修改了容器文件系统的指令(如 RUN, COPY, ADD)才会创建新的镜像层。像 ENV, EXPOSE, WORKDIR 这些指令只是向镜像中添加元数据,不会增加新的层。为了减小镜像体积,实际开发中常利用 && 将多条 RUN 命令合并为一层。

3、 Docker 执行 Dockerfile 的底层逻辑

你总结的“启动基础容器 -> 执行修改 -> commit -> 再启动新容器”是 Docker 经典的构建模型,非常准确。

详细流程梳理:

  1. Docker 从 FROM 指定的基础镜像启动一个临时容器。
  2. 执行下一条指令,对容器做出修改。
  3. 执行类似 docker commit 的操作,提交一个新的镜像层。
  4. Docker 再基于刚提交的镜像层运行一个新的临时容器。
  5. 继续执行下一条指令,直到所有指令执行完成。
  6. 最后删除所有中间产生的临时容器。

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"]

操作全流程:

  1. 将打好的 JAR 包和这个 Dockerfile 放在 Linux/Ubuntu 宿主机的同一目录下。
  2. 构建镜像:docker build -t my-microservice:v1.0 .
  3. 运行容器: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 (垫片)核心精妙设计! 它充当真正业务进程的“保姆”。有了它,即使 dockerdcontainerd 重启或崩溃,正在运行的容器也完全不受影响(热升级的基础)。
  • 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 防火墙规则

请求流转过程:

  1. 外部访问 宿主机IP:8083
  2. 宿主机内核拦截请求,触发 DNAT(目标地址转换),将目标地址改写为 容器IP:8080
  3. 数据包被转发给 docker0 网桥。
  4. 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:libcontainergraph driverexec driver 是什么?

  • 这些是你查阅到的早期 Docker(1.11 版本之前)的核心架构组件包。在现代 Docker 中,它们的功能已经被 OCI 标准下的 containerdrunc 完美平替和升级了,但“通过驱动(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 标签下的一个个应用容器实例。
  • 实战三步曲:
    1. 编写 Dockerfile 定义微服务镜像。
    2. 编写 docker-compose.yml 安排好整体应用中的各个容器服务。
    3. 执行 docker compose up -d 完成一键部署上线(等价于一次性运行了多个 docker run 命令)。

2、 实战疑问与核心机制解答 (Q&A)

❓ 疑问一:关于容器的自动命名 (container_name)

我的问题:

如果配置了 container_name 那就按照配置的来,但如果没有配置,执行 docker-compose up -d 后,容器名好像会自动加上前缀和后缀,比如 mydocker_mysql_1mydocker_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 了,直接写 mysqlredis 就可以替换。 这和我们 Docker 实际生成的容器名字(比如 mydocker_mysql_1)也对应不上啊,这是根据 YML 文件中 services 下的子标签来决定的吗?

✅ 核心解答(服务发现与内嵌 DNS): 直觉非常准!这正是由 services 下的子标签名称决定的。 这背后依赖的是 Docker 强大的内嵌 DNS 服务器服务发现机制

  1. 痛点: 容器每次重启,内部 IP 都可能改变,写死 IP 会导致服务直接宕机。

  2. 工作原理:

    • 当你执行 docker-compose up 时,它会默认创建一个自定义的桥接网络,并把所有容器放进去。
    • Docker 在每个加入该网络的容器里,塞了一个隐形的 DNS 服务器(地址固定为 127.0.0.11)。
    • 它会将 YML 中的服务名(如 mysql)与该容器当前的动态 IP 偷偷绑定。
    • 当微服务代码请求 mysql:3306 时,底层会问 127.0.0.11 获取到正确的实时 IP,从而建立连接。
  3. 💡 总结: 服务名相当于一个不会变的“域名”,彻底屏蔽了底层 IP 变化的干扰。注意,这个机制只有在自定义网络下才生效(Compose 默认就是自定义网络)。

  4. 核心避坑点:自定义网络

    内嵌 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、 初始化与配置步骤

根据思维导图和截图,初次使用的流程如下:

  1. 访问 Web 界面:容器启动后,通过浏览器访问服务器 IP 地址加端口号(通常是 http://xxx.xxx.xxx.xxx:9000)。
  2. 创建管理员账号:首次登录时,系统会强制要求设置 admin 用户的密码。
  3. 选择环境:登录后,选择 Local 选项卡,连接并接管本地的 Docker 守护进程(通常通过挂载 /var/run/docker.sock 实现)。
  4. 进入概览:连接成功后,即可展示本地 Docker 的详细资源使用情况。

4、 核心功能展示与对应命令

Portainer 把以下原本需要敲命令的操作变成了点击:

  • Endpoint / Dashboard (仪表盘)
    • 界面展示:直观统计了当前系统中运行的 Stacks(栈)、Containers(容器数量,包含健康/运行/停止状态)、Images(镜像数量与占用空间)、Volumes(数据卷数量)和 Networks(网络)。
    • 对应命令:类似于 docker infodocker system df
  • Images (镜像管理)
    • 界面展示:列出了所有已下载的镜像及其 Tag、占用大小(Size)和创建时间。带有 Unused 标签的表示当前没有容器在使用该镜像。
    • 对应命令docker images。删除 Unused 镜像则相当于 docker image prunedocker rmi <image_id>
  • Containers (容器管理)
    • 界面展示:列表展示了所有容器的名字、状态(running / stopped / created)、使用的镜像、端口映射(Published Ports)等。并且可以直接通过顶部的按钮进行 Start (启动)、Stop (停止)、Restart (重启)、Remove (删除) 等快捷操作。
    • 对应命令docker ps (查看运行中) 和 docker ps -a (查看所有)。启动/停止等操作对应 docker start/stop/rm <container_name>

总结来说: 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 会面临巨大的挑战:

  1. 高可用与故障自愈 (自我恢复)
    • Docker:如果一台服务器物理宕机,上面跑的所有 Docker 容器全挂,--restart=always 也没用,因为宿主机死了。你需要手动去另一台机器拉取镜像并启动。
    • K8s:如果 K8s 集群中的某个节点(Node)宕机,K8s 的控制面板会立刻察觉,并自动将该节点上的服务调度到其他健康的节点上重新启动,整个过程无需人工干预。
  2. 弹性伸缩 (Auto-scaling)
    • Docker:遇到突发的高并发,你需要手动敲命令去启动更多的容器实例,甚至还要手动去 Nacos 里确认服务有没有注册成功。
    • K8s:你可以配置 HPA(Horizontal Pod Autoscaler)。当 CPU 或内存使用率达到阈值时,K8s 会自动帮你把实例从 2 个扩容到 20 个;流量洪峰过去后,再自动缩容回 2 个,极其契合高并发场景。
  3. 跨主机网络与负载均衡
    • Docker:单机网络很简单,但如果要让不同物理机上的 Docker 容器互相通信,配置起来非常繁琐。
    • K8s:天生支持集群级别的网络。它有自己的 Service 概念,能够自动为后端的多个容器实例提供统一的访问入口和负载均衡。
  4. 无缝滚动更新
    • 当你发布新版本时,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)

工程化部署思路:

  1. 新建目录:准备一个专属的工作目录。
  2. 编写配置:新建包含 CAdvisor、InfluxDB、Grafana 三件套组合的 docker-compose.yml 编排文件。
  3. 一键启动:执行命令启动 docker-compose 文件。
  4. 状态检查:查看这三个服务容器是否已经成功挂载并启动。
  5. 页面测试:进入 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_HOSTINFLUXDB_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 的数据目录,用于获取各个容器的具体信息。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐