Java 项目部署之 Docker工具快速入门: Docker 架构拆解:镜像、容器、守护进程与 Registry

概述

执行 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 组即可:

    bash 复制代码
    sudo 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}

官方文档

总结

  • 镜像是只读的分层文件包,容器是它跑起来的实例加一层可写层;写操作只会落在容器自己的层,镜像永不被污染,因此可以放心分发。
  • 一个镜像对应 N 个容器,容器之间靠 Namespace 隔离视野、Cgroups 限制资源,共用宿主机内核------这是它比虚拟机轻的根本原因。
  • Docker 是 C/S 架构:CLI 只是 REST API 的一层包装,真正干活的是 dockerd,容器由 containerd/runc 创建,所以重启 dockerd 不会连带杀掉容器。
  • 三条主链路里,pull 会被 run 隐式触发,build 只用于自己的应用或深度定制,通用组件直接用官方镜像。
  • docker ps 看不到容器,八成是它跑完就退出了,加 -a 看状态、docker logs 看输出,别急着重装 Docker。
相关推荐
栗子~~1 小时前
SpringCloud Gateway 基于 Nacos 实现动态路由
java·spring cloud·gateway
FYKJ_20101 小时前
django个性化新闻推荐35173-计算机课程设计、毕业设计
java·spring boot·后端·python·架构·django·课程设计
java_logo1 小时前
Docker 部署 OpenViking:轻松搭建 AI Agent 上下文数据库平台
数据库·人工智能·docker·agent·火山引擎·openviking·轩辕镜像
RoboWizard1 小时前
搭配W790芯片组的超频服务器内存(架构选型)FAQ合集
运维·服务器·架构
计算机毕设定制辅导-无忧学长1 小时前
《校园食堂在线订餐小程序的设计与实现》
java·vue.js·spring boot·微信小程序·uniapp·校园食堂在线订餐小程序
a5986521 小时前
Docker云手机搭建挂机环境,外网远程访问完整实操
docker·容器·智能手机·云手机
夜之眷属1 小时前
服务启动即崩:InetAddress.getLocalHost() 抛 UnknownHostException 排查记
java·运维·后端·docker
IT研究室2 小时前
最新计算机毕业设计选题推荐-基于spring boot的食品安全信息管理系统-网站-文档指导-Java-springboot
java·spring boot·课程设计
Sayai2 小时前
MCP Server 开发实战:Spring AI 把后端日志查询暴露给 AI Agent(7 条设计原则与踩坑实录)
java·人工智能·ai agent·spring ai·mcp