很多刚接手服务器的运维新手都会遇到同一个困惑:明明在本机开发环境跑得好好的程序,一到服务器上就报错,缺库、版本不对、环境变量没配、路径写死,折腾半天。Docker 要解决的就是这类问题。它不是虚拟机,也不是什么全新的操作系统,而是一种把应用和它的运行环境打包在一起、随处可运行的技术。这篇文章从原理讲到场景,先把"容器到底是什么"讲透,再讲它在服务器上有哪些真实用途。看完你应该能回答三个问题:容器和普通进程差在哪、为什么它启动这么快、什么时候该用它什么时候不该用。
需要先建立一个认知:Docker 本身并不"创造"容器技术。容器依赖的能力早就存在于 Linux 内核里,Docker 做的是把这些零散的内核能力封装成一套好用、标准化的工具链。理解这一点,很多概念就不会觉得玄乎。
一、容器到底是个什么东西
一句话概括:容器就是被内核"关起来"的一个普通进程。
当你在 Linux 上运行一个程序,内核会分配一个进程号、给它一份内存空间、让它能看到整个文件系统、能让它访问所有网卡。默认情况下,这台机器上的所有进程彼此可见、资源共享,一个进程可以给另一个进程发信号,可以窥探系统信息。这种"毫无边界"的状态对普通程序没影响,但当你把不同来源、不同用途的应用塞进同一台机器时,冲突就来了:端口争抢、依赖版本互相打架、一个程序崩溃拖垮别人。
容器做的事,就是在启动进程的那一刻,给这个进程套上几层"围栏":让它只看到属于自己的进程列表、只看到属于自己的网络、只看到属于自己的文件系统、只看到属于自己的主机名。这个进程在围栏里活得像个"小系统",但它的本质仍然是一个跑在宿主内核上的普通进程。所以容器里没有独立的操作系统内核,它和宿主机共用同一个内核。
这带来两个直接后果。第一,容器非常轻,因为它不需要启动一套完整的操作系统,省掉了内核初始化那几秒钟。第二,容器和宿主机内核强绑定,Linux 容器跑在 Linux 内核上,无法在 Linux 上直接跑一个 Windows 内核的容器。理解这两点,后面讲启动速度和兼容性就顺理成章了。
二、隔离靠什么实现:namespace 与 cgroups
很多人以为隔离是 Docker 自己做的,其实真正干活的是内核的两大机制:namespace(命名空间)负责"看得到什么",cgroups(控制组)负责"能用多少"。
namespace 是内核提供的视图隔离。Linux 支持多种命名空间,每一种隔离一类资源,容器常用的是下面六种:
| namespace | 隔离内容 | 通俗理解 |
|---|---|---|
| pid | 进程编号 | 容器里看到的 PID 从 1 开始,看不到宿主进程 |
| net | 网络设备、端口、路由表 | 每个容器有自己的网卡和端口空间 |
| mnt | 挂载点、文件系统视图 | 容器看到自己的根目录,而非宿主根目录 |
| uts | 主机名与域名 | 容器可以有自己的 hostname |
| ipc | 信号量、消息队列、共享内存 | 进程间通信资源彼此隔离 |
| user | 用户与用户组 ID 映射 | 容器内 root 可以映射成宿主普通用户 |
举个例子。你在容器里执行 ps aux,只会看到容器内的几个进程,看不到宿主机上跑了多少服务;你在容器里监听 80 端口,和你隔壁另一个同样监听 80 端口的容器完全不冲突——因为它们各自在独立的 net namespace 里,像两个平行世界。这就是为什么一台机器上能跑几十上百个容器而不打架。
光有隔离还不够。如果某个容器里的程序内存泄漏,把宿主内存吃光,整台机器都会卡死。cgroups 就是干这个的:它限制一个进程组能吃多少 CPU、多少内存、多少 IO、多少进程数。Docker 启动容器时可以传参数,把限制写进 cgroups:
# --memory 限制内存,--cpus 限制 CPU 核数,--pids-limit 限制进程数
docker run -d --name web \
--memory=512m --memory-swap=1g \
--cpus=1.5 \
--pids-limit=200 \
nginx:alpine
上面的例子把容器内存限制在 512 MiB(交换区共用 1 GiB),最多用 1.5 个 CPU 核,最多创建 200 个进程。一旦容器内存使用触顶,内核的 OOM killer 就会按 cgroup 范围杀掉容器内进程,而不是影响整台机器——这也就是后面排障时常见的"容器被 OOMKilled"。
namespace 管"看不见",cgroups 管"用不多",两者配合,才构成完整的容器隔离。
三、镜像分层与 OverlayFS
容器从哪来?答案是镜像。镜像是应用的静态模板,容器是镜像运行起来后的实例,类似"类和对象"的关系。镜像的最大特点是分层存储。
一个镜像由若干只读层叠加而成,每层记录一组文件变更。比如你构建一个镜像,第一层是 Ubuntu 基础系统,第二层是安装 Python,第三层是拷贝你的代码,第四层是设一个环境变量。每一层只记录自己相对上一层的增量。这样带来两个好处:层可以复用,多个镜像共享同一个基础层,磁盘只存一份;层可以缓存,只要某一层没变,重新构建时就不必重做。
这些层最终怎么"叠"成一个可用的文件系统?靠的是联合文件系统。早期 Docker 用 AUFS,现在默认用 OverlayFS(准确说是 Overlay2 驱动)。OverlayFS 把多个只读层(lower layer)和一个可写层(upper layer)合并成一个统一视图。你读文件时,从上层往下找,找到就用;你写文件时,内核的 copy-on-write 机制会把文件从只读层复制到可写层再改,原有只读层不动。
# 看镜像分了几层、每层多大
docker history nginx:alpine
# 查看镜像的底层存储驱动与层信息
docker image inspect nginx:alpine --format '{{.GraphDriver.Name}}'
这个"分层+写时复制"的机制解释了很多现象:为什么多个容器可以共享同一个镜像却不互相影响(每个容器的可写层是独立的);为什么容器删除后数据会丢(可写层随容器一起销毁,所以要挂数据卷持久化);为什么镜像层数越多体积越大、构建越慢(每一层都有开销)。理解了这一层,第十八篇讲的镜像瘦身就有了理论依据。
四、Docker 引擎的组件构成
很多人以为 Docker 就是一个命令。实际上,当你敲下 docker run,背后有一整条调用链在协作。现代 Docker 引擎主要由下面几个组件构成:
| 组件 | 角色 | 说明 |
|---|---|---|
| docker CLI | 客户端 | 你敲的命令,通过 REST API 和守护进程通信 |
| dockerd | 守护进程 | 负责镜像、网络、卷、容器生命周期管理 |
| containerd | 容器运行时 | 负责容器的创建、启动、停止等底层操作 |
| runc | 低层运行时 | 真正调用内核 clone、namespace、cgroups 的那个程序 |
| containerd-shim | 垫片进程 | 容器启动后驻留,负责转发信号、收集退出码 |
调用顺序大致是:docker 命令发给 dockerd,dockerd 把创建工作交给 containerd,containerd 调用 runc 真正拉起容器进程。runc 干完活就退出,进程由 containerd-shim 接管。你可能听说过 OCI(Open Container Initiative,开放容器倡议)标准,正是它定义了容器镜像格式和运行时接口,让不同厂商的容器工具可以互相兼容——这也是 Docker 之外的 containerd、CRI-O 等运行时能兴起的原因。
# 查看 dockerd 与 containerd 服务状态(systemd 系统)
systemctl status docker
systemctl status containerd
# 看当前容器的低层运行时进程树
ps -ef | grep -E 'dockerd|containerd|runc|shim'
五、镜像仓库:镜像从哪拉、往哪推
镜像是要共享的。存放和分发镜像的地方叫镜像仓库。最著名的是官方公共仓库 Docker Hub,此外还有各大云厂商提供的容器镜像服务,企业内网也常自建 Harbor 之类的私有仓库。
一个镜像的完整名字由三部分拼成:仓库地址、命名空间/镜像名、标签。不写地址默认是 Docker Hub。
# 完整格式:registry/namespace/name:tag
docker pull nginx:1.25-alpine # 从 Docker Hub 拉取
docker pull registry.example.com/team/app:v1 # 从私有仓库拉取
docker tag app:v1 registry.example.com/team/app:v1
docker push registry.example.com/team/app:v1 # 推送到私有仓库
这里要强调一点:镜像拉取走的是外网。国内服务器直连 Docker Hub 常常很慢甚至超时,所以生产环境几乎都会配置镜像加速器或在本地自建仓库缓存。这个实操留到第十二篇安装教程里细讲。
六、Docker 与 LXC 是什么关系
"容器"这个词比 Docker 早得多。在 Docker 出现之前,LXC(Linux Containers)就已经在用 namespaces 和 cgroups 提供"像虚拟机一样的轻量隔离环境"了。那 Docker 和 LXC 是什么关系?
简单说:底层原理同源,产品定位不同。LXC 更像"轻量虚拟机",它强调的是提供一个完整的、类操作系统的运行环境,要装系统、要配 init、用起来和虚拟机近似。Docker 则把重心转向了"应用打包与分发",它关注的是镜像怎么分层、怎么构建、怎么共享、怎么一行命令跑起来。Docker 早期确实借用了 LXC 作为执行后端,后来自己实现了 libcontainer,再发展成现在的 runc。
可以这样理解:LXC 是内核容器能力的"传统封装",Docker 是围绕容器能力构建的一套"应用交付工具链"。今天大家说"容器",绝大多数场景指的就是 Docker 或兼容 OCI 的这一套。
七、容器在服务器上的典型应用场景
理解了原理,来看它到底能解决什么实际问题。
第一是环境一致性。开发、测试、生产三套环境用同一个镜像,程序里跑的和服务器上跑的完全一致,消除了"我这儿能跑"的经典扯皮。镜像本身是版本化的,谁在什么时间用哪个版本,一清二楚。
第二是 CI/CD 与持续集成。构建流水线里,每一步都在干净、固定的容器里执行,构建结果可复现,不受构建机环境影响。Jenkins、GitLab CI 这类工具大量依赖容器做构建。
第三是微服务架构。一个系统拆成十几个服务,每个服务单独打包成镜像、独立部署、独立扩缩容。服务之间的依赖被镜像固定下来,升级某个服务不会牵动全部。
# 典型微服务部署:一个服务一个容器,通过自定义网络互相访问
docker network create app-net
docker run -d --name user-svc --network app-net registry.example.com/user:v1
docker run -d --name order-svc --network app-net registry.example.com/order:v1
第四是快速交付与弹性伸缩。新机器要上线,不必再手工装一堆软件,装好 Docker 后拉镜像即用,分钟级完成。业务高峰期临时扩容几台,跑起同样的容器即可。
第五是资源利用率提升。一台物理机上,用容器跑的实例密度通常远高于用虚拟机跑,因为省掉了每台虚拟机的整套操作系统开销。对于大量中小型服务,这是实实在在省成本的一环。
当然,容器不是银弹。它有明确的边界:不适合跑需要独立内核特性的负载、不适合跑与宿主内核强绑定的程序、对有状态服务的数据持久化需要额外设计(数据卷)、安全隔离强度弱于虚拟机(共享内核)。这些在后续几篇里会逐一展开。
常见问题
Q:容器里算不算一台独立的机器?
不算。它没有独立内核,只是被 namespaces 隔离、被 cgroups 限制的一个或一组进程。你在容器里看到的"系统"是宿主机文件系统的一部分加上自己的可写层拼出来的视图。
Q:为什么容器启动只要一两秒,虚拟机却要几十秒?
虚拟机要模拟硬件、加载 Guest OS 内核、跑完整的开机流程,本质是启动一套操作系统。容器只是创建几个 namespace、设置 cgroups、执行一个进程,省掉了内核启动,所以快到毫秒到秒级。
Q:删除容器后数据为什么会丢?
容器的数据默认写在镜像之上的可写层,这个可写层生命周期跟容器绑死,容器一删就没了。要持久化必须把数据放到 volume 或 bind mount 上,让数据脱离容器生命周期。
Q:容器的安全性够吗?
够用但不等于虚拟机。容器共享宿主内核,一旦内核有漏洞被突破,可能影响宿主。所以多租户、强隔离场景要么上虚拟机、要么用 gVisor/Kata 这类安全容器,配合非 root 运行、只读文件系统、最小权限等措施加固。
Q:Docker 和 Kubernetes 是一回事吗?
不是。Docker 解决"单个容器怎么跑",Kubernetes 解决"成百上千个容器怎么编排调度、怎么做高可用"。小规模用 Docker Compose 就够,服务多了再考虑 Kubernetes。
总结
容器的本质是被内核 namespaces 和 cgroups 约束起来的普通进程,没有独立内核,所以轻量、快、密度高,但也因此和宿主内核绑定,隔离强度弱于虚拟机。
隔离分两面看:namespace 决定容器"看不见什么",cgroups 决定容器"用不了多少";排障时遇到的 OOMKilled、看不到宿主机进程等现象,都能从这两者找到解释。
镜像是分层只读模板,靠 OverlayFS 这类联合文件系统叠加,写时复制让多容器共享同一镜像成为可能;容器可写层随容器销毁,因此持久化必须依赖数据卷。
Docker 引擎由 CLI、dockerd、containerd、runc 等组成,遵循 OCI 标准;镜像通过仓库分发,国内环境建议配置加速器或自建仓库。
落到选型上:需要环境一致、快速交付、微服务拆分、CI 构建、提升密度,就上容器;需要强隔离、跑到非 Linux 内核、或有复杂有状态需求,就要谨慎评估或配合虚拟机与数据卷方案。
企业QQ咨询




