把应用从开发机搬到测试机,缺个库;从测试机搬到生产机,glibc 版本对不上——“在我这儿是好的”这类扯皮,开发和运维都熟。容器就是为终结这种扯皮而生的打包方式:把应用和它依赖的整个环境一起装箱,搬到哪儿都按同一套环境跑。这篇讲清三件事:容器到底是什么、它凭什么比虚拟机轻、以及什么时候你不该用它。

先把类比摆上桌:标准集装箱

container 这个词直译就是集装箱,Docker 的 logo 也是一只驮着集装箱的鲸鱼,这个类比不是巧合。

集装箱出现之前,散货装卸靠码头工人一件件搬:桶装的、袋装的、形状不规则的,每换一种运输方式(船 → 火车 → 卡车)就得重新装卸一遍,破损和丢失是常态。标准集装箱推广开之后事情变了:箱子尺寸统一,里面的货不用管,船、火车、卡车用同一套吊具装卸。

容器对软件做的是同一件事。镜像就是那个标准箱子,里面装着应用代码、运行时、库和配置;宿主机、CI 流水线、云厂商的容器服务就是船和卡车——只要这台机器能跑容器,箱子里的应用行为就一致,不用为每个环境重新”装卸”一遍依赖。

第二个类比:别墅与公寓

这个类比解释的是”轻”从哪来。

虚拟机是独栋别墅。每户自带地基、水电、锅炉,对应一套完整的客户操作系统,各家的系统调用由自己的内核处理,互不相关;代价是每栋都占一大块地、养一整套设备。

容器是公寓。全楼共用地基、水电和锅炉,对应宿主机内核;每户有自己的门锁、房间布局和家具(文件系统、进程、网络栈),彼此看不见对方屋里的事,但管道是共用的。

共用管道带来两个直接后果:公寓盖得快、占地小,容器启动是秒级、镜像通常只有 MB 到百 MB 量级;管道出问题全楼受影响,内核漏洞会波及所有容器。后面讲适用边界,推论就是从这句出来的。

它是什么

容器是一种把应用及其全部依赖(代码、运行时、系统工具、库、配置)打包成标准镜像、并在宿主机内核上以隔离进程形式运行的轻量级软件包。

拆开这个定义里的三个关键词:

  • 打包:镜像是只读模板,容器是镜像的运行实例。同一个镜像能在一台机器上起十个容器,互不干扰。
  • 隔离:靠 Linux 内核的两个机制——命名空间(namespace)划定”能看见什么”,控制组(cgroups)限制”能用多少”。
  • 共享内核:容器不模拟硬件,也不自带内核,所有容器的系统调用都由宿主机内核处理。这是它与虚拟机的分水岭。

容器化不是新概念,操作系统级虚拟化几十年前就有了。现代容器化的真正起点是 2013 年 Docker 发布:它把这套底层能力的使用门槛大幅拉低,容器从此成为开发者和企业的主流选择。

隔离是怎么实现的

命名空间管视野,控制组管配额,两者都是 Linux 内核的既有能力。

命名空间(namespace)决定容器里能看见什么:

  • PID namespace:容器内进程从 1 开始编号,看不见宿主机上的其他进程;
  • Mount namespace:容器有自己的一套文件系统挂载点;
  • Network namespace:独立的网卡、IP、端口和路由表;
  • UTS / IPC / User namespace:主机名、进程间通信、用户 ID 映射。

控制组(cgroups)决定容器能用多少:CPU 配额、内存上限、IO 带宽、进程数上限。少了 cgroups,一个容器里的失控进程能吃光宿主机内存,把邻居一起拖死。

这两层机制不玄学,一条命令就能看见:容器里启动的 bash 是 1 号进程(PID namespace 的效果),cgroups v2 的 memory.max 接口则如实暴露着内存配额(未设限时为 max):

配图1

Docker 没有发明这两个机制,它的贡献是把它们包装成好用的工具链:统一镜像格式、可重复的构建流程、镜像仓库分发。

容器 vs 虚拟机

维度 虚拟机 容器
虚拟化层级 硬件级,Hypervisor 模拟整套设备 操作系统级,共享宿主机内核
操作系统 每个实例一套完整 guest OS 只打包应用和依赖
启动速度 分钟级,要走完整开机流程 秒级,本质是启动一个进程
镜像体积 GB 级 MB 到百 MB 量级
隔离强度 强,硬件级隔离 较弱,内核漏洞可波及全部容器
资源开销 每台各占一份内核与系统服务内存 共享内核,同规格机器能跑更多实例
跨内核能力 可以,Linux 宿主能跑 Windows 虚拟机 不行,Linux 容器需要 Linux 内核

一句话结论:虚拟机隔离更强、更重;容器启动更快、密度更高、隔离更弱。选型问题不是”哪个先进”,而是”我需要多强的隔离、能容忍多重的开销”。

怎么用起来:Docker 四步

