Update Docker.md

重新绘制配图,补充容器的实现原理和 Kubernetes。
This commit is contained in:
CyC2018
2026-10-26 22:03:43 +08:00
parent 51e1f47a0e
commit c4fb20bc35
7 changed files with 268 additions and 5 deletions
+88 -5
View File
@@ -6,6 +6,8 @@
* [三、优势](#三优势)
* [四、使用场景](#四使用场景)
* [五、镜像与容器](#五镜像与容器)
* [六、实现原理](#六实现原理)
* [七、Kubernetes](#七kubernetes)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
@@ -16,13 +18,13 @@
Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其它机器上。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/011f3ef6-d824-4d43-8b2c-36dab8eaaa72-1.png" width="400px"/> </div><br>
<div align="center"> <img src="pics/docker-why.png" width="740px"/> </div><br>
## 二、与虚拟机的比较
虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/be608a77-7b7f-4f8e-87cc-f2237270bf69.png" width="500"/> </div><br>
<div align="center"> <img src="pics/docker-vs-vm.png" width="760px"/> </div><br>
### 启动速度
@@ -76,7 +78,84 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/docker-filesystems-busyboxrw.png"/> </div><br>
<div align="center"> <img src="pics/docker-layers.png" width="740px"/> </div><br>
## 六、实现原理
容器就是宿主机上的普通进程,Docker 用 Linux 内核的两个机制把它和其它进程隔开:
- 命名空间(Namespace)负责隔离“能看到什么”:PID 命名空间让容器中的第一个进程以为自己是 1 号进程;网络命名空间让容器有自己的网卡、IP 和端口;挂载命名空间让容器有自己的根文件系统;UTS 命名空间让容器有自己的主机名;
- 控制组(cgroup)负责限制“能用多少”:限制 CPU、内存、磁盘 I/O 等资源。
例如用 docker run --memory 64m --cpus 0.5 启动一个容器,在容器中查看:
```sh
$ cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max
67108864
50000 100000
$ echo $$
1
```
内存上限是 64 MB,CPU 每 100 ms 最多使用 50 ms,即半个 CPU;容器里的 shell 的进程号是 1。在容器中不断申请内存,超过 64 MB 时进程被内核杀掉,退出码为 137(128 + SIGKILL 的 9)。
因为所有容器共享宿主机的内核,所以容器比虚拟机轻量,但隔离性也更弱:内核漏洞可能让容器中的进程影响宿主机。
## 七、Kubernetes
Docker 解决的是单台机器上怎样运行容器。服务有几十个、部署在几十台机器上时,还需要决定每个容器放在哪台机器上、挂了谁来重启、怎样扩容、怎样不停机升级,这就是 Kubernetes(K8s)做的事情。
K8s 的核心思想是声明期望的状态,由控制器不断把实际状态调整为期望状态。例如下面的配置声明 order-service 要运行 3 个副本,并通过一个 Service 对外提供访问:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: app
image: nginx:1.27-alpine
ports:
- containerPort: 80
readinessProbe:
httpGet: { path: /, port: 80 }
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 80
```
其中的几个概念:
- Pod:K8s 调度的最小单位,包含一个或几个共享网络和存储的容器,每个 Pod 有自己的 IP,重建后 IP 会变;
- Deployment:管理一组相同的 Pod,负责保持副本数、扩缩容和滚动升级;
- Service:为一组 Pod 提供固定的虚拟 IP 和域名,把请求负载均衡到其中就绪的 Pod 上;
- 控制平面:API Server 接收所有请求,etcd 保存集群状态,调度器决定 Pod 放在哪个节点,控制器管理器运行各种控制器;每个节点上的 kubelet 负责启动和检查本节点的容器。
在本机的单节点 K8s(k3s v1.31)上实际操作:
- kubectl apply 之后约 20 秒,3 个 Pod 全部就绪,Service 得到虚拟 IP 10.43.189.89,在集群内用 http://order-service 就能访问;
- 删除一个 Pod,Deployment 发现副本数少于 3,立即创建新的 Pod,约 1.2 秒后恢复为 3/3,新 Pod 的 IP 与原来不同,Service 自动更新了后端列表;
- kubectl scale 把副本数改为 5;kubectl set image 修改镜像版本后,Deployment 创建新的 ReplicaSet,逐个用新版本的 Pod 替换旧的,约 12 秒完成滚动升级,期间一直有就绪的 Pod 在提供服务,需要时可以用 kubectl rollout undo 回滚。
readinessProbe 是就绪探针:Pod 启动后要通过检查才会加入 Service 的后端列表,避免把请求转发给还没启动完成的实例。
## 参考资料
@@ -85,7 +164,11 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php)
- [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/)
- [理解 Docker(2):Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html)
- [为什么要使用 Docker?](https://yeasy.gitbooks.io/docker_practice/introduction/why.html)
- [为什么要使用 Docker?](https://yeasy.gitbook.io/docker_practice/introduction/why)
- [What is Docker](https://www.docker.com/what-docker)
- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)
- [Understanding the image layers | Docker Docs](https://docs.docker.com/get-started/docker-concepts/building-images/understanding-image-layers/)
- [Kubernetes 文档:Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
- [Kubernetes 文档:Service](https://kubernetes.io/docs/concepts/services-networking/service/)
- [namespaces(7) — Linux manual page](https://man7.org/linux/man-pages/man7/namespaces.7.html)
- [cgroups(7) — Linux manual page](https://man7.org/linux/man-pages/man7/cgroups.7.html)
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+49
View File
@@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" width="740" height="300" viewBox="0 0 740 300">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="370" y="28" class="title">镜像分层与容器的可写层</text>
<rect x="60" y="56" width="380" height="36" rx="6" fill="#E6E3FA" stroke="#B8B0DE"/>
<text x="250" y="78.5" class="c sub">容器的可写层:运行时的修改都写在这里</text>
<rect x="60" y="100" width="380" height="36" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="250" y="122.5" class="c sub">RUN pip install flask</text>
<rect x="60" y="144" width="380" height="36" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="250" y="166.5" class="c sub">COPY app.py /app/</text>
<rect x="60" y="188" width="380" height="36" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="250" y="210.5" class="c sub">FROM python:3.12-slim(基础镜像的多层)</text>
<text x="460" y="82" class="sub">可读写,容器删除后就没有了</text>
<path d="M452,108 q10,0 10,10 V220 q0,10 -10,10" class="line"/>
<text x="476" y="172" class="sub b" style="fill:#444">镜像的只读层</text>
<text x="476" y="190" class="sub">Dockerfile 的每条指令生成一层,</text>
<text x="476" y="206" class="sub">多个镜像共享相同的层</text>
<text x="30" y="262" class="sub">容器修改镜像中的文件时,先把文件复制到可写层再修改(写时复制),只读层不变,所以同一个镜像可以启动多个容器。</text>
<text x="30" y="280" class="sub">Dockerfile 中把变化少的指令放在前面,修改代码后重新构建时前面的层可以直接复用缓存。</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+70
View File
@@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="330" viewBox="0 0 760 330">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="380" y="28" class="title">虚拟机与容器</text>
<text x="190" y="56" class="c b">虚拟机</text>
<rect x="40" y="66" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="112.5" y="85.5" class="c sub">App A</text>
<rect x="195" y="66" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="267.5" y="85.5" class="c sub">App B</text>
<rect x="40" y="102" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="112.5" y="121.5" class="c sub">依赖库</text>
<rect x="195" y="102" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="267.5" y="121.5" class="c sub">依赖库</text>
<rect x="40" y="138" width="145" height="30" rx="6" fill="#E6E3FA" stroke="#B8B0DE"/>
<text x="112.5" y="157.5" class="c sub">客户机 OS</text>
<rect x="195" y="138" width="145" height="30" rx="6" fill="#E6E3FA" stroke="#B8B0DE"/>
<text x="267.5" y="157.5" class="c sub">客户机 OS</text>
<rect x="40" y="174" width="300" height="30" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="190" y="193.5" class="c sub">Hypervisor</text>
<rect x="40" y="210" width="300" height="30" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="190" y="229.5" class="c sub">宿主机 OS</text>
<rect x="40" y="246" width="300" height="30" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="190" y="265.5" class="c sub">服务器</text>
<text x="560" y="56" class="c b">容器</text>
<rect x="410" y="66" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="482.5" y="85.5" class="c sub">App A</text>
<rect x="565" y="66" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="637.5" y="85.5" class="c sub">App B</text>
<rect x="410" y="102" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="482.5" y="121.5" class="c sub">依赖库</text>
<rect x="565" y="102" width="145" height="30" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="637.5" y="121.5" class="c sub">依赖库</text>
<rect x="410" y="138" width="300" height="30" rx="6" fill="#C6ECE9" stroke="#7DBCB7"/>
<text x="560" y="157.5" class="c sub">容器运行时(Docker Engine)</text>
<rect x="410" y="174" width="300" height="30" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="560" y="193.5" class="c sub">宿主机 OS(共享一个内核)</text>
<rect x="410" y="210" width="300" height="30" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="560" y="229.5" class="c sub">服务器</text>
<text x="30" y="296" class="sub">虚拟机为每个应用运行一个完整的操作系统,启动要几十秒、占用 GB 级内存;容器只是宿主机上隔离的进程,</text>
<text x="30" y="314" class="sub">共享宿主机的内核,启动通常在 1 秒以内。代价是隔离性不如虚拟机,也不能在 Linux 宿主机上直接运行 Windows 容器。</text>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

+61
View File
@@ -0,0 +1,61 @@
<svg xmlns="http://www.w3.org/2000/svg" width="740" height="250" viewBox="0 0 740 250">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="370" y="28" class="title">容器:把应用和依赖打包在一起</text>
<rect x="40" y="56" width="200" height="130" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="140" y="76" class="c sub b" style="fill:#444">容器</text>
<rect x="60" y="86" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="140" y="103" class="c sub" style="fill:#444">订单服务</text>
<rect x="60" y="116" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="140" y="133" class="c sub" style="fill:#444">JDK 21</text>
<rect x="60" y="146" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="140" y="163" class="c sub" style="fill:#444">Debian</text>
<rect x="270" y="56" width="200" height="130" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="370" y="76" class="c sub b" style="fill:#444">容器</text>
<rect x="290" y="86" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="370" y="103" class="c sub" style="fill:#444">推荐服务</text>
<rect x="290" y="116" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="370" y="133" class="c sub" style="fill:#444">Python 3.12</text>
<rect x="290" y="146" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="370" y="163" class="c sub" style="fill:#444">Ubuntu</text>
<rect x="500" y="56" width="200" height="130" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="600" y="76" class="c sub b" style="fill:#444">容器</text>
<rect x="520" y="86" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="600" y="103" class="c sub" style="fill:#444">网关</text>
<rect x="520" y="116" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="600" y="133" class="c sub" style="fill:#444">静态二进制</text>
<rect x="520" y="146" width="160" height="26" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="600" y="163" class="c sub" style="fill:#444">Alpine</text>
<rect x="40" y="196" width="660" height="30" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="370" y="215.5" class="c sub b">宿主机的 Linux 内核</text>
<text x="30" y="244" class="sub">各容器的依赖互不影响,同一个镜像在开发机、测试环境和生产环境中运行结果一致。</text>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB