很多人第一次接触 Kubernetes,通常会先看到这些东西:
Pod、Deployment、Service、Ingress、Controller、Scheduler。
再往里面走,会看到:
API Server、etcd、kubelet、kube-proxy、kube-controller-manager。
但很少有人在最开始问一个看起来很简单的问题:
Kubernetes 为什么是用 Go 写的?
这并不是一个“语言偏好”问题。
因为 Kubernetes 不是一个普通 Web 应用。
它需要长期运行在 Linux 节点上,需要处理大量网络连接,需要管理容器生命周期,需要不断监听集群状态,还需要同时完成调度、控制、重试、故障恢复和 API 服务。
换句话说:
Kubernetes 本质上是在构建一个分布式操作系统式的控制系统。
而这种系统,到底应该用什么语言实现,直接决定了它的开发成本、运行模型、并发模型、调试方式,以及最终能够形成多大规模的开源生态。
Kubernetes 最初并不是从 Go 开始的。
它最早的一个原型是用 Java 写的,后来团队决定把原型重写成 Go,并在 2014 年 6 月 6 日将 Kubernetes 的第一次提交推送到 GitHub。当时第一次提交已经包含 250 个文件,以及 47,501 行 Go、Bash 和 Markdown。
这意味着:
Go 并不是 Kubernetes 成功以后才“蹭上云原生”的。
Go 和 Kubernetes 的关系,从项目非常早期就已经建立。
那么问题就来了:
为什么?
一、真正的问题不是“选择哪门语言”,而是“如何管理一整个集群”
先把 Kubernetes 放回它出现的时代。
2010 年前后,互联网服务开始发生一个重要变化。
过去的软件通常是一台服务器运行一个或者几个应用。
服务器是固定的。
程序是固定的。
资源是固定的。
如果程序出问题,工程师登录服务器,把进程重启。
但随着虚拟化、容器以及互联网规模不断扩大,这种方式开始失效。
今天,一个大型系统可能拥有:
数百台机器。
数千个容器。
甚至更多工作负载。
而这些工作负载不是静态的。
它们会不断创建、销毁、迁移、扩容、缩容。
一台机器可能突然宕机。
一个容器可能不断崩溃。
一个服务可能突然流量暴涨。
一个节点可能突然变得不可用。
一个应用的副本数量也可能随时变化。
于是,一个新的工程问题出现了:
谁来持续管理这些动态变化?
这就是 Kubernetes 真正解决的问题。
Kubernetes 官方对自己的定义也非常直接:它是一个用于管理跨多个主机的容器化应用的平台,提供部署、维护和扩缩容等基本机制。其设计思想又大量吸收了 Google 长期运行 Borg 的经验。
因此,Kubernetes 并不是:
“一个更好用的 Docker 命令行工具。”
它实际上是在做:
集群级别的资源管理。
二、从 Borg 到 Kubernetes:系统复杂度开始进入另一个阶段
Google 在 Kubernetes 出现以前,就已经运行了大规模集群管理系统。
其中最著名的就是 Borg。
Kubernetes 的设计明显受到 Borg 的长期实践影响。官方资料明确提到,Kubernetes 建立在 Google 十多年运行 Borg 的经验基础之上,同时吸收了社区中的优秀实践。
这背后有一个很重要的技术转折。
早期系统最关心的是:
如何启动一个进程。
而集群系统开始关心:
如何持续维持一个系统的目标状态。
比如:
用户声明:
“我要运行 100 个 Pod。”
系统真正面对的问题却不是:
“创建 100 个 Pod。”
而是:
“当前到底有多少个?”
“哪些 Pod 正常?”
“哪些 Pod 消失了?”
“哪些节点不可用?”
“是不是应该重新创建?”
“是不是应该换一台机器?”
“新的状态和目标状态之间差了多少?”
这实际上已经从传统的:
命令式程序
转向:
持续运行的控制系统。
Kubernetes 后来的核心 Controller 模型,就是这种思想最典型的体现。
官方文档将 Controller 定义为持续运行的控制循环:Controller 观察集群状态,然后不断采取行动,让当前状态逐步接近用户声明的期望状态。
于是,一个典型的 Kubernetes 系统就开始变成:
用户 ↓ API Server ↓ 声明期望状态 ↓ Controller ↓ 实际资源 ↓ 持续观察 ↓ 再次调整
这不是一次函数调用。
这是一个:
永不结束的程序。
而这恰恰是 Go 擅长的场景之一。
三、传统语言当然能做,那么为什么不是 C++?
先看 C 和 C++。
如果单纯考虑性能,C/C++ 完全可以实现 Kubernetes。
实际上,Google 内部大量基础设施软件本身就使用 C/C++。
Kubernetes 团队成员也并不陌生于 C/C++。
Joe Beda 在 Kubernetes 早期文章中明确提到,团队认真考虑过 C/C++,而类似基础设施软件在 Google 也大量使用这些语言。
那么为什么没有选择它们?
因为 Kubernetes 面对的不只有“运行效率”一个问题。
更关键的问题是:
谁来维护?谁来扩展?谁来贡献?
一个云原生基础设施项目,最终会变成一个巨大的开源软件工程。
如果实现语言需要开发者长期处理:
手动内存管理。
复杂生命周期。
大量底层资源释放。
平台差异。
高度复杂的构建链。
那么它会提高贡献门槛。
对于一个企业内部项目,这可能不是致命问题。
但对于 Kubernetes 这种需要全球社区共同维护的开源项目,情况完全不同。
Kubernetes 团队当年的判断非常有代表性:
他们希望拥有一个活跃的贡献者社区,也希望标准库和扩展库能够比较容易获得;从这个角度看,C/C++ 对一些不那么熟悉底层开发的贡献者而言门槛更高。
这说明一个重要问题:
基础设施语言的选择,不只是性能选择,也是社区设计。
四、那么 Java 呢?
Java 其实比 C++ 更接近 Kubernetes。
因为 Java 有:
成熟的 GC。
完善的工具链。
强大的 IDE。
丰富的企业级生态。
类型系统。
异常处理。
线程模型。
如果只是做一个大型后端系统,Java 完全能够胜任。
而 Kubernetes 最初的原型,确实就是 Java。
但问题在于:
Kubernetes 不只是一个大型业务系统。
它还必须:
运行在不同 Linux 发行版。
运行在不同机器。
运行在各种基础设施环境。
以尽可能简单的方式进行发布和部署。
Kubernetes 团队当时特别关注跨平台安装,而 Java 的运行时依赖和安装负担让它在这一维度上不如 Go 有吸引力。
这里需要理解一个很容易被忽略的问题。
一个 Kubernetes 节点上的核心组件,比如 kubelet,本质上属于:
基础设施代理。
它不应该依赖一个非常复杂的应用运行环境才能工作。
它更希望接近:
“编译一个二进制文件,然后扔到目标机器上。”
这正是 Go 非常典型的工程优势。
五、Python 又怎么样?
Python 更简单。
开发速度甚至可能更快。
写一个原型非常舒服。
调试业务逻辑也非常直接。
但 Kubernetes 遇到的问题并不是:
“如何快速写一个原型?”
而是:
如何长期运行一个庞大的系统控制面。
Python 的动态类型,在快速开发时非常方便。
可是当系统规模扩大到:
数十万行代码。
大量 API 对象。
大量跨模块通信。
复杂并发。
复杂状态转换。
大量网络输入。
类型错误和数据模型错误的成本会越来越高。
Kubernetes 早期团队给出的理由非常明确:
Python 的动态类型特性对于系统软件构建存在挑战,而强类型能够消灭一整类错误,让开发者把精力更多放到系统本身。
注意。
这里并不是说:
动态语言不适合大型软件。
而是说:
对于 Kubernetes 这种基础设施系统,强类型带来的约束具有工程价值。
六、Go 最关键的优势其实不是“快”
很多文章讲到这里,就会得出一句:
Go 性能高,所以 Kubernetes 用 Go。
这句话不完整。
甚至可以说,它错过了最重要的部分。
Go 真正适合 Kubernetes 的原因,是一种非常罕见的平衡:
既接近系统,又没有把系统开发变得过于复杂。
Joe Beda 当时用了一个非常形象的说法:
Goldilocks。
也就是“不高不低,刚刚好”。
Go 既不是太高层,也不是太底层。
这就是 Kubernetes 语言选择真正值得研究的地方。
先看 Kubernetes 的工作性质。
它需要同时处理大量事情:
监听 API。
处理 HTTP 请求。
访问 etcd。
监听资源变化。
执行 Controller。
运行 Scheduler。
处理网络连接。
管理节点状态。
执行周期任务。
等待超时。
处理重试。
处理事件。
这些任务天然存在大量并发。
传统线程模型当然可以做。
但是线程的管理成本通常要高得多。
Go 提供的是一种不同的模型:
Goroutine。
开发者看到的是:
一个非常轻量的执行单元。
而真正的调度工作由 Go Runtime 完成。
一个最简单的并发程序,可以这样写:
package main
import (
"fmt"
"time"
)
func worker(id int) {
for i := 0; i < 3; i++ {
fmt.Printf("worker=%d task=%d\n", id, i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
for i := 0; i < 10; i++ {
go worker(i)
}
time.Sleep(1 * time.Second)
}
真正值得关注的并不是:
“go 关键字很方便。”
而是:
Kubernetes 不需要让业务开发者自己管理每一个 OS Thread。
于是大量控制循环、网络任务、后台任务,都可以建立在 goroutine 之上。
这让 Kubernetes 很自然地形成一种结构:
一个控制逻辑。
一个等待逻辑。
一个网络处理逻辑。
一个后台同步逻辑。
一个重试逻辑。
它们可以彼此独立运行。
八、Kubernetes 最核心的控制模型,天然适合并发
Controller 是 Kubernetes 最重要的设计之一。
可以把一个 Controller 想象成一个永远不会退出的循环:
观察。
判断。
执行。
等待。
再观察。
它的逻辑并不复杂。
复杂的是:
它们同时存在数百种甚至更多。
Deployment Controller。
Job Controller。
Node Controller。
EndpointSlice Controller。
Service Controller。
还有大量扩展 Controller。
Kubernetes 官方文档明确强调,Controller 是控制环;而且系统倾向于使用多个相对简单的 Controller,而不是一个巨大的单体控制循环,因为 Controller 本身可能失败,系统需要能够隔离和恢复。
这和 Go 的并发模型非常契合。
因为每一个 Controller 本质上都可以被理解为:
一个长期运行的并发任务。
于是整个系统就像:
Controller A ↕ Controller B ↕ Controller C ↕ API Server ↕ etcd
每个模块负责自己的状态。
每个模块独立运行。
通过 API 或事件进行协作。
这就是 Go 的并发原语和 Kubernetes 架构之间非常深的一层匹配。
九、第二张牌:网络编程
Kubernetes 是一个网络系统。
这一点经常被低估。
用户执行:
kubectl get pods
实际上不是直接访问 Pod。
而是:
kubectl ↓ HTTP API ↓ kube-apiserver ↓ 认证 / 授权 / Admission ↓ 数据存储 ↓ 返回响应
Kubernetes 官方明确指出,API Server 是控制平面的核心,并通过 HTTP API 对外提供 Kubernetes API;用户、集群内部不同组件以及外部组件都会通过 API Server 进行通信。
这意味着:
Kubernetes 的核心能力之一,本质上就是网络服务。
而 Go 在网络编程领域的标准库非常成熟。
例如:
net。
net/http。
context。
crypto/tls。
encoding/json。
这些能力让大型网络基础设施能够尽量少依赖外部运行环境。
早期 Kubernetes 团队也特别强调了 Go 自带的系统库,以及广泛可获得的高质量扩展库。
这非常关键。
因为 Kubernetes 不是某个单一服务器。
它是:
API Server + Scheduler + Controller + Kubelet + Proxy + CLI + 客户端生态
共同形成的一整套软件系统。
语言的标准库越强,基础设施团队自己维护的“胶水代码”就越少。
十、第三张牌:一个真正的基础设施系统,需要“不依赖复杂环境”的发布能力
很多业务开发者习惯:
安装运行时。
安装依赖。
安装框架。
安装环境。
然后启动应用。
但基础设施软件经常不是这样。
Kubernetes 的组件需要运行在:
服务器。
节点。
控制平面。
边缘环境。
不同 Linux 环境。
这时一个更简单的交付模型非常重要。
Go 最大的工程优势之一,就是可以构建出独立的原生二进制。
这并不意味着:
“Go 二进制一定最小。”
也不意味着:
“Go 程序完全不需要任何系统能力。”
但从基础设施交付角度看,它避免了用户必须先安装一个庞大语言运行环境这一类额外负担。
Kubernetes 官方项目今天的构建方式依然明显围绕 Go 展开。官方仓库明确提供本地 Go 环境构建 Kubernetes 的流程;当前 Kubernetes 主仓库的 go.mod 也声明使用 Go 1.27.0。
这也意味着:
Go 并不是 Kubernetes 历史上的一次偶然选择。
它已经成为 Kubernetes 整个工程体系的基础设施。
十一、第四张牌:垃圾回收,解决的是“工程复杂度”
说到 Go 的 GC,很多系统程序员第一反应可能是:
“基础设施为什么不用手动内存管理?”
因为 GC 并不是没有代价。
它一定会增加运行时机制。
也一定存在吞吐、延迟、CPU 和内存方面的权衡。
那么 Kubernetes 为什么仍然接受?
因为 Kubernetes 最大的问题,不是:
某一段代码能不能再快 5%。
而是:
几十万行系统代码,几千名甚至更多开发者长期维护,会不会因为资源生命周期出错而产生灾难性 Bug?
C/C++ 可以做到更细的内存控制。
但是控制能力越强,责任也越大。
Kubernetes 团队明确把 GC、类型安全等能力列为 Go 的工程优势之一。
这里真正的思想是:
用 Runtime 的一部分成本,换取系统整体复杂度的下降。
这其实是 Go 最重要的哲学之一。
Go 并没有追求:
“让程序员拥有最大的语言自由。”
它更倾向于:
把大量常见复杂度直接从工程流程中移除。
十二、第五张牌:类型系统
Kubernetes 是一个 API 驱动系统。
而 API 系统最怕什么?
数据模型混乱。
例如一个 Pod 对象可能拥有:
Spec。
Status。
Metadata。
Labels。
Annotations。
Resources。
Volumes。
Containers。
SecurityContext。
……
这些数据会在:
客户端。
API Server。
Controller。
Scheduler。
Kubelet。
各种扩展组件之间传递。
如果数据模型非常弱,那么:
“字段拼错了。”
“类型不一致。”
“状态处理错误。”
“接口调用方式错误。”
这些问题会大量出现。
强类型语言无法消灭所有 Bug。
但它可以把一部分 Bug 提前变成:
编译错误。
对于基础设施软件来说,这种错误前移非常有价值。
十三、真正让 Go 和 Kubernetes 形成化学反应的,是“并发 + 网络 + 工具链”
如果只看其中一个特点:
C++ 也能并发。
Java 也有强大的网络库。
Python 也可以快速开发。
Rust 也拥有强大的性能和安全性。
Go 并没有某个“单项能力”绝对领先。
它真正特殊的地方在于:
把几项关键能力放在了一起。
可以把它理解成:
系统级性能 + 轻量级并发 + 网络库 + GC + 静态类型 + 简单语法 + 快速编译 + 统一工具链 + 跨平台构建 + 开放的社区生态
这才是 Kubernetes 需要的组合。
早期 Kubernetes 团队也明确提到了 Go 的几个工程特征:标准库、快速构建和测试、简单的代码风格、gofmt、go vet、竞态检测、内建并发、GC 和类型安全等。
注意这里最重要的一句话:
不是因为 Go 有一个杀手级特性,而是因为它降低了整个系统的平均复杂度。
十四、Runtime 才是隐藏在 Kubernetes 背后的第二层力量
如果你真正去研究 Kubernetes,就会发现:
Kubernetes 自己并不负责所有事情。
很多执行能力都来自:
云原生基础设施的底层 Runtime。
比如:
Goroutine 调度。
内存管理。
GC。
网络 Poller。
Timer。
Syscall。
Context。
这些能力共同构成了一个高并发基础设施程序的运行基础。
可以把 Kubernetes 的执行路径抽象成:
Kubernetes Controller ↓ Goroutine ↓ Go Scheduler ↓ P / M / G ↓ System Call / Network Poller ↓ Linux Kernel
这就意味着:
Kubernetes 的很多架构能力,其实建立在 Go Runtime 提供的执行模型之上。
十五、一个 Kubernetes Controller 到底为什么需要这么多并发?
我们假设有一个 Deployment。
目标:
3 个 Pod。
当前:
2 个 Pod。
Controller 发现状态不一致。
于是它要创建第 3 个 Pod。
但是创建之后,并不意味着工作结束。
因为接下来还会发生:
等待 API Server 返回。
等待 Pod 被调度。
等待容器启动。
等待状态更新。
等待 kubelet 上报。
等待网络状态。
等待最终一致。
如果整个逻辑使用:
“同步函数调用 → 一直阻塞”
那么系统会变得非常笨重。
而 Controller 模型更接近:
事件驱动 + 异步等待 + 状态重新检查。
这与 Go 的 goroutine 非常契合。
你可以让一个 goroutine 等待一个事件。
另一个 goroutine 处理队列。
另一个 goroutine 执行定时任务。
另一个 goroutine 负责重试。
它们不需要全部绑定到独立操作系统线程。
这就是 Go 在 Kubernetes 中体现出来的架构价值。
十六、源码层面真正值得看的,不是某一个函数,而是“程序形态”
如果你打开 Kubernetes 源码,很容易掉进一个陷阱:
看到几百万行代码之后,不知道从哪里看。
对于今天的 Kubernetes 来说,这个问题尤其明显。
项目已经拥有大量分拆出来的 k8s.io/* 模块,官方 staging 目录也明确说明,这些代码通过 Go workspace 和 module replace 等机制参与主仓库构建。
真正值得学习的不是:
“某个函数第 327 行做了什么。”
而是:
Kubernetes 是如何把一个超级复杂的系统拆成大量长期运行的 Go 组件的。
例如:
API Server ↓ Handlers ↓ Authentication ↓ Authorization ↓ Admission ↓ API Storage ↓ etcd
这是一条完整的数据处理路径。
而 Controller 则更像:
Informer / Watch ↓ Event ↓ Queue ↓ Reconcile ↓ API Update
这又是另一种程序形态。
Scheduler 则是:
Pod ↓ Filter ↓ Score ↓ Select ↓ Bind
这又形成了另一种计算模型。
这些程序形态背后都有一个共同点:
它们高度依赖并发、网络、队列和长期运行的状态循环。
而 Go 恰好非常适合表达这种程序。
十七、Kubernetes 甚至反过来“教育”了 Go 工程
更有意思的是:
不是只有 Go 成就了 Kubernetes。
Kubernetes 的长期发展,也反过来推动了 Go 工程实践。
一个典型例子就是:
Go 版本升级。
Kubernetes 是一个生命周期很长的软件项目。
Go 则持续推出新版本。
二者之间天然存在一个问题:
Go 版本不断变化。
Kubernetes 又需要维持长期稳定和安全。
Kubernetes 官方曾专门介绍过这个问题:随着 Kubernetes 版本支持周期延长,旧分支与 Go 安全支持周期之间可能出现错位,因此 Kubernetes 与 Go 团队合作,改进兼容性和版本更新机制,以减少升级带来的风险。
这件事情非常有意思。
因为它说明:
当一种语言真正进入基础设施核心之后,语言升级本身也会变成系统工程问题。
也就是说:
语言已经不只是开发工具。
它已经成为平台的一部分。
十八、性能真的不重要吗?
当然不是。
Kubernetes 面对的是大规模集群。
API Server。
Scheduler。
Controller。
都需要处理大量请求和事件。
所以性能当然非常重要。
而 Go 在这里采用的是一种:
工程化性能。
不是:
“我要把某一段代码优化到极限。”
而是:
“整个系统在足够高的吞吐下仍然保持可维护。”
这两者完全不同。
Kubernetes 历史上也真实经历过性能瓶颈。
例如,早期大规模集群中,JSON 序列化和反序列化曾经成为明显的 CPU 成本来源。
Kubernetes 团队通过性能分析发现,大量时间消耗在序列化上,随后使用代码生成等方法减少反射相关成本,官方记录曾报告 JSON 编解码性能获得显著改善,同时减少了对象分配。
这里出现了一个非常值得程序员学习的结论:
Go 并不意味着“无需优化”。
恰恰相反。
Go 给你的只是一个比较高效的默认起点。
真正进入大规模系统以后:
Allocation。
GC。
Reflection。
JSON。
TCP。
Lock。
Cache。
Scheduling。
Syscall。
这些都会重新成为性能问题。
所以:
Go 是工程效率和性能之间的平衡,不是性能的免死金牌。
十九、Go 和 Rust 的差别,也可以从 Kubernetes 看出来
今天再讨论“基础设施语言”,一定会想到 Rust。
Rust 在内存安全、零成本抽象、底层控制能力等方面非常强。
那么:
为什么 Kubernetes 没有从 Go 转向 Rust?
这个问题其实不应该简单理解成:
“谁性能更好?”
真正需要比较的是:
系统复杂度、开发效率、语言约束、运行模型、生态成熟度和历史路径。
如果今天从零开始构建一个新型基础设施:
Rust 完全可能成为重要选择。
但 Kubernetes 并不是从零开始。
它已经拥有:
庞大的 Go 代码库。
成熟的 Go API。
大量客户端。
控制器框架。
测试系统。
构建系统。
贡献者生态。
以及整个云原生生态围绕 Go 形成的开发工具链。
因此语言选择存在一个巨大的:
路径依赖。
语言一旦进入基础设施的核心,就很难再轻易替换。
这也是为什么今天 Kubernetes 仍然深度依赖 Go。
二十、Go 真正改变 Kubernetes 的地方,可能不是性能
这是整篇文章最重要的一句话:
Go 对 Kubernetes 最大的贡献,可能不是让 Kubernetes 跑得更快,而是让 Kubernetes 能够被更多工程师持续构建。
想象一下:
如果 Kubernetes 使用一种非常复杂的系统语言。
可能最初的核心团队仍然能够写出来。
但是几年以后呢?
一个新贡献者想修改 Controller。
一个新团队想增加一个 API。
一个公司想开发 Kubernetes Operator。
一个工程师想调试 kubelet。
一个开发者想参与 client-go。
语言的学习成本和工具链复杂度,会直接影响生态。
而 Kubernetes 最终成长成了一个:
全球协作的软件工程。
2014 年第一次提交时,项目只有最早的一批代码;到了十周年时,Kubernetes 官方回顾已经统计到超过 88,000 名贡献者和 8,000 多家公司参与。
这时候,语言选择的含义就完全改变了。
它不再只是:
“程序员喜欢什么语言?”
而变成:
什么语言能够承载一个全球开发者社区长期维护的基础设施?
二十一、Go 为什么能成为云原生基础设施语言?
Kubernetes 并不是孤立存在的。
Docker 使用 Go。
Kubernetes 使用 Go。
大量云原生工具使用 Go。
Go 官方也明确把 Docker、Kubernetes 等列为典型云基础设施项目。
于是产生了一个非常有意思的正反馈:
Go ↓ 云基础设施项目 ↓ 开发者进入 Go ↓ 更多库和工具 ↓ 更多基础设施项目 ↓ 更多企业采用 ↓ 更大的 Go 生态
这时候语言和生态开始互相强化。
因此今天讨论:
“为什么 Kubernetes 用 Go?”
已经不能只从 Kubernetes 本身看。
还应该看到:
Kubernetes 和 Go 共同推动了云原生基础设施软件的一种工程范式。
二十二、但 Go 并不是没有代价
到这里千万不要把文章写成:
“Go 天生完美。”
它完全不是。
Go 的优势,同样对应着它的妥协。
第一,GC 会带来额外的运行时成本。
第二,虽然 Go 很适合系统服务,但它并不提供 C/C++ 那样的全部底层控制能力。
第三,与 Rust 相比,Go 的内存安全能力边界不同。
第四,极限性能场景下,开发者仍然需要理解:
逃逸分析。
Allocation。
GC。
Scheduler。
Syscall。
CPU Cache。
Network Poller。
第五,Go 的简单并不意味着系统本身简单。
实际上:
Kubernetes 非常复杂。
复杂的是系统,而不是语言。
这恰恰说明一个问题:
Go 的目标从来不是:
“让所有软件都简单。”
而更像:
不要让语言本身成为大型工程额外制造的复杂度。
二十三、Kubernetes 选择 Go,本质上选择了一种工程哲学
现在回头看,会发现 Kubernetes 选择 Go 并不是因为某一个功能。
不是因为:
“Goroutine 很酷。”
也不是因为:
“Go 性能不错。”
更不是因为:
“Google 自己造的语言,所以就用了。”
真正决定这次选择的,是一组工程取舍:
C/C++
给出了更多底层控制能力。
代价是更高的复杂度和贡献门槛。
Java
给出了成熟运行时和丰富生态。
代价是基础设施部署环境更加复杂。
Python
给出了更快的原型速度。
代价是动态类型对于大规模系统软件存在额外挑战。
Go
则选择了:
足够接近系统。
足够高效。
足够简单。
足够安全。
足够强的网络能力。
足够好的并发模型。
足够低的工程摩擦。
这就是所谓的:
Sweet Spot。
真正的“刚刚好”。
二十四、如果今天重新设计 Kubernetes,还会不会选择 Go?
这是一个很有意思的问题。
答案不会简单。
因为今天的技术世界已经和 2014 年完全不同。
现在有:
更成熟的 Rust。
更好的异步运行时。
更先进的编译器。
更强的静态分析。
更完善的内存安全工具。
所以如果今天从零设计一个全新的超大规模基础设施平台,设计者可能会认真考虑更多语言。
但是:
Kubernetes 的价值已经不再只是它使用了什么语言。
它真正建立起来的是:
API。
Controller。
Declarative Configuration。
Reconciliation。
Scheduler。
Operator。
Extension。
Cloud Native。
这些设计已经超过语言本身。
所以未来的基础设施系统可能不一定继续使用 Go。
但它们很可能继续继承:
Kubernetes 所代表的控制系统思想。
二十五、真正值得 Go 程序员学习的是“为什么”
如果你只是记住:
“Kubernetes 是 Go 写的。”
其实没有太大价值。
如果你继续追问:
为什么是 Go?
你会看到一个更深的答案:
大型基础设施软件最困难的部分,从来不是写第一版。
而是:
让它在十年以后依然能够被维护。
让新开发者可以理解。
让贡献者能够参与。
让代码能够持续重构。
让系统能够持续扩展。
让线上性能可以被分析。
让 Bug 可以被定位。
让组件能够并发运行。
让网络服务能够稳定工作。
让数千家公司能够基于它继续开发。
这时候,语言真正的价值就出现了。
语言不是为了让某一段代码写得更漂亮。
而是为了:
降低整个软件生命周期的复杂度。
二十六、回到 Go 本身:为什么它如此适合云原生?
现在可以把答案浓缩成一句话:
Kubernetes 选择 Go,并不是因为 Go 在某一个维度上绝对领先,而是因为 Go 在系统能力、并发、网络、类型安全、工具链、部署方式和工程复杂度之间找到了一个非常适合基础设施开发的平衡点。
这也是为什么 Go 后来并没有只出现在 Kubernetes 中。
它开始出现在:
容器。
服务发现。
代理。
数据库周边工具。
消息系统。
监控系统。
基础设施控制面。
云平台。
云原生工具链。
Go 官方今天依然把 Kubernetes、Docker 等视为典型的云基础设施项目。
这不是偶然。
因为:
Go 和云原生解决的是相似的问题。
云原生想解决:
如何让复杂基础设施能够自动化。
而 Go 想解决:
如何让复杂系统软件能够更高效地构建和维护。
两者在目标上天然有交集。
二十七、今天的 Kubernetes,已经反过来证明了早期语言选择
这是最有意思的一点。
2014 年,团队选择 Go 时,只能看到当时的问题。
他们不知道 Kubernetes 最终会发展到什么规模。
但十多年以后,我们再回头看:
Kubernetes 依然以 Go 为核心实现语言。
当前 Kubernetes 主仓库的 go.mod 已经声明使用 Go 1.27.0。
而 Kubernetes 的内部组件和大量独立模块仍然建立在 Go 的模块体系之上。
这说明:
一个语言选择一旦与架构模型、工程文化、社区生态结合起来,它的意义会远远超过最初的技术比较。
它最终会变成:
整个系统的组织方式。
写在最后
Kubernetes 为什么选择 Go?
表面上看,这是一个编程语言的问题。
往深处看,这是一个系统工程问题。
更往深处看,它其实是在回答:
当一个软件系统开始拥有数千台机器、数万个容器、海量网络请求和不断变化的状态之后,开发者究竟应该用什么方式管理这种复杂度?
C 可以给你控制力。
C++ 可以给你抽象能力。
Java 可以给你成熟的运行时。
Python 可以给你极高的开发速度。
Rust 可以给你强大的内存安全与底层控制能力。
而 Go 做了一件看起来并不惊艳,但影响非常深远的事情:
它试图把“足够强大”和“足够简单”放在同一套工程系统里。
这也是 Kubernetes 真正需要的东西。
因为 Kubernetes 最终面对的,从来不是某个 Pod。
不是某个 Deployment。
甚至不是某一个集群。
它面对的是:
复杂性本身。
而 Go 真正帮助 Kubernetes 解决的,也许从来不是:
“如何把程序写得更快。”
而是:
如何让越来越复杂的基础设施,依然能够被越来越多的人持续构建。
当软件系统从几十台机器走向几十万台机器时,真正稀缺的资源可能不再是 CPU。
也不只是内存。
而是:
人的理解能力。
语言、Runtime、架构和工具链最终都在做同一件事情:
把机器世界的复杂性,压缩成人类可以长期维护的工程系统。
这可能才是 Kubernetes 选择 Go 最值得我们今天重新理解的地方。
如果你对云原生基础设施的语言选型有更多思考,欢迎来云栈社区一起交流。