
简介这份Word文档围绕Docker容器技术与微服务解决方案展开定位为云计算、运维及架构方向的技术梳理资料适合希望理解容器技术原理及其在微服务、DevOps实践中作用的开发者与架构师。文档结构完整共设前言、容器技术简介、Docker简介、Docker的组成、Docker的实现、生态圈与总结等章节从容器技术历史讲起梳理chroot、cgroups、namespaces等关键基础并系统讲解Docker Hub、Engine、Dockerfile等核心组件解释镜像构建、分发与容器运行机制。同时用“集装箱与码头工人”的比喻说明Docker如何降低容器使用门槛并分析其对微服务、DevOps落地的推动作用。资源为单个docx文档容量约323KB内容详实、逻辑清晰适合作为学习笔记或内部培训材料。目前已有223人学习建议先通读建立整体框架再结合实际环境深入学习。1. Docker容器技术与微服务这份文档值得实操前先读一遍微服务架构拆到第几十个服务的时候部署问题会比业务问题更早找上门环境不一致、依赖冲突、扩容要重装系统。Docker 容器技术能把这些痛点压到最小而这份《Docker容器技术与微服务解决方案》的文档正好把容器技术背后的原理链路讲清楚了——namespaces 怎么隔离进程、cgroups 怎么限制资源、AUFS 怎么把镜像分层、Dockerfile 怎么写。它没有给你一套现成的命令行教程而是把 Docker 之所以能跑起来的底层逻辑讲透了。对正在做微服务拆分、又想搞明白容器不是什么黑匣子的后端和运维来说这份文档是一份挺好的理论底稿读完之后再去碰 docker run你才知道每个参数到底在约束什么。2. namespaces与cgroups容器隔离和资源限制是怎么落地的很多人上手 Docker 之后会把容器当成“轻量虚拟机”这个类比省事但会在排查问题的时候坑自己。文档里反复强调一个事实容器并没有虚拟硬件容器和容器内的进程都直接跑在宿主 Linux 内核上。既然没有 Hypervisor 那层隔离那靠什么让容器看起来像一台独立的机器靠的就是 Linux 内核的 namespaces 和 cgroups 两个机制。前者负责“看不见”后者负责“抢不过”。2.1 五大namespace隔离PID、网络、挂载点、IPC、主机名进程隔离这事最容易被忽略的是 pid namespace。文档里列了几个特征我实际用下来感触最深的是三条每个 namespace 都有自己的 pid1 初始进程容器里的 /proc 只能看到自己 namespace 里的进程父 namespace 能看到子 namespace 的进程但看到的 pid 不一样。这直接导致一个问题——你在容器里执行ps -ef看到的进程列表和宿主机上不一样不是命令出了问题而是 /proc 挂载的就是当前 namespace 的进程视图。排查问题时如果忘了这层很容易把“容器里没这个进程”误判成“进程丢了”。net namespace 是另一个大头。每个容器有自己的 lo loopback 接口还有一个通常叫 eth0 的网络接口容器通过它跟宿主机和别的容器通信。文档里说得很具体eth0 会被分配一个 172.17.0.XXX 的 IP 地址容器之间可以拿这个 IP 互相通信。而在宿主机上这个容器的网卡名字是个类似 vethdfb7 的古怪名字它会跟 docker0 网卡桥接在一起。我一般在排查“容器之间 ping 不通”这类问题时第一件事就是上宿主机看ip addr确认 veth 接口是不是挂在了 docker0 上而不是进容器里瞎试。mnt namespace 处理的是挂载点隔离不同容器可以拥有不同的挂载文件系统和 root 目录在一个 namespace 里挂载的文件系统只有同一 namespace 的进程能看见。uts namespace 隔离 hostnameipc namespace 隔离管道这类 Unix IPC 资源。文档里说得很直白有了这几层隔离一个容器就能对外展现出独立计算机的能力。这几层加在一起才是“容器像一台小机器”的真正原因。2.2 cgroups 资源限制CPU 份额、内存上限和块 IO 的边界namespaces 只解决“隔离”不解决“争抢”。一个进程可以通过吃满 CPU 或内存把同宿主上其他容器的资源挤垮。文档里这句话值得反复读不同 namespace 之间的资源是相互竞争的所以要靠 cgroups 来约束。cgroups 全称 Control Groups2003 年由 Google 工程师实现2007 年加入 Linux 内核从内核 2.6.4 开始包含它负责限制进程对 CPU、内存、块存储和网络的使用。Docker 对资源的所有限制参数最终都是落到 cgroups 上。实际使用中这几个参数最常用我整理了一个简单的对照资源类型Docker 参数说明内存-m 512m、--memory-swap限制容器最大内存和 swap超出会被 OOM KillCPU 份额--cpu-shares老版本可用-c相对值默认 1024影响容器能拿到的 CPU 时间片比例CPU 绑核--cpuset 0,1指定容器内进程跑在第几块 CPU 上块 IO新版 Docker 走 blkio文档提到 Docker 1.3 时代尚未支持 IO 限制现在是支持的这里有个最容易误解的点--cpu-shares是相对权重不是绝对值。你只启动一个容器时这个值没有任何意义因为整个宿主机的 CPU 都是它的但当两个容器抢 CPU 时一个默认 1024、另一个设成 512前者大约能拿到 2/3 的计算能力后者拿 1/3。而且-c对 CPU 的运行频率没有任何影响它只影响时间片的分配比例。--cpuset则是直接把容器进程绑到某一个核心上比如docker run -i -t --cpuset 0 ubuntu:14.04就是让容器跑在第一个 CPU 核心上。做性能压测时我一般会把中间件容器绑核避免上下文切换干扰数据。另外 cgroups 的接口是一个伪文件系统它实际存在于内存中但映射到目录里用户通过往目录里写文件来操作 cgroups——这个机制在排查“明明限了内存却不管用”的时候会很有用直接去看/sys/fs/cgroup下对应容器的目录比猜 Docker 参数快得多。2.3 验证隔离与限制是否生效用 inspect 和 stats 说话环境变量和参数写归写是否生效要验证。我常用的做法是两条命令组合docker inspect看配置docker stats看实时数据。# 查看容器的 OOM 是否触发过、IP 地址、内存限制、CPU 权重 docker inspect order-service | jq .[0].State.OOMKilled, .[0].NetworkSettings.IPAddress, .[0].HostConfig.Memory, .[0].HostConfig.CpuShares # 实时观察 CPU 和内存占用--no-stream 表示只取一次快照 docker stats --no-stream --format table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}} order-service第一段里的jq按字段过滤把 JSON 输出里最关键的几个值提取出来OOMKilled 是布尔值如果容器曾经因为内存超限被杀过这字段就是 true。第二段docker stats看的是 cgroups 实际统计的数据CPU 百分比和内存使用量都来自内核统计不是 Docker 猜的。这两条命令配合能确认你写的参数到底有没有进到容器的运行配置里。如果docker inspect显示 Memory 是 0那说明-m没生效最常见的翻车原因是命令里参数写在了镜像名后面导致它被当成了容器启动命令的参数。3. 镜像分层的实现AUFS 读写层、Dockerfile 指令与 copy-on-write容器能不能秒级启动、镜像能不能跨机器搬靠的是镜像分层机制。文档里专门画了一张图来解释 AUFSAnother Union File System的层叠结构最底层是 bootfs包含 Linux 内核往上是某个发行版的文件层比如 Debian再往上是 emacs、Apache 这些应用层最顶上是容器运行时可读写的层。下面几层在容器运行时都是只读的上面一层可以读下面层里的文件。这个结构的意义比表面看起来大得多。3.1 AUFS 的层叠模型节省磁盘、内存和部署时间AUFS 的核心能力是能把两个目录结构“合二为一”。Docker 镜像就是由多个层组成的最上层可读写下面所有层只读。这个设计带来的第一个好处是节省磁盘空间不同的容器镜像之间可以共享相同的层。举个最常见的例子两个不同的 Java 应用镜像都基于 Ubuntu 14.04 和 JDK7在同一台宿主机上这两个镜像会复用同一个 Ubuntu 层和同一个 JDK 层不需要各存一份。第二个好处是节省内存。Linux 为了加快磁盘访问会把磁盘文件加载到内存里磁盘占用少了间接省了内存。第三个好处是加快部署速度相同的基础层不用重复下载。这对微服务落地特别关键——你几十个服务都基于同一个基础镜像时第一次拉取慢后面每个服务只传自己的业务层几 MB 到几十 MB几秒钟就能拉完。文档还提了一个细节最新的 Docker 用 BTRFS 等替代了 AUFS但实现的功能相同。现在的 Docker 默认用 overlay2思路还是一样用“目录融合”能力把各层叠起来对最上层可写、对下面的层保持只读保护。真正妙的是“允许对上层的任意改动”这个能力。可读写层可以修改下面只读层里的文件但这其实是 copy-on-write——你改的是拷贝不是原文件。所以无论你怎么折腾下面的层都是安全的。这个机制是容器能随意docker commit、能随意装软件又不怕毁掉镜像底层的底气。3.2 Dockerfile 指令逐条拆解构建一个可复现的微服务镜像文档里给了一个 Dockerfile 的例子我把它稍微扩展了一下变成一个更贴近微服务场景的版本# 基础镜像已经装好 Java8 的官方镜像 FROM dockerfile/java:oracle-java8 # 文档时期用 MAINTAINER现在建议改用 LABEL 声明维护信息 LABEL maintainerdevopsexample.com version1.2.0 # 把本地构建产物 device.jar 放进镜像根目录 ADD device.jar /device.jar # 暴露 8080 端口供宿主机或其它容器访问 EXPOSE 8080 # 设置容器默认启动命令java -jar device.jar ENTRYPOINT java -jar /device.jar # 设置时区和 JVM 内存参数这类环境变量 ENV TZAsia/Shanghai JAVA_OPTS-Xms256m -Xmx512m逐条说FROM是指明基于哪个基础镜像它是每一层的基础。MAINTAINER是文档时代的写法新版 Docker 会提示用LABEL替代。ADD把一个文件或目录添加到镜像里前面是源、后面是目标源文件必须用相对路径这个坑我踩过用绝对路径直接构建报错。EXPOSE是指定容器对外暴露的端口它配合-P会把端口映射到宿主机随机端口上配合-p 8080:8080则做指定映射。需要说明的是EXPOSE只是声明真正让外部可访问必须靠-p或-P。ENTRYPOINT指定容器启动时执行的命令如果有多进程需求文档给的方案是通过 Supervisor 来管理。ENV用来设置环境变量TZ 和 JVM 参数这类运行时配置放这里比塞进启动命令更清晰。另一个需要分清的是ENTRYPOINT和CMD。文档里提到 CMD 用于在构建过程中执行命令但它和 ENTRYPOINT 有微妙区别CMD 的内容可以被 docker run 后面的命令覆盖ENTRYPOINT 不行。如果两者共存CMD 会作为 ENTRYPOINT 的默认参数。实际写 Dockerfile 时我一般把固定不变的启动命令放 ENTRYPOINT把可覆盖的默认参数放 CMD这样微服务在不同环境启动时可以通过 docker run 追加参数调整配置。3.3 用 history 和 diff 看分层理解构建过程的黑匣子镜像不是一层黑盒它每一层都能被检查。docker history就是干这个的# 查看镜像每一层是怎么生成的大小多少 docker history order-service:1.2.0 # 对比容器和镜像的差异看运行中改了什么文件 docker diff order-servicedocker history输出里每一行对应一个层能看到哪一层是 ADD、哪一层是 RUN、哪一层只是元数据变更。docker diff则显示容器运行时相对于镜像改了哪些文件输出中 A 表示新增、C 表示修改、D 表示删除。这两个命令配合起来排查“镜像为什么这么大”特别好用——经常是某个 RUN 命令把临时文件留在了层里没有清理导致每个层都带着一份垃圾。镜像体积膨胀的问题靠这两条命令一眼就能定位到具体是哪一层背锅。4. Docker 核心命令与参数从 run 到 inspect 的微服务落地用法文档里列了一组常见命令这些不是摆设微服务部署每一天都会用到。这一章不是命令字典而是挑出在真实部署里最容易出错的几个参数组合讲清楚它们之间的配合关系。4.1 核心命令与关键参数对照命令关键参数 / 用途常见坑run-d后台运行、-t分配伪终端、-i保持 stdin 打开参数放镜像名后面会被当作容器命令pull下载镜像默认从 Docker Hub 拉网络不通时先查 DNS 和镜像加速地址start/stop启动 / 停止一个已存在的容器stop 是优雅停机kill 才是强杀rm删除容器先 stop 再 rm运行中会报错rmi删除镜像有容器引用时删不掉需要先清容器commit把容器修改提交成新镜像用于分发变更时别用它替代 Dockerfilelogs查看容器控制台输出容器日志量大时记得加--tail和--sincebuild从 Dockerfile 构建镜像注意上下文目录.dockerignore要配好inspect查看容器 / 镜像的完整 JSON 配置字段太多用-f或管道配合过滤images查看宿主机上的所有镜像-a会显示中间层镜像这些命令里run是出场率最高的也最容易出问题。单独说。4.2 一次完整的微服务容器启动端口、资源和网络一起配好以下是我部署一个订单服务时的习惯写法参数都加注释说明docker run -d \ --name order-service \ -p 8080:8080 \ -m 512m \ --memory-swap 768m \ --cpu-shares 1024 \ --cpuset 0,1 \ --restartalways \ registry.example.com/order-service:1.2.0-d让容器在后台运行避免一关终端就死掉--name给容器起一个固定名字后续start/stop/logs都用它-p 8080:8080将宿主机 8080 端口映射到容器 8080这是微服务对外提供 API 的关键通道-P则是随机映射EXPOSE声明的端口适合测试环境图省事生产环境我不用。-m 512m把容器内存限制到 512MB--memory-swap 768m表示最多可用 768MB含 swap注意 swap 配太高会让容器实际可用内存超出预期引起性能幻觉。--cpu-shares 1024是相对权重配合--cpuset 0,1把容器绑在两个核心上适合对延迟敏感的服务。--restartalways让 Docker 守护进程宕掉时自动拉起容器这是微服务高可用的最后一层兜底不加的话宿主机一重启服务就永远起不来了。这套参数组合背后的逻辑是先通过-p解决“外面能不能访问”再通过-m和--cpu-shares解决“会不会挤垮别人”最后用--restart解决“挂了怎么办”。微服务实例多了之后每个实例的资源上限必须提前定好否则一个流量高峰就能把整台宿主机拖垮。文档讲 cgroups 时说得很清楚不限制资源容器之间就是竞争关系加了限制才是可控的关系。4.3 本地多服务编排我一般会用 docker compose文档里提到的生态圈包含容器编排管理Kubernetes、Mesos 解决的是集群层面的调度。但在单机开发和微服务联调阶段我一般会先上 Docker Compose它是 Docker 官方配套的单机编排工具适合本地把多个容器一次性拉起。下面是一个简单的编排文件模拟一个 API 服务和一个 Redis 的微服务依赖场景version: 3.8 services: api: build: ./api ports: - 8080:8080 depends_on: - redis environment: - REDIS_HOSTredis redis: image: redis:7-alpine ports: - 6379:6379services下每个键是一个独立容器build指定从当前目录的 Dockerfile 构建ports是端口映射depends_on控制启动顺序环境变量里REDIS_HOSTredis让 API 服务通过服务名访问 Redis 容器——Compose 会自动创建内部网络容器之间用服务名就能互通这里不涉及宿主机端口冲突。启动命令很简单docker compose up -d日志统一查看用docker compose logs -f。这套东西虽然不是文档里的原始内容但属于 Docker 落地微服务最自然的一步从单容器 run 走向多容器编排先拿 Compose 练手再上 Kubernetes路径比较顺。5. 避坑指南Docker 部署微服务的五个常见问题与排查Docker 用顺手了会觉得很顺畅但中间那些坑基本是固定的。这一章把我在微服务部署中反复踩过的五个问题写透每条按现象、原因、解决三个步骤来给你一个可以直接抄的排查路径。5.1 docker 网络不通容器之间 ping 不通现象两个容器在同一台宿主机上A 容器 ping B 容器的 IP不通或者端口一直连接失败。原因容器默认网络模式是桥接容器网卡通过 veth 接口桥接到 docker0 网卡。文档里写过容器 eth0 的 IP 是 172.17.0.XXX如果你是自定义网络网段可能是 172.18、172.19。最常见的翻车点是docker compose 或 docker run 没指定同一个网络容器被分配到了不同的 bridge 网络里它们之间默认是不互通的。解决先docker inspect 容器名 | jq .[0].NetworkSettings.Networks看两个容器各自的网络名确认是否在同一个网络下。不在的话创建一个自定义 bridge 网络然后把容器加入进来docker network create app-net运行时加--network app-net。自定义网络比默认 bridge 多一个好处容器之间可以用容器名解析 IP不用再去 inspect 查 IP微服务之间互相调用用服务名就行IP 变化不影响调用。5.2 docker pull 镜像下载慢、经常超时现象docker pull卡在 manifest 或 layer 下载进度条不动最后报 timeout。原因Docker Hub 的镜像仓库服务地理位置不在本地跨海网络不稳定层数据下载中途断掉就会卡住。这跟容器本身无关是注册表访问的问题。解决配置 registry mirror 地址。在/etc/docker/daemon.json加入以下内容然后重启 Docker# 编辑 daemon.json cat /etc/docker/daemon.json EOF { registry-mirrors: [https://your-registry-mirror.example.com] } EOF # 重启 docker 后重新 pull systemctl restart docker需要注意registry-mirror 不是万能加速它只对 Docker Hub 官方仓库生效拉私有仓库镜像还是走原地址。镜像下载到一半失败时优先检查 daemon.json 里的 mirror 配置是否可用别急着怀疑磁盘空间。另外团队内部可以搭一套镜像仓库做中转把所有基础镜像提前同步进去这样 CI 构建时拉取速度基本秒开。5.3 Windows 上 Docker Desktop 启动失败Virtualization support not detected现象Docker Desktop 启动直接报错提示 virtualization support not detected或者启动到一半就退出日志里显示 failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinux。原因Docker Desktop 在 Windows 上依赖虚拟化技术运行 Linux 内核主要是 WSL2 或 Hyper-V。报这个错绝大多数情况是 CPU 的虚拟化功能没开或者 Windows 功能里的虚拟机平台、Windows Hypervisor 平台没启用。解决进 BIOS 把 Intel VT-x或 AMD SVM打开然后在控制面板启用“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两个 Windows 功能最后确认 WSL 默认版本是 2wsl --set-default-version 2。这套做完基本能解决。还是起不来的话打开“任务管理器 → 性能 → CPU”看虚拟化那栏是不是显示“已启用”如果显示“已禁用”那不是软件问题是硬件/固件层没开。这问题最烦人的地方在于错误信息是英文的、窗口一闪而过很多人以为是 Docker Desktop 本身坏了其实是底层系统功能没到位。5.4 容器被 OOM Kill-m 参数设小了现象容器里的微服务进程突然消失docker ps显示容器状态是 Exited退出码 137docker logs里没有 Java 异常堆栈只有进程终止的痕迹。原因退出码 137 是 1289表示进程被 SIGKILL 杀死。最常见的原因是-m内存限制设得太小容器内 Java 应用实际占用内存超过限制触发 cgroups 的 OOM Killer内核直接把进程杀掉。这跟容器内应用自己出现 OOM 异常不一样后者会有堆栈日志前者没有任何业务日志。解决先确认是不是 OOM 干的执行docker inspect 容器名 | jq .[0].State.OOMKilled返回 true 就是。然后把-m调大同时检查--memory-swap记住 swap 也算内存配额JVM 的堆内存设置要低于容器内存限制比如容器限制 512MBJVM 的-Xmx就设 256MB~384MB留出堆外内存、线程栈、元空间的空间。自从我发现 JVM 进程总是“莫名消失”之后每次上线前都会强制检查一遍容器内存限制和 JVM Xmx 之间必须留 25% 以上的余量这不算浪费叫保命空间。5.5 容器里 top 看不到全部进程pid namespace 的 /proc 陷阱现象进容器执行ps -ef或top发现进程数很少明明宿主机上跑了一大堆应用容器里根本看不见反过来在宿主机上能看到容器进程。原因这正是文档里 pid namespace 的隔离效果。容器内的 /proc 只显示当前 namespace 里的进程它看不到宿主机和其他容器的进程。父 namespace 倒能看到子 namespace 的进程但显示的 pid 不同。这不是故障是特性。解决不用解决但要分清场景。排查容器内进程时以容器内的 ps 为准排查宿主机资源争抢时以宿主机的 top 为准。别在容器里执行 kill 宿主机上的进程因为根本不存在那个 pid你会误以为“命令没生效”。如果确实需要从宿主机管理容器内进程用docker top 容器名或docker inspect查容器的主进程 pid而不是进容器里靠 ps 猜。6. 进阶验证用 docker inspect 与 docker stats 给容器做体检容器跑起来只是开始微服务集群最怕的是“服务还在但已经不正常”。单纯看docker ps只能知道进程活着不知道它是不是快被 OOM 杀掉、是不是在疯狂吃 CPU、端口映射对不对。所以我一般会做一次“体检”全部靠 Docker 原生命令完成不需要装额外的监控组件。先把 inspect 的 JSON 过滤练熟。inspect 输出的 JSON 字段非常多肉眼翻容易漏我用-f直接截取关键字段# 一次取多个关键字段是否 OOM、容器 IP、端口映射、内存限制、CPU 权重、重启次数 docker inspect -f OOM:{{.State.OOMKilled}} IP:{{.NetworkSettings.IPAddress}} Ports:{{.NetworkSettings.Ports}} Mem:{{.HostConfig.Memory}} CPU:{{.HostConfig.CpuShares}} Restart:{{.RestartCount}} order-service输出示例OOM:false IP:172.17.0.5 Ports:map[8080/tcp:[{0.0.0.0 8080}]] Mem:536870912 CPU:1024 Restart:3。看到 RestartCount 是 3就要警惕容器可能一直在崩溃重启这时候去查docker logs --tail 100 order-service看应用日志比看系统日志更直接因为容器没死只是进程被拉起过。OOMKilled 字段是体检里最重要的一个布林值它一旦是 true不管现在容器跑得多稳都说明刚才发生过内存超限被杀的事件必须去看内存曲线。再看资源使用的实时快照。docker stats默认是流式刷新加--no-stream取一次值方便做定时采集# 取当前 CPU、内存使用率注意容器能看到的 CPU 是全部核心不是绑核后的单核 docker stats --no-stream --format table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}# 持续看某个容器20 秒一次用于压测时观察 watch -n 20 docker stats --no-stream --format table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}} order-service结合第 2 章的 Cgroups 内容这些数字的来源是内核统计不是 Docker 估算的。看 stats 时要记得MemUsage 显示的是“当前占用/限制总量”如果当前占用已经接近-m限制那就是扩容或者调参的信号不用等 OOM 杀进程再来后悔。体检的最后一步是把容器绑核策略再确认一遍。文档里提过的--cpuset参数在压测场景里价值很大。如果你想确认容器确实跑在了指定的 CPU 核上不要只看 run 时的参数去 inspect 里面查 HostConfig.CpusetCpus 字段docker inspect -f {{.HostConfig.CpusetCpus}} order-service。容器内看到的 CPU 核心号是重新编号的0 对应宿主机第一个核这个映射关系在容器内看不到只有宿主机视角才能验证。做这行时间久了我养成了一个习惯每次微服务上线前都会强制对每个容器执行一遍 inspect 体检确认 OOM 标志位是 false、端口映射没写错、内存限制和 JVM 参数匹配。这套流程既不是玄学也不是仪式感是用最原始的命令验证最关键的运行时状态。容器本身藏不住问题只是问题经常藏在你不看的字段里。希望这份笔记能帮你在微服务路上少踩几个坑把更多时间留给你真正想做好的业务上。本文还有配套的精品资源点击获取