落到操作上,Docker 的标准流程是四步:写 Dockerfile(用文本描述镜像怎么一层层构建:基础镜像、拷贝代码、装依赖、启动命令,文件本身可版本化)→ 构建镜像 → 运行容器 → 管理容器。

1
2
3
4
docker build -t myapp:1.0 .          # -t 指定镜像名和标签,. 是当前目录作为构建上下文
docker run -d -p 8080:80 myapp:1.0 # -d 后台运行,-p 把宿主机 8080 映射到容器内 80
docker ps -a # 查看容器,-a 连已退出的一起列
docker stop <容器> && docker rm <容器>

镜像构建完就不可变,这是一致性的关键:同一份镜像在开发机、CI、生产跑出来的东西一样,差别只在外部注入的配置和环境变量。

生态:单机会了,一群怎么管

  • Kubernetes:自动部署、扩展和管理容器化应用的开源平台,解决的是”几百个容器分布在几十台机器上,谁调度、挂了谁重启、流量怎么分发”。
  • Helm:Kubernetes 的包管理工具,用 Charts 描述一整套应用清单,一条命令装完。
  • OpenShift:基于 Kubernetes 的企业级平台,补齐权限、镜像仓库、CI/CD 等开箱能力。

什么时候才需要 Kubernetes?单机加 Docker Compose 管得住就别急着上,跨机器调度、自动扩缩容、故障自愈成了刚需再上。

三个已经跑熟的落地场景

微服务:单体应用难以支撑快速迭代和高可用,微服务把应用拆成多个单一职责的小服务,各自独立开发、部署和扩展,容器正好是它们的运行环境。电商把系统拆成用户、订单、库存服务,每个跑在自己的容器里,单独更新扩展,不拖累整个系统。

DevOps 与 CI/CD:开发、测试、生产跑同一个镜像,环境差异被消掉;每次代码变更经流水线自动构建镜像、跑测试、上线,手动干预和环境不一致导致的问题大幅减少。

多租户隔离:云平台里不同用户的负载共享同一组物理资源,容器靠命名空间和 cgroups 把每个客户的应用独立封装,互不干扰、资源各自独立。

适用边界:什么时候别用容器

容器不是万能解,下面几类需求它天生做不到或不划算。

  • 需要不同内核:Windows 应用、需要加载特定内核模块、需要定制内核参数的场景,容器只能用宿主机内核,做不到。这类需求上虚拟机。
  • 强隔离合规要求:金融、政务等要求租户之间硬件级隔离的场合,共享内核的隔离强度不够。要么在虚拟机里再跑容器(一层 VM 兜底),要么考虑 gVisor、Kata Containers 这类强隔离运行时方案。
  • 重状态、强 IO 的核心数据库:容器无状态,数据要靠卷(volume)挂出来。能做,但备份、故障切换、磁盘 IO 性能都要额外设计,不如装在物理机或专用虚拟机上省心。
  • 图形界面应用:容器里没有显示服务器,X11/Wayland 要额外转发,得不偿失。

反过来说,容器最擅长的是:无状态或状态外置的服务、发布频繁的应用、需要在多种环境保持一致的工作负载、批处理与 CI 任务。

还没解决的三个麻烦

  • 存储和数据管理:容器本身无状态,要持久化就得挂卷,卷的生命周期、备份、跨节点共享都得单独设计。
  • 网络配置和管理:微服务多了之后,服务间通信、服务发现、跨主机网络成了独立课题,通常靠 CNI 插件加服务网格来管。
  • 安全问题:内核级漏洞可能波及所有容器;镜像也可能带着有漏洞的依赖被拉进来,需要镜像扫描和最小化基础镜像兜底。

接下来往哪走

容器加 Serverless(只交代码或镜像,扩缩容交给平台)、边缘计算(镜像小、启动快,适合资源受限的边缘节点)、把调度决策交给机器学习的智能编排。

小结

回到开头那个集装箱类比:容器把应用和它的整个运行环境装进标准箱子,搬到哪都一致,这是它最核心的价值。

  • 容器轻的根本原因是共享宿主机内核,靠命名空间隔离视野、靠 cgroups 限制用量;虚拟机模拟整套硬件,每个实例自带一套操作系统。
  • 共享内核既是效率来源,也是隔离强度的上限:内核漏洞影响面覆盖全部容器,强隔离合规场景要么上虚拟机,要么在虚拟机里再跑容器。
  • Docker 解决”一台机器上怎么跑”,Kubernetes 解决”一群容器跨机器怎么管”。
  • 跨内核需求、重状态核心数据库、图形界面应用不适合直接容器化。

参考文献

  1. “What is a Container?”, Docker Official website. Link
  2. “Containers and Virtual Machines”, Red Hat Official website. Link
  3. “Kubernetes”, Kubernetes Official website. Link
  4. “Introduction to DevOps on AWS”, AWS Whitepapers. Link
  5. “Microservices”, Martin Fowler’s Blog. Link

本文由作者 2020-2024 年间的 CSDN 博客文章重构而来,原发布于 CSDN。