docker学习(1)Docker 与容器的核心概念
1. Docker 是如何实现“容器”的? ➜(提示:与虚拟机相比,它少了什么层,多了什么机制?)
🌊 一、为什么会有“Docker”这种东西?
想象这样一个问题:
我在电脑上写了一个程序,它能在我机器上跑;
可是我发给别人,他说“跑不起来”,因为他的系统环境不一样。
这就是所谓的 “环境不一致问题”。
比如:
- 我本地是 Ubuntu,别人是 Windows;
- 我装了 Python 3.9,他是 Python 3.12;
- 我装了某个依赖库,而他没装。
结果:
代码在我这能跑,在别人那报错。
所以工程师们希望有一种办法:
能把“程序 + 它需要的一切环境”一起打包起来,
发给别人时,别人直接运行这个包就能得到完全一样的效果。
🚢 二、Docker 就是干这个的
Docker 就像一个码头工人(docker),
它会帮你:
- 把应用装上船(打包成镜像);
- 把船放到水里开起来(启动容器);
- 每艘船(容器)互不干扰、但都能用同一个大海(宿主机内核)。
这就是“容器化”的直觉。
🧱 三、那虚拟机(VM)又是什么?
在 Docker 出现之前,人们已经用“虚拟机(Virtual Machine)”解决环境隔离的问题。
虚拟机的原理是:
- 在你的电脑上跑一个虚拟化层(Hypervisor);
- 这个层可以模拟出完整的一台电脑(包括CPU、内存、硬盘等);
- 然后在上面再装一个完整的操作系统(比如再装个Ubuntu);
- 最后在这个“虚拟操作系统”里装你的程序。
👉 所以虚拟机“层级结构”是:
物理硬件
└── 宿主机操作系统
└── 虚拟化层 (Hypervisor)
└── 虚拟机
└── 客户操作系统
└── 应用程序
🐳 四、Docker 的不同(它“少了”什么)
Docker 没有 Hypervisor,也不再虚拟出完整的“操作系统”。
它直接利用宿主机操作系统内核的机制,创建一种“隔离的运行环境”,
让每个容器都“以为”自己是独立的系统,但其实它们都在共用同一个内核。
所以 Docker 的结构:
物理硬件
└── 宿主机操作系统
└── Docker 引擎 (Docker Daemon)
└── 容器(共享同一个内核)
└── 应用程序
⚙️ 五、Docker “多了”什么机制
为了实现这种“轻量隔离”,Docker 借助了 Linux 的两大机制:
-
Namespace(命名空间)
用来做“隔离”。
每个容器看起来都有独立的进程空间、网络空间、文件系统空间。
实际上只是 Linux 内核帮它们“隔开看起来不一样”。 -
Cgroups(控制组)
用来做“资源限制”。
可以限制容器能用多少 CPU、内存、磁盘、网络带宽等。
这两者是 Linux 内核提供的能力,Docker 只是封装得更好用。
🧩 六、总结成一句话:
Docker 实现容器,是通过 Linux 内核的 namespace 和 cgroup 实现的进程隔离与资源限制。
它不像虚拟机那样重新启动一整套操作系统,而是直接在宿主机系统上“分出一块独立空间”。
2. 容器和镜像的关系是什么?启动一个容器的过程中,镜像起了什么作用?
🧩 一、基本原则
“修改容器不会改变镜像。” ✅
这是 Docker 的基本设计原则。
容器 = 镜像 + 可写层(Write Layer)
镜像是只读的(read-only),每次启动容器时,Docker 会在镜像之上叠加一层“可写层”,所有的更改都会写到这里。
这层关系可以想象成:
容器运行时:
---------------------------------
| 可写层(你的修改、日志、缓存) |
|--------------------------------|
| 只读镜像层(系统+环境+依赖) |
---------------------------------
就像一个“透明保护膜”:你在上面乱画,不会弄脏底下那张画(镜像)。
🧠 二、容器文件系统 = 联合挂载(UnionFS)
Docker 使用一种叫 Union File System(联合文件系统)的机制(常见实现是 OverlayFS)。
它做的事情其实很简单:
在多个只读层(镜像层)上面叠加一个可写层(容器层),
让你看到的是一个“统一视图”。
当你修改容器里的文件时:
- 内核不会改动镜像层;
- 它会在写层中创建一个“副本”;
- 容器再读文件时,就优先读这个副本;
- 所以你以为改了原文件,其实只是改了顶层的拷贝。
🔁 三、那容器重启会怎样?
这就关键了:
✅ 如果你是用 docker start 启动(重新启动同一个容器)
可写层还在,修改不会丢。
因为容器的文件系统(包括写层)还被保存着。
❌ 但如果你用 docker run 启动了一个新容器
那就相当于:
- 把旧容器的可写层丢掉;
- 新建一个干净的可写层;
- 所有修改、安装的软件、日志,全没了。
这也是为什么你说:
“有时要调整镜像很麻烦。”
因为临时改的环境只存在于容器写层,镜像本身没变。
要永久化,必须“固化”成新的镜像。
🧱 四、把修改固化进镜像的方式
你其实已经在无意中触碰到了两个重要方法:
🪣 方法1:直接重新构建镜像(推荐)
用 Dockerfile 明确写出环境变化,例如:
FROM python:3.10
RUN pip install pymysql
COPY ./app /app
然后重新构建:
docker build -t myapp:v2 .
→ 这样镜像层里就自带 pymysql 了,容器再也不用临时装。
🧩 方法2:从修改后的容器生成新镜像(手动快照)
如果你在容器里手动装好了环境,可以执行:
docker commit <容器ID> myapp:v2
这个命令会把当前容器的写层保存成一个新镜像层。
(就像拍个“环境快照”。)
缺点是:不记录过程,不可复现(别人看不懂你干了啥),
所以生产环境更推荐 Dockerfile。
📦 五、为什么你会说“挂载是救命稻草”
非常正确。挂载(Volume 或 Bind Mount)解决的正是:
“我要在容器外保存或修改一些东西”的问题。
它把宿主机的目录映射进容器内,比如:
docker run -v /home/app:/app myapp
- 宿主机的
/home/app变成容器里的/app - 修改代码时,无需重建镜像;
- 容器删掉,代码依然在宿主机上,不会丢。
所以:
- 镜像保存“环境”;
- 挂载保存“数据/代码”;
- 容器保存“运行时状态”。
🎯 六、你刚提到的真实场景分析
“新增数据库连接,需要 pymysql 等包。”
这正是 Docker 思维的典型分界线:
- 临时开发:可以直接在容器里
pip install pymysql - 长期部署:最好更新 Dockerfile,把
pip install pymysql写进去,再重建镜像
这就实现了环境的可复现性——
任何人、任何机器,用这个镜像,都能跑出一样的环境。
🧩 七、总结成一句话
镜像定义了“起点环境”,
容器提供了“临时可写层”,
Volume 负责“持久化”。
如果你用这三者的组合来思考问题,就能完全掌控 Docker 的运行逻辑。
3. 为什么容器能“彼此隔离”又能共享宿主机的内核?
1️⃣ 镜像 → 静态隔离(文件系统层面)
2️⃣ Namespace + Cgroups → 动态隔离(运行时层面)
你说的「跟内存虚拟化那块差不多」也很聪明——确实,Docker 的隔离思想在理念上和「虚拟内存」很像:
都是让“每个应用以为自己独占一片天地”,但底层其实是共享的。
不过,为了让你真正看懂 Docker 的“魔法”,我们现在可以更具体地看下 Namespace 和 Cgroups 各自是怎么“动手脚”的。
🧩 一、Namespace:让容器“看不见别人”
Namespace 是用来“改变进程的视野”的。
Linux 提供了很多种 Namespace,用来把系统资源“隔离视角”。
就像在宿舍里,你拉上了窗帘,就只能看到自己房间的东西。
| Namespace 类型 | 控制的内容 | 容器内看到的效果 |
|---|---|---|
PID | 进程编号 | 容器里从 PID 1 开始,看不到宿主机其他进程 |
NET | 网络接口 | 容器有自己的网卡、IP、路由表 |
IPC | 进程通信 | 容器间不能互相直接共享内存 |
MNT | 文件系统挂载 | 容器有自己的根目录(来自镜像) |
UTS | 主机名 / 域名 | 容器有独立的 hostname |
USER | 用户和权限 | 容器内可以有独立的 root 用户 |
🔹 举个例子:
在宿主机上,你能 ps aux 看到一堆进程;
但进入容器后再 ps aux,你只能看到容器自己的那几个。
→ 因为 PID Namespace 把“世界”变小了。
⚙️ 二、Cgroups:让容器“用不了太多资源”
Cgroups(Control Groups)是用来“控制进程能吃多少”的。
Namespace 是“视觉隔离”,Cgroups 是“资源限制”。
| 资源类型 | 控制内容 |
|---|---|
cpu | 容器最多能用几个核、占多少比例 |
memory | 限制最大可用内存、交换空间 |
blkio | 限制磁盘读写速率 |
pids | 限制进程数量 |
devices | 限制容器能访问哪些硬件设备 |
比如:
docker run --cpus=2 --memory=1g ubuntu
这条命令的意思是:
给容器分两核 CPU,1GB 内存。
但注意:
这并不是“真的切出两核、1GB”。
而是宿主机内核通过 Cgroups 在调度时 “人为加上限”。
一旦容器达到这个限制,内核就会暂停它或杀掉它。
🧠 三、你提到的“内存虚拟化类比”非常好
在虚拟内存里:
- 每个进程都以为自己有完整 4GB 地址空间;
- 实际上都是映射到物理内存的不同块;
- 中间有一个内存映射表在欺骗进程。
在 Docker 的世界里:
- 每个容器都以为自己有独立系统;
- 实际上都是同一个内核上不同 Namespace 的进程;
- Namespace 就像内存映射表,骗过了程序的“世界观”。
→ 这就是“轻量级虚拟化”的精髓。
🧩 四、把它们串起来看
当你执行 docker run 时,Docker 引擎大概干了这些事:
- 从镜像生成一个独立的文件系统(隔离环境);
- 通过 Namespace 给这个容器创建独立的 PID、网络、挂载等空间;
- 用 Cgroups 限制 CPU、内存、IO 等资源;
- 启动容器中的主进程(PID=1);
- 容器运行时,一切操作仍通过宿主机内核完成。
4. Docker Daemon(守护进程)在整个体系中起什么作用?CLI 命令是怎么和它交互的?
一、Docker CLI 是什么?
CLI 是 “Command Line Interface”(命令行接口)的缩写。
就是你在终端(如 Linux 的 shell、Windows 的 PowerShell 或 macOS 的 Terminal)中输入的命令,比如:
docker run nginx
docker ps
docker stop mycontainer
这些命令,统称为 Docker CLI 命令。
它其实就是一个“客户端程序”,你可以把它看作是:
“Docker 的遥控器”。
当你执行这些命令时,并不是 CLI 自己在操作容器,而是 它把请求发给另一个后台进程——Docker Daemon(守护进程)。
二、Docker Daemon(守护进程)是什么?
Docker Daemon(中文常叫 “Docker 守护进程”)是 Docker 的核心大脑,真正负责“干活”的部分。
它通常是系统启动时自动运行的后台服务,名字是:
dockerd
📌 类比一下:
- CLI 是你(命令发出者)
- Docker Daemon 是“执行你命令的工人”
- 它才是真正创建、删除、管理容器的人。
三、CLI 和 Daemon 的交互机制
当你输入命令:
docker run nginx
实际上发生了下面这一连串的过程 👇:
-
CLI 把命令打包成 API 请求,通过一个叫 Docker Engine API 的接口发送给 Docker Daemon。
- 通常是一个本地 Unix socket:
/var/run/docker.sock - 或者是远程 TCP 通信,比如:
tcp://192.168.1.10:2375
- 通常是一个本地 Unix socket:
-
Docker Daemon 收到请求后,开始执行实际操作:
- 从镜像仓库拉取
nginx镜像(若本地没有)。 - 根据镜像创建一个新的容器层(用
Namespace+Cgroups隔离)。 - 为容器分配文件系统、网络、PID、内存等资源。
- 启动容器内的主进程(比如 nginx 的进程)。
- 从镜像仓库拉取
-
执行完后,Daemon 把结果返回给 CLI。
- CLI 再把这些信息打印到你的终端上,比如容器 ID。
👉 所以整个关系大致是:
你(用户)
↓
Docker CLI(命令行客户端)
↓ 通过 Unix Socket 或 API 通信
Docker Daemon(守护进程)
↓
操作系统内核 + 容器资源
四、那 Docker Daemon 是不是类似“内核”?
这个比喻 部分正确:
| 对比点 | 内核(Kernel) | Docker Daemon |
|---|---|---|
| 运行位置 | 操作系统底层 | 用户空间(运行在操作系统之上) |
| 功能 | 直接控制硬件、调度资源 | 调用内核提供的容器接口(namespace、cgroups)来管理容器 |
| 控制对象 | 程序、进程、内存等 | 容器、镜像、网络、卷等 |
| 启动方式 | 随系统启动 | 由操作系统启动,常驻后台服务 |
可以这样理解:
内核 是控制“操作系统资源”的。
Docker Daemon 是控制“容器资源”的。
它通过内核提供的接口(namespace、cgroups 等)实现容器隔离。
五、总结一句话
Docker CLI 发命令,Docker Daemon 执行命令,内核提供支撑。
整个执行链条如下:
[你输入命令] → [Docker CLI] → [Docker Daemon] → [Linux 内核] → [容器真正启动运行]
是否希望我接着用一个实际例子(比如执行 docker run hello-world 时,CLI 与 Daemon、内核三者的完整交互过程)画成一张图给你?这样这个体系的动态流程就非常直观了。
更多推荐




所有评论(0)