概述
执行 docker run hello-world 只花两秒,但这两秒里客户端、守护进程、镜像仓库三方完成了一轮完整协作。搞不清这条链路,后面遇到 Cannot connect to the Docker daemon、pull access denied 就只能靠猜。本文把 Docker 的 C/S 架构、镜像与容器的分层关系、以及 build / pull / run 三条主链路一次讲透。
纲要
- 四个核心对象
- 镜像 Image:应用 + 依赖 + 函数库 + 配置的只读文件包
- 容器 Container:镜像的运行实例 = 只读层 + 可写层 + 隔离进程
- 守护进程 dockerd:真正干活的 Server 端
- Registry:镜像的托管与分发服务器
- 镜像 vs 容器:分层结构、写时复制、为什么镜像永远只读
- C/S 架构 :docker CLI →
docker.sock→ dockerd → containerd → runc - 三条主链路 :
docker build、docker pull、docker run的数据流 - 一次
docker run hello-world背后发生的四步 - 动手命令 :
pull/images/run/ps/logs/inspect - 常见坑:权限、镜像加速器、容器随命令退出
镜像:把应用连根拔起打包
一个 MySQL 能跑起来,靠的不是 mysqld 这一个可执行文件,而是它在磁盘上的一整棵文件树:
/usr/bin/mysqld、mysql这些可执行文件/var/lib/mysql数据目录/var/log/mysql日志目录- 它依赖的
glibc、libaio、libnuma等系统函数库 - 一份
my.cnf配置
这些文件平时散落在操作系统的各个目录里,跟系统自己的库混在一起。Docker 干的事是把它们整体收拢、打包,产出一个自包含的文件包,这就是镜像。
#mermaid-svg-k97sUdXaaglOyzgb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-k97sUdXaaglOyzgb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-k97sUdXaaglOyzgb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-k97sUdXaaglOyzgb .error-icon{fill:#552222;}#mermaid-svg-k97sUdXaaglOyzgb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-k97sUdXaaglOyzgb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-k97sUdXaaglOyzgb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-k97sUdXaaglOyzgb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-k97sUdXaaglOyzgb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-k97sUdXaaglOyzgb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-k97sUdXaaglOyzgb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-k97sUdXaaglOyzgb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-k97sUdXaaglOyzgb .marker.cross{stroke:#333333;}#mermaid-svg-k97sUdXaaglOyzgb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-k97sUdXaaglOyzgb p{margin:0;}#mermaid-svg-k97sUdXaaglOyzgb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-k97sUdXaaglOyzgb .cluster-label text{fill:#333;}#mermaid-svg-k97sUdXaaglOyzgb .cluster-label span{color:#333;}#mermaid-svg-k97sUdXaaglOyzgb .cluster-label span p{background-color:transparent;}#mermaid-svg-k97sUdXaaglOyzgb .label text,#mermaid-svg-k97sUdXaaglOyzgb span{fill:#333;color:#333;}#mermaid-svg-k97sUdXaaglOyzgb .node rect,#mermaid-svg-k97sUdXaaglOyzgb .node circle,#mermaid-svg-k97sUdXaaglOyzgb .node ellipse,#mermaid-svg-k97sUdXaaglOyzgb .node polygon,#mermaid-svg-k97sUdXaaglOyzgb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-k97sUdXaaglOyzgb .rough-node .label text,#mermaid-svg-k97sUdXaaglOyzgb .node .label text,#mermaid-svg-k97sUdXaaglOyzgb .image-shape .label,#mermaid-svg-k97sUdXaaglOyzgb .icon-shape .label{text-anchor:middle;}#mermaid-svg-k97sUdXaaglOyzgb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-k97sUdXaaglOyzgb .rough-node .label,#mermaid-svg-k97sUdXaaglOyzgb .node .label,#mermaid-svg-k97sUdXaaglOyzgb .image-shape .label,#mermaid-svg-k97sUdXaaglOyzgb .icon-shape .label{text-align:center;}#mermaid-svg-k97sUdXaaglOyzgb .node.clickable{cursor:pointer;}#mermaid-svg-k97sUdXaaglOyzgb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-k97sUdXaaglOyzgb .arrowheadPath{fill:#333333;}#mermaid-svg-k97sUdXaaglOyzgb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-k97sUdXaaglOyzgb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-k97sUdXaaglOyzgb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k97sUdXaaglOyzgb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-k97sUdXaaglOyzgb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k97sUdXaaglOyzgb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-k97sUdXaaglOyzgb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-k97sUdXaaglOyzgb .cluster text{fill:#333;}#mermaid-svg-k97sUdXaaglOyzgb .cluster span{color:#333;}#mermaid-svg-k97sUdXaaglOyzgb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-k97sUdXaaglOyzgb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-k97sUdXaaglOyzgb rect.text{fill:none;stroke-width:0;}#mermaid-svg-k97sUdXaaglOyzgb .icon-shape,#mermaid-svg-k97sUdXaaglOyzgb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k97sUdXaaglOyzgb .icon-shape p,#mermaid-svg-k97sUdXaaglOyzgb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-k97sUdXaaglOyzgb .icon-shape .label rect,#mermaid-svg-k97sUdXaaglOyzgb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k97sUdXaaglOyzgb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-k97sUdXaaglOyzgb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-k97sUdXaaglOyzgb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MySQL 镜像
可执行文件
mysqld / mysql
系统函数库
glibc / libaio / libnuma
依赖与运行时
JDK / Python 等
配置文件
my.cnf
环境变量与元数据
CMD / EXPOSE / ENV
只读文件包 = 镜像
镜像落地之后就是硬盘上的文件,不是进程,也不占内存。这一点后面跟容器对比时是关键。
镜像是分层的
镜像不是一个大 tar 包,而是由一层层只读层(layer)堆叠起来的。Dockerfile 里每一条 FROM / RUN / COPY 大致对应一层:
#mermaid-svg-cePkPfyeXteVIuqj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cePkPfyeXteVIuqj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cePkPfyeXteVIuqj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cePkPfyeXteVIuqj .error-icon{fill:#552222;}#mermaid-svg-cePkPfyeXteVIuqj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cePkPfyeXteVIuqj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cePkPfyeXteVIuqj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cePkPfyeXteVIuqj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cePkPfyeXteVIuqj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cePkPfyeXteVIuqj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cePkPfyeXteVIuqj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cePkPfyeXteVIuqj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cePkPfyeXteVIuqj .marker.cross{stroke:#333333;}#mermaid-svg-cePkPfyeXteVIuqj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cePkPfyeXteVIuqj p{margin:0;}#mermaid-svg-cePkPfyeXteVIuqj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cePkPfyeXteVIuqj .cluster-label text{fill:#333;}#mermaid-svg-cePkPfyeXteVIuqj .cluster-label span{color:#333;}#mermaid-svg-cePkPfyeXteVIuqj .cluster-label span p{background-color:transparent;}#mermaid-svg-cePkPfyeXteVIuqj .label text,#mermaid-svg-cePkPfyeXteVIuqj span{fill:#333;color:#333;}#mermaid-svg-cePkPfyeXteVIuqj .node rect,#mermaid-svg-cePkPfyeXteVIuqj .node circle,#mermaid-svg-cePkPfyeXteVIuqj .node ellipse,#mermaid-svg-cePkPfyeXteVIuqj .node polygon,#mermaid-svg-cePkPfyeXteVIuqj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cePkPfyeXteVIuqj .rough-node .label text,#mermaid-svg-cePkPfyeXteVIuqj .node .label text,#mermaid-svg-cePkPfyeXteVIuqj .image-shape .label,#mermaid-svg-cePkPfyeXteVIuqj .icon-shape .label{text-anchor:middle;}#mermaid-svg-cePkPfyeXteVIuqj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cePkPfyeXteVIuqj .rough-node .label,#mermaid-svg-cePkPfyeXteVIuqj .node .label,#mermaid-svg-cePkPfyeXteVIuqj .image-shape .label,#mermaid-svg-cePkPfyeXteVIuqj .icon-shape .label{text-align:center;}#mermaid-svg-cePkPfyeXteVIuqj .node.clickable{cursor:pointer;}#mermaid-svg-cePkPfyeXteVIuqj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cePkPfyeXteVIuqj .arrowheadPath{fill:#333333;}#mermaid-svg-cePkPfyeXteVIuqj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cePkPfyeXteVIuqj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cePkPfyeXteVIuqj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cePkPfyeXteVIuqj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cePkPfyeXteVIuqj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cePkPfyeXteVIuqj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cePkPfyeXteVIuqj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cePkPfyeXteVIuqj .cluster text{fill:#333;}#mermaid-svg-cePkPfyeXteVIuqj .cluster span{color:#333;}#mermaid-svg-cePkPfyeXteVIuqj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cePkPfyeXteVIuqj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cePkPfyeXteVIuqj rect.text{fill:none;stroke-width:0;}#mermaid-svg-cePkPfyeXteVIuqj .icon-shape,#mermaid-svg-cePkPfyeXteVIuqj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cePkPfyeXteVIuqj .icon-shape p,#mermaid-svg-cePkPfyeXteVIuqj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cePkPfyeXteVIuqj .icon-shape .label rect,#mermaid-svg-cePkPfyeXteVIuqj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cePkPfyeXteVIuqj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cePkPfyeXteVIuqj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cePkPfyeXteVIuqj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第 4 层:COPY app.jar /tmp/app.jar(只读)
第 3 层:RUN apt-get install -y openjdk-8-jdk(只读)
第 2 层:RUN apt-get update(只读)
第 1 层:FROM ubuntu:16.04(只读)
容器可写层 Container Layer(读写)
merged 统一视图
容器看到的完整文件系统
分层的收益很实在:多个镜像如果共用同一个 ubuntu:16.04 基础层,磁盘上只存一份;docker pull 时已存在的层直接跳过,这就是为什么第二次拉一个"差不多"的镜像会快很多。
底层靠联合文件系统实现,现代 Docker 默认用 overlay2:
bash
# 看一个镜像到底分了几层
docker image inspect mysql:5.7 --format '{{json .RootFS.Layers}}' | python3 -m json.tool
# 看 overlay2 的挂载细节,lowerdir 就是那些只读层
mount | grep overlay
# overlay on /var/lib/docker/overlay2/xxx/merged type overlay \
# (rw,lowerdir=...,upperdir=...,workdir=...)
upperdir 就是容器自己的可写层,merged 是容器进程实际看到的目录。
容器:镜像跑起来之后多了点什么
镜像里的程序被加载进内存运行、形成进程,这个进程加上它的隔离环境,就是容器。
所以容器 = 镜像的运行实例。一个镜像可以启动多次,形成多个互相隔离的容器------这也是后面起集群的基础。
#mermaid-svg-3Y0ZFbEbsASYTeL0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .error-icon{fill:#552222;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .marker.cross{stroke:#333333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 p{margin:0;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .cluster-label text{fill:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .cluster-label span{color:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .cluster-label span p{background-color:transparent;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .label text,#mermaid-svg-3Y0ZFbEbsASYTeL0 span{fill:#333;color:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .node rect,#mermaid-svg-3Y0ZFbEbsASYTeL0 .node circle,#mermaid-svg-3Y0ZFbEbsASYTeL0 .node ellipse,#mermaid-svg-3Y0ZFbEbsASYTeL0 .node polygon,#mermaid-svg-3Y0ZFbEbsASYTeL0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .rough-node .label text,#mermaid-svg-3Y0ZFbEbsASYTeL0 .node .label text,#mermaid-svg-3Y0ZFbEbsASYTeL0 .image-shape .label,#mermaid-svg-3Y0ZFbEbsASYTeL0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .rough-node .label,#mermaid-svg-3Y0ZFbEbsASYTeL0 .node .label,#mermaid-svg-3Y0ZFbEbsASYTeL0 .image-shape .label,#mermaid-svg-3Y0ZFbEbsASYTeL0 .icon-shape .label{text-align:center;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .node.clickable{cursor:pointer;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .arrowheadPath{fill:#333333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3Y0ZFbEbsASYTeL0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3Y0ZFbEbsASYTeL0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3Y0ZFbEbsASYTeL0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .cluster text{fill:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .cluster span{color:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3Y0ZFbEbsASYTeL0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .icon-shape,#mermaid-svg-3Y0ZFbEbsASYTeL0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .icon-shape p,#mermaid-svg-3Y0ZFbEbsASYTeL0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .icon-shape .label rect,#mermaid-svg-3Y0ZFbEbsASYTeL0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3Y0ZFbEbsASYTeL0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3Y0ZFbEbsASYTeL0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3Y0ZFbEbsASYTeL0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 镜像 Image
只读分层
容器 1
独立可写层 + 独立命名空间
容器 2
独立可写层 + 独立命名空间
容器 3
独立可写层 + 独立命名空间
互不可见
各自以为自己是唯一进程
为什么必须给容器加一层可写层
MySQL 容器启动后要往 /var/lib/mysql 写数据。如果直接写进镜像,等于把镜像污染了------这个镜像再分发出去,别人拉到的是一个带脏数据的 MySQL。更糟的是,多个容器同时写同一份镜像文件,直接乱套。
Docker 的做法是写时复制(Copy-on-Write):容器要改某个文件时,把该文件从只读层复制到自己的可写层再改。读的时候能穿透到只读层,写只会落在自己这层。
这就带来一个必须记住的结论:容器删掉,可写层里的数据也一起没了。想持久化必须用数据卷或绑定挂载:
bash
# 错误示范:数据落在容器可写层,容器一删数据就没
docker run -d --name mysql-bad -e MYSQL_ROOT_PASSWORD=123 mysql:5.7
# 正确做法:挂数据卷
docker run -d --name mysql-ok \
-e MYSQL_ROOT_PASSWORD=123 \
-v mysql-data:/var/lib/mysql \
mysql:5.7
隔离是怎么做到的
容器不是虚拟机,没有模拟硬件、没有独立的 Guest 内核。它靠的是 Linux 内核的两组机制:
| 机制 | 作用 | 具体隔离项 |
|---|---|---|
| Namespace | 让进程"看不见"别人 | pid 进程树、net 网络栈、mnt 挂载点、uts 主机名、ipc 信号量、user 用户 |
| Cgroups | 限制进程"能用多少" | CPU 配额、内存上限、磁盘 I/O、进程数 |
Namespace 负责隔离视野,Cgroups 负责隔离资源。容器内的 1 号进程以为自己是整机唯一进程,其实是宿主机上一棵被 pid namespace 遮住的普通进程树。宿主机上 ps aux 依然能看到它------这也是排查容器问题时的一个抓手。
镜像与容器的完整对比
| 维度 | 镜像 Image | 容器 Container |
|---|---|---|
| 本质 | 静态的只读文件包 | 动态的运行中进程 |
| 状态 | 无状态,不随运行变化 | 有状态,可写层随运行变化 |
| 读写 | 全程只读 | 只读层 + 顶部一个可写层 |
| 存在位置 | 磁盘 /var/lib/docker/image、overlay2 |
内存中的进程 + 磁盘上的可写层 |
| 数量关系 | 一个镜像可启动 N 个容器 | 一个容器只能由一个镜像创建 |
| 生命周期 | 无 | created → running → paused → stopped → deleted |
| 创建 | docker build / docker pull |
docker run / docker create + docker start |
| 查看 | docker images |
docker ps -a |
| 删除 | docker rmi |
docker rm |
| 类比 | 类 Class、可执行程序文件 | 对象 Object、进程 |
一句话记忆:镜像是模具,容器是铸件。模具不会变,铸件可以有很多个,砸了铸件模具还在。
Registry:镜像的分发中心
镜像打包得再好,别人拿不到也没意义。托管、分发镜像的服务器统称为 Docker Registry,Docker Hub 是其中最知名的一个,地位相当于代码托管里的 GitHub。
| Registry | 地址 | 特点 |
|---|---|---|
| Docker Hub | docker.io |
官方默认,镜像最全,国内直连不稳定 |
| 阿里云 ACR | <id>.mirror.aliyuncs.com |
需登录控制台获取专属加速地址 |
| 网易云 | hub-mirror.c.163.com |
公开镜像服务 |
| 私有 Registry / Harbor | 自建域名 | 企业内部镜像,不对外暴露 |
公司内部的业务镜像不能推到公网,就得自建私有仓库。Docker 官方的 registry 镜像能起一个最简私有仓库,生产环境一般用 Harbor(带权限、漏洞扫描、复制策略)。
bash
# 起一个最简私有仓库
docker run -d -p 5000:5000 --restart=always --name registry registry:2
# 打标签时必须带上仓库地址,否则默认推到 Docker Hub
docker tag my-app:1.0 192.168.10.10:5000/my-app:1.0
docker push 192.168.10.10:5000/my-app:1.0
docker tag 这步经常被忘。镜像名结构是 [registry地址]/[命名空间]/[仓库名]:[标签],不写 registry 地址默认就是 docker.io,不写标签默认 latest。
国内拉取 Docker Hub 慢或者超时,配加速器:
bash
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://<你的专属地址>.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
# 确认生效
docker info | grep -A 3 "Registry Mirrors"
这里有个坑:公开加速器地址这两年关停了一大批,网上搜到的地址很可能是失效的。阿里云的加速器要在控制台拿到自己账号的专属地址才稳,别直接抄别人的。
Docker 的 C/S 架构
Docker 是典型的 C/S 程序,装完 Docker 之后机器上跑着一个长驻的守护进程,你敲的每条 docker 命令其实都是发给它的请求。
#mermaid-svg-kRfAkUGz7a1mXXnN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kRfAkUGz7a1mXXnN .error-icon{fill:#552222;}#mermaid-svg-kRfAkUGz7a1mXXnN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kRfAkUGz7a1mXXnN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kRfAkUGz7a1mXXnN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kRfAkUGz7a1mXXnN .marker.cross{stroke:#333333;}#mermaid-svg-kRfAkUGz7a1mXXnN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kRfAkUGz7a1mXXnN p{margin:0;}#mermaid-svg-kRfAkUGz7a1mXXnN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN .cluster-label text{fill:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN .cluster-label span{color:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN .cluster-label span p{background-color:transparent;}#mermaid-svg-kRfAkUGz7a1mXXnN .label text,#mermaid-svg-kRfAkUGz7a1mXXnN span{fill:#333;color:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN .node rect,#mermaid-svg-kRfAkUGz7a1mXXnN .node circle,#mermaid-svg-kRfAkUGz7a1mXXnN .node ellipse,#mermaid-svg-kRfAkUGz7a1mXXnN .node polygon,#mermaid-svg-kRfAkUGz7a1mXXnN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kRfAkUGz7a1mXXnN .rough-node .label text,#mermaid-svg-kRfAkUGz7a1mXXnN .node .label text,#mermaid-svg-kRfAkUGz7a1mXXnN .image-shape .label,#mermaid-svg-kRfAkUGz7a1mXXnN .icon-shape .label{text-anchor:middle;}#mermaid-svg-kRfAkUGz7a1mXXnN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kRfAkUGz7a1mXXnN .rough-node .label,#mermaid-svg-kRfAkUGz7a1mXXnN .node .label,#mermaid-svg-kRfAkUGz7a1mXXnN .image-shape .label,#mermaid-svg-kRfAkUGz7a1mXXnN .icon-shape .label{text-align:center;}#mermaid-svg-kRfAkUGz7a1mXXnN .node.clickable{cursor:pointer;}#mermaid-svg-kRfAkUGz7a1mXXnN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kRfAkUGz7a1mXXnN .arrowheadPath{fill:#333333;}#mermaid-svg-kRfAkUGz7a1mXXnN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kRfAkUGz7a1mXXnN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kRfAkUGz7a1mXXnN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kRfAkUGz7a1mXXnN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kRfAkUGz7a1mXXnN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kRfAkUGz7a1mXXnN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kRfAkUGz7a1mXXnN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kRfAkUGz7a1mXXnN .cluster text{fill:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN .cluster span{color:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kRfAkUGz7a1mXXnN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kRfAkUGz7a1mXXnN rect.text{fill:none;stroke-width:0;}#mermaid-svg-kRfAkUGz7a1mXXnN .icon-shape,#mermaid-svg-kRfAkUGz7a1mXXnN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kRfAkUGz7a1mXXnN .icon-shape p,#mermaid-svg-kRfAkUGz7a1mXXnN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kRfAkUGz7a1mXXnN .icon-shape .label rect,#mermaid-svg-kRfAkUGz7a1mXXnN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kRfAkUGz7a1mXXnN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kRfAkUGz7a1mXXnN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kRfAkUGz7a1mXXnN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务端 Server ------ Docker 主机
客户端 Client
/var/run/docker.sock
Unix Socket
docker CLI
命令行
REST API
HTTP 请求
Portainer / IDEA Docker 插件
dockerd 守护进程
接收指令 / 管理镜像与容器
本地镜像库
/var/lib/docker/image
数据卷
volumes
网络
bridge / host / overlay
containerd
容器生命周期
runc
创建 namespace + cgroups
容器进程 1
容器进程 2
Registry 镜像仓库
Docker Hub / 阿里云 / 私有 Harbor
几个容易混淆的点:
-
客户端不只是 CLI 。你用 IDEA 的 Docker 插件、用 Portainer、用
curl --unix-socket /var/run/docker.sock http://localhost/containers/json,走的都是同一个 REST 接口。CLI 只是这层 API 的官方包装。 -
默认走 Unix Socket,不走网络端口 。
unix:///var/run/docker.sock意味着只有本机、且属于docker组的用户能操作。这也是Got permission denied while trying to connect to the Docker daemon socket的来源------把当前用户加进docker组即可:bashsudo usermod -aG docker $USER newgrp docker # 让组权限在当前 shell 立即生效,否则要重新登录 -
别随便开 2375 。
tcp://0.0.0.0:2375是无认证的远程管理端口,一旦暴露到公网,等于把宿主机 root 权限送出去(容器能挂载宿主机根目录)。要远程管理就走 2376 + TLS 证书。 -
dockerd 与容器是父子关系吗 ?现代 Docker 里容器进程由
containerd-shim托管,不是 dockerd 的直接子进程。所以重启 dockerd 不会把正在运行的容器全部杀掉------这是 1.11 之后引入 containerd/runc 拆分的核心收益之一。
bash
# 看客户端和服务端各自的版本,两者版本可以不一致
docker version
# 看服务端全貌:存储驱动、Cgroup 版本、镜像数、容器数
docker info | head -30
# 直接调 REST API,和 docker ps 等价
curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json | head -c 500
三条主链路:build / pull / run
所有 Docker 操作最终都归到这三条链路上。
#mermaid-svg-h1DNe2OIgVxaX6Jz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h1DNe2OIgVxaX6Jz .error-icon{fill:#552222;}#mermaid-svg-h1DNe2OIgVxaX6Jz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h1DNe2OIgVxaX6Jz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .marker.cross{stroke:#333333;}#mermaid-svg-h1DNe2OIgVxaX6Jz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h1DNe2OIgVxaX6Jz p{margin:0;}#mermaid-svg-h1DNe2OIgVxaX6Jz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .cluster-label text{fill:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .cluster-label span{color:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .cluster-label span p{background-color:transparent;}#mermaid-svg-h1DNe2OIgVxaX6Jz .label text,#mermaid-svg-h1DNe2OIgVxaX6Jz span{fill:#333;color:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .node rect,#mermaid-svg-h1DNe2OIgVxaX6Jz .node circle,#mermaid-svg-h1DNe2OIgVxaX6Jz .node ellipse,#mermaid-svg-h1DNe2OIgVxaX6Jz .node polygon,#mermaid-svg-h1DNe2OIgVxaX6Jz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .rough-node .label text,#mermaid-svg-h1DNe2OIgVxaX6Jz .node .label text,#mermaid-svg-h1DNe2OIgVxaX6Jz .image-shape .label,#mermaid-svg-h1DNe2OIgVxaX6Jz .icon-shape .label{text-anchor:middle;}#mermaid-svg-h1DNe2OIgVxaX6Jz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .rough-node .label,#mermaid-svg-h1DNe2OIgVxaX6Jz .node .label,#mermaid-svg-h1DNe2OIgVxaX6Jz .image-shape .label,#mermaid-svg-h1DNe2OIgVxaX6Jz .icon-shape .label{text-align:center;}#mermaid-svg-h1DNe2OIgVxaX6Jz .node.clickable{cursor:pointer;}#mermaid-svg-h1DNe2OIgVxaX6Jz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .arrowheadPath{fill:#333333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h1DNe2OIgVxaX6Jz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h1DNe2OIgVxaX6Jz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h1DNe2OIgVxaX6Jz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-h1DNe2OIgVxaX6Jz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .cluster text{fill:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz .cluster span{color:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h1DNe2OIgVxaX6Jz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h1DNe2OIgVxaX6Jz rect.text{fill:none;stroke-width:0;}#mermaid-svg-h1DNe2OIgVxaX6Jz .icon-shape,#mermaid-svg-h1DNe2OIgVxaX6Jz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h1DNe2OIgVxaX6Jz .icon-shape p,#mermaid-svg-h1DNe2OIgVxaX6Jz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-h1DNe2OIgVxaX6Jz .icon-shape .label rect,#mermaid-svg-h1DNe2OIgVxaX6Jz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h1DNe2OIgVxaX6Jz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-h1DNe2OIgVxaX6Jz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-h1DNe2OIgVxaX6Jz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户指令
有
没有
没有
有
docker build -t my-app:1.0 .
docker pull mysql:5.7
docker run -d -p 3306:3306 mysql:5.7
dockerd 读 Dockerfile
逐条执行指令生成分层
本地镜像库
docker push
可推送到 Registry
本地已有该镜像?
直接返回,不联网
dockerd 向 Registry
请求 manifest 与分层
逐层下载,跳过已存在的层
本地已有该镜像?
先自动 pull
创建容器
分配可写层 + namespace + cgroups
执行镜像 CMD/ENTRYPOINT
容器进入 running
docker build
docker build 读 Dockerfile,按指令逐条执行,每条生成一层,最后打上标签存进本地镜像库。构建上下文(那个 .)会被打包发给 dockerd,所以别在含有大文件的目录下执行------用 .dockerignore 挡掉。
bash
docker build -t docker-demo:1.0 .
docker images docker-demo
像 Redis、MySQL、Nginx 这类通用组件,直接用官方镜像就行,没必要自己构建。自己构建的场景只有两类:自己的微服务,或者需要在官方镜像基础上做深度定制(预装字体、改时区、加agent)。
docker pull
从 Registry 拉镜像到本地。注意它和 docker run 的关系:run 时发现本地没镜像会自动触发一次 pull,所以很多人从来没手动敲过 pull,但这步一直存在。
bash
docker pull mysql:5.7
# 5.7: Pulling from library/mysql
# a10c77af2613: Already exists <- 已存在的层直接跳过
# ...
# Status: Downloaded newer image for mysql:5.7
docker run
docker run = docker create + docker start,一次完成"创建容器并启动"。它做的事:找镜像 → 建可写层 → 建 namespace/cgroups → 挂网络与数据卷 → 执行 ENTRYPOINT/CMD。
bash
docker run -d --name mysql-demo -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123 \
-v mysql-data:/var/lib/mysql \
mysql:5.7
docker run hello-world 背后发生了什么
官方文档把这个例子留在安装步骤里不是没道理------它的输出本身就是一份架构说明。
bash
docker run hello-world
text
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:d37ada95d47e...
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client and sent it
to your terminal.
拆成时序看更清楚:
容器进程 Docker Hub dockerd 守护进程 docker CLI 用户终端 容器进程 Docker Hub dockerd 守护进程 docker CLI 用户终端 #mermaid-svg-AzA2k1frI8UmgtWC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AzA2k1frI8UmgtWC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AzA2k1frI8UmgtWC .error-icon{fill:#552222;}#mermaid-svg-AzA2k1frI8UmgtWC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AzA2k1frI8UmgtWC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AzA2k1frI8UmgtWC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AzA2k1frI8UmgtWC .marker.cross{stroke:#333333;}#mermaid-svg-AzA2k1frI8UmgtWC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AzA2k1frI8UmgtWC p{margin:0;}#mermaid-svg-AzA2k1frI8UmgtWC .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AzA2k1frI8UmgtWC text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AzA2k1frI8UmgtWC .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AzA2k1frI8UmgtWC .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AzA2k1frI8UmgtWC .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AzA2k1frI8UmgtWC .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AzA2k1frI8UmgtWC #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AzA2k1frI8UmgtWC .sequenceNumber{fill:white;}#mermaid-svg-AzA2k1frI8UmgtWC #sequencenumber{fill:#333;}#mermaid-svg-AzA2k1frI8UmgtWC #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AzA2k1frI8UmgtWC .messageText{fill:#333;stroke:none;}#mermaid-svg-AzA2k1frI8UmgtWC .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AzA2k1frI8UmgtWC .labelText,#mermaid-svg-AzA2k1frI8UmgtWC .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AzA2k1frI8UmgtWC .loopText,#mermaid-svg-AzA2k1frI8UmgtWC .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AzA2k1frI8UmgtWC .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AzA2k1frI8UmgtWC .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AzA2k1frI8UmgtWC .noteText,#mermaid-svg-AzA2k1frI8UmgtWC .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AzA2k1frI8UmgtWC .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AzA2k1frI8UmgtWC .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AzA2k1frI8UmgtWC .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AzA2k1frI8UmgtWC .actorPopupMenu{position:absolute;}#mermaid-svg-AzA2k1frI8UmgtWC .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AzA2k1frI8UmgtWC .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AzA2k1frI8UmgtWC .actor-man circle,#mermaid-svg-AzA2k1frI8UmgtWC line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AzA2k1frI8UmgtWC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker run hello-world 经 docker.sock 发送 REST 请求 查本地镜像库,hello-world:latest 不存在 拉取 manifest + 分层 返回镜像层数据 解压落盘,存入 /var/lib/docker 创建可写层、namespace、cgroups,启动进程 执行 /hello,打印文本后退出 标准输出 流式回传输出 打印到终端
四步对应关系:
| 步骤 | 动作 | 涉及的架构组件 |
|---|---|---|
| 1 | 客户端联系守护进程 | CLI → docker.sock → dockerd |
| 2 | 守护进程从 Docker Hub 拉镜像 | dockerd → Registry → 本地镜像库 |
| 3 | 用镜像创建容器并执行 | dockerd → containerd → runc → 容器进程 |
| 4 | 守护进程把输出回传 | 容器 stdout → dockerd → CLI → 终端 |
注意这个容器跑完就退出了 。hello-world 镜像的入口程序只打印一段文本,进程结束容器就进入 Exited 状态。这不是故障,是容器的生命周期规则:容器内没有前台常驻进程,容器就会退出 。很多人第一次跑 docker run -d centos 然后 docker ps 发现什么都没有,就是这个原因,不是 Docker 没启动成功。
bash
# 默认只看 running 的容器,所以看不到已退出的 hello-world
docker ps
# 加 -a 才能看到全部状态
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 3f2b1c9a7e10 hello-world "/hello" 10 seconds ago Exited (0) 8 seconds ago hopeful_curie
# 退出后的输出依然能捞回来
docker logs 3f2b1c9a7e10
想让它别退出,得给一个前台阻塞命令:
bash
docker run -d --name centos-keep centos:7 tail -f /dev/null
动手:一条完整链路的命令
bash
# 1. 确认服务端在跑
docker version
docker info | grep -E "Server Version|Storage Driver|Cgroup Version"
# 2. 拉镜像
docker pull nginx:1.25
# 3. 看本地镜像
docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# nginx 1.25 a8758716bb6a 2 weeks ago 187MB
# 4. 起容器
docker run -d --name my-nginx -p 8080:80 nginx:1.25
# 5. 看运行中的容器
docker ps
# CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
# 7d3c9f1a2b45 nginx:1.25 "/docker-entrypoint...." Up 5 seconds 0.0.0.0:8080->80/tcp my-nginx
# 6. 看容器日志,-f 实时跟随
docker logs my-nginx
docker logs -f --tail 100 my-nginx
# 7. 进容器内部看看文件系统分层
docker exec -it my-nginx bash
# root@7d3c9f1a2b45:/# ls /
# root@7d3c9f1a2b45:/# cat /etc/os-release
# 8. 看容器详细信息:挂载、网络、状态
docker inspect my-nginx
# 9. 停 / 删
docker stop my-nginx
docker rm my-nginx
# 容器删了镜像还在
docker images
Docker 在磁盘上的落点,看完这个树就知道 docker rmi 和 docker rm 分别动了什么:
text
/var/lib/docker/
├── image/
│ └── overlay2/
│ ├── imagedb/ # 镜像元数据,config 里存着分层摘要与默认 CMD
│ └── layerdb/ # 层的元数据与父子关系
├── overlay2/ # 实际的层内容,lowerdir 指向这里
│ ├── <layer-id>/
│ │ ├── diff/ # 该层相对下层的增量文件
│ │ └── merged/ # 容器视角的统一视图
├── containers/ # 每个容器的配置与日志,docker inspect 的数据源
│ └── <container-id>/
│ ├── config.v2.json
│ └── <container-id>-json.log
├── volumes/ # 数据卷,容器删了这里还在
└── network/
└── files/
└── local-kv.db # 网络配置
所以 docker rm 删的是 containers/ 下的目录和容器那层可写层;docker rmi 删的是 image/ 的元数据,层的实际内容要等没有容器引用了才会被回收。
命令速览
| 命令 | 作用 | 对应 REST API |
|---|---|---|
docker version |
查看客户端与服务端版本 | GET /version |
docker info |
服务端全局信息:存储驱动、镜像数、容器数 | GET /info |
docker pull <image> |
从 Registry 拉取镜像 | POST /images/create |
docker images |
列出本地镜像 | GET /images/json |
docker build -t <tag> . |
依据 Dockerfile 构建镜像 | POST /build |
docker tag <src> <dst> |
给镜像打标签,推送前必做 | POST /images/{name}/tag |
docker push <image> |
推送镜像到 Registry | POST /images/{name}/push |
docker run [opt] <image> |
创建并启动容器 | POST /containers/create + POST /containers/{id}/start |
docker ps [-a] |
列出容器,-a 含已退出 |
GET /containers/json |
docker logs [-f] <id> |
查看容器日志 | GET /containers/{id}/logs |
docker exec -it <id> bash |
在运行中容器里执行命令 | POST /containers/{id}/exec |
docker inspect <id> |
查看镜像/容器完整配置 | GET /containers/{id}/json |
docker stop <id> |
停止容器,发 SIGTERM 后 SIGKILL | POST /containers/{id}/stop |
docker rm <id> |
删除容器 | DELETE /containers/{id} |
docker rmi <image> |
删除镜像 | DELETE /images/{name} |
官方文档
- Docker architecture
- Docker daemon (dockerd) 配置
- Docker Engine API
- 镜像分层与存储驱动 overlay2
- Docker Hub
- 自建私有仓库 registry
总结
- 镜像是只读的分层文件包,容器是它跑起来的实例加一层可写层;写操作只会落在容器自己的层,镜像永不被污染,因此可以放心分发。
- 一个镜像对应 N 个容器,容器之间靠 Namespace 隔离视野、Cgroups 限制资源,共用宿主机内核------这是它比虚拟机轻的根本原因。
- Docker 是 C/S 架构:CLI 只是 REST API 的一层包装,真正干活的是 dockerd,容器由 containerd/runc 创建,所以重启 dockerd 不会连带杀掉容器。
- 三条主链路里,
pull会被run隐式触发,build只用于自己的应用或深度定制,通用组件直接用官方镜像。 docker ps看不到容器,八成是它跑完就退出了,加-a看状态、docker logs看输出,别急着重装 Docker。