2013 年 3 月,一家名不见经传的 PaaS 初创公司 dotCloud 在 PyCon 上发布了一个内部工具——Docker。演示视频里,工程师 Solomon Hykes 在台上用几行命令打包了一个应用,把它传到另一台机器上,一个命令运行起来:环境完全一致,没有任何"在我机器上能跑"的问题。
那次演示被后来的开发者称为"容器革命的起点"。Docker 项目在 GitHub 上发布后,在接下来 18 个月里收获了 1.5 万个 Star——这个速度在当时前所未有。它用一种直观的体验,解决了软件分发和运行环境一致性这个困扰开发者数十年的问题。
破除误解:容器不是轻量级虚拟机
虚拟机(VM)在硬件上模拟一套完整的计算机——包括虚拟 CPU、虚拟内存、虚拟网卡,以及一个完整的操作系统内核。VM 隔离彻底,但每个 VM 通常占用几 GB 内存,启动需要数十秒。
容器共享宿主机的 Linux 内核,只隔离进程的视图——用 Linux 内核的两个核心机制实现:
- 命名空间(Namespaces):让进程看到一个独立的文件系统、网络栈、进程列表、主机名——实际上是内核提供的"视图隔离"
- 控制组(cgroups):限制进程能使用的 CPU、内存、磁盘 I/O——资源的硬性上限
结果是:容器镜像通常几十 MB,启动时间毫秒级,单台机器可以运行数百个容器。代价是:容器之间的内核隔离不如 VM 彻底——一个内核漏洞可能被容器内的恶意进程利用。
容器的史前史:Docker 没有发明容器
容器在 2013 年因 Docker 而流行,但构成容器的内核机制早在它之前几十年就已存在。Docker 的贡献是把它们包装得好用,而不是发明了它们。
- chroot(1979):Unix V7 引入的系统调用,把进程的根目录限定到某个子目录,是"隔离文件系统视图"的远祖;1982 年进入 BSD。它只换了根目录,谈不上真正隔离。
- 命名空间(Namespaces):第一个 mount namespace 随 Linux 2.4.19 内核(2002 年)登场,由 Al Viro 实现;此后逐步补齐——UTS 与 IPC(2.6.19,2006 年)、PID(2.6.24,2008 年)、network(2.6.29,2009 年),最关键的 user namespace 直到 3.8 内核(2013 年)才完整可用。
- cgroups(2006–2008):由 Google 工程师 Paul Menage 与 Rohit Seth 在 2006 年发起,最初叫"process containers",2007 年改名 control groups,2008 年 1 月随 2.6.24 内核并入主线。它源自 Google 内部对资源限制的需求。
- LXC(2008):第一个把 namespaces 与 cgroups 封装成易用工具集的项目,无需给内核打补丁。Docker 早期正是构建在 LXC 之上,后来才用自研的 libcontainer(即 runc 的前身)替换它。
也就是说,Docker 真正的贡献不是隔离技术本身,而是把它包装成一套可移植的开发者体验:镜像分层、Registry 分发、一条命令构建并运行。这正是"在我机器上能跑"问题终于有解的原因——镜像不可变,构建一次,到哪儿跑都一样。
Docker 的三个核心概念
镜像(Image):一个只读的文件系统快照,包含应用代码和所有依赖。镜像是分层的(Union FS)——每条 Dockerfile 指令增加一层,层可以被共享和复用,节省存储和传输。
容器(Container):镜像的运行实例,在镜像基础上增加一个可写层。容器停止后,可写层消失;如需持久化数据,必须挂载外部卷(Volume)。
仓库(Registry):存储和分发镜像的服务。Docker Hub 是默认公共仓库,各云厂商也提供私有仓库(Amazon ECR、Google GCR)。
Kubernetes:编排的兴起
Docker 解决了"打包"问题,但当你有数百个容器需要在几十台机器上运行,新问题来了:
- 哪个容器运行在哪台机器上?
- 一台机器宕机,上面的容器如何自动迁移?
- 如何在流量高峰时自动扩容,低谷时缩容?
- 容器之间如何互相发现和通信?
这些问题属于容器编排的范畴。Google 在 2014 年开源了 Kubernetes(希腊语"舵手"),它脱胎于 Google 内部约从 2003–2004 年起运行的 Borg 系统——管理着 Google 全球数据中心的几百万个任务。Kubernetes 1.0 于 2015 年 7 月 21 日发布,同日 Google 与 Linux 基金会宣布成立 CNCF,Kubernetes 成为其托管的第一个项目。
Kubernetes 的核心设计哲学是声明式配置:用户声明"我想要 10 个运行 nginx:1.20 的副本,其中端口 80 对外暴露",Kubernetes 负责让集群状态收敛到这个声明。用户不命令机器"现在启动一个容器",而是声明"期望状态是什么"——协调循环(Reconciliation Loop)持续把实际状态驱动向期望状态。
关键抽象
| 抽象 | 含义 |
|---|---|
| Pod | 一个或多个共享网络和存储的容器,是最小调度单元 |
| Deployment | 声明 Pod 的副本数和更新策略 |
| Service | 为一组 Pod 提供稳定的网络访问入口(DNS 名 + 负载均衡) |
| ConfigMap / Secret | 配置数据和敏感数据与应用代码分离 |
| Namespace | 在同一集群内做软隔离 |
标准化与运行时分层:OCI、runc、containerd
容器爆发后,业界担心格式碎片化,于是 2015 年 6 月 22 日,Docker、CoreOS 等公司在 Linux 基金会下成立 OCI(Open Container Initiative,开放容器倡议),统一两件事:镜像长什么样(image-spec)、运行时怎么启动容器(runtime-spec),后来又补上分发规范(distribution-spec)。Docker 把自研的运行时 runc 捐出来,作为 runtime-spec 的基石实现。
这暴露了一个常被忽略的事实:"Docker"其实是一摞分层组件,不是一个单体程序。 从上到下大致是:
- Docker、Podman 等:面向开发者的高层工具(构建镜像、命令行)
- containerd:管理镜像与容器生命周期的高层运行时;它本是 Docker 的内部组件,2017 年 Docker 把它捐给 CNCF,如今是 CNCF 毕业项目
- runc:真正调用 namespaces 和 cgroups 创建容器的低层 OCI 运行时
正因为分层,Kubernetes 早在 1.5 版就定义了 CRI(Container Runtime Interface,容器运行时接口),让自己不绑定任何单一运行时。后果是:Kubernetes 1.20 弃用、1.24 版(2022 年)彻底移除了对接 Docker 的 dockershim。所以"Kubernetes 运行 Docker"这句话今天已不准确——它直接通过 CRI 对接 containerd 或 CRI-O,而它们最终都调用 runc。你的镜像照样能跑,因为镜像格式遵循 OCI 标准。
云原生生态
Kubernetes 催生了整个云原生(Cloud Native)生态,由 CNCF(云原生计算基金会,2015 年成立)托管:
Helm:Kubernetes 的包管理器,把一组资源打包为"Chart",方便分发和重用。
Prometheus + Grafana:监控和可视化,成为云原生监控的事实标准。
Istio / Linkerd:服务网格,在 Pod 旁注入 Sidecar 代理,提供服务间 mTLS、流量管理、可观测性。
Argo CD / Flux:GitOps 工具,以 Git 仓库为声明式配置的唯一真相源,自动同步到集群。
生产环境真正难在哪里
容器在开发机上显得很简单:docker run 一条命令就能启动服务。但生产环境的问题很少停留在"能不能启动"这一层,而是在事故发生时,系统能不能被解释、恢复和审计。
第一层难点是状态。无状态 Web 服务很适合容器化,数据库、消息队列和搜索索引却不同。它们关心磁盘延迟、复制拓扑、备份恢复点和一致性窗口。Kubernetes 可以用 StatefulSet、PersistentVolume 和 Operator 管理这些系统,但这不是把状态问题消灭了,而是把它们显式写进控制平面。
第二层难点是升级。Deployment 的滚动发布看似自动,实际需要处理数据库迁移、向后兼容 API、长连接、缓存污染和灰度回滚。一个镜像能快速替换,前提是应用本身遵守可替换的工程契约。容器让发布更快,也让错误传播更快。
第三层难点是可观测性。容器生命周期短,Pod 名称会变,节点会漂移。如果日志、指标、追踪没有统一的标签体系,事故现场很快会变成碎片。Prometheus、OpenTelemetry 和集中式日志系统的意义,不只是画图,而是把一次请求从入口、服务网格、业务服务、数据库到外部 API 的路径重新拼起来。
第四层难点是供应链。容器镜像常由基础镜像、系统包、语言依赖和构建脚本叠成多层。一个漏洞可能来自应用代码,也可能来自几个月前继承的 openssl、glibc 或 npm 包。因此现代容器平台开始强调 SBOM、镜像签名、准入控制和最小基础镜像。换句话说,容器把"机器环境"变成了可分发文件,也把机器环境的安全责任带进了代码审查。
安全边界:容器不是安全沙箱
容器给人的错觉,是它像虚拟机一样提供了完整边界。实际情况更微妙:容器的边界主要由 Linux 内核维护,容器内外共享同一个内核。只要内核攻击面足够大,容器逃逸就不是理论问题。
生产加固通常从几件小事开始:不以 root 用户运行进程;丢弃不需要的 Linux capabilities;启用只读根文件系统;限制 seccomp、AppArmor 或 SELinux 策略;禁止特权容器;控制容器能否挂载宿主机路径;用 NetworkPolicy 明确东西向流量。
这些配置看起来琐碎,却决定了容器平台的真实风险。Kubernetes 的强大之处在于它能统一表达这些约束;危险也在于,如果团队没有认真写这些约束,默认开放的能力会被误认为默认安全。
代价与争议
复杂性被低估:Kubernetes 的学习曲线极陡峭。一个完整的生产 Kubernetes 集群需要理解几十个概念,配置数百个选项。Kelsey Hightower(Google 工程师,Kubernetes 最有影响力的布道者之一)在 2023 年坦言,大多数小团队不需要 Kubernetes,一台配置优良的虚拟机足够了。
资源开销:Kubernetes 的控制平面(API Server、etcd、Scheduler、Controller Manager)本身需要消耗相当资源,对中小规模集群来说比例较高。
安全基线低:Kubernetes 默认配置的安全性较差——Pod 可以访问云厂商元数据 API、容器以 root 运行、网络策略默认是全开放的。生产加固需要大量额外配置。
替代方案复苏:Nomad(HashiCorp)、AWS ECS/Fargate、托管 Lambda(Serverless)都是 Kubernetes 的替代路线,针对 Kubernetes 过于复杂的批评进行了取舍。
跨域连接
- 可观测性与监控:容器寿命短、名字会变、节点会漂移,靠主机名定位问题的老办法因此失效。必须先有统一的标签与追踪上下文,才能把一次请求从入口、网格、服务到数据库重新拼起来,否则事故现场只剩碎片。推论是:把日志与指标的标签体系留到出事后再补,基本等于没有。
- 国家能力:声明式配置的关键不是写配置文件,而是控制器能持续观测实际状态并与期望比较。只声明目标却测不到现状,收敛就无从发生。治理同理:看不见的东西无法被调控,可观测性是执行能力的前提而非附属品。推论是:控制平面的价值不在自动化本身,而在它把状态差异变成了可比较的量。
- 助推与自由家长制:默认值就是事实政策。容器默认以超级用户运行、网络策略默认全通,于是多数集群的真实风险由默认值而非文档决定。可检验推论:把安全基线做成默认配置,比写进规范有效得多。
- 制度经济学:互相竞争的厂商把镜像与运行时规范交给中立组织托管,是为了避免格式碎片化抬高各方的对接成本。收益是镜像到处能跑,代价是创新被锁在规范的演进节奏上,谁也不能单方面改格式。推论是:标准化收益越大的领域,越难出现颠覆性的替代格式。
- 忒修斯之船:滚动发布不修补实例,而是整体替换。"同一个服务"的身份因此由声明维持,不再由任何一个进程的连续存在维持——这既是可回滚的前提,也意味着写在容器内的状态默认会被丢掉。推论是:有状态服务上容器平台,难点从来不是启动,而是数据放在哪里。
参考文献
- Verma, A., Pedrosa, L., Korupolu, M., Oppenheimer, D., Tune, E., Wilkes, J. Large-scale cluster management at Google with Borg. EuroSys, 2015.(Borg 系统的原始论文,Kubernetes 的设计源头)
- Burns, B., Grant, B., Oppenheimer, D., Brewer, E., Wilkes, J. Borg, Omega, and Kubernetes. ACM Queue 14(1), 2016.(Google 三代集群管理系统的综述)
- Merkel, D. Docker: Lightweight Linux Containers for Consistent Development and Deployment. Linux Journal, 2014.
- Open Container Initiative. Image Format Specification 与 Runtime Specification. opencontainers.org, 2015 起持续维护.(容器镜像与运行时的开放标准)
- Kubernetes Documentation. Container Runtime Interface (CRI) 与 Dockershim Removal FAQ. kubernetes.io.(CRI 接口与 dockershim 移除说明)
延伸阅读
- Hightower, K., Burns, B., Beda, J. Kubernetes: Up and Running. 3rd ed., O'Reilly, 2022.
- Richardson, C. Pattern: Microservice Architecture. microservices.io.(微服务与容器的模式参考)
- Turnbull, J. The Docker Book. 自出版,2014.(早期 Docker 实践参考